From b09a75275a42e096058aafc048a2ca54d0b97e7c Mon Sep 17 00:00:00 2001 From: shadcn Date: Tue, 7 Feb 2023 21:39:08 +0400 Subject: [PATCH] feat(command): add command component --- apps/www/app/layout.tsx | 2 +- apps/www/components/command-menu.tsx | 115 +++++++++++++ apps/www/components/component-example.tsx | 10 +- .../components/examples/command/combobox.tsx | 90 ++++++++++ apps/www/components/examples/command/demo.tsx | 64 +++++++ .../components/examples/command/dialog.tsx | 86 ++++++++++ .../examples/command/dropdown-menu.tsx | 110 ++++++++++++ .../components/examples/command/popover.tsx | 124 ++++++++++++++ apps/www/components/examples/index.tsx | 10 ++ apps/www/components/search.tsx | 32 ---- apps/www/components/sidebar-nav.tsx | 9 +- apps/www/components/site-header.tsx | 9 +- apps/www/components/ui/command.tsx | 158 ++++++++++++++++++ apps/www/components/ui/dialog.tsx | 4 +- apps/www/components/ui/popover.tsx | 2 +- apps/www/config/docs.ts | 9 + .../www/content/docs/primitives/accordion.mdx | 1 + .../content/docs/primitives/alert-dialog.mdx | 1 + .../content/docs/primitives/aspect-ratio.mdx | 1 + apps/www/content/docs/primitives/avatar.mdx | 1 + apps/www/content/docs/primitives/button.mdx | 1 + apps/www/content/docs/primitives/checkbox.mdx | 1 + .../content/docs/primitives/collapsible.mdx | 1 + apps/www/content/docs/primitives/command.mdx | 121 ++++++++++++++ apps/www/content/docs/primitives/dialog.mdx | 1 + .../content/docs/primitives/dropdown-menu.mdx | 1 + .../content/docs/primitives/hover-card.mdx | 1 + apps/www/content/docs/primitives/input.mdx | 1 + apps/www/content/docs/primitives/label.mdx | 1 + apps/www/content/docs/primitives/menubar.mdx | 1 + .../docs/primitives/navigation-menu.mdx | 1 + apps/www/content/docs/primitives/popover.mdx | 1 + apps/www/content/docs/primitives/progress.mdx | 1 + .../content/docs/primitives/radio-group.mdx | 1 + .../content/docs/primitives/scroll-area.mdx | 1 + apps/www/content/docs/primitives/select.mdx | 1 + .../www/content/docs/primitives/separator.mdx | 1 + apps/www/content/docs/primitives/slider.mdx | 1 + apps/www/content/docs/primitives/tabs.mdx | 1 + apps/www/content/docs/primitives/textarea.mdx | 1 + apps/www/content/docs/primitives/toggle.mdx | 1 + apps/www/content/docs/primitives/tooltip.mdx | 1 + .../content/docs/primitives/typography.mdx | 1 + apps/www/contentlayer.config.js | 5 + apps/www/package.json | 1 + apps/www/types/nav.ts | 1 + pnpm-lock.yaml | 140 ++++++++++++++++ 47 files changed, 1084 insertions(+), 44 deletions(-) create mode 100644 apps/www/components/command-menu.tsx create mode 100644 apps/www/components/examples/command/combobox.tsx create mode 100644 apps/www/components/examples/command/demo.tsx create mode 100644 apps/www/components/examples/command/dialog.tsx create mode 100644 apps/www/components/examples/command/dropdown-menu.tsx create mode 100644 apps/www/components/examples/command/popover.tsx delete mode 100644 apps/www/components/search.tsx create mode 100644 apps/www/components/ui/command.tsx create mode 100644 apps/www/content/docs/primitives/command.mdx diff --git a/apps/www/app/layout.tsx b/apps/www/app/layout.tsx index aef39ac04e..8b86c52ee7 100644 --- a/apps/www/app/layout.tsx +++ b/apps/www/app/layout.tsx @@ -11,7 +11,7 @@ import { ThemeProvider } from "@/components/theme-provider" const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans", - display: 'swap', + display: "swap", }) interface RootLayoutProps { diff --git a/apps/www/components/command-menu.tsx b/apps/www/components/command-menu.tsx new file mode 100644 index 0000000000..e277ee111a --- /dev/null +++ b/apps/www/components/command-menu.tsx @@ -0,0 +1,115 @@ +"use client" + +import * as React from "react" +import { useRouter } from "next/navigation" +import { DialogProps } from "@radix-ui/react-alert-dialog" +import { allDocs } from "contentlayer/generated" +import { Circle, File, Laptop, Moon, SunMedium } from "lucide-react" +import { useTheme } from "next-themes" + +import { docsConfig } from "@/config/docs" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "@/components/ui/command" + +export function CommandMenu({ ...props }: DialogProps) { + const router = useRouter() + const [open, setOpen] = React.useState(false) + const { setTheme } = useTheme() + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && e.metaKey) { + setOpen((open) => !open) + } + } + + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) + }, []) + + const runCommand = React.useCallback((command: () => unknown) => { + setOpen(false) + command() + }, []) + + return ( + <> + + + + + No results found. + + {docsConfig.mainNav + .filter((navitem) => !navitem.external) + .map((navItem) => ( + { + runCommand(() => router.push(navItem.href as string)) + }} + > + + {navItem.title} + + ))} + + + {allDocs + .filter((doc) => doc.component) + .map((doc) => ( + { + runCommand(() => router.push(doc.slug)) + }} + > +
+ +
+ {doc.title} +
+ ))} +
+ + + runCommand(() => setTheme("light"))}> + + Light + + runCommand(() => setTheme("dark"))}> + + Dark + + runCommand(() => setTheme("system"))}> + + System + + +
+
+ + ) +} diff --git a/apps/www/components/component-example.tsx b/apps/www/components/component-example.tsx index 9e2a867ff5..524c25d06c 100644 --- a/apps/www/components/component-example.tsx +++ b/apps/www/components/component-example.tsx @@ -9,6 +9,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" interface ComponentExampleProps extends React.HTMLAttributes { extractClassname?: boolean extractedClassNames?: string + align?: "center" | "start" | "end" } export function ComponentExample({ @@ -16,6 +17,7 @@ export function ComponentExample({ className, extractClassname, extractedClassNames, + align = "center", ...props }: ComponentExampleProps) { const [Example, Code, ...Children] = React.Children.toArray( @@ -55,7 +57,13 @@ export function ComponentExample({ )} -
+
{Example}
diff --git a/apps/www/components/examples/command/combobox.tsx b/apps/www/components/examples/command/combobox.tsx new file mode 100644 index 0000000000..c90f2c2f1a --- /dev/null +++ b/apps/www/components/examples/command/combobox.tsx @@ -0,0 +1,90 @@ +"use client" + +import * as React from "react" +import { Check, ChevronsUpDown } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +const frameworks = [ + { + value: "next.js", + label: "Next.js", + }, + { + value: "sveltekit", + label: "SvelteKit", + }, + { + value: "nuxt.js", + label: "Nuxt.js", + }, + { + value: "remix", + label: "Remix", + }, + { + value: "astro", + label: "Astro", + }, +] + +export function CommandCombobox() { + const [open, setOpen] = React.useState(false) + const [value, setValue] = React.useState("") + + return ( + + + + + + + + No framework found. + + {frameworks.map((framework) => ( + { + setValue(currentValue === value ? "" : currentValue) + setOpen(false) + }} + > + + {framework.label} + + ))} + + + + + ) +} diff --git a/apps/www/components/examples/command/demo.tsx b/apps/www/components/examples/command/demo.tsx new file mode 100644 index 0000000000..b2baff109f --- /dev/null +++ b/apps/www/components/examples/command/demo.tsx @@ -0,0 +1,64 @@ +"use client" + +import { + Calculator, + Calendar, + CreditCard, + Settings, + Smile, + User, +} from "lucide-react" + +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "@/components/ui/command" + +export function CommandDemo() { + return ( + + + + No results found. + + + + Calendar + + + + Search Emoji + + + + Calculator + + + + + + + Profile + ⌘P + + + + Billing + ⌘B + + + + Settings + ⌘S + + + + + ) +} diff --git a/apps/www/components/examples/command/dialog.tsx b/apps/www/components/examples/command/dialog.tsx new file mode 100644 index 0000000000..967174deff --- /dev/null +++ b/apps/www/components/examples/command/dialog.tsx @@ -0,0 +1,86 @@ +"use client" + +import * as React from "react" +import { + Calculator, + Calendar, + CreditCard, + Settings, + Smile, + User, +} from "lucide-react" + +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "@/components/ui/command" + +export function CommandDialogDemo() { + const [open, setOpen] = React.useState(false) + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "j" && e.metaKey) { + setOpen((open) => !open) + } + } + + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) + }, []) + + return ( + <> +

+ Press{" "} + + J + +

+ + + + No results found. + + + + Calendar + + + + Search Emoji + + + + Calculator + + + + + + + Profile + ⌘P + + + + Billing + ⌘B + + + + Settings + ⌘S + + + + + + ) +} diff --git a/apps/www/components/examples/command/dropdown-menu.tsx b/apps/www/components/examples/command/dropdown-menu.tsx new file mode 100644 index 0000000000..6423a6a706 --- /dev/null +++ b/apps/www/components/examples/command/dropdown-menu.tsx @@ -0,0 +1,110 @@ +"use client" + +import * as React from "react" +import { Calendar, MoreHorizontal, Pen, Tags, Trash, User } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" + +const labels = [ + "feature", + "bug", + "enhancement", + "documentation", + "design", + "question", + "maintenance", +] + +export function CommandDropdownMenu() { + const [label, setLabel] = React.useState("feature") + const [open, setOpen] = React.useState(false) + + return ( +
+

+ + {label} + + Create a new project +

+ + + + + + Actions + + + + Assign to... + + + + Set due date... + + + + + + Apply label + + + + + + No label found. + + {labels.map((label) => ( + { + setLabel(value) + setOpen(false) + }} + > + {label} + + ))} + + + + + + + + + Delete + ⌘⌫ + + + + +
+ ) +} diff --git a/apps/www/components/examples/command/popover.tsx b/apps/www/components/examples/command/popover.tsx new file mode 100644 index 0000000000..1500534dff --- /dev/null +++ b/apps/www/components/examples/command/popover.tsx @@ -0,0 +1,124 @@ +"use client" + +import * as React from "react" +import { + ArrowUpCircle, + CheckCircle2, + Circle, + HelpCircle, + LucideIcon, + XCircle, +} from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +type Status = { + value: string + label: string + icon: LucideIcon +} + +const statuses: Status[] = [ + { + value: "backlog", + label: "Backlog", + icon: HelpCircle, + }, + { + value: "todo", + label: "Todo", + icon: Circle, + }, + { + value: "in progress", + label: "In Progress", + icon: ArrowUpCircle, + }, + { + value: "done", + label: "Done", + icon: CheckCircle2, + }, + { + value: "canceled", + label: "Canceled", + icon: XCircle, + }, +] + +export function CommandPopover() { + const [open, setOpen] = React.useState(false) + const [selectedStatus, setSelectedStatus] = React.useState( + null + ) + + return ( +
+

Status

+ + + + + + + + + No results found. + + {statuses.map((status) => ( + { + setSelectedStatus( + statuses.find((priority) => priority.value === value) || + null + ) + setOpen(false) + }} + > + + {status.label} + + ))} + + + + + +
+ ) +} diff --git a/apps/www/components/examples/index.tsx b/apps/www/components/examples/index.tsx index 2d6a808a98..7b53300f29 100644 --- a/apps/www/components/examples/index.tsx +++ b/apps/www/components/examples/index.tsx @@ -14,6 +14,11 @@ import { CheckboxDemo } from "@/components/examples/checkbox/demo" import { CheckboxDisabled } from "@/components/examples/checkbox/disabled" import { CheckboxWithText } from "@/components/examples/checkbox/with-text" import { CollapsibleDemo } from "@/components/examples/collapsible/demo" +import { CommandCombobox } from "@/components/examples/command/combobox" +import { CommandDemo } from "@/components/examples/command/demo" +import { CommandDialogDemo } from "@/components/examples/command/dialog" +import { CommandDropdownMenu } from "@/components/examples/command/dropdown-menu" +import { CommandPopover } from "@/components/examples/command/popover" import { ContextMenuDemo } from "@/components/examples/context-menu/demo" import { DialogDemo } from "@/components/examples/dialog/demo" import { DropdownMenuCheckboxes } from "@/components/examples/dropdown-menu/checkboxes" @@ -81,6 +86,11 @@ export const examples = { CheckboxDisabled, CheckboxWithText, CollapsibleDemo, + CommandDemo, + CommandDialogDemo, + CommandCombobox, + CommandPopover, + CommandDropdownMenu, ContextMenuDemo, DialogDemo, DropdownMenuCheckboxes, diff --git a/apps/www/components/search.tsx b/apps/www/components/search.tsx deleted file mode 100644 index a6b20170d3..0000000000 --- a/apps/www/components/search.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client" - -import * as React from "react" - -import { cn } from "@/lib/utils" -import { Input } from "@/components/ui/input" - -interface DocsSearchProps extends React.HTMLAttributes {} - -export function DocsSearch({ className, ...props }: DocsSearchProps) { - function onSubmit(event: React.SyntheticEvent) { - event.preventDefault() - } - - return ( -
- - - K - -
- ) -} diff --git a/apps/www/components/sidebar-nav.tsx b/apps/www/components/sidebar-nav.tsx index d15179083f..f98960e7c7 100644 --- a/apps/www/components/sidebar-nav.tsx +++ b/apps/www/components/sidebar-nav.tsx @@ -16,7 +16,7 @@ export function DocsSidebarNav({ items }: DocsSidebarNavProps) { return items.length ? (
{items.map((item, index) => ( -
+

{item.title}

@@ -46,7 +46,7 @@ export function DocsSidebarNavItems({ key={index} href={item.href} className={cn( - "flex w-full items-center rounded-md p-2 hover:underline", + "group flex w-full items-center rounded-md py-1.5 px-2 hover:bg-slate-50 dark:hover:bg-slate-800", item.disabled && "cursor-not-allowed opacity-60", { "bg-slate-100 dark:bg-slate-800": pathname === item.href, @@ -56,6 +56,11 @@ export function DocsSidebarNavItems({ rel={item.external ? "noreferrer" : ""} > {item.title} + {item.label && ( + + {item.label} + + )} ) : ( -
-
- +
+
+