textarea]:h-auto",
+
+ // Variants based on alignment.
+ "dec:has-[>[data-align=inline-start]]:[&>input]:pl-2",
+ "dec:has-[>[data-align=inline-end]]:[&>input]:pr-2",
+ "dec:has-[>[data-align=block-start]]:h-auto dec:has-[>[data-align=block-start]]:flex-col dec:has-[>[data-align=block-start]]:[&>input]:pb-3",
+ "dec:has-[>[data-align=block-end]]:h-auto dec:has-[>[data-align=block-end]]:flex-col dec:has-[>[data-align=block-end]]:[&>input]:pt-3",
+
+ // Focus state.
+ "dec:has-[[data-slot=input-group-control]:focus-visible]:border-ring dec:has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] dec:has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
+
+ // Error state.
+ "dec:has-[[data-slot][aria-invalid=true]]:border-destructive dec:has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dec:dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
+
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+const inputGroupAddonVariants = cva(
+ "dec:flex dec:h-auto dec:cursor-text dec:items-center dec:justify-center dec:gap-2 dec:py-1.5 dec:text-sm dec:font-medium dec:text-muted-foreground dec:select-none dec:group-data-[disabled=true]/input-group:opacity-50 dec:[&>kbd]:rounded-[calc(var(--radius)-5px)] dec:[&>svg:not([class*=size-])]:size-4",
+ {
+ variants: {
+ align: {
+ "inline-start":
+ "dec:order-first dec:pl-3 dec:has-[>button]:ml-[-0.45rem] dec:has-[>kbd]:ml-[-0.35rem]",
+ "inline-end":
+ "dec:order-last dec:pr-3 dec:has-[>button]:mr-[-0.45rem] dec:has-[>kbd]:mr-[-0.35rem]",
+ "block-start":
+ "dec:order-first dec:w-full dec:justify-start dec:px-3 dec:pt-3 dec:group-has-[>input]/input-group:pt-2.5 dec:[.border-b]:pb-3",
+ "block-end":
+ "dec:order-last dec:w-full dec:justify-start dec:px-3 dec:pb-3 dec:group-has-[>input]/input-group:pb-2.5 dec:[.border-t]:pt-3",
+ },
+ },
+ defaultVariants: {
+ align: "inline-start",
+ },
+ },
+);
+
+function InputGroupAddon({
+ className,
+ align = "inline-start",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps
) {
+ return (
+ {
+ if ((e.target as HTMLElement).closest("button")) {
+ return;
+ }
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
+ }}
+ {...props}
+ />
+ );
+}
+
+const inputGroupButtonVariants = cva(
+ "dec:flex dec:items-center dec:gap-2 dec:text-sm dec:shadow-none",
+ {
+ variants: {
+ size: {
+ xs: "dec:h-6 dec:gap-1 dec:rounded-[calc(var(--radius)-5px)] dec:px-2 dec:has-[>svg]:px-2 dec:[&>svg:not([class*=size-])]:size-3.5",
+ sm: "dec:h-8 dec:gap-1.5 dec:rounded-md dec:px-2.5 dec:has-[>svg]:px-2.5",
+ "icon-xs": "dec:size-6 dec:rounded-[calc(var(--radius)-5px)] dec:p-0 dec:has-[>svg]:p-0",
+ "icon-sm": "dec:size-8 dec:p-0 dec:has-[>svg]:p-0",
+ },
+ },
+ defaultVariants: {
+ size: "xs",
+ },
+ },
+);
+
+function InputGroupButton({
+ className,
+ type = "button",
+ variant = "ghost",
+ size = "xs",
+ ...props
+}: Omit, "size"> &
+ VariantProps) {
+ return (
+
+ );
+}
+
+function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function InputGroupInput({ className, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ );
+}
+
+function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ );
+}
+
+export {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupText,
+ InputGroupInput,
+ InputGroupTextarea,
+};
diff --git a/packages/open-workflow-diagram-editor/src/components/ui/switch.tsx b/packages/open-workflow-diagram-editor/src/components/ui/switch.tsx
new file mode 100644
index 00000000..a979eb6e
--- /dev/null
+++ b/packages/open-workflow-diagram-editor/src/components/ui/switch.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021-Present The Open Workflow Specification Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as React from "react";
+import { Switch as SwitchPrimitive } from "radix-ui";
+
+import { cn } from "@/lib/utils";
+
+function Switch({
+ className,
+ size = "default",
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default";
+}) {
+ return (
+
+
+
+ );
+}
+
+export { Switch };
diff --git a/packages/open-workflow-diagram-editor/src/components/ui/textarea.tsx b/packages/open-workflow-diagram-editor/src/components/ui/textarea.tsx
new file mode 100644
index 00000000..ce505105
--- /dev/null
+++ b/packages/open-workflow-diagram-editor/src/components/ui/textarea.tsx
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021-Present The Open Workflow Specification Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ );
+}
+
+export { Textarea };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ec33d039..2f8731ca 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,6 +6,9 @@ settings:
catalogs:
default:
+ '@base-ui/react':
+ specifier: ^1.7.0
+ version: 1.7.0
'@changesets/changelog-github':
specifier: ^0.7.0
version: 0.7.0
@@ -255,6 +258,9 @@ importers:
packages/open-workflow-diagram-editor:
dependencies:
+ '@base-ui/react':
+ specifier: 'catalog:'
+ version: 1.7.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@openworkflowspec/i18n':
specifier: workspace:*
version: link:../i18n
@@ -461,6 +467,33 @@ packages:
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
engines: {node: '>=6.9.0'}
+ '@base-ui/react@1.7.0':
+ resolution: {integrity: sha512-j+8QjX44C32jrXD/qyEAGpFr70FRpGL2CY61mQd9nBPWN737CK0xxD1ceJ055rW4RtdvFDT1e7otzdlfxvsYug==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ '@date-fns/tz': ^1.2.0
+ '@types/react': ^17 || ^18 || ^19
+ date-fns: ^4.0.0
+ react: ^17 || ^18 || ^19
+ react-dom: ^17 || ^18 || ^19
+ peerDependenciesMeta:
+ '@date-fns/tz':
+ optional: true
+ '@types/react':
+ optional: true
+ date-fns:
+ optional: true
+
+ '@base-ui/utils@0.3.2':
+ resolution: {integrity: sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==}
+ peerDependencies:
+ '@types/react': ^17 || ^18 || ^19
+ react: ^17 || ^18 || ^19
+ react-dom: ^17 || ^18 || ^19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@bcoe/v8-coverage@1.0.2':
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
@@ -761,18 +794,33 @@ packages:
'@floating-ui/core@1.7.5':
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
+ '@floating-ui/core@1.8.0':
+ resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
+
'@floating-ui/dom@1.7.6':
resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
+ '@floating-ui/dom@1.8.0':
+ resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
+
'@floating-ui/react-dom@2.1.8':
resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
+ '@floating-ui/react-dom@2.1.9':
+ resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
'@floating-ui/utils@0.2.11':
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
+ '@floating-ui/utils@0.2.12':
+ resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
+
'@inquirer/external-editor@1.0.3':
resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==}
engines: {node: '>=18'}
@@ -3670,6 +3718,9 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
+ reselect@5.2.0:
+ resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==}
+
resolve-from@5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
@@ -4305,6 +4356,29 @@ snapshots:
'@babel/helper-string-parser': 7.29.7
'@babel/helper-validator-identifier': 7.29.7
+ '@base-ui/react@1.7.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@base-ui/utils': 0.3.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@floating-ui/utils': 0.2.12
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ use-sync-external-store: 1.6.0(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ '@base-ui/utils@0.3.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@floating-ui/utils': 0.2.12
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ reselect: 5.2.0
+ use-sync-external-store: 1.6.0(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+
'@bcoe/v8-coverage@1.0.2': {}
'@blazediff/core@1.9.1': {}
@@ -4623,19 +4697,36 @@ snapshots:
dependencies:
'@floating-ui/utils': 0.2.11
+ '@floating-ui/core@1.8.0':
+ dependencies:
+ '@floating-ui/utils': 0.2.12
+
'@floating-ui/dom@1.7.6':
dependencies:
'@floating-ui/core': 1.7.5
'@floating-ui/utils': 0.2.11
+ '@floating-ui/dom@1.8.0':
+ dependencies:
+ '@floating-ui/core': 1.8.0
+ '@floating-ui/utils': 0.2.12
+
'@floating-ui/react-dom@2.1.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
'@floating-ui/dom': 1.7.6
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
+ '@floating-ui/react-dom@2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@floating-ui/dom': 1.8.0
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+
'@floating-ui/utils@0.2.11': {}
+ '@floating-ui/utils@0.2.12': {}
+
'@inquirer/external-editor@1.0.3(@types/node@26.1.2)':
dependencies:
chardet: 2.2.0
@@ -7280,6 +7371,8 @@ snapshots:
require-from-string@2.0.2: {}
+ reselect@5.2.0: {}
+
resolve-from@5.0.0: {}
resolve@1.22.12:
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 46a21d59..584ba350 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -25,6 +25,7 @@ catalog:
"@vitest/browser": ^4.1.10
"@vitest/browser-playwright": ^4.1.10
"@xyflow/react": ^12.11.2
+ "@base-ui/react": ^1.7.0
class-variance-authority: ^0.7.1
clsx: ^2.1.1
elkjs: ^0.12.0