@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
|
@@ -259,14 +259,14 @@ ChromeHeader / SurfaceHeader 新增 `tabsSlot?: ReactNode` prop。提供時自
|
|
|
259
259
|
<ChromeHeader
|
|
260
260
|
withTabs?: boolean // false (default) | true → auto-suppress border + delegate to TabsList
|
|
261
261
|
tabsSlot?: ReactNode // Tabs row(column mode + W1-W6 lockstep,2026-05-18 ship)
|
|
262
|
-
lockDensity?: 'inherit' | 'lg' // 'inherit' (default)
|
|
262
|
+
lockDensity?: 'inherit' | 'lg' // 'inherit' (default)。'lg' = 在 header 上設 data-density=lg;⚠️ 2026-06-15:density lock 應設在 surface 根而非 header(header 鎖、body 沒鎖 → 左緣不對齊,FileViewer 圖二教訓)→ 現無 consumer 使用,保留僅為 API 相容。見 density.spec.md 消費者清單 + anti-pattern
|
|
263
263
|
leadingRail?: ReactNode // 2026-05-21 ship per AppShell primary-header globalHeader:固定 width=sidebar-width-icon 的左 rail,內 justify-center 排列(toggle/logo 跟 sidebar 收合 icon center x 對齊)。對齊 GitHub global nav 左 logo 區 / Slack thin workspace rail 慣例。
|
|
264
264
|
>
|
|
265
265
|
{children}
|
|
266
266
|
</ChromeHeader>
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
**不開**:`density?: 'md'|'lg'` 自由 prop(M21 違反 — 任意 density 等於 cva-on-pattern,該由 density context 決定)。`lockDensity="lg"`
|
|
269
|
+
**不開**:`density?: 'md'|'lg'` 自由 prop(M21 違反 — 任意 density 等於 cva-on-pattern,該由 density context 決定)。`lockDensity="lg"` 原為 fullscreen viewer escape hatch,但 **2026-06-15 起無 consumer 使用** — FileViewer 改為全 surface 繼承 page density(原本只鎖兩個 header、沒鎖 body → 左緣不對齊,圖二 bug)。density lock 一律設在 **surface 根**而非 header primitive(見 `density.spec.md` anti-pattern)。其餘 consumer 走 `inherit`(跟 page density)。**Grep evidence**(historical 錨,migration 已完成):migration 前 `Sidebar` + `FileViewer Toolbar/InfoPanel` 各自手刻同一 signature `flex items-center gap-2 shrink-0 h-[var(--chrome-header-height)] border-b border-divider px-[var(--layout-space-loose)]`(production 重複 contract,非 premature abstraction)→ 過 M21 prop variant test 3 條(`<Sidebar>` 等近親 grep / 3 家世界級 page-header primitive / value 結構不同)→ 抽成本 primitive。**現況**:全部已消費 `<ChromeHeader>`(`sidebar.tsx:479` / `file-viewer.tsx:328` Toolbar + `:459` InfoPanel),grep 全 repo 已無此手刻 signature(除 `chrome-header.tsx` primitive 本身)。
|
|
270
270
|
|
|
271
271
|
### Layer 4 — Hook mechanical
|
|
272
272
|
|
|
@@ -313,6 +313,7 @@ ChromeHeader / SurfaceHeader 新增 `tabsSlot?: ReactNode` prop。提供時自
|
|
|
313
313
|
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
314
314
|
|
|
315
315
|
- `app-shell.spec.md`
|
|
316
|
+
- `density.spec.md`
|
|
316
317
|
- `dialog.spec.md`
|
|
317
318
|
- `file-viewer.spec.md`
|
|
318
319
|
- `item-anatomy.spec.md`
|
|
@@ -236,13 +236,13 @@ Overlay family 的 header title typography 依「modal vs non-modal」分級,**
|
|
|
236
236
|
|
|
237
237
|
## Size canonical(per-overlay default size SSOT,2026-05-04)
|
|
238
238
|
|
|
239
|
-
> **背景**:Popover / HoverCard 是「輕量浮層」(chrome 45 / slot
|
|
239
|
+
> **背景**:Popover / HoverCard 是「輕量浮層」(chrome 45 / slot 21 / title 小一級 / density 鎖 md);Dialog / Sheet 是「heavy commitment chrome」(chrome 48 / slot 24)。Per-surface body+footer button/field default size 跟「輕量 vs heavy」分化對應。
|
|
240
240
|
>
|
|
241
241
|
> **依據**:Linear / Notion / Airtable / Carbon / Material Menu / Atlassian Popup popover bodies 共識用 sm-density;Material AlertDialog / Carbon Modal / shadcn Dialog 共識用 md。world-class 雙派分明依「浮層密度」分。 <!-- @benchmark-unverified -->
|
|
242
242
|
|
|
243
243
|
| Overlay | Header chrome | Body field controls | Body inline action(drag handle / trash / +CTA)| Footer action Buttons |
|
|
244
244
|
|--|--|--|--|--|
|
|
245
|
-
| **Popover** | unbounded sm →
|
|
245
|
+
| **Popover** | unbounded sm → 21 slot(輕)| **sm**(輕量 dense info)| inline-action sm/md = **16+18** | **sm** |
|
|
246
246
|
| **HoverCard** | (同 Popover)| **sm** | 同 | **sm** |
|
|
247
247
|
| **Dialog** | unbounded sm → 24 slot | **md** | inline-action sm/md = 16+18 | **md** |
|
|
248
248
|
| **Sheet** | (同 Dialog)| **md** | 同 | **md** |
|
|
@@ -264,13 +264,13 @@ Overlay family 的 header title typography 依「modal vs non-modal」分級,**
|
|
|
264
264
|
|
|
265
265
|
## Chrome dismiss size canonical
|
|
266
266
|
|
|
267
|
-
**User 設計 insight**:header 的 padding-based sizing 在 **unbounded button**(text variant / dismiss,無 bg/border)場景視覺 padding 過大;在 **bounded button** 則剛好。解法 = **保持 button native size 不變(touch target / 視覺 render 都是 sm 原尺寸),但 layout
|
|
267
|
+
**User 設計 insight**:header 的 padding-based sizing 在 **unbounded button**(text variant / dismiss,無 bg/border)場景視覺 padding 過大;在 **bounded button** 則剛好。解法 = **保持 button native size 不變(touch target / 視覺 render 都是 sm 原尺寸),但 layout 佔位縮到 title line-box(24,衍生自 --font-body-lg-size×1.5)** via 負 margin。
|
|
268
268
|
|
|
269
269
|
**Canonical**:button native size **保留 sm**(touch target / 視覺 render 不動);**unbounded 的靠 CSS 負 my 把 layout 佔位縮到 `--chrome-slot-h`** ≤ title line-height,讓 title 主導 chrome 高度。
|
|
270
270
|
|
|
271
|
-
| Button 類型 | 判定(button.tsx L429-430)| Trick 套用 | Layout 佔位 | Dialog/Sheet header (slot 24)| Popover header (slot
|
|
271
|
+
| Button 類型 | 判定(button.tsx L429-430)| Trick 套用 | Layout 佔位 | Dialog/Sheet header (slot 24)| Popover header (slot 21)|
|
|
272
272
|
|--|--|--|--|--|--|
|
|
273
|
-
| **Unbounded** | `variant === 'text'` OR `dismiss` → `data-unbounded="true"` | ✓ 套負 my | = `var(--chrome-slot-h)` | max(24, 24) + py-tight = **48** | max(21,
|
|
273
|
+
| **Unbounded** | `variant === 'text'` OR `dismiss` → `data-unbounded="true"` | ✓ 套負 my | = `var(--chrome-slot-h)` | max(24, 24) + py-tight = **48** | max(21, 21) + py-tight = **45** |
|
|
274
274
|
| **Bounded** | `primary` / `tertiary` / `outline` 等(有 bg/border) | ✗ 不套 | = native sm (28 md / 32 lg) | max(24, 28) + py-tight = **52**(自然長)| max(21, 28) + py-tight = **52**(自然長)|
|
|
275
275
|
|
|
276
276
|
**為什麼差別**:Unbounded 沒視覺邊界 → native 28 是純 hit-target padding,縮 layout 不損視覺;Bounded 的 bg/border 就是內容,縮會切掉 → 必須讓 chrome 自然長高,**這是設計宣告「此 chrome 有重要 action,視覺重量該配得上」**。
|
|
@@ -281,13 +281,15 @@ Overlay family 的 header title typography 依「modal vs non-modal」分級,**
|
|
|
281
281
|
// button.tsx ── 自動標記 data-unbounded
|
|
282
282
|
const unboundedAttr = resolvedVariant === 'text' || dismiss ? { 'data-unbounded': 'true' } : {}
|
|
283
283
|
|
|
284
|
-
// overlay-surface.tsx ── slot 透過 CSS var 參數化(2026-05-04 v3
|
|
284
|
+
// overlay-surface.tsx ── slot 透過 CSS var 參數化(2026-05-04 v3;2026-06-16 default 改衍生)
|
|
285
285
|
const CHROME_UNBOUNDED_SLOT =
|
|
286
|
-
'[&_[data-unbounded]]:my-[calc((var(--chrome-slot-h,var(--
|
|
286
|
+
'[&_[data-unbounded]]:my-[calc((var(--chrome-slot-h,calc(var(--font-body-lg-size)*1.5))-var(--field-height-sm))/2)]'
|
|
287
287
|
|
|
288
|
-
// SurfaceHeader default → slot =
|
|
289
|
-
//
|
|
290
|
-
//
|
|
288
|
+
// SurfaceHeader default → slot 衍生自 title typography = calc(--font-body-lg-size × 1.5) = 24
|
|
289
|
+
// (2026-06-16:從寫死 field-height-xs 改衍生 → title 字級改了 slot 自動跟、不靠巧合;M17 SSOT;
|
|
290
|
+
// 1.5 = typography.spec body 行高常數,非 token 故 inline,body 行高改須同步)
|
|
291
|
+
// Popover-tier override → COMPACT_HEADER_SLOT(= calc(--font-body-size × 1.5) = 21,衍生自 text-body title)
|
|
292
|
+
// 公式 = (slot - native) / 2 ── Dialog md: (24-28)/2 = -2px / Popover md: (21-28)/2 = -3.5px
|
|
291
293
|
```
|
|
292
294
|
|
|
293
295
|
**覆蓋範圍**:
|
|
@@ -51,18 +51,30 @@ import { HEADER_TABS_SLOT_WRAPPER_CLASS } from '@/design-system/patterns/header-
|
|
|
51
51
|
// 時標記)的 **native size 不變**(sm: 28 md / 32 lg),但 **layout 佔位** via 負 `my` 縮成 `--chrome-slot-h`。
|
|
52
52
|
//
|
|
53
53
|
// **Slot height = header title 的 line-height**(讓 button 不 dominate,title 自然撐 header)
|
|
54
|
-
// - Default
|
|
55
|
-
//
|
|
54
|
+
// - Default 衍生自 title typography:`calc(var(--font-body-lg-size)*1.5)` = 16×1.5 = 24(Dialog/Sheet title = text-body-lg)。
|
|
55
|
+
// 2026-06-16:從寫死 `var(--field-height-xs)`(巧合也=24)改為衍生 → title 字級若改,slot 自動跟、不靠巧合(M17 SSOT;
|
|
56
|
+
// 1.5 = typography.spec body 行高常數,非 token 故 inline,若 body 行高改 1.3 須同步此處)。
|
|
57
|
+
// - Popover-tier(title = text-body)走 `COMPACT_HEADER_SLOT`(見下)= calc(--font-body-size×1.5) = 14×1.5 = 21
|
|
58
|
+
// (= PopoverTitle text-body line-box,衍生非寫死;2026-06-16 從各自寫死 1.25rem/20 巧合值收斂進 SSOT)
|
|
56
59
|
//
|
|
57
|
-
// Header 永遠 padding-based(無 min-h),但因 slot
|
|
60
|
+
// Header 永遠 padding-based(無 min-h),但因 slot = title line-box,header 高度由 title 主導:
|
|
58
61
|
// - Dialog: max(24 title, 24 slot) + py-tight(12*2)= 48 ✓ chrome-header-height
|
|
59
|
-
// - Popover: max(21 title,
|
|
60
|
-
// Q10 穩定性:title-only / title+button / refresh in/out 全 case header 高度 = title + py(slot
|
|
62
|
+
// - Popover: max(21 title, 21 slot) + py-tight(12*2)= 45 ✓ 自然輕量
|
|
63
|
+
// Q10 穩定性:title-only / title+button / refresh in/out 全 case header 高度 = title + py(slot=title,不超過)
|
|
61
64
|
//
|
|
62
65
|
// 負 my 公式:(slot - native) / 2,density-aware:
|
|
63
66
|
// Dialog md: (24 - 28) / 2 = -2px; lg: (24 - 32) / 2 = -4px
|
|
64
|
-
// Popover md: (
|
|
65
|
-
const CHROME_UNBOUNDED_SLOT = '[&_[data-unbounded]]:my-[calc((var(--chrome-slot-h,var(--
|
|
67
|
+
// Popover md: (21 - 28) / 2 = -3.5px(sub-pixel,可接受)
|
|
68
|
+
const CHROME_UNBOUNDED_SLOT = '[&_[data-unbounded]]:my-[calc((var(--chrome-slot-h,calc(var(--font-body-lg-size)*1.5))-var(--field-height-sm))/2)]'
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 輕量浮層 header 的 slot override —— **SSOT,禁各自 inline**(2026-06-16 收斂)。
|
|
72
|
+
* Popover-tier header(title = PopoverTitle `text-body`)的 slot = body line-box = `calc(--font-body-size×1.5)` = 21,
|
|
73
|
+
* 衍生自 title typography → title 字級改,slot 自動跟(取代舊各自寫死的 1.25rem / 20 巧合值)。
|
|
74
|
+
* 消費者(同設計邏輯,皆 raw SurfaceHeader + PopoverTitle):PopoverHeader / DataTable sort·filter panel /
|
|
75
|
+
* FileItem upload-manager。Default slot(Dialog/Sheet,title = text-body-lg)= 24,在 CHROME_UNBOUNDED_SLOT fallback。
|
|
76
|
+
*/
|
|
77
|
+
export const COMPACT_HEADER_SLOT = '[--chrome-slot-h:calc(var(--font-body-size)*1.5)]'
|
|
66
78
|
|
|
67
79
|
export interface SurfaceHeaderProps
|
|
68
80
|
extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -136,7 +148,7 @@ export const SurfaceHeader = React.forwardRef<
|
|
|
136
148
|
|
|
137
149
|
// Padding-based(預設) — Dialog/Sheet 用 body-lg title (16/24),自然撐 max(24 title, 24 button slot) = 24
|
|
138
150
|
// → header = 24 + py-tight 12×2 = 48 chrome-header-height ✓ 穩定無需 min-h
|
|
139
|
-
// Popover 等輕量 chrome 走
|
|
151
|
+
// Popover 等輕量 chrome 走 COMPACT_HEADER_SLOT(slot = calc(--font-body-size×1.5)=21 = title line-box,無 min-h / 無 py override):max(21 title, 21 slot) + py-tight 12×2 = 45(見上方 L57)
|
|
140
152
|
//
|
|
141
153
|
// withTabs=true(無 tabsSlot,backward compat):移除 border-b,consumer 自畫
|
|
142
154
|
return (
|
|
@@ -37,22 +37,21 @@ Density 由**兩個獨立維度**構成,並配合一個 convenience attribute
|
|
|
37
37
|
|
|
38
38
|
當需要「版面間距寬鬆(layout-space)+ 控件高度標準(ui-size)」時,**顯式設兩個 attribute**:
|
|
39
39
|
|
|
40
|
-
### Canonical 情境 1 —
|
|
40
|
+
### Canonical 情境 1 — Overlay 的 layout-space tier(2026-06-15 重訂)
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
**核心原則**:浮層(Portal overlay)**預設全繼承 page density**;只鎖「**有設計理由 + 該元件真的消費到**」的那個 dial,**禁用 `data-density` master switch 鎖浮層**(它連 ui-size 一起鎖 → 內部控件 / 選單 item 對不上觸發點)。**ui-size 永遠跟 page** —— 浮層只決定「版面間距 tier」(layout-space),控件大小交給 page。
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
| 浮層 | 鎖 | 理由 |
|
|
45
|
+
|------|----|------|
|
|
46
|
+
| Dialog | **不鎖(繼承 page)** | layout-space 本 spec 第 10 行定義即管「dialog body padding」,鎖死 = override 自家 dial 自相矛盾;modal 寬鬆需求在 lg 階自然滿足(md 16 = Polaris 下限合格);button 不撐高由 ui-size 繼承解決。有同類 dial 的 SAP/Cloudscape modal 跟 dial、不鎖 |
|
|
47
|
+
| Popover / Coachmark | `data-layout-space="md"` | 輕量浮層 → header py-tight 保持精簡;ui-size 跟 page 讓內部 field 控件對齊觸發點。Coachmark 建在 Popover 上自動繼承 |
|
|
48
|
+
| Tooltip / DropdownMenu | 不鎖(繼承 page) | 不消費任何 layout-space token(Tooltip padding 寫死 px-3 py-2;Dropdown 是 RowSize + py-2);鎖 ui-size 會把選單 item(field-height-{size})釘在 md-scale → 對不上 lg 觸發點 |
|
|
49
|
+
| Sheet | 不鎖(繼承 page) | 側板,螢幕變小時常駐側板會 responsive 變 Sheet;若 Sheet 鎖 tier 而側板繼承 page → 切換瞬間跳 mode,不一致 |
|
|
50
|
+
| FileViewer | 不鎖(繼承 page) | 全螢幕 chrome 殼。原 `lockDensity="lg"` 只鎖在兩個 ChromeHeader、沒鎖 body → header(px-loose@lg=24)與 body(px-loose@page=16)左緣不對齊;移除 → 全 surface 同密度 |
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
// 假設未來某元件需要 lg 呼吸 + page-default ui-size
|
|
48
|
-
<SomeContent data-layout-space="lg">
|
|
49
|
-
<Button size="sm" /> {/* 仍是 28px md */}
|
|
50
|
-
</SomeContent>
|
|
51
|
-
```
|
|
52
|
+
**Tier alignment**:每個 overlay 宣告自己的 layout-space tier → header 高度隨之(Dialog lg → title 24 + py-tight 16×2 = 56;Popover md → 45)。「對齊 `--chrome-header-height`」是**同 tier 內**成立(Popover 45 ≠ Dialog 56 ≠ chrome 48 本就允許,各宣告各 tier),非跨 page 強制。詳 `patterns/header-canonical/header-canonical.spec.md` A 家族。
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
- Dialog / Sheet:繼承 page density(無 override)
|
|
55
|
-
- Popover / DropdownMenu / Tooltip:`data-density="md"` 鎖定(Portal 逃逸 + compact 語意)
|
|
54
|
+
**為何不鎖 ui-size**:field-height token 隨 density 變(md 28/32/36 → lg 32/36/40);鎖 ui-size=md 會把浮層內控件 / 選單 item 釘死 md-scale,lg page 上對不上 lg 觸發點。decouple = 只鎖版面間距、控件跟 page。對標 Carbon spacing scale 獨立於 control size / GitHub Primer 8px scale 獨立。
|
|
56
55
|
|
|
57
56
|
### Canonical 情境 2 — 單獨切 ui-size
|
|
58
57
|
|
|
@@ -94,24 +93,36 @@ document.documentElement.setAttribute('data-layout-space', 'md')
|
|
|
94
93
|
- 是 → 用 `data-density="X"`(convenience)
|
|
95
94
|
- 否(想解耦) → 明示 `data-layout-space="X"` + / 或 `data-ui-size="Y"`
|
|
96
95
|
|
|
97
|
-
3. **Portal 逃逸 subtree?**(Dialog / Popover / Sheet / DropdownMenu)
|
|
98
|
-
-
|
|
96
|
+
3. **Portal 逃逸 subtree?**(Dialog / Popover / Sheet / DropdownMenu / Tooltip)
|
|
97
|
+
- density 設在 `html` root 時,CSS cascade 會傳到 portal 出去的內容(portal 掛 body,仍在 html 底下)→ **浮層繼承 page density 正常,不需自設**。
|
|
98
|
+
- 只有 density 設在「中層 div」時 portal 才逃逸該 subtree(M3 邊界)。
|
|
99
|
+
- **鎖浮層 density/layout-space = 刻意覆蓋成某 tier,需設計理由(見情境 1 表)**,非 Portal 必要;且只鎖該元件真正消費的 dial。
|
|
99
100
|
|
|
100
101
|
## 消費者清單
|
|
101
102
|
|
|
102
103
|
| 元件 | attribute 設置 | 用法理由 |
|
|
103
104
|
|------|---------------|---------|
|
|
104
|
-
| Dialog |
|
|
105
|
-
| Sheet | 無(繼承 page) |
|
|
106
|
-
| Popover | `data-
|
|
107
|
-
| DropdownMenu |
|
|
108
|
-
| Tooltip |
|
|
109
|
-
|
|
|
105
|
+
| Dialog | **無(繼承 page,2026-06-16)** | layout-space 定義即管 dialog body padding → 鎖 = override 自家 dial 自相矛盾;header 隨 page md=48 / lg=56;有同類 dial 的 SAP/Cloudscape modal 跟 dial |
|
|
106
|
+
| Sheet | 無(繼承 page) | 側板;responsive 會跟常駐側板互換,不能跳 mode |
|
|
107
|
+
| Popover / Coachmark | `data-layout-space="md"`(2026-06-15) | 輕量;header py-tight 精簡;ui-size 繼承 page → 內部控件對齊觸發點。Coachmark 建在 Popover 上自動繼承 |
|
|
108
|
+
| DropdownMenu | 無(繼承 page,2026-06-15) | 不消費 layout-space token;鎖 ui-size 會讓 menu item(field-height-{size})對不上觸發點 |
|
|
109
|
+
| Tooltip | 無(繼承 page,2026-06-15) | 不消費任何 density/layout-space token,鎖 = inert |
|
|
110
|
+
| FileViewer | 無(繼承 page,2026-06-15) | 全螢幕 chrome 殼;原 lockDensity 只鎖 header 造成左緣不對齊 → 移除 |
|
|
111
|
+
| Sidebar | 無(繼承 page) | inline chrome(非 Portal 逃逸),跟隨 page density;size="md" 在 density="lg" 下自動變 36px row(見 sidebar.spec.md)|
|
|
110
112
|
|
|
111
|
-
**邊界:page density=lg 下的 Portal overlay**——
|
|
113
|
+
**邊界:page density=lg 下的 Portal overlay**——Tooltip / DropdownMenu 繼承 page → lg page 上隨 page 放大,選單 item 對齊 lg 觸發點。Popover / Coachmark 鎖 layout-space=md(header 精簡)但 ui-size 仍繼承 lg(內部控件放大)。Dialog 繼承 page(不鎖)→ md 16 / lg 24 padding 隨 page。皆透過 `[data-layout-space="md|lg"]` reset selector(layoutSpace.css L21/L31)在任意 page density 上正確解析。
|
|
112
114
|
|
|
113
115
|
## Anti-patterns(禁止)
|
|
114
116
|
|
|
115
117
|
- ❌ 元件同時設 `data-density` + `data-ui-size`(重複,以後者為準但混亂)
|
|
116
|
-
- ❌
|
|
118
|
+
- ❌ **用 `data-density` master switch 鎖浮層**(連 ui-size 一起鎖 → 內部控件 / 選單 item 對不上觸發點)。只鎖該元件真正消費的 dial(見情境 1 表)
|
|
119
|
+
- ❌ **把 density/layout-space lock 設在 header primitive(ChromeHeader / SurfaceHeader)而非 surface 根**——header 鎖、body 沒鎖 → 左緣不對齊(FileViewer 圖二 bug)。lock 一律放 surface 根容器
|
|
117
120
|
- ❌ 為了追求表面一致性硬把 Dialog button 綁 lg ui-size(犧牲 header 高度 / strapline 彈性)
|
|
121
|
+
- ❌ **鎖 Dialog 的 layout-space**(2026-06-16 定論)—— 本 spec 第 10 行定義 layout-space 就管「dialog body padding」,鎖 Dialog = 令自家 dial 對它點名要管的對象失效 = 自相矛盾;有同類 dial 的 SAP/Cloudscape 都讓 modal 跟 page dial。modal 寬鬆需求在 lg 階自然滿足(md 16 = Polaris 下限),不靠鎖。Popover/Coachmark 鎖 md 是另一回事(空間效率、消費 layout-space token、且為非阻斷錨定浮層)
|
|
122
|
+
|
|
123
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
124
|
+
|
|
125
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
126
|
+
|
|
127
|
+
- `file-viewer.spec.md`
|
|
128
|
+
- `header-canonical.spec.md`
|
|
@@ -56,15 +56,15 @@ export const Overview: Story = {
|
|
|
56
56
|
Pattern 層級間距 token,跟 density 連動(md / lg 兩個值)。下表展示 md density 預覽。
|
|
57
57
|
</p>
|
|
58
58
|
|
|
59
|
-
<SpaceRow utility="--layout-space-tight" mdValue="
|
|
59
|
+
<SpaceRow utility="--layout-space-tight" mdValue="12px" lgValue="16px" usage="header / footer 上下 padding;緊密 inline gap" />
|
|
60
60
|
<SpaceRow utility="--layout-space-loose" mdValue="16px" lgValue="24px" usage="surface 左右 padding;tabs trigger gap;form field 間距" />
|
|
61
|
-
<SpaceRow utility="--layout-space-bottom" mdValue="
|
|
61
|
+
<SpaceRow utility="--layout-space-bottom" mdValue="48px" lgValue="48px" usage="Dialog / Sheet body 底部多一拍 + modal viewport inset(固定,不隨 density)" />
|
|
62
62
|
|
|
63
63
|
<h3 className="text-h5 mt-8 mb-2">使用規則</h3>
|
|
64
64
|
<ul className="list-disc pl-6 text-body text-fg-secondary space-y-1">
|
|
65
|
-
<li>跟 block 元素相鄰 → <code>tight</code>(
|
|
65
|
+
<li>跟 block 元素相鄰 → <code>tight</code>(12/16)</li>
|
|
66
66
|
<li>inline ↔ inline parallel → <code>loose</code>(16/24)</li>
|
|
67
|
-
<li>容器最底部 → <code>bottom</code>(
|
|
67
|
+
<li>容器最底部 → <code>bottom</code>(48 固定)</li>
|
|
68
68
|
</ul>
|
|
69
69
|
|
|
70
70
|
<p className="text-caption text-fg-muted mt-6">
|