@qijenchen/design-system 0.1.0-beta.67 → 0.1.0-beta.68

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.
Files changed (60) hide show
  1. package/dist/components/Alert/alert.d.ts.map +1 -1
  2. package/dist/components/Alert/alert.js +4 -4
  3. package/dist/components/Alert/alert.js.map +1 -1
  4. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  5. package/dist/components/Dialog/dialog.js.map +1 -1
  6. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  7. package/dist/components/DropdownMenu/dropdown-menu.js +0 -1
  8. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  9. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  10. package/dist/components/FileViewer/file-viewer.js +1 -2
  11. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  12. package/dist/components/Popover/popover.d.ts +1 -1
  13. package/dist/components/Popover/popover.d.ts.map +1 -1
  14. package/dist/components/Popover/popover.js +9 -10
  15. package/dist/components/Popover/popover.js.map +1 -1
  16. package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
  17. package/dist/components/Tooltip/tooltip.js +0 -1
  18. package/dist/components/Tooltip/tooltip.js.map +1 -1
  19. package/dist/patterns/overlay-surface/index.js +2 -1
  20. package/dist/patterns/overlay-surface/overlay-surface.d.ts +8 -0
  21. package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
  22. package/dist/patterns/overlay-surface/overlay-surface.js +3 -1
  23. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  24. package/ds-canonical/hooks/tests/test_check_story_invariants.sh +1 -1
  25. package/ds-canonical/templates/dashboard-app.tsx +9 -4
  26. package/llms-full.txt +1 -1
  27. package/llms.txt +1 -1
  28. package/package.json +1 -1
  29. package/src/components/Alert/alert.spec.md +1 -1
  30. package/src/components/Alert/alert.tsx +7 -4
  31. package/src/components/Button/button.spec.md +2 -2
  32. package/src/components/Checkbox/checkbox.principles.stories.tsx +18 -15
  33. package/src/components/Coachmark/coachmark.principles.stories.tsx +3 -2
  34. package/src/components/DataTable/data-table-filter-panel.tsx +3 -3
  35. package/src/components/DataTable/data-table-sort-manager.tsx +3 -3
  36. package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -1
  37. package/src/components/Dialog/dialog.spec.md +11 -11
  38. package/src/components/Dialog/dialog.tsx +7 -8
  39. package/src/components/DropdownMenu/dropdown-menu.tsx +4 -1
  40. package/src/components/FileItem/file-item.spec.md +1 -1
  41. package/src/components/FileItem/file-item.stories.tsx +3 -3
  42. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +4 -4
  43. package/src/components/FileViewer/file-viewer.spec.md +4 -4
  44. package/src/components/FileViewer/file-viewer.tsx +6 -3
  45. package/src/components/Notice/notice.anatomy.stories.tsx +4 -4
  46. package/src/components/Notice/notice.spec.md +1 -0
  47. package/src/components/Notice/notice.stories.tsx +4 -4
  48. package/src/components/Popover/popover.anatomy.stories.tsx +13 -11
  49. package/src/components/Popover/popover.principles.stories.tsx +10 -8
  50. package/src/components/Popover/popover.spec.md +2 -2
  51. package/src/components/Popover/popover.tsx +14 -11
  52. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +3 -2
  53. package/src/components/Sheet/sheet.principles.stories.tsx +5 -4
  54. package/src/components/Sidebar/sidebar.spec.md +2 -0
  55. package/src/components/Tooltip/tooltip.tsx +3 -1
  56. package/src/patterns/header-canonical/header-canonical.spec.md +3 -2
  57. package/src/patterns/overlay-surface/overlay-surface.spec.md +12 -10
  58. package/src/patterns/overlay-surface/overlay-surface.tsx +20 -8
  59. package/src/tokens/density/density.spec.md +33 -22
  60. package/src/tokens/layoutSpace/layoutSpace.stories.tsx +4 -4
@@ -294,7 +294,7 @@ expect_block "16. R9 手刻 <div px-loose border-b border-divider> overlay heade
294
294
  run_hook "PreToolUse" "Write" "$TMP_DIR/r9-correct.stories.tsx" '
295
295
  export const Panel = () => (
296
296
  <div className="max-w-md flex flex-col rounded-lg border-border bg-surface-raised shadow-[var(--elevation-200)]">
297
- <SurfaceHeader className="justify-between [--chrome-slot-h:1.25rem]">
297
+ <SurfaceHeader className="justify-between">
298
298
  <div className="flex-1 min-w-0"><PopoverTitle>正在上傳 3 個項目</PopoverTitle></div>
299
299
  </SurfaceHeader>
300
300
  </div>
@@ -20,7 +20,7 @@
20
20
  // 3. 替換 DashboardPage 為真實業務 widgets(DataTable / Chart / Card 等 DS 元件)
21
21
  // 4. 替換 PageHeader rightSlot 的 primary action(若有)
22
22
 
23
- import { useState } from 'react'
23
+ import { useState, type ReactElement } from 'react'
24
24
  import {
25
25
  AppShell,
26
26
  SidebarProvider,
@@ -97,9 +97,14 @@ function AppSidebar() {
97
97
  // 繞過 header-canonical 全部機械簽名 + 違反「消費 primitive 不 hand-craft」canonical;
98
98
  // 對齊 _demo-helpers.tsx PageHeader 同款消費形)──
99
99
  // SidebarTrigger 必有(primary-sidebar mode 的 menu toggle 入口,⌘B keyboard shortcut)
100
- // rightSlot JSX.Element(非 React.ReactNode):workspace 內 app 自帶 @types/react 副本與
101
- // DS 套件 .d.ts 解析的 ReactNode 版本不相容(bigint 差異)— Element 兩邊皆可指派
102
- function PageHeader({ title, rightSlot }: { title: string; rightSlot?: JSX.Element }) {
100
+ // rightSlot 型別用 ReactElement<any, any>(= 舊全域 JSX.Element 的去全域等價寫法):
101
+ // - 不用裸 JSX.Element:React 19 @types/react 移除「全域」JSX namespace → `JSX.Element` 在 fresh React19 install
102
+ // 下 TS2503「Cannot find namespace 'JSX'」(本機 @types/react 19.2.15 仍含全域 shim 源端 tsc 假陰性,
103
+ // 只在 receiver 拿到無 shim 的 19.x fresh install 才炸;2026-06-12 bde81e7e 引入,brick 下游 receiver build + audit)
104
+ // - 不用裸 ReactElement:@types/react@19 預設參數由 any 改 unknown,`ReactElement<unknown>` 因 ReactPortal.children
105
+ // 分支不可指派給 ReactNode(TS2322)→ 必顯式 <any, any>(JSX.Element 本就是 ReactElement<any, any>,語意不變)
106
+ // - 不用 ReactNode:workspace app 自帶 @types/react 副本與 DS .d.ts 的 ReactNode 版本 bigint 差異不相容
107
+ function PageHeader({ title, rightSlot }: { title: string; rightSlot?: ReactElement<any, any> }) {
103
108
  return (
104
109
  <ChromeHeader className="bg-surface">
105
110
  <SidebarTrigger />
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.67
3
+ > 全 component / pattern 的 variants / sizes / 禁止事項。build-time 從 spec.md frontmatter 生成,禁手改。v0.1.0-beta.68
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.67
4
+ > 54 components + 4 public patterns + design tokens。v0.1.0-beta.68
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.67",
3
+ "version": "0.1.0-beta.68",
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",
@@ -142,7 +142,7 @@ Alert chrome corner 可承載多個 action(close 左側並排):
142
142
 
143
143
  ### Subtle(預設)
144
144
 
145
- 淺底色 + 四邊 1px border(邊框採語意色的 hover tint)。99% 場景用 subtle——視覺重量適中,使用者注意但可繼續主要任務。不設 `data-theme`,元素跟隨頁面 theme。
145
+ 淺底色 + 四邊 1px border(邊框採 `--{hue}-text` = icon 同色,2026-06-15 user 拍板;原借 `-hover` 狀態 token 是語意 smell 已改)。99% 場景用 subtle——視覺重量適中,使用者注意但可繼續主要任務。不設 `data-theme`,元素跟隨頁面 theme。
146
146
 
147
147
  ### Solid
148
148
 
@@ -23,12 +23,15 @@ import { Notice, useInverseTheme, SUBTLE_ICON_COLOR, type NoticeVariant } from '
23
23
  * fixed: 無圓角,full-width,無 border
24
24
  */
25
25
 
26
+ // border 用 `--{hue}-text`(= icon 同色,2026-06-15 user 拍板「直接共用 hue-text,不開新 token」)。
27
+ // 原借 `--{hue}-hover`(互動狀態 token)當靜態 border 是語意 smell + 跟 icon 不同色;
28
+ // 改共用 hue-text(SUBTLE_ICON_COLOR 用的同一顆,blue-7 等)→ 0 新 token + border/icon cohesion。
26
29
  const SUBTLE_CONTAINER: Record<NoticeVariant, string> = {
27
30
  neutral: 'bg-muted border border-border',
28
- info: 'bg-info-subtle border border-[var(--info-hover)]',
29
- success: 'bg-success-subtle border border-[var(--success-hover)]',
30
- warning: 'bg-warning-subtle border border-[var(--warning-hover)]',
31
- error: 'bg-error-subtle border border-[var(--error-hover)]',
31
+ info: 'bg-info-subtle border border-[var(--info-text)]',
32
+ success: 'bg-success-subtle border border-[var(--success-text)]',
33
+ warning: 'bg-warning-subtle border border-[var(--warning-text)]',
34
+ error: 'bg-error-subtle border border-[var(--error-text)]',
32
35
  }
33
36
 
34
37
  const SOLID_HUE_BG: Record<string, string> = {
@@ -370,9 +370,9 @@ Button 自動加 **`data-unbounded="true"`** attribute 當 **`variant === 'text'
370
370
 
371
371
  **實際應用**:`SurfaceHeader` / `SurfaceFooter` 內建 CSS rule(SSOT 在 `overlay-surface.tsx` `CHROME_UNBOUNDED_SLOT`,此處僅引述不重述完整公式):
372
372
  ```css
373
- [&_[data-unbounded]]:my-[calc((var(--chrome-slot-h,var(--field-height-xs))-var(--field-height-sm))/2)]
373
+ [&_[data-unbounded]]:my-[calc((var(--chrome-slot-h,calc(var(--font-body-lg-size)*1.5))-var(--field-height-sm))/2)]
374
374
  ```
375
- → slot 高度由 `--chrome-slot-h` 參數化(default `var(--field-height-xs)`=24;Popover override 20)
375
+ → slot 高度由 `--chrome-slot-h` 參數化(default 衍生 `calc(var(--font-body-lg-size)*1.5)`=24;Popover-tier override = `COMPACT_HEADER_SLOT` 衍生 21;皆 title 字級改自動跟)
376
376
  → default(md): my=-2px / lg: my=-4px
377
377
  → 效果:Button native size 不變(sm 28/32,touch target 亦同),**layout 佔位縮到 24**(等效 xs 幾何),header = 24 + 2×tight = 48/56 = `--chrome-header-height` ✓
378
378
 
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import LinkTo from '@storybook/addon-links/react'
3
3
  import type { Meta, StoryObj } from '@storybook/react'
4
4
  import { Checkbox } from './checkbox'
5
+ import { CheckboxGroup } from './checkbox-group'
5
6
  import { Switch } from '@/design-system/components/Switch/switch'
6
7
  import { RadioGroup, RadioGroupItem } from '@/design-system/components/RadioGroup/radio-group'
7
8
 
@@ -64,17 +65,19 @@ export const UsageGuidance: Story = {
64
65
  note="勾選不代表立刻生效——使用者按「儲存」前都可以反悔。心智模型是「選擇 / 同意」,視覺語言強調「尚未確定」"
65
66
  >
66
67
  <div className="border border-border rounded-lg p-4 space-y-3">
67
- <Checkbox
68
- label="我同意服務條款與隱私政策"
69
- checked={agree}
70
- onCheckedChange={(v) => setAgree(v === true)}
71
- />
72
- <Checkbox
73
- label="訂閱行銷訊息"
74
- description="每月最多 2 封,可隨時取消"
75
- checked={notif === 'checked'}
76
- onCheckedChange={(v) => setNotif(v ? 'checked' : 'unchecked')}
77
- />
68
+ <CheckboxGroup>
69
+ <Checkbox
70
+ label="我同意服務條款與隱私政策"
71
+ checked={agree}
72
+ onCheckedChange={(v) => setAgree(v === true)}
73
+ />
74
+ <Checkbox
75
+ label="訂閱行銷訊息"
76
+ description="每月最多 2 封,可隨時取消"
77
+ checked={notif === 'checked'}
78
+ onCheckedChange={(v) => setNotif(v ? 'checked' : 'unchecked')}
79
+ />
80
+ </CheckboxGroup>
78
81
  <div className="flex gap-2 pt-2">
79
82
  <button className="h-field-md px-3 text-body rounded-md bg-primary text-inverse-fg">儲存</button>
80
83
  <button className="h-field-md px-3 text-body rounded-md border border-border">取消</button>
@@ -119,7 +122,7 @@ export const UsageGuidance: Story = {
119
122
  title="vs Radio — Checkbox 是獨立 toggle(多選 或 單一 agreement)"
120
123
  note="每個 Checkbox 是獨立 boolean——可同時勾多個。也用於單一「我同意 X」agreement"
121
124
  >
122
- <div className="space-y-2">
125
+ <CheckboxGroup>
123
126
  <Checkbox
124
127
  label="讀取權限"
125
128
  description="可查看資料"
@@ -138,7 +141,7 @@ export const UsageGuidance: Story = {
138
141
  checked={perms.admin}
139
142
  onCheckedChange={(v) => setPerms({ ...perms, admin: v === true })}
140
143
  />
141
- </div>
144
+ </CheckboxGroup>
142
145
  <Label>↑ 權限可以任意組合——多選用 Checkbox</Label>
143
146
  </Rule>
144
147
 
@@ -158,11 +161,11 @@ export const UsageGuidance: Story = {
158
161
  title="❌ 多選一用 Checkbox"
159
162
  note="使用者可以同時勾 3 個 → 邏輯錯誤,破壞互斥語意"
160
163
  >
161
- <div className="space-y-2">
164
+ <CheckboxGroup>
162
165
  <Checkbox label="信用卡" />
163
166
  <Checkbox label="銀行轉帳" />
164
167
  <Checkbox label="貨到付款" />
165
- </div>
168
+ </CheckboxGroup>
166
169
  <Label warn>↑ 付款方式只能一個,卻用 Checkbox 允許多選 → 用 RadioGroup</Label>
167
170
  </Rule>
168
171
 
@@ -26,6 +26,7 @@ import {
26
26
  } from '@/design-system/components/Dialog/dialog'
27
27
  import { Button } from '@/design-system/components/Button/button'
28
28
  import { Checkbox } from '@/design-system/components/Checkbox/checkbox'
29
+ import { CheckboxGroup } from '@/design-system/components/Checkbox/checkbox-group'
29
30
 
30
31
  const meta: Meta = {
31
32
  title: 'Design System/Components/Coachmark/設計原則',
@@ -138,10 +139,10 @@ export const UsageGuidance: Story = {
138
139
  <PopoverContent align="start">
139
140
  <PopoverHeader><PopoverTitle>依狀態篩選</PopoverTitle></PopoverHeader>
140
141
  <PopoverBody>
141
- <div className="grid">
142
+ <CheckboxGroup>
142
143
  <Checkbox defaultChecked label="進行中" />
143
144
  <Checkbox label="已完成" />
144
- </div>
145
+ </CheckboxGroup>
145
146
  </PopoverBody>
146
147
  </PopoverContent>
147
148
  </Popover>
@@ -13,7 +13,7 @@ import { NumberInput } from '@/design-system/components/NumberInput/number-input
13
13
  import { DatePicker, DatePickerRange } from '@/design-system/components/DatePicker/date-picker'
14
14
  import { PeoplePicker } from '@/design-system/components/PeoplePicker/people-picker'
15
15
  import type { PersonValue } from '@/design-system/components/PeoplePicker/person-display'
16
- import { SurfaceHeader, SurfaceBody } from '@/design-system/patterns/overlay-surface/overlay-surface'
16
+ import { SurfaceHeader, SurfaceBody, COMPACT_HEADER_SLOT } from '@/design-system/patterns/overlay-surface/overlay-surface'
17
17
  import { PopoverTitle, PopoverClose } from '@/design-system/components/Popover/popover'
18
18
  import { ButtonDivider } from '@/design-system/components/Button/button-group'
19
19
  import { FieldControlGroup } from '@/design-system/components/FieldControlGroup/field-control-group'
@@ -551,8 +551,8 @@ function DataTableFilterPanelInner<TData>({
551
551
  : 'w-[min(640px,calc(100vw-2rem))]',
552
552
  className,
553
553
  )}>
554
- {/* Popover 派輕量 chrome — slot 20 匹配 PopoverTitle text-body line-height,header 自然 ~45px */}
555
- <SurfaceHeader className="[--chrome-slot-h:1.25rem]">
554
+ {/* Popover 派輕量 chrome — slot COMPACT_HEADER_SLOT(=21,衍生自 PopoverTitle text-body line-box),header 自然 ~45px */}
555
+ <SurfaceHeader className={COMPACT_HEADER_SLOT}>
556
556
  <PopoverTitle className="flex-1">篩選</PopoverTitle>
557
557
  {/* Refresh icon — 只在 value ≠ defaultValue 時顯示(對齊 sort modified-from-default UX)
558
558
  含 ButtonDivider 對齊「欄位顯示」+「排序」chrome corner action canonical(2026-05-04) */}
@@ -10,7 +10,7 @@ import { cn } from '@/lib/utils'
10
10
  import { dragSourceStyle, dragHandleCursorClass } from '@/design-system/lib/drag-visual'
11
11
  import { Button } from '@/design-system/components/Button/button'
12
12
  import { Select, type SelectOption } from '@/design-system/components/Select/select'
13
- import { SurfaceHeader, SurfaceBody } from '@/design-system/patterns/overlay-surface/overlay-surface'
13
+ import { SurfaceHeader, SurfaceBody, COMPACT_HEADER_SLOT } from '@/design-system/patterns/overlay-surface/overlay-surface'
14
14
  import { ButtonDivider } from '@/design-system/components/Button/button-group'
15
15
  import { PopoverTitle, PopoverClose } from '@/design-system/components/Popover/popover'
16
16
  import { ItemInlineActionButton } from '@/design-system/patterns/element-anatomy/item-anatomy'
@@ -108,8 +108,8 @@ export function DataTableSortManager<TData>({
108
108
  // 2026-05-23 Phase A.4 Decision 2: w-[480px] → token `--data-table-sort-panel-width`(SSOT in uiSize.css)
109
109
  return (
110
110
  <div className={cn('flex flex-col h-full min-h-0 w-[var(--data-table-sort-panel-width)]', className)}>
111
- {/* Popover 派輕量 chrome — slot 20 匹配 PopoverTitle text-body line-height,header 自然 ~45px */}
112
- <SurfaceHeader className="[--chrome-slot-h:1.25rem]">
111
+ {/* Popover 派輕量 chrome — slot COMPACT_HEADER_SLOT(=21,衍生自 PopoverTitle text-body line-box),header 自然 ~45px */}
112
+ <SurfaceHeader className={COMPACT_HEADER_SLOT}>
113
113
  <PopoverTitle className="flex-1">排序</PopoverTitle>
114
114
  {onReset && sorting.length > 0 && (
115
115
  <>
@@ -417,7 +417,7 @@ export const ColorMatrix: Story = {
417
417
  <div className="flex flex-col gap-8">
418
418
  <div>
419
419
  <H3>Density</H3>
420
- <Desc>Dialog 繼承 page `data-density`(v5 校準,跟 Sheet 對齊)— 不自設密度 attribute。先前曾設 `data-layout-space="lg"` body 寬鬆呼吸,但跟 `--chrome-header-height` canonical(md=48)衝突,2026-04-22 撤回。世界級:Polaris Modal px 16(md loose)/ Material M3 24(lg loose)— 我方跟 page density 自動對齊。</Desc>
420
+ <Desc>Dialog `data-layout-space="lg"`(只鎖版面間距 tier)+ ui-size 繼承 page `data-density`(控件不被撐大)。modal 值得寬鬆呼吸 padding:body px-loose 24 / header py-tight 16 → header 56(宣告 lg tier)。reinstate c3d3b736 decouple(只鎖 layout-space,非當年連 ui-size 一起鎖撐高 header density)。世界級:Material M3 / Atlassian modal padding 24。</Desc>
421
421
  </div>
422
422
 
423
423
  <div>
@@ -58,17 +58,17 @@ DialogContent (fixed, centered)
58
58
 
59
59
  **Padding SSOT**:Header / Body / Footer 的 padding + 分隔線由 `patterns/overlay-surface/overlay-surface.spec.md` own——Dialog 與 Popover 共用同一套 primitive,避免 token 漂移。Dialog 特有行為:Header 的 Close 按鈕;Body 用 `<ScrollArea>` wrap(viewport-fill 專用,SSOT 見 overlay-surface.spec.md 「Body overflow canonical」節 + `components/ScrollArea/scroll-area.spec.md`)。
60
60
 
61
- ## Density(2026-04-22 v5 校準:繼承 page density,跟 Sheet 對齊)
61
+ ## Density(2026-06-16 定論:全繼承 page,不鎖)
62
62
 
63
- Dialog **繼承 page `data-density`**,不自設密度 attribute。這是 overlay primitive canonical(跟 `components/Sheet/sheet.tsx` 對齊 Sheet 不自設 density,繼承 page 層級 `html[data-density]`,見 sheet.tsx line 111 canonical)
63
+ Dialog **不自設任何 density attribute**,layout-space + ui-size 全繼承 page。效果:md page → body `px-loose`=16 / header `py-tight`=12(header 48);lg page 24 / 16(header 56),隨 page
64
64
 
65
- **歷史備忘**:先前曾設 `data-layout-space="lg"` header/body 寬鬆呼吸,但跟 `--chrome-header-height` canonical 衝突(md page dialog header 期望 48,強設 lg 會變 56)。**已於 2026-04-22 v5 撤回**,Dialog 全盤繼承 page density,header 高度 = `--chrome-header-height` 自動對齊(md=48 / lg=56)。
65
+ **為何不鎖(撤回本 session 一度加的 `data-layout-space="lg"`)**:本 `density.spec` 第 10 行親自定義 layout-space 管「dialog body padding」—— Dialog 若鎖死 layout-space,等於 override 我們自家 dial 對它點名要管的對象失效 = **自相矛盾**。有「同類 padding-density dial」的世界級 DS(SAP Fiori cozy/compact 做 `syncStyleClass` 專門把 page density 同步進 dialog;AWS Cloudscape compact「propagate to all components, in all view types」,例外只列 informational + tiny dropdown,modal 不在內)都讓 **modal page dial 走**,沒有一家把 modal 釘固定 tier。Material/Atlassian 的固定 24 是因為它們**沒有** density dial(其 24 = 我方 lg)。
66
66
 
67
- **世界級對照**:
68
- - Polaris Modal:px 16(= md loose)
69
- - Material M3 Dialog:px 24(= lg loose)
70
- - Atlassian Dialog:px 24
71
- - 我方:跟隨 page density,md=16 / lg=24,兩端都在世界級 range 內。
67
+ **「modal 要寬鬆呼吸」怎麼滿足**:lg 產品自動拿 24(Material 級);md 產品本就要緊湊(資訊密集),16 是合格 modal padding(Polaris Modal = 16,世界級下限)。**「button 不撐高 header」**由 ui-size 繼承 page 解決(button = page sm),與 layout-space 鎖不鎖無關 → 故不需鎖。
68
+
69
+ **Header 高度**:padding-based = title line-box 24 + `py-tight`×2 → md 48 / lg 56,**隨 page tier**(= `--chrome-header-height` 同 tier 對齊)
70
+
71
+ **世界級對照**:Material M3 / Atlassian 24(無 dial,= 我方 lg)/ Polaris 16(= 我方 md 下限)/ SAP Fiori + Cloudscape(有 dial → modal 跟 dial = 我方繼承)。
72
72
 
73
73
  ## Layout
74
74
 
@@ -99,8 +99,8 @@ Modal 與 viewport 四邊保持 `--layout-space-bottom`(48px)最小間距。
99
99
 
100
100
  永遠存在於 DialogHeader 右側。使用 `<Button data-dismiss iconOnly dismiss size="sm" startIcon={X} aria-label="關閉" />`,不可移除——使用者永遠需要明確的關閉手段。
101
101
 
102
- **Size canonical(v5 chrome-unbounded)**:Button native size **sm**(28 md / 32 lg),touch target 亦同。SurfaceHeader 的 `[data-unbounded]` CSS rule 自動對 text variant / dismiss 套負 my `calc((xs-sm)/2)` → **layout 佔位 = 24**(xs 固定)。效果:
103
- - Header 只有 title + close X → max layout = 24 → header = 24 + 2×tight = **48 md / 56 lg = `--chrome-header-height`**
102
+ **Size canonical(v5 chrome-unbounded)**:Button native size **sm**(隨 page density:28 md / 32 lg),touch target 亦同。SurfaceHeader 的 `[data-unbounded]` CSS rule 自動對 text variant / dismiss 套負 my → **layout 佔位 = title line-box**(slot 衍生自 `--font-body-lg-size`×1.5=24,見 `overlay-surface.spec.md` slot 段)。效果:
103
+ - Header 只有 title + close X → max layout = 24 → header = 24 + 2×`py-tight`(隨 page:md 12 / lg 16)= **48 md / 56 lg**(= `--chrome-header-height` 同 tier 對齊)
104
104
  - Header 塞 bounded primary(無 `data-unbounded`)→ header 自然長高
105
105
 
106
106
  **Canonical 來源**:Dialog 是 overlay chrome,corner close X 屬 action group region,必用 Button(非 Inline Action / 非自刻 button)。詳見 `patterns/element-anatomy/item-anatomy.spec.md`「Dismiss canonical」+ `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」。
@@ -142,7 +142,7 @@ Dialog 是容器,無整體 disabled / loading / empty 狀態——這些屬於
142
142
 
143
143
  **Dark mode**:由 semantic token(`bg-surface-raised` / `border-border`)自動切換,無自訂 palette。
144
144
 
145
- **Density**:Dialog **繼承 page density**(v5 校準,跟 Sheet 對齊),見上「Density」段。
145
+ **Density**:Dialog **全繼承 page**(layout-space + ui-size 皆不自鎖),見上「Density」段。
146
146
 
147
147
  ---
148
148
 
@@ -95,14 +95,13 @@ const DialogContent = React.forwardRef<
95
95
  <DialogOverlay />
96
96
  <DialogPrimitive.Content
97
97
  ref={ref}
98
- // Density canonical(2026-04-22 v5 校準):Dialog 繼承 page density( Sheet 對齊
99
- // sheet.tsx line 111 canonical),不自設 data-layout-space="lg" data-density。
100
- //
101
- // 先前曾設 `data-layout-space="lg"` header/body 寬鬆呼吸,但跟 chrome-header-height
102
- // canonical 衝突(md page dialog header 期望 48,強設 lg 會變 56)。
103
- // 世界級對照:Polaris Modal horizontal padding 16 / Material M3 24 / Atlassian 24 — 16 是
104
- // 合理 lower bound;md page 16 loose body padding 可接受,lg page 自動 24。
105
- // 詳 overlay-surface.spec.md「Chrome dismiss size canonical」
98
+ // Density:**全繼承 page**(layout-space + ui-size 都不自鎖)。2026-06-16 定論(撤回本 session 一度加的
99
+ // data-layout-space="lg"):density.spec 10 行親自定義 layout-space 管「dialog body padding」——
100
+ // Dialog 鎖死它 = override 自家 dial 對它點名要管的對象失效 = 自相矛盾。有同類 padding-density dial 的
101
+ // 世界級(SAP Fiori syncStyleClass / AWS Cloudscape「all view types」)都讓 modal 跟 page dial 走、不鎖固定 tier。
102
+ // 效果:md page body px-loose 16 / header py-tight 12(header 48);lg page → 24 / 16(header 56),隨 page
103
+ // 「modal 要寬鬆」需求在 lg 階自然滿足(Polaris modal 16 = 世界級下限,證明 md 16 合格);「button 不撐高
104
+ // header」由 ui-size 繼承 page 解決(button=page sm),與 layout-space 鎖不鎖無關 故不需鎖。
106
105
  onOpenAutoFocus={handleOpenAutoFocus}
107
106
  className={cn(
108
107
  "fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2",
@@ -129,7 +129,10 @@ const DropdownMenuContent = React.forwardRef<
129
129
  sideOffset={sideOffset}
130
130
  collisionPadding={collisionPadding}
131
131
  align={align}
132
- data-density="md"
132
+ // Density:繼承 page density(2026-06-15 canonical)。menu item 高度 = field-height-{size},而
133
+ // field-height 隨 density 變(md 28/32/36 → lg 32/36/40)→ 鎖 data-density="md" 會把選單釘在 md-scale,
134
+ // lg page 上對不上 lg 觸發點。原 data-density 是 409b91da a11y 批次順手加(對齊 Popover),非設計
135
+ // 決策 → 移除,item 隨 page density 與觸發點一致(tier 仍由 size prop 決定)。
133
136
  // Focus return on close:不 override `onCloseAutoFocus` — 用 Radix 內建 default
134
137
  // (close 時 focus 還 trigger;outside-interaction 例外由 Radix `hasInteractedOutsideRef` 自管)。
135
138
  // W3C APG menubar「Escape: …return focus to the element…from which the menu was opened」
@@ -293,7 +293,7 @@ upload-manager 的 completed(100% bar + ✓)屬「剛完成的 upload session」
293
293
  - **它是 popover-class 浮層 surface,但不是 Radix `<Popover>`**(常駐面板:不靠 trigger 開、不 outside-click 關、用 chevron 收合非 X dismiss)→ **不包 `<Popover>`**,而是直接消費 overlay-surface 三件套 primitive。
294
294
  - **殼 + header + body 全消費 overlay-surface SSOT(禁手刻)**:
295
295
  - 殼:用 Popover 同款 chrome token `rounded-lg border border-border bg-surface-raised shadow-[var(--elevation-200)] flex flex-col`(DS 無獨立 shell primitive — `PopoverContent`/`DialogContent` 各自套這組 token;常駐面板鏡像同值)。
296
- - header:`<SurfaceHeader className="justify-between [--chrome-slot-h:1.25rem]">` + `<PopoverTitle>`(輕量浮層 header SSOT,padding = px-loose py-tight + border-b + unbounded-slot 負 my trick)。
296
+ - header:`<SurfaceHeader className={cn("justify-between", COMPACT_HEADER_SLOT)}>` + `<PopoverTitle>`(輕量浮層 header SSOT,slot 走 `COMPACT_HEADER_SLOT`=21 衍生自 text-body title;padding = px-loose py-tight + border-b + unbounded-slot 負 my trick)。
297
297
  - body:**`<SurfaceBody>`(body SSOT,含 px-loose py-tight + flex-1 scroll 鏈)**,FileItem-specific padding 用 className override(見下)。**這不是「List-as-region」**(那專指 edge-to-edge 選單清單:item hover-bg 貼容器、px-0;Cmd+K / menu / nav)—— upload-manager list 有 px-loose、item 無 hover-bg、是 chrome-padded body,故就用 SurfaceBody。**scroll(consumer 注意)**:SurfaceBody 的 `flex-1 / overflow-y-auto` 只在 shell 有 `max-h` + `overflow-hidden` 時生效;常駐面板若檔案數可超 viewport,shell 須加 `max-h`(對齊 overlay-surface.spec.md「viewport-aware scroll」),demo 短內容不需。
298
298
  - **禁手刻**:`<div px-loose py-2 border-b>`(header)/ 手刻 `<div px-loose py-tight>`(body)= drift(2026-06-03/04 user 抓:py-2≠py-tight / 殼 token 全偏 / body 重刻 SurfaceBody)。Hook `check_story_invariants.sh R9` 機械攔手刻 header。
299
299
 
@@ -6,7 +6,7 @@ import { FileItem } from './file-item'
6
6
  import { Button } from '@/design-system/components/Button/button'
7
7
  import { FileViewer, type FileInfo } from '@/design-system/components/FileViewer/file-viewer'
8
8
  // upload-manager 面板消費 overlay-surface header + body SSOT(非手刻)—— 跟 Popover/Dialog 同一組 primitive
9
- import { SurfaceHeader, SurfaceBody } from '@/design-system/patterns/overlay-surface/overlay-surface'
9
+ import { SurfaceHeader, SurfaceBody, COMPACT_HEADER_SLOT } from '@/design-system/patterns/overlay-surface/overlay-surface'
10
10
  import { PopoverTitle } from '@/design-system/components/Popover/popover'
11
11
 
12
12
  // 錯誤 description 範例(含 clickable "View log"):consumer 自由 ReactNode,通常用底線 link 表 clickable
@@ -217,7 +217,7 @@ export const UploadManagerSurface = {
217
217
  <div className="max-w-md flex flex-col rounded-lg border border-border bg-surface-raised shadow-[var(--elevation-200)]">
218
218
  {/* header 消費 overlay-surface SurfaceHeader + PopoverTitle(輕量浮層 chrome SSOT,非手刻):
219
219
  px-loose py-tight + border-b + chevron(variant=text → 自動 data-unbounded → 套 slot 負 my trick)*/}
220
- <SurfaceHeader className="justify-between [--chrome-slot-h:1.25rem]">
220
+ <SurfaceHeader className={`justify-between ${COMPACT_HEADER_SLOT}`}>
221
221
  <div className="flex-1 min-w-0"><PopoverTitle>正在上傳 3 個項目</PopoverTitle></div>
222
222
  <Button iconOnly variant="text" size="sm" startIcon={ChevronDown} aria-label="收合" onClick={noop} />
223
223
  </SurfaceHeader>
@@ -243,7 +243,7 @@ export const UploadManagerCompactSurface = {
243
243
  render: () => (
244
244
  <div className="max-w-md flex flex-col rounded-lg border border-border bg-surface-raised shadow-[var(--elevation-200)]">
245
245
  {/* header 消費同一個 overlay-surface SurfaceHeader + PopoverTitle SSOT(同 rich panel)*/}
246
- <SurfaceHeader className="justify-between [--chrome-slot-h:1.25rem]">
246
+ <SurfaceHeader className={`justify-between ${COMPACT_HEADER_SLOT}`}>
247
247
  <div className="flex-1 min-w-0"><PopoverTitle>同步 3 個檔案</PopoverTitle></div>
248
248
  <Button iconOnly variant="text" size="sm" startIcon={ChevronDown} aria-label="收合" onClick={noop} />
249
249
  </SurfaceHeader>
@@ -394,7 +394,7 @@ export const Inspector: Story = {
394
394
  </tr>
395
395
  <tr>
396
396
  <Td>Toolbar 高度</Td>
397
- <Td mono>--chrome-header-height(lg=56px,ChromeHeader lockDensity="lg",與 InfoPanel header 等高)</Td>
397
+ <Td mono>--chrome-header-height(隨 page:48 md / 56 lg,ChromeHeader 繼承 page,與 InfoPanel header 等高)</Td>
398
398
  </tr>
399
399
  <tr>
400
400
  <Td>Toolbar 水平 padding</Td>
@@ -632,9 +632,9 @@ export const SizeMatrix: Story = {
632
632
  </tr>
633
633
  <tr>
634
634
  <Td>Toolbar 高</Td>
635
- <Td mono>--chrome-header-height(lg=56px)</Td>
635
+ <Td mono>--chrome-header-height(48 md / 56 lg)</Td>
636
636
  <Td>Figma toolbar 48–56 / macOS Preview 56</Td>
637
- <Td>ChromeHeader lockDensity="lg",token 驅動;lg 下值為 56px</Td>
637
+ <Td>ChromeHeader 繼承 page density,token 驅動;md=48 / lg=56(2026-06-15 移除 lockDensity)</Td>
638
638
  </tr>
639
639
  <tr>
640
640
  <Td>InfoPanel 寬</Td>
@@ -698,7 +698,7 @@ export const SizeMatrix: Story = {
698
698
  <tr>
699
699
  <Td>Panel header 高</Td>
700
700
  <Td mono>--chrome-header-height</Td>
701
- <Td>消費 &lt;ChromeHeader lockDensity=&quot;lg&quot;&gt;,lg = 56px;與 Toolbar 等高,視覺水平對齊</Td>
701
+ <Td>消費 &lt;ChromeHeader&gt;(繼承 page density);md=48 / lg=56,與 Toolbar 等高,視覺水平對齊</Td>
702
702
  </tr>
703
703
  <tr>
704
704
  <Td>Body 水平 padding</Td>
@@ -100,7 +100,7 @@ Family 的 canonical 規定的是「同用途同 layout」;FileViewer 用途(ful
100
100
  ```
101
101
 
102
102
  **分區決策**:
103
- - **Toolbar 高度 = `--chrome-header-height`**(md=48 / lg=56)——與 InfoPanel header 同高,視覺對齊;**code 已消費 `<ChromeHeader>` primitive**(`file-viewer.tsx:328` Toolbar + `:459` InfoPanel header,皆 `<ChromeHeader lockDensity="lg">`),高度由 primitive 內部套 `h-[var(--chrome-header-height)]`(`chrome-header.tsx`),tsx 不硬寫 height className。**跨家族 SSOT pointer**:FileViewer Toolbar + InfoPanel header 屬 **Chrome header(Fixed-h)家族**,border / padding / dismiss size / withTabs 跨家族契約 SSOT 詳 `patterns/header-canonical/header-canonical.spec.md`。viewer lock lg density `lockDensity="lg"` prop
103
+ - **Toolbar 高度 = `--chrome-header-height`**(隨 page density:md=48 / lg=56)——與 InfoPanel header 同高,視覺對齊;**code 已消費 `<ChromeHeader>` primitive**(Toolbar + InfoPanel header,皆 `<ChromeHeader>` **繼承 page density**,2026-06-15 移除 lockDensity),高度由 primitive 內部套 `h-[var(--chrome-header-height)]`(`chrome-header.tsx`),tsx 不硬寫 height className。**跨家族 SSOT pointer**:FileViewer Toolbar + InfoPanel header 屬 **Chrome header(Fixed-h)家族**,border / padding / dismiss size / withTabs 跨家族契約 SSOT 詳 `patterns/header-canonical/header-canonical.spec.md`。FileViewer **不鎖 density**(全 surface 繼承 page;原 `lockDensity="lg"` 只鎖兩個 header、沒鎖 body → header(px-loose@lg=24)與 body(px-loose@page=16)左緣不對齊 = 圖二 bug,已移除,見 `density.spec.md` 消費者清單)
104
104
  - **Viewport `flex-1`**——填滿剩餘空間;InfoPanel 透過 `w-80 shrink-0` 從右側切出,不吃 viewport 自然寬
105
105
  - **Filmstrip 固定 h-24**——預留 thumb 64 + padding;只在 `showFilmstrip && files.length > 1` 時顯示
106
106
  - **Prev/Next arrows 絕對定位**——避免 layout shift,只在 `files.length > 1` 渲染
@@ -244,7 +244,7 @@ Shell 看到 `pageNumber` capability 時自動在 toolbar 顯示 page navigator(
244
244
 
245
245
  ### 同 flex 列幾何鐵律(CLAUDE.md 規則)
246
246
 
247
- `[−]` / `[%input]` / `[+]` 三個 slot **都是 h-field-sm**,統一高度確保 gap 不被 hover bg 吃掉。Toolbar 包在 `<ChromeHeader lockDensity="lg">`(`file-viewer.tsx:328`)內,subtree density = lg → h-field-sm 解析為 **32px**(對齊本 spec「Density」段 L313 + `uiSize.spec.md`:sm 在 md density = 28px / lg density = 32px)。Button iconOnly size="sm" aspect-square 32×32,Input size="sm" 32 高,視覺嚴格對齊。
247
+ `[−]` / `[%input]` / `[+]` 三個 slot **都是 h-field-sm**,統一高度確保 gap 不被 hover bg 吃掉。Toolbar 包在 `<ChromeHeader>`(繼承 page density,2026-06-15 移除 lockDensity)內 → h-field-sm page density 解析(`uiSize.spec.md`:sm 在 md = 28px / lg = 32px)。Button iconOnly size="sm" aspect-square Input size="sm" 同高,視覺嚴格對齊。
248
248
 
249
249
  ### Why inline(不抽獨立 primitive)
250
250
 
@@ -255,7 +255,7 @@ Shell 看到 `pageNumber` capability 時自動在 toolbar 顯示 page navigator(
255
255
  ## InfoPanel 規則
256
256
 
257
257
  - **寬度固定 w-80(320px)**——對齊 Figma right panel(320)的業界慣例;Google Photos 用 360 偏寬,FileViewer 走 Figma 偏窄以讓 viewport 多一些空間 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
258
- - **Header 高度 = `--chrome-header-height`**(56px @ viewer lockDensity="lg")——與 Toolbar 等高,視覺對齊;消費 `<ChromeHeader lockDensity="lg">`(`file-viewer.tsx:459`)
258
+ - **Header 高度 = `--chrome-header-height`**( page density:48 md / 56 lg)——與 Toolbar 等高,視覺對齊;消費 `<ChromeHeader>`(繼承 page,2026-06-15 移除 lockDensity)
259
259
  - **內容分兩區**:
260
260
  - 「說明」Textarea(可編輯 / readOnly 依 `readOnly` prop)
261
261
  - 「檔案資訊」`<dl>`:檔名 / 類型 / 大小 / 自訂 metadata 條目
@@ -310,7 +310,7 @@ Shell 看到 `pageNumber` capability 時自動在 toolbar 顯示 page navigator(
310
310
 
311
311
  **Dark mode**:FileViewer chrome 鎖 dark(`data-theme="dark"` subtree);背景頁面的 theme 不影響 viewer chrome——viewer 是獨立沉浸式 context,類似 Tooltip / 全螢幕影片播放器的 convention。
312
312
 
313
- **Density**:FileViewer chrome `<ChromeHeader lockDensity="lg">`(`file-viewer.tsx:328,459` Toolbar + InfoPanel header),強制 lg-equivalent chrome-header-height = 56px;Filmstrip `h-24`(96px)+ thumb 64×64 屬媒體展示框尺寸,**刻意不隨 density 放大**(viewer 是展示殼不是工作區)。Toolbar 內的 `<Button size="sm">` 與 `<ZoomInput h-field-sm>` 會隨 density 微調(sm 在 md density = 28px,lg density = 32px),在 viewer 這個尺度可忽略。
313
+ **Density**:FileViewer **全 surface 繼承 page density**(2026-06-15 移除 lockDensity 原本只鎖兩個 ChromeHeader、沒鎖 body → header(px-loose@lg=24) body(px-loose@page=16)左緣不對齊,圖二 bug)。chrome-header-height page(48 md / 56 lg),header 與 body 同密度 → 左緣對齊;Filmstrip `h-24`(96px)+ thumb 64×64 屬媒體展示框尺寸,**刻意不隨 density 放大**(viewer 是展示殼不是工作區)。Toolbar `<Button size="sm">` 與 `<ZoomInput h-field-sm>` page density(sm 在 md = 28px / lg = 32px)
314
314
 
315
315
  ---
316
316
 
@@ -338,7 +338,9 @@ const Toolbar: React.FC<ToolbarProps> = ({
338
338
  }) => {
339
339
  return (
340
340
  <ChromeHeader
341
- lockDensity="lg"
341
+ // Density:繼承 page(2026-06-15 canonical)。原 lockDensity="lg" 只鎖在 header、沒鎖 body →
342
+ // InfoPanel header(px-loose@lg=24)與其 body(px-loose@page=16)左緣不對齊(圖二 bug)。移除 lock
343
+ // → header + body 全繼承 page density,左緣對齊;FileViewer 全 surface 同一密度(non-special)。
342
344
  className={cn(
343
345
  // Chrome layer — `bg-surface-raised` 對齊 token semantic「遮蓋型浮層必須不透明」。
344
346
  // FileViewer 整體是 overlay,chrome 屬其 raised surface(同 DropdownMenuContent line 244)。
@@ -467,8 +469,9 @@ const InfoPanel: React.FC<InfoPanelProps> = ({
467
469
  )}
468
470
  aria-label={labels.detailPanel}
469
471
  >
470
- {/* Panel header — 與 Toolbar 等高(consume ChromeHeader lockDensity="lg"),視覺一致 */}
471
- <ChromeHeader lockDensity="lg" className="justify-between">
472
+ {/* Panel header — 與 Toolbar ChromeHeader,繼承 page density(2026-06-15:移除 lockDensity,
473
+ header(原 px-loose@lg=24)與 body(px-loose@page=16)左緣不對齊 = 圖二 bug;全 surface 同密度)。 */}
474
+ <ChromeHeader className="justify-between">
472
475
  <h3 className="text-body-lg font-medium text-foreground">{labels.detailsHeading}</h3>
473
476
  {/* InfoPanel close 走 dismiss canonical `<Button iconOnly dismiss />`,對齊 button.spec.md
474
477
  「Dismiss 視覺類」+ inline-action.spec.md「Dismiss canonical — X close only」。 */}
@@ -53,10 +53,10 @@ const VARIANT_ICON: Record<NoticeVariant, LucideIcon | null> = {
53
53
 
54
54
  const SUBTLE_CONTAINER: Record<NoticeVariant, string> = {
55
55
  neutral: 'bg-muted border border-border',
56
- info: 'bg-info-subtle border border-[var(--info-hover)]',
57
- success: 'bg-success-subtle border border-[var(--success-hover)]',
58
- warning: 'bg-warning-subtle border border-[var(--warning-hover)]',
59
- error: 'bg-error-subtle border border-[var(--error-hover)]',
56
+ info: 'bg-info-subtle border border-[var(--info-text)]',
57
+ success: 'bg-success-subtle border border-[var(--success-text)]',
58
+ warning: 'bg-warning-subtle border border-[var(--warning-text)]',
59
+ error: 'bg-error-subtle border border-[var(--error-text)]',
60
60
  }
61
61
 
62
62
  function SubtleShell({ variant, children }: { variant: NoticeVariant; children: React.ReactNode }) {
@@ -154,4 +154,5 @@ Notice **不**自帶 Esc-to-dismiss 行為(`notice.tsx` 無 keydown handler);dis
154
154
  - `alert.spec.md`
155
155
  - `bulk-action-bar.spec.md`
156
156
  - `coachmark.spec.md`
157
+ - `item-anatomy.spec.md`
157
158
  - `toast.spec.md`
@@ -26,10 +26,10 @@ type Story = StoryObj<typeof Notice>
26
26
 
27
27
  const SUBTLE_CONTAINER: Record<NoticeVariant, string> = {
28
28
  neutral: 'bg-muted border border-border',
29
- info: 'bg-info-subtle border border-[var(--info-hover)]',
30
- success: 'bg-success-subtle border border-[var(--success-hover)]',
31
- warning: 'bg-warning-subtle border border-[var(--warning-hover)]',
32
- error: 'bg-error-subtle border border-[var(--error-hover)]',
29
+ info: 'bg-info-subtle border border-[var(--info-text)]',
30
+ success: 'bg-success-subtle border border-[var(--success-text)]',
31
+ warning: 'bg-warning-subtle border border-[var(--warning-text)]',
32
+ error: 'bg-error-subtle border border-[var(--error-text)]',
33
33
  }
34
34
 
35
35
  const SOLID_BG: Record<Exclude<NoticeVariant, 'neutral'>, string> = {
@@ -2,6 +2,8 @@
2
2
  import type { Meta, StoryObj } from '@storybook/react'
3
3
  import { Popover, PopoverTrigger, PopoverContent, PopoverHeader, PopoverBody, PopoverFooter, PopoverTitle } from './popover'
4
4
  import { Button } from '@/design-system/components/Button/button'
5
+ import { Checkbox } from '@/design-system/components/Checkbox/checkbox'
6
+ import { CheckboxGroup } from '@/design-system/components/Checkbox/checkbox-group'
5
7
  import { H3, Desc, Td, Th, TokenCell } from '@/design-system/stories-helpers/anatomy/anatomy-utils'
6
8
 
7
9
  const meta: Meta = {
@@ -26,11 +28,11 @@ export const Overview: Story = {
26
28
  <PopoverContent>
27
29
  <PopoverHeader><PopoverTitle>依類型篩選</PopoverTitle></PopoverHeader>
28
30
  <PopoverBody>
29
- <div className="flex flex-col gap-1.5 text-caption text-fg-secondary">
30
- <label className="flex items-center gap-2"><input type="checkbox" defaultChecked /> 待處理</label>
31
- <label className="flex items-center gap-2"><input type="checkbox" defaultChecked /> 進行中</label>
32
- <label className="flex items-center gap-2"><input type="checkbox" /> 已完成</label>
33
- </div>
31
+ <CheckboxGroup>
32
+ <Checkbox defaultChecked label="待處理" />
33
+ <Checkbox defaultChecked label="進行中" />
34
+ <Checkbox label="已完成" />
35
+ </CheckboxGroup>
34
36
  </PopoverBody>
35
37
  <PopoverFooter>
36
38
  <Button variant="tertiary" size="sm" className="flex-1">清除</Button>
@@ -129,11 +131,11 @@ export const Inspector: Story = {
129
131
  <PopoverHeader><PopoverTitle>依類型篩選</PopoverTitle></PopoverHeader>
130
132
  )}
131
133
  <PopoverBody>
132
- <div className="flex flex-col gap-1.5 text-caption text-fg-secondary">
133
- <label className="flex items-center gap-2"><input type="checkbox" defaultChecked /> 待處理</label>
134
- <label className="flex items-center gap-2"><input type="checkbox" defaultChecked /> 進行中</label>
135
- <label className="flex items-center gap-2"><input type="checkbox" /> 已完成</label>
136
- </div>
134
+ <CheckboxGroup>
135
+ <Checkbox defaultChecked label="待處理" />
136
+ <Checkbox defaultChecked label="進行中" />
137
+ <Checkbox label="已完成" />
138
+ </CheckboxGroup>
137
139
  </PopoverBody>
138
140
  {showFooter && (
139
141
  <PopoverFooter>
@@ -194,7 +196,7 @@ export const ColorMatrix: Story = {
194
196
  <tr><Td>次要文字</Td><Td><TokenCell token="--fg-secondary" display="text-fg-secondary" /></Td><Td>說明 / 次要資訊</Td></tr>
195
197
  <tr><Td>圓角</Td><Td mono>rounded-lg(8px)</Td><Td>對齊 Dialog radius(浮層視覺語言一致)</Td></tr>
196
198
  <tr><Td>陰影</Td><Td mono>--elevation-200</Td><Td>浮層級 elevation(見 `elevation.spec.md`)</Td></tr>
197
- <tr><Td>Density</Td><Td mono>data-density="md"</Td><Td>Popover 永遠鎖 md,不隨頁面 density 放大(lightweight)</Td></tr>
199
+ <tr><Td>Layout-space</Td><Td mono>data-layout-space="md"</Td><Td>只鎖版面間距(header py-tight 精簡);ui-size 跟 page → 內部控件對齊觸發點(2026-06-15 改,原 data-density master switch)</Td></tr>
198
200
  <tr><Td>Header/Body/Footer padding</Td><Td mono>px-[loose] py-[tight]</Td><Td>結構化 sub-components 採 Dialog 同一套 padding token</Td></tr>
199
201
  <tr><Td>Portal z-index</Td><Td mono>z-50</Td><Td>Radix Portal 統一層級,避開一般頁面內容</Td></tr>
200
202
  </tbody>