@qijenchen/design-system 0.1.0-beta.71 → 0.1.0-beta.72

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.
@@ -25,10 +25,12 @@ export declare const MAIN_NAV: readonly [{
25
25
  }];
26
26
  export declare const WorkspaceBrand: () => import("react/jsx-runtime").JSX.Element;
27
27
  export declare const UserFooter: () => import("react/jsx-runtime").JSX.Element;
28
- export declare function AcmeSidebar({ viewportInsetTop, includeWorkspaceBrand, }?: {
28
+ export declare function AcmeSidebar({ viewportInsetTop, includeWorkspaceBrand, includeUserFooter, }?: {
29
29
  viewportInsetTop?: string;
30
30
  includeWorkspaceBrand?: boolean;
31
+ includeUserFooter?: boolean;
31
32
  }): import("react/jsx-runtime").JSX.Element;
33
+ export declare function AccountMenu(): import("react/jsx-runtime").JSX.Element;
32
34
  export declare function GlobalHeader({ rightSlot }?: {
33
35
  rightSlot?: React.ReactNode;
34
36
  }): import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"_demo-helpers.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/_demo-helpers.tsx"],"names":[],"mappings":"AAuCA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;EAOX,CAAA;AAqBV,eAAO,MAAM,cAAc,+CAM1B,CAAA;AAID,eAAO,MAAM,UAAU,+CAuBtB,CAAA;AAMD,wBAAgB,WAAW,CAAC,EAC1B,gBAAgB,EAChB,qBAA4B,GAC7B,GAAE;IACD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAC3B,2CAgCL;AAQD,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,GAAE;IAAE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAAO,2CAY/E;AASD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,qBAA4B,GAC7B,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC,2CAOA"}
1
+ {"version":3,"file":"_demo-helpers.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/_demo-helpers.tsx"],"names":[],"mappings":"AAiDA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;EAOX,CAAA;AAqBV,eAAO,MAAM,cAAc,+CAM1B,CAAA;AAID,eAAO,MAAM,UAAU,+CAuBtB,CAAA;AAQD,wBAAgB,WAAW,CAAC,EAC1B,gBAAgB,EAChB,qBAA4B,EAC5B,iBAAwB,GACzB,GAAE;IACD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAA;CACvB,2CAkCL;AAWD,wBAAgB,WAAW,4CAyB1B;AAQD,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,GAAE;IAAE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAAO,2CAY/E;AASD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,qBAA4B,GAC7B,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC,2CAOA"}
package/llms-full.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # @qijenchen/design-system — 完整設計參考(llms-full)
2
2
 
3
- > 全 component / pattern 的 variants / sizes / 禁止事項。build-time 從 spec.md frontmatter 生成,禁手改。v0.1.0-beta.71
3
+ > 全 component / pattern 的 variants / sizes / 禁止事項。build-time 從 spec.md frontmatter 生成,禁手改。v0.1.0-beta.72
4
4
 
5
5
  # Components
6
6
 
package/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # @qijenchen/design-system
2
2
 
3
3
  > World-class React design system(Radix/shadcn + Tailwind v4 + 自訂 design token)。
4
- > 54 components + 4 public patterns + design tokens。v0.1.0-beta.71
4
+ > 54 components + 4 public patterns + design tokens。v0.1.0-beta.72
5
5
 
6
6
  本檔由 source(spec.md frontmatter + Storybook index)build-time 自動生成,**禁手改**(CI --check drift gate 守)。
7
7
  每元件 / pattern 的完整 variants / sizes / 禁止事項 全文見 [llms-full.txt](./llms-full.txt)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qijenchen/design-system",
3
- "version": "0.1.0-beta.71",
3
+ "version": "0.1.0-beta.72",
4
4
  "private": false,
5
5
  "description": "World-class design system — components, patterns, tokens, hooks (single source of truth for team distribution).",
6
6
  "type": "module",
@@ -12,6 +12,8 @@ import {
12
12
  Users,
13
13
  BarChart3,
14
14
  LayoutDashboard,
15
+ User,
16
+ LogOut,
15
17
  } from 'lucide-react'
16
18
  import {
17
19
  Sidebar,
@@ -34,6 +36,14 @@ import {
34
36
  ProfileCard,
35
37
  ProfileCardDefaultActions,
36
38
  } from '@/design-system/components/ProfileCard/profile-card'
39
+ import {
40
+ DropdownMenu,
41
+ DropdownMenuTrigger,
42
+ DropdownMenuContent,
43
+ DropdownMenuItem,
44
+ DropdownMenuLabel,
45
+ DropdownMenuGroup,
46
+ } from '@/design-system/components/DropdownMenu/dropdown-menu'
37
47
 
38
48
  // ── MAIN_NAV(對齊 sidebar.stories.tsx baseline)────────────────────────
39
49
 
@@ -103,13 +113,17 @@ export const UserFooter = () => (
103
113
  // ── AcmeSidebar(完整 production-grade,對齊 sidebar IconCollapse story)──
104
114
  // `includeWorkspaceBrand` default true(primary-sidebar 派 Linear/Notion 慣例:workspace brand 在 sidebar 頂)。
105
115
  // `false` 用於 primary-header mode:workspace brand 移到 globalHeader 左側(GitHub logo / Slack workspace bar 慣例)。
116
+ // `includeUserFooter` default true(primary-sidebar:帳號在 sidebar 底)。`false` 用於 primary-header:
117
+ // 帳號入口移到 globalHeader 右側 AccountMenu(GitHub/Gmail/Slack 慣例;見 app-shell.spec.md 帳號入口放置 SSOT)。
106
118
 
107
119
  export function AcmeSidebar({
108
120
  viewportInsetTop,
109
121
  includeWorkspaceBrand = true,
122
+ includeUserFooter = true,
110
123
  }: {
111
124
  viewportInsetTop?: string
112
125
  includeWorkspaceBrand?: boolean
126
+ includeUserFooter?: boolean
113
127
  } = {}) {
114
128
  return (
115
129
  <Sidebar collapsible="icon" viewportInsetTop={viewportInsetTop}>
@@ -137,14 +151,52 @@ export function AcmeSidebar({
137
151
  </SidebarGroupContent>
138
152
  </SidebarGroup>
139
153
  </SidebarContent>
140
- <SidebarFooter>
141
- <UserFooter />
142
- </SidebarFooter>
154
+ {includeUserFooter && (
155
+ <SidebarFooter>
156
+ <UserFooter />
157
+ </SidebarFooter>
158
+ )}
143
159
  </Sidebar>
144
160
  )
145
161
  }
146
162
 
147
- // ── GlobalHeader(primary-header mode 用,跨頁 chrome:WorkspaceBrand 左 + 跨頁 actions 右)──
163
+ // ── AccountMenu(primary-header mode 用,主標頭右側「個人設定入口」)──────────────
164
+ // 2026-06-17 加 per user directive「primary-header 不該把個人設定放 sidebar footer,該放主標頭右側 avatar」。
165
+ // 對齊 GitHub / Gmail / Slack / Atlassian:自己的帳號入口在 global top bar 右上 + 點開帳號選單
166
+ // (非 ProfileCard——ProfileCard 是看「別人」的人員卡,預設動作 Chat/通話用在自己身上不對)。
167
+ // 消費 SSOT:
168
+ // - <Avatar size={24}>(header-canonical.spec.md 4.5 chrome header avatar:brand + account 同 24px;
169
+ // sync with --chrome-header-avatar-size)。互動感由 focus ring + hover 提供,不放大到 field height。
170
+ // - <DropdownMenu>(個人資料 / 設定 / 登出;baseline = dropdown-menu.stories.tsx Groups)
171
+ // - 放置 / 邊距對稱 canonical → app-shell.spec.md「帳號入口(Account entry)放置 SSOT」段
172
+ export function AccountMenu() {
173
+ return (
174
+ <DropdownMenu>
175
+ <DropdownMenuTrigger asChild>
176
+ <button
177
+ type="button"
178
+ aria-label="帳號與設定"
179
+ className="flex items-center justify-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
180
+ >
181
+ {/* 24 per header-canonical.spec.md 4.5 chrome header avatar canonical(brand + account 同尺寸); sync with --chrome-header-avatar-size */}
182
+ <Avatar size={24} alt="Alan Chen" color="blue" />
183
+ </button>
184
+ </DropdownMenuTrigger>
185
+ <DropdownMenuContent align="end">
186
+ <DropdownMenuGroup>
187
+ <DropdownMenuLabel>Alan Chen</DropdownMenuLabel>
188
+ <DropdownMenuItem startIcon={User}>個人資料</DropdownMenuItem>
189
+ <DropdownMenuItem startIcon={Settings}>設定</DropdownMenuItem>
190
+ </DropdownMenuGroup>
191
+ <DropdownMenuGroup>
192
+ <DropdownMenuItem startIcon={LogOut}>登出</DropdownMenuItem>
193
+ </DropdownMenuGroup>
194
+ </DropdownMenuContent>
195
+ </DropdownMenu>
196
+ )
197
+ }
198
+
199
+ // ── GlobalHeader(primary-header mode 用,跨頁 chrome:WorkspaceBrand 左 + 帳號入口右)──
148
200
  // 2026-05-21 加 per user clarification「primary-header = primary-sidebar + 一條 global header」。
149
201
  // 對齊 GitHub top nav(logo 左 / search 中 / account 右)+ Slack workspace bar 慣例。
150
202
  // 消費 ChromeHeader(per `header-canonical.spec.md` Element + Background ownership 段:
@@ -159,7 +211,7 @@ export function GlobalHeader({ rightSlot }: { rightSlot?: React.ReactNode } = {}
159
211
  <ChromeHeader className="bg-surface" leadingRail={<SidebarTrigger />}>
160
212
  <WorkspaceBrand />
161
213
  <div className="flex-1" />
162
- {rightSlot}
214
+ {rightSlot ?? <AccountMenu />}
163
215
  </ChromeHeader>
164
216
  )
165
217
  }
@@ -123,6 +123,17 @@ export const LayoutModeRule: Story = {
123
123
  <li>❌ 禁:`primary-header` mode 同時在 globalHeader + SidebarHeader 各放一份 = 視覺冗餘 + 跨產品識別混淆(WorkspaceBrand 視覺 SSOT,只能出現一次)</li>
124
124
  </ul>
125
125
  </section>
126
+
127
+ <section>
128
+ <h2 className="text-h4 mb-2">帳號入口(個人設定)跟著 mode 走(只能出現一次)</h2>
129
+ <ul className="text-body space-y-1">
130
+ <li>• <strong>primary-sidebar</strong>:帳號 / 個人設定放 Sidebar 底部(`&lt;SidebarFooter&gt;`)— Linear / Notion / Figma</li>
131
+ <li>• <strong>primary-header</strong>:帳號入口改放 globalHeader 右側 avatar(品牌左、帳號右,左右對稱),sidebar 不放 user footer — GitHub / Gmail / Slack 帳號一律在 global bar 右上</li>
132
+ <li>• 開「個人資料 / 設定 / 登出」帳號選單(`&lt;DropdownMenu&gt;`),<strong>不用 ProfileCard</strong>(ProfileCard 是看別人的人員卡,預設動作 Chat/通話用在自己身上不對)</li>
133
+ <li>• 帳號 avatar = 24px,跟左側品牌 avatar 同尺寸(header-canonical 4.5 chrome header avatar);右側邊距與品牌距分割線對稱</li>
134
+ <li>❌ 禁:`primary-header` mode 同時在 globalHeader + sidebar footer 各放一份 = 入口混淆(帳號入口視覺 SSOT,只能出現一次)</li>
135
+ </ul>
136
+ </section>
126
137
  </div>
127
138
  ),
128
139
  }
@@ -134,6 +134,28 @@ function CustomAside() {
134
134
  header={<PageHeader title="..." />}>...
135
135
  ```
136
136
 
137
+ ### 帳號入口(Account entry)放置 SSOT(2026-06-17 codify per user directive「primary-header 不該把個人設定放 sidebar footer,該放主標頭右側 avatar」)
138
+
139
+ 「帳號 / 個人設定入口」(目前使用者的 avatar,點開 = 帳號選單)放置同樣 **mode-dependent**,鏡像上方 WorkspaceBrand 規則:
140
+
141
+ | Mode | 帳號入口放置 | Sidebar footer | 對齊 World-class |
142
+ |---|---|---|---|
143
+ | `primary-sidebar` | **Sidebar 底部**(`<SidebarFooter>` 內,當前使用者)| 有 — 放使用者 | Linear / Notion / Figma(無 global header,帳號自然在 sidebar 底)|
144
+ | `primary-header` | **globalHeader 右側**(trailing slot,品牌在左、帳號在右)| **無 / 空** | GitHub / Gmail / Slack(帳號 avatar 一律在 global top bar 右上)|
145
+
146
+ **Rule:帳號入口只能出現一次**(視覺 SSOT,同 WorkspaceBrand)。`primary-header` mode 同時放(globalHeader 右 + sidebar footer)= 視覺冗餘 + 入口混淆 → **禁止**;`primary-header` 的 sidebar **不放** user footer(與上方「globalHeader 存在 → sidebar 不重複 chrome 角色」同源邏輯)。
147
+
148
+ **為什麼 primary-header 帳號入口在「右上」**:GitHub / Gmail / Slack / Atlassian 的全域標頭一律把「自己的帳號」放右上(品牌在左、帳號在右,左右對稱)= global chrome 標準位置,不是 sidebar footer。
149
+
150
+ **入口開什麼 = 帳號選單(`<DropdownMenu>`),不是 ProfileCard**:
151
+ - 開「個人資料 / 設定 / 登出」navigation 選單(對齊 GitHub / Gmail / Slack / Atlassian「自己的帳號入口」慣例)。
152
+ - **不用 `ProfileCard`**:ProfileCard 的預設動作(Chat / 通話)語義是「聯絡某人」,用在**自己**身上不對 — ProfileCard 是看**別人**的人員卡。自己的帳號入口 = 選單。
153
+ - 消費既有 `components/DropdownMenu`(`DropdownMenuTrigger asChild` 包 avatar 作 focusable 觸發點)。
154
+
155
+ **Avatar 尺寸 + 邊距**:帳號 avatar = `<Avatar size={24}>`(density-fixed,跟左側品牌 avatar 同尺寸),走 `../../patterns/header-canonical/header-canonical.spec.md` 4.5 chrome header avatar SSOT(brand + account 皆 24px raw Avatar、非 ItemAvatar)。右側邊距 = 作為 ChromeHeader 主內容區最後一個 child 自然繼承 `--layout-space-loose`(16px@md / 24px@lg),**與左側品牌 avatar 距 leadingRail 分割線的距離對稱**(ChromeHeader px-loose canonical 結構性保證,**禁 hardcode margin**)。
156
+
157
+ **Reference implementation**:`_demo-helpers.tsx` `AccountMenu`(帳號選單)+ `GlobalHeader`(右 slot 預設放 `<AccountMenu />`)。
158
+
137
159
  **真正的 distinguishing factor = Header scope(local toolbar vs global bar)**:
138
160
  - **Local toolbar 派**(當前頁 anchor / breadcrumb / page-level actions / filter / 該頁 specific 操作)→ `primary-sidebar`
139
161
  - **Global bar 派**(account avatar / workspace switcher / notifications / 跨頁 search / 跨頁導覽)→ `primary-header`
@@ -322,3 +344,9 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
322
344
  - `components/Sheet/sheet.spec.md` — modal fallback SSOT(`aria-labelledby` 強制 + `z-50`)
323
345
  - `tokens/layoutSpace/layoutSpace.spec.md` — Main 內 layout 6 條規則 SSOT
324
346
  - `tokens/uiSize/uiSize.spec.md` — `--chrome-header-height` 等 size token
347
+
348
+ ## 被引用(auto-maintained,Dim 3 reciprocal audit)
349
+
350
+ > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
351
+
352
+ - `header-canonical.spec.md`
@@ -400,6 +400,7 @@ export const PrimaryHeader: Story = {
400
400
  <AcmeSidebar
401
401
  viewportInsetTop="var(--chrome-header-height)"
402
402
  includeWorkspaceBrand={false}
403
+ includeUserFooter={false}
403
404
  />
404
405
  }
405
406
  globalHeader={<GlobalHeader />}
@@ -170,7 +170,9 @@ Avatar 堆疊的「+N」hover 也出 **HoverCard**(不是 Tooltip),因為
170
170
  | 場景 | Avatar 用法 |
171
171
  |---|---|
172
172
  | Menu item / List item inside prefix slot | `<Avatar size="fill" />`(讓父 prefix 控制尺寸) |
173
- | 獨立使用(page header user avatar 等) | `<Avatar size={40} />`(顯式指定 px) |
173
+ | 獨立使用(獨立頁面 hero / profile header 的大 avatar | `<Avatar size={40} />`(顯式指定 px) |
174
+
175
+ > **勿混**:上表「page header 40px」指**獨立頁面 hero / profile header** 的大 avatar。**App chrome / global header 內的 avatar(品牌 + 帳號入口)≠ 此處**——走 `../../patterns/header-canonical/header-canonical.spec.md` 4.5 = **24px density-fixed**(chrome header avatar canonical),兩者 scope 不同。
174
176
 
175
177
  ---
176
178
 
@@ -72,11 +72,11 @@ benchmark:
72
72
  - **永遠 size="sm"**(不論家族 / density):chrome / overlay header 內 button 一律 sm。**廣化(2026-06-12 user 拍板)**:header 內**所有 field-height 控件**(Button / Input / Select / SegmentedControl / Combobox 等)一律 `sm`,禁混雜不同 field height(Tabs 跟齊 chrome 高度,唯一例外 = 「Tabs 取代整個 header」的 standalone `size="lg"`,見上方表)
73
73
  - 對齊 既有 `dialog.tsx:132-153` + `sheet.tsx:122-139` + `popover.tsx:87-110`(Dialog/Sheet/Popover 已內建 close X)
74
74
 
75
- ### 4.5 Chrome header avatar SSOT(2026-05-21 codify per user directive)
75
+ ### 4.5 Chrome header avatar SSOT(2026-05-21 codify per user directive;2026-06-17 擴涵 account entry)
76
76
 
77
- **Canonical**:Chrome header brand mark avatar = **24px,density-fixed,row-size-fixed**。
77
+ **Canonical**:Chrome header avatar = **24px,density-fixed,row-size-fixed**。涵蓋同一條 chrome header 內**兩種** avatar,皆 24px:**(a) 品牌 mark**(WorkspaceBrand,左)+ **(b) 帳號入口 account entry**(primary-header globalHeader 右側的個人設定 avatar)。同一條 chrome header 內所有 avatar 尺寸一致。
78
78
 
79
- 對齊 5 家世界級共識(Linear / Notion / Figma / Slack / Polaris chrome header brand mark 皆固定 24px,不 density-scale 也不 row-size-scale)。設計理由:avatar 是品牌識別 mark,視覺穩定優於 density 緊鬆 / row size 調整(button 跟 density 走 touch target 邏輯,avatar 不該綁同邏輯)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
79
+ 對齊 5 家世界級共識(Linear / Notion / Figma / Slack / Polaris chrome header brand mark 皆固定 24px,不 density-scale 也不 row-size-scale;GitHub 20 / Atlassian small 24 / Linear 右上帳號 avatar 同屬此小尺寸區間)。設計理由:chrome header avatar(品牌或帳號)是身份識別 mark,視覺穩定優於 density 緊鬆 / row size 調整;**帳號入口雖可互動,互動感由 focus ring + hover 提供,不靠把 avatar 放大到 field height**(button 跟 density 走 touch target 邏輯,avatar 不綁同邏輯)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
80
80
 
81
81
  **Chrome header 不是 row context**:無 sm/md/lg row size lookup 需求 → chrome header 內 avatar 用 **raw `<Avatar size={24}>`**,**不用 `<ItemAvatar>`**(後者是 row primitive anatomy helper,scope 是 row context — 詳 `item-anatomy.spec.md`「Scope 例外:Chrome header 不是 row context」段)。
82
82
 
@@ -88,8 +88,9 @@ benchmark:
88
88
  - 在 chrome header 內放 avatar → **必用 raw `<Avatar size={24}>`**,**禁用 `<ItemAvatar>`**(會誤啟動 row anatomy lookup)
89
89
  - SidebarHeader 收合對齊公式必消費 `var(--chrome-header-avatar-size)`,**禁** hardcode 24
90
90
  - WorkspaceBrand demo 即此 pattern 的 reference implementation(`components/AppShell/_demo-helpers.tsx:WorkspaceBrand`)
91
+ - **帳號入口(account entry)同規則**:primary-header globalHeader 右側的個人設定 avatar 也用 raw `<Avatar size={24}>`(reference:`_demo-helpers.tsx:AccountMenu`)。放置 / 開帳號選單 / 邊距對稱 canonical 見 `../../components/AppShell/app-shell.spec.md`「帳號入口(Account entry)放置 SSOT」段。
91
92
 
92
- **Sync invariant**:`--chrome-header-avatar-size` CSS 值 + `<Avatar size={24}>` JS literal + `AVATAR_SIZE.inline.md = 24`(`item-anatomy.tsx:94`,L93 是 `export const AVATAR_SIZE = {` 宣告開頭)三者必同 24px 值。改 spec canonical 時 grep `--chrome-header-avatar-size` + `chrome header avatar canonical` keyword 找全 sync 點。
93
+ **Sync invariant**:`--chrome-header-avatar-size` CSS 值 + `<Avatar size={24}>` JS literal(WorkspaceBrand + AccountMenu 兩處)+ `AVATAR_SIZE.inline.md = 24`(`item-anatomy.tsx:94`,L93 是 `export const AVATAR_SIZE = {` 宣告開頭)必同 24px 值。改 spec canonical 時 grep `--chrome-header-avatar-size` + `chrome header avatar canonical` keyword 找全 sync 點。
93
94
 
94
95
  ### 5. Title typography
95
96