9.1 关键概念
- Command(cmdk)
- 基于
cmdk库的命令面板/搜索列表,带即时过滤、键盘导航、分组。是 ⌘K 全局搜索、Combobox 的底座。 - CommandDialog
- Command 套一层 Dialog,配合快捷键(如 ⌘K)唤起的全局命令面板,Vercel/Linear 同款交互。
- Combobox
- shadcn 没有单独的 Combobox 组件,而是用 Popover + Command 组合出"可搜索的下拉选择器",是一种模式而非文件。
- DatePicker
- 同样是组合模式:Popover + Calendar(Calendar 基于 react-day-picker),点触发按钮弹出日历选日期。
- Chart
- shadcn 对
Recharts的一层薄封装:ChartContainer+ChartConfig把图表色接入 CSS 变量令牌,配ChartTooltip。 - 复合组件封装
- 把常用组合(如带图标+校验的输入)二次封装成项目内组件,减少重复、统一风格,仍建立在 shadcn 原语上。
- Registry(注册表)
- 一个符合 shadcn schema 的 JSON 端点,描述组件的文件、依赖、注册依赖。他人可用
npx shadcn add <你的url>拉取你的组件。 - registry:item schema
- 单个组件的清单格式:
name / type / files / dependencies / registryDependencies,CLI 据此把文件写进使用者项目。
9.2 安装
$ npx shadcn@latest add command popover calendar chart
$ npm i cmdk react-day-picker date-fns recharts
9.3 Command 面板(⌘K)
"use client";
import { useEffect, useState } from "react";
import {
CommandDialog, CommandEmpty, CommandGroup,
CommandInput, CommandItem, CommandList,
} from "@/components/ui/command";
export function CommandMenu() {
const [open, setOpen] = useState(false);
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault(); setOpen((o) => !o);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="输入命令或搜索…" />
<CommandList>
<CommandEmpty>无结果。</CommandEmpty>
<CommandGroup heading="建议">
<CommandItem>打开仪表盘</CommandItem>
<CommandItem>新建项目</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
);
}
9.4 Combobox = Popover + Command
const [open, setOpen] = useState(false);
const [value, setValue] = useState("");
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="outline" role="combobox" aria-expanded={open}
className="w-[200px] justify-between">
{value || "选择框架…"}
<ChevronsUpDown className="opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="搜索…" />
<CommandList>
<CommandEmpty>无结果</CommandEmpty>
<CommandGroup>
{frameworks.map((f) => (
<CommandItem key={f.value}
onSelect={() => { setValue(f.value); setOpen(false); }}>
{f.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
"模式"不是"组件"
Combobox 和 DatePicker 在 shadcn 里没有独立文件,而是把已有原语组合出来的配方。官网给示例代码,你抄进项目后就是自己的复合组件——可任意改造。
9.5 DatePicker = Popover + Calendar
import { Calendar } from "@/components/ui/calendar";
import { format } from "date-fns";
const [date, setDate] = useState<Date>();
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "yyyy-MM-dd") : "选择日期"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar mode="single" selected={date} onSelect={setDate} />
</PopoverContent>
</Popover>
9.6 Chart(Recharts + 令牌)
import {
ChartContainer, ChartTooltip, ChartTooltipContent,
type ChartConfig,
} from "@/components/ui/chart";
import { Bar, BarChart, XAxis } from "recharts";
const config = {
desktop: { label: "桌面", color: "var(--chart-1)" },
} satisfies ChartConfig;
<ChartContainer config={config} className="h-[200px] w-full">
<BarChart data={data}>
<XAxis dataKey="month" />
<ChartTooltip content={<ChartTooltipContent />} />
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
</BarChart>
</ChartContainer>
图表颜色引用 --chart-1..5 令牌,明暗主题自动跟随——与第 3 章的设计系统打通。
9.7 二次封装复合组件
// components/text-field.tsx —— 把 Label+Input+错误 封成一个件
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
export function TextField({ label, id, error, className, ...props }) {
return (
<div className={cn("grid gap-1.5", className)}>
<Label htmlFor={id}>{label}</Label>
<Input id={id} aria-invalid={!!error} {...props} />
{error && <p className="text-sm text-destructive">{error}</p>}
</div>
);
}
9.8 自建 Registry 分发组件
// registry.json —— 描述你要分发的组件
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"name": "acme-ui",
"homepage": "https://acme.com",
"items": [
{
"name": "text-field",
"type": "registry:component",
"registryDependencies": ["input", "label"],
"files": [
{ "path": "registry/text-field.tsx", "type": "registry:component" }
]
}
]
}
# 构建并托管后,别人这样安装你的组件
$ npx shadcn@latest build # 生成 public/r/*.json
$ npx shadcn@latest add https://acme.com/r/text-field.json
企业内共享组件的正解
团队想统一组件又要各项目"拥有代码"?自建 Registry:把公共组件发布成 JSON 端点,各项目用 CLI 拉取。既复用又保留可改的开放代码优势。
9.9 小结与下一步
- Command 是搜索列表底座;Combobox = Popover + Command、DatePicker = Popover + Calendar 是组合模式而非单文件。
- Chart 把 Recharts 接入
--chart-*令牌,图表随主题换肤。 - 把常用组合二次封装成项目组件,或做成 Registry 用 CLI 分发给团队。
- 最后一章实战与工程化:把全部所学拼成后台面板,附 a11y 清单与 AI 工作流。