Skip to content

Commit

Permalink
replace combobox command menu with cmdk-solid (#83)
Browse files Browse the repository at this point in the history
Co-authored-by: Stefan E-K <[email protected]>
  • Loading branch information
create-signal and stefan-karger authored Apr 29, 2024
1 parent 73a7b22 commit f281cb0
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 273 deletions.
3 changes: 2 additions & 1 deletion apps/docs/public/registry/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@
{
"name": "command",
"dependencies": [
"@kobalte/core"
"@kobalte/core",
"cmdk-solid"
],
"registryDependencies": [
"dialog"
Expand Down
5 changes: 3 additions & 2 deletions apps/docs/public/registry/ui/command.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
{
"name": "command",
"dependencies": [
"@kobalte/core"
"@kobalte/core",
"cmdk-solid"
],
"registryDependencies": [
"dialog"
],
"files": [
{
"name": "command.tsx",
"content": "import type { ComponentProps, VoidComponent, VoidProps } from \"solid-js\"\nimport { splitProps, type ParentComponent } from \"solid-js\"\n\nimport type { Dialog as DialogPrimitive } from \"@kobalte/core\"\nimport { Combobox as ComboboxPrimitive } from \"@kobalte/core\"\n\nimport { cn } from \"~/lib/utils\"\nimport { Dialog, DialogContent } from \"~/registry/ui/dialog\"\n\ntype CommandProps<Option, OptGroup> = Omit<\n ComboboxPrimitive.ComboboxRootProps<Option, OptGroup>,\n | \"open\"\n | \"defaultOpen\"\n | \"multiple\"\n | \"value\"\n | \"defaultValue\"\n | \"removeOnBackspace\"\n | \"readOnly\"\n | \"allowsEmptyCollection\"\n>\n\nconst Command = <Option, OptGroup>(props: CommandProps<Option, OptGroup>) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <ComboboxPrimitive.Root\n // force render list\n open\n // @ts-expect-error -- prevent select\n value=\"\"\n allowsEmptyCollection\n class={cn(\n \"flex size-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n local.class\n )}\n {...rest}\n />\n )\n}\n\nconst CommandInput: VoidComponent<ComboboxPrimitive.ComboboxInputProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <ComboboxPrimitive.Control class=\"flex items-center border-b px-3\" cmdk-input-wrapper=\"\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n class=\"mr-2 size-4 shrink-0 opacity-50\"\n >\n <path d=\"M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0\" />\n <path d=\"M21 21l-6 -6\" />\n </svg>\n <ComboboxPrimitive.Input\n cmdk-input=\"\"\n class={cn(\n \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n local.class\n )}\n {...rest}\n />\n </ComboboxPrimitive.Control>\n )\n}\n\nconst CommandList = <Option, OptGroup>(\n props: VoidProps<ComboboxPrimitive.ComboboxListboxProps<Option, OptGroup>>\n) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <ComboboxPrimitive.Listbox\n cmdk-list=\"\"\n class={cn(\"max-h-[300px] overflow-y-auto overflow-x-hidden p-1\", local.class)}\n {...rest}\n />\n )\n}\n\nconst CommandItem: ParentComponent<ComboboxPrimitive.ComboboxItemProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"item\"])\n\n return (\n <ComboboxPrimitive.Item\n item={local.item}\n cmdk-item=\"\"\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50\",\n local.class\n )}\n {...rest}\n />\n )\n}\n\nconst CommandItemLabel = ComboboxPrimitive.ItemLabel\n\nconst CommandHeading: ParentComponent<ComboboxPrimitive.ComboboxSectionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <ComboboxPrimitive.Section\n cmdk-heading=\"\"\n class={cn(\n \"px-2 py-1.5 text-xs font-medium text-muted-foreground [&:not(&:first-of-type)]:mt-2\",\n local.class\n )}\n {...rest}\n />\n )\n}\n\nconst CommandItemShortcut: ParentComponent<ComponentProps<\"span\">> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <span\n class={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", local.class)}\n {...rest}\n />\n )\n}\n\ntype CommandDialogProps<Option, OptGroup> = DialogPrimitive.DialogRootProps &\n CommandProps<Option, OptGroup>\n\nconst CommandDialog = <Option, OptGroup>(props: CommandDialogProps<Option, OptGroup>) => {\n const [local, rest] = splitProps(props, [\"children\"])\n\n return (\n <Dialog {...rest}>\n <DialogContent class=\"overflow-hidden p-0\">\n <Command\n class=\"[&_[cmdk-heading]]:px-2 [&_[cmdk-heading]]:font-medium [&_[cmdk-heading]]:text-muted-foreground [&_[cmdk-input-wrapper]_svg]:size-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:size-5 [&_[cmdk-list]:not([hidden])_~[cmdk-list]]:pt-0 [&_[cmdk-list]]:px-2\"\n {...rest}\n >\n {local.children}\n </Command>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport {\n Command,\n CommandInput,\n CommandList,\n CommandItem,\n CommandItemLabel,\n CommandItemShortcut,\n CommandHeading,\n CommandDialog\n}\n"
"content": "import type { ComponentProps, VoidComponent } from \"solid-js\"\nimport { splitProps, type ParentComponent } from \"solid-js\"\n\nimport type { Dialog as DialogPrimitive } from \"@kobalte/core\"\nimport {\n CommandEmptyProps,\n CommandGroupProps,\n CommandInputProps,\n CommandItemProps,\n CommandListProps,\n Command as CommandPrimitive,\n CommandRootProps,\n CommandSeparatorProps\n} from \"cmdk-solid\"\n\nimport { cn } from \"~/lib/utils\"\nimport { Dialog, DialogContent } from \"~/registry/ui/dialog\"\n\nconst Command: ParentComponent<CommandRootProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <CommandPrimitive\n class={cn(\n \"flex size-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n local.class\n )}\n {...rest}\n />\n )\n}\n\ntype CommandDialogProps = DialogPrimitive.DialogRootProps\n\nconst CommandDialog: ParentComponent<CommandDialogProps> = (props) => {\n const [local, rest] = splitProps(props, [\"children\"])\n\n return (\n <Dialog {...rest}>\n <DialogContent class=\"overflow-hidden p-0\">\n <Command\n class=\"[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:size-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:size-5\"\n {...local}\n />\n </DialogContent>\n </Dialog>\n )\n}\n\nconst CommandInput: VoidComponent<CommandInputProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <div class=\"flex items-center border-b px-3\" cmdk-input-wrapper=\"\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n class=\"mr-2 size-4 shrink-0 opacity-50\"\n >\n <path d=\"M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0\" />\n <path d=\"M21 21l-6 -6\" />\n </svg>\n <CommandPrimitive.Input\n class={cn(\n \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n local.class\n )}\n {...rest}\n />\n </div>\n )\n}\n\nconst CommandList: ParentComponent<CommandListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <CommandPrimitive.List\n class={cn(\"max-h-[300px] overflow-y-auto overflow-x-hidden\", local.class)}\n {...rest}\n />\n )\n}\n\nconst CommandEmpty: ParentComponent<CommandEmptyProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return <CommandPrimitive.Empty class={cn(\"py-6 text-center text-sm\", local.class)} {...rest} />\n}\n\nconst CommandGroup: ParentComponent<CommandGroupProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <CommandPrimitive.Group\n class={cn(\n \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n local.class\n )}\n {...rest}\n />\n )\n}\n\nconst CommandSeparator: VoidComponent<CommandSeparatorProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return <CommandPrimitive.Separator class={cn(\"h-px bg-border\", local.class)} {...rest} />\n}\n\nconst CommandItem: ParentComponent<CommandItemProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <CommandPrimitive.Item\n cmdk-item=\"\"\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50\",\n local.class\n )}\n {...rest}\n />\n )\n}\n\nconst CommandShortcut: ParentComponent<ComponentProps<\"span\">> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"])\n\n return (\n <span\n class={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", local.class)}\n {...rest}\n />\n )\n}\n\nexport {\n Command,\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator\n}\n"
}
],
"type": "ui"
Expand Down
125 changes: 42 additions & 83 deletions apps/docs/src/registry/example/command-demo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { type JSXElement } from "solid-js"

import {
IconCalendar,
IconMail,
Expand All @@ -10,93 +8,54 @@ import {
} from "~/components/icons"
import {
Command,
CommandHeading,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandItemLabel,
CommandItemShortcut,
CommandList
CommandList,
CommandSeparator,
CommandShortcut
} from "~/registry/ui/command"

type ListOption = {
icon: JSXElement
label: string
value: string
shortcut?: JSXElement
}

type List = {
label: string
options: ListOption[]
}

export default function CommandDemo() {
const data: List[] = [
{
label: "Suggestions",
options: [
{
icon: <IconCalendar class="mr-2 size-4" />,
label: "Calendar",
value: "Calendar"
},
{
icon: <IconSmile class="mr-2 size-4" />,
label: "Search emoji",
value: "Search emoji"
},
{
icon: <IconRocket class="mr-2 size-4" />,
label: "Launch",
value: "Launch"
}
]
},
{
label: "Settings",
options: [
{
icon: <IconUser class="mr-2 size-4" />,
label: "Profile",
value: "Profile",
shortcut: <CommandItemShortcut>⌘P</CommandItemShortcut>
},
{
icon: <IconMail class="mr-2 size-4" />,
label: "Mail",
value: "Mail",
shortcut: <CommandItemShortcut>⌘B</CommandItemShortcut>
},
{
icon: <IconSettings class="mr-2 size-4" />,
label: "Setting",
value: "Setting",
shortcut: <CommandItemShortcut>⌘S</CommandItemShortcut>
}
]
}
]

return (
<Command<ListOption, List>
options={data}
optionValue="value"
optionTextValue="label"
optionLabel="label"
optionGroupChildren="options"
placeholder="Type a command or search..."
itemComponent={(props) => (
<CommandItem item={props.item}>
{props.item.rawValue.icon}
<CommandItemLabel>{props.item.rawValue.label}</CommandItemLabel>
{props.item.rawValue.shortcut}
</CommandItem>
)}
sectionComponent={(props) => <CommandHeading>{props.section.rawValue.label}</CommandHeading>}
class="rounded-lg border shadow-md"
>
<CommandInput />
<CommandList />
<Command class="rounded-lg border shadow-md">
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>
<IconCalendar class="mr-2 size-4" />
<span>Calendar</span>
</CommandItem>
<CommandItem>
<IconSmile class="mr-2 size-4" />
<span>Search Emoji</span>
</CommandItem>
<CommandItem>
<IconRocket class="mr-2 size-4" />
<span>Launch</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>
<IconUser class="mr-2 size-4" />
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem>
<IconMail class="mr-2 size-4" />
<span>Mail</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem>
<IconSettings class="mr-2 size-4" />
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
)
}
134 changes: 45 additions & 89 deletions apps/docs/src/registry/example/command-dialog-demo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createEffect, createSignal, onCleanup, type JSXElement } from "solid-js"
import { createEffect, createSignal, onCleanup } from "solid-js"

import {
IconCalendar,
Expand All @@ -10,73 +10,16 @@ import {
} from "~/components/icons"
import {
CommandDialog,
CommandHeading,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandItemLabel,
CommandItemShortcut,
CommandList
CommandList,
CommandSeparator,
CommandShortcut
} from "~/registry/ui/command"

type ListOption = {
icon: JSXElement
label: string
value: string
shortcut?: JSXElement
}

type List = {
label: string
options: ListOption[]
}

export default function CommandDemo() {
const data: List[] = [
{
label: "Suggestions",
options: [
{
icon: <IconCalendar class="mr-2 size-4" />,
label: "Calendar",
value: "Calendar"
},
{
icon: <IconSmile class="mr-2 size-4" />,
label: "Search emoji",
value: "Search emoji"
},
{
icon: <IconRocket class="mr-2 size-4" />,
label: "Launch",
value: "Launch"
}
]
},
{
label: "Settings",
options: [
{
icon: <IconUser class="mr-2 size-4" />,
label: "Profile",
value: "Profile",
shortcut: <CommandItemShortcut>⌘P</CommandItemShortcut>
},
{
icon: <IconMail class="mr-2 size-4" />,
label: "Mail",
value: "Mail",
shortcut: <CommandItemShortcut>⌘B</CommandItemShortcut>
},
{
icon: <IconSettings class="mr-2 size-4" />,
label: "Setting",
value: "Setting",
shortcut: <CommandItemShortcut>⌘S</CommandItemShortcut>
}
]
}
]

export default function CommandDialogDemo() {
const [open, setOpen] = createSignal(false)

createEffect(() => {
Expand All @@ -89,9 +32,7 @@ export default function CommandDemo() {

document.addEventListener("keydown", down)

onCleanup(() => {
document.removeEventListener("keydown", down)
})
onCleanup(() => document.removeEventListener("keydown", down))
})

return (
Expand All @@ -102,28 +43,43 @@ export default function CommandDemo() {
<span class="text-xs"></span>J
</kbd>
</p>
<CommandDialog<ListOption, List>
options={data}
optionValue="value"
optionTextValue="label"
optionLabel="label"
optionGroupChildren="options"
placeholder="Type a command or search..."
itemComponent={(props) => (
<CommandItem item={props.item}>
{props.item.rawValue.icon}
<CommandItemLabel>{props.item.rawValue.label}</CommandItemLabel>
{props.item.rawValue.shortcut}
</CommandItem>
)}
sectionComponent={(props) => (
<CommandHeading>{props.section.rawValue.label}</CommandHeading>
)}
open={open()}
onOpenChange={setOpen}
>
<CommandInput />
<CommandList />
<CommandDialog open={open()} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>
<IconCalendar class="mr-2 size-4" />
<span>Calendar</span>
</CommandItem>
<CommandItem>
<IconSmile class="mr-2 size-4" />
<span>Search Emoji</span>
</CommandItem>
<CommandItem>
<IconRocket class="mr-2 size-4" />
<span>Launch</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>
<IconUser class="mr-2 size-4" />
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem>
<IconMail class="mr-2 size-4" />
<span>Mail</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem>
<IconSettings class="mr-2 size-4" />
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</>
)
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/registry/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const ui: Registry = [
{
name: "command",
type: "ui",
dependencies: ["@kobalte/core"],
dependencies: ["@kobalte/core", "cmdk-solid"],
registryDependencies: ["dialog"],
files: ["ui/command.tsx"]
},
Expand Down
Loading

0 comments on commit f281cb0

Please sign in to comment.