Command
Spotlight: the cmd-K command palette.
Installation
npx shadcn@latest add https://cupertino-ui.baltoon.jp/r/command.jsonRequires the theme tokens — see Installation if this is your first component.
Source
"use client";
import * as React from "react";
import { Command as CommandPrimitive } from "cmdk";
import { SearchIcon } from "lucide-react";
import { Dialog as DialogPrimitive } from "radix-ui";
import { cn } from "@/lib/utils";
/**
* Spotlight. A command palette on thick material with the big
* rounded search field; open the dialog with ⌘K.
*/
function Command({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
"material-thick flex h-full w-full flex-col overflow-hidden rounded-[16px] text-label shadow-[var(--shadow-window)]",
className
)}
{...props}
/>
);
}
/** Spotlight-style ⌘K dialog. */
function CommandDialog({
children,
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Root> & {
className?: string;
}) {
return (
<DialogPrimitive.Root data-slot="command-dialog" {...props}>
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay className="fixed inset-0 z-50 bg-black/20 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0" />
<DialogPrimitive.Content
className={cn(
"fixed left-1/2 top-[20%] z-50 w-[min(560px,calc(100vw-32px))] -translate-x-1/2 outline-none data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
className
)}
>
<DialogPrimitive.Title className="sr-only">
Search
</DialogPrimitive.Title>
<Command className="max-h-[420px]">{children}</Command>
</DialogPrimitive.Content>
</DialogPrimitive.Portal>
</DialogPrimitive.Root>
);
}
function CommandInput({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div
data-slot="command-input-wrapper"
className="flex items-center gap-3 px-4 py-1"
>
<SearchIcon className="size-6 shrink-0 text-secondary-label" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"h-12 w-full bg-transparent text-[22px] tracking-[-0.26px] text-label outline-none placeholder:text-tertiary-label disabled:opacity-40",
className
)}
{...props}
/>
</div>
);
}
function CommandList({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn(
"max-h-80 scroll-py-1 overflow-y-auto overflow-x-hidden p-1.5 shadow-[0_-0.5px_0_0_var(--separator)] empty:p-0 empty:shadow-none",
className
)}
{...props}
/>
);
}
function CommandEmpty({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return (
<CommandPrimitive.Empty
data-slot="command-empty"
className={cn(
"py-8 text-center text-subheadline text-secondary-label",
className
)}
{...props}
/>
);
}
function CommandGroup({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"overflow-hidden [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:pb-1 [&_[cmdk-group-heading]]:pt-2 [&_[cmdk-group-heading]]:text-caption-1 [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:text-secondary-label",
className
)}
{...props}
/>
);
}
function CommandSeparator({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("mx-2.5 my-1 h-px bg-separator", className)}
{...props}
/>
);
}
function CommandItem({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"relative flex cursor-default select-none items-center gap-3 rounded-[8px] px-2.5 py-2 text-subheadline text-label outline-none data-[selected=true]:bg-blue data-[selected=true]:text-white data-[selected=true]:[&_[data-slot=command-shortcut]]:text-white/70 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-40 [&_svg]:pointer-events-none [&_svg]:size-5 [&_svg]:shrink-0",
className
)}
{...props}
/>
);
}
function CommandShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="command-shortcut"
className={cn(
"ml-auto text-footnote tracking-widest text-tertiary-label",
className
)}
{...props}
/>
);
}
export {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
};