@qijenchen/design-system 0.1.0-beta.25 → 0.1.0-beta.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ds-canonical/hooks/check_opacity_token_usage.sh +1 -1
- package/ds-canonical/hooks/lib/_token_hygiene.sh +2 -2
- package/ds-canonical/hooks/retired/check_container_breathing.sh +1 -1
- package/ds-canonical/references/tailwind-gotchas.md +1 -1
- package/ds-canonical/rules/ui-development.md +1 -1
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
- package/ds-canonical/templates/_README.md +23 -0
- package/ds-canonical/templates/dashboard-app.tsx +145 -0
- package/ds-story-manifest.json +1690 -0
- package/package.json +5 -3
- package/src/components/Avatar/avatar.spec.md +0 -10
- package/src/components/Breadcrumb/breadcrumb.spec.md +0 -6
- package/src/components/Button/button.spec.md +0 -15
- package/src/components/Chip/chip.spec.md +0 -7
- package/src/components/Dialog/dialog.spec.md +0 -9
- package/src/components/DropdownMenu/dropdown-menu.spec.md +0 -9
- package/src/components/Empty/empty.spec.md +0 -10
- package/src/components/Field/form-validation.spec.md +0 -10
- package/src/components/FileViewer/file-viewer.spec.md +1 -1
- package/src/components/HoverCard/hover-card.spec.md +1 -9
- package/src/components/Menu/menu-item.spec.md +0 -7
- package/src/components/Popover/popover.spec.md +0 -7
- package/src/components/Sheet/sheet.spec.md +0 -7
- package/src/components/Sidebar/sidebar.spec.md +0 -7
- package/src/components/Tabs/tabs.spec.md +1 -1
- package/src/components/Tooltip/tooltip.spec.md +1 -8
- package/src/patterns/element-anatomy/inline-action.spec.md +0 -11
- package/src/patterns/element-anatomy/item-anatomy.spec.md +1 -1
- package/src/patterns/overlay-surface/overlay-surface.spec.md +1 -10
- package/src/tokens/color/color.spec.md +0 -32
- package/src/tokens/elevation/elevation.spec.md +0 -9
- package/src/tokens/layoutSpace/layoutSpace.spec.md +0 -11
- package/src/tokens/uiSize/uiSize.spec.md +0 -10
|
@@ -105,7 +105,7 @@ if [ -n "$HITS_RADIUS" ]; then
|
|
|
105
105
|
VIOLATIONS="${VIOLATIONS}\n [radius out-of-range] $(echo "$HITS_RADIUS" | tr '\n' ' ')"
|
|
106
106
|
fi
|
|
107
107
|
|
|
108
|
-
# 7) Shadow Tailwind size(shadow-sm/md/lg/xl/2xl/inner — DS 用 shadow-[var(--elevation
|
|
108
|
+
# 7) Shadow Tailwind size(shadow-sm/md/lg/xl/2xl/inner — DS 用 shadow-[var(--elevation-N)] N∈{100,200,300})
|
|
109
109
|
HITS_SHADOW=$(echo "$NEW_CONTENT" | grep -oE "\bshadow-(sm|md|lg|xl|2xl|inner)\b" | sort -u || true)
|
|
110
110
|
if [ -n "$HITS_SHADOW" ]; then
|
|
111
111
|
VIOLATIONS="${VIOLATIONS}\n [elevation size] $(echo "$HITS_SHADOW" | tr '\n' ' ')"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# (these are shadcn safety-net aliases; our DS code MUST use direct tokens)
|
|
7
7
|
# 2. Tailwind v4 `[--foo]` shorthand — must be `var(--foo)` wrapped; historical bug:
|
|
8
8
|
# Sidebar's `w-[--sidebar-width]` broke 8 places (silent fail, no error)
|
|
9
|
-
# 3. Hardcoded Tailwind shadow — `shadow-sm/md/lg/xl/2xl` is forbidden; must use `shadow-[var(--elevation
|
|
9
|
+
# 3. Hardcoded Tailwind shadow — `shadow-sm/md/lg/xl/2xl` is forbidden; must use `shadow-[var(--elevation-N)] N∈{100,200,300}`
|
|
10
10
|
# 4. Native overflow-{auto,scroll} without ScrollArea — cross-OS scrollbar drift
|
|
11
11
|
# (macOS overlay 不吃寬 / Windows always-visible 吃 17px = 跨 OS 跑版)
|
|
12
12
|
# 應改用 ScrollArea(Components/ScrollArea/)— overlay scrollbar 跨 OS 一致
|
|
@@ -52,7 +52,7 @@ fi
|
|
|
52
52
|
|
|
53
53
|
# ── Check 3: Hardcoded Tailwind shadow ────────────────────────────────────────
|
|
54
54
|
# shadow-sm/md/lg/xl/2xl 是 Tailwind 預設,繞過 elevation token 系統——禁止.
|
|
55
|
-
# 允許:shadow-none / shadow-[var(--elevation
|
|
55
|
+
# 允許:shadow-none / shadow-[var(--elevation-N)] N∈{100,200,300} / shadow-[calc(...)]
|
|
56
56
|
SHADOW_PATTERN='\bshadow-(sm|md|lg|xl|2xl|inner)\b'
|
|
57
57
|
SHADOW_HITS=$(grep -nE "$SHADOW_PATTERN" "$FILE_PATH" 2>/dev/null | head -5)
|
|
58
58
|
if [ -n "$SHADOW_HITS" ]; then
|
|
@@ -121,7 +121,7 @@ while IFS= read -r LINE || [ -n "$LINE" ]; do
|
|
|
121
121
|
視覺邊界:${REASONS# }
|
|
122
122
|
> $(echo "$LINE" | sed 's/^[[:space:]]*//' | cut -c1-120)
|
|
123
123
|
判斷: 視覺容器(bg/border/shadow)必有 inner padding,不讓內容物貼容器邊。
|
|
124
|
-
建議:chrome 層用 p-[var(--layout-space-loose
|
|
124
|
+
建議:chrome 層用 p-[var(--layout-space-N) N∈{loose,tight}] / px-3 等。
|
|
125
125
|
若是 (a) chrome primitive override 刻意 p-0,(b) 父容器另外提供 padding,
|
|
126
126
|
(c) 子元件自帶足夠 padding → 加 // @breathing-exempt-next 到此行上一行。
|
|
127
127
|
SSOT: patterns/element-anatomy/element-anatomy.spec.md「視覺容器 breathing invariant」"
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
| 類別 | 為什麼禁止 | 改用 |
|
|
60
60
|
|------|----------|------|
|
|
61
|
-
| `shadow-sm/md/lg/xl/2xl` | 繞過 elevation token 系統,沒跟 dark mode 調整聯動 | `shadow-[var(--elevation-100
|
|
61
|
+
| `shadow-sm/md/lg/xl/2xl` | 繞過 elevation token 系統,沒跟 dark mode 調整聯動 | 用 `shadow-[var(--elevation-N)]` 其中 N ∈ `{100, 200, 300}`(per elevation.spec.md tier)|
|
|
62
62
|
| 硬寫色值 `#xxx`, `rgb(...)`, `bg-red-500` | 繞過 semantic token,dark mode / brand swap 會斷 | 對應 semantic token |
|
|
63
63
|
| Tailwind 預設 typography `text-xs/sm/base/lg` | 我們有自己的 `text-caption/body/body-lg/h1/h2` 系統 | 用我們的 typography token |
|
|
64
64
|
| 硬寫 px 值 `w-[48px]` 當有 token | 失去 token 關聯,改值時零散處要一起改 | 對應 token 或 calc() |
|
|
@@ -52,7 +52,7 @@ paths:
|
|
|
52
52
|
|
|
53
53
|
1. **CSS variable 必 `var()` 包覆** — `w-[var(--foo)]` 而非 `w-[--foo]`(v4 silent 失效)
|
|
54
54
|
2. **自訂 utility 必在 `lib/utils.ts` 註冊 group** — 否則 tailwind-merge 誤判 strip
|
|
55
|
-
3. **禁 `shadow-sm/md/lg` / `text-xs/sm/base` / 硬寫色值** — 用 `shadow-[var(--elevation
|
|
55
|
+
3. **禁 `shadow-sm/md/lg` / `text-xs/sm/base` / 硬寫色值** — 用 `shadow-[var(--elevation-N)]` (N ∈ {100,200,300}) / `text-body`
|
|
56
56
|
4. **禁 shadcn compat alias**(`bg-popover` / `text-muted-foreground` / `bg-accent` 等)— 用 direct token
|
|
57
57
|
5. **禁 primitive 色名作 utility**(`bg-neutral-3` / `text-blue-6`)— 用 semantic utility 或 `bg-[var(--color-blue-6)]`
|
|
58
58
|
|
|
@@ -614,7 +614,7 @@ Forbidden in our code (these SHOULD be migrated to direct tokens):
|
|
|
614
614
|
- `bg-card` / `text-card-foreground` → `bg-surface` / `text-foreground`
|
|
615
615
|
- `text-primary-foreground` → `text-white`
|
|
616
616
|
- `border-input` → `border-border`
|
|
617
|
-
- `shadow-md / shadow-sm / shadow-lg / shadow-xl / shadow-2xl` → `shadow-[var(--elevation
|
|
617
|
+
- `shadow-md / shadow-sm / shadow-lg / shadow-xl / shadow-2xl` → `shadow-[var(--elevation-N)]` (N ∈ {100,200,300})
|
|
618
618
|
|
|
619
619
|
OK (these are OUR approved tokens, not shadcn aliases):
|
|
620
620
|
- `bg-muted` (semantic.css keeps --muted as real token)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// AUTO-GENERATED canonical reference from product-workspace/apps/template/src/App.tsx
|
|
2
|
+
# DS canonical templates
|
|
3
|
+
|
|
4
|
+
This directory ships **canonical app templates** for consumer apps to mirror.
|
|
5
|
+
Per user 2026-05-27「ds repo push main → PW template 永遠 latest」directive.
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
- `dashboard-app.tsx` — AppShell + Sidebar + DashboardPage canonical full composition
|
|
10
|
+
(aligned with `sidebar.stories.tsx#IconCollapse` + `header-canonical.spec.md:57-72`)
|
|
11
|
+
|
|
12
|
+
## Sync mechanism
|
|
13
|
+
|
|
14
|
+
DS bump → `ssot-sync-dispatch.yml` dispatch → PW `sync-design-system.yml` 收 event:
|
|
15
|
+
1. `npm update` design-system + storybook-config(既有)
|
|
16
|
+
2. **NEW**: pull `node_modules/@qijenchen/design-system/ds-canonical/templates/dashboard-app.tsx`
|
|
17
|
+
diff against PW `apps/template/src/App.tsx`
|
|
18
|
+
3. If drift → auto-create PR(NOT direct push to main — user review)
|
|
19
|
+
|
|
20
|
+
Fork users 從 PW main fork → 取到當下最新 canonical baseline。Fork 後 diverge customization 是 fork user 自己責任。
|
|
21
|
+
|
|
22
|
+
## SSOT chain
|
|
23
|
+
DS canonical(此 file)→ npm pkg ship → PW sync auto-PR → fork user fork 取 latest.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// 完整 AppShell 範例 — 對齊 DS canonical `sidebar.stories.tsx#IconCollapse` baseline
|
|
2
|
+
// (per `.claude/rules/story-rules.md`「Production-grade composition fidelity」+ M23(d) nearest-same-purpose canonical wins)
|
|
3
|
+
//
|
|
4
|
+
// @story-baseline: @qijenchen/design-system/components/Sidebar/sidebar.stories.tsx#IconCollapse
|
|
5
|
+
//
|
|
6
|
+
// SSOT 鐵律:
|
|
7
|
+
// - Consumer 只 import `@qijenchen/design-system` public exports
|
|
8
|
+
// - 禁修改 DS source(走 fork DS repo)
|
|
9
|
+
// - 視覺 token 透過 DS 提供的 `@qijenchen/design-system/styles/tokens` 載入
|
|
10
|
+
//
|
|
11
|
+
// Fork user 替換步驟:
|
|
12
|
+
// 1. 替換 NAV array(新 product 的真實導覽項)
|
|
13
|
+
// 2. 替換 WorkspaceBrand 內 workspace 名 / Avatar 顏色
|
|
14
|
+
// 3. 替換 DashboardPage 為真實業務 widgets(DataTable / Chart / Card 等 DS 元件)
|
|
15
|
+
// 4. 替換 PageHeader rightSlot 的 primary action(若有)
|
|
16
|
+
|
|
17
|
+
import { useState } from 'react'
|
|
18
|
+
import {
|
|
19
|
+
AppShell,
|
|
20
|
+
SidebarProvider,
|
|
21
|
+
Sidebar,
|
|
22
|
+
SidebarContent,
|
|
23
|
+
SidebarFooter,
|
|
24
|
+
SidebarGroup,
|
|
25
|
+
SidebarGroupContent,
|
|
26
|
+
SidebarHeader,
|
|
27
|
+
SidebarMenu,
|
|
28
|
+
SidebarMenuItem,
|
|
29
|
+
SidebarMenuButton,
|
|
30
|
+
SidebarTrigger,
|
|
31
|
+
TooltipProvider,
|
|
32
|
+
Avatar,
|
|
33
|
+
ItemAvatar,
|
|
34
|
+
Button,
|
|
35
|
+
} from '@qijenchen/design-system'
|
|
36
|
+
import { LayoutDashboard, Users, Settings, FileText, BarChart3 } from 'lucide-react'
|
|
37
|
+
|
|
38
|
+
const NAV = [
|
|
39
|
+
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
|
|
40
|
+
{ id: 'customers', label: 'Customers', icon: Users },
|
|
41
|
+
{ id: 'orders', label: 'Orders', icon: FileText },
|
|
42
|
+
{ id: 'reports', label: 'Reports', icon: BarChart3 },
|
|
43
|
+
{ id: 'settings', label: 'Settings', icon: Settings },
|
|
44
|
+
] as const
|
|
45
|
+
|
|
46
|
+
// ── Sidebar(對齊 DS IconCollapse baseline:collapsible="icon" + WorkspaceBrand + footer)──
|
|
47
|
+
function AppSidebar() {
|
|
48
|
+
return (
|
|
49
|
+
<Sidebar collapsible="icon">
|
|
50
|
+
<SidebarHeader>
|
|
51
|
+
{/* Chrome header avatar canonical(per header-canonical.spec.md:57-72):chrome header 不是 row context → raw <Avatar size={24}>,禁用 <ItemAvatar>(會誤啟動 row anatomy lookup)*/}
|
|
52
|
+
<div className="flex items-center gap-2 min-w-0 group-data-[collapsible=icon]:justify-center">
|
|
53
|
+
<Avatar alt="Acme Product" size={24} shape="square" color="blue" solid />
|
|
54
|
+
<span className="text-body-lg font-medium truncate group-data-[collapsible=icon]:hidden">Acme Product</span>
|
|
55
|
+
</div>
|
|
56
|
+
</SidebarHeader>
|
|
57
|
+
<SidebarContent>
|
|
58
|
+
<SidebarGroup>
|
|
59
|
+
<SidebarGroupContent>
|
|
60
|
+
<SidebarMenu>
|
|
61
|
+
{NAV.map(({ id, label, icon }) => (
|
|
62
|
+
<SidebarMenuItem key={id}>
|
|
63
|
+
<SidebarMenuButton id={id} startIcon={icon} tooltip={label}>
|
|
64
|
+
{label}
|
|
65
|
+
</SidebarMenuButton>
|
|
66
|
+
</SidebarMenuItem>
|
|
67
|
+
))}
|
|
68
|
+
</SidebarMenu>
|
|
69
|
+
</SidebarGroupContent>
|
|
70
|
+
</SidebarGroup>
|
|
71
|
+
</SidebarContent>
|
|
72
|
+
<SidebarFooter>
|
|
73
|
+
{/* 對齊 DS canonical UserFooter(sidebar.stories.tsx):asChild + <div role="group"> + data-sidebar="menu-label" 必有,否則 SidebarMenuButton 把 children 全 wrap 進 ItemLabel 視覺垂直 stack */}
|
|
74
|
+
<SidebarMenu>
|
|
75
|
+
<SidebarMenuItem>
|
|
76
|
+
<SidebarMenuButton asChild>
|
|
77
|
+
<div role="group" aria-label="當前使用者">
|
|
78
|
+
<ItemAvatar alt="Current user" color="blue" />
|
|
79
|
+
<span data-sidebar="menu-label" className="min-w-0 flex-1 truncate">當前使用者</span>
|
|
80
|
+
</div>
|
|
81
|
+
</SidebarMenuButton>
|
|
82
|
+
</SidebarMenuItem>
|
|
83
|
+
</SidebarMenu>
|
|
84
|
+
</SidebarFooter>
|
|
85
|
+
</Sidebar>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── PageHeader(對齊 DS canonical chrome-header pattern:fixed --chrome-header-height + SidebarTrigger + title)──
|
|
90
|
+
// SidebarTrigger 必有(primary-sidebar mode 的 menu toggle 入口,⌘B keyboard shortcut)
|
|
91
|
+
function PageHeader({ title, rightSlot }: { title: string; rightSlot?: React.ReactNode }) {
|
|
92
|
+
return (
|
|
93
|
+
<header className="flex items-center gap-2 h-[var(--chrome-header-height)] px-[var(--layout-space-loose)] bg-surface border-b border-divider">
|
|
94
|
+
<SidebarTrigger />
|
|
95
|
+
<h1 className="text-body-lg font-medium flex-1 truncate">{title}</h1>
|
|
96
|
+
{rightSlot}
|
|
97
|
+
</header>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function DashboardPage() {
|
|
102
|
+
return (
|
|
103
|
+
<div className="px-[var(--layout-space-loose)] py-[var(--layout-space-tight)] space-y-6">
|
|
104
|
+
<section>
|
|
105
|
+
<h2 className="text-h5 mb-2">Today</h2>
|
|
106
|
+
<p className="text-body text-fg-secondary">
|
|
107
|
+
替換為真實業務 — 訂單 / 收入 / 待處理任務等 dashboard widgets。Consume DS components
|
|
108
|
+
(DataTable / Chart / Card / Stat 等),never modify DS source。
|
|
109
|
+
</p>
|
|
110
|
+
</section>
|
|
111
|
+
<section className="grid grid-cols-3 gap-4">
|
|
112
|
+
{['Revenue', 'Active customers', 'Pending orders'].map((label) => (
|
|
113
|
+
<div key={label} className="rounded-lg border border-divider bg-surface p-4">
|
|
114
|
+
<div className="text-caption text-fg-secondary">{label}</div>
|
|
115
|
+
<div className="text-h3 mt-1">—</div>
|
|
116
|
+
</div>
|
|
117
|
+
))}
|
|
118
|
+
</section>
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default function App() {
|
|
124
|
+
const [activeId, setActiveId] = useState<string>('dashboard')
|
|
125
|
+
const current = NAV.find((n) => n.id === activeId) ?? NAV[0]
|
|
126
|
+
// TooltipProvider self-wrap(Storybook story render 跳過 main.tsx → App 必自帶 TooltipProvider context)
|
|
127
|
+
return (
|
|
128
|
+
<TooltipProvider delayDuration={500} skipDelayDuration={300}>
|
|
129
|
+
<SidebarProvider activeId={activeId} onActiveChange={setActiveId}>
|
|
130
|
+
<AppShell
|
|
131
|
+
layout="primary-sidebar"
|
|
132
|
+
sidebar={<AppSidebar />}
|
|
133
|
+
header={
|
|
134
|
+
<PageHeader
|
|
135
|
+
title={current.label}
|
|
136
|
+
rightSlot={<Button variant="primary" size="md">New customer</Button>}
|
|
137
|
+
/>
|
|
138
|
+
}
|
|
139
|
+
>
|
|
140
|
+
<DashboardPage />
|
|
141
|
+
</AppShell>
|
|
142
|
+
</SidebarProvider>
|
|
143
|
+
</TooltipProvider>
|
|
144
|
+
)
|
|
145
|
+
}
|