Skip to content

Commit

Permalink
feat: added command component
Browse files Browse the repository at this point in the history
  • Loading branch information
sek-consulting committed Mar 13, 2024
1 parent 30e267a commit dde9cbb
Show file tree
Hide file tree
Showing 10 changed files with 541 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"printWidth": 100,
"trailingComma": "none",
"importOrder": [
"<BUILTIN_MODULES>",
"",
"^(solid-js/(.*)$)|^(solid-js$)",
"",
"<THIRD_PARTY_MODULES>",
Expand Down
14 changes: 14 additions & 0 deletions apps/docs/public/registry/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,20 @@
],
"type": "ui"
},
{
"name": "command",
"dependencies": [
"@kobalte/core",
"solid-icons"
],
"registryDependencies": [
"dialog"
],
"files": [
"ui/combobox.tsx"
],
"type": "ui"
},
{
"name": "context-menu",
"dependencies": [
Expand Down
17 changes: 17 additions & 0 deletions apps/docs/public/registry/ui/command.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "command",
"dependencies": [
"@kobalte/core",
"solid-icons"
],
"registryDependencies": [
"dialog"
],
"files": [
{
"name": "combobox.tsx",
"content": "import type { Component } from \"solid-js\"\nimport { splitProps } from \"solid-js\"\n\nimport { Combobox as ComboboxPrimitive } from \"@kobalte/core\"\nimport { TbCheck, TbSelector } from \"solid-icons/tb\"\n\nimport { cn } from \"~/lib/utils\"\n\nconst Combobox = ComboboxPrimitive.Root\n\nconst ComboboxItem: Component<ComboboxPrimitive.ComboboxItemProps> = (props) => {\n const [, rest] = splitProps(props, [\"class\"])\n return (\n <ComboboxPrimitive.Item\n class={cn(\n \"relative flex cursor-default select-none items-center justify-between 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 props.class\n )}\n {...rest}\n />\n )\n}\n\nconst ComboboxItemLabel = ComboboxPrimitive.ItemLabel\n\nconst ComboboxItemIndicator: Component<ComboboxPrimitive.ComboboxItemIndicatorProps> = (props) => {\n const [, rest] = splitProps(props, [\"children\"])\n return (\n <ComboboxPrimitive.ItemIndicator {...rest}>\n {props.children ?? <TbCheck />}\n </ComboboxPrimitive.ItemIndicator>\n )\n}\n\nconst ComboboxSection: Component<ComboboxPrimitive.ComboboxSectionProps> = (props) => {\n const [, rest] = splitProps(props, [\"class\"])\n return (\n <ComboboxPrimitive.Section\n class={cn(\n \"overflow-hidden p-1 px-2 py-1.5 text-xs font-medium text-muted-foreground \",\n props.class\n )}\n {...rest}\n />\n )\n}\n\n// due to the generic typing this needs to be a function\nfunction ComboboxControl<T>(props: ComboboxPrimitive.ComboboxControlProps<T>) {\n const [, rest] = splitProps(props, [\"class\"])\n return (\n <ComboboxPrimitive.Control\n class={cn(\"flex items-center rounded-md border px-3\", props.class)}\n {...rest}\n />\n )\n}\n\nconst ComboboxInput: Component<ComboboxPrimitive.ComboboxInputProps> = (props) => {\n const [, rest] = splitProps(props, [\"class\"])\n return (\n <ComboboxPrimitive.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 props.class\n )}\n {...rest}\n />\n )\n}\n\nconst ComboboxHiddenSelect = ComboboxPrimitive.HiddenSelect\n\nconst ComboboxTrigger: Component<ComboboxPrimitive.ComboboxTriggerProps> = (props) => {\n const [, rest] = splitProps(props, [\"class\", \"children\"])\n return (\n <ComboboxPrimitive.Trigger class={cn(\"size-4 opacity-50\", props.class)} {...rest}>\n <ComboboxPrimitive.Icon>{props.children ?? <TbSelector />}</ComboboxPrimitive.Icon>\n </ComboboxPrimitive.Trigger>\n )\n}\n\nconst ComboboxContent: Component<ComboboxPrimitive.ComboboxContentProps> = (props) => {\n const [, rest] = splitProps(props, [\"class\"])\n return (\n <ComboboxPrimitive.Portal>\n <ComboboxPrimitive.Content\n class={cn(\n \"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md animate-in fade-in-80\",\n props.class\n )}\n {...rest}\n >\n <ComboboxPrimitive.Listbox class=\"m-0 p-1\" />\n </ComboboxPrimitive.Content>\n </ComboboxPrimitive.Portal>\n )\n}\n\nexport {\n Combobox,\n ComboboxItem,\n ComboboxItemLabel,\n ComboboxItemIndicator,\n ComboboxSection,\n ComboboxControl,\n ComboboxTrigger,\n ComboboxInput,\n ComboboxHiddenSelect,\n ComboboxContent\n}\n"
}
],
"type": "ui"
}
21 changes: 21 additions & 0 deletions apps/docs/src/__registry__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ export const Index: Record<string, any> = {
component: lazy(() => import("~/registry/ui/combobox")),
files: ["registry/ui/combobox.tsx"],
},
"command": {
name: "command",
type: "ui",
registryDependencies: ["dialog"],
component: lazy(() => import("~/registry/ui/command")),
files: ["registry/ui/combobox.tsx"],
},
"context-menu": {
name: "context-menu",
type: "ui",
Expand Down Expand Up @@ -405,6 +412,20 @@ export const Index: Record<string, any> = {
component: lazy(() => import("~/registry/example/combobox-demo")),
files: ["registry/example/combobox-demo.tsx"],
},
"command-demo": {
name: "command-demo",
type: "example",
registryDependencies: ["dialog"],
component: lazy(() => import("~/registry/example/command-demo")),
files: ["registry/example/command-demo.tsx"],
},
"command-dialog-demo": {
name: "command-dialog-demo",
type: "example",
registryDependencies: ["dialog"],
component: lazy(() => import("~/registry/example/command-dialog-demo")),
files: ["registry/example/command-dialog-demo.tsx"],
},
"context-menu-demo": {
name: "context-menu-demo",
type: "example",
Expand Down
6 changes: 5 additions & 1 deletion apps/docs/src/config/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export const docsConfig: Config = {
title: "Combobox",
href: "/docs/components/combobox"
},
{
title: "Command",
href: "/docs/components/command"
},
{
title: "Context Menu",
href: "/docs/components/context-menu"
Expand Down Expand Up @@ -165,7 +169,7 @@ export const docsConfig: Config = {
href: "/docs/components/menubar"
},
{
title: "NumberField",
title: "Number Field",
href: "/docs/components/number-field"
},
{
Expand Down
96 changes: 96 additions & 0 deletions apps/docs/src/registry/example/command-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { type JSXElement } from "solid-js"

import { TbCalendar, TbMail, TbMoodSmile, TbRocket, TbSettings, TbUser } from "solid-icons/tb"

import {
Command,
CommandHeading,
CommandInput,
CommandItem,
CommandItemLabel,
CommandList,
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: <TbCalendar class="mr-2 size-4" />,
label: "Calendar",
value: "Calendar"
},
{
icon: <TbMoodSmile class="mr-2 size-4" />,
label: "Search emoji",
value: "Search emoji"
},
{
icon: <TbRocket class="mr-2 size-4" />,
label: "Launch",
value: "Launch"
}
]
},
{
label: "Settings",
options: [
{
icon: <TbUser class="mr-2 size-4" />,
label: "Profile",
value: "Profile",
shortcut: <CommandShortcut>⌘P</CommandShortcut>
},
{
icon: <TbMail class="mr-2 size-4" />,
label: "Mail",
value: "Mail",
shortcut: <CommandShortcut>⌘B</CommandShortcut>
},
{
icon: <TbSettings class="mr-2 size-4" />,
label: "Setting",
value: "Setting",
shortcut: <CommandShortcut>⌘S</CommandShortcut>
}
]
}
]

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>
)
}
124 changes: 124 additions & 0 deletions apps/docs/src/registry/example/command-dialog-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { createEffect, createSignal, onCleanup, type JSXElement } from "solid-js"

import { TbCalendar, TbMail, TbMoodSmile, TbRocket, TbSettings, TbUser } from "solid-icons/tb"

import {
CommandDialog,
CommandHeading,
CommandInput,
CommandItem,
CommandItemLabel,
CommandList,
CommandShortcut
} from "~/registry/ui/command"

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

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

export default function CommandDialogDemo() {
const data: List[] = [
{
label: "Suggestions",
options: [
{
icon: <TbCalendar class="mr-2 size-4" />,
label: "Calendar",
value: "Calendar"
},
{
icon: <TbMoodSmile class="mr-2 size-4" />,
label: "Search emoji",
value: "Search emoji"
},
{
icon: <TbRocket class="mr-2 size-4" />,
label: "Launch",
value: "Launch"
}
]
},
{
label: "Settings",
options: [
{
icon: <TbUser class="mr-2 size-4" />,
label: "Profile",
value: "Profile",
shortcut: <CommandShortcut>⌘P</CommandShortcut>
},
{
icon: <TbMail class="mr-2 size-4" />,
label: "Mail",
value: "Mail",
shortcut: <CommandShortcut>⌘B</CommandShortcut>
},
{
icon: <TbSettings class="mr-2 size-4" />,
label: "Setting",
value: "Setting",
shortcut: <CommandShortcut>⌘S</CommandShortcut>
}
]
}
]

const [open, setOpen] = createSignal(false)

createEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setOpen((open) => !open)
}
}

document.addEventListener("keydown", down)

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

return (
<>
<p class="text-sm text-muted-foreground">
Press{" "}
<kbd class="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">
<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>
</>
)
}
19 changes: 19 additions & 0 deletions apps/docs/src/registry/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ const ui: Registry = [
dependencies: ["@kobalte/core", "solid-icons"],
files: ["ui/combobox.tsx"]
},
{
name: "command",
type: "ui",
dependencies: ["@kobalte/core", "solid-icons"],
registryDependencies: ["dialog"],
files: ["ui/combobox.tsx"]
},
{
name: "context-menu",
type: "ui",
Expand Down Expand Up @@ -329,6 +336,18 @@ const examples: Registry = [
type: "example",
files: ["example/combobox-demo.tsx"]
},
{
name: "command-demo",
type: "example",
registryDependencies: ["dialog"],
files: ["example/command-demo.tsx"]
},
{
name: "command-dialog-demo",
type: "example",
registryDependencies: ["dialog"],
files: ["example/command-dialog-demo.tsx"]
},
{
name: "context-menu-demo",
type: "example",
Expand Down
Loading

0 comments on commit dde9cbb

Please sign in to comment.