diff --git a/examples/vanilla-web-component/CHANGELOG.md b/examples/vanilla-web-component/CHANGELOG.md index bd74c242..0019704f 100644 --- a/examples/vanilla-web-component/CHANGELOG.md +++ b/examples/vanilla-web-component/CHANGELOG.md @@ -10,3 +10,19 @@ - Updated dependencies [[`d988d0e`](https://github.com/open-workflow-specification/editor/commit/d988d0ea18d0b8ab84c63d6e4ef25c34df69e680), [`f060c52`](https://github.com/open-workflow-specification/editor/commit/f060c529339852efc7154b746156b160336c5f4a), [`a216248`](https://github.com/open-workflow-specification/editor/commit/a216248b8592d02157e36463cd331d69a16beb47), [`643d578`](https://github.com/open-workflow-specification/editor/commit/643d5783db2e0115b49357d99a1529395a43476f)]: - @openworkflowspec/diagram-editor@1.1.0 + + diff --git a/packages/open-workflow-diagram-editor/README.md b/packages/open-workflow-diagram-editor/README.md index 4d0c8d79..20e08e27 100644 --- a/packages/open-workflow-diagram-editor/README.md +++ b/packages/open-workflow-diagram-editor/README.md @@ -157,7 +157,7 @@ Tests mirror the source structure: ## UI Components (shadcn/ui) -This package uses [shadcn/ui](https://ui.shadcn.com/) for UI primitives. Configuration: [`components.json`](components.json) +This package uses [shadcn/ui](https://ui.shadcn.com/) for UI primitives. Configuration: [`components.json`](components.json). By default, use Radix UI. Fall back to Base UI only when a component isn't available in Radix. ### Key Settings diff --git a/packages/open-workflow-diagram-editor/package.json b/packages/open-workflow-diagram-editor/package.json index 406170da..4f29c831 100644 --- a/packages/open-workflow-diagram-editor/package.json +++ b/packages/open-workflow-diagram-editor/package.json @@ -41,6 +41,7 @@ "test-e2e:ui": "playwright test --ui" }, "dependencies": { + "@base-ui/react": "catalog:", "@openworkflowspec/i18n": "workspace:*", "@openworkflowspec/sdk": "catalog:", "@xyflow/react": "catalog:", diff --git a/packages/open-workflow-diagram-editor/src/components/ui/combobox.tsx b/packages/open-workflow-diagram-editor/src/components/ui/combobox.tsx new file mode 100644 index 00000000..705bec4f --- /dev/null +++ b/packages/open-workflow-diagram-editor/src/components/ui/combobox.tsx @@ -0,0 +1,301 @@ +/* + * 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. + */ + +"use client"; + +import * as React from "react"; +import { Combobox as ComboboxPrimitive } from "@base-ui/react"; +import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupInput, +} from "@/components/ui/input-group"; + +const Combobox = ComboboxPrimitive.Root; + +function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { + return ; +} + +function ComboboxTrigger({ className, children, ...props }: ComboboxPrimitive.Trigger.Props) { + return ( + + {children} + + + ); +} + +function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { + return ( + } + className={cn(className)} + {...props} + > + + + ); +} + +function ComboboxInput({ + className, + children, + disabled = false, + showTrigger = true, + showClear = false, + ...props +}: ComboboxPrimitive.Input.Props & { + showTrigger?: boolean; + showClear?: boolean; +}) { + return ( + + } {...props} /> + + {showTrigger && ( + + + + )} + {showClear && } + + {children} + + ); +} + +function ComboboxContent({ + className, + side = "bottom", + sideOffset = 6, + align = "start", + alignOffset = 0, + anchor, + ...props +}: ComboboxPrimitive.Popup.Props & + Pick< + ComboboxPrimitive.Positioner.Props, + "side" | "align" | "sideOffset" | "alignOffset" | "anchor" + >) { + return ( + + + + + + ); +} + +function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { + return ( + + ); +} + +function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props) { + return ( + + {children} + + } + > + + + + ); +} + +function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { + return ( + + ); +} + +function ComboboxLabel({ className, ...props }: ComboboxPrimitive.GroupLabel.Props) { + return ( + + ); +} + +function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { + return ; +} + +function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { + return ( + + ); +} + +function ComboboxSeparator({ className, ...props }: ComboboxPrimitive.Separator.Props) { + return ( + + ); +} + +function ComboboxChips({ + className, + ...props +}: React.ComponentPropsWithRef & ComboboxPrimitive.Chips.Props) { + return ( + + ); +} + +function ComboboxChip({ + className, + children, + showRemove = true, + ...props +}: ComboboxPrimitive.Chip.Props & { + showRemove?: boolean; +}) { + return ( + + {children} + {showRemove && ( + } + className="dec:-ml-1 dec:opacity-50 dec:hover:opacity-100" + data-slot="combobox-chip-remove" + > + + + )} + + ); +} + +function ComboboxChipsInput({ className, children, ...props }: ComboboxPrimitive.Input.Props) { + return ( + + ); +} + +function useComboboxAnchor() { + return React.useRef(null); +} + +export { + Combobox, + ComboboxInput, + ComboboxContent, + ComboboxList, + ComboboxItem, + ComboboxGroup, + ComboboxLabel, + ComboboxCollection, + ComboboxEmpty, + ComboboxSeparator, + ComboboxChips, + ComboboxChip, + ComboboxChipsInput, + ComboboxTrigger, + ComboboxValue, + useComboboxAnchor, +}; diff --git a/packages/open-workflow-diagram-editor/src/components/ui/input-group.tsx b/packages/open-workflow-diagram-editor/src/components/ui/input-group.tsx new file mode 100644 index 00000000..fcb9d060 --- /dev/null +++ b/packages/open-workflow-diagram-editor/src/components/ui/input-group.tsx @@ -0,0 +1,177 @@ +/* + * 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 { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; + +function InputGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
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 ( +