diff --git a/apps/ui/app/particles/search-field.tsx b/apps/ui/app/particles/search-field.tsx index 05881dbae..eaf345bbf 100644 --- a/apps/ui/app/particles/search-field.tsx +++ b/apps/ui/app/particles/search-field.tsx @@ -155,12 +155,16 @@ export default function SearchField({ open={open} value={selectedItems} > - - + + } + > {( value: { value: string; label: string; isComponent?: boolean }[], @@ -193,8 +197,6 @@ export default function SearchField({ @@ -206,7 +208,9 @@ export default function SearchField({ {(group: (typeof groupedItems)[number]) => ( - {group.type === "disabled" && } + {group.type === "disabled" && ( + + )} {group.type === "enabled" diff --git a/apps/ui/components/command-menu.tsx b/apps/ui/components/command-menu.tsx index fac274c72..d3963435e 100644 --- a/apps/ui/components/command-menu.tsx +++ b/apps/ui/components/command-menu.tsx @@ -1,5 +1,6 @@ "use client"; +import { CommandPanel } from "@coss/ui/components/command"; import { ArrowTurnBackwardIcon, Atom01Icon, @@ -10,37 +11,45 @@ import { HugeiconsIcon } from "@hugeicons/react"; import { useRouter } from "next/navigation"; import type { ComponentProps } from "react"; import * as React from "react"; - +import { useConfig } from "@/hooks/use-config"; +import { useIsMac } from "@/hooks/use-is-mac"; +import type { source } from "@/lib/source"; +import { useCopyToClipboard } from "@/registry/default/hooks/use-copy-to-clipboard"; +import { Button } from "@/registry/default/ui/button"; import { Command, + CommandCollection, + CommandDialog, + CommandDialogPopup, + CommandDialogTrigger, CommandEmpty, + CommandFooter, CommandGroup, + CommandGroupLabel, CommandInput, CommandItem, CommandList, -} from "@/components/ui/command"; -import { useConfig } from "@/hooks/use-config"; -import { useIsMac } from "@/hooks/use-is-mac"; -import { useMutationObserver } from "@/hooks/use-mutation-observer"; -import type { source } from "@/lib/source"; -import { cn } from "@/lib/utils"; -import { useCopyToClipboard } from "@/registry/default/hooks/use-copy-to-clipboard"; -import { Button } from "@/registry/default/ui/button"; -import { - Dialog, - DialogDescription, - DialogHeader, - DialogPopup, - DialogTitle, - DialogTrigger, -} from "@/registry/default/ui/dialog"; -import { Separator } from "@/registry/default/ui/separator"; +} from "@/registry/default/ui/command"; +import { Kbd, KbdGroup } from "@/registry/default/ui/kbd"; + +interface PageItem { + value: string; + label: string; + url: string; + isComponent: boolean; + keywords?: string[]; +} + +interface PageGroup { + value: string; + items: PageItem[]; +} export function CommandMenu({ tree, navItems, ...props -}: ComponentProps & { +}: ComponentProps & { tree: typeof source.pageTree; navItems?: { href: string; label: string }[]; }) { @@ -55,9 +64,57 @@ export function CommandMenu({ const [copyPayload, setCopyPayload] = React.useState(""); const packageManager = config.packageManager || "pnpm"; + // Convert tree structure to grouped items + const groupedItems = React.useMemo(() => { + const groups: PageGroup[] = []; + + // Add nav items group + if (navItems && navItems.length > 0) { + groups.push({ + items: navItems.map((item) => ({ + isComponent: false, + keywords: ["nav", "navigation", item.label.toLowerCase()], + label: item.label, + url: item.href, + value: `Navigation ${item.label}`, + })), + value: "Pages", + }); + } + + // Add tree groups + tree.children.forEach((group) => { + if (group.type === "folder") { + const items: PageItem[] = []; + group.children.forEach((item) => { + if (item.type === "page") { + const isComponent = item.url.includes("/components/"); + const itemName = item.name?.toString() || ""; + items.push({ + isComponent, + keywords: isComponent ? ["component"] : undefined, + label: itemName, + url: item.url, + value: itemName ? `${group.name} ${itemName}` : "", + }); + } + }); + if (items.length > 0) { + groups.push({ + items, + value: + typeof group.name === "string" ? group.name : String(group.name), + }); + } + } + }); + + return groups; + }, [tree, navItems]); + const handlePageHighlight = React.useCallback( - (isComponent: boolean, item: { url: string; name?: React.ReactNode }) => { - if (isComponent) { + (item: PageItem) => { + if (item.isComponent) { const componentName = item.url.split("/").pop(); setSelectedType("component"); const registryItem = `@coss/${componentName}`; @@ -85,10 +142,13 @@ export function CommandMenu({ [packageManager], ); - const runCommand = React.useCallback((command: () => unknown) => { - setOpen(false); - command(); - }, []); + const handleItemClick = React.useCallback( + (item: PageItem) => { + setOpen(false); + router.push(item.url); + }, + [router], + ); React.useEffect(() => { const down = (e: KeyboardEvent) => { @@ -107,191 +167,81 @@ export function CommandMenu({ } if (e.key === "c" && (e.metaKey || e.ctrlKey)) { - runCommand(() => { - if (selectedType === "page" || selectedType === "component") { - copyToClipboard(copyPayload); - } - }); + if (selectedType === "page" || selectedType === "component") { + copyToClipboard(copyPayload); + } } }; document.addEventListener("keydown", down); return () => document.removeEventListener("keydown", down); - }, [copyPayload, runCommand, selectedType, copyToClipboard]); + }, [copyPayload, selectedType, copyToClipboard]); return ( - - setOpen(true)} variant="outline" {...props}> - -
- {isMac ? "⌘" : "Ctrl"} - K -
- - } - /> - - - Search documentation... - Search for a command to run... - + + }> + + + {isMac ? "⌘" : "Ctrl"} + K + + + { - const extendValue = `${value} ${keywords?.join(" ") || ""}`; - if (extendValue.toLowerCase().includes(search.toLowerCase())) { - return 1; + items={groupedItems} + onItemHighlighted={(highlightedValue) => { + const item = highlightedValue as PageItem | null; + if (item) { + handlePageHighlight(item); } - return 0; }} > - - - No results found. - - {navItems && navItems.length > 0 && ( - - {navItems.map((item) => ( - { - setSelectedType("page"); - setCopyPayload(""); - }} - onSelect={() => { - runCommand(() => router.push(item.href)); - }} - value={`Navigation ${item.label}`} - > - - {item.label} - - ))} - + + No results found. + + {(group: PageGroup, _index: number) => ( + + {group.value} + + {(item: PageItem) => ( + handleItemClick(item)} + value={item.value} + > + + {item.label} + + )} + + + )} + + + +
+ Go to Page + + + +
+ {copyPayload && ( +
+ {copyPayload} + + {isMac ? "⌘" : "Ctrl"} + C + +
)} - {tree.children.map((group) => ( - - {group.type === "folder" && - group.children.map((item) => { - if (item.type === "page") { - const isComponent = item.url.includes("/components/"); - - return ( - - handlePageHighlight(isComponent, item) - } - onSelect={() => { - runCommand(() => router.push(item.url)); - }} - value={ - item.name?.toString() - ? `${group.name} ${item.name}` - : "" - } - > - {isComponent ? ( - - ) : ( - - )} - {item.name} - - ); - } - return null; - })} - - ))} -
+
-
-
- - - {" "} - {selectedType === "page" || selectedType === "component" - ? "Go to Page" - : null} -
- {copyPayload && ( - <> - -
- {isMac ? "⌘" : "Ctrl"} - C - {copyPayload} -
- - )} -
-
-
- ); -} - -function CommandMenuItem({ - children, - onHighlight, - ...props -}: React.ComponentProps & { - onHighlight?: () => void; - "data-selected"?: string; - "aria-selected"?: string; -}) { - const ref = React.useRef(null); - - useMutationObserver(ref, (mutations) => { - mutations.forEach((mutation) => { - if ( - mutation.type === "attributes" && - mutation.attributeName === "aria-selected" && - ref.current?.getAttribute("aria-selected") === "true" - ) { - onHighlight?.(); - } - }); - }); - - return ( - - {children} - - ); -} - -function CommandMenuKbd({ className, ...props }: React.ComponentProps<"kbd">) { - return ( - + + ); } diff --git a/apps/ui/components/ui/command.tsx b/apps/ui/components/ui/command.tsx deleted file mode 100644 index e7ce8cde0..000000000 --- a/apps/ui/components/ui/command.tsx +++ /dev/null @@ -1,180 +0,0 @@ -"use client"; - -import { Search01Icon } from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { Command as CommandPrimitive } from "cmdk"; -import type * as React from "react"; - -import { cn } from "@/registry/default/lib/utils"; -import { - Dialog, - DialogDescription, - DialogHeader, - DialogPopup, - DialogTitle, -} from "@/registry/default/ui/dialog"; - -function Command({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandDialog({ - title = "Command Palette", - description = "Search for a command to run…", - children, - ...props -}: Omit, "children"> & { - title?: string; - description?: string; - children?: React.ReactNode; -}) { - return ( - - - {title} - {description} - - - - {children} - - - - ); -} - -function CommandInput({ - className, - ...props -}: React.ComponentProps) { - return ( -
-
- -
-
- -
-
- ); -} - -function CommandList({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandEmpty({ - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandGroup({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandSeparator({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandItem({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandShortcut({ - className, - ...props -}: React.ComponentProps<"span">) { - return ( - - ); -} - -export { - Command, - CommandDialog, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, - CommandShortcut, -}; diff --git a/apps/ui/content/docs/components/autocomplete.mdx b/apps/ui/content/docs/components/autocomplete.mdx index 0b03fc0f1..84b091b82 100644 --- a/apps/ui/content/docs/components/autocomplete.mdx +++ b/apps/ui/content/docs/components/autocomplete.mdx @@ -81,13 +81,92 @@ const items = [ ## API Reference -The `AutocompleteInput` component extends the original Base UI component with a few extra props: +### Autocomplete + +The root autocomplete component. Manages the autocomplete state and provides context to child components. + +| Prop | Type | Description | +| ---------- | ----------------------------------------- | --------------------------------------------------- | +| `items` | `readonly unknown[]` | The array of items to display in the autocomplete | +| `open` | `boolean` | Controls whether the popup is open | +| `...props` | `React.ComponentProps` | All Base UI Autocomplete props are supported | + +### AutocompleteInput + +The input field component with extended features for size variants and addon support. | Prop | Type | Default | Description | | ------------- | --------------------------- | ----------- | ------------------------------------------------------------------------------------------------ | -| `size` | `"sm" \| "default" \| "lg"` | `"default"` | The size variant of the input field. | -| `showTrigger` | `boolean` | `false` | Whether to display a trigger button (chevron icon) on the right side of the input. | -| `showClear` | `boolean` | `false` | Whether to display a clear button (X icon) on the right side of the input when there is a value. | +| `size` | `"sm" \| "default" \| "lg"` | `"default"` | The size variant of the input field | +| `startAddon` | `React.ReactNode` | - | Element to display at the start (left side) of the input, such as an icon | +| `showTrigger` | `boolean` | `false` | Whether to display a trigger button (chevron icon) on the right side of the input | +| `showClear` | `boolean` | `false` | Whether to display a clear button (X icon) on the right side of the input when there is a value | +| `className` | `string` | - | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Input props | - | All standard autocomplete input attributes are supported | + +### AutocompletePopup + +The popup container that displays the autocomplete suggestions. + +| Prop | Type | Description | +| ----------- | --------------------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Popup props | All standard autocomplete popup attributes are supported | + +### AutocompleteList + +A scrollable container for autocomplete items. + +| Prop | Type | Description | +| ----------- | ---------------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete List props | All standard autocomplete list attributes are supported | + +### AutocompleteItem + +An individual selectable autocomplete item. + +| Prop | Type | Description | +| ----------- | ---------------------------------------- | ------------------------------------------------ | +| `value` | `unknown` | The value of the item | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Item props | All standard autocomplete item attributes are supported | + +### AutocompleteEmpty + +Displays a message when no results are found. + +| Prop | Type | Description | +| ----------- | ----------------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Empty props | All standard autocomplete empty attributes are supported | + +### AutocompleteGroup + +Groups related autocomplete items together. + +| Prop | Type | Description | +| ----------- | ----------------------------------------- | ------------------------------------------------ | +| `items` | `readonly unknown[]` | The array of items in this group | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Group props | All standard autocomplete group attributes are supported | + +### AutocompleteGroupLabel + +Displays a label for an autocomplete group. + +| Prop | Type | Description | +| ----------- | ---------------------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Group Label props | All standard autocomplete group label attributes are supported | + +### AutocompleteCollection + +Used to wrap items within a group for rendering. + +| Prop | Type | Description | +| ----------- | ---------------------------------------------- | ------------------------------------------------ | +| `...props` | Base UI Autocomplete Collection props | All standard autocomplete collection attributes are supported | ## Examples @@ -127,6 +206,12 @@ Automatically highlight the first matching option. +### With Start Addon + +Display an icon or other element at the start of the input using the `startAddon` prop. + + + ### With Groups diff --git a/apps/ui/content/docs/components/combobox.mdx b/apps/ui/content/docs/components/combobox.mdx index 30cc99ebd..98dbd6f54 100644 --- a/apps/ui/content/docs/components/combobox.mdx +++ b/apps/ui/content/docs/components/combobox.mdx @@ -134,13 +134,120 @@ const items = [ ## API Reference -The `ComboboxInput` component extends the original Base UI component with a few extra props: +### Combobox + +The root combobox component. Manages the combobox state and provides context to child components. + +| Prop | Type | Description | +| ---------- | --------------------------------------- | ------------------------------------------------- | +| `items` | `readonly unknown[]` | The array of items to display in the combobox | +| `multiple` | `boolean` | Whether to allow multiple selection | +| `open` | `boolean` | Controls whether the popup is open | +| `...props` | `React.ComponentProps` | All Base UI Combobox props are supported | + +### ComboboxInput + +The input field component with extended features for size variants and addon support. | Prop | Type | Default | Description | | ------------- | --------------------------- | ----------- | ------------------------------------------------------------------------------------------------ | -| `size` | `"sm" \| "default" \| "lg"` | `"default"` | The size variant of the input field. | -| `showTrigger` | `boolean` | `true` | Whether to display a trigger button (chevron icon) on the right side of the input. | -| `showClear` | `boolean` | `false` | Whether to display a clear button (X icon) on the right side of the input when there is a value. | +| `size` | `"sm" \| "default" \| "lg"` | `"default"` | The size variant of the input field | +| `startAddon` | `React.ReactNode` | - | Element to display at the start (left side) of the input, such as an icon | +| `showTrigger` | `boolean` | `true` | Whether to display a trigger button (chevron icon) on the right side of the input | +| `showClear` | `boolean` | `false` | Whether to display a clear button (X icon) on the right side of the input when there is a value | +| `className` | `string` | - | Additional CSS classes to apply to the component | +| `...props` | Base UI Combobox Input props | - | All standard combobox input attributes are supported | + +### ComboboxPopup + +The popup container that displays the combobox options. + +| Prop | Type | Description | +| ----------- | ---------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Combobox Popup props | All standard combobox popup attributes are supported | + +### ComboboxList + +A scrollable container for combobox items. + +| Prop | Type | Description | +| ----------- | ----------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Combobox List props | All standard combobox list attributes are supported | + +### ComboboxItem + +An individual selectable combobox item. + +| Prop | Type | Description | +| ----------- | ----------------------------- | ------------------------------------------------ | +| `value` | `unknown` | The value of the item | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Combobox Item props | All standard combobox item attributes are supported | + +### ComboboxEmpty + +Displays a message when no results are found. + +| Prop | Type | Description | +| ----------- | ------------------------------ | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Combobox Empty props | All standard combobox empty attributes are supported | + +### ComboboxGroup + +Groups related combobox items together. + +| Prop | Type | Description | +| ----------- | ------------------------------ | ------------------------------------------------ | +| `items` | `readonly unknown[]` | The array of items in this group | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Combobox Group props | All standard combobox group attributes are supported | + +### ComboboxGroupLabel + +Displays a label for a combobox group. + +| Prop | Type | Description | +| ----------- | ----------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Combobox Group Label props | All standard combobox group label attributes are supported | + +### ComboboxCollection + +Used to wrap items within a group for rendering. + +| Prop | Type | Description | +| ----------- | ------------------------------------ | ------------------------------------------------ | +| `...props` | Base UI Combobox Collection props | All standard combobox collection attributes are supported | + +### ComboboxChips + +Container for displaying selected chips in multiple selection mode. + +| Prop | Type | Description | +| ------------- | ----------------------------- | -------------------------------------------------------------- | +| `startAddon` | `React.ReactNode` | Element to display at the start (left side) of the chips area | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | `React.ComponentProps<'div'>` | All standard div attributes are supported | + +### ComboboxChip + +An individual chip representing a selected item in multiple selection mode. + +| Prop | Type | Description | +| ----------- | ----------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Combobox Chip props | All standard combobox chip attributes are supported | + +### ComboboxValue + +Provides access to the current value for custom rendering. + +| Prop | Type | Description | +| ----------- | ------------------------------ | ------------------------------------------------ | +| `...props` | Base UI Combobox Value props | All standard combobox value attributes are supported | ## Examples @@ -178,6 +285,18 @@ Automatically highlight the first matching option. +### With Start Addon + +Display an icon or other element at the start of the input using the `startAddon` prop. + + + +### With Start Addon - Multiple + +Use the `startAddon` prop on `ComboboxChips` to display an icon at the start when using multiple selection. + + + ### With Input Inside Popup @@ -189,3 +308,4 @@ Automatically highlight the first matching option. ### Form Integration - Multiple + diff --git a/apps/ui/content/docs/components/command.mdx b/apps/ui/content/docs/components/command.mdx new file mode 100644 index 000000000..39805e542 --- /dev/null +++ b/apps/ui/content/docs/components/command.mdx @@ -0,0 +1,336 @@ +--- +title: Command +description: A command palette component built with Dialog and Autocomplete for searching and executing commands. + +links: + doc: https://base-ui.com/react/components/autocomplete#api-reference +--- + + + +## Installation + + + + + CLI + Manual + + + + +```bash +npx shadcn@latest add @coss/command +``` + + + + + + + +Install the following dependencies: + +```bash +npm install @base-ui/react +``` + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { + Command, + CommandCollection, + CommandDialog, + CommandDialogPopup, + CommandDialogTrigger, + CommandEmpty, + CommandFooter, + CommandGroup, + CommandGroupLabel, + CommandInput, + CommandItem, + CommandList, + CommandPanel, + CommandSeparator, + CommandShortcut, +} from "@/components/ui/command" +import { Button } from "@/components/ui/button" +``` + +```tsx +const items = [ + { value: "linear", label: "Linear" }, + { value: "figma", label: "Figma" }, + { value: "slack", label: "Slack" }, +] + + + }> + Open Command Palette + + + + + + No results found. + + {(item) => ( + + {item.label} + + )} + + + + +``` + +## API Reference + +### Command + +The root component that wraps the autocomplete functionality. It's an alias for `Autocomplete.Root` with sensible defaults for command palette behavior: `autoHighlight="always"`, `keepHighlight={true}`, and `open={true}`. + +| Prop | Type | Default | Description | +| --------------- | -------------------------------------- | ----------- | ------------------------------------------------ | +| `items` | `readonly unknown[]` | - | The array of items to display in the command | +| `open` | `boolean` | `true` | Controls whether the command is open | +| `autoHighlight` | `boolean \| "always"` | `"always"` | Controls automatic highlighting of items | +| `keepHighlight` | `boolean` | `true` | Whether to maintain highlight state | +| `...props` | `React.ComponentProps` | - | All Base UI Autocomplete props are supported | + +### CommandDialog + +A wrapper component that provides the dialog root functionality. It's an alias for `Dialog.Root` from Base UI. + +| Prop | Type | Description | +| -------------- | ---------- | ------------------------------------------- | +| `open` | `boolean` | Controls whether the dialog is open | +| `onOpenChange` | `function` | Callback fired when the open state changes | +| `...props` | Base UI Dialog props | All standard dialog attributes are supported | + +### CommandDialogTrigger + +The trigger button that opens the command dialog. Renders as a button by default. + +| Prop | Type | Description | +| ----------- | ------------------------------------------- | ------------------------------------------------ | +| `render` | `React.ReactElement` | Element to render as the trigger | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Dialog Trigger props | All standard dialog trigger attributes are supported | + +### CommandDialogPopup + +The popup content container that displays the command palette inside a dialog. + +| Prop | Type | Description | +| ----------- | ---------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Dialog Popup props | All standard dialog popup attributes are supported | + +### CommandInput + +The search input field with an integrated search icon. Automatically includes a search icon via `startAddon` and is sized to `lg` by default. + +| Prop | Type | Description | +| ------------- | --------------------------------- | ------------------------------------------------ | +| `placeholder` | `string` | The placeholder text for the input | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Input props | All standard autocomplete input attributes are supported | + +### CommandList + +A scrollable container for command items. It wraps `AutocompleteList` with scroll functionality. + +| Prop | Type | Description | +| ----------- | --------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete List props | All standard autocomplete list attributes are supported | + +### CommandPanel + +A container component that provides styling for command content outside of dialogs. Useful when building standalone command interfaces with a bordered, elevated appearance. + +| Prop | Type | Description | +| ----------- | ----------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | `React.ComponentProps<'div'>` | All standard div attributes are supported | + +### CommandEmpty + +Displays a message when no results are found. + +| Prop | Type | Description | +| ----------- | --------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Empty props | All standard autocomplete empty attributes are supported | + +### CommandGroup + +Groups related command items together. Wraps `AutocompleteGroup`. + +| Prop | Type | Description | +| ----------- | --------------------------------- | ------------------------------------------------ | +| `items` | `readonly unknown[]` | The array of items in this group | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Group props | All standard autocomplete group attributes are supported | + +### CommandGroupLabel + +Displays a label for a command group. + +| Prop | Type | Description | +| ----------- | -------------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Group Label props | All standard autocomplete group label attributes are supported | + +### CommandItem + +An individual selectable command item. Extends `AutocompleteItem`. + +| Prop | Type | Description | +| ----------- | --------------------------------- | ------------------------------------------------ | +| `value` | `unknown` | The value of the item | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | Base UI Autocomplete Item props | All standard autocomplete item attributes are supported | + +### CommandSeparator + +A visual separator between command groups or items. Includes default vertical spacing with `my-2` className. + +| Prop | Type | Description | +| ----------- | -------------------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component (default includes `my-2`) | +| `...props` | Base UI Autocomplete Separator props | All standard autocomplete separator attributes are supported | + +### CommandShortcut + +Displays keyboard shortcuts in a styled span element. + +| Prop | Type | Description | +| ----------- | ------------------------------ | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | `React.ComponentProps<'span'>` | All standard span attributes are supported | + +### CommandFooter + +A footer section for displaying hints or additional keyboard shortcuts. Renders as a styled `div` with padding and border. + +| Prop | Type | Description | +| ----------- | ----------------------------- | ------------------------------------------------ | +| `className` | `string` | Additional CSS classes to apply to the component | +| `...props` | `React.ComponentProps<'div'>` | All standard div attributes are supported | + +### CommandCollection + +Used within `CommandGroup` to wrap items when using grouped data. It's an alias for `AutocompleteCollection` from the Autocomplete component. + +| Prop | Type | Description | +| ----------- | --------------------------------------- | ------------------------------------------------ | +| `...props` | Base UI Autocomplete Collection props | All standard autocomplete collection attributes are supported | + +## Examples + +### With Keyboard Shortcut + +You can add a keyboard shortcut to open the command palette: + +```tsx +React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + setOpen((open) => !open) + } + } + + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) +}, []) +``` + +### With Grouped Items + +```tsx +const groupedItems = [ + { + value: "Suggestions", + items: [ + { value: "linear", label: "Linear" }, + { value: "figma", label: "Figma" }, + ] + }, + { + value: "Commands", + items: [ + { value: "clipboard", label: "Clipboard History" }, + { value: "settings", label: "System Preferences" }, + ] + }, +] + + + + No results found. + + {(group, index) => ( + + + {group.value} + + {(item) => ( + + {item.label} + + )} + + + {index < groupedItems.length - 1 && } + + )} + + +``` + +### Standalone Command (Without Dialog) + +You can use the Command component without a dialog wrapper: + +```tsx + + + No results found. + + {(item) => ( + + {item.label} + + )} + + +``` + +## Comparing with shadcn + +The API is significantly different from shadcn/ui (cmdk). Please review both docs before migrating: [cmdk Docs](https://cmdk.paco.me/) and [shadcn/ui Command](https://ui.shadcn.com/docs/components/command), and our Base UI Autocomplete docs referenced at the top of this page. + +### Key Differences + +- No `cmdk` dependency - built entirely with Base UI's Autocomplete and Dialog components +- Data-driven approach - pass an `items` array to `Command` and use render functions instead of manually composing `CommandItem` children +- Use `CommandCollection` within `CommandGroup` when rendering grouped data with the items pattern +- Use `CommandDialog`, `CommandDialogTrigger`, and `CommandDialogPopup` for dialog functionality instead of composing separate Dialog components +- `CommandGroup` uses `` as a child instead of a `heading` prop diff --git a/apps/ui/content/docs/components/input-group.mdx b/apps/ui/content/docs/components/input-group.mdx index 22686984c..df5acb98d 100644 --- a/apps/ui/content/docs/components/input-group.mdx +++ b/apps/ui/content/docs/components/input-group.mdx @@ -82,10 +82,6 @@ import { -### With Number Field - - - ### With Tooltip @@ -126,6 +122,10 @@ import { +### With Number Field + + + ### With Textarea diff --git a/apps/ui/content/docs/components/meta.json b/apps/ui/content/docs/components/meta.json index f9728a630..71dfdfdd8 100644 --- a/apps/ui/content/docs/components/meta.json +++ b/apps/ui/content/docs/components/meta.json @@ -13,6 +13,7 @@ "checkbox-group", "collapsible", "combobox", + "command", "dialog", "empty", "field", diff --git a/apps/ui/lib/docs.ts b/apps/ui/lib/docs.ts index 818b66209..b2fe0d302 100644 --- a/apps/ui/lib/docs.ts +++ b/apps/ui/lib/docs.ts @@ -1,3 +1,4 @@ export const PAGES_NEW = [ // "/docs/components/{component-name}", + "/docs/components/command", ]; diff --git a/apps/ui/package.json b/apps/ui/package.json index 02348978c..4da76e178 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -8,7 +8,6 @@ "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "cmdk": "1.1.1", "fumadocs-core": "^16.0.14", "fumadocs-mdx": "^14.0.3", "jotai": "^2.15.1", diff --git a/apps/ui/public/llms.txt b/apps/ui/public/llms.txt index b9b24cef0..17cd6e50b 100644 --- a/apps/ui/public/llms.txt +++ b/apps/ui/public/llms.txt @@ -23,6 +23,7 @@ - [Checkbox Group](https://coss.com/ui/docs/components/checkbox-group.md): A collection of related checkboxes with group-level control. - [Collapsible](https://coss.com/ui/docs/components/collapsible.md): A component that toggles visibility of content sections. - [Combobox](https://coss.com/ui/docs/components/combobox.md): An input combined with a list of predefined items to select. +- [Command](https://coss.com/ui/docs/components/command.md): A command palette component built with Dialog and Autocomplete for searching and executing commands. - [Dialog](https://coss.com/ui/docs/components/dialog.md): A modal overlay for displaying content that requires user interaction. - [Empty](https://coss.com/ui/docs/components/empty.md): A container for displaying empty state information. - [Field](https://coss.com/ui/docs/components/field.md): A wrapper component for form inputs with labels and validation. diff --git a/apps/ui/public/r/autocomplete.json b/apps/ui/public/r/autocomplete.json index 3fb582c13..d9a7f1be5 100644 --- a/apps/ui/public/r/autocomplete.json +++ b/apps/ui/public/r/autocomplete.json @@ -12,7 +12,7 @@ "files": [ { "path": "registry/default/ui/autocomplete.tsx", - "content": "\"use client\";\n\nimport { Autocomplete as AutocompletePrimitive } from \"@base-ui/react/autocomplete\";\nimport { ChevronsUpDownIcon, XIcon } from \"lucide-react\";\n\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Input } from \"@/registry/default/ui/input\";\nimport { ScrollArea } from \"@/registry/default/ui/scroll-area\";\n\nconst Autocomplete = AutocompletePrimitive.Root;\n\nfunction AutocompleteInput({\n className,\n showTrigger = false,\n showClear = false,\n size,\n ...props\n}: Omit & {\n showTrigger?: boolean;\n showClear?: boolean;\n size?: \"sm\" | \"default\" | \"lg\" | number;\n}) {\n const sizeValue = (size ?? \"default\") as \"sm\" | \"default\" | \"lg\" | number;\n\n return (\n
\n }\n {...props}\n />\n {showTrigger && (\n \n \n \n )}\n {showClear && (\n \n \n \n )}\n
\n );\n}\n\nfunction AutocompletePopup({\n className,\n children,\n sideOffset = 4,\n ...props\n}: AutocompletePrimitive.Popup.Props & {\n sideOffset?: number;\n}) {\n return (\n \n \n \n \n {children}\n \n \n \n \n );\n}\n\nfunction AutocompleteItem({\n className,\n children,\n ...props\n}: AutocompletePrimitive.Item.Props) {\n return (\n \n {children}\n \n );\n}\n\nfunction AutocompleteSeparator({\n className,\n ...props\n}: AutocompletePrimitive.Separator.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteGroup({\n className,\n ...props\n}: AutocompletePrimitive.Group.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteGroupLabel({\n className,\n ...props\n}: AutocompletePrimitive.GroupLabel.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteEmpty({\n className,\n ...props\n}: AutocompletePrimitive.Empty.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteRow({\n className,\n ...props\n}: AutocompletePrimitive.Row.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteList({\n className,\n ...props\n}: AutocompletePrimitive.List.Props) {\n return (\n \n \n \n );\n}\n\nfunction AutocompleteClear({\n className,\n ...props\n}: AutocompletePrimitive.Clear.Props) {\n return (\n \n \n \n );\n}\n\nfunction AutocompleteStatus({\n className,\n ...props\n}: AutocompletePrimitive.Status.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteCollection({\n ...props\n}: AutocompletePrimitive.Collection.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteTrigger({\n className,\n ...props\n}: AutocompletePrimitive.Trigger.Props) {\n return (\n \n );\n}\n\nexport {\n Autocomplete,\n AutocompleteInput,\n AutocompleteTrigger,\n AutocompletePopup,\n AutocompleteItem,\n AutocompleteSeparator,\n AutocompleteGroup,\n AutocompleteGroupLabel,\n AutocompleteEmpty,\n AutocompleteValue,\n AutocompleteList,\n AutocompleteClear,\n AutocompleteStatus,\n AutocompleteRow,\n AutocompleteCollection,\n};\n", + "content": "\"use client\";\n\nimport { Autocomplete as AutocompletePrimitive } from \"@base-ui/react/autocomplete\";\nimport { ChevronsUpDownIcon, XIcon } from \"lucide-react\";\n\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Input } from \"@/registry/default/ui/input\";\nimport { ScrollArea } from \"@/registry/default/ui/scroll-area\";\n\nconst Autocomplete = AutocompletePrimitive.Root;\n\nfunction AutocompleteInput({\n className,\n showTrigger = false,\n showClear = false,\n startAddon,\n size,\n ...props\n}: Omit & {\n showTrigger?: boolean;\n showClear?: boolean;\n startAddon?: React.ReactNode;\n size?: \"sm\" | \"default\" | \"lg\" | number;\n ref?: React.Ref;\n}) {\n const sizeValue = (size ?? \"default\") as \"sm\" | \"default\" | \"lg\" | number;\n\n return (\n
\n {startAddon && (\n \n {startAddon}\n
\n )}\n }\n {...props}\n />\n {showTrigger && (\n \n \n \n )}\n {showClear && (\n \n \n \n )}\n \n );\n}\n\nfunction AutocompletePopup({\n className,\n children,\n sideOffset = 4,\n ...props\n}: AutocompletePrimitive.Popup.Props & {\n sideOffset?: number;\n}) {\n return (\n \n \n \n \n {children}\n \n \n \n \n );\n}\n\nfunction AutocompleteItem({\n className,\n children,\n ...props\n}: AutocompletePrimitive.Item.Props) {\n return (\n \n {children}\n \n );\n}\n\nfunction AutocompleteSeparator({\n className,\n ...props\n}: AutocompletePrimitive.Separator.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteGroup({\n className,\n ...props\n}: AutocompletePrimitive.Group.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteGroupLabel({\n className,\n ...props\n}: AutocompletePrimitive.GroupLabel.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteEmpty({\n className,\n ...props\n}: AutocompletePrimitive.Empty.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteRow({\n className,\n ...props\n}: AutocompletePrimitive.Row.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteList({\n className,\n ...props\n}: AutocompletePrimitive.List.Props) {\n return (\n \n \n \n );\n}\n\nfunction AutocompleteClear({\n className,\n ...props\n}: AutocompletePrimitive.Clear.Props) {\n return (\n \n \n \n );\n}\n\nfunction AutocompleteStatus({\n className,\n ...props\n}: AutocompletePrimitive.Status.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteCollection({\n ...props\n}: AutocompletePrimitive.Collection.Props) {\n return (\n \n );\n}\n\nfunction AutocompleteTrigger({\n className,\n ...props\n}: AutocompletePrimitive.Trigger.Props) {\n return (\n \n );\n}\n\nexport {\n Autocomplete,\n AutocompleteInput,\n AutocompleteTrigger,\n AutocompletePopup,\n AutocompleteItem,\n AutocompleteSeparator,\n AutocompleteGroup,\n AutocompleteGroupLabel,\n AutocompleteEmpty,\n AutocompleteValue,\n AutocompleteList,\n AutocompleteClear,\n AutocompleteStatus,\n AutocompleteRow,\n AutocompleteCollection,\n};\n", "type": "registry:ui" } ] diff --git a/apps/ui/public/r/combobox.json b/apps/ui/public/r/combobox.json index bb3ed9eb2..51276062a 100644 --- a/apps/ui/public/r/combobox.json +++ b/apps/ui/public/r/combobox.json @@ -12,7 +12,7 @@ "files": [ { "path": "registry/default/ui/combobox.tsx", - "content": "\"use client\";\n\nimport { Combobox as ComboboxPrimitive } from \"@base-ui/react/combobox\";\nimport { ChevronsUpDownIcon, XIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Input } from \"@/registry/default/ui/input\";\nimport { ScrollArea } from \"@/registry/default/ui/scroll-area\";\n\nconst ComboboxContext = React.createContext<{\n chipsRef: React.RefObject | null;\n multiple: boolean;\n}>({\n chipsRef: null,\n multiple: false,\n});\n\ntype ComboboxRootProps<\n ItemValue,\n Multiple extends boolean | undefined,\n> = Parameters>[0];\n\nfunction Combobox(\n props: ComboboxPrimitive.Root.Props,\n) {\n const chipsRef = React.useRef(null);\n return (\n \n )}\n />\n \n );\n}\n\nfunction ComboboxInput({\n className,\n showTrigger = true,\n showClear = false,\n size,\n ...props\n}: Omit & {\n showTrigger?: boolean;\n showClear?: boolean;\n size?: \"sm\" | \"default\" | \"lg\" | number;\n}) {\n const { multiple } = React.useContext(ComboboxContext);\n const sizeValue = (size ?? \"default\") as \"sm\" | \"default\" | \"lg\" | number;\n\n // multiple mode\n if (multiple) {\n return (\n \n );\n }\n // single mode\n return (\n
\n }\n {...props}\n />\n {showTrigger && (\n \n \n \n )}\n {showClear && (\n \n \n \n )}\n
\n );\n}\n\nfunction ComboboxTrigger({\n className,\n ...props\n}: ComboboxPrimitive.Trigger.Props) {\n return (\n \n );\n}\n\nfunction ComboboxPopup({\n className,\n children,\n sideOffset = 4,\n ...props\n}: ComboboxPrimitive.Popup.Props & {\n sideOffset?: number;\n}) {\n const { chipsRef } = React.useContext(ComboboxContext);\n\n return (\n \n \n \n \n {children}\n \n \n \n \n );\n}\n\nfunction ComboboxItem({\n className,\n children,\n ...props\n}: ComboboxPrimitive.Item.Props) {\n return (\n \n \n \n \n \n \n
{children}
\n \n );\n}\n\nfunction ComboboxSeparator({\n className,\n ...props\n}: ComboboxPrimitive.Separator.Props) {\n return (\n \n );\n}\n\nfunction ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {\n return (\n \n );\n}\n\nfunction ComboboxGroupLabel({\n className,\n ...props\n}: ComboboxPrimitive.GroupLabel.Props) {\n return (\n \n );\n}\n\nfunction ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {\n return (\n \n );\n}\n\nfunction ComboboxRow({ className, ...props }: ComboboxPrimitive.Row.Props) {\n return (\n \n );\n}\n\nfunction ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {\n return ;\n}\n\nfunction ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {\n return (\n \n \n \n );\n}\n\nfunction ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {\n return (\n \n );\n}\n\nfunction ComboboxStatus({\n className,\n ...props\n}: ComboboxPrimitive.Status.Props) {\n return (\n \n );\n}\n\nfunction ComboboxCollection(props: ComboboxPrimitive.Collection.Props) {\n return (\n \n );\n}\n\nfunction ComboboxChips({ className, ...props }: ComboboxPrimitive.Chips.Props) {\n const { chipsRef } = React.useContext(ComboboxContext);\n\n return (\n | null}\n {...props}\n />\n );\n}\n\nfunction ComboboxChip({ children, ...props }: ComboboxPrimitive.Chip.Props) {\n return (\n \n {children}\n \n \n );\n}\n\nfunction ComboboxChipRemove(props: ComboboxPrimitive.ChipRemove.Props) {\n return (\n \n \n \n );\n}\n\nexport {\n Combobox,\n ComboboxInput,\n ComboboxTrigger,\n ComboboxPopup,\n ComboboxItem,\n ComboboxSeparator,\n ComboboxGroup,\n ComboboxGroupLabel,\n ComboboxEmpty,\n ComboboxValue,\n ComboboxList,\n ComboboxClear,\n ComboboxStatus,\n ComboboxRow,\n ComboboxCollection,\n ComboboxChips,\n ComboboxChip,\n};\n", + "content": "\"use client\";\n\nimport { Combobox as ComboboxPrimitive } from \"@base-ui/react/combobox\";\nimport { ChevronsUpDownIcon, XIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Input } from \"@/registry/default/ui/input\";\nimport { ScrollArea } from \"@/registry/default/ui/scroll-area\";\n\nconst ComboboxContext = React.createContext<{\n chipsRef: React.RefObject | null;\n multiple: boolean;\n}>({\n chipsRef: null,\n multiple: false,\n});\n\ntype ComboboxRootProps<\n ItemValue,\n Multiple extends boolean | undefined,\n> = Parameters>[0];\n\nfunction Combobox(\n props: ComboboxPrimitive.Root.Props,\n) {\n const chipsRef = React.useRef(null);\n return (\n \n )}\n />\n \n );\n}\n\nfunction ComboboxInput({\n className,\n showTrigger = true,\n showClear = false,\n startAddon,\n size,\n ...props\n}: Omit & {\n showTrigger?: boolean;\n showClear?: boolean;\n startAddon?: React.ReactNode;\n size?: \"sm\" | \"default\" | \"lg\" | number;\n ref?: React.Ref;\n}) {\n const { multiple } = React.useContext(ComboboxContext);\n const sizeValue = (size ?? \"default\") as \"sm\" | \"default\" | \"lg\" | number;\n\n // multiple mode\n if (multiple) {\n return (\n \n );\n }\n\n // single mode\n return (\n
\n {startAddon && (\n \n {startAddon}\n
\n )}\n }\n {...props}\n />\n {showTrigger && (\n \n \n \n )}\n {showClear && (\n \n \n \n )}\n \n );\n}\n\nfunction ComboboxTrigger({\n className,\n ...props\n}: ComboboxPrimitive.Trigger.Props) {\n return (\n \n );\n}\n\nfunction ComboboxPopup({\n className,\n children,\n sideOffset = 4,\n ...props\n}: ComboboxPrimitive.Popup.Props & {\n sideOffset?: number;\n}) {\n const { chipsRef } = React.useContext(ComboboxContext);\n\n return (\n \n \n \n \n {children}\n \n \n \n \n );\n}\n\nfunction ComboboxItem({\n className,\n children,\n ...props\n}: ComboboxPrimitive.Item.Props) {\n return (\n \n \n \n \n \n \n
{children}
\n \n );\n}\n\nfunction ComboboxSeparator({\n className,\n ...props\n}: ComboboxPrimitive.Separator.Props) {\n return (\n \n );\n}\n\nfunction ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {\n return (\n \n );\n}\n\nfunction ComboboxGroupLabel({\n className,\n ...props\n}: ComboboxPrimitive.GroupLabel.Props) {\n return (\n \n );\n}\n\nfunction ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {\n return (\n \n );\n}\n\nfunction ComboboxRow({ className, ...props }: ComboboxPrimitive.Row.Props) {\n return (\n \n );\n}\n\nfunction ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {\n return ;\n}\n\nfunction ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {\n return (\n \n \n \n );\n}\n\nfunction ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {\n return (\n \n );\n}\n\nfunction ComboboxStatus({\n className,\n ...props\n}: ComboboxPrimitive.Status.Props) {\n return (\n \n );\n}\n\nfunction ComboboxCollection(props: ComboboxPrimitive.Collection.Props) {\n return (\n \n );\n}\n\nfunction ComboboxChips({\n className,\n children,\n startAddon,\n ...props\n}: ComboboxPrimitive.Chips.Props & {\n startAddon?: React.ReactNode;\n}) {\n const { chipsRef } = React.useContext(ComboboxContext);\n\n return (\n {\n const target = e.target as HTMLElement;\n const isChip = target.closest('[data-slot=\"combobox-chip\"]');\n if (isChip || !chipsRef?.current) return;\n e.preventDefault();\n const input: HTMLInputElement | null =\n chipsRef.current.querySelector(\"input\");\n if (input && !chipsRef.current.querySelector(\"input:focus\")) {\n input.focus();\n }\n }}\n ref={chipsRef as React.Ref | null}\n {...props}\n >\n {startAddon && (\n \n {startAddon}\n \n )}\n {children}\n \n );\n}\n\nfunction ComboboxChip({ children, ...props }: ComboboxPrimitive.Chip.Props) {\n return (\n \n {children}\n \n \n );\n}\n\nfunction ComboboxChipRemove(props: ComboboxPrimitive.ChipRemove.Props) {\n return (\n \n \n \n );\n}\n\nexport {\n Combobox,\n ComboboxInput,\n ComboboxTrigger,\n ComboboxPopup,\n ComboboxItem,\n ComboboxSeparator,\n ComboboxGroup,\n ComboboxGroupLabel,\n ComboboxEmpty,\n ComboboxValue,\n ComboboxList,\n ComboboxClear,\n ComboboxStatus,\n ComboboxRow,\n ComboboxCollection,\n ComboboxChips,\n ComboboxChip,\n};\n", "type": "registry:ui" } ] diff --git a/apps/ui/public/r/command.json b/apps/ui/public/r/command.json new file mode 100644 index 000000000..88db68254 --- /dev/null +++ b/apps/ui/public/r/command.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "command", + "type": "registry:ui", + "dependencies": [ + "@base-ui/react" + ], + "registryDependencies": [ + "@coss/autocomplete" + ], + "files": [ + { + "path": "registry/default/ui/command.tsx", + "content": "\"use client\";\n\nimport { Dialog as CommandDialogPrimitive } from \"@base-ui/react/dialog\";\nimport { SearchIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport {\n Autocomplete,\n AutocompleteCollection,\n AutocompleteEmpty,\n AutocompleteGroup,\n AutocompleteGroupLabel,\n AutocompleteInput,\n AutocompleteItem,\n AutocompleteList,\n AutocompleteSeparator,\n} from \"@/registry/default/ui/autocomplete\";\n\nconst CommandInputContext = React.createContext<{\n inputRef: React.RefObject | null;\n}>({\n inputRef: null,\n});\n\nconst CommandDialog = CommandDialogPrimitive.Root;\n\nconst CommandDialogPortal = CommandDialogPrimitive.Portal;\n\nfunction CommandDialogTrigger(props: CommandDialogPrimitive.Trigger.Props) {\n return (\n \n );\n}\n\nfunction CommandDialogBackdrop({\n className,\n ...props\n}: CommandDialogPrimitive.Backdrop.Props) {\n return (\n \n );\n}\n\nfunction CommandDialogViewport({\n className,\n ...props\n}: CommandDialogPrimitive.Viewport.Props) {\n return (\n \n );\n}\n\nfunction CommandDialogPopup({\n className,\n children,\n ...props\n}: CommandDialogPrimitive.Popup.Props) {\n const inputRef = React.useRef(null);\n\n return (\n \n \n \n \n \n {children}\n \n \n \n \n );\n}\n\nfunction Command({\n autoHighlight = \"always\",\n keepHighlight = true,\n open = true,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\nfunction CommandInput({\n className,\n placeholder = undefined,\n ...props\n}: React.ComponentProps) {\n const { inputRef } = React.useContext(CommandInputContext);\n\n return (\n
\n }\n {...props}\n />\n
\n );\n}\n\nfunction CommandList({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\nfunction CommandEmpty({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\nfunction CommandPanel({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n \n );\n}\n\nfunction CommandGroup({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\nfunction CommandGroupLabel({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\nfunction CommandCollection({\n ...props\n}: React.ComponentProps) {\n return ;\n}\n\nfunction CommandItem({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\nfunction CommandSeparator({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<\"kbd\">) {\n return (\n \n );\n}\n\nfunction CommandFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n \n );\n}\n\nexport {\n Command,\n CommandCollection,\n CommandDialog,\n CommandDialogPopup,\n CommandDialogTrigger,\n CommandEmpty,\n CommandFooter,\n CommandGroup,\n CommandGroupLabel,\n CommandInput,\n CommandItem,\n CommandList,\n CommandPanel,\n CommandSeparator,\n CommandShortcut,\n};\n", + "type": "registry:ui" + } + ] +} \ No newline at end of file diff --git a/apps/ui/public/r/dialog.json b/apps/ui/public/r/dialog.json index a59e2bea9..b31e1464d 100644 --- a/apps/ui/public/r/dialog.json +++ b/apps/ui/public/r/dialog.json @@ -12,7 +12,7 @@ "files": [ { "path": "registry/default/ui/dialog.tsx", - "content": "\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport { XIcon } from \"lucide-react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { ScrollArea } from \"@/registry/default/ui/scroll-area\";\n\nconst Dialog = DialogPrimitive.Root;\n\nconst DialogPortal = DialogPrimitive.Portal;\n\nfunction DialogTrigger(props: DialogPrimitive.Trigger.Props) {\n return ;\n}\n\nfunction DialogClose(props: DialogPrimitive.Close.Props) {\n return ;\n}\n\nfunction DialogBackdrop({\n className,\n ...props\n}: DialogPrimitive.Backdrop.Props) {\n return (\n \n );\n}\n\nfunction DialogViewport({\n className,\n ...props\n}: DialogPrimitive.Viewport.Props) {\n return (\n \n );\n}\n\nfunction DialogPopup({\n className,\n children,\n showCloseButton = true,\n ...props\n}: DialogPrimitive.Popup.Props & {\n showCloseButton?: boolean;\n}) {\n return (\n \n \n \n \n {children}\n {showCloseButton && (\n }\n >\n \n \n )}\n \n \n \n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n \n );\n}\n\nfunction DialogFooter({\n className,\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"div\"> & {\n variant?: \"default\" | \"bare\";\n}) {\n return (\n \n );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n return (\n \n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: DialogPrimitive.Description.Props) {\n return (\n \n );\n}\n\nfunction DialogPanel({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n \n \n \n );\n}\n\nexport {\n Dialog,\n DialogTrigger,\n DialogPortal,\n DialogClose,\n DialogBackdrop,\n DialogBackdrop as DialogOverlay,\n DialogPopup,\n DialogPopup as DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n DialogPanel,\n DialogViewport,\n};\n", + "content": "\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport { XIcon } from \"lucide-react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { ScrollArea } from \"@/registry/default/ui/scroll-area\";\n\nconst Dialog = DialogPrimitive.Root;\n\nconst DialogPortal = DialogPrimitive.Portal;\n\nfunction DialogTrigger(props: DialogPrimitive.Trigger.Props) {\n return ;\n}\n\nfunction DialogClose(props: DialogPrimitive.Close.Props) {\n return ;\n}\n\nfunction DialogBackdrop({\n className,\n ...props\n}: DialogPrimitive.Backdrop.Props) {\n return (\n \n );\n}\n\nfunction DialogViewport({\n className,\n ...props\n}: DialogPrimitive.Viewport.Props) {\n return (\n \n );\n}\n\nfunction DialogPopup({\n className,\n children,\n showCloseButton = true,\n bottomStickOnMobile = true,\n ...props\n}: DialogPrimitive.Popup.Props & {\n showCloseButton?: boolean;\n bottomStickOnMobile?: boolean;\n}) {\n return (\n \n \n \n \n {children}\n {showCloseButton && (\n }\n >\n \n \n )}\n \n \n \n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n \n );\n}\n\nfunction DialogFooter({\n className,\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"div\"> & {\n variant?: \"default\" | \"bare\";\n}) {\n return (\n \n );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n return (\n \n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: DialogPrimitive.Description.Props) {\n return (\n \n );\n}\n\nfunction DialogPanel({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n \n \n \n );\n}\n\nexport {\n Dialog,\n DialogTrigger,\n DialogPortal,\n DialogClose,\n DialogBackdrop,\n DialogBackdrop as DialogOverlay,\n DialogPopup,\n DialogPopup as DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n DialogPanel,\n DialogViewport,\n};\n", "type": "registry:ui" } ] diff --git a/apps/ui/public/r/input-group.json b/apps/ui/public/r/input-group.json index 0ef3caeb7..94cf4120f 100644 --- a/apps/ui/public/r/input-group.json +++ b/apps/ui/public/r/input-group.json @@ -9,7 +9,7 @@ "files": [ { "path": "registry/default/ui/input-group.tsx", - "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Input, type InputProps } from \"@/registry/default/ui/input\";\nimport { Textarea, type TextareaProps } from \"@/registry/default/ui/textarea\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n \n );\n}\n\nconst inputGroupAddonVariants = cva(\n \"[&_svg]:-mx-0.5 not-has-[button]:**:[svg]:not([class*='opacity-'])]:opacity-80 flex h-auto cursor-text select-none items-center justify-center gap-2 [&>kbd]:rounded-[calc(var(--radius)-5px)] in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4\",\n {\n defaultVariants: {\n align: \"inline-start\",\n },\n variants: {\n align: {\n \"block-end\":\n \"order-last w-full justify-start px-[calc(--spacing(3)-1px)] pb-[calc(--spacing(3)-1px)] [.border-t]:pt-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]\",\n \"block-start\":\n \"order-first w-full justify-start px-[calc(--spacing(3)-1px)] pt-[calc(--spacing(3)-1px)] [.border-b]:pb-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]\",\n \"inline-end\":\n \"has-[>:last-child[data-slot=badge]]:-me-1.5 has-[>button]:-me-2 order-last pe-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:me-[-0.35rem] [[data-size=sm]+&]:pe-[calc(--spacing(2.5)-1px)]\",\n \"inline-start\":\n \"has-[>:last-child[data-slot=badge]]:-ms-1.5 has-[>button]:-ms-2 order-first ps-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:ms-[-0.35rem] [[data-size=sm]+&]:ps-[calc(--spacing(2.5)-1px)]\",\n },\n },\n },\n);\n\nfunction InputGroupAddon({\n className,\n align = \"inline-start\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps) {\n return (\n {\n const target = e.target as HTMLElement;\n const isInteractive = target.closest(\"button, a\");\n if (isInteractive) return;\n e.preventDefault();\n const parent = e.currentTarget.parentElement;\n const input = parent?.querySelector<\n HTMLInputElement | HTMLTextAreaElement\n >(\"input, textarea\");\n if (input && !parent?.querySelector(\"input:focus, textarea:focus\")) {\n input.focus();\n }\n }}\n {...props}\n />\n );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n \n );\n}\n\nfunction InputGroupInput({ className, ...props }: InputProps) {\n return ;\n}\n\nfunction InputGroupTextarea({ className, ...props }: TextareaProps) {\n return