Command
Free, copy-and-go Command components built on the SevenUI Command primitive.Read the primitive docs.
"use client";
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
type Country = { value: string; label: string; code: string };
const countries: Country[] = [
{ value: "au", label: "Australia", code: "+61" },
{ value: "br", label: "Brazil", code: "+55" },
{ value: "ca", label: "Canada", code: "+1" },
{ value: "de", label: "Germany", code: "+49" },
{ value: "in", label: "India", code: "+91" },
{ value: "jp", label: "Japan", code: "+81" },
{ value: "mx", label: "Mexico", code: "+52" },
{ value: "ng", label: "Nigeria", code: "+234" },
{ value: "tr", label: "Türkiye", code: "+90" },
{ value: "gb", label: "United Kingdom", code: "+44" },
{ value: "us", label: "United States", code: "+1" },
];
export default function Command01() {
return (
<Command
items={countries}
className="w-full max-w-xs border border-border shadow-xs"
>
<CommandInput
placeholder="Search countries..."
aria-label="Search country calling codes"
/>
<CommandList className="max-h-60">
{(country: Country) => (
<CommandItem key={country.value} value={country}>
<span className="truncate">{country.label}</span>
<span className="ml-auto text-xs text-muted-foreground tabular-nums">
{country.code}
</span>
</CommandItem>
)}
</CommandList>
<CommandEmpty>No country matches that name.</CommandEmpty>
</Command>
);
}
npx shadcn@latest add @sevenui/component/command-01pnpm dlx shadcn@latest add @sevenui/component/command-01yarn dlx shadcn@latest add @sevenui/component/command-01bunx --bun shadcn@latest add @sevenui/component/command-01"use client";
import type * as React from "react";
import {
CodeIcon,
Heading2Icon,
ImageIcon,
ListChecksIcon,
QuoteIcon,
TableIcon,
} from "lucide-react";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
type Block = {
value: string;
label: string;
description: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
};
type Group = { value: string; items: Block[] };
const groups: Group[] = [
{
value: "Text",
items: [
{
value: "heading",
label: "Heading",
description: "Section title for longer pages.",
icon: Heading2Icon,
},
{
value: "checklist",
label: "Checklist",
description: "Track tasks with checkboxes.",
icon: ListChecksIcon,
},
{
value: "quote",
label: "Quote",
description: "Call out a customer or source.",
icon: QuoteIcon,
},
],
},
{
value: "Media & data",
items: [
{
value: "image",
label: "Image",
description: "Upload or embed a picture.",
icon: ImageIcon,
},
{
value: "table",
label: "Table",
description: "Rows and columns of structured data.",
icon: TableIcon,
},
{
value: "code",
label: "Code block",
description: "Syntax-highlighted snippet.",
icon: CodeIcon,
},
],
},
];
export default function Command02() {
return (
<Command
items={groups}
className="w-full max-w-sm border border-border shadow-md"
>
<CommandInput placeholder="Insert a block..." aria-label="Search blocks" />
<CommandList className="max-h-80">
{(group: Group) => (
<CommandGroup key={group.value} heading={group.value} items={group.items}>
{(block: Block) => (
<CommandItem key={block.value} value={block} className="gap-3 py-2">
<span className="flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-background text-muted-foreground group-data-highlighted/command-item:text-foreground">
<block.icon aria-hidden="true" />
</span>
<span className="flex min-w-0 flex-col gap-0.5">
<span className="font-medium">{block.label}</span>
<span className="truncate text-xs text-muted-foreground">
{block.description}
</span>
</span>
</CommandItem>
)}
</CommandGroup>
)}
</CommandList>
<CommandEmpty>No block type matches your search.</CommandEmpty>
</Command>
);
}
npx shadcn@latest add @sevenui/component/command-02pnpm dlx shadcn@latest add @sevenui/component/command-02yarn dlx shadcn@latest add @sevenui/component/command-02bunx --bun shadcn@latest add @sevenui/component/command-02Locked actions need the Team plan.
"use client";
import * as React from "react";
import {
ArchiveIcon,
CopyIcon,
DownloadIcon,
LockIcon,
PencilIcon,
ShieldCheckIcon,
Trash2Icon,
UsersIcon,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
type Action = {
value: string;
label: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
shortcut?: string;
locked?: boolean;
destructive?: boolean;
};
type Group = { value: string; items: Action[] };
const groups: Group[] = [
{
value: "Project",
items: [
{ value: "rename", label: "Rename project", icon: PencilIcon, shortcut: "F2" },
{ value: "duplicate", label: "Duplicate", icon: CopyIcon, shortcut: "⌘D" },
{ value: "export", label: "Export as CSV", icon: DownloadIcon },
],
},
{
value: "Team plan",
items: [
{ value: "guests", label: "Invite guests", icon: UsersIcon, locked: true },
{ value: "sso", label: "Enforce SSO", icon: ShieldCheckIcon, locked: true },
],
},
{
value: "Danger zone",
items: [
{ value: "archive", label: "Archive project", icon: ArchiveIcon },
{
value: "delete",
label: "Delete project",
icon: Trash2Icon,
shortcut: "⌘⌫",
destructive: true,
},
],
},
];
export default function Command03() {
const [lastAction, setLastAction] = React.useState<string | null>(null);
return (
<div className="flex w-full max-w-sm flex-col gap-2">
<Command items={groups} className="border border-border shadow-md">
<CommandInput
placeholder="Search project actions..."
aria-label="Search project actions"
/>
<CommandList className="max-h-96">
{(group: Group, index: number) => (
<React.Fragment key={group.value}>
{index > 0 && <CommandSeparator className="my-1" />}
<CommandGroup heading={group.value} items={group.items}>
{(action: Action) => (
<CommandItem
key={action.value}
value={action}
disabled={action.locked}
onClick={() => setLastAction(action.label)}
className={
action.destructive
? "text-destructive data-highlighted:bg-destructive/10 data-highlighted:text-destructive data-highlighted:*:[svg]:text-destructive"
: undefined
}
>
<action.icon aria-hidden="true" />
{action.label}
{action.locked ? (
<Badge variant="outline" className="ml-auto gap-1">
<LockIcon aria-hidden="true" />
Team
</Badge>
) : (
action.shortcut && (
<CommandShortcut
className={
action.destructive
? "text-destructive/70 group-data-highlighted/command-item:text-destructive"
: undefined
}
>
{action.shortcut}
</CommandShortcut>
)
)}
</CommandItem>
)}
</CommandGroup>
</React.Fragment>
)}
</CommandList>
<CommandEmpty>No action matches your search.</CommandEmpty>
</Command>
<p className="px-1 text-xs text-muted-foreground" aria-live="polite">
{lastAction
? `Ran “${lastAction}”.`
: "Locked actions need the Team plan."}
</p>
</div>
);
}
npx shadcn@latest add @sevenui/component/command-03pnpm dlx shadcn@latest add @sevenui/component/command-03yarn dlx shadcn@latest add @sevenui/component/command-03bunx --bun shadcn@latest add @sevenui/component/command-033 results
"use client";
import * as React from "react";
import { FileTextIcon, HashIcon, UserIcon } from "lucide-react";
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
type Scope = "all" | "docs" | "channels" | "people";
type Result = {
value: string;
label: string;
meta: string;
scope: Exclude<Scope, "all">;
};
const scopes: { value: Scope; label: string }[] = [
{ value: "all", label: "All" },
{ value: "docs", label: "Docs" },
{ value: "channels", label: "Channels" },
{ value: "people", label: "People" },
];
const scopeIcons = {
docs: FileTextIcon,
channels: HashIcon,
people: UserIcon,
};
const results: Result[] = [
{ value: "q3-roadmap", label: "Q3 roadmap", meta: "Edited 2h ago", scope: "docs" },
{ value: "release-notes", label: "Release notes 4.2", meta: "Edited yesterday", scope: "docs" },
{ value: "onboarding", label: "Onboarding checklist", meta: "Edited Mon", scope: "docs" },
{ value: "design-review", label: "design-review", meta: "18 members", scope: "channels" },
{ value: "releases", label: "releases", meta: "42 members", scope: "channels" },
{ value: "support-escalations", label: "support-escalations", meta: "9 members", scope: "channels" },
{ value: "rosa", label: "Rosa Delgado", meta: "Release manager", scope: "people" },
{ value: "noah", label: "Noah Fischer", meta: "Support lead", scope: "people" },
];
function Highlight({ text, query }: { text: string; query: string }) {
const index = query ? text.toLowerCase().indexOf(query.toLowerCase()) : -1;
if (index === -1) return <>{text}</>;
return (
<>
{text.slice(0, index)}
<mark className="rounded-xs bg-primary/15 text-foreground">
{text.slice(index, index + query.length)}
</mark>
{text.slice(index + query.length)}
</>
);
}
export default function Command04() {
const [query, setQuery] = React.useState("re");
const [scope, setScope] = React.useState<Scope>("all");
const trimmed = query.trim();
const visible = results.filter(
(result) =>
(scope === "all" || result.scope === scope) &&
result.label.toLowerCase().includes(trimmed.toLowerCase()),
);
return (
<Command
items={visible}
mode="none"
value={query}
onValueChange={setQuery}
className="w-full max-w-sm border border-border shadow-md"
>
<CommandInput placeholder="Search workspace..." aria-label="Search workspace" />
<div className="flex items-center justify-between gap-2 px-1 pt-2 pb-1">
<ToggleGroup
aria-label="Search scope"
size="sm"
value={[scope]}
onValueChange={(next) => {
// Keep one scope selected at all times.
if (next.length > 0) setScope(next[0] as Scope);
}}
className="min-w-0"
>
{scopes.map((item) => (
<ToggleGroupItem
key={item.value}
value={item.value}
className="h-7 px-2 text-xs aria-pressed:bg-accent aria-pressed:text-accent-foreground"
>
{item.label}
</ToggleGroupItem>
))}
</ToggleGroup>
<span
className="shrink-0 pr-1 text-xs text-muted-foreground tabular-nums max-sm:sr-only"
aria-live="polite"
>
{visible.length} {visible.length === 1 ? "result" : "results"}
</span>
</div>
<CommandList>
{(result: Result) => {
const Icon = scopeIcons[result.scope];
return (
<CommandItem key={result.value} value={result}>
<Icon aria-hidden="true" className="text-muted-foreground" />
<span className="truncate">
<Highlight text={result.label} query={trimmed} />
</span>
<span className="ml-auto shrink-0 text-xs text-muted-foreground">
{result.meta}
</span>
</CommandItem>
);
}}
</CommandList>
<CommandEmpty>
Nothing in {scope === "all" ? "this workspace" : scope} matches “{trimmed}”.
</CommandEmpty>
</Command>
);
}
npx shadcn@latest add @sevenui/component/command-04pnpm dlx shadcn@latest add @sevenui/component/command-04yarn dlx shadcn@latest add @sevenui/component/command-04bunx --bun shadcn@latest add @sevenui/component/command-04Searching billing records…
"use client";
import * as React from "react";
import { CircleAlertIcon, ReceiptTextIcon, RotateCwIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Label } from "@/components/ui/label";
import { Skeleton } from "@/components/ui/skeleton";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch";
type Invoice = {
value: string;
label: string;
customer: string;
amount: string;
};
type Status = "loading" | "error" | "ready";
const invoices: Invoice[] = [
{ value: "inv-2041", label: "INV-2041", customer: "Northwind Traders", amount: "$4,200.00" },
{ value: "inv-2042", label: "INV-2042", customer: "Globex Logistics", amount: "$860.50" },
{ value: "inv-2043", label: "INV-2043", customer: "Northwind Traders", amount: "$1,125.00" },
{ value: "inv-2044", label: "INV-2044", customer: "Acme Studio", amount: "$12,480.00" },
{ value: "inv-2045", label: "INV-2045", customer: "Brightline Health", amount: "$399.00" },
];
// Simulated network latency for the fake request.
const LATENCY_MS = 700;
export default function Command05() {
const [query, setQuery] = React.useState("");
const [offline, setOffline] = React.useState(false);
const [status, setStatus] = React.useState<Status>("loading");
const [items, setItems] = React.useState<Invoice[]>([]);
const [attempt, setAttempt] = React.useState(0);
React.useEffect(() => {
// `attempt` re-runs the request when the user presses Retry.
void attempt;
setStatus("loading");
const timer = window.setTimeout(() => {
if (offline) {
setStatus("error");
setItems([]);
return;
}
const needle = query.trim().toLowerCase();
setItems(
invoices.filter((invoice) =>
`${invoice.label} ${invoice.customer}`.toLowerCase().includes(needle),
),
);
setStatus("ready");
}, LATENCY_MS);
return () => window.clearTimeout(timer);
}, [query, offline, attempt]);
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<Command
items={status === "ready" ? items : []}
mode="none"
value={query}
onValueChange={setQuery}
className="border border-border shadow-md"
>
<CommandInput
placeholder="Search invoices or customers..."
aria-label="Search invoices"
/>
<div
className="flex h-7 items-center gap-1.5 px-3 pt-1 text-xs text-muted-foreground"
aria-live="polite"
>
{status === "loading" && (
<>
<Spinner className="size-3" aria-hidden="true" />
Searching billing records…
</>
)}
{status === "ready" &&
`${items.length} ${items.length === 1 ? "invoice" : "invoices"} found`}
{status === "error" && (
<span className="text-destructive">Request failed</span>
)}
</div>
{status === "loading" && (
<div className="flex flex-col gap-1 p-1" aria-hidden="true">
{[0, 1, 2].map((row) => (
<div key={row} className="flex items-center gap-3 px-2 py-1.5">
<Skeleton className="size-4 rounded-sm" />
<Skeleton className="h-3.5 w-24" />
<Skeleton className="ml-auto h-3.5 w-14" />
</div>
))}
</div>
)}
{status === "error" && (
<div
role="alert"
className="m-1 flex flex-col items-center gap-2 rounded-lg bg-destructive/5 px-4 py-6 text-center"
>
<CircleAlertIcon aria-hidden="true" className="size-5 text-destructive" />
<p className="text-sm font-medium">Couldn’t reach the billing service</p>
<p className="text-xs text-muted-foreground">
Check your connection, then try the search again.
</p>
<Button
size="sm"
variant="outline"
className="mt-1"
onClick={() => setAttempt((count) => count + 1)}
>
<RotateCwIcon aria-hidden="true" data-icon="inline-start" />
Retry
</Button>
</div>
)}
<CommandList>
{(invoice: Invoice) => (
<CommandItem key={invoice.value} value={invoice}>
<ReceiptTextIcon aria-hidden="true" className="text-muted-foreground" />
<span className="shrink-0 font-mono text-xs whitespace-nowrap tabular-nums">{invoice.label}</span>
<span className="truncate text-muted-foreground">{invoice.customer}</span>
<span className="ml-auto shrink-0 tabular-nums">{invoice.amount}</span>
</CommandItem>
)}
</CommandList>
{status === "ready" && (
<CommandEmpty>No invoice matches “{query.trim()}”.</CommandEmpty>
)}
</Command>
<div className="flex items-center gap-2 px-1">
<Switch
id="command-05-offline"
size="sm"
checked={offline}
onCheckedChange={setOffline}
/>
<Label htmlFor="command-05-offline" className="text-xs font-normal">
Simulate offline
</Label>
</div>
</div>
);
}
npx shadcn@latest add @sevenui/component/command-05pnpm dlx shadcn@latest add @sevenui/component/command-05yarn dlx shadcn@latest add @sevenui/component/command-05bunx --bun shadcn@latest add @sevenui/component/command-05BugPerformance
2 of 8 selected
"use client";
import * as React from "react";
import { CheckIcon, XIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
type IssueLabel = { value: string; label: string; dot: string };
const labels: IssueLabel[] = [
{ value: "bug", label: "Bug", dot: "bg-destructive" },
{ value: "feature", label: "Feature request", dot: "bg-chart-1" },
{ value: "performance", label: "Performance", dot: "bg-chart-2" },
{ value: "accessibility", label: "Accessibility", dot: "bg-chart-3" },
{ value: "docs", label: "Documentation", dot: "bg-chart-4" },
{ value: "design", label: "Design", dot: "bg-chart-5" },
{ value: "regression", label: "Regression", dot: "bg-warning" },
{ value: "good-first-issue", label: "Good first issue", dot: "bg-success" },
];
export default function Command06() {
const [selected, setSelected] = React.useState<string[]>(["bug", "performance"]);
function toggle(value: string) {
setSelected((current) =>
current.includes(value)
? current.filter((item) => item !== value)
: [...current, value],
);
}
const chosen = labels.filter((label) => selected.includes(label.value));
return (
<Command items={labels} className="w-full max-w-xs border border-border shadow-md">
<div className="flex min-h-9 flex-wrap items-center gap-1 px-2 pt-1.5 pb-1">
{chosen.length === 0 ? (
<span className="text-xs text-muted-foreground">No labels applied</span>
) : (
chosen.map((label) => (
<Badge key={label.value} variant="outline" className="gap-1.5 pr-0.5">
<span aria-hidden="true" className={`size-1.5 rounded-full ${label.dot}`} />
{label.label}
<button
type="button"
aria-label={`Remove ${label.label}`}
onClick={() => toggle(label.value)}
className="inline-flex size-4 items-center justify-center rounded-full text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50"
>
<XIcon aria-hidden="true" />
</button>
</Badge>
))
)}
</div>
<CommandInput placeholder="Filter labels..." aria-label="Filter labels" />
<CommandList aria-multiselectable="true" className="mt-1 max-h-64">
{(label: IssueLabel) => {
const isSelected = selected.includes(label.value);
return (
<CommandItem
key={label.value}
value={label}
aria-selected={isSelected}
onClick={() => toggle(label.value)}
>
<span
aria-hidden="true"
data-checked={isSelected ? "" : undefined}
className="flex size-4 items-center justify-center rounded-[4px] border border-input transition-colors data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground"
>
<CheckIcon
className="size-3 scale-50 opacity-0 transition-[opacity,scale] duration-150 ease-out in-data-checked:scale-100 in-data-checked:opacity-100"
/>
</span>
<span aria-hidden="true" className={`size-2 rounded-full ${label.dot}`} />
{label.label}
</CommandItem>
);
}}
</CommandList>
<CommandEmpty>No label with that name.</CommandEmpty>
<div className="mt-1 flex items-center justify-between border-t border-border px-2 pt-1.5 pb-0.5">
<span className="text-xs text-muted-foreground tabular-nums" aria-live="polite">
{selected.length} of {labels.length} selected
</span>
<Button
size="xs"
variant="ghost"
disabled={selected.length === 0}
onClick={() => setSelected([])}
>
Clear
</Button>
</div>
</Command>
);
}
npx shadcn@latest add @sevenui/component/command-06pnpm dlx shadcn@latest add @sevenui/component/command-06yarn dlx shadcn@latest add @sevenui/component/command-06bunx --bun shadcn@latest add @sevenui/component/command-06Math works too: + − × ÷ and parentheses.EnterCopy
"use client";
import * as React from "react";
import {
CalculatorIcon,
CalendarIcon,
FilePlusIcon,
MailIcon,
NotebookPenIcon,
TerminalIcon,
TimerIcon,
} from "lucide-react";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut,
} from "@/components/ui/command";
import { Kbd } from "@/components/ui/kbd";
type Entry = {
value: string;
label: string;
hint: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
// The calculator answer skips text filtering.
answer?: boolean;
};
type Group = { value: string; items: Entry[] };
const launchers: Group[] = [
{
value: "Applications",
items: [
{ value: "calendar", label: "Calendar", hint: "Application", icon: CalendarIcon },
{ value: "mail", label: "Mail", hint: "Application", icon: MailIcon },
{ value: "notes", label: "Notes", hint: "Application", icon: NotebookPenIcon },
{ value: "terminal", label: "Terminal", hint: "Application", icon: TerminalIcon },
],
},
{
value: "Quick actions",
items: [
{ value: "new-note", label: "New note", hint: "Notes", icon: FilePlusIcon },
{ value: "focus", label: "Start a 25-minute focus timer", hint: "Clock", icon: TimerIcon },
],
},
];
const numberFormat = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 6,
});
// A tiny recursive-descent parser for + - * / and parentheses. No eval.
function calculate(input: string): number | null {
const source = input
.replace(/×/g, "*")
.replace(/−/g, "-")
.replace(/÷/g, "/")
.replace(/,/g, "");
if (!/^[\d\s.+\-*/()]+$/.test(source) || !/\d\s*[+\-*/]/.test(source)) {
return null;
}
const tokens = source.match(/\d*\.?\d+|[+\-*/()]/g) ?? [];
let position = 0;
function factor(): number {
const token = tokens[position++];
if (token === "-") return -factor();
if (token === "(") {
const value = expression();
if (tokens[position++] !== ")") throw new Error("Unclosed parenthesis");
return value;
}
const value = Number(token);
if (token === undefined || Number.isNaN(value)) {
throw new Error("Expected a number");
}
return value;
}
function term(): number {
let value = factor();
while (tokens[position] === "*" || tokens[position] === "/") {
const operator = tokens[position++];
const next = factor();
value = operator === "*" ? value * next : value / next;
}
return value;
}
function expression(): number {
let value = term();
while (tokens[position] === "+" || tokens[position] === "-") {
const operator = tokens[position++];
const next = term();
value = operator === "+" ? value + next : value - next;
}
return value;
}
try {
const result = expression();
return position === tokens.length && Number.isFinite(result) ? result : null;
} catch {
return null;
}
}
export default function Command07() {
const [query, setQuery] = React.useState("1450 * 0.18");
const [status, setStatus] = React.useState("");
const result = calculate(query);
const groups: Group[] =
result === null
? launchers
: [
{
value: "Calculator",
items: [
{
value: "answer",
label: numberFormat.format(result),
hint: query.trim().replace(/\*/g, "×").replace(/\//g, "÷"),
icon: CalculatorIcon,
answer: true,
},
],
},
...launchers,
];
function run(entry: Entry) {
if (entry.answer) {
navigator.clipboard?.writeText(entry.label).catch(() => {});
setStatus(`Copied ${entry.label} to the clipboard.`);
return;
}
setStatus(`Opening ${entry.label}…`);
}
return (
<Command
items={groups}
value={query}
onValueChange={(next, details) => {
if (details.reason === "item-press") return;
setQuery(next);
setStatus("");
}}
filter={(item, value) => {
const entry = item as Entry;
return (
entry.answer === true ||
entry.label.toLowerCase().includes(value.trim().toLowerCase())
);
}}
className="w-full max-w-md border border-border shadow-lg"
>
<CommandInput
placeholder="Search apps, or type a sum like 24 * 7"
aria-label="Search apps and actions, or calculate"
/>
<CommandList className="mt-1">
{(group: Group) => (
<CommandGroup key={group.value} heading={group.value} items={group.items}>
{(entry: Entry) =>
entry.answer ? (
<CommandItem
key={entry.value}
value={entry}
onClick={() => run(entry)}
className="gap-3 py-2.5"
>
<span className="flex size-9 shrink-0 items-center justify-center rounded-md bg-primary text-primary-foreground">
<entry.icon aria-hidden="true" />
</span>
<span className="flex min-w-0 flex-col">
<span className="truncate text-xl font-semibold tracking-tight tabular-nums">
<span className="sr-only">Result: </span>
{entry.label}
</span>
<span className="truncate text-xs text-muted-foreground tabular-nums">
{entry.hint}
</span>
</span>
<CommandShortcut className="tracking-normal">Copy</CommandShortcut>
</CommandItem>
) : (
<CommandItem
key={entry.value}
value={entry}
onClick={() => run(entry)}
>
<entry.icon aria-hidden="true" className="text-muted-foreground" />
<span className="truncate">{entry.label}</span>
<span className="ml-auto shrink-0 text-xs text-muted-foreground">
{entry.hint}
</span>
</CommandItem>
)
}
</CommandGroup>
)}
</CommandList>
<CommandEmpty>No app or action matches “{query.trim()}”.</CommandEmpty>
<div className="mt-1 flex min-h-9 items-center justify-between gap-3 border-t border-border px-2 pt-1 text-xs text-muted-foreground">
<span aria-live="polite" className="min-w-0">
{status || "Math works too: + − × ÷ and parentheses."}
</span>
<span className="flex shrink-0 items-center gap-1.5">
<Kbd>
<span aria-hidden="true">↵</span>
<span className="sr-only">Enter</span>
</Kbd>
{result === null ? "Open" : "Copy"}
</span>
</div>
</Command>
);
}
npx shadcn@latest add @sevenui/component/command-07pnpm dlx shadcn@latest add @sevenui/component/command-07yarn dlx shadcn@latest add @sevenui/component/command-07bunx --bun shadcn@latest add @sevenui/component/command-07Time zone
Due dates, reminders and the daily digest follow this zone.
Your daily digest will arrive at 4:00 PM Berlin time.
"use client";
import * as React from "react";
import { CheckIcon, GlobeIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
type Zone = {
value: string;
label: string;
country: string;
offset: string;
// Minutes from UTC, used to preview the local send time.
minutes: number;
};
type Region = { value: string; items: Zone[] };
const regions: Region[] = [
{
value: "Americas",
items: [
{ value: "America/Los_Angeles", label: "Los Angeles", country: "United States", offset: "UTC−7", minutes: -420 },
{ value: "America/New_York", label: "New York", country: "United States", offset: "UTC−4", minutes: -240 },
{ value: "America/Sao_Paulo", label: "São Paulo", country: "Brazil", offset: "UTC−3", minutes: -180 },
],
},
{
value: "Europe & Africa",
items: [
{ value: "Europe/London", label: "London", country: "United Kingdom", offset: "UTC+1", minutes: 60 },
{ value: "Europe/Berlin", label: "Berlin", country: "Germany", offset: "UTC+2", minutes: 120 },
{ value: "Europe/Istanbul", label: "Istanbul", country: "Türkiye", offset: "UTC+3", minutes: 180 },
{ value: "Africa/Lagos", label: "Lagos", country: "Nigeria", offset: "UTC+1", minutes: 60 },
],
},
{
value: "Asia & Pacific",
items: [
{ value: "Asia/Kolkata", label: "Kolkata", country: "India", offset: "UTC+5:30", minutes: 330 },
{ value: "Asia/Singapore", label: "Singapore", country: "Singapore", offset: "UTC+8", minutes: 480 },
{ value: "Asia/Tokyo", label: "Tokyo", country: "Japan", offset: "UTC+9", minutes: 540 },
{ value: "Australia/Sydney", label: "Sydney", country: "Australia", offset: "UTC+10", minutes: 600 },
],
},
];
const allZones = regions.flatMap((region) => region.items);
function matchesZone(zone: Zone, query: string) {
const needle = query.trim().toLowerCase();
if (!needle) return true;
return [zone.label, zone.country, zone.value, zone.offset].some((field) =>
field.toLowerCase().includes(needle),
);
}
// The digest goes out at 14:00 UTC; show it in the selected zone.
function digestTime(zone: Zone) {
const total = (14 * 60 + zone.minutes + 24 * 60) % (24 * 60);
const hours = Math.floor(total / 60);
const minutes = total % 60;
const suffix = hours >= 12 ? "PM" : "AM";
const display = hours % 12 === 0 ? 12 : hours % 12;
return `${display}:${minutes.toString().padStart(2, "0")} ${suffix}`;
}
export default function Command08() {
const [savedZone, setSavedZone] = React.useState("Europe/Berlin");
const [draftZone, setDraftZone] = React.useState("Europe/Berlin");
const [query, setQuery] = React.useState("");
const draft = allZones.find((zone) => zone.value === draftZone) ?? allZones[0];
const dirty = draftZone !== savedZone;
return (
<section
aria-labelledby="command-08-title"
className="w-full max-w-md rounded-xl border border-border bg-card text-card-foreground"
>
<header className="flex flex-col gap-1 p-4 pb-3">
<h3 id="command-08-title" className="text-sm font-medium">
Time zone
</h3>
<p className="text-sm text-muted-foreground">
Due dates, reminders and the daily digest follow this zone.
</p>
</header>
<div className="px-3">
<Command
items={regions}
value={query}
onValueChange={(next, details) => {
if (details.reason === "item-press") return;
setQuery(next);
}}
filter={(zone, value) => matchesZone(zone as Zone, value)}
className="rounded-lg! border border-border bg-background p-0"
>
<CommandInput
placeholder="Search city, country or UTC offset"
aria-label="Search time zones"
/>
<CommandList className="max-h-56">
{(region: Region) => (
<CommandGroup
key={region.value}
heading={region.value}
items={region.items}
>
{(zone: Zone) => {
const selected = zone.value === draftZone;
return (
<CommandItem
key={zone.value}
value={zone}
onClick={() => setDraftZone(zone.value)}
>
<span className="flex min-w-0 flex-col">
<span className="truncate">{zone.label}</span>
<span className="truncate text-xs text-muted-foreground">
{zone.country}
</span>
</span>
<span className="ml-auto text-xs text-muted-foreground tabular-nums">
{zone.offset}
</span>
<CheckIcon
aria-hidden="true"
className={selected ? "text-foreground" : "invisible"}
/>
{selected && <span className="sr-only">(selected)</span>}
</CommandItem>
);
}}
</CommandGroup>
)}
</CommandList>
<CommandEmpty>
No zone matches “{query}”. Try a nearby city.
</CommandEmpty>
</Command>
</div>
<p
aria-live="polite"
className="flex items-start gap-2 px-4 pt-3 text-sm text-muted-foreground"
>
<GlobeIcon className="mt-0.5 size-4 shrink-0" aria-hidden="true" />
<span>
Your daily digest will arrive at{" "}
<span className="font-medium text-foreground tabular-nums">
{digestTime(draft)}
</span>{" "}
{draft.label} time.
</span>
</p>
<footer className="mt-3 flex justify-end gap-2 border-t border-border p-3">
<Button
variant="ghost"
disabled={!dirty}
onClick={() => setDraftZone(savedZone)}
>
Reset
</Button>
<Button disabled={!dirty} onClick={() => setSavedZone(draftZone)}>
{dirty ? "Save time zone" : "Saved"}
</Button>
</footer>
</section>
);
}
npx shadcn@latest add @sevenui/component/command-08pnpm dlx shadcn@latest add @sevenui/component/command-08yarn dlx shadcn@latest add @sevenui/component/command-08bunx --bun shadcn@latest add @sevenui/component/command-08Add link
Before planning starts, review the quarterly targets with your team.
Pick a page to link the highlighted text.
"use client";
import * as React from "react";
import { FilePlusIcon, FileTextIcon, GlobeIcon, Link2OffIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
type Target = {
value: string;
label: string;
detail: string;
kind: "page" | "url" | "create";
};
type Group = { value: string; items: Target[] };
type Link = { label: string; href: string; external: boolean };
const pages: Target[] = [
{ value: "q4-targets", label: "Q4 targets", detail: "Company / Planning", kind: "page" },
{ value: "okr-guide", label: "How we write OKRs", detail: "Company / Handbook", kind: "page" },
{ value: "roadmap", label: "Product roadmap 2026", detail: "Product", kind: "page" },
{ value: "pricing-review", label: "Pricing review notes", detail: "Product / Research", kind: "page" },
{ value: "hiring-plan", label: "Hiring plan H2", detail: "People / Planning", kind: "page" },
];
const URL_PATTERN = /^(https?:\/\/)?[\w-]+(\.[\w-]+)+(\/\S*)?$/i;
function buildGroups(query: string): Group[] {
const trimmed = query.trim();
if (URL_PATTERN.test(trimmed)) {
const href = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
return [
{
value: "Web",
items: [{ value: "url", label: href, detail: "External link", kind: "url" }],
},
];
}
const groups: Group[] = [
{ value: trimmed ? "Pages" : "Recent pages", items: pages },
];
const exists = pages.some(
(page) => page.label.toLowerCase() === trimmed.toLowerCase(),
);
if (trimmed && !exists) {
groups.push({
value: "New",
items: [
{
value: "create",
label: trimmed,
detail: "Company / Planning",
kind: "create",
},
],
});
}
return groups;
}
export default function Command09() {
const [query, setQuery] = React.useState("");
const [link, setLink] = React.useState<Link | null>(null);
function apply(target: Target) {
setLink({
label: target.kind === "url" ? target.label.replace(/^https?:\/\//i, "") : target.label,
href: target.kind === "url" ? target.label : `/wiki/${target.value}`,
external: target.kind === "url",
});
setQuery("");
}
return (
<section
aria-labelledby="command-09-title"
className="flex w-full max-w-sm flex-col gap-3 rounded-xl border border-border bg-card p-3 text-card-foreground"
>
<div className="flex flex-col gap-1 px-1">
<h3 id="command-09-title" className="text-sm font-medium">
Add link
</h3>
<p className="text-sm text-pretty text-muted-foreground">
Before planning starts, review the{" "}
{link ? (
<a
href={link.href}
onClick={(event) => event.preventDefault()}
className="font-medium text-foreground underline decoration-primary/60 underline-offset-2 hover:decoration-primary"
title={link.label}
>
quarterly targets
</a>
) : (
<mark className="rounded-xs bg-primary/15 px-0.5 text-foreground">
quarterly targets
</mark>
)}{" "}
with your team.
</p>
</div>
<Command
items={buildGroups(query)}
value={query}
onValueChange={(next, details) => {
if (details.reason === "item-press") return;
setQuery(next);
}}
filter={(item, value) => {
const target = item as Target;
if (target.kind !== "page") return true;
const needle = value.trim().toLowerCase();
return `${target.label} ${target.detail}`.toLowerCase().includes(needle);
}}
className="rounded-lg! border border-border bg-background"
>
<CommandInput
placeholder="Search pages or paste a URL"
aria-label="Link to a page or URL"
/>
<CommandList className="max-h-56">
{(group: Group) => (
<CommandGroup key={group.value} heading={group.value} items={group.items}>
{(target: Target) => (
<CommandItem
key={target.value}
value={target}
onClick={() => apply(target)}
>
{target.kind === "url" ? (
<GlobeIcon aria-hidden="true" className="text-muted-foreground" />
) : target.kind === "create" ? (
<FilePlusIcon aria-hidden="true" className="text-muted-foreground" />
) : (
<FileTextIcon aria-hidden="true" className="text-muted-foreground" />
)}
<span className="flex min-w-0 flex-col">
<span className="truncate">
{target.kind === "create" ? (
<>
Create page{" "}
<span className="font-medium">“{target.label}”</span>
</>
) : (
target.label
)}
</span>
<span className="truncate text-xs text-muted-foreground">
{target.kind === "create" ? `in ${target.detail}` : target.detail}
</span>
</span>
</CommandItem>
)}
</CommandGroup>
)}
</CommandList>
<CommandEmpty>No page matches. Paste a full URL instead.</CommandEmpty>
</Command>
<div className="flex min-h-8 items-center justify-between gap-2 px-1">
<p
aria-live="polite"
className="min-w-0 truncate text-xs text-muted-foreground"
>
{link ? (
<>
Linked to{" "}
<span className="text-foreground">
{link.external ? link.label : `“${link.label}”`}
</span>
</>
) : (
"Pick a page to link the highlighted text."
)}
</p>
{link && (
<Button variant="ghost" size="sm" onClick={() => setLink(null)}>
<Link2OffIcon aria-hidden="true" />
Unlink
</Button>
)}
</div>
</section>
);
}
npx shadcn@latest add @sevenui/component/command-09pnpm dlx shadcn@latest add @sevenui/component/command-09yarn dlx shadcn@latest add @sevenui/component/command-09bunx --bun shadcn@latest add @sevenui/component/command-09RG
Rosa Gallo
Order no. 40318 · Double charge on checkout
I was charged twice for my last order. Can you help?
⌘ Enter
"use client";
import * as React from "react";
import { MessageSquareTextIcon, SendIcon, SlashIcon } from "lucide-react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Kbd } from "@/components/ui/kbd";
import { Textarea } from "@/components/ui/textarea";
type Reply = { value: string; label: string; body: string };
type Topic = { value: string; items: Reply[] };
const customer = { name: "Rosa", order: "40318" };
const topics: Topic[] = [
{
value: "Billing",
items: [
{
value: "refund",
label: "Refund issued",
body: `Hi ${customer.name}, I've issued a full refund for order no. ${customer.order}. It usually reaches your card within 5–10 business days.`,
},
{
value: "invoice",
label: "Resend invoice",
body: `Hi ${customer.name}, I've resent the invoice for order no. ${customer.order} to the email on file. Let me know if it doesn't arrive in the next few minutes.`,
},
],
},
{
value: "Shipping",
items: [
{
value: "tracking",
label: "Share tracking link",
body: `Your parcel for order no. ${customer.order} is on its way. You can follow it here: track.example.com/${customer.order}`,
},
{
value: "delay",
label: "Carrier delay apology",
body: "Sorry for the wait. The carrier has flagged a regional delay, and we'll cover express shipping on your next order.",
},
],
},
{
value: "Account",
items: [
{
value: "password",
label: "Password reset steps",
body: "You can reset your password from Settings → Security → Reset password. The link in the email stays valid for 30 minutes.",
},
],
},
];
export default function Command10() {
const [draft, setDraft] = React.useState("");
const [open, setOpen] = React.useState(false);
const [query, setQuery] = React.useState("");
const [sent, setSent] = React.useState<{ id: number; text: string }[]>([]);
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
const searchRef = React.useRef<HTMLInputElement>(null);
const caretRef = React.useRef(0);
React.useEffect(() => {
if (open) searchRef.current?.focus();
}, [open]);
function focusComposer(position: number) {
requestAnimationFrame(() => {
const textarea = textareaRef.current;
if (!textarea) return;
textarea.focus();
textarea.setSelectionRange(position, position);
});
}
function openReplies() {
caretRef.current = textareaRef.current?.selectionStart ?? draft.length;
setQuery("");
setOpen(true);
}
function closeReplies() {
setOpen(false);
focusComposer(caretRef.current);
}
function insert(reply: Reply) {
const at = caretRef.current;
const next = draft.slice(0, at) + reply.body + draft.slice(at);
setDraft(next);
setOpen(false);
focusComposer(at + reply.body.length);
}
function send() {
if (!draft.trim()) return;
setSent((current) => [
...current,
{ id: current.length + 1, text: draft.trim() },
]);
setDraft("");
}
return (
<div className="flex w-full max-w-md flex-col rounded-xl border border-border bg-card text-card-foreground">
<div className="flex items-center gap-3 border-b border-border p-3">
<Avatar>
<AvatarFallback>RG</AvatarFallback>
</Avatar>
<div className="min-w-0">
<p className="truncate text-sm font-medium">Rosa Gallo</p>
<p className="truncate text-xs text-muted-foreground">
Order no. {customer.order} · Double charge on checkout
</p>
</div>
</div>
<div className="flex flex-col gap-2 p-3">
<p className="max-w-[85%] rounded-lg bg-muted px-3 py-2 text-sm">
I was charged twice for my last order. Can you help?
</p>
{sent.map((message) => (
<p
key={message.id}
className="ml-auto max-w-[85%] rounded-lg bg-primary px-3 py-2 text-sm text-primary-foreground"
>
{message.text}
</p>
))}
</div>
<div className="flex flex-col gap-2 border-t border-border p-3">
{open && (
<Command
items={topics}
value={query}
onValueChange={(next, details) => {
if (details.reason === "item-press") return;
setQuery(next);
}}
className="rounded-lg! border border-border shadow-sm"
>
<CommandInput
ref={searchRef}
placeholder="Search saved replies"
aria-label="Search saved replies"
onKeyDown={(event) => {
if (
event.key === "Escape" ||
(event.key === "Backspace" && query === "")
) {
event.preventDefault();
closeReplies();
}
}}
/>
<CommandList className="max-h-48">
{(topic: Topic) => (
<CommandGroup
key={topic.value}
heading={topic.value}
items={topic.items}
>
{(reply: Reply) => (
<CommandItem
key={reply.value}
value={reply}
onClick={() => insert(reply)}
className="items-start"
>
<MessageSquareTextIcon
className="mt-0.5 text-muted-foreground"
aria-hidden="true"
/>
<span className="flex min-w-0 flex-col">
<span>{reply.label}</span>
<span className="line-clamp-1 text-xs text-muted-foreground">
{reply.body}
</span>
</span>
</CommandItem>
)}
</CommandGroup>
)}
</CommandList>
<CommandEmpty>No saved reply matches. Write it by hand.</CommandEmpty>
</Command>
)}
<label htmlFor="command-10-reply" className="sr-only">
Reply to Rosa
</label>
<Textarea
id="command-10-reply"
ref={textareaRef}
value={draft}
onChange={(event) => setDraft(event.target.value)}
onKeyDown={(event) => {
const at = event.currentTarget.selectionStart;
const before = draft.slice(0, at);
if (event.key === "/" && (before === "" || /\s$/.test(before))) {
event.preventDefault();
openReplies();
}
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
send();
}
}}
placeholder="Write a reply. Type / for saved replies"
className="min-h-20 resize-none"
/>
<div className="flex items-center justify-between gap-2">
<Button
variant="ghost"
size="sm"
aria-expanded={open}
onClick={() => (open ? closeReplies() : openReplies())}
>
<SlashIcon aria-hidden="true" />
Saved replies
</Button>
<div className="flex items-center gap-2">
<Kbd className="hidden sm:inline-flex">⌘ Enter</Kbd>
<Button size="sm" onClick={send} disabled={!draft.trim()}>
<SendIcon aria-hidden="true" />
Send
</Button>
</div>
</div>
</div>
</div>
);
}
npx shadcn@latest add @sevenui/component/command-10pnpm dlx shadcn@latest add @sevenui/component/command-10yarn dlx shadcn@latest add @sevenui/component/command-10bunx --bun shadcn@latest add @sevenui/component/command-10"use client";
import * as React from "react";
import {
FileCodeIcon,
FileImageIcon,
FileSpreadsheetIcon,
FileTextIcon,
FolderIcon,
} from "lucide-react";
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
type Kind = "doc" | "sheet" | "image" | "code";
type FileEntry = {
value: string;
label: string;
folder: string;
kind: Kind;
size: string;
modified: string;
owner: string;
};
const files: FileEntry[] = [
{
value: "q3-board-update",
label: "Q3 board update.docx",
folder: "Finance / Reports",
kind: "doc",
size: "284 KB",
modified: "Today, 09:12",
owner: "Priya Nair",
},
{
value: "runway-model",
label: "Runway model 2026.xlsx",
folder: "Finance / Planning",
kind: "sheet",
size: "1.2 MB",
modified: "Yesterday",
owner: "Daniel Okafor",
},
{
value: "brand-hero",
label: "Spring launch hero.png",
folder: "Marketing / Campaigns",
kind: "image",
size: "3.8 MB",
modified: "Sep 18",
owner: "Lena Fischer",
},
{
value: "pricing-page-copy",
label: "Pricing page copy.docx",
folder: "Marketing / Website",
kind: "doc",
size: "96 KB",
modified: "Sep 16",
owner: "Tomás Rivera",
},
{
value: "export-script",
label: "export-invoices.ts",
folder: "Engineering / Scripts",
kind: "code",
size: "12 KB",
modified: "Sep 12",
owner: "Maya Chen",
},
{
value: "hiring-plan",
label: "Hiring plan H2.xlsx",
folder: "People / Planning",
kind: "sheet",
size: "418 KB",
modified: "Sep 3",
owner: "Priya Nair",
},
];
const kindIcon: Record<Kind, typeof FileTextIcon> = {
doc: FileTextIcon,
sheet: FileSpreadsheetIcon,
image: FileImageIcon,
code: FileCodeIcon,
};
const kindName: Record<Kind, string> = {
doc: "Document",
sheet: "Spreadsheet",
image: "Image",
code: "Source file",
};
function matchesFile(file: FileEntry, query: string) {
const needle = query.trim().toLowerCase();
if (!needle) return true;
return `${file.folder} ${file.label}`.toLowerCase().includes(needle);
}
// Emphasize the part of the name that matched the query.
function Highlight({ text, query }: { text: string; query: string }) {
const needle = query.trim();
const index = needle ? text.toLowerCase().indexOf(needle.toLowerCase()) : -1;
if (index === -1) return <>{text}</>;
return (
<>
{text.slice(0, index)}
<mark className="rounded-[2px] bg-transparent font-semibold text-foreground underline decoration-primary/60 underline-offset-2">
{text.slice(index, index + needle.length)}
</mark>
{text.slice(index + needle.length)}
</>
);
}
export default function Command11() {
const [query, setQuery] = React.useState("");
const [active, setActive] = React.useState<FileEntry>(files[0]);
const [opened, setOpened] = React.useState<string | null>(null);
const ActiveIcon = kindIcon[active.kind];
return (
<div className="grid w-full max-w-2xl overflow-hidden rounded-xl border border-border bg-card text-card-foreground sm:grid-cols-[minmax(0,1fr)_14rem]">
<Command
items={files}
value={query}
onValueChange={(next, details) => {
if (details.reason === "item-press") return;
setQuery(next);
}}
filter={(file, value) => matchesFile(file as FileEntry, value)}
onItemHighlighted={(file) => {
if (file) setActive(file as FileEntry);
}}
className="rounded-none! bg-card"
>
<CommandInput
placeholder="Go to file or folder…"
aria-label="Go to file"
/>
<CommandList className="max-h-64 sm:max-h-72">
{(file: FileEntry) => {
const Icon = kindIcon[file.kind];
return (
<CommandItem
key={file.value}
value={file}
onClick={() => setOpened(file.value)}
>
<Icon className="text-muted-foreground" aria-hidden="true" />
<span className="flex min-w-0 flex-col">
<span className="truncate">
<Highlight text={file.label} query={query} />
</span>
<span className="truncate text-xs text-muted-foreground">
{file.folder}
</span>
</span>
{opened === file.value && (
<span className="ml-auto text-xs text-muted-foreground">
Open
</span>
)}
</CommandItem>
);
}}
</CommandList>
<CommandEmpty>
Nothing in your drive matches that name.
</CommandEmpty>
</Command>
<aside
aria-label="File details"
className="flex flex-col gap-4 border-t border-border bg-muted/40 p-4 sm:border-t-0 sm:border-l"
>
<div className="flex aspect-[4/3] items-center justify-center rounded-lg border border-border bg-background">
<ActiveIcon
className="size-10 text-muted-foreground"
strokeWidth={1.25}
aria-hidden="true"
/>
</div>
<div className="min-w-0">
<p className="truncate text-sm font-medium">{active.label}</p>
<p className="flex items-center gap-1 text-xs text-muted-foreground">
<FolderIcon className="size-3" aria-hidden="true" />
<span className="truncate">{active.folder}</span>
</p>
</div>
<dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5 text-xs">
<dt className="text-muted-foreground">Type</dt>
<dd className="text-right">{kindName[active.kind]}</dd>
<dt className="text-muted-foreground">Size</dt>
<dd className="text-right tabular-nums">{active.size}</dd>
<dt className="text-muted-foreground">Modified</dt>
<dd className="text-right">{active.modified}</dd>
<dt className="text-muted-foreground">Owner</dt>
<dd className="truncate text-right">{active.owner}</dd>
</dl>
<p className="mt-auto text-xs text-muted-foreground">
{opened === active.value
? "Opened in a new tab."
: "Press Enter to open."}
</p>
</aside>
</div>
);
}
npx shadcn@latest add @sevenui/component/command-11pnpm dlx shadcn@latest add @sevenui/component/command-11yarn dlx shadcn@latest add @sevenui/component/command-11bunx --bun shadcn@latest add @sevenui/component/command-11How can we help?
"use client";
import * as React from "react";
import {
ArrowLeftIcon,
BookOpenIcon,
LifeBuoyIcon,
ThumbsDownIcon,
ThumbsUpIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
type Article = {
value: string;
label: string;
summary: string;
keywords: string;
minutes: number;
steps: string[];
};
type Section = { value: string; items: Article[] };
const sections: Section[] = [
{
value: "Billing",
items: [
{
value: "change-plan",
label: "Upgrade or downgrade your plan",
summary: "Plan changes are prorated to the day.",
keywords: "pricing subscription seats prorate",
minutes: 2,
steps: [
"Open Settings → Billing.",
"Choose Change plan and pick the new tier.",
"Review the prorated amount and confirm.",
],
},
{
value: "vat-invoice",
label: "Add a VAT number to invoices",
summary: "Tax IDs appear on every invoice after you save them.",
keywords: "tax receipt company eu",
minutes: 1,
steps: [
"Open Settings → Billing → Invoice details.",
"Enter your VAT number and company address.",
"Past invoices can be regenerated from the invoice list.",
],
},
],
},
{
value: "Account & security",
items: [
{
value: "two-factor",
label: "Recover access without your 2FA device",
summary: "Use a backup code or ask a workspace owner to reset.",
keywords: "two factor authenticator lost phone login mfa",
minutes: 3,
steps: [
"On the sign-in screen, choose Use a backup code.",
"Enter one of the ten codes you saved during setup.",
"No codes left? A workspace owner can reset 2FA from Members.",
],
},
{
value: "sso",
label: "Set up SAML single sign-on",
summary: "Available on Business and Enterprise plans.",
keywords: "okta azure google workspace saml login",
minutes: 5,
steps: [
"Open Settings → Security → Single sign-on.",
"Paste your identity provider's metadata URL.",
"Test the connection before enforcing SSO for everyone.",
],
},
],
},
];
function matchesArticle(article: Article, query: string) {
const words = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
const haystack =
`${article.label} ${article.summary} ${article.keywords}`.toLowerCase();
// Every word must appear somewhere, in any order.
return words.every((word) => haystack.includes(word));
}
export default function Command12() {
const [query, setQuery] = React.useState("");
const [article, setArticle] = React.useState<Article | null>(null);
const [feedback, setFeedback] = React.useState<"yes" | "no" | null>(null);
const [ticket, setTicket] = React.useState<string | null>(null);
const headingRef = React.useRef<HTMLHeadingElement>(null);
const searchRef = React.useRef<HTMLInputElement>(null);
// Only move focus after a user action, never on the first render.
const navigated = React.useRef(false);
React.useEffect(() => {
if (!navigated.current) return;
// The article view and the search swap places, so hand focus to
// whichever one just appeared instead of dropping it on the page.
if (article) headingRef.current?.focus();
else searchRef.current?.focus();
}, [article]);
function openArticle(next: Article) {
navigated.current = true;
setFeedback(null);
setArticle(next);
}
return (
<section
aria-label="Help center"
className="flex w-full max-w-sm flex-col overflow-hidden rounded-xl border border-border bg-card text-card-foreground shadow-sm"
>
<header className="flex items-center gap-2 border-b border-border bg-muted/40 px-4 py-3">
<LifeBuoyIcon className="size-4 text-muted-foreground" aria-hidden="true" />
<h3 className="text-sm font-medium">How can we help?</h3>
</header>
{article ? (
<div className="flex flex-col gap-3 p-4">
<Button
variant="ghost"
size="sm"
className="-ml-2 self-start"
onClick={() => {
navigated.current = true;
setArticle(null);
}}
>
<ArrowLeftIcon aria-hidden="true" />
Back to results
</Button>
<h4
ref={headingRef}
tabIndex={-1}
className="text-base font-medium text-balance outline-none"
>
{article.label}
</h4>
<p className="text-sm text-muted-foreground">{article.summary}</p>
<ol className="flex list-decimal flex-col gap-1.5 pl-5 text-sm marker:text-muted-foreground">
{article.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ol>
<div className="mt-1 flex items-center justify-between gap-2 border-t border-border pt-3">
<p className="text-xs text-muted-foreground" aria-live="polite">
{feedback === null
? "Did this answer your question?"
: feedback === "yes"
? "Thanks, glad it helped."
: "Sorry about that. We'll improve this article."}
</p>
<div className="flex gap-1">
<Button
variant={feedback === "yes" ? "secondary" : "ghost"}
size="icon-sm"
aria-label="Yes, this helped"
aria-pressed={feedback === "yes"}
onClick={() => setFeedback("yes")}
>
<ThumbsUpIcon aria-hidden="true" />
</Button>
<Button
variant={feedback === "no" ? "secondary" : "ghost"}
size="icon-sm"
aria-label="No, this did not help"
aria-pressed={feedback === "no"}
onClick={() => setFeedback("no")}
>
<ThumbsDownIcon aria-hidden="true" />
</Button>
</div>
</div>
</div>
) : (
<Command
items={sections}
value={query}
onValueChange={(next, details) => {
if (details.reason === "item-press") return;
setQuery(next);
setTicket(null);
}}
filter={(item, value) => matchesArticle(item as Article, value)}
className="rounded-none! bg-card p-2"
>
<CommandInput
ref={searchRef}
placeholder="Search guides, e.g. “lost 2FA phone”"
aria-label="Search help articles"
/>
<CommandList className="max-h-64">
{(section: Section) => (
<CommandGroup
key={section.value}
heading={section.value}
items={section.items}
>
{(item: Article) => (
<CommandItem
key={item.value}
value={item}
onClick={() => openArticle(item)}
className="items-start"
>
<BookOpenIcon
className="mt-0.5 text-muted-foreground"
aria-hidden="true"
/>
<span className="flex min-w-0 flex-col">
<span>{item.label}</span>
<span className="text-xs text-muted-foreground">
{item.summary}
</span>
</span>
<span className="ml-auto shrink-0 text-xs text-muted-foreground tabular-nums">
{item.minutes} min
</span>
</CommandItem>
)}
</CommandGroup>
)}
</CommandList>
<CommandEmpty className="px-4 not-empty:py-5">
<div className="flex flex-col items-center gap-3">
<p className="text-muted-foreground text-pretty">
No guide covers “{query.trim()}” yet.
</p>
{ticket ? (
<p className="text-sm" role="status">
Ticket <span className="font-mono">{ticket}</span> opened.
We reply within 4 hours.
</p>
) : (
<Button size="sm" onClick={() => setTicket("SUP-20931")}>
<LifeBuoyIcon aria-hidden="true" />
Ask our support team
</Button>
)}
</div>
</CommandEmpty>
</Command>
)}
</section>
);
}
npx shadcn@latest add @sevenui/component/command-12pnpm dlx shadcn@latest add @sevenui/component/command-12yarn dlx shadcn@latest add @sevenui/component/command-12bunx --bun shadcn@latest add @sevenui/component/command-12Order · Table 7
2 itemsOat milk latte
$10.40
2
- Subtotal
- $10.40
- Tax (8%)
- $0.83
- Total
- $11.23
"use client";
import * as React from "react";
import { MinusIcon, PlusIcon, ShoppingBagIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Separator } from "@/components/ui/separator";
type Product = {
value: string;
label: string;
sku: string;
price: number;
stock: number;
};
const products: Product[] = [
{ value: "oat-latte", label: "Oat milk latte", sku: "BEV-104", price: 5.2, stock: 48 },
{ value: "cold-brew", label: "Cold brew, 12 oz", sku: "BEV-221", price: 4.5, stock: 12 },
{ value: "cardamom-bun", label: "Cardamom bun", sku: "BAK-310", price: 3.8, stock: 3 },
{ value: "sourdough", label: "Sourdough loaf", sku: "BAK-402", price: 7.5, stock: 0 },
{ value: "beans-ethiopia", label: "Ethiopia Guji beans, 250 g", sku: "RET-015", price: 16, stock: 9 },
{ value: "ceramic-cup", label: "Ceramic cup, sand", sku: "RET-088", price: 22, stock: 4 },
];
const TAX_RATE = 0.08;
const currency = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
});
function matchesProduct(product: Product, query: string) {
const needle = query.trim().toLowerCase();
if (!needle) return true;
return `${product.label} ${product.sku}`.toLowerCase().includes(needle);
}
export default function Command13() {
const [query, setQuery] = React.useState("");
const [cart, setCart] = React.useState<Record<string, number>>({
"oat-latte": 2,
});
const [charged, setCharged] = React.useState<string | null>(null);
function setQuantity(product: Product, quantity: number) {
setCharged(null);
setCart((current) => {
const next = { ...current };
const clamped = Math.min(Math.max(quantity, 0), product.stock);
if (clamped === 0) delete next[product.value];
else next[product.value] = clamped;
return next;
});
}
function add(product: Product) {
setQuantity(product, (cart[product.value] ?? 0) + 1);
setQuery("");
}
const lines = products.filter((product) => cart[product.value]);
const subtotal = lines.reduce(
(sum, product) => sum + product.price * cart[product.value],
0,
);
const tax = subtotal * TAX_RATE;
const total = subtotal + tax;
const count = lines.reduce((sum, product) => sum + cart[product.value], 0);
return (
<div className="grid w-full max-w-2xl gap-3 sm:grid-cols-[minmax(0,1fr)_16rem]">
<Command
items={products}
value={query}
onValueChange={(next, details) => {
if (details.reason === "item-press") return;
setQuery(next);
}}
filter={(product, value) => matchesProduct(product as Product, value)}
className="h-fit rounded-xl! border border-border"
>
<CommandInput
placeholder="Scan or search name, SKU…"
aria-label="Add product to order"
/>
<CommandList>
{(product: Product) => {
const inCart = cart[product.value] ?? 0;
const soldOut = product.stock === 0;
const maxed = !soldOut && inCart >= product.stock;
return (
<CommandItem
key={product.value}
value={product}
disabled={soldOut || maxed}
onClick={() => add(product)}
>
<span className="flex min-w-0 flex-col">
<span className="truncate">{product.label}</span>
<span className="truncate text-xs text-muted-foreground">
<span className="font-mono">{product.sku}</span>
{" · "}
{soldOut
? "Sold out"
: product.stock <= 5
? `Only ${product.stock} left`
: `${product.stock} in stock`}
</span>
</span>
<span className="ml-auto flex shrink-0 items-center gap-2">
{inCart > 0 && (
<span className="rounded-full bg-primary px-1.5 text-xs font-medium text-primary-foreground tabular-nums">
{inCart}
<span className="sr-only"> in order</span>
</span>
)}
<span className="text-sm tabular-nums">
{currency.format(product.price)}
</span>
</span>
</CommandItem>
);
}}
</CommandList>
<CommandEmpty>No product or SKU matches.</CommandEmpty>
</Command>
<section
aria-labelledby="command-13-order"
className="flex flex-col rounded-xl border border-border bg-card p-3 text-card-foreground"
>
<div className="flex items-center justify-between">
<h3 id="command-13-order" className="text-sm font-medium">
Order · Table 7
</h3>
<span className="text-xs text-muted-foreground tabular-nums">
{count} {count === 1 ? "item" : "items"}
</span>
</div>
{lines.length === 0 ? (
<div className="flex flex-1 flex-col items-center justify-center gap-2 py-8 text-center">
<ShoppingBagIcon
className="size-5 text-muted-foreground"
aria-hidden="true"
/>
<p className="text-sm text-muted-foreground">
Search a product and press Enter to add it.
</p>
</div>
) : (
<ul className="flex flex-col gap-2 py-3">
{lines.map((product) => (
<li key={product.value} className="flex items-center gap-2">
<div className="min-w-0 flex-1">
<p className="truncate text-sm">{product.label}</p>
<p className="text-xs text-muted-foreground tabular-nums">
{currency.format(product.price * cart[product.value])}
</p>
</div>
<div className="flex items-center gap-1">
<Button
variant="outline"
size="icon-xs"
aria-label={`Remove one ${product.label}`}
onClick={() =>
setQuantity(product, cart[product.value] - 1)
}
>
<MinusIcon aria-hidden="true" />
</Button>
<span className="w-5 text-center text-sm tabular-nums">
{cart[product.value]}
</span>
<Button
variant="outline"
size="icon-xs"
aria-label={`Add one ${product.label}`}
disabled={cart[product.value] >= product.stock}
onClick={() =>
setQuantity(product, cart[product.value] + 1)
}
>
<PlusIcon aria-hidden="true" />
</Button>
</div>
</li>
))}
</ul>
)}
<Separator />
<dl className="grid grid-cols-2 gap-y-1 py-3 text-sm">
<dt className="text-muted-foreground">Subtotal</dt>
<dd className="text-right tabular-nums">{currency.format(subtotal)}</dd>
<dt className="text-muted-foreground">Tax (8%)</dt>
<dd className="text-right tabular-nums">{currency.format(tax)}</dd>
<dt className="font-medium">Total</dt>
<dd className="text-right font-medium tabular-nums">
{currency.format(total)}
</dd>
</dl>
<Button
disabled={lines.length === 0}
onClick={() => {
setCharged(currency.format(total));
setCart({});
}}
>
Charge {currency.format(total)}
</Button>
<p role="status" className="min-h-4 pt-2 text-center text-xs text-muted-foreground">
{charged && `Payment of ${charged} approved. Receipt sent.`}
</p>
</section>
</div>
);
}
npx shadcn@latest add @sevenui/component/command-13pnpm dlx shadcn@latest add @sevenui/component/command-13yarn dlx shadcn@latest add @sevenui/component/command-13bunx --bun shadcn@latest add @sevenui/component/command-13acme-storefront
shop.acme.dev
ProductionReady
Checkout copy tweaks
dpl_62e1bf · main
Activity
- 09:41dpl_62e1bf promoted to production
Project actions
Deploy, roll back and manage acme-storefront.
"use client";
import * as React from "react";
import {
ArrowUpCircleIcon,
ChevronRightIcon,
CircleCheckIcon,
CommandIcon,
CopyIcon,
GitBranchIcon,
HistoryIcon,
RefreshCwIcon,
ScrollTextIcon,
Undo2Icon,
XIcon,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut,
} from "@/components/ui/command";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
type Deployment = {
id: string;
branch: string;
message: string;
age: string;
};
type Action = "promote" | "rollback";
type Entry = {
value: string;
label: string;
hint?: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
shortcut?: string;
run: () => void;
// Entries that open another page show a chevron.
nested?: boolean;
};
type Section = { value: string; items: Entry[] };
type Page =
| { kind: "root" }
| { kind: "pick"; action: Action }
| { kind: "confirm"; action: Action; deployment: Deployment };
const previews: Deployment[] = [
{ id: "dpl_8f2a91", branch: "feat/gift-cards", message: "Add gift card redemption at checkout", age: "12m ago" },
{ id: "dpl_71c0de", branch: "fix/tax-rounding", message: "Round VAT per line instead of per order", age: "1h ago" },
{ id: "dpl_5b3e47", branch: "chore/deps", message: "Bump payments SDK to 4.2", age: "Yesterday" },
];
const history: Deployment[] = [
{ id: "dpl_40d9aa", branch: "main", message: "Faster product image loading", age: "2d ago" },
{ id: "dpl_3a7f12", branch: "main", message: "New shipping rates for EU", age: "5d ago" },
];
const actionCopy: Record<Action, { title: string; verb: string }> = {
promote: { title: "Promote to production", verb: "Promote" },
rollback: { title: "Roll back production", verb: "Roll back to" },
};
type LogLine = { id: number; text: string; time: string };
export default function Command14() {
const [open, setOpen] = React.useState(false);
const [pages, setPages] = React.useState<Page[]>([{ kind: "root" }]);
const [query, setQuery] = React.useState("");
const [production, setProduction] = React.useState<Deployment>({
id: "dpl_62e1bf",
branch: "main",
message: "Checkout copy tweaks",
age: "4h ago",
});
const [log, setLog] = React.useState<LogLine[]>([
{ id: 1, text: "dpl_62e1bf promoted to production", time: "09:41" },
]);
const page = pages[pages.length - 1];
// Scoped to this card (and its palette, which bubbles through the portal)
// so ⌘K does not also trigger an app-wide search listening on document.
function handleShortcut(event: React.KeyboardEvent) {
if (event.key.toLowerCase() === "k" && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
event.stopPropagation();
// React may be mounted on document itself (Next.js App Router), where
// stopPropagation cannot reach sibling listeners on the same node.
event.nativeEvent.stopImmediatePropagation();
handleOpenChange(!open);
}
}
function record(text: string) {
setLog((current) => [
{ id: current.length + 1, text, time: "Just now" },
...current,
]);
}
function push(next: Page) {
setPages((current) => [...current, next]);
setQuery("");
}
function pop() {
setPages((current) => (current.length > 1 ? current.slice(0, -1) : current));
}
function finish(text: string) {
record(text);
setOpen(false);
}
function handleOpenChange(next: boolean) {
setOpen(next);
if (!next) {
setPages([{ kind: "root" }]);
setQuery("");
}
}
function toEntry(action: Action, deployment: Deployment): Entry {
return {
value: deployment.id,
label: deployment.message,
hint: `${deployment.id} · ${deployment.branch} · ${deployment.age}`,
icon: action === "promote" ? GitBranchIcon : HistoryIcon,
nested: true,
run: () => push({ kind: "confirm", action, deployment }),
};
}
let sections: Section[];
if (page.kind === "root") {
sections = [
{
value: "Deployments",
items: [
{
value: "promote",
label: "Promote a preview…",
icon: ArrowUpCircleIcon,
nested: true,
run: () => push({ kind: "pick", action: "promote" }),
},
{
value: "rollback",
label: "Roll back production…",
icon: Undo2Icon,
nested: true,
run: () => push({ kind: "pick", action: "rollback" }),
},
],
},
{
value: "Project",
items: [
{
value: "logs",
label: "Open runtime logs",
icon: ScrollTextIcon,
shortcut: "⌘L",
run: () => finish("Runtime logs opened in a new tab"),
},
{
value: "purge",
label: "Purge CDN cache",
icon: RefreshCwIcon,
run: () => finish("CDN cache purged for acme-storefront"),
},
{
value: "copy-url",
label: "Copy production URL",
icon: CopyIcon,
shortcut: "⌘⇧C",
run: () => finish("Copied shop.acme.dev to clipboard"),
},
],
},
];
} else if (page.kind === "pick") {
sections = [
{
value:
page.action === "promote"
? "Ready previews"
: "Previous production deployments",
items: (page.action === "promote" ? previews : history).map(
(deployment) => toEntry(page.action, deployment),
),
},
];
} else {
const { action, deployment } = page;
sections = [
{
value: `${actionCopy[action].verb} ${deployment.id}?`,
items: [
{
value: "confirm",
label: `${actionCopy[action].verb} ${deployment.id}`,
hint: `Replaces ${production.id}. Traffic switches in about 10 seconds.`,
icon: CircleCheckIcon,
run: () => {
setProduction(deployment);
finish(
action === "promote"
? `${deployment.id} promoted to production`
: `Production rolled back to ${deployment.id}`,
);
},
},
{
value: "cancel",
label: "Cancel",
icon: XIcon,
run: pop,
},
],
},
];
}
const crumbs = pages.slice(1).map((entry) =>
entry.kind === "pick"
? actionCopy[entry.action].title
: entry.kind === "confirm"
? entry.deployment.id
: "",
);
return (
<section
aria-labelledby="command-14-title"
onKeyDown={handleShortcut}
className="w-full max-w-md rounded-xl border border-border bg-card text-card-foreground"
>
<header className="flex items-center justify-between gap-3 border-b border-border p-4">
<div className="min-w-0">
<h3 id="command-14-title" className="truncate text-sm font-medium">
acme-storefront
</h3>
<p className="truncate text-xs text-muted-foreground">
shop.acme.dev
</p>
</div>
<Button variant="outline" size="sm" onClick={() => setOpen(true)}>
<CommandIcon aria-hidden="true" />
Actions
<KbdGroup className="ml-1 hidden sm:inline-flex">
<Kbd>⌘</Kbd>
<Kbd>K</Kbd>
</KbdGroup>
</Button>
</header>
<div className="flex flex-col gap-1 p-4">
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">Production</span>
<Badge variant="secondary" className="gap-1">
<span
className="size-1.5 rounded-full bg-success"
aria-hidden="true"
/>
Ready
</Badge>
</div>
<p className="truncate text-sm font-medium">{production.message}</p>
<p className="truncate font-mono text-xs text-muted-foreground">
{production.id} · {production.branch}
</p>
</div>
<div className="border-t border-border p-4">
<h4 className="text-xs font-medium text-muted-foreground">Activity</h4>
<ol aria-live="polite" className="mt-2 flex flex-col gap-1.5">
{log.slice(0, 4).map((line) => (
<li key={line.id} className="flex items-baseline gap-3 text-sm">
<span className="w-16 shrink-0 text-xs text-muted-foreground tabular-nums">
{line.time}
</span>
<span className="min-w-0 truncate">{line.text}</span>
</li>
))}
</ol>
</div>
<CommandDialog
open={open}
onOpenChange={handleOpenChange}
title="Project actions"
description="Deploy, roll back and manage acme-storefront."
>
<Command
items={sections}
value={query}
onValueChange={(next, details) => {
if (details.reason === "item-press") return;
setQuery(next);
}}
filter={(item, value) => {
const entry = item as Entry;
const needle = value.trim().toLowerCase();
// Deployments are found by message, id or branch.
return `${entry.label} ${entry.hint ?? ""}`
.toLowerCase()
.includes(needle);
}}
className="rounded-lg border-none"
>
{crumbs.length > 0 && (
<nav
aria-label="Command path"
className="flex flex-wrap items-center gap-1 px-2 pt-2 text-xs text-muted-foreground"
>
<span>Actions</span>
{crumbs.map((crumb) => (
<React.Fragment key={crumb}>
<ChevronRightIcon className="size-3" aria-hidden="true" />
<span className="font-medium text-foreground">{crumb}</span>
</React.Fragment>
))}
</nav>
)}
<CommandInput
placeholder={
page.kind === "root"
? "Search project actions…"
: page.kind === "pick"
? "Filter by message, id or branch…"
: "Confirm or cancel…"
}
aria-label="Search project actions"
onKeyDown={(event) => {
if (event.key === "Backspace" && query === "" && pages.length > 1) {
event.preventDefault();
pop();
}
}}
/>
<CommandList>
{(section: Section) => (
<CommandGroup
key={section.value}
heading={section.value}
items={section.items}
>
{(entry: Entry) => (
<CommandItem
key={entry.value}
value={entry}
onClick={entry.run}
className={entry.hint ? "items-start" : undefined}
>
<entry.icon
className={
entry.hint
? "mt-0.5 text-muted-foreground"
: "text-muted-foreground"
}
aria-hidden="true"
/>
<span className="flex min-w-0 flex-col">
<span className="truncate">{entry.label}</span>
{entry.hint && (
<span className="truncate text-xs text-muted-foreground">
{entry.hint}
</span>
)}
</span>
{entry.shortcut && (
<CommandShortcut>{entry.shortcut}</CommandShortcut>
)}
{entry.nested && (
<ChevronRightIcon
className="ml-auto text-muted-foreground"
aria-hidden="true"
/>
)}
</CommandItem>
)}
</CommandGroup>
)}
</CommandList>
<CommandEmpty>No matching action.</CommandEmpty>
<div className="flex items-center justify-between gap-2 border-t border-border px-3 py-2 text-xs text-muted-foreground">
<span className="flex items-center gap-1.5">
<Kbd>↵</Kbd> Select
</span>
{pages.length > 1 && (
<span className="flex items-center gap-1.5">
<Kbd>⌫</Kbd> Back
</span>
)}
<span className="flex items-center gap-1.5">
<Kbd>Esc</Kbd> Close
</span>
</div>
</Command>
</CommandDialog>
</section>
);
}
npx shadcn@latest add @sevenui/component/command-14pnpm dlx shadcn@latest add @sevenui/component/command-14yarn dlx shadcn@latest add @sevenui/component/command-14bunx --bun shadcn@latest add @sevenui/component/command-14