Live preview
"use client";
import { Calculator, Calendar, CreditCard, Settings, Smile, User } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
export default function CommandDemo() {
const [open, setOpen] = React.useState(false);
const [message, setMessage] = React.useState("");
const runCommand = (label: string) => {
setOpen(false);
setMessage(`${label} selected.`);
};
React.useEffect(() => {
const down = (event: KeyboardEvent) => {
if (event.key === "j" && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
setOpen((currentOpen) => !currentOpen);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);
return (
<div className="grid justify-items-start gap-3">
<Button type="button" onClick={() => setOpen(true)}>
Open command menu
</Button>
<p className="text-sm text-foreground">
Or press{" "}
<kbd className="pointer-events-none inline-flex h-5 items-center gap-1 rounded-base border-2 bg-main px-1.5 font-mono text-[10px] font-heading text-main-foreground select-none">
<span>Ctrl/Command</span>
<span>J</span>
</kbd>
</p>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem onSelect={() => runCommand("Calendar")}>
<Calendar />
<span>Calendar</span>
</CommandItem>
<CommandItem onSelect={() => runCommand("Emoji search")}>
<Smile />
<span>Search Emoji</span>
</CommandItem>
<CommandItem onSelect={() => runCommand("Calculator")}>
<Calculator />
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem onSelect={() => runCommand("Profile")}>
<User />
<span>Profile</span>
<CommandShortcut>Ctrl+P</CommandShortcut>
</CommandItem>
<CommandItem onSelect={() => runCommand("Billing")}>
<CreditCard />
<span>Billing</span>
<CommandShortcut>Ctrl+B</CommandShortcut>
</CommandItem>
<CommandItem onSelect={() => runCommand("Settings")}>
<Settings />
<span>Settings</span>
<CommandShortcut>Ctrl+S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
<output className="block min-h-5 text-sm" aria-live="polite">
{message}
</output>
</div>
);
}
#Installation
npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/command.json"use client";
import * as React from "react";
import { Command as CommandPrimitive, useCommandState } from "cmdk";
import { cn } from "@/lib/utils";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { SearchIcon } from "lucide-react";
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-[0px] border-2 border-border bg-main font-base text-main-foreground",
className,
)}
{...props}
/>
);
}
function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
className,
showCloseButton = false,
...props
}: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
title?: string;
description?: string;
className?: string;
showCloseButton?: boolean;
children?: React.ReactNode;
}) {
return (
<Dialog {...props}>
<DialogContent
className={cn("overflow-hidden rounded-[0px]! border-0 p-0 shadow-shadow", className)}
showCloseButton={showCloseButton}
>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<Command className="**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:mb-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-heading [&_[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">
{children}
</Command>
</DialogContent>
</Dialog>
);
}
function CommandInput({
className,
"aria-label": ariaLabel,
placeholder,
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div
data-slot="command-input-wrapper"
className="flex h-9 items-center gap-2 border-b-2 border-border px-3"
>
<SearchIcon className="size-4 shrink-0" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"flex h-10 w-full rounded-base bg-transparent py-3 text-sm outline-hidden placeholder:text-main-foreground placeholder:opacity-50 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
aria-label={ariaLabel ?? placeholder ?? "Command search"}
placeholder={placeholder}
{...props}
/>
</div>
);
}
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn(
"no-scrollbar max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto outline-none",
className,
)}
{...props}
/>
);
}
function CommandEmpty({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
const search = useCommandState((state) => state.search);
if (!search) {
return null;
}
return (
<CommandPrimitive.Empty
data-slot="command-empty"
className={cn("py-6 text-center text-sm", className)}
{...props}
/>
);
}
function CommandGroup({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"overflow-hidden p-2 text-main-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-base **:[[cmdk-group-heading]]:font-heading",
className,
)}
{...props}
/>
);
}
function CommandSeparator({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("-mx-1 h-0.5 bg-border", className)}
{...props}
/>
);
}
function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"relative flex cursor-default items-center gap-2 rounded-base px-2 py-1.5 text-sm text-main-foreground outline-0 outline-border select-none aria-selected:outline-2 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:outline-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="command-shortcut"
className={cn("ml-auto text-xs tracking-widest text-main-foreground", className)}
{...props}
/>
);
}
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};
#Usage
import { Calculator, Calendar, CreditCard, Settings, Smile, User } from "lucide-react";
import * as React from "react";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";const [open, setOpen] = React.useState(false);
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);<>
<p className="text-foreground text-sm">
Press{" "}
<kbd className="bg-main text-main-foreground pointer-events-none inline-flex h-5 items-center gap-1 rounded-base border-2 px-1.5 font-mono text-[10px] font-heading select-none">
<span className="text-xs">⌘</span>J
</kbd>
</p>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>
<Calendar />
<span>Calendar</span>
</CommandItem>
<CommandItem>
<Smile />
<span>Search Emoji</span>
</CommandItem>
<CommandItem>
<Calculator />
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>
<User />
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem>
<CreditCard />
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem>
<Settings />
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</>