@qijenchen/design-system 0.1.0-beta.75 → 0.1.0-beta.76
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/CLAUDE.md +1 -1
- package/dist/components/AppShell/app-shell.d.ts +2 -2
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Chart/chart.d.ts +1 -1
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +1 -1
- package/dist/components/Checkbox/checkbox.js +1 -1
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +1 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +13 -10
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +1 -1
- package/dist/components/Command/command.js +3 -3
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +2 -2
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +2 -2
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts +1 -1
- package/dist/components/DescriptionList/description-list.js +2 -2
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/Empty/empty.d.ts +2 -0
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Field/field-wrapper.js +4 -4
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -2
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +2 -2
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +1 -1
- package/dist/components/ProfileCard/profile-card.js +2 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/Select/select.js +4 -4
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +1 -1
- package/dist/components/Textarea/textarea.js +2 -2
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +14 -23
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +1 -1
- package/dist/components/TreeView/tree-view.js +1 -1
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/ds-canonical/fork/governance.lock +1 -1
- package/ds-canonical/fork/preamble.md +2 -2
- package/ds-canonical/hooks/check_field_controls_contracts.sh +30 -3
- package/ds-canonical/references/props-naming.md +15 -1
- package/ds-canonical/rules/ui-development.md +2 -2
- package/llms-full.txt +7 -2
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/components/Alert/alert.anatomy.stories.tsx +4 -4
- package/src/components/AppShell/app-shell.spec.md +4 -4
- package/src/components/AppShell/app-shell.tsx +2 -2
- package/src/components/Avatar/avatar.tsx +1 -1
- package/src/components/Breadcrumb/breadcrumb.spec.md +11 -1
- package/src/components/Button/button.tsx +0 -10
- package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
- package/src/components/Chart/chart.tsx +1 -1
- package/src/components/Checkbox/checkbox.tsx +1 -1
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.spec.md +2 -2
- package/src/components/Combobox/combobox.anatomy.stories.tsx +12 -12
- package/src/components/Combobox/combobox.principles.stories.tsx +1 -1
- package/src/components/Combobox/combobox.spec.md +1 -1
- package/src/components/Combobox/combobox.tsx +25 -14
- package/src/components/Command/command.anatomy.stories.tsx +2 -0
- package/src/components/Command/command.tsx +2 -2
- package/src/components/DataTable/cell-registry.tsx +6 -2
- package/src/components/DataTable/data-table-filter-panel.tsx +3 -3
- package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
- package/src/components/DataTable/data-table.css +1 -1
- package/src/components/DataTable/data-table.spec.md +2 -2
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +15 -11
- package/src/components/DatePicker/date-picker.spec.md +1 -1
- package/src/components/DatePicker/date-picker.tsx +9 -6
- package/src/components/DescriptionList/description-list.tsx +1 -1
- package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.spec.md +1 -1
- package/src/components/Empty/empty.tsx +2 -0
- package/src/components/Field/field-controls.spec.md +9 -6
- package/src/components/Field/field-wrapper.tsx +4 -4
- package/src/components/FileItem/file-item.principles.stories.tsx +1 -0
- package/src/components/FileUpload/file-upload.principles.stories.tsx +2 -2
- package/src/components/FileUpload/file-upload.spec.md +1 -1
- package/src/components/HoverCard/hover-card.principles.stories.tsx +1 -1
- package/src/components/Input/input.anatomy.stories.tsx +3 -3
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +3 -3
- package/src/components/Notice/notice.anatomy.stories.tsx +1 -1
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +8 -7
- package/src/components/NumberInput/number-input.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.spec.md +3 -3
- package/src/components/PeoplePicker/people-picker.tsx +6 -6
- package/src/components/Popover/popover.principles.stories.tsx +4 -4
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.spec.md +1 -1
- package/src/components/Rating/rating.anatomy.stories.tsx +2 -2
- package/src/components/Rating/rating.spec.md +1 -1
- package/src/components/Rating/rating.tsx +1 -1
- package/src/components/Select/select.anatomy.stories.tsx +18 -18
- package/src/components/Select/select.spec.md +1 -1
- package/src/components/Select/select.tsx +7 -7
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
- package/src/components/Sidebar/sidebar.spec.md +2 -2
- package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +2 -2
- package/src/components/Tabs/tabs.principles.stories.tsx +1 -1
- package/src/components/Tabs/tabs.spec.md +1 -1
- package/src/components/Textarea/textarea.tsx +3 -3
- package/src/components/TimePicker/time-picker.spec.md +1 -1
- package/src/components/TimePicker/time-picker.tsx +11 -12
- package/src/components/TreeView/tree-view.tsx +1 -1
- package/src/patterns/element-anatomy/item-anatomy.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -1
- package/src/patterns/overlay-surface/overlay-surface.spec.md +1 -0
- package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
- package/src/tokens/color/semantic.css +1 -1
- package/src/tokens/uiSize/uiSize.css +5 -0
- package/src/tokens/uiSize/uiSize.spec.md +17 -3
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.
|
|
3
|
+
> 全 component / pattern 的 variants / sizes / 禁止事項。build-time 從 spec.md frontmatter 生成,禁手改。v0.1.0-beta.76。
|
|
4
4
|
|
|
5
5
|
# Components
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ Source(AI 讀此看官方範例): src/components/Alert/
|
|
|
21
21
|
- **warning**: 警告但非阻斷(方案即將到期、需更新付款方式);最高頻使用
|
|
22
22
|
- **error**: 錯誤但非阻斷(系統錯誤但可重試、API 失敗摘要);搭配 aria-live=assertive
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## AppShell(family composite)
|
|
25
25
|
|
|
26
26
|
Storybook: https://ajenchen-design-system.netlify.app
|
|
27
27
|
Source(AI 讀此看官方範例): src/components/AppShell/
|
|
@@ -52,6 +52,11 @@ Source(AI 讀此看官方範例): src/components/Badge/
|
|
|
52
52
|
Storybook: https://ajenchen-design-system.netlify.app
|
|
53
53
|
Source(AI 讀此看官方範例): src/components/Breadcrumb/
|
|
54
54
|
|
|
55
|
+
### Sizes
|
|
56
|
+
- **sm**: Dialog / panel / drawer 內;text-body(14)+ icon 16,建議配 text-h4(20)title。對齊 BREADCRUMB_TEXT_CLASS.sm + BREADCRUMB_ICON_SIZE.sm(breadcrumb.tsx:124-136)
|
|
57
|
+
- **md**: 預設 — 一般頁面 header;text-body(14)+ icon 16,建議配 text-h3(24)title
|
|
58
|
+
- **lg**: Detail page hero / landing;text-body-lg(16)+ icon 20,建議配 text-h2(32)title
|
|
59
|
+
|
|
55
60
|
## BulkActionBar(family composite)
|
|
56
61
|
|
|
57
62
|
Storybook: https://ajenchen-design-system.netlify.app
|
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.
|
|
4
|
+
> 54 components + 4 public patterns + design tokens。v0.1.0-beta.76。
|
|
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)。
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
## Components
|
|
11
11
|
- [Accordion](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/Accordion
|
|
12
12
|
- [Alert](https://ajenchen-design-system.netlify.app): variants:neutral/info/success/warning/error — src:components/Alert
|
|
13
|
-
- [
|
|
13
|
+
- [AppShell](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/AppShell
|
|
14
14
|
- [AspectRatio](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/AspectRatio
|
|
15
15
|
- [Avatar](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/Avatar
|
|
16
16
|
- [Badge](https://ajenchen-design-system.netlify.app): variants:critical/high/medium/low — src:components/Badge
|
|
17
|
-
- [Breadcrumb](https://ajenchen-design-system.netlify.app):
|
|
17
|
+
- [Breadcrumb](https://ajenchen-design-system.netlify.app): sizes:sm/md/lg — src:components/Breadcrumb
|
|
18
18
|
- [BulkActionBar](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/BulkActionBar
|
|
19
19
|
- [Button](https://ajenchen-design-system.netlify.app): variants:primary/secondary/tertiary/text/link;sizes:xs/sm/md/lg — src:components/Button
|
|
20
20
|
- [Calendar](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/Calendar
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qijenchen/design-system",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.76",
|
|
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",
|
|
@@ -128,10 +128,10 @@ export const ColorMatrix: Story = {
|
|
|
128
128
|
<thead><tr><Th>Variant</Th><Th>Subtle</Th><Th>Solid</Th></tr></thead>
|
|
129
129
|
<tbody>
|
|
130
130
|
<tr><Td mono>neutral</Td><Td mono>bg-muted + border-border;無 status icon(VARIANT_ICON.neutral = null)</Td><Td>bg-surface-raised + data-theme inverse(跟頁面反)</Td></tr>
|
|
131
|
-
<tr><Td mono>info</Td><Td mono>bg-info-subtle + border-info-
|
|
132
|
-
<tr><Td mono>success</Td><Td mono>bg-success-subtle + border-success-
|
|
133
|
-
<tr><Td mono>warning</Td><Td mono>bg-warning-subtle + border-warning-
|
|
134
|
-
<tr><Td mono>error</Td><Td mono>bg-error-subtle + border-error-
|
|
131
|
+
<tr><Td mono>info</Td><Td mono>bg-info-subtle + border-[var(--info-text)] + text-info-text</Td><Td mono>bg-info + data-theme="dark"(藍底白字)</Td></tr>
|
|
132
|
+
<tr><Td mono>success</Td><Td mono>bg-success-subtle + border-[var(--success-text)] + text-success-text</Td><Td mono>bg-success + data-theme="dark"(綠底白字)</Td></tr>
|
|
133
|
+
<tr><Td mono>warning</Td><Td mono>bg-warning-subtle + border-[var(--warning-text)] + text-warning-text</Td><Td mono>bg-warning + data-theme="light"(黃底深字)</Td></tr>
|
|
134
|
+
<tr><Td mono>error</Td><Td mono>bg-error-subtle + border-[var(--error-text)] + text-error-text</Td><Td mono>bg-error + data-theme="dark"(橘底白字)</Td></tr>
|
|
135
135
|
</tbody>
|
|
136
136
|
</table>
|
|
137
137
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
2
|
+
component: AppShell
|
|
3
3
|
family: composite # 組合 sidebar+header+main+aside,非 Family 1-4 row element
|
|
4
4
|
scope: web service page-level layout primitive (sidebar + header + main + aside composition)
|
|
5
5
|
benchmark:
|
|
@@ -58,7 +58,7 @@ benchmark:
|
|
|
58
58
|
asideOpen={open} onAsideOpenChange={setOpen} // ⌘. / Ctrl+. toggle (本 AppShell own)
|
|
59
59
|
sidebar={<Sidebar>...</Sidebar>} // SSOT in components/Sidebar/sidebar.spec.md
|
|
60
60
|
header={<ChromeHeader>...</ChromeHeader>} // SSOT in patterns/header-canonical/header-canonical.spec.md
|
|
61
|
-
aside={<AppShellAside title="Detail">...</AppShellAside>} // 本
|
|
61
|
+
aside={<AppShellAside title="Detail">...</AppShellAside>} // 本 composite own;modal mode 必 title(Sheet a11y)
|
|
62
62
|
>
|
|
63
63
|
{children} {/* <main> landmark, padding=0,內容遵循 layoutSpace 6 條規則 */}
|
|
64
64
|
</AppShell>
|
|
@@ -288,7 +288,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
|
|
|
288
288
|
|
|
289
289
|
## Future extension(目前不定義)
|
|
290
290
|
|
|
291
|
-
**Multi-sidebar**(Notion / Linear 雙側欄派):**尚未實作**。目前 `sidebar` prop 僅 `React.ReactNode`(單一 slot,無 array signature、無取首位邏輯、無 dev warning)。未來若支援會擴充為 array,SSOT 放 `sidebar.spec.md`,不在本
|
|
291
|
+
**Multi-sidebar**(Notion / Linear 雙側欄派):**尚未實作**。目前 `sidebar` prop 僅 `React.ReactNode`(單一 slot,無 array signature、無取首位邏輯、無 dev warning)。未來若支援會擴充為 array,SSOT 放 `sidebar.spec.md`,不在本 composite(per user 2026-05-19「AppShell 不該 customize Sidebar」)。
|
|
292
292
|
|
|
293
293
|
**Footer**:不 expose slot(per user 2026-05-19「不用 footer」)。Web service 通常不用 footer,consumer 若需要自貼 Main 底。
|
|
294
294
|
|
|
@@ -342,7 +342,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
|
|
|
342
342
|
|
|
343
343
|
## 相關 spec(per codex Layer B 比稿修正 path/case)
|
|
344
344
|
|
|
345
|
-
- `components/Sidebar/sidebar.spec.md` — sidebar 視覺 + 鍵盤 + mobile + SidebarTrigger Pattern A/B SSOT(本
|
|
345
|
+
- `components/Sidebar/sidebar.spec.md` — sidebar 視覺 + 鍵盤 + mobile + SidebarTrigger Pattern A/B SSOT(本 composite 必消費)
|
|
346
346
|
- `patterns/header-canonical/header-canonical.spec.md` — header 視覺契約 SSOT
|
|
347
347
|
- `patterns/overlay-surface/overlay-surface.spec.md` — Aside modal mode 上層 SSOT
|
|
348
348
|
- `components/Sheet/sheet.spec.md` — modal fallback SSOT(`aria-labelledby` 強制 + `z-50`)
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* AppShell — web service page-level layout primitive。
|
|
4
4
|
*
|
|
5
|
-
* 組合 Sidebar + ChromeHeader + Aside + main 成完整 page shell。SSOT 邊界:本
|
|
5
|
+
* 組合 Sidebar + ChromeHeader + Aside + main 成完整 page shell。SSOT 邊界:本 composite component only
|
|
6
6
|
* own slot composition + layout mode + Aside responsive mode;不 own sidebar / header /
|
|
7
7
|
* sheet 視覺(各自 spec own)。
|
|
8
8
|
*
|
|
9
9
|
* 對齊 Mantine AppShell compound API + Ant Layout slot 模式 + Material 3 standard/modal
|
|
10
10
|
* drawer canonical(per spec.md frontmatter cite)。
|
|
11
11
|
*
|
|
12
|
-
* Spec SSOT:`
|
|
12
|
+
* Spec SSOT:`components/AppShell/app-shell.spec.md`
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import * as React from 'react'
|
|
@@ -277,7 +277,7 @@ const AvatarInner = React.forwardRef<HTMLDivElement, AvatarProps>(
|
|
|
277
277
|
style={{
|
|
278
278
|
boxShadow: `0 0 0 2px var(--surface-raised, var(--canvas))`,
|
|
279
279
|
}}
|
|
280
|
-
aria-label={`${badgeCount} unread`}
|
|
280
|
+
aria-label={`${badgeCount} unread`} // i18n-allow: DS default(未覆寫英文 'N unread')
|
|
281
281
|
/>
|
|
282
282
|
)}
|
|
283
283
|
</div>
|
|
@@ -2,9 +2,19 @@
|
|
|
2
2
|
component: Breadcrumb
|
|
3
3
|
family: composite
|
|
4
4
|
variants: {}
|
|
5
|
-
sizes:
|
|
5
|
+
sizes:
|
|
6
|
+
sm:
|
|
7
|
+
when: "Dialog / panel / drawer 內;text-body(14)+ icon 16,建議配 text-h4(20)title。對齊 BREADCRUMB_TEXT_CLASS.sm + BREADCRUMB_ICON_SIZE.sm(breadcrumb.tsx:124-136)"
|
|
8
|
+
world-class: ["Atlassian Breadcrumb", "Ant Design Breadcrumb"]
|
|
9
|
+
md:
|
|
10
|
+
when: "預設 — 一般頁面 header;text-body(14)+ icon 16,建議配 text-h3(24)title"
|
|
11
|
+
world-class: ["Ant Design Breadcrumb default", "MUI Breadcrumbs", "Polaris Breadcrumbs"]
|
|
12
|
+
lg:
|
|
13
|
+
when: "Detail page hero / landing;text-body-lg(16)+ icon 20,建議配 text-h2(32)title"
|
|
14
|
+
world-class: ["Carbon Breadcrumb", "Material 3 breadcrumb"]
|
|
6
15
|
traits:
|
|
7
16
|
- hasInteractiveStates
|
|
17
|
+
- hasSizes
|
|
8
18
|
- isStructural
|
|
9
19
|
benchmark:
|
|
10
20
|
- Ant Design Breadcrumb: github.com/ant-design/ant-design/tree/master/components/breadcrumb
|
|
@@ -313,16 +313,6 @@ export interface ButtonProps
|
|
|
313
313
|
fullWidth?: boolean
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
/**
|
|
317
|
-
* Icon-only padding — calc `(field-height - icon-size) / 2` per size。
|
|
318
|
-
*
|
|
319
|
-
* 設計:startIcon 到左邊距離 = padding = `(height - icon) / 2`。
|
|
320
|
-
* 純 icon-only 時 width = 2*padding + icon = height → **自然正方形**,不需要 aspect-square。
|
|
321
|
-
* 有 suffix(badge / endIcon)時 width = 2*pad + icon + gap + suffix > height → **自然長方形**。
|
|
322
|
-
* StartIcon 到左邊距離始終不變,形狀自動適應內容。
|
|
323
|
-
*
|
|
324
|
-
* 用 CSS var 讓 density 切換時 padding 自動跟著算(field-height 會變)。
|
|
325
|
-
*/
|
|
326
316
|
// IconOnly 用 padding-free + aspect-square + flex-center 的 Polaris/Atlassian idiom
|
|
327
317
|
// (M17 SSOT 必可傳播 — 取代 4 個 size 的 magic-number 公式):
|
|
328
318
|
// - aspect-square 鎖 width=height(來自 h-field-X)
|
|
@@ -124,7 +124,7 @@ export const ColorMatrix: Story = {
|
|
|
124
124
|
<tr>
|
|
125
125
|
<Td>Today cell(日期數字)</Td>
|
|
126
126
|
<Td mono>bg-info · text-on-emphasis · rounded-full · min-w-6 h-6 px-2 · text-body font-medium</Td>
|
|
127
|
-
<Td>info-filled pill,固定 h-6 + min-w-6
|
|
127
|
+
<Td>info-filled pill,固定 h-6 + min-w-6 做 pill badge(對齊 Google Calendar today pill)</Td>
|
|
128
128
|
</tr>
|
|
129
129
|
<tr>
|
|
130
130
|
<Td>Outside day cell</Td>
|
|
@@ -15,7 +15,7 @@ import { cn } from '@/lib/utils'
|
|
|
15
15
|
* 預設建議使用 --chart-1..5(本 DS 提供的 5 色類別 token)
|
|
16
16
|
*
|
|
17
17
|
* ── 視覺 token ──
|
|
18
|
-
* Tooltip: bg-surface-raised / border-border / shadow-[elevation-200] / rounded-md
|
|
18
|
+
* Tooltip: bg-surface-raised / border-border / shadow-[var(--elevation-200)] / rounded-md
|
|
19
19
|
* Legend text: text-fg-secondary / text-caption
|
|
20
20
|
* Grid: stroke-divider
|
|
21
21
|
* Axis tick: text-fg-muted / text-caption
|
|
@@ -281,7 +281,7 @@ export const checkboxMeta = {
|
|
|
281
281
|
// 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)
|
|
282
282
|
sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },
|
|
283
283
|
md: { fieldHeight: 32, iconSize: 12, typography: 'body' },
|
|
284
|
-
lg: { fieldHeight: 36, iconSize: 16, typography: 'body' },
|
|
284
|
+
lg: { fieldHeight: 36, iconSize: 16, typography: 'body-lg' },
|
|
285
285
|
},
|
|
286
286
|
states: ['default', 'hover', 'focus-visible', 'disabled'],
|
|
287
287
|
tokens: {
|
|
@@ -167,7 +167,7 @@ export const Inspector: Story = {
|
|
|
167
167
|
<tr><Td>Previous / Skip variant</Td><Td mono>tertiary</Td></tr>
|
|
168
168
|
<tr><Td>Next variant</Td><Td mono>primary</Td></tr>
|
|
169
169
|
<tr><Td>sideOffset</Td><Td mono>8px(對齊 Popover DS 設計準則)</Td></tr>
|
|
170
|
-
<tr><Td>
|
|
170
|
+
<tr><Td>Layout-space</Td><Td mono>繼承 Popover(鎖 layout-space md;ui-size 繼承 page)</Td></tr>
|
|
171
171
|
</tbody>
|
|
172
172
|
</table>
|
|
173
173
|
</div>
|
|
@@ -193,7 +193,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
|
|
|
193
193
|
- ❌ **不用 Coachmark 做確認框**——確認破壞性動作必須阻斷流程,改用 `Dialog`
|
|
194
194
|
- ❌ **Media 區不放互動元件**——按鈕 / 輸入 / checkbox 一律走 footer。Media 是視覺說明不是互動區
|
|
195
195
|
- ❌ **Description 不寫超過 3 行**——Coachmark 是快速說明,超過 3 行改用 Dialog + 完整 body 或連結到說明文件
|
|
196
|
-
- ❌ **不強迫完成 tour**——永遠提供退出機制(Esc / header Close / 第一步 Skip)。沒有退出的 onboarding
|
|
196
|
+
- ❌ **不強迫完成 tour**——永遠提供退出機制(Esc / header Close / 第一步 Skip)。沒有退出的 onboarding 讓使用者感到被綁架,反而降低完成率。註:header Close 僅在傳 `kind`(有 header)時才 render;無 `kind` 的 multi-step tour 第 2+ 步退出靠 Esc(永遠可用),multi-step 務必傳 `kind` 以提供 header Close 退出入口
|
|
197
197
|
- ❌ **單步驟 CTA 不叫 "Next"**——沒有下一步就不用 Next 字眼,用 `'知道了' / 'Got it' / 'Start'`
|
|
198
198
|
- ❌ **有 Prev 時不同時顯示 Skip**——使用者投入進度後兩個退出路徑衝突(見 CTA 語義表)
|
|
199
199
|
- ❌ **不自包視覺 token**——bg / shadow / radius / padding 一律繼承 Popover,改視覺就改 Popover
|
|
@@ -212,7 +212,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
|
|
|
212
212
|
|
|
213
213
|
## 邊界狀態
|
|
214
214
|
|
|
215
|
-
disabled /
|
|
215
|
+
disabled / layout-space 繼承 Popover(鎖 `data-layout-space="md"` 保持 header py-tight 精簡;ui-size / 控件大小繼承 page density),SSOT 見 `tokens/density/density.spec.md` L47;empty(title + description 都不傳)則 Body 不渲染(已於「Props 結構」規定);loading 由 consumer 決定。
|
|
216
216
|
|
|
217
217
|
- **步數 > 5**:元件不機械限制 — `step.total` 照實顯示(如「6 of 8」);≤ 5 是設計建議(見 Multi-step Tour),超過應改靜態 onboarding 頁
|
|
218
218
|
- **無 `image`**:整個 Media 區不渲染(非 skeleton 佔位,見「Props 結構」)
|
|
@@ -39,15 +39,15 @@ const TOKEN_MAP: Record<ModeKey, Record<StateKey, ColorSpec>> = {
|
|
|
39
39
|
},
|
|
40
40
|
readonly: {
|
|
41
41
|
default: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
|
|
42
|
-
hover: { bg: '--bg-
|
|
43
|
-
focus: { bg: '--bg-
|
|
44
|
-
disabled: { bg: '--bg-
|
|
42
|
+
hover: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
|
|
43
|
+
focus: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
|
|
44
|
+
disabled: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
|
|
45
45
|
},
|
|
46
46
|
disabled: {
|
|
47
|
-
default: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '
|
|
48
|
-
hover: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '
|
|
49
|
-
focus: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '
|
|
50
|
-
disabled: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '
|
|
47
|
+
default: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '--fg-disabled' },
|
|
48
|
+
hover: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '--fg-disabled' },
|
|
49
|
+
focus: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '--fg-disabled' },
|
|
50
|
+
disabled: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '--fg-disabled' },
|
|
51
51
|
},
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -270,7 +270,7 @@ export const Overview = {
|
|
|
270
270
|
<div className="flex flex-col gap-4">
|
|
271
271
|
<div className="flex flex-col gap-1">
|
|
272
272
|
<H3>結構(Anatomy)— readonly / disabled</H3>
|
|
273
|
-
<Desc>Tag 沒有 dismiss 按鈕、沒有 clear;ChevronDown 保留為類型身份 indicator(pointer-events-none,
|
|
273
|
+
<Desc>Tag 沒有 dismiss 按鈕、沒有 clear;readonly 不顯示 ChevronDown(純值、不可開下拉),disabled 保留為類型身份 indicator(pointer-events-none,fg-disabled)。溢出行為與 edit 相同(+N 指示器)。</Desc>
|
|
274
274
|
</div>
|
|
275
275
|
<div className="flex gap-8">
|
|
276
276
|
<div className="flex flex-col gap-2 items-start">
|
|
@@ -285,7 +285,7 @@ export const Overview = {
|
|
|
285
285
|
style={{ borderColor: `var(--${s.color})`, backgroundColor: `var(--${s.color}-subtle)`, color: `var(--${s.color})` }}>{s.name}</span>
|
|
286
286
|
))}
|
|
287
287
|
</div>
|
|
288
|
-
<span className="text-[10px] text-fg-muted font-mono">無 dismiss · 無 clear · chevron
|
|
288
|
+
<span className="text-[10px] text-fg-muted font-mono">無 dismiss · 無 clear · chevron:readonly 不顯示 / disabled 保留 · tagPadding</span>
|
|
289
289
|
</div>
|
|
290
290
|
<div className="flex flex-col gap-2 items-start">
|
|
291
291
|
<span className="text-[11px] text-fg-secondary font-medium">空值</span>
|
|
@@ -466,7 +466,7 @@ const InspectorInner = () => {
|
|
|
466
466
|
<PropRow label="Fill"><TokenValue value={colors.bg} /></PropRow>
|
|
467
467
|
<PropRow label="Text"><TokenValue value={colors.text} /></PropRow>
|
|
468
468
|
<PropRow label="Stroke"><TokenValue value={colors.border} /></PropRow>
|
|
469
|
-
{
|
|
469
|
+
{mode !== 'readonly' && <PropRow label="Icon"><TokenValue value={colors.icon} /></PropRow>}
|
|
470
470
|
</div>
|
|
471
471
|
|
|
472
472
|
{/* LAYOUT */}
|
|
@@ -479,7 +479,7 @@ const InspectorInner = () => {
|
|
|
479
479
|
}
|
|
480
480
|
</PropRow>
|
|
481
481
|
<PropRow label="tagPadding" dot={Z.pad.text}><TkVal token="calc()" value={s.tagPaddingCalc} /></PropRow>
|
|
482
|
-
<PropRow label="右側內距">
|
|
482
|
+
<PropRow label="右側內距">var(--field-px) (12px)</PropRow>
|
|
483
483
|
<PropRow label="Tag 間距" dot={Z.gap.text}>{s.tagGap}</PropRow>
|
|
484
484
|
<PropRow label="Icon 尺寸" dot={Z.icon.text}>{s.icon}px</PropRow>
|
|
485
485
|
<PropRow label="Tag 高度" dot={Z.tag.text}>{s.tagHeight} ({s.tagSize})</PropRow>
|
|
@@ -677,7 +677,7 @@ export const SizeMatrix = {
|
|
|
677
677
|
{SIZES.map((sz) => (
|
|
678
678
|
<Td key={sz} mono>
|
|
679
679
|
<div className="text-fg-secondary">paddingRight</div>
|
|
680
|
-
<div className="text-fg-muted text-[10px]">
|
|
680
|
+
<div className="text-fg-muted text-[10px]">var(--field-px) (12px)</div>
|
|
681
681
|
</Td>
|
|
682
682
|
))}
|
|
683
683
|
</tr>
|
|
@@ -195,7 +195,7 @@ export const TagOperationRule: Story = {
|
|
|
195
195
|
|
|
196
196
|
<Rule
|
|
197
197
|
title="readonly / disabled 的 Tag 沒有任何互動"
|
|
198
|
-
note="沒有 dismiss X、沒有 clear;ChevronDown
|
|
198
|
+
note="沒有 dismiss X、沒有 clear;readonly 不顯示 ChevronDown(純值、不可開啟),disabled 才保留為類型身份 indicator。Tag 變純顯示,溢出規則仍然套用(+N 指示器可 hover 查看完整)"
|
|
199
199
|
>
|
|
200
200
|
<Combobox mode="readonly" options={categoryOptions} value={ro} />
|
|
201
201
|
<Label>↑ 不可移除、不可新增、不可清空——整個 field 變「顯示」</Label>
|
|
@@ -116,7 +116,7 @@ value 軸 controlled-only;open 軸方向相反 — **uncontrolled-only**:`defaul
|
|
|
116
116
|
## readonly / disabled 的 Tag
|
|
117
117
|
|
|
118
118
|
- 沒有 dismiss 按鈕——不可操作
|
|
119
|
-
- ChevronDown
|
|
119
|
+
- ChevronDown:**readonly 不顯示**(純值、不可開下拉)/ **disabled 保留**(類型身份 indicator,`fg-disabled`,`pointer-events-none`)/ naked cell 依 `showDisplayEndIcon`(2026-06-26)
|
|
120
120
|
- 沒有 clear 按鈕——不可清除
|
|
121
121
|
- 溢出行為與 edit 模式相同(+N 指示器)
|
|
122
122
|
|
|
@@ -448,7 +448,7 @@ export interface ComboboxProps {
|
|
|
448
448
|
* **2026-05-13 v2 deprecate path**:原 PeoplePicker pass `{8}` 假設「Combobox tagPadding=4px,4+8=12」
|
|
449
449
|
* 但 `tagPadding[size]` 是 density-dependent calc `(field-height - icon-size) / 2`,只在 md size +
|
|
450
450
|
* default density 才 = 4px;其他 size/density 漂 6/8px → 4+8=12 公式破。改 PeoplePicker 直接 inject
|
|
451
|
-
* `!px-
|
|
451
|
+
* `!px-[var(--field-px)]` className 到 Combobox Field wrapper(per people-picker.spec.md:94 v2),`tagAreaPaddingLeftPx`
|
|
452
452
|
* 走 undefined。Future 仍保留此 prop 給其他 consumer 精準調整 padding,但 PeoplePicker 已不再用。
|
|
453
453
|
*/
|
|
454
454
|
tagAreaPaddingLeftPx?: number
|
|
@@ -488,6 +488,7 @@ function ReadonlyMultiSelect({
|
|
|
488
488
|
const variant = variantProp ?? 'default'
|
|
489
489
|
const sz = size ?? 'md'
|
|
490
490
|
const iconSize = sz === 'lg' ? 20 : 16
|
|
491
|
+
const tagHeight = sz === 'sm' ? 20 : 24
|
|
491
492
|
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
492
493
|
const hasTags = (value?.length ?? 0) > 0
|
|
493
494
|
|
|
@@ -527,8 +528,10 @@ function ReadonlyMultiSelect({
|
|
|
527
528
|
// 2026-05-18 #6A Round 1 Step 1/4(per user 拍板「決策6選a」+ codex M31 Step 5 verdict cite combobox.tsx:451):
|
|
528
529
|
// readonly/disabled path 對齊 L293 display wrapper 已 ship 的 overflow-hidden fix。
|
|
529
530
|
// M10 propagation:原 overflow-visible 讓 readonly tag 越界蓋 indicator,跟 display 不對稱。
|
|
530
|
-
|
|
531
|
-
|
|
531
|
+
// 2026-06-27 對齊 edit path(L598-617):wrap 時 items-start + chevron self-start/tagHeight 鎖第一行;
|
|
532
|
+
// paddingRight: var(--field-px) re-assert 右緣 12px(tagPadding 對稱 calc 會吃掉右緣,跟 edit 一致)。
|
|
533
|
+
wrap ? 'flex-wrap items-start py-1' : 'overflow-hidden', className)}
|
|
534
|
+
style={{ gap: GAP, paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode={resolvedMode}
|
|
532
535
|
aria-disabled={resolvedMode === 'disabled' ? true : undefined}>
|
|
533
536
|
{hasTags ? (
|
|
534
537
|
<ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap}
|
|
@@ -536,9 +539,9 @@ function ReadonlyMultiSelect({
|
|
|
536
539
|
) : (
|
|
537
540
|
<span className="text-fg-muted">{EMPTY_DISPLAY}</span>
|
|
538
541
|
)}
|
|
539
|
-
{/* 2026-06-
|
|
540
|
-
{(variant === 'naked' ? !!showDisplayEndIcon :
|
|
541
|
-
<ItemSuffix className=
|
|
542
|
+
{/* 2026-06-26 類型身份 indicator:edit 顯示 / readonly 不顯示(純值、不可開下拉) / disabled 保留(fg-disabled,對齊原生 <select disabled>);naked cell 依 showDisplayEndIcon */}
|
|
543
|
+
{(variant === 'naked' ? !!showDisplayEndIcon : resolvedMode === 'disabled') && (
|
|
544
|
+
<ItemSuffix className={cn('pointer-events-none', wrap && 'self-start')} style={wrap ? { height: tagHeight } : undefined}>
|
|
542
545
|
<ChevronDown size={iconSize} className={cn('shrink-0', resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted')} aria-hidden />
|
|
543
546
|
</ItemSuffix>
|
|
544
547
|
)}
|
|
@@ -575,14 +578,18 @@ function NativeCombobox({
|
|
|
575
578
|
const handleRemove = (v: string) => onChange?.(value.filter(x => x !== v))
|
|
576
579
|
const handleAdd = (v: string) => { if (!value.includes(v)) onChange?.([...value, v]) }
|
|
577
580
|
|
|
581
|
+
// React #310 fix(對齊 select.tsx):hooks 必在 conditional early-return 前無條件呼叫。
|
|
582
|
+
// resolvedMode 在 edit↔非edit 切換(<Field mode/disabled> cascade / DataTable cell 進出編輯)時
|
|
583
|
+
// hook 數量不可變動,否則 Rules of Hooks violation → React #310 「rendered fewer/more hooks」crash。
|
|
584
|
+
const selectRef = React.useRef<HTMLSelectElement>(null)
|
|
585
|
+
const tagAreaRef = React.useRef<HTMLDivElement>(null)
|
|
586
|
+
|
|
578
587
|
if (resolvedMode !== 'edit') {
|
|
579
588
|
return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />
|
|
580
589
|
}
|
|
581
590
|
|
|
582
591
|
const items = value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v }))
|
|
583
592
|
const unselected = options.filter(o => !value.includes(o.value))
|
|
584
|
-
const selectRef = React.useRef<HTMLSelectElement>(null)
|
|
585
|
-
const tagAreaRef = React.useRef<HTMLDivElement>(null)
|
|
586
593
|
const tagHeight = size === 'sm' ? 20 : 24
|
|
587
594
|
|
|
588
595
|
const selectDropdown = unselected.length > 0 ? (
|
|
@@ -597,7 +604,7 @@ function NativeCombobox({
|
|
|
597
604
|
return (
|
|
598
605
|
<div ref={__triggerRef} className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative',
|
|
599
606
|
wrap && 'items-start py-1', error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
|
|
600
|
-
style={{ paddingRight: '
|
|
607
|
+
style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode="edit" data-error={error ? '' : undefined}
|
|
601
608
|
onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>
|
|
602
609
|
{/* 2026-05-18 F2 sync(per user verbatim「modifying 修好 PeoplePicker stack 後改壞 Combobox tag display」
|
|
603
610
|
+ 「tag 應該要判斷所在空間最多可以呈現幾個tag(包括+n)去自動判斷何時要變成+n」):
|
|
@@ -671,10 +678,10 @@ function CustomCombobox({
|
|
|
671
678
|
|
|
672
679
|
React.useEffect(() => { if (!open) setSearch('') }, [open])
|
|
673
680
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
681
|
+
// React #310 fix(對齊 select.tsx):以下 hooks(useMemo/useRef)必在 conditional early-return 前
|
|
682
|
+
// 無條件呼叫。resolvedMode 在 edit↔非edit 切換時 hook 數量不可變動,否則 Rules of Hooks
|
|
683
|
+
// violation → React #310 「rendered fewer/more hooks」crash(ReadonlyMultiSelect 用不到這些值,
|
|
684
|
+
// 多算無害,對齊 select.tsx hoist pattern)。
|
|
678
685
|
const items = React.useMemo(
|
|
679
686
|
() => value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v })),
|
|
680
687
|
[value, options]
|
|
@@ -708,6 +715,10 @@ function CustomCombobox({
|
|
|
708
715
|
[filteredOptions]
|
|
709
716
|
)
|
|
710
717
|
|
|
718
|
+
if (resolvedMode !== 'edit') {
|
|
719
|
+
return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />
|
|
720
|
+
}
|
|
721
|
+
|
|
711
722
|
const chevronEl = <ChevronDown size={iconSize} className={cn('shrink-0 text-fg-muted transition-transform', open && 'rotate-180')} aria-hidden />
|
|
712
723
|
|
|
713
724
|
const trigger = (
|
|
@@ -725,7 +736,7 @@ function CustomCombobox({
|
|
|
725
736
|
// 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default
|
|
726
737
|
// 統一處理 — open=灰深(data-state)/ focus=藍(focus-within !important)。改一處全 control 跟動。
|
|
727
738
|
error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
|
|
728
|
-
style={{ paddingRight: '
|
|
739
|
+
style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }}
|
|
729
740
|
data-field-mode="edit" data-error={error ? '' : undefined}
|
|
730
741
|
// WAI-ARIA APG combobox 鍵盤開啟語意 — 對齊 sibling Select(select.tsx:593-598)。
|
|
731
742
|
// <div role=combobox> 不像 native <button> 自動 synthesize Enter/Space click,故顯式補:
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
CommandGroup,
|
|
15
15
|
CommandItem,
|
|
16
16
|
CommandSeparator,
|
|
17
|
+
CommandShortcut,
|
|
17
18
|
} from './command'
|
|
18
19
|
import { H3, Desc, Td, Th } from '@/design-system/stories-helpers/anatomy/anatomy-utils'
|
|
19
20
|
|
|
@@ -44,6 +45,7 @@ export const Overview: Story = {
|
|
|
44
45
|
<CommandItem>
|
|
45
46
|
<Settings className="mr-2 h-4 w-4" />
|
|
46
47
|
前往設定
|
|
48
|
+
<CommandShortcut>⌘K</CommandShortcut>
|
|
47
49
|
</CommandItem>
|
|
48
50
|
</CommandGroup>
|
|
49
51
|
<CommandSeparator />
|
|
@@ -135,7 +135,7 @@ const CommandItem = React.forwardRef<
|
|
|
135
135
|
<CommandPrimitive.Item
|
|
136
136
|
ref={ref}
|
|
137
137
|
className={cn(
|
|
138
|
-
"relative flex cursor-default gap-2 select-none items-center rounded-md px-2 py-1.5 text-body outline-none data-[disabled=true]:pointer-events-none data-[selected=
|
|
138
|
+
"relative flex cursor-default gap-2 select-none items-center rounded-md px-2 py-1.5 text-body outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-neutral-hover data-[selected=true]:text-foreground data-[disabled=true]:text-fg-disabled [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
139
139
|
className
|
|
140
140
|
)}
|
|
141
141
|
{...props}
|
|
@@ -164,7 +164,7 @@ CommandShortcut.displayName = "CommandShortcut"
|
|
|
164
164
|
// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
|
|
165
165
|
export const commandMeta = {
|
|
166
166
|
component: 'Command',
|
|
167
|
-
family:
|
|
167
|
+
family: 'composite', // 對齊 command.spec.md frontmatter family: composite(SSOT)
|
|
168
168
|
variants: {
|
|
169
169
|
|
|
170
170
|
},
|
|
@@ -174,6 +174,11 @@ function NumberCell({ value, meta, mode, size, isDisabled, onCommit, onCancel, o
|
|
|
174
174
|
// currency 透過 columnType-aware prefix:type='currency' → 預設 '$'(可 override)
|
|
175
175
|
const isCurrency = meta?.type === 'currency'
|
|
176
176
|
const prefix = isCurrency ? (meta?.prefix ?? '$') : meta?.prefix
|
|
177
|
+
// React #310 fix:useState 必在 display early-return 前無條件呼叫。同一 memo'd cell instance
|
|
178
|
+
// 在 display↔edit 切換時被重用(render site 無 key={mode},data-table.tsx:1352),hook 數量不可變,
|
|
179
|
+
// 否則 Rules of Hooks violation → React #310 crash。對齊 combobox/select hoist pattern。
|
|
180
|
+
const initial = typeof value === 'number' ? value : null
|
|
181
|
+
const [localValue, setLocalValue] = React.useState<number | null>(initial)
|
|
177
182
|
if (mode === 'display') {
|
|
178
183
|
return (
|
|
179
184
|
<NumberInput
|
|
@@ -193,8 +198,7 @@ function NumberCell({ value, meta, mode, size, isDisabled, onCommit, onCancel, o
|
|
|
193
198
|
// (`value={value ?? ''}`)— 若 NumberCell 以 `defaultValue` 傳入,NumberInput value=undefined → ''
|
|
194
199
|
// empty。對齊 cell-as-input「edit mode 自動帶入 display 值」(對齊 Notion / Airtable 共識),
|
|
195
200
|
// 改用 local state controlled。User typing → setLocalValue;blur/Enter → onCommit(localValue)。
|
|
196
|
-
|
|
197
|
-
const [localValue, setLocalValue] = React.useState<number | null>(initial)
|
|
201
|
+
// (initial + useState 已 hoist 到 display-return 前 — 見上方 React #310 fix。)
|
|
198
202
|
return (
|
|
199
203
|
<NumberInput
|
|
200
204
|
autoFocus
|
|
@@ -650,13 +650,13 @@ function ConjunctionLabel({
|
|
|
650
650
|
// index === 1:**唯一可改**的 AND/OR Select(連動整 group conjunction)
|
|
651
651
|
// index ≥ 2:被連動的 row,read-only 顯示當前 conjunction 文字(同 Where 視覺,A6 canonical)
|
|
652
652
|
// 對齊 Airtable / Notion / Linear 共識 @benchmark-unverified(non-OSS)
|
|
653
|
-
// px-
|
|
653
|
+
// px-[var(--field-px)] 對齊 Field 內部 padding 12px(Q13)
|
|
654
654
|
if (index === 0) {
|
|
655
|
-
return <div className="w-20 shrink-0 text-body text-fg-muted px-
|
|
655
|
+
return <div className="w-20 shrink-0 text-body text-fg-muted px-[var(--field-px)] self-center">Where</div>
|
|
656
656
|
}
|
|
657
657
|
if (index >= 2) {
|
|
658
658
|
const label = conjunction === 'and' ? 'And' : 'Or'
|
|
659
|
-
return <div className="w-20 shrink-0 text-body text-fg-muted px-
|
|
659
|
+
return <div className="w-20 shrink-0 text-body text-fg-muted px-[var(--field-px)] self-center">{label}</div>
|
|
660
660
|
}
|
|
661
661
|
// index === 1:可切換的 AND/OR Select
|
|
662
662
|
// minRows={2} — And/Or 2 選項,顯式縮 menu 高度避免 reserve 3 row 空白(Q5)
|
|
@@ -131,7 +131,7 @@ export const Inspector: Story = {
|
|
|
131
131
|
bordered: { control: 'boolean', description: '外框(嵌入已帶框容器時可設 false 避免雙重邊框)' },
|
|
132
132
|
pinnedLeft: { control: 'boolean', description: 'Pin 產品名稱欄到左側,橫向捲動時保持可見' },
|
|
133
133
|
pinnedRight: { control: 'boolean', description: 'Pin 上架日期欄到右側' },
|
|
134
|
-
inlineEdit: { control: 'boolean', description: 'inline edit 視覺:cell 間加垂直分隔線
|
|
134
|
+
inlineEdit: { control: 'boolean', description: 'inline edit 視覺:cell 間加垂直分隔線(dtCellGrid)。Select 欄 chevron 由 column meta.editable 控制(showDisplayEndIcon),非本 prop' },
|
|
135
135
|
height: {
|
|
136
136
|
control: 'select',
|
|
137
137
|
options: ['auto', '300px'],
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
───────────────────────────────────────────────────────── */
|
|
8
8
|
|
|
9
9
|
:root {
|
|
10
|
-
--table-cell-px:
|
|
10
|
+
--table-cell-px: var(--field-px); /* 預設取自 --field-px(form/cell 同 12px content gutter SSOT);仍是 DataTable-scoped named token、可獨立 override(spec field-controls.spec.md:330 scoped 決策不變) */
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
[data-table-size="sm"] {
|
|
@@ -386,12 +386,12 @@ ValueShape ↔ DS picker 對照(canonical 2026-05-02):
|
|
|
386
386
|
|
|
387
387
|
### 四、UI canonical
|
|
388
388
|
|
|
389
|
-
- 第 1 row conjunction 是靜態 `Where` label(`px-
|
|
389
|
+
- 第 1 row conjunction 是靜態 `Where` label(`px-[var(--field-px)]` 對齊下方 Field value 起點 = 12px)
|
|
390
390
|
- field 未選 → operator + value picker disabled;同 group 共用 conjunction(toggle 任一 → flip 整 group)
|
|
391
391
|
- **空狀態**:無 condition → 只顯 inline `+ 加篩選` CTA(對齊 Notion / Airtable / Linear,**禁止** auto-create 空 row) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
392
392
|
- **CTA 位置**:緊貼最後一條 row(text variant 輕量,**廢 SurfaceFooter**),條件與「加入」屬同一語境
|
|
393
393
|
- **Trash / 刪除**:row 是 form-control row → text Button(non Inline Action,違 item-anatomy canonical)
|
|
394
|
-
- **And/Or Select** `minRows={2}`(2 選項顯式縮 menu 高度);**Where padding** `px-
|
|
394
|
+
- **And/Or Select** `minRows={2}`(2 選項顯式縮 menu 高度);**Where padding** `px-[var(--field-px)]` align Field
|
|
395
395
|
- Header refresh icon:`value !== defaultValue` 顯;ButtonDivider 串接 close X(對齊欄位顯示 chrome canonical)
|
|
396
396
|
- **Relative date 群組**:`DATE_RELATIVE_GROUPS` Past / Current / Future,走 `<Select groups>`
|
|
397
397
|
- Trigger button checked(`aria-pressed`):`value` 有 ≥ 1 active condition → on(語意:資料被篩,獨立於 refresh)
|
|
@@ -39,7 +39,7 @@ const PARTS: Record<PartKey, PartSpec> = {
|
|
|
39
39
|
caption: { label: '月份標題', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'text-body font-medium' },
|
|
40
40
|
nav: { label: 'Nav 按鈕', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'Button variant=text size=xs iconOnly · hover 藍圈' },
|
|
41
41
|
weekday: { label: '星期標頭', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'text-body font-medium · h-7' },
|
|
42
|
-
day: { label: '日格(default)', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'h-field-sm w-field-sm rounded-full' },
|
|
42
|
+
day: { label: '日格(default)', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'h-field-sm w-[var(--field-height-sm)] rounded-full' },
|
|
43
43
|
daySelected: { label: 'Selected', bg: '--primary', text: 'white', border: 'transparent' },
|
|
44
44
|
dayToday: { label: 'Today(未選)', bg: 'transparent', text: '--foreground', border: 'transparent', extra: '數字下方藍色底線(underline bar)' },
|
|
45
45
|
dayHover: { label: 'Hover', bg: 'transparent', text: '--foreground', border: '--primary', extra: 'hover 藍圈 1.5px(無填底)' },
|
|
@@ -82,7 +82,7 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
|
|
|
82
82
|
|
|
83
83
|
因此用 `[&[data-range-middle=true]]:xxx` 這種 attribute selector **根本不會生效**(舊版做法錯誤)。
|
|
84
84
|
|
|
85
|
-
**正解**:把 state 樣式放進 `classNames[state]` 物件,v9 的 `getClassNamesForModifiers` 會在對應 modifier 為 true 時把該 key 的 class 附加到 Day CELL。範例:`classNames.range_middle: '
|
|
85
|
+
**正解**:把 state 樣式放進 `classNames[state]` 物件,v9 的 `getClassNamesForModifiers` 會在對應 modifier 為 true 時把該 key 的 class 附加到 Day CELL。範例:`classNames.range_middle: "before:content-[''] before:absolute before:inset-y-0 before:-inset-x-[2px] before:bg-neutral-selected [&>button]:!bg-transparent"`(用 `before:` pseudo + semantic `--neutral-selected` token,對齊下方 Range track canonical 與 tsx)。
|
|
86
86
|
|
|
87
87
|
`[&>button]:xxx` 從 cell 向內選子 button 用於 button-level 樣式(selected / disabled 的藍底白圓等)。
|
|
88
88
|
|