Commit 7225e57e by luoqi

merge: main → test(design-sync 同步产物)

parents a92bed53 e1697a0d
# PAC design-sync 备注
## 仓库形态
- 无独立设计系统包、无 Storybook → **package 形态**。同步入口是 `apps/pac-web/src/ds/index.ts`(为 design-sync 新建的显式 re-export 桶),收录 `ui/` 14 个 shadcn 基础件 + `plan-detail/shared.tsx` 5 个工作台业务原语(Chip/PriorityBar/AIStamp/SidebarCard/MD)。
- `pkg=@pac/web`,`globalName=PAC`,`--entry apps/pac-web/src/ds/index.ts`,`--node-modules apps/pac-web/node_modules`
- 组件清单以 `componentSrcMap`(19 条 pin)为准,不靠 .d.ts 自动发现(app 是应用不是库,无 dist/无导出类型树)。
## Tailwind v4 CSS 管线(关键)
- 无 tailwind.config.js;v4 用 `@import "tailwindcss"` + `@theme`,globals.css 在 `apps/pac-web/src/app/globals.css`
- design-sync 用独立入口 `.design-sync/tailwind-entry.css`(@import globals + @source 扫 pac-web/src 和 previews),CLI(`@tailwindcss/cli`,装在 .ds-sync)编译到 `apps/pac-web/.ds-css/tailwind.css`(gitignored),cfg.cssEntry 指它。
- **重建顺序**:先跑 buildCmd(tailwindcss 编 CSS)→ 再 package-build。CSS 要扫到预览里的工具类,所以改了 previews/*.tsx 后必须重编 CSS 再 build,否则新类名不进产物。
## 组件排除
- `MD` 被 isComponentName 过滤掉(两字母全大写疑似常量/枚举)→ 只同步了 18/19。MD 是轻量 markdown 渲染器,设计系统里价值低,不救,接受 18。ds/index.ts 仍导出它(bundle 里有,只是无卡片)。
## 字体
- CSS 引用 "Microsoft YaHei"/"PingFang" 中文字体族,不 ship woff2(宿主/系统提供)→ 用 `runtimeFontPrefixes: ["Microsoft YaHei","PingFang"]` 抑制 [FONT_MISSING]。设计系统面板会用系统中文字体渲染,视觉无碍。
## 浮层卡片模式(cfg.overrides)
- Dialog/AlertDialog/Popover/HoverCard/DropdownMenu/Toaster → cardMode:single + 固定 viewport(open 态在卡内渲染,不逃逸)。Calendar → cardMode:column。
## 已知 render warns
- 无(本次跳过机检,--no-render-check;18 卡自检 iframe 均非空,截图确认令牌色/字体/Radix 结构正常)。
## Re-sync 风险
- **机检从未跑过**(用户选跳过 playwright)。下次若装了 chromium,去掉 --no-render-check 做一次真机检,可能暴露自检漏掉的问题。
- previews/*.tsx 里的示例文案是手写业务场景(黄莉/杭州高德诊所等),与真实数据无耦合,改 API 不影响;但组件 props 变更(如 Chip tone 增删)要同步改预览。
- ds/index.ts 是 design-sync 专属桶文件,不被 pac-web 应用引用——新增组件进设计系统需手动在此 re-export + componentSrcMap 登记。
- CSS 走 Tailwind CLI 全量扫描 apps/pac-web/src:若 pac-web 源码大改,产物类名集会变(通常无害,确定性重建即可)。
{
"projectId": "e75dbe4d-5024-46ff-8c82-9d0634f44a01",
"shape": "package",
"pkg": "@pac/web",
"globalName": "PAC",
"tsconfig": "tsconfig.json",
"cssEntry": ".ds-css/tailwind.css",
"buildCmd": ".ds-sync/node_modules/.bin/tailwindcss -i .design-sync/tailwind-entry.css -o apps/pac-web/.ds-css/tailwind.css --minify",
"componentSrcMap": {
"AlertDialog": "src/components/ui/alert-dialog.tsx",
"Badge": "src/components/ui/badge.tsx",
"Button": "src/components/ui/button.tsx",
"Calendar": "src/components/ui/calendar.tsx",
"Card": "src/components/ui/card.tsx",
"DatePicker": "src/components/ui/date-picker.tsx",
"Dialog": "src/components/ui/dialog.tsx",
"DropdownMenu": "src/components/ui/dropdown-menu.tsx",
"HoverCard": "src/components/ui/hover-card.tsx",
"Input": "src/components/ui/input.tsx",
"Popover": "src/components/ui/popover.tsx",
"Select": "src/components/ui/select.tsx",
"Tabs": "src/components/ui/tabs.tsx",
"Toaster": "src/components/ui/toaster.tsx",
"Chip": "src/components/plan-detail/shared.tsx",
"PriorityBar": "src/components/plan-detail/shared.tsx",
"AIStamp": "src/components/plan-detail/shared.tsx",
"SidebarCard": "src/components/plan-detail/shared.tsx",
"MD": "src/components/plan-detail/shared.tsx"
},
"runtimeFontPrefixes": [
"Microsoft YaHei",
"PingFang"
],
"overrides": {
"Dialog": {
"cardMode": "single",
"viewport": "560x420"
},
"AlertDialog": {
"cardMode": "single",
"viewport": "560x400"
},
"Popover": {
"cardMode": "single",
"viewport": "440x300"
},
"HoverCard": {
"cardMode": "single",
"viewport": "460x300"
},
"DropdownMenu": {
"cardMode": "single",
"viewport": "400x320"
},
"Toaster": {
"cardMode": "single",
"viewport": "480x320"
},
"Calendar": {
"cardMode": "column"
}
},
"readmeHeader": ".design-sync/conventions.md"
}
\ No newline at end of file
# PAC 设计系统 — 用法约定
PAC「疗效保障」是嵌进牙科宿主系统的**临床召回客服工作台**。栈:React 19 + Tailwind v4 + shadcn/ui(基于 Radix)。风格克制、信息密集、teal 品牌色主导、以边框而非阴影分隔。组件从 `window.PAC.*` 取,全部真实可用。
## 包裹与主题
**无需 Provider。** 所有组件靠 `styles.css` 里的 CSS 变量(`:root` 定义)取色,不依赖运行时主题上下文——直接用即可,唯一要求是页面加载了 `styles.css``Toaster` 例外:它是全局 toast 容器,每页**挂一次**(如根布局),再用 `toast()` / `toast.success()` / `toast.error()` 触发,别在每个组件里重复挂。
## 样式惯用法(两套并存,别混造)
这是 **Tailwind 工具类**系统,不是 props-样式或 CSS-modules。给你自己的布局胶水写工具类;组件外观由组件自身负责,你只需传对 variant/props。
**语义 CSS 变量(shadcn 层,组件内部用的角色色)**——改主题只动这些,组件不用改:
| 变量 | 角色 |
|---|---|
| `--primary` / `--primary-foreground` | 主交互色(= teal-600)/ 其上文字 |
| `--background` / `--foreground` | 页面底 / 主文字 |
| `--muted` / `--muted-foreground` | 弱化底 / 次要文字 |
| `--border` | 分隔线(PAC 特意加深) |
| `--card` `--popover` `--destructive` `--ring` | 卡片/浮层底、危险色、焦点环 |
**原始色阶工具类(你写布局时直接用的品牌调色板)**,真实存在于产物:
| 族 | 例(真实类名) | 用途 |
|---|---|---|
| 品牌 teal | `bg-teal-50` `text-teal-700` `ring-teal-200` `bg-teal-600` | 主色、选中态、标签 |
| 中性 slate | `text-slate-900` `text-slate-500` `border-slate-100` `bg-slate-50` | ~90% 界面 |
| 语义强调 | `text-rose-600`(危险)`text-amber-700`(进行中)`text-emerald-700`(成功)`text-sky-700`(待认领)`text-indigo-500`(AI) | 状态色 |
**尺寸口径**:PAC 偏小字密排——正文常 `text-[11px]`~`text-[13px]`,间距 `gap-1.5`/`gap-2``p-2`/`p-3`。别用大号松散排版。
## 真相在哪
- 样式:读 `styles.css`(它 `@import``_ds_bundle.css` 组件样式 + Tailwind 工具层),里面有全部 `--*` 变量与工具类的真实定义。
- 每个组件读 `components/<组>/<Name>/<Name>.prompt.md`(用法+示例)与 `<Name>.d.ts`(props 契约)。
- 分组:`actions`(Button/DropdownMenu)、`display`(Badge/Card/Tabs)、`forms`(Input/Select/Calendar/DatePicker)、`overlay`(Dialog/AlertDialog/Popover/HoverCard/Toaster)、`plan-detail`(工作台业务原语:Chip/PriorityBar/AIStamp/SidebarCard/MD)。
## 一个惯用片段
```tsx
import { Button, Card, CardHeader, CardTitle, CardContent, CardFooter, Chip } from '@pac/web';
// 召回池患者卡:库组件搭骨架,工具类做布局胶水
<Card className="w-[340px]">
<CardHeader>
<CardTitle className="text-base">
黄莉 <span className="ml-1 text-sm font-normal text-slate-500">女 · 51 岁</span>
</CardTitle>
</CardHeader>
<CardContent className="text-[12.5px] leading-relaxed text-slate-700">
36/46 缺牙拖月未治,重要价值新客,建议尽快邀约种植评估。
<div className="mt-2 flex flex-wrap gap-1.5">
<Chip tone="teal" size="xs">重要价值</Chip>
<Chip tone="rose" size="xs" icon>应治未治</Chip>
</div>
</CardContent>
<CardFooter className="gap-2">
<Button size="sm">认领</Button>
<Button size="sm" variant="outline">查看详情</Button>
</CardFooter>
</Card>
```
**业务原语 Chip 的 tone**:`teal`/`slate`/`amber`/`rose`/`indigo`/`emerald`/`sky`/`violet`(不是 shadcn 的 variant,是 PAC 自有色板);`size``xs`/`sm`;`icon` 加圆点。**Button 的 variant**:`default`/`secondary`/`outline`/`ghost`/`destructive`/`link`,`size`:`sm`/`default`/`lg`/`icon`
import { AIStamp } from '@pac/web';
export const Sources = () => (
<div className="flex flex-col items-start gap-2 p-3">
<AIStamp relative="刚刚" source="agent" />
<AIStamp relative="5 分钟前" source="template_fallback" />
<AIStamp relative="2 小时前" source="manual" />
</div>
);
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@pac/web';
export const AbandonConfirm = () => (
<AlertDialog open>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>放弃跟进该患者?</AlertDialogTitle>
<AlertDialogDescription>
放弃后本条召回将关闭并进入 90 天冷静期,期间不再推荐。此操作可由主管恢复。
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>再想想</AlertDialogCancel>
<AlertDialogAction className="bg-rose-600 hover:bg-rose-700">确认放弃</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
import { Badge } from '@pac/web';
export const Variants = () => (
<div className="flex flex-wrap items-center gap-2 p-2">
<Badge>默认</Badge>
<Badge variant="secondary">次要</Badge>
<Badge variant="destructive">警示</Badge>
<Badge variant="outline">描边</Badge>
</div>
);
export const Statuses = () => (
<div className="flex flex-wrap items-center gap-2 p-2">
<Badge variant="secondary">待认领</Badge>
<Badge>进行中</Badge>
<Badge variant="outline">已完成</Badge>
<Badge variant="destructive">已放弃</Badge>
</div>
);
import { Button } from '@pac/web';
import { PhoneCall, RefreshCw } from 'lucide-react';
export const Variants = () => (
<div className="flex flex-wrap items-center gap-2 p-2">
<Button>认领患者</Button>
<Button variant="secondary">查看档案</Button>
<Button variant="outline">新建预约</Button>
<Button variant="ghost">取消</Button>
<Button variant="destructive">放弃跟进</Button>
<Button variant="link">查看全部</Button>
</div>
);
export const Sizes = () => (
<div className="flex flex-wrap items-center gap-2 p-2">
<Button size="sm">小按钮</Button>
<Button>默认</Button>
<Button size="lg">大按钮</Button>
<Button size="icon" aria-label="拨打"><PhoneCall className="h-4 w-4" /></Button>
</div>
);
export const WithIcon = () => (
<div className="flex flex-wrap items-center gap-2 p-2">
<Button><PhoneCall className="h-4 w-4" /> 拨打电话</Button>
<Button variant="outline"><RefreshCw className="h-4 w-4" /> 刷新数据</Button>
</div>
);
export const Disabled = () => (
<div className="flex flex-wrap items-center gap-2 p-2">
<Button disabled>提交中…</Button>
<Button variant="outline" disabled>不可用</Button>
</div>
);
import { Calendar } from '@pac/web';
export const SingleSelect = () => (
<div className="p-2">
<Calendar mode="single" selected={new Date(2026, 6, 15)} defaultMonth={new Date(2026, 6, 1)} />
</div>
);
import { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@pac/web';
export const PatientCard = () => (
<Card className="w-[340px]">
<CardHeader>
<CardTitle>黄莉 <span className="ml-1 text-sm font-normal text-slate-500">女 · 51 岁</span></CardTitle>
<CardDescription>杭州高德诊所 · 病历号 ZJ0A034947</CardDescription>
</CardHeader>
<CardContent className="text-sm text-slate-700">
36/46 缺牙及多颗坏牙拖月未治,重要价值新客,建议尽快邀约种植评估。
</CardContent>
<CardFooter className="gap-2">
<Button size="sm">认领</Button>
<Button size="sm" variant="outline">查看详情</Button>
</CardFooter>
</Card>
);
export const Simple = () => (
<Card className="w-[300px]">
<CardHeader>
<CardTitle className="text-base">本周召回统计</CardTitle>
</CardHeader>
<CardContent className="text-sm text-slate-600">
已完成 42 通回访,转化新预约 11 个。
</CardContent>
</Card>
);
import { Chip } from '@pac/web';
export const Tones = () => (
<div className="flex flex-wrap items-center gap-2 p-2">
<Chip tone="teal">重要价值</Chip>
<Chip tone="amber">进行中</Chip>
<Chip tone="rose">应治未治</Chip>
<Chip tone="emerald">已完成</Chip>
<Chip tone="sky">待认领</Chip>
<Chip tone="indigo">AI 生成</Chip>
<Chip tone="violet">深度分析</Chip>
<Chip tone="slate">默认</Chip>
</div>
);
export const WithIconDot = () => (
<div className="flex flex-wrap items-center gap-2 p-2">
<Chip tone="rose" icon>应治未治</Chip>
<Chip tone="amber" icon>高优先级</Chip>
<Chip tone="emerald" icon size="xs">小号带点</Chip>
</div>
);
import { DatePicker } from '@pac/web';
export const WithValue = () => (
<div className="w-[260px] p-2">
<DatePicker value={new Date(2026, 6, 15)} onChange={() => {}} placeholder="选择回访日期" />
</div>
);
export const Empty = () => (
<div className="w-[260px] p-2">
<DatePicker onChange={() => {}} placeholder="选择回访日期" />
</div>
);
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@pac/web';
export const AppointmentConfirm = () => (
<Dialog open>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>确认新建预约</DialogTitle>
<DialogDescription>为 黄莉(ZJ0A034947)预约种植评估,2026-07-22 下午。</DialogDescription>
</DialogHeader>
<div className="text-sm text-slate-600">预约成功后本次召回任务将自动归档。</div>
<DialogFooter>
<Button variant="outline">取消</Button>
<Button>确认预约</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@pac/web';
export const RowActions = () => (
<div className="flex h-[260px] items-start justify-center p-2">
<DropdownMenu defaultOpen modal={false}>
<DropdownMenuTrigger asChild><Button variant="outline" size="sm">操作</Button></DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuLabel>患者操作</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>认领</DropdownMenuItem>
<DropdownMenuItem>查看档案</DropdownMenuItem>
<DropdownMenuItem>新建预约</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-rose-600">放弃跟进</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@pac/web';
export const PatientSummary = () => (
<div className="flex h-[220px] items-start justify-center p-2">
<HoverCard open>
<HoverCardTrigger asChild>
<span className="cursor-help font-medium text-teal-700 underline decoration-dotted">黄莉</span>
</HoverCardTrigger>
<HoverCardContent className="w-72 text-sm">
<p className="font-semibold">黄莉 · 女 · 51 岁</p>
<p className="mt-1 text-slate-600">重要价值新客,有种植、修复、拔牙及补牙四项应治未治项目待跟进,偏好工作日下午就诊。</p>
</HoverCardContent>
</HoverCard>
</div>
);
import { Input } from '@pac/web';
export const States = () => (
<div className="flex w-[300px] flex-col gap-3 p-2">
<Input placeholder="姓名 / 手机 / 病历号" />
<Input defaultValue="黄莉" />
<Input disabled placeholder="不可编辑" />
</div>
);
export const Types = () => (
<div className="flex w-[300px] flex-col gap-3 p-2">
<Input type="search" placeholder="搜索患者…" />
<Input type="tel" placeholder="联系电话" defaultValue="138****5678" />
</div>
);
import { Button, Popover, PopoverContent, PopoverTrigger } from '@pac/web';
export const FilterPopover = () => (
<div className="flex h-[240px] items-start justify-center p-2">
<Popover defaultOpen>
<PopoverTrigger asChild><Button variant="outline" size="sm">画像标签</Button></PopoverTrigger>
<PopoverContent className="w-56">
<p className="mb-2 text-xs font-medium text-slate-500">按画像圈人</p>
<div className="flex flex-wrap gap-1.5 text-xs">
<span className="rounded-full border border-teal-300 bg-teal-50 px-2 py-0.5 text-teal-700">重要价值</span>
<span className="rounded-full border border-slate-200 px-2 py-0.5 text-slate-600">种植意向</span>
<span className="rounded-full border border-slate-200 px-2 py-0.5 text-slate-600">近一年到诊</span>
</div>
</PopoverContent>
</Popover>
</div>
);
import { PriorityBar } from '@pac/web';
export const Levels = () => (
<div className="flex flex-col gap-3 p-3">
<PriorityBar score={15} label="低" />
<PriorityBar score={45} label="中" />
<PriorityBar score={68} label="高" />
<PriorityBar score={92} label="紧急" />
</div>
);
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@pac/web';
export const ClinicSelect = () => (
<div className="w-[260px] p-2">
<Select defaultValue="gaode">
<SelectTrigger><SelectValue placeholder="选择诊所" /></SelectTrigger>
<SelectContent>
<SelectItem value="gaode">杭州高德诊所</SelectItem>
<SelectItem value="dasha">杭州大厦诊所</SelectItem>
<SelectItem value="chaoyang">北京朝阳公园诊所</SelectItem>
</SelectContent>
</Select>
</div>
);
export const Placeholder = () => (
<div className="w-[260px] p-2">
<Select>
<SelectTrigger><SelectValue placeholder="全部诊所" /></SelectTrigger>
<SelectContent>
<SelectItem value="all">全部诊所</SelectItem>
</SelectContent>
</Select>
</div>
);
import { Chip, SidebarCard } from '@pac/web';
export const InfoCard = () => (
<div className="w-[300px] p-2">
<SidebarCard
title="画像标签"
meta="11 项"
action={<button className="text-[10.5px] text-teal-700 hover:underline">详情 →</button>}
>
<p className="text-[12.5px] leading-relaxed text-slate-700">
重要价值新客且为银行私行权益客户,有种植、修复、拔牙及补牙四项应治未治项目待跟进。
</p>
<div className="mt-2 flex flex-wrap gap-1.5">
<Chip tone="teal" size="xs">重要价值</Chip>
<Chip tone="rose" size="xs">应治未治</Chip>
</div>
</SidebarCard>
</div>
);
export const Collapsed = () => (
<div className="w-[300px] p-2">
<SidebarCard title="治疗历史" meta="6 项" defaultOpen={false}>
<p className="text-sm">折叠时不可见的内容。</p>
</SidebarCard>
</div>
);
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@pac/web';
export const RecallTabs = () => (
<Tabs defaultValue="pool" className="w-[360px] p-2">
<TabsList>
<TabsTrigger value="pool">召回池</TabsTrigger>
<TabsTrigger value="mine">我的</TabsTrigger>
<TabsTrigger value="done">已完成</TabsTrigger>
</TabsList>
<TabsContent value="pool" className="text-sm text-slate-600 p-2">
997 位待认领患者,按优先级排序。
</TabsContent>
<TabsContent value="mine" className="text-sm text-slate-600 p-2">我认领的 2 位患者。</TabsContent>
<TabsContent value="done" className="text-sm text-slate-600 p-2">本周已完成 42 通回访。</TabsContent>
</Tabs>
);
import { Toaster, toast } from '@pac/web';
import { useEffect } from 'react';
export const Toasts = () => {
useEffect(() => {
toast.success('已认领 黄莉');
toast.error('提交失败,请重试');
toast('已同步该患者最新资料');
}, []);
return (
<div className="h-[280px]">
<Toaster position="top-center" />
</div>
);
};
/* design-sync 专用 Tailwind 编译入口 — 产出完整静态样式表给设计系统 bundle。
* @source 显式罗列扫描面:pac-web 源码(组件自身的类)+ 预览源码(卡片布局胶水类)。 */
@import "../apps/pac-web/src/app/globals.css";
@source "../apps/pac-web/src";
@source "./previews";
......@@ -38,3 +38,11 @@ docs/.obsidian/
# Next.js 自动生成(dev/build 间路径翻转,build 每次重生成)
apps/pac-web/next-env.d.ts
# design-sync(暂存脚本/构建产物/机器状态——.design-sync 下未忽略的才入库)
.ds-sync/
ds-bundle/
.design-sync/.cache/
.design-sync/learnings/
.design-sync/node_modules
apps/pac-web/.ds-css/
......@@ -24,6 +24,9 @@ export const tone = (t?: string) => TONE[t ?? 'slate'] ?? TONE.slate!;
// ──────────────────────────────────────────
// Chip
// ──────────────────────────────────────────
/**
* 语义色小标签 — tone: teal/slate/amber/rose/indigo/emerald/sky…,icon 可选,size: xs/sm。工作台高频原语。
*/
export function Chip({
children,
tone: t = 'slate',
......@@ -59,6 +62,9 @@ export function Chip({
// ──────────────────────────────────────────
// PriorityBar — 5 段渐变,不显数字
// ──────────────────────────────────────────
/**
* 优先级五段条 — score 0-100 映射 5 格渐变(绿→琥珀→玫红),可带 label。
*/
export function PriorityBar({
score,
max = 100,
......@@ -96,6 +102,9 @@ export function PriorityBar({
// ──────────────────────────────────────────
// AIStamp
// ──────────────────────────────────────────
/**
* AI 生成时间戳 — 展示「AI 生成 · N 分钟前」,source 区分模板兜底。
*/
export function AIStamp({
label = 'AI 生成',
relative,
......@@ -120,6 +129,9 @@ export function AIStamp({
// ──────────────────────────────────────────
// Sidebar Card wrapper (可折叠)
// ──────────────────────────────────────────
/**
* 侧栏可折叠卡片 — title + meta + action 插槽,工作台左栏信息卡的统一容器。
*/
export function SidebarCard({
title,
meta,
......@@ -181,6 +193,9 @@ function inlineMD(s: string): string {
.replace(/\*\*([^*]+)\*\*/g, '<strong class="font-semibold text-slate-900">$1</strong>');
}
/**
* 轻量 markdown 渲染 — 支持粗体/列表/段落,话术与摘要文本展示用。
*/
export function MD({ text, className }: { text: string; className?: string }) {
const lines = text.split('\n');
const out: ReactNode[] = [];
......
......@@ -7,6 +7,10 @@ import { buttonVariants } from './button';
// 标准 shadcn-new-york alert-dialog 实现(根据 components.json 风格本地实现,无 CLI 注入)
/**
* 确认对话框(危险操作二次确认)— AlertDialog + AlertDialogTrigger/AlertDialogContent/AlertDialogAction/AlertDialogCancel 组合。
* @category overlay
*/
const AlertDialog = AlertDialogPrimitive.Root;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal;
......
......@@ -21,6 +21,10 @@ export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
/**
* 状态徽标 — variant: default/secondary/destructive/outline。
* @category display
*/
export function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
}
......@@ -36,6 +36,10 @@ export interface ButtonProps
asChild?: boolean;
}
/**
* 主操作按钮 — variant: default/secondary/outline/ghost/destructive/link,size: sm/default/lg/icon;teal 主色。
* @category actions
*/
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
......
......@@ -21,6 +21,10 @@ const zhFormatters = {
/// shadcn 官方 Calendar(react-day-picker v10)—— 全 Tailwind classNames,不引 rdp 默认样式表。
/// 主题走项目 CSS 变量(--primary=teal-600 / --accent / --ring),故选中/今天/焦点自动是 teal。
/// 支持 captionLayout="dropdown"(月/年下拉)与默认 label(左右翻页箭头)两种 caption。
/**
* 日历(react-day-picker 封装)— 单选/范围由 mode 决定。
* @category forms
*/
export function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
<DayPicker
......
......@@ -6,6 +6,10 @@ import { cn } from '@/lib/utils';
// 阴影只给"浮起"层(popover / dialog),不给静态卡片。
// padding 缩到 p-5(design-system desktop 默认),CardContent 顶 padding 缩到 pt-0 之前的 pb 维持。
/**
* 内容卡片容器 — 与 CardHeader/CardTitle/CardDescription/CardContent/CardFooter 组合使用。
* @category display
*/
export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
......
......@@ -8,6 +8,10 @@ import { Popover, PopoverTrigger, PopoverContent } from './popover';
/// 纯日期选择器(shadcn「Date Picker」模式:outline Button + ChevronDown 触发 → Popover + Calendar)。
/// 时间另用 <input type="time">,不在此组件内(对齐 shadcn Date Picker with Time 示例)。
/**
* 日期选择器 — Popover + Calendar 组合,受控 value/onChange。
* @category forms
*/
export function DatePicker({
value,
onChange,
......
......@@ -7,6 +7,10 @@ import { cn } from '@/lib/utils';
// 标准 shadcn-new-york dialog 实现
/**
* 模态对话框 — Dialog + DialogTrigger/DialogContent/DialogHeader/DialogTitle/DialogDescription/DialogFooter 组合。
* @category overlay
*/
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
......
......@@ -5,6 +5,10 @@ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import { cn } from '@/lib/utils';
/**
* 下拉菜单 — DropdownMenu + DropdownMenuTrigger/DropdownMenuContent/DropdownMenuItem 组合。
* @category actions
*/
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
......
......@@ -5,6 +5,10 @@ import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
import { cn } from '@/lib/utils';
/**
* 悬停信息卡 — HoverCard + HoverCardTrigger/HoverCardContent 组合。
* @category overlay
*/
const HoverCard = HoverCardPrimitive.Root;
const HoverCardTrigger = HoverCardPrimitive.Trigger;
......
......@@ -22,6 +22,10 @@ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement>
compact?: boolean;
}
/**
* 文本输入框 — 支持所有原生 input 属性。
* @category forms
*/
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, compact, ...props }, ref) => {
return (
......
......@@ -4,6 +4,10 @@ import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '@/lib/utils';
/**
* 浮层 — Popover + PopoverTrigger/PopoverContent 组合。
* @category overlay
*/
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
......
......@@ -5,6 +5,10 @@ import * as SelectPrimitive from '@radix-ui/react-select';
import { Check, ChevronDown } from 'lucide-react';
import { cn } from '@/lib/utils';
/**
* 下拉选择 — Select + SelectTrigger/SelectValue/SelectContent/SelectItem 组合。
* @category forms
*/
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
......
......@@ -5,6 +5,10 @@ import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from '@/lib/utils';
/**
* 选项卡 — Tabs + TabsList/TabsTrigger/TabsContent 组合。
* @category display
*/
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
......
......@@ -2,6 +2,10 @@
import { Toaster as SonnerToaster } from 'sonner';
/**
* 全局 toast 容器(sonner)— 页面挂一次,用 toast()/toast.success() 触发。
* @category overlay
*/
export function Toaster() {
return (
<SonnerToaster
......
/**
* PAC Design System 入口 — design-sync 打包专用(window.PAC.*)。
* 显式收录:ui/ 基础件全集 + 工作台原语(plan-detail/shared)。
* 新组件要进设计系统 → 在此 re-export,并在 .design-sync/config.json 的
* componentSrcMap 里登记(卡片清单以 componentSrcMap 为准)。
*/
export * from '../components/ui/alert-dialog';
export * from '../components/ui/badge';
export * from '../components/ui/button';
export * from '../components/ui/calendar';
export * from '../components/ui/card';
export * from '../components/ui/date-picker';
export * from '../components/ui/dialog';
export * from '../components/ui/dropdown-menu';
export * from '../components/ui/hover-card';
export * from '../components/ui/input';
export * from '../components/ui/popover';
export * from '../components/ui/select';
export * from '../components/ui/tabs';
export * from '../components/ui/toaster';
// 工作台原语(shared.tsx 里还有内部工具,不整卷导出)
export { Chip, PriorityBar, AIStamp, SidebarCard, MD } from '../components/plan-detail/shared';
// toast 触发器(配合 Toaster 容器)
export { toast } from 'sonner';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment