@qijenchen/design-system 0.1.0-beta.67 → 0.1.0-beta.69
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/dist/components/Alert/alert.d.ts.map +1 -1
- package/dist/components/Alert/alert.js +4 -4
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +0 -1
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +1 -2
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/Popover/popover.d.ts +1 -1
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +9 -10
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +11 -3
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/tooltip.js +0 -1
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/patterns/overlay-surface/index.js +2 -1
- package/dist/patterns/overlay-surface/overlay-surface.d.ts +8 -0
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js +3 -1
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/ds-canonical/hooks/tests/test_check_story_invariants.sh +1 -1
- package/ds-canonical/templates/dashboard-app.tsx +9 -4
- package/llms-full.txt +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/src/components/Alert/alert.spec.md +1 -1
- package/src/components/Alert/alert.tsx +7 -4
- package/src/components/Button/button.spec.md +2 -2
- package/src/components/Checkbox/checkbox.principles.stories.tsx +18 -15
- package/src/components/Coachmark/coachmark.principles.stories.tsx +3 -2
- package/src/components/DataTable/data-table-filter-panel.tsx +3 -3
- package/src/components/DataTable/data-table-sort-manager.tsx +3 -3
- package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -1
- package/src/components/Dialog/dialog.spec.md +11 -11
- package/src/components/Dialog/dialog.tsx +7 -8
- package/src/components/DropdownMenu/dropdown-menu.tsx +4 -1
- package/src/components/FileItem/file-item.spec.md +1 -1
- package/src/components/FileItem/file-item.stories.tsx +3 -3
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +4 -4
- package/src/components/FileViewer/file-viewer.spec.md +4 -4
- package/src/components/FileViewer/file-viewer.tsx +6 -3
- package/src/components/Notice/notice.anatomy.stories.tsx +4 -4
- package/src/components/Notice/notice.spec.md +1 -0
- package/src/components/Notice/notice.stories.tsx +4 -4
- package/src/components/Popover/popover.anatomy.stories.tsx +13 -11
- package/src/components/Popover/popover.principles.stories.tsx +10 -8
- package/src/components/Popover/popover.spec.md +2 -2
- package/src/components/Popover/popover.tsx +14 -11
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +3 -2
- package/src/components/Sheet/sheet.principles.stories.tsx +5 -4
- package/src/components/Sidebar/sidebar.spec.md +2 -0
- package/src/components/Steps/steps.tsx +11 -3
- package/src/components/Tooltip/tooltip.tsx +3 -1
- package/src/patterns/header-canonical/header-canonical.spec.md +3 -2
- package/src/patterns/overlay-surface/overlay-surface.spec.md +12 -10
- package/src/patterns/overlay-surface/overlay-surface.tsx +20 -8
- package/src/tokens/density/density.spec.md +33 -22
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +4 -4
|
@@ -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
|
|
112
|
-
<SurfaceHeader className=
|
|
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
|
|
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-
|
|
61
|
+
## Density(2026-06-16 定論:全繼承 page,不鎖)
|
|
62
62
|
|
|
63
|
-
Dialog
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
103
|
-
- Header 只有 title + close X → max layout = 24 → header = 24 + 2
|
|
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
|
|
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
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
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
|
-
|
|
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
|
|
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=
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
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>消費 <ChromeHeader
|
|
701
|
+
<Td>消費 <ChromeHeader>(繼承 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**(
|
|
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
|
|
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`**(
|
|
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
|
|
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
|
|
471
|
-
|
|
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-
|
|
57
|
-
success: 'bg-success-subtle border border-[var(--success-
|
|
58
|
-
warning: 'bg-warning-subtle border border-[var(--warning-
|
|
59
|
-
error: 'bg-error-subtle border border-[var(--error-
|
|
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 }) {
|
|
@@ -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-
|
|
30
|
-
success: 'bg-success-subtle border border-[var(--success-
|
|
31
|
-
warning: 'bg-warning-subtle border border-[var(--warning-
|
|
32
|
-
error: 'bg-error-subtle border border-[var(--error-
|
|
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
|
-
<
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
<label
|
|
33
|
-
</
|
|
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
|
-
<
|
|
133
|
-
<
|
|
134
|
-
<
|
|
135
|
-
<label
|
|
136
|
-
</
|
|
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>
|
|
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>
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from '@/design-system/components/Tooltip/tooltip'
|
|
35
35
|
import { Button } from '@/design-system/components/Button/button'
|
|
36
36
|
import { Checkbox } from '@/design-system/components/Checkbox/checkbox'
|
|
37
|
+
import { CheckboxGroup } from '@/design-system/components/Checkbox/checkbox-group'
|
|
37
38
|
|
|
38
39
|
const meta: Meta = {
|
|
39
40
|
title: 'Design System/Components/Popover/設計原則',
|
|
@@ -95,10 +96,11 @@ export const UsageGuidance: Story = {
|
|
|
95
96
|
<PopoverContent align="start">
|
|
96
97
|
<PopoverHeader><PopoverTitle>依狀態篩選</PopoverTitle></PopoverHeader>
|
|
97
98
|
<PopoverBody>
|
|
98
|
-
|
|
99
|
+
{/* CheckboxGroup zero-gap canonical(checkbox.spec.md L225)— 非手刻 grid div */}
|
|
100
|
+
<CheckboxGroup>
|
|
99
101
|
<Checkbox defaultChecked label="進行中" />
|
|
100
102
|
<Checkbox label="已完成" />
|
|
101
|
-
</
|
|
103
|
+
</CheckboxGroup>
|
|
102
104
|
</PopoverBody>
|
|
103
105
|
</PopoverContent>
|
|
104
106
|
</Popover>
|
|
@@ -168,12 +170,12 @@ export const UsageGuidance: Story = {
|
|
|
168
170
|
<PopoverContent align="start" className="w-80">
|
|
169
171
|
<PopoverHeader><PopoverTitle>進階篩選</PopoverTitle></PopoverHeader>
|
|
170
172
|
<PopoverBody>
|
|
171
|
-
<
|
|
173
|
+
<CheckboxGroup>
|
|
172
174
|
<Checkbox defaultChecked label="我指派的" />
|
|
173
175
|
<Checkbox label="我建立的" />
|
|
174
176
|
<Checkbox label="我追蹤的" />
|
|
175
|
-
|
|
176
|
-
|
|
177
|
+
</CheckboxGroup>
|
|
178
|
+
<div className="border-t border-divider pt-3 mt-1 text-caption text-fg-muted">最多 3 個條件</div>
|
|
177
179
|
</PopoverBody>
|
|
178
180
|
<PopoverFooter>
|
|
179
181
|
<Button variant="tertiary" size="sm" className="flex-1">清除</Button>
|
|
@@ -217,10 +219,10 @@ export const UsageGuidance: Story = {
|
|
|
217
219
|
</PopoverTrigger>
|
|
218
220
|
<PopoverContent align="start">
|
|
219
221
|
<PopoverBody>
|
|
220
|
-
<
|
|
222
|
+
<CheckboxGroup>
|
|
221
223
|
<Checkbox defaultChecked label="我的任務" />
|
|
222
224
|
<Checkbox label="全部" />
|
|
223
|
-
</
|
|
225
|
+
</CheckboxGroup>
|
|
224
226
|
</PopoverBody>
|
|
225
227
|
</PopoverContent>
|
|
226
228
|
</Popover>
|
|
@@ -251,7 +253,7 @@ export const VisualAlignmentRule: Story = {
|
|
|
251
253
|
<div>
|
|
252
254
|
<Rule
|
|
253
255
|
title="Popover 與 Dialog 共用 overlay-surface 視覺語言"
|
|
254
|
-
note="bg-surface-raised / border-border / rounded-lg / elevation-200 完全一致。Header / Body / Footer 內 padding 來自 overlay-surface pattern 主檔(px-loose py-tight)
|
|
256
|
+
note="bg-surface-raised / border-border / rounded-lg / elevation-200 完全一致。Header / Body / Footer 內 padding 來自 overlay-surface pattern 主檔(px-loose py-tight)。差異:(1) Popover 是 non-modal 無 overlay 遮罩,(2) Popover 鎖 layout-space=md(header 精簡)、Dialog 鎖 layout-space=lg(寬鬆呼吸),兩者 ui-size 都繼承 page"
|
|
255
257
|
>
|
|
256
258
|
<Popover>
|
|
257
259
|
<PopoverTrigger asChild>
|
|
@@ -62,9 +62,9 @@ Popover 是**點擊觸發的浮層容器**——提供定位、動畫、焦點
|
|
|
62
62
|
|
|
63
63
|
> **跨家族 SSOT pointer**:PopoverHeader 屬 **Padding-based overlay header 家族**;border / padding / dismiss size / withTabs(tabs 進 PopoverHeader 時 border auto-suppress + tabs size sm)的跨家族視覺契約 SSOT 詳 `patterns/header-canonical/header-canonical.spec.md`。本節僅 codify Popover 特有 close X v5 unbounded canonical。
|
|
64
64
|
|
|
65
|
-
**Close X 按鈕(2026-04-22 v5 unbounded canonical)**:所有 `PopoverHeader` 內建右上 X 按鈕,`<Button data-dismiss iconOnly dismiss size="sm" />` native sm(28 md / 32 lg)。透過 SurfaceHeader 的 v5 CSS rule 自動套負 margin 讓 layout 佔位;PopoverHeader
|
|
65
|
+
**Close X 按鈕(2026-04-22 v5 unbounded canonical)**:所有 `PopoverHeader` 內建右上 X 按鈕,`<Button data-dismiss iconOnly dismiss size="sm" />` native sm(28 md / 32 lg)。透過 SurfaceHeader 的 v5 CSS rule 自動套負 margin 讓 layout 佔位;PopoverHeader 走 `COMPACT_HEADER_SLOT`(= calc(--font-body-size×1.5) = 21 = title line-box,衍生)→ 佔位 21(輕量 chrome,見 `popover.tsx` docblock),非 Dialog 的 24。`hideClose` prop 可讓 composition 元件(如 Coachmark)選擇隱藏。詳 `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」。
|
|
66
66
|
|
|
67
|
-
**Header / Footer 高度 canonical**:padding-based(繼承 SurfaceHeader / SurfaceFooter),高度 = max(child layout) + 2×tight。header:slot 佔位
|
|
67
|
+
**Header / Footer 高度 canonical**:padding-based(繼承 SurfaceHeader / SurfaceFooter),高度 = max(child layout) + 2×tight。header:slot 佔位 21(`COMPACT_HEADER_SLOT` = title line-box,衍生)→ max(21 title, 21 slot) + 2×12 = **45**,自然比 Dialog / Sheet 48 輕一級(`popover.tsx` PopoverHeader docblock + overlay-surface.tsx 對照);footer:無 override,unbounded 佔位 24 → 48(若只有 unbounded)或自然長(若有 bounded)。詳 `tokens/uiSize/uiSize.spec.md`「Chrome header 選型 canonical」。
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
@@ -4,7 +4,7 @@ import * as PopoverPrimitive from "@radix-ui/react-popover"
|
|
|
4
4
|
import { X as XIcon } from "lucide-react"
|
|
5
5
|
|
|
6
6
|
import { cn } from "@/lib/utils"
|
|
7
|
-
import { SurfaceHeader, SurfaceBody, SurfaceFooter } from "@/design-system/patterns/overlay-surface/overlay-surface"
|
|
7
|
+
import { SurfaceHeader, SurfaceBody, SurfaceFooter, COMPACT_HEADER_SLOT } from "@/design-system/patterns/overlay-surface/overlay-surface"
|
|
8
8
|
import { Button } from "@/design-system/components/Button/button"
|
|
9
9
|
import { OVERLAY_SIDE_OFFSET, OVERLAY_COLLISION_PADDING } from "@/design-system/tokens/elevation/overlay-geometry"
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@ import { OVERLAY_SIDE_OFFSET, OVERLAY_COLLISION_PADDING } from "@/design-system/
|
|
|
13
13
|
*
|
|
14
14
|
* ── 視覺 ──
|
|
15
15
|
* 與 Dialog 對齊:bg-surface-raised / rounded-lg / border-border / elevation-200。
|
|
16
|
-
*
|
|
16
|
+
* layout-space 鎖 md(輕量浮層 header py-tight 保持精簡);ui-size 繼承 page(內部控件對齊觸發點,2026-06-15 改,原 data-density master switch)。
|
|
17
17
|
*
|
|
18
18
|
* ── 結構 ──
|
|
19
19
|
* PopoverContent:外殼(bg / border / radius / shadow / density),無內距。
|
|
@@ -56,7 +56,11 @@ const PopoverContent = React.forwardRef<
|
|
|
56
56
|
align={align}
|
|
57
57
|
sideOffset={sideOffset}
|
|
58
58
|
collisionPadding={collisionPadding}
|
|
59
|
-
data-density="md"
|
|
59
|
+
// Layout-space lock(2026-06-15 canonical,原 data-density="md" 改為只鎖 layout-space):Popover 是
|
|
60
|
+
// 輕量浮層,header/footer 用 py-tight(layout-space)→ 鎖 layout-space=md 保持精簡 padding;但
|
|
61
|
+
// **ui-size 不鎖、繼承 page** → 內部 field 控件 / dismiss 按鈕隨 page 放大,跟觸發點一致(decouple)。
|
|
62
|
+
// [data-layout-space="md"] 有 reset selector(layoutSpace.css L31)→ lg page 上正確 reset 回 md。
|
|
63
|
+
data-layout-space="md"
|
|
60
64
|
onOpenAutoFocus={onOpenAutoFocus ?? handlePopoverOpenAutoFocus}
|
|
61
65
|
className={cn(
|
|
62
66
|
"z-50 w-72 rounded-lg border border-border bg-surface-raised text-foreground shadow-[var(--elevation-200)] outline-none",
|
|
@@ -86,20 +90,19 @@ interface PopoverHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
86
90
|
|
|
87
91
|
const PopoverHeader = React.forwardRef<HTMLDivElement, PopoverHeaderProps>(
|
|
88
92
|
({ className, children, hideClose = false, ...props }, ref) => (
|
|
89
|
-
// Popover lightweight chrome canonical(2026-05-04 重思 v2):
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
// max(21 title,
|
|
93
|
-
// Q10 穩定:title-only / title+close 都 = title + py
|
|
94
|
-
// 無 min-h / 無 py override — 修正前一版過度設計。
|
|
93
|
+
// Popover lightweight chrome canonical(2026-05-04 重思 v2;2026-06-16 slot 改衍生 + 收斂 SSOT):
|
|
94
|
+
// 走 `COMPACT_HEADER_SLOT`(= calc(--font-body-size×1.5) = 14×1.5 = 21 = PopoverTitle text-body line-box,
|
|
95
|
+
// 衍生非寫死,取代舊各自寫死的 1.25rem / 20 巧合值)→ slot = title 行高,title 字級改 slot 自動跟。
|
|
96
|
+
// Header 維持 padding-based:max(21 title, 21 slot) + py-tight(12*2) = 45 → 自然比 Dialog/Sheet 48 輕一級。
|
|
97
|
+
// Q10 穩定:title-only / title+close 都 = title + py 主導(slot=title)。無 min-h / 無 py override。
|
|
95
98
|
<SurfaceHeader
|
|
96
99
|
ref={ref}
|
|
97
|
-
className={cn("justify-between
|
|
100
|
+
className={cn("justify-between", COMPACT_HEADER_SLOT, className)}
|
|
98
101
|
{...props}
|
|
99
102
|
>
|
|
100
103
|
<div className="flex-1 min-w-0">{children}</div>
|
|
101
104
|
{!hideClose && (
|
|
102
|
-
// Dismiss X = native sm,SurfaceHeader 負 my trick 讓 layout
|
|
105
|
+
// Dismiss X = native sm,SurfaceHeader 負 my trick 讓 layout 佔位縮到 slot(21 = title line-box)
|
|
103
106
|
<PopoverPrimitive.Close asChild>
|
|
104
107
|
<Button data-dismiss iconOnly dismiss size="sm" startIcon={XIcon} aria-label="關閉" />
|
|
105
108
|
</PopoverPrimitive.Close>
|
|
@@ -609,8 +609,9 @@ export const SizeMatrix: Story = {
|
|
|
609
609
|
<H3>Size token 對照</H3>
|
|
610
610
|
<Desc>
|
|
611
611
|
size 傳遞到浮層內的 CommandInput / MenuItem,三者統一尺寸。RowSizeProvider 確保所有 slot 自動讀取正確
|
|
612
|
-
size,不需在每個 item
|
|
613
|
-
|
|
612
|
+
size,不需在每個 item 重設。size prop 決定 tier(sm/md/lg);實際像素隨 page density(2026-06-15:
|
|
613
|
+
PopoverContent 改鎖 `layout-space=md`、ui-size 繼承 page → 下拉項目跟觸發點 / 頁面 density 一致,
|
|
614
|
+
不再被鎖死 md-scale)。下表為 md page 的 field-height 值。
|
|
614
615
|
</Desc>
|
|
615
616
|
<div className="overflow-x-auto">
|
|
616
617
|
<table className="text-caption border-collapse">
|
|
@@ -33,6 +33,7 @@ import { Button } from '@/design-system/components/Button/button'
|
|
|
33
33
|
import { Field, FieldLabel } from '@/design-system/components/Field/field'
|
|
34
34
|
import { Input } from '@/design-system/components/Input/input'
|
|
35
35
|
import { Checkbox } from '@/design-system/components/Checkbox/checkbox'
|
|
36
|
+
import { CheckboxGroup } from '@/design-system/components/Checkbox/checkbox-group'
|
|
36
37
|
|
|
37
38
|
const meta: Meta = {
|
|
38
39
|
title: 'Design System/Components/Sheet/設計原則',
|
|
@@ -112,10 +113,10 @@ export const UsageGuidance: Story = {
|
|
|
112
113
|
</Field>
|
|
113
114
|
<Field>
|
|
114
115
|
<FieldLabel>通知</FieldLabel>
|
|
115
|
-
<
|
|
116
|
+
<CheckboxGroup>
|
|
116
117
|
<Checkbox defaultChecked label="新任務" />
|
|
117
118
|
<Checkbox label="每日摘要" />
|
|
118
|
-
</
|
|
119
|
+
</CheckboxGroup>
|
|
119
120
|
</Field>
|
|
120
121
|
</SheetBody>
|
|
121
122
|
<SheetFooter>
|
|
@@ -180,10 +181,10 @@ export const UsageGuidance: Story = {
|
|
|
180
181
|
</PopoverTrigger>
|
|
181
182
|
<PopoverContent align="start">
|
|
182
183
|
<PopoverBody>
|
|
183
|
-
<
|
|
184
|
+
<CheckboxGroup>
|
|
184
185
|
<Checkbox defaultChecked label="進行中" />
|
|
185
186
|
<Checkbox label="已完成" />
|
|
186
|
-
</
|
|
187
|
+
</CheckboxGroup>
|
|
187
188
|
</PopoverBody>
|
|
188
189
|
</PopoverContent>
|
|
189
190
|
</Popover>
|
|
@@ -715,6 +715,8 @@ Item-level default / hover / selected / disabled **色彩**完全共用 item-ana
|
|
|
715
715
|
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
716
716
|
|
|
717
717
|
- `app-shell.spec.md`
|
|
718
|
+
- `density.spec.md`
|
|
719
|
+
- `header-canonical.spec.md`
|
|
718
720
|
- `scroll-area.spec.md`
|
|
719
721
|
- `sheet.spec.md`
|
|
720
722
|
- `tree-view.spec.md`
|
|
@@ -455,7 +455,11 @@ function StepItemHeader({ children, className, style }: { children: React.ReactN
|
|
|
455
455
|
onKeyDown={item.clickable ? onKeyDown : undefined}
|
|
456
456
|
aria-disabled={item.disabled || undefined}
|
|
457
457
|
className={cn(
|
|
458
|
-
|
|
458
|
+
// leading-compact:scanning-family header 行高 = 1.3(item-anatomy.spec.md:776 掃描模式 label 行高)。
|
|
459
|
+
// 設在 header 而非 li 根 → prefix h-[1lh] + 水平 connector h-[1lh] + label(StepLabel 亦 leading-compact)
|
|
460
|
+
// 全用 1.3 對齊;li 根 text-body(1.5,steps.tsx:329-331)留給展開 content 的 reading 行高,不被
|
|
461
|
+
// scanning 波及(避免 改A壞B)。對齊 MenuItem 把 leading-compact 放 row 容器之原則(item-anatomy.tsx:144-146)。
|
|
462
|
+
'outline-none leading-compact',
|
|
459
463
|
item.clickable
|
|
460
464
|
? 'cursor-pointer rounded-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
461
465
|
: 'cursor-not-allowed',
|
|
@@ -539,7 +543,9 @@ function VerticalConnectorLine() {
|
|
|
539
543
|
<div
|
|
540
544
|
aria-hidden
|
|
541
545
|
className={cn(
|
|
542
|
-
|
|
546
|
+
// leading-compact:connector 的 0.5lh 須跟 scanning label 同行高(1.3)→ 起點對齊 circle 中心
|
|
547
|
+
// (circle 對齊 label 第一行;label 已 leading-compact)。否則 connector 繼承 li 根 1.5 → 0.5lh 偏大 → 起點偏低。
|
|
548
|
+
'absolute w-px leading-compact',
|
|
543
549
|
isBlue ? 'bg-info' : 'bg-border',
|
|
544
550
|
)}
|
|
545
551
|
style={{
|
|
@@ -773,7 +779,9 @@ const StepLabel = React.forwardRef<HTMLSpanElement, StepLabelProps>(
|
|
|
773
779
|
<span
|
|
774
780
|
ref={ref}
|
|
775
781
|
className={cn(
|
|
776
|
-
|
|
782
|
+
// leading-compact:scanning-family label 行高 = 1.3(item-anatomy.spec.md:776);text-body utility
|
|
783
|
+
// 自帶 lh:1.5,須顯式 leading-compact 蓋回 1.3,跟 MenuItem label(實測 14px/18px=1.3)一致。
|
|
784
|
+
'font-medium break-words leading-compact',
|
|
777
785
|
size === 'lg' ? 'text-body-lg' : 'text-body',
|
|
778
786
|
disabled
|
|
779
787
|
? 'text-fg-disabled'
|
|
@@ -30,7 +30,9 @@ const TooltipContent = React.forwardRef<
|
|
|
30
30
|
ref={ref}
|
|
31
31
|
sideOffset={sideOffset}
|
|
32
32
|
collisionPadding={collisionPadding}
|
|
33
|
-
|
|
33
|
+
// Density:繼承 page density(2026-06-15 canonical)。Tooltip padding 寫死 px-3 py-2、內容 text-body,
|
|
34
|
+
// 不消費任何 density / layout-space token → 鎖 density 對它是 inert(原 data-density="md" 是 409b91da
|
|
35
|
+
// a11y 批次「對齊 Popover」順手加,非設計決策)→ 移除,讓全浮層行為一致(全繼承 page)。
|
|
34
36
|
className={cn(
|
|
35
37
|
"z-50 overflow-hidden rounded-md px-3 py-2 text-body font-normal text-on-emphasis bg-tooltip max-w-[280px]",
|
|
36
38
|
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 motion-reduce:animate-none motion-reduce:zoom-in-100",
|