@qijenchen/design-system 0.1.0-beta.74 → 0.1.0-beta.75
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/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +27 -6
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +57 -34
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/ds-canonical/hooks/check_story_invariants.sh +26 -0
- package/ds-canonical/hooks/tests/test_check_story_invariants.sh +30 -0
- package/llms-full.txt +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/accordion.principles.stories.tsx +3 -3
- package/src/components/Alert/alert.principles.stories.tsx +5 -5
- package/src/components/AppShell/app-shell.principles.stories.tsx +6 -6
- package/src/components/AspectRatio/aspect-ratio.principles.stories.tsx +1 -1
- package/src/components/Avatar/avatar.principles.stories.tsx +3 -3
- package/src/components/Badge/badge.principles.stories.tsx +3 -3
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +3 -3
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +3 -3
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
- package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +2 -2
- package/src/components/BulkActionBar/bulk-action-bar.tsx +3 -2
- package/src/components/Button/button.principles.stories.tsx +3 -3
- package/src/components/Calendar/calendar.principles.stories.tsx +3 -3
- package/src/components/Carousel/carousel.principles.stories.tsx +2 -2
- package/src/components/Chart/chart.principles.stories.tsx +4 -4
- package/src/components/Checkbox/checkbox.principles.stories.tsx +2 -2
- package/src/components/Chip/chip.principles.stories.tsx +3 -3
- package/src/components/Coachmark/coachmark.principles.stories.tsx +3 -3
- package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
- package/src/components/Combobox/combobox.principles.stories.tsx +5 -5
- package/src/components/Command/command.principles.stories.tsx +7 -7
- package/src/components/DataTable/data-table.principles.stories.tsx +3 -3
- package/src/components/DataTable/data-table.spec.md +23 -15
- package/src/components/DataTable/data-table.stories.tsx +29 -25
- package/src/components/DataTable/data-table.tsx +92 -44
- package/src/components/DateGrid/date-grid.principles.stories.tsx +4 -4
- package/src/components/DatePicker/date-picker.principles.stories.tsx +5 -5
- package/src/components/DescriptionList/description-list.principles.stories.tsx +5 -5
- package/src/components/Dialog/dialog.principles.stories.tsx +4 -4
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +5 -5
- package/src/components/Empty/empty.principles.stories.tsx +2 -2
- package/src/components/Field/field.principles.stories.tsx +4 -4
- package/src/components/FileItem/file-item.principles.stories.tsx +5 -5
- package/src/components/FileUpload/file-upload.principles.stories.tsx +4 -4
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +5 -5
- package/src/components/HoverCard/hover-card.principles.stories.tsx +5 -5
- package/src/components/Input/input.principles.stories.tsx +4 -4
- package/src/components/LinkInput/link-input.principles.stories.tsx +5 -5
- package/src/components/Menu/menu-item.principles.stories.tsx +7 -7
- package/src/components/Notice/notice.principles.stories.tsx +7 -7
- package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +5 -5
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +3 -3
- package/src/components/Popover/popover.principles.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +2 -2
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +2 -2
- package/src/components/Rating/rating.principles.stories.tsx +3 -3
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +4 -4
- package/src/components/Select/select.principles.stories.tsx +5 -5
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +8 -8
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +7 -7
- package/src/components/Separator/separator.principles.stories.tsx +4 -4
- package/src/components/Sheet/sheet.principles.stories.tsx +2 -2
- package/src/components/Sidebar/sidebar.principles.stories.tsx +4 -4
- package/src/components/Skeleton/skeleton.principles.stories.tsx +5 -5
- package/src/components/Slider/slider.principles.stories.tsx +3 -3
- package/src/components/Steps/steps.principles.stories.tsx +4 -4
- package/src/components/Switch/switch.principles.stories.tsx +1 -1
- package/src/components/Tabs/tabs.principles.stories.tsx +3 -3
- package/src/components/Tag/tag.principles.stories.tsx +3 -3
- package/src/components/Textarea/textarea.principles.stories.tsx +2 -2
- package/src/components/TimePicker/time-picker.principles.stories.tsx +5 -5
- package/src/components/Toast/toast.principles.stories.tsx +2 -2
- package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
- package/src/components/TreeView/tree-view.principles.stories.tsx +5 -5
- package/src/components/TreeView/tree-view.stories.tsx +1 -1
- package/src/tokens/color/color.spec.md +2 -0
|
@@ -71,13 +71,13 @@ export const UsageGuidance: Story = {
|
|
|
71
71
|
<p>適合 DataTable 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
72
72
|
<ul className="space-y-1">
|
|
73
73
|
<li>
|
|
74
|
-
<LinkTo kind="Design System/Components/DataTable/展示" name="數字靠右對齊"><span className="text-primary hover:
|
|
74
|
+
<LinkTo kind="Design System/Components/DataTable/展示" name="數字靠右對齊"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">商品庫存後台 — 單價 / 金額數字靠右對齊</span></LinkTo>
|
|
75
75
|
</li>
|
|
76
76
|
<li>
|
|
77
|
-
<LinkTo kind="Design System/Components/DataTable/展示" name="自動行高"><span className="text-primary hover:
|
|
77
|
+
<LinkTo kind="Design System/Components/DataTable/展示" name="自動行高"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">商品備註長短不一 — 自動行高撐開列高</span></LinkTo>
|
|
78
78
|
</li>
|
|
79
79
|
<li>
|
|
80
|
-
<LinkTo kind="Design System/Components/DataTable/展示" name="空狀態"><span className="text-primary hover:
|
|
80
|
+
<LinkTo kind="Design System/Components/DataTable/展示" name="空狀態"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">目錄尚無商品 — 空狀態引導新增</span></LinkTo>
|
|
81
81
|
</li>
|
|
82
82
|
</ul>
|
|
83
83
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見 <code>Vs*Rule</code> stories)。</p>
|
|
@@ -263,18 +263,24 @@ DataTable 的 row selection layer。提供 controlled/uncontrolled state + 視
|
|
|
263
263
|
|
|
264
264
|
**世界級對照**:Material DataGrid `rowSelectionModel` / Polaris IndexTable `selectedResources` / Linear / Notion 全 controlled-first + uncontrolled fallback。AG Grid 的 imperative `gridRef.api` 不採(違背 React idiom + 既有 Field/Switch/Checkbox controllable 慣例)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
265
265
|
|
|
266
|
-
### 一、State 模式
|
|
266
|
+
### 一、State 模式(discriminated union,2026-06-22 支援反向選取 inverted)
|
|
267
267
|
|
|
268
268
|
```ts
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
// 選取模型:include(列舉)/ all(反向,全集 − excluded)
|
|
270
|
+
type DataTableSelection =
|
|
271
|
+
| { mode: 'include'; ids: string[] } // 只選 ids 列(預設)
|
|
272
|
+
| { mode: 'all'; excluded: string[] } // 全資料集(filter 後)選取,扣掉 excluded
|
|
273
|
+
|
|
274
|
+
selection?: string[] | DataTableSelection // controlled;傳 string[] = include shorthand(向後相容)
|
|
275
|
+
defaultSelection?: string[] | DataTableSelection // uncontrolled
|
|
276
|
+
onSelectionChange?: (next: DataTableSelection) => void // 一律 emit union
|
|
277
|
+
totalCount?: number // 全集筆數 M(server-side / filter 後);all 模式計數用
|
|
278
|
+
selectable?: boolean | 'single' | 'multi' // default 'multi';single 永遠 include
|
|
273
279
|
isRowSelectable?: (row: TData) => boolean
|
|
274
280
|
preserveSelectionOnFilter?: boolean // default false
|
|
275
281
|
```
|
|
276
282
|
|
|
277
|
-
對齊 `useControllableState` idiom(Field / Switch / Checkbox 已用)。
|
|
283
|
+
對齊 `useControllableState` idiom(Field / Switch / Checkbox 已用)+ MUI X DataGrid v8 `rowSelectionModel { type:'include'|'exclude', ids }` / AG Grid `selectAll + toggledNodes` 反向選取共識。**計數(consumer)**:`mode==='all' ? totalCount − excluded.length : ids.length`。**向後相容**:傳 `string[]` 自動正規化為 `{ mode:'include' }`;但 `onSelectionChange` 一律 emit union(consumer 讀取端需處理兩 mode)。
|
|
278
284
|
|
|
279
285
|
### 二、Checkbox column
|
|
280
286
|
|
|
@@ -283,15 +289,16 @@ preserveSelectionOnFilter?: boolean // default false
|
|
|
283
289
|
- **顯示時機**:**always visible**(對齊 Linear 2024 / Polaris / Material consensus,不允 hover-show) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
284
290
|
- **Header tri-state**:none / indeterminate / all,使用既有 Checkbox `indeterminate` prop
|
|
285
291
|
|
|
286
|
-
### 三、全選邏輯(2-step pattern)
|
|
292
|
+
### 三、全選邏輯(2-step pattern + 反向選取 inverted)
|
|
287
293
|
|
|
288
|
-
對齊 ref 圖 + Linear / Gmail / Notion
|
|
294
|
+
對齊 ref 圖 + Linear / Gmail / Notion 2-step + MUI X v8 / AG Grid inverted: <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
289
295
|
|
|
290
|
-
1. Header checkbox click(none → all)→ 選**目前可見** rows(filter 後 visible-only)
|
|
291
|
-
2. 全頁可見已選 → BulkActionBar
|
|
292
|
-
3. 點 hint → 擴 dataset 全選,hint 改:「已選取全部 M 個。**清除選取項目**」
|
|
296
|
+
1. Header checkbox click(none → all)→ 選**目前可見** rows(filter 後 visible-only)= `{ mode:'include', ids:[…visible] }`
|
|
297
|
+
2. 全頁可見已選 → BulkActionBar hint:「已選取本頁 N 個。**點此選取全部 M 個**」
|
|
298
|
+
3. 點 hint → consumer `setSelection({ mode:'all', excluded:[] })` 擴 dataset 全選,hint 改:「已選取全部 M 個。**清除選取項目**」
|
|
299
|
+
4. **反向選取(inverted)**:all 模式下取消勾選某幾筆 → 加進 `excluded`(`選取 = 全集 − excluded`);再勾回 → 移出 `excluded`。對 10k 筆只載 50 筆**不需列舉其餘 ID**,任意 toggle 順序封閉、O(1)。count = `totalCount − excluded.length`,hint 顯示「已選取全部 M 個(排除 K 個)」。
|
|
293
300
|
|
|
294
|
-
|
|
301
|
+
不**一鍵**直接擴 dataset(避免誤觸大量資料,必先 2-step);擴選後的反向扣除由 inverted 模型自動處理。
|
|
295
302
|
|
|
296
303
|
### 四、互動
|
|
297
304
|
|
|
@@ -308,9 +315,10 @@ preserveSelectionOnFilter?: boolean // default false
|
|
|
308
315
|
|
|
309
316
|
### 六、Selection × filter / sort 互動
|
|
310
317
|
|
|
311
|
-
-
|
|
312
|
-
-
|
|
313
|
-
-
|
|
318
|
+
- **`include` 模式**:filter 套用 → filtered-out 的 selected rows 預設清掉(對齊 Material / AG Grid / Polaris / GitHub / Gmail consensus) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
319
|
+
- **`all`(反向)模式**:語意 = 「全部**符合當前 filter**的列 − excluded」→ filter 變動時 selection set 隨 filter **自然重算**(M 跟著變),`excluded` 保留不清(被 filter 掉的 excluded 列無害,回到該 filter 時仍排除);**不**套用上面的 include-mode 清除。consumer 計數用更新後的 `totalCount`。
|
|
320
|
+
- **opt-in `preserveSelectionOnFilter={true}`**(僅 include 模式)→ 給 productivity scope(Linear / Airtable 用法),保留 hidden selected,BulkActionBar 顯示「{visible} selected ({hidden} hidden by filter)」
|
|
321
|
+
- sort 套用 → selection 全保留(sort 不影響可見性,兩 mode 同)
|
|
314
322
|
|
|
315
323
|
### 七、BulkActionBar 整合(inline composition canonical)
|
|
316
324
|
|
|
@@ -4,7 +4,7 @@ import React from 'react'
|
|
|
4
4
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
5
5
|
import { createColumnHelper, type ColumnDef } from '@tanstack/react-table'
|
|
6
6
|
import { Pencil, Trash2, MoreVertical, Search, Filter, Eye, Download, Plus, ArrowUpDown } from 'lucide-react'
|
|
7
|
-
import { DataTable } from './data-table'
|
|
7
|
+
import { DataTable, type DataTableSelection } from './data-table'
|
|
8
8
|
import { DataTableSortManager } from './data-table-sort-manager'
|
|
9
9
|
import { DataTableColumnVisibilityPanel } from './data-table-column-visibility-panel'
|
|
10
10
|
import { DataTableFilterPanel, evaluateTree, createEmptyFilterTree, isFilterTreeActive, type FilterTree } from './data-table-filter-panel'
|
|
@@ -948,8 +948,7 @@ export const WithBulkActions: Story = {
|
|
|
948
948
|
name: '選取 + 批次操作',
|
|
949
949
|
parameters: { layout: 'fullscreen' },
|
|
950
950
|
render: () => {
|
|
951
|
-
const [selection, setSelection] = React.useState<
|
|
952
|
-
const [allSelected, setAllSelected] = React.useState(false)
|
|
951
|
+
const [selection, setSelection] = React.useState<DataTableSelection>({ mode: 'include', ids: [] })
|
|
953
952
|
const [search, setSearch] = React.useState('')
|
|
954
953
|
const [columnVisibility, setColumnVisibility] = React.useState<Record<string, boolean>>({})
|
|
955
954
|
// Issue 3(2026-05-10):columnSearch 移到 `<DataTableColumnVisibilityPanel>` 內 own,
|
|
@@ -968,13 +967,16 @@ export const WithBulkActions: Story = {
|
|
|
968
967
|
[search]
|
|
969
968
|
)
|
|
970
969
|
const VISIBLE = filteredData.length
|
|
971
|
-
//
|
|
972
|
-
//
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
970
|
+
// 反向選取(inverted)showcase:include 全可見已選且 dataset 更大 → offer「選取全部 M」;
|
|
971
|
+
// all 模式 → 顯示「已選取全部 M(排除 K)」。count = M − excluded(consumer 端計算)。
|
|
972
|
+
const isAll = selection.mode === 'all'
|
|
973
|
+
const visibleIds = filteredData.map((p) => p.sku)
|
|
974
|
+
const selectedCount = isAll ? TOTAL - selection.excluded.length : selection.ids.length
|
|
975
|
+
const visibleSelectedIds = isAll
|
|
976
|
+
? visibleIds.filter((id) => !selection.excluded.includes(id))
|
|
977
|
+
: selection.ids.filter((id) => visibleIds.includes(id))
|
|
978
|
+
const allVisibleSelected = VISIBLE > 0 && visibleSelectedIds.length === VISIBLE
|
|
979
|
+
const showHint = isAll || (allVisibleSelected && TOTAL > VISIBLE)
|
|
978
980
|
|
|
979
981
|
return (
|
|
980
982
|
// 撐滿 parent(layout=fullscreen);
|
|
@@ -1068,6 +1070,7 @@ export const WithBulkActions: Story = {
|
|
|
1068
1070
|
selectable
|
|
1069
1071
|
selection={selection}
|
|
1070
1072
|
onSelectionChange={setSelection}
|
|
1073
|
+
totalCount={TOTAL}
|
|
1071
1074
|
columnVisibility={columnVisibility}
|
|
1072
1075
|
onColumnVisibilityChange={setColumnVisibility}
|
|
1073
1076
|
getRowId={(row) => row.sku}
|
|
@@ -1092,7 +1095,7 @@ export const WithBulkActions: Story = {
|
|
|
1092
1095
|
|
|
1093
1096
|
{/* 底部 chrome group(撤回前一版 absolute overlay,2026-05-04 user 抓 BulkActionBar 沒底色 + 蓋表底列 regression):
|
|
1094
1097
|
回 flex flow 自然推 table。Q7 mount-time growth 真因 = virtualizer estimateRowHeight ≠ token,已在 DataTable 內修(estimate size-aware) */}
|
|
1095
|
-
{(showHint ||
|
|
1098
|
+
{(showHint || selectedCount > 0) && (
|
|
1096
1099
|
<div className="flex flex-col">
|
|
1097
1100
|
{showHint && (
|
|
1098
1101
|
<Alert
|
|
@@ -1100,24 +1103,24 @@ export const WithBulkActions: Story = {
|
|
|
1100
1103
|
placement="fixed"
|
|
1101
1104
|
dismissible={false}
|
|
1102
1105
|
title={
|
|
1103
|
-
|
|
1106
|
+
isAll ? (
|
|
1104
1107
|
<>
|
|
1105
|
-
已選取全部 {TOTAL}
|
|
1108
|
+
已選取全部 {TOTAL} 個項目{selection.excluded.length > 0 ? `(排除 ${selection.excluded.length} 個)` : ''}。{' '}
|
|
1106
1109
|
<button
|
|
1107
1110
|
type="button"
|
|
1108
|
-
onClick={() => {
|
|
1109
|
-
className="text-primary hover:
|
|
1111
|
+
onClick={() => setSelection({ mode: 'include', ids: [] })}
|
|
1112
|
+
className="text-primary hover:text-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
|
|
1110
1113
|
>
|
|
1111
1114
|
清除選取項目
|
|
1112
1115
|
</button>
|
|
1113
1116
|
</>
|
|
1114
1117
|
) : (
|
|
1115
1118
|
<>
|
|
1116
|
-
已選取本頁全部 {
|
|
1119
|
+
已選取本頁全部 {selectedCount} 個。{' '}
|
|
1117
1120
|
<button
|
|
1118
1121
|
type="button"
|
|
1119
|
-
onClick={() =>
|
|
1120
|
-
className="text-primary hover:
|
|
1122
|
+
onClick={() => setSelection({ mode: 'all', excluded: [] })}
|
|
1123
|
+
className="text-primary hover:text-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
|
|
1121
1124
|
>
|
|
1122
1125
|
點此選取全部 {TOTAL} 個項目
|
|
1123
1126
|
</button>
|
|
@@ -1126,10 +1129,11 @@ export const WithBulkActions: Story = {
|
|
|
1126
1129
|
}
|
|
1127
1130
|
/>
|
|
1128
1131
|
)}
|
|
1129
|
-
{
|
|
1132
|
+
{selectedCount > 0 && (
|
|
1130
1133
|
<BulkActionBar
|
|
1131
|
-
selection={
|
|
1132
|
-
|
|
1134
|
+
selection={visibleSelectedIds}
|
|
1135
|
+
totalSelected={selectedCount}
|
|
1136
|
+
onClear={() => setSelection({ mode: 'include', ids: [] })}
|
|
1133
1137
|
actions={
|
|
1134
1138
|
<>
|
|
1135
1139
|
<Button variant="tertiary" size="md" startIcon={Download}>下載</Button>
|
|
@@ -1165,7 +1169,7 @@ export const SelectionKeyboardAndShift: Story = {
|
|
|
1165
1169
|
height="400px"
|
|
1166
1170
|
selectable
|
|
1167
1171
|
selection={selection}
|
|
1168
|
-
onSelectionChange={setSelection}
|
|
1172
|
+
onSelectionChange={(s) => setSelection(s.mode === 'include' ? s.ids : [])}
|
|
1169
1173
|
getRowId={(row) => row.sku}
|
|
1170
1174
|
/>
|
|
1171
1175
|
</div>
|
|
@@ -1192,7 +1196,7 @@ export const SelectionSingleMode: Story = {
|
|
|
1192
1196
|
height="auto"
|
|
1193
1197
|
selectable="single"
|
|
1194
1198
|
selection={selection}
|
|
1195
|
-
onSelectionChange={setSelection}
|
|
1199
|
+
onSelectionChange={(s) => setSelection(s.mode === 'include' ? s.ids : [])}
|
|
1196
1200
|
getRowId={(row) => row.sku}
|
|
1197
1201
|
/>
|
|
1198
1202
|
</div>
|
|
@@ -1216,7 +1220,7 @@ export const SelectionDisabledRows: Story = {
|
|
|
1216
1220
|
height="auto"
|
|
1217
1221
|
selectable
|
|
1218
1222
|
selection={selection}
|
|
1219
|
-
onSelectionChange={setSelection}
|
|
1223
|
+
onSelectionChange={(s) => setSelection(s.mode === 'include' ? s.ids : [])}
|
|
1220
1224
|
getRowId={(row) => row.sku}
|
|
1221
1225
|
isRowSelectable={(row) => row.stock !== 'Out of stock'}
|
|
1222
1226
|
/>
|
|
@@ -1819,7 +1823,7 @@ export const RoadmapAllInOne: Story = {
|
|
|
1819
1823
|
inlineEdit
|
|
1820
1824
|
selectable
|
|
1821
1825
|
selection={selection}
|
|
1822
|
-
onSelectionChange={setSelection}
|
|
1826
|
+
onSelectionChange={(s) => setSelection(s.mode === 'include' ? s.ids : [])}
|
|
1823
1827
|
columnVisibility={columnVisibility}
|
|
1824
1828
|
onColumnVisibilityChange={setColumnVisibility}
|
|
1825
1829
|
pinnedLeftColumns={['id']}
|
|
@@ -134,12 +134,14 @@ export interface DataTableProps<TData>
|
|
|
134
134
|
spreadsheetMode?: boolean
|
|
135
135
|
|
|
136
136
|
// ── L2 Selection(see data-table.spec.md「L2 選取」)──
|
|
137
|
-
/**
|
|
138
|
-
selection?: string[]
|
|
139
|
-
/** 預設選取(uncontrolled) */
|
|
140
|
-
defaultSelection?: string[]
|
|
141
|
-
/** Selection 變更 callback */
|
|
142
|
-
onSelectionChange?: (next:
|
|
137
|
+
/** 已選列(controlled)。傳 string[] = include shorthand;傳 DataTableSelection 支援反向選取(all + excluded) */
|
|
138
|
+
selection?: string[] | DataTableSelection
|
|
139
|
+
/** 預設選取(uncontrolled);同上接受 string[] 或 DataTableSelection */
|
|
140
|
+
defaultSelection?: string[] | DataTableSelection
|
|
141
|
+
/** Selection 變更 callback(emit DataTableSelection union;include / all 兩模型) */
|
|
142
|
+
onSelectionChange?: (next: DataTableSelection) => void
|
|
143
|
+
/** 全資料集筆數 M(server-side / filter 後);all 模式 count = totalCount − excluded.length(consumer 計算) */
|
|
144
|
+
totalCount?: number
|
|
143
145
|
/** 是否啟用 selection / 模式;true 等同 'multi' */
|
|
144
146
|
selectable?: boolean | 'single' | 'multi'
|
|
145
147
|
/** Row 是否可選(disabled rows 只 disable checkbox,row 內容正常 render) */
|
|
@@ -264,6 +266,48 @@ const cellEditId = (rowId: string, colId: string) => `${rowId}__${colId}`
|
|
|
264
266
|
// (詳 ./data-table.css)— consumer 可走 CSS override 改值,不再 hard-code in TS。
|
|
265
267
|
// L2 selection 內部 column id(避免 magic string 重複)
|
|
266
268
|
const SELECT_COL_ID = '__select__'
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* L2 選取模型(discriminated union,2026-06-22 對齊 MUI X DataGrid v8
|
|
272
|
+
* rowSelectionModel { type: include | exclude, ids } + AG Grid selectAll + toggledNodes)。
|
|
273
|
+
*
|
|
274
|
+
* - mode==='include' (ids):只選 ids 列(預設;= 載入/可見列逐一選取)。
|
|
275
|
+
* - mode==='all' (excluded):全資料集(filter 後)選取,扣掉 excluded —— 反向選取(inverted)。
|
|
276
|
+
* 解決「全選 10k 筆只載 50 筆 → 無法列舉其餘 ID」:all 模式「選取 = 全集 − excluded」,
|
|
277
|
+
* 任何 toggle 都只是 excluded 的 add/remove,對任意順序封閉、O(1)、不需列舉未載入 ID。
|
|
278
|
+
*
|
|
279
|
+
* 計數(consumer):mode==='all' ? totalCount − excluded.length : ids.length(需傳 totalCount)。
|
|
280
|
+
* 進入 all 模式:consumer 在「選取全部 M」hint 點擊時 setSelection({ mode: 'all', excluded: [] })。
|
|
281
|
+
*/
|
|
282
|
+
export type DataTableSelection =
|
|
283
|
+
| { mode: 'include'; ids: string[] }
|
|
284
|
+
| { mode: 'all'; excluded: string[] }
|
|
285
|
+
|
|
286
|
+
// 正規化:string[] shorthand → { mode:'include' }(向後相容既有 consumer 傳 ID 陣列)
|
|
287
|
+
function normalizeSelection(
|
|
288
|
+
input: string[] | DataTableSelection | undefined,
|
|
289
|
+
): DataTableSelection | undefined {
|
|
290
|
+
if (input === undefined) return undefined
|
|
291
|
+
if (Array.isArray(input)) return { mode: 'include', ids: input }
|
|
292
|
+
return input
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// union-aware「把 ids 設成 willSelect 狀態」純函式(對任意 toggle 順序封閉)
|
|
296
|
+
function applySelectIds(
|
|
297
|
+
sel: DataTableSelection,
|
|
298
|
+
ids: string[],
|
|
299
|
+
willSelect: boolean,
|
|
300
|
+
): DataTableSelection {
|
|
301
|
+
if (sel.mode === 'include') {
|
|
302
|
+
const set = new Set(sel.ids)
|
|
303
|
+
ids.forEach((id) => { if (willSelect) set.add(id); else set.delete(id) })
|
|
304
|
+
return { mode: 'include', ids: Array.from(set) }
|
|
305
|
+
}
|
|
306
|
+
// all 模式:選取 = 全集 − excluded → willSelect 移出 excluded;取消選取 加進 excluded
|
|
307
|
+
const set = new Set(sel.excluded)
|
|
308
|
+
ids.forEach((id) => { if (willSelect) set.delete(id); else set.add(id) })
|
|
309
|
+
return { mode: 'all', excluded: Array.from(set) }
|
|
310
|
+
}
|
|
267
311
|
const cellPadding: React.CSSProperties = { paddingBlock: 'var(--table-cell-py)', paddingInline: 'var(--table-cell-px)' }
|
|
268
312
|
const HEADER_BG = 'bg-muted'
|
|
269
313
|
|
|
@@ -774,6 +818,7 @@ function DataTableInner<TData>(
|
|
|
774
818
|
estimateRowHeight, tableOptions, rowActions, cellErrors,
|
|
775
819
|
pinnedLeftColumns, pinnedRightColumns, inlineEdit = false,
|
|
776
820
|
selection: selectionProp, defaultSelection, onSelectionChange,
|
|
821
|
+
totalCount,
|
|
777
822
|
selectable = false, isRowSelectable, getRowId, getRowAriaLabel,
|
|
778
823
|
preserveSelectionOnFilter = false,
|
|
779
824
|
columnVisibility: columnVisibilityProp, defaultColumnVisibility, onColumnVisibilityChange,
|
|
@@ -887,9 +932,9 @@ function DataTableInner<TData>(
|
|
|
887
932
|
// ── L2 Selection state ──
|
|
888
933
|
const enabled = selectable !== false
|
|
889
934
|
const mode = selectable === 'single' ? 'single' : 'multi'
|
|
890
|
-
const [selection, setSelection] = useControllable<
|
|
891
|
-
value: selectionProp,
|
|
892
|
-
defaultValue: defaultSelection ?? [],
|
|
935
|
+
const [selection, setSelection] = useControllable<DataTableSelection>({
|
|
936
|
+
value: normalizeSelection(selectionProp),
|
|
937
|
+
defaultValue: normalizeSelection(defaultSelection) ?? { mode: 'include', ids: [] },
|
|
893
938
|
onChange: onSelectionChange,
|
|
894
939
|
})
|
|
895
940
|
// Shift-click anchor:存最後一次「單擊」的 row id,shift-click 時做區間選
|
|
@@ -1381,7 +1426,7 @@ function DataTableInner<TData>(
|
|
|
1381
1426
|
// 內部 checkbox/radio 用 stopPropagation 避免 double-fire
|
|
1382
1427
|
const onCellClick = isDisabled ? undefined : (e: React.MouseEvent) => {
|
|
1383
1428
|
e.stopPropagation()
|
|
1384
|
-
if (mode === 'single') setSelection([rowId])
|
|
1429
|
+
if (mode === 'single') setSelection({ mode: 'include', ids: [rowId] })
|
|
1385
1430
|
else toggleRow(rowId, rowOriginal, { shiftKey: e.shiftKey })
|
|
1386
1431
|
}
|
|
1387
1432
|
return (
|
|
@@ -1408,7 +1453,7 @@ function DataTableInner<TData>(
|
|
|
1408
1453
|
) : (
|
|
1409
1454
|
<Checkbox
|
|
1410
1455
|
size={checkboxSize}
|
|
1411
|
-
checked={
|
|
1456
|
+
checked={isSelectedId(rowId)}
|
|
1412
1457
|
disabled={isDisabled}
|
|
1413
1458
|
aria-label={ariaLabel}
|
|
1414
1459
|
onClick={(e) => {
|
|
@@ -1679,8 +1724,10 @@ function DataTableInner<TData>(
|
|
|
1679
1724
|
React.useEffect(() => {
|
|
1680
1725
|
if (!enabled || preserveSelectionOnFilter) return
|
|
1681
1726
|
setSelection(prev => {
|
|
1682
|
-
|
|
1683
|
-
|
|
1727
|
+
// all 模式 = 「全部符合當前 filter」→ 不清(excluded 留著,被 filter 掉的 excluded 列無害)
|
|
1728
|
+
if (prev.mode === 'all') return prev
|
|
1729
|
+
const filtered = prev.ids.filter(id => visibleRowIdsSet.has(id))
|
|
1730
|
+
return filtered.length === prev.ids.length ? prev : { mode: 'include', ids: filtered }
|
|
1684
1731
|
})
|
|
1685
1732
|
}, [visibleRowIdsKey, enabled, preserveSelectionOnFilter, visibleRowIdsSet, setSelection])
|
|
1686
1733
|
|
|
@@ -1692,9 +1739,22 @@ function DataTableInner<TData>(
|
|
|
1692
1739
|
.map(r => r.id)
|
|
1693
1740
|
}, [rows, enabled, isRowSelectable])
|
|
1694
1741
|
|
|
1742
|
+
// Union-aware「某列是否選取」+ 計數(include = ids 內;all = 不在 excluded 內)
|
|
1743
|
+
const includeSet = React.useMemo(
|
|
1744
|
+
() => (selection.mode === 'include' ? new Set(selection.ids) : new Set<string>()),
|
|
1745
|
+
[selection],
|
|
1746
|
+
)
|
|
1747
|
+
const excludeSet = React.useMemo(
|
|
1748
|
+
() => (selection.mode === 'all' ? new Set(selection.excluded) : new Set<string>()),
|
|
1749
|
+
[selection],
|
|
1750
|
+
)
|
|
1751
|
+
const isSelectedId = React.useCallback(
|
|
1752
|
+
(id: string) => (selection.mode === 'include' ? includeSet.has(id) : !excludeSet.has(id)),
|
|
1753
|
+
[selection.mode, includeSet, excludeSet],
|
|
1754
|
+
)
|
|
1755
|
+
const hasAnySelection = selection.mode === 'all' || includeSet.size > 0
|
|
1695
1756
|
// Header tri-state checkbox value
|
|
1696
|
-
const
|
|
1697
|
-
const visibleSelectedCount = selectableVisibleIds.filter(id => selectionSet.has(id)).length
|
|
1757
|
+
const visibleSelectedCount = selectableVisibleIds.filter(id => isSelectedId(id)).length
|
|
1698
1758
|
const headerCheckedState: boolean | 'indeterminate' =
|
|
1699
1759
|
selectableVisibleIds.length === 0 ? false
|
|
1700
1760
|
: visibleSelectedCount === 0 ? false
|
|
@@ -1708,20 +1768,16 @@ function DataTableInner<TData>(
|
|
|
1708
1768
|
)
|
|
1709
1769
|
|
|
1710
1770
|
const toggleHeaderCheckbox = React.useCallback(() => {
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
} else {
|
|
1716
|
-
// 選全可見(扣除 disabled);保留可見外的既有 selection
|
|
1717
|
-
setSelection(prev => Array.from(new Set([...prev, ...selectableVisibleIds])))
|
|
1718
|
-
}
|
|
1771
|
+
// header tri-state visible-scoped:全可見已選 → 取消可見;否則 → 選全可見。
|
|
1772
|
+
// include / all 兩模型由 applySelectIds 處理(all 模式 toggle 改寫 excluded)。
|
|
1773
|
+
const willSelect = headerCheckedState !== true
|
|
1774
|
+
setSelection(prev => applySelectIds(prev, selectableVisibleIds, willSelect))
|
|
1719
1775
|
}, [headerCheckedState, selectableVisibleIds, setSelection])
|
|
1720
1776
|
|
|
1721
1777
|
const toggleRow = React.useCallback((rowId: string, rowOriginal: TData, opts?: { shiftKey?: boolean }) => {
|
|
1722
1778
|
if (isRowSelectable && !isRowSelectable(rowOriginal)) return
|
|
1723
1779
|
if (mode === 'single') {
|
|
1724
|
-
setSelection(
|
|
1780
|
+
setSelection(isSelectedId(rowId) ? { mode: 'include', ids: [] } : { mode: 'include', ids: [rowId] })
|
|
1725
1781
|
anchorRowIdRef.current = rowId
|
|
1726
1782
|
return
|
|
1727
1783
|
}
|
|
@@ -1739,24 +1795,16 @@ function DataTableInner<TData>(
|
|
|
1739
1795
|
return row && (!isRowSelectable || isRowSelectable(row.original))
|
|
1740
1796
|
})
|
|
1741
1797
|
// Mail / GitHub 慣例:shift-click 把 range 全變「rowId 點擊後該變的狀態」
|
|
1742
|
-
const willCheck = !
|
|
1743
|
-
setSelection(prev =>
|
|
1744
|
-
const set = new Set(prev)
|
|
1745
|
-
rangeIds.forEach(id => willCheck ? set.add(id) : set.delete(id))
|
|
1746
|
-
return Array.from(set)
|
|
1747
|
-
})
|
|
1798
|
+
const willCheck = !isSelectedId(rowId)
|
|
1799
|
+
setSelection(prev => applySelectIds(prev, rangeIds, willCheck))
|
|
1748
1800
|
return
|
|
1749
1801
|
}
|
|
1750
1802
|
}
|
|
1751
|
-
// 一般 toggle + 更新 anchor
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
if (set.has(rowId)) set.delete(rowId)
|
|
1755
|
-
else set.add(rowId)
|
|
1756
|
-
return Array.from(set)
|
|
1757
|
-
})
|
|
1803
|
+
// 一般 toggle + 更新 anchor(include / all 由 applySelectIds 處理)
|
|
1804
|
+
const willCheck = !isSelectedId(rowId)
|
|
1805
|
+
setSelection(prev => applySelectIds(prev, [rowId], willCheck))
|
|
1758
1806
|
anchorRowIdRef.current = rowId
|
|
1759
|
-
}, [isRowSelectable, mode,
|
|
1807
|
+
}, [isRowSelectable, mode, isSelectedId, rows, visibleIdToRow, setSelection])
|
|
1760
1808
|
|
|
1761
1809
|
// ── Cmd+A / Esc / Arrow keys 鍵盤 handler(table-level)──
|
|
1762
1810
|
// code-quality-allow: long-function — single keyboard dispatch covering Cmd+A / Esc / Arrow / Space + selection state mutations,拆 sub-handler 會切散 keyboard mode coherence
|
|
@@ -1823,18 +1871,18 @@ function DataTableInner<TData>(
|
|
|
1823
1871
|
// Cmd/Ctrl+A:選全可見(扣 disabled)— 對齊 Mail / GitHub / Linear 慣例
|
|
1824
1872
|
if ((e.metaKey || e.ctrlKey) && e.key === 'a' && mode === 'multi') {
|
|
1825
1873
|
e.preventDefault()
|
|
1826
|
-
setSelection(prev =>
|
|
1874
|
+
setSelection(prev => applySelectIds(prev, selectableVisibleIds, true))
|
|
1827
1875
|
return
|
|
1828
1876
|
}
|
|
1829
1877
|
// Esc:clear selection
|
|
1830
|
-
if (e.key === 'Escape' &&
|
|
1878
|
+
if (e.key === 'Escape' && hasAnySelection) {
|
|
1831
1879
|
e.preventDefault()
|
|
1832
|
-
setSelection([])
|
|
1880
|
+
setSelection({ mode: 'include', ids: [] })
|
|
1833
1881
|
anchorRowIdRef.current = null
|
|
1834
1882
|
return
|
|
1835
1883
|
}
|
|
1836
1884
|
},
|
|
1837
|
-
[enabled, mode,
|
|
1885
|
+
[enabled, mode, hasAnySelection, selectableVisibleIds, setSelection,
|
|
1838
1886
|
spreadsheetMode, selectedCellId, editingCellId, table, isCellEditable]
|
|
1839
1887
|
)
|
|
1840
1888
|
|
|
@@ -2906,8 +2954,8 @@ function DataTableInner<TData>(
|
|
|
2906
2954
|
if (enabled && mode === 'single') {
|
|
2907
2955
|
return (
|
|
2908
2956
|
<RadioGroupPrimitive.Root
|
|
2909
|
-
value={selection[0] ?? ''}
|
|
2910
|
-
onValueChange={(v) => v && setSelection([v])}
|
|
2957
|
+
value={selection.mode === 'include' ? (selection.ids[0] ?? '') : ''}
|
|
2958
|
+
onValueChange={(v) => v && setSelection({ mode: 'include', ids: [v] })}
|
|
2911
2959
|
>
|
|
2912
2960
|
{wrapWithDnd(tableContent)}
|
|
2913
2961
|
</RadioGroupPrimitive.Root>
|
|
@@ -71,16 +71,16 @@ export const UsageGuidance: Story = {
|
|
|
71
71
|
<p>適合 DateGrid 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
72
72
|
<ul className="space-y-1">
|
|
73
73
|
<li>
|
|
74
|
-
<LinkTo kind="Design System/Internal/DateGrid/展示" name="單日 — 生日 / 到期日"><span className="text-primary hover:
|
|
74
|
+
<LinkTo kind="Design System/Internal/DateGrid/展示" name="單日 — 生日 / 到期日"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Single — 生日 / 到期日</span></LinkTo>
|
|
75
75
|
</li>
|
|
76
76
|
<li>
|
|
77
|
-
<LinkTo kind="Design System/Internal/DateGrid/展示" name="多日 — 活動可參加日期"><span className="text-primary hover:
|
|
77
|
+
<LinkTo kind="Design System/Internal/DateGrid/展示" name="多日 — 活動可參加日期"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Multiple — 活動可參加日期</span></LinkTo>
|
|
78
78
|
</li>
|
|
79
79
|
<li>
|
|
80
|
-
<LinkTo kind="Design System/Internal/DateGrid/展示" name="範圍 — 分析時段 / 訂單範圍"><span className="text-primary hover:
|
|
80
|
+
<LinkTo kind="Design System/Internal/DateGrid/展示" name="範圍 — 分析時段 / 訂單範圍"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Range — 分析時段 / 訂單範圍</span></LinkTo>
|
|
81
81
|
</li>
|
|
82
82
|
<li>
|
|
83
|
-
<LinkTo kind="Design System/Internal/DateGrid/展示" name="行內 — 儀表板小卡"><span className="text-primary hover:
|
|
83
|
+
<LinkTo kind="Design System/Internal/DateGrid/展示" name="行內 — 儀表板小卡"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">行內 — Linear 專案截止日小卡</span></LinkTo>
|
|
84
84
|
</li>
|
|
85
85
|
</ul>
|
|
86
86
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方「vs 近親」)。</p>
|
|
@@ -47,11 +47,11 @@ export const UsageGuidance: Story = {
|
|
|
47
47
|
<div className="prose prose-sm max-w-prose mb-8">
|
|
48
48
|
<p>適合 DatePicker 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
49
49
|
<ul className="space-y-1">
|
|
50
|
-
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="四模式"><span className="text-primary hover:
|
|
51
|
-
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="可清除"><span className="text-primary hover:
|
|
52
|
-
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="尺寸"><span className="text-primary hover:
|
|
53
|
-
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="範圍模式:訂房 / 訂機票情境"><span className="text-primary hover:
|
|
54
|
-
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="展示樣式"><span className="text-primary hover:
|
|
50
|
+
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="四模式"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">請假單送審後日期欄位從可編輯轉唯讀/純展示(四模式)</span></LinkTo></li>
|
|
51
|
+
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="可清除"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">篩選器的選填截止日,填錯一鍵清空(可清除)</span></LinkTo></li>
|
|
52
|
+
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="尺寸"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">緊湊工具列與標準表單的尺寸對應(尺寸)</span></LinkTo></li>
|
|
53
|
+
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="範圍模式:訂房 / 訂機票情境"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Range:訂房 / 訂機票情境</span></LinkTo></li>
|
|
54
|
+
<li><LinkTo kind="Design System/Components/DatePicker/展示" name="展示樣式"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">審批詳情頁唯讀展示申請日期(展示樣式)</span></LinkTo></li>
|
|
55
55
|
</ul>
|
|
56
56
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方 vs 近親 段)。</p>
|
|
57
57
|
</div>
|
|
@@ -53,19 +53,19 @@ export const UsageGuidance: Story = {
|
|
|
53
53
|
<p>適合 DescriptionList 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
54
54
|
<ul className="space-y-1">
|
|
55
55
|
<li>
|
|
56
|
-
<LinkTo kind="Design System/Components/DescriptionList/展示" name="使用者個資"><span className="text-primary hover:
|
|
56
|
+
<LinkTo kind="Design System/Components/DescriptionList/展示" name="使用者個資"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">使用者個資</span></LinkTo>
|
|
57
57
|
</li>
|
|
58
58
|
<li>
|
|
59
|
-
<LinkTo kind="Design System/Components/DescriptionList/展示" name="產品規格"><span className="text-primary hover:
|
|
59
|
+
<LinkTo kind="Design System/Components/DescriptionList/展示" name="產品規格"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">產品規格</span></LinkTo>
|
|
60
60
|
</li>
|
|
61
61
|
<li>
|
|
62
|
-
<LinkTo kind="Design System/Components/DescriptionList/展示" name="訂單明細"><span className="text-primary hover:
|
|
62
|
+
<LinkTo kind="Design System/Components/DescriptionList/展示" name="訂單明細"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">訂單明細</span></LinkTo>
|
|
63
63
|
</li>
|
|
64
64
|
<li>
|
|
65
|
-
<LinkTo kind="Design System/Components/DescriptionList/展示" name="詳情面板"><span className="text-primary hover:
|
|
65
|
+
<LinkTo kind="Design System/Components/DescriptionList/展示" name="詳情面板"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">詳情面板</span></LinkTo>
|
|
66
66
|
</li>
|
|
67
67
|
<li>
|
|
68
|
-
<LinkTo kind="Design System/Components/DescriptionList/展示" name="水平佈局"><span className="text-primary hover:
|
|
68
|
+
<LinkTo kind="Design System/Components/DescriptionList/展示" name="水平佈局"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">水平佈局</span></LinkTo>
|
|
69
69
|
</li>
|
|
70
70
|
</ul>
|
|
71
71
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見 <code>Vs*Rule</code> stories)。</p>
|
|
@@ -52,16 +52,16 @@ export const UsageGuidance: Story = {
|
|
|
52
52
|
<p>適合 Dialog 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
53
53
|
<ul className="space-y-1">
|
|
54
54
|
<li>
|
|
55
|
-
<LinkTo kind="Design System/Components/Dialog/展示" name="表單"><span className="text-primary hover:
|
|
55
|
+
<LinkTo kind="Design System/Components/Dialog/展示" name="表單"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">表單</span></LinkTo>
|
|
56
56
|
</li>
|
|
57
57
|
<li>
|
|
58
|
-
<LinkTo kind="Design System/Components/Dialog/展示" name="長內容"><span className="text-primary hover:
|
|
58
|
+
<LinkTo kind="Design System/Components/Dialog/展示" name="長內容"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">長內容</span></LinkTo>
|
|
59
59
|
</li>
|
|
60
60
|
<li>
|
|
61
|
-
<LinkTo kind="Design System/Components/Dialog/展示" name="危險操作"><span className="text-primary hover:
|
|
61
|
+
<LinkTo kind="Design System/Components/Dialog/展示" name="危險操作"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">危險操作</span></LinkTo>
|
|
62
62
|
</li>
|
|
63
63
|
<li>
|
|
64
|
-
<LinkTo kind="Design System/Components/Dialog/展示" name="主體放清單"><span className="text-primary hover:
|
|
64
|
+
<LinkTo kind="Design System/Components/Dialog/展示" name="主體放清單"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">主體放清單</span></LinkTo>
|
|
65
65
|
</li>
|
|
66
66
|
</ul>
|
|
67
67
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見 <code>Vs*Rule</code> stories)。</p>
|
|
@@ -963,7 +963,7 @@ export const Accessibility = {
|
|
|
963
963
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
964
964
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
965
965
|
<p className="whitespace-pre-line">{"無障礙能力沿用 Radix 選單元件的內建支援,涵蓋語意角色、選單導覽與焦點管理,設計師與工程師不需額外設定。\n\n鍵盤操作:\n\n- Tab — 將焦點移到觸發按鈕\n- Enter / Space / 向下鍵 — 開啟選單\n- 向上鍵 / 向下鍵 — 在選項之間移動\n- Enter — 選擇目前的選項\n- Esc — 關閉整個選單(含子選單;逐層收合用左方向鍵)\n\n焦點管理:選單開啟時焦點會進入選單,關閉後自動回到原本的觸發按鈕;目前導覽到的選項會以明顯的 highlight 底色標示,鍵盤使用者隨時看得到自己在哪一項。\n\n驗證:鍵盤可完整操作(不需滑鼠),文字對比達 WCAG AA;在 Storybook 無障礙檢查面板上應為零項嚴重問題。"}</p>
|
|
966
|
-
<p className="mt-4 text-fg-muted">完整 ARIA 細節參考 <a className="text-primary hover:
|
|
966
|
+
<p className="mt-4 text-fg-muted">完整 ARIA 細節參考 <a className="text-primary hover:text-primary-hover" href="https://www.radix-ui.com/primitives/docs/components/dropdown-menu#accessibility" target="_blank" rel="noreferrer">Radix Accessibility 文件</a>。</p>
|
|
967
967
|
</div>
|
|
968
968
|
),
|
|
969
969
|
}
|
|
@@ -58,19 +58,19 @@ export const UsageGuidance: Story = {
|
|
|
58
58
|
<p>適合 DropdownMenu 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
59
59
|
<ul className="space-y-1">
|
|
60
60
|
<li>
|
|
61
|
-
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="群組"><span className="text-primary hover:
|
|
61
|
+
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="群組"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">群組 — 帳號選單(個人資料 / 設定 / 登出分組)</span></LinkTo>
|
|
62
62
|
</li>
|
|
63
63
|
<li>
|
|
64
|
-
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="後綴"><span className="text-primary hover:
|
|
64
|
+
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="後綴"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">後綴 — 收件匣未讀數 badge、外開連結提示</span></LinkTo>
|
|
65
65
|
</li>
|
|
66
66
|
<li>
|
|
67
|
-
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="子選單"><span className="text-primary hover:
|
|
67
|
+
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="子選單"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">子選單 — 主題切換(淺色 / 深色 / 跟隨系統)</span></LinkTo>
|
|
68
68
|
</li>
|
|
69
69
|
<li>
|
|
70
|
-
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="勾選項"><span className="text-primary hover:
|
|
70
|
+
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="勾選項"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">勾選項 — 表格顯示欄位切換</span></LinkTo>
|
|
71
71
|
</li>
|
|
72
72
|
<li>
|
|
73
|
-
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="單選"><span className="text-primary hover:
|
|
73
|
+
<LinkTo kind="Design System/Components/DropdownMenu/展示" name="單選"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">單選 — 檔案清單排序方式</span></LinkTo>
|
|
74
74
|
</li>
|
|
75
75
|
</ul>
|
|
76
76
|
<p className="text-fg-muted mt-3">判斷不確定時:先看「選完之後畫面是否需要保留選中狀態」——需要就改用 Select / SelectMenu。下方的「DropdownMenu vs 選值元件」與「群組 vs 分隔線」範例提供更完整的對照。</p>
|