@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-action-bar.d.ts","sourceRoot":"","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAmB9B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAA;IACxC,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAGD,eAAO,MAAM,8BAA8B,EAAE,mBAK5C,CAAA;AAID,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC9E,wCAAwC;IACxC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,sFAAsF;IACtF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAA;CACtC;AAeD,QAAA,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"bulk-action-bar.d.ts","sourceRoot":"","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAmB9B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAA;IACxC,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAGD,eAAO,MAAM,8BAA8B,EAAE,mBAK5C,CAAA;AAID,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC9E,wCAAwC;IACxC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,sFAAsF;IACtF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAA;CACtC;AAeD,QAAA,MAAM,aAAa,2FAmElB,CAAA;AAID,eAAO,MAAM,iBAAiB;;;;;;;;;;CAUpB,CAAA;AAEV,OAAO,EAAE,aAAa,EAAE,CAAA"}
|
|
@@ -16,7 +16,7 @@ const BulkActionBar = React.forwardRef(
|
|
|
16
16
|
() => ({ ...BULK_ACTION_BAR_DEFAULT_LABELS, ...labelsOverride }),
|
|
17
17
|
[labelsOverride]
|
|
18
18
|
);
|
|
19
|
-
if (selection.length === 0) return null;
|
|
19
|
+
if (selection.length === 0 && (totalSelected ?? 0) === 0) return null;
|
|
20
20
|
return /* @__PURE__ */ jsxs(
|
|
21
21
|
"div",
|
|
22
22
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-action-bar.js","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"sourcesContent":["import * as React from 'react'\nimport { X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ButtonDivider } from '@/design-system/components/Button/button-group'\n\n// ── 消費的 SSOT ───────────────────────────────────────────────────────────────\n// - bulk-action-bar.spec.md(本元件 SSOT)\n// - DataTable/data-table.spec.md「L2 選取」(整合方式)\n// - button.spec.md + button-group.tsx(action variant=tertiary,size=md,\n// gap-2 + ButtonDivider 自帶 mx-1 = 12px 視覺距離)\n// - inline-action.spec.md「same-row consistency rule」(close X 同尺寸 md)\n// - tokens/layoutSpace/layoutSpace.spec.md(footer 用 px-loose py-tight)\n// - patterns/overlay-surface/overlay-surface.spec.md SurfaceFooter canonical\n// - Alert(banner)用 title ReactNode 帶 inline link CTA\n\n// ── i18n labels ─────────────────────────────────────────────────────────────\n\n// code-quality-allow: dead-export — public API per spec.md(consumer i18n override hook)\nexport interface BulkActionBarLabels {\n count: (n: number) => string\n clear: string\n hiddenSuffix: (hidden: number) => string\n toolbarAriaLabel: string\n}\n\n// code-quality-allow: dead-export — public API per spec.md(consumer spread + override)\nexport const BULK_ACTION_BAR_DEFAULT_LABELS: BulkActionBarLabels = {\n count: (n) => `已選 ${n} 項`,\n clear: '清除選取',\n hiddenSuffix: (hidden) => `· ${hidden} 個被 filter 隱藏`,\n toolbarAriaLabel: '批次操作',\n}\n\n// ── Props ───────────────────────────────────────────────────────────────────\n\nexport interface BulkActionBarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** 已選 ID,length === 0 時自動隱藏(回傳 null) */\n selection: readonly string[]\n /** Clear 觸發,user 點 X icon 或 Esc(consumer 在 page-level 監聽) */\n onClear?: () => void\n /** 批次 actions(consumer 提供 md Button,variant=tertiary 或 tertiary+danger;不用 primary) */\n actions?: React.ReactNode\n /** Filter 模式:hidden 數量,顯示在 count 區 inline「{N} 已選 · {M} 個被 filter 隱藏」 */\n hiddenByFilter?: number\n /**\n * 「擴選整個 dataset」狀態(2026-05-13 ship,per user 抓 Alert「已選 5370」但 BulkActionBar\n * 仍顯「已選 50 項」regression):\n * - undefined / null(default):count 走 `selection.length`(page-level 視覺選取)\n * - number:count 走此數值(整個 dataset 擴選後 user 已選的真總數)\n *\n * Canonical pattern:consumer 把 BulkActionBar 跟「Alert info banner(提示擴選 dataset)」\n * 一起 mount,Alert 點「點此選取全部 N 個」→ setTotalSelected(N) → BulkActionBar count 同步。\n * 對齊 Gmail / Linear / Notion 全選 dataset hint pattern。\n * 詳 `bulk-action-bar.spec.md`「Extend dataset pattern」段。\n */\n totalSelected?: number | null\n /** i18n labels(Partial,merge with default) */\n labels?: Partial<BulkActionBarLabels>\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n// 視覺結構(同 SurfaceFooter / DataTable toolbar canonical):\n// px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]\n// gap-2 between elements\n// 全 md Buttons(same-row consistency)\n// <ButtonDivider /> 自帶 mx-1 = 12px 視覺距離\n//\n// Hint banner(擴 dataset 提示)完全外包給 Alert 元件 — consumer 視 ref 圖\n// 黏在 BulkActionBar 上方/下方,用 Alert variant=\"info\" placement=\"fixed\"\n// title={inline link JSX}。BulkActionBar 自己不再有 hint banner slot。\n//\n// 浮起 / fixed positioning 由 consumer wrap 決定(BulkActionBar 不限定 placement)。\n\nconst BulkActionBar = React.forwardRef<HTMLDivElement, BulkActionBarProps>(\n function BulkActionBar(\n { selection, onClear, actions, hiddenByFilter, totalSelected, labels: labelsOverride, className, ...props },\n ref\n ) {\n const labels: BulkActionBarLabels = React.useMemo(\n () => ({ ...BULK_ACTION_BAR_DEFAULT_LABELS, ...labelsOverride }),\n [labelsOverride]\n )\n\n // selection.length === 0 自動藏(對齊 spec 禁止事項 #3)\n if (selection.length === 0) return null\n\n return (\n <div\n ref={ref}\n role=\"toolbar\"\n aria-label={labels.toolbarAriaLabel}\n className={cn(\n 'flex items-center gap-2',\n 'px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]',\n // 上分隔線:bottom toolbar canonical(Linear / Apple Mail / Notion 共識)— 視覺從上方內容收尾\n 'border-t border-divider',\n className\n )}\n {...props}\n >\n {/* X close — md dismiss(2026-05-04 spec update:default placement = footer variant,\n visual weight 對齊 Dialog footer commitment buttons md;same-row consistency 維持)\n 未來若有 top-toolbar variant(覆蓋 sm-density toolbar)→ 該 variant override sm */}\n {onClear && (\n <Button\n variant=\"text\"\n size=\"md\"\n iconOnly\n dismiss\n startIcon={X}\n aria-label={labels.clear}\n onClick={onClear}\n />\n )}\n\n {/* count + filter hidden inline\n color canonical(2026-05-04):count = primary foreground + medium weight\n 理由:count 是 state-bearing 主資訊(「你在 selection mode + N items」),非裝飾\n World-class 共識:Linear/Notion/Carbon/Polaris 均用 primary foreground;muted 化弱化 state signal\n hiddenByFilter suffix 維持 muted(這是次資訊,視覺層次正確) */}\n {/* 2026-05-31 #3:aria-live 通知 SR 選取數變更;#20:補 font-medium 對齊 spec L90 + 上方 comment(state-bearing 主資訊) */}\n <span className=\"text-body text-foreground font-medium tabular-nums\" aria-live=\"polite\" aria-atomic=\"true\">\n {/* 2026-05-13:totalSelected override 走 dataset 擴選後真總數,否則 fallback page-level selection.length */}\n {labels.count(typeof totalSelected === 'number' ? totalSelected : selection.length)}\n {hiddenByFilter !== undefined && hiddenByFilter > 0 && (\n <span className=\"text-fg-muted font-normal\"> {labels.hiddenSuffix(hiddenByFilter)}</span>\n )}\n </span>\n\n {/* divider */}\n {actions && <ButtonDivider />}\n\n {/* batch actions slot(consumer 提供 md Buttons) */}\n {actions && (\n <div className=\"flex items-center gap-2 flex-1 min-w-0\">{actions}</div>\n )}\n </div>\n )\n }\n)\nBulkActionBar.displayName = 'BulkActionBar'\n\n// Story auto-compile metadata\nexport const bulkActionBarMeta = {\n component: 'BulkActionBar',\n family: null,\n variants: {},\n sizes: {},\n states: ['default'],\n tokens: {\n fg: ['text-foreground', 'text-fg-muted'],\n border: ['border-divider'],\n },\n} as const\n\nexport { BulkActionBar }\n"],"names":["BulkActionBar"],"mappings":";;;;;;AA2BO,MAAM,iCAAsD;AAAA,EACjE,OAAO,CAAC,MAAM,MAAM,CAAC;AAAA,EACrB,OAAO;AAAA,EACP,cAAc,CAAC,WAAW,KAAK,MAAM;AAAA,EACrC,kBAAkB;AACpB;AA0CA,MAAM,gBAAgB,MAAM;AAAA,EAC1B,SAASA,eACP,EAAE,WAAW,SAAS,SAAS,gBAAgB,eAAe,QAAQ,gBAAgB,WAAW,GAAG,MAAA,GACpG,KACA;AACA,UAAM,SAA8B,MAAM;AAAA,MACxC,OAAO,EAAE,GAAG,gCAAgC,GAAG;MAC/C,CAAC,cAAc;AAAA,IAAA;
|
|
1
|
+
{"version":3,"file":"bulk-action-bar.js","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"sourcesContent":["import * as React from 'react'\nimport { X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ButtonDivider } from '@/design-system/components/Button/button-group'\n\n// ── 消費的 SSOT ───────────────────────────────────────────────────────────────\n// - bulk-action-bar.spec.md(本元件 SSOT)\n// - DataTable/data-table.spec.md「L2 選取」(整合方式)\n// - button.spec.md + button-group.tsx(action variant=tertiary,size=md,\n// gap-2 + ButtonDivider 自帶 mx-1 = 12px 視覺距離)\n// - inline-action.spec.md「same-row consistency rule」(close X 同尺寸 md)\n// - tokens/layoutSpace/layoutSpace.spec.md(footer 用 px-loose py-tight)\n// - patterns/overlay-surface/overlay-surface.spec.md SurfaceFooter canonical\n// - Alert(banner)用 title ReactNode 帶 inline link CTA\n\n// ── i18n labels ─────────────────────────────────────────────────────────────\n\n// code-quality-allow: dead-export — public API per spec.md(consumer i18n override hook)\nexport interface BulkActionBarLabels {\n count: (n: number) => string\n clear: string\n hiddenSuffix: (hidden: number) => string\n toolbarAriaLabel: string\n}\n\n// code-quality-allow: dead-export — public API per spec.md(consumer spread + override)\nexport const BULK_ACTION_BAR_DEFAULT_LABELS: BulkActionBarLabels = {\n count: (n) => `已選 ${n} 項`,\n clear: '清除選取',\n hiddenSuffix: (hidden) => `· ${hidden} 個被 filter 隱藏`,\n toolbarAriaLabel: '批次操作',\n}\n\n// ── Props ───────────────────────────────────────────────────────────────────\n\nexport interface BulkActionBarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** 已選 ID,length === 0 時自動隱藏(回傳 null) */\n selection: readonly string[]\n /** Clear 觸發,user 點 X icon 或 Esc(consumer 在 page-level 監聽) */\n onClear?: () => void\n /** 批次 actions(consumer 提供 md Button,variant=tertiary 或 tertiary+danger;不用 primary) */\n actions?: React.ReactNode\n /** Filter 模式:hidden 數量,顯示在 count 區 inline「{N} 已選 · {M} 個被 filter 隱藏」 */\n hiddenByFilter?: number\n /**\n * 「擴選整個 dataset」狀態(2026-05-13 ship,per user 抓 Alert「已選 5370」但 BulkActionBar\n * 仍顯「已選 50 項」regression):\n * - undefined / null(default):count 走 `selection.length`(page-level 視覺選取)\n * - number:count 走此數值(整個 dataset 擴選後 user 已選的真總數)\n *\n * Canonical pattern:consumer 把 BulkActionBar 跟「Alert info banner(提示擴選 dataset)」\n * 一起 mount,Alert 點「點此選取全部 N 個」→ setTotalSelected(N) → BulkActionBar count 同步。\n * 對齊 Gmail / Linear / Notion 全選 dataset hint pattern。\n * 詳 `bulk-action-bar.spec.md`「Extend dataset pattern」段。\n */\n totalSelected?: number | null\n /** i18n labels(Partial,merge with default) */\n labels?: Partial<BulkActionBarLabels>\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n// 視覺結構(同 SurfaceFooter / DataTable toolbar canonical):\n// px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]\n// gap-2 between elements\n// 全 md Buttons(same-row consistency)\n// <ButtonDivider /> 自帶 mx-1 = 12px 視覺距離\n//\n// Hint banner(擴 dataset 提示)完全外包給 Alert 元件 — consumer 視 ref 圖\n// 黏在 BulkActionBar 上方/下方,用 Alert variant=\"info\" placement=\"fixed\"\n// title={inline link JSX}。BulkActionBar 自己不再有 hint banner slot。\n//\n// 浮起 / fixed positioning 由 consumer wrap 決定(BulkActionBar 不限定 placement)。\n\nconst BulkActionBar = React.forwardRef<HTMLDivElement, BulkActionBarProps>(\n function BulkActionBar(\n { selection, onClear, actions, hiddenByFilter, totalSelected, labels: labelsOverride, className, ...props },\n ref\n ) {\n const labels: BulkActionBarLabels = React.useMemo(\n () => ({ ...BULK_ACTION_BAR_DEFAULT_LABELS, ...labelsOverride }),\n [labelsOverride]\n )\n\n // selection.length === 0 自動藏(對齊 spec 禁止事項 #3)。\n // 反向選取(DataTable all 模式)例外:visible 全被 excluded 但 totalSelected>0 仍有選取 → 顯示(2026-06-22)\n if (selection.length === 0 && (totalSelected ?? 0) === 0) return null\n\n return (\n <div\n ref={ref}\n role=\"toolbar\"\n aria-label={labels.toolbarAriaLabel}\n className={cn(\n 'flex items-center gap-2',\n 'px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]',\n // 上分隔線:bottom toolbar canonical(Linear / Apple Mail / Notion 共識)— 視覺從上方內容收尾\n 'border-t border-divider',\n className\n )}\n {...props}\n >\n {/* X close — md dismiss(2026-05-04 spec update:default placement = footer variant,\n visual weight 對齊 Dialog footer commitment buttons md;same-row consistency 維持)\n 未來若有 top-toolbar variant(覆蓋 sm-density toolbar)→ 該 variant override sm */}\n {onClear && (\n <Button\n variant=\"text\"\n size=\"md\"\n iconOnly\n dismiss\n startIcon={X}\n aria-label={labels.clear}\n onClick={onClear}\n />\n )}\n\n {/* count + filter hidden inline\n color canonical(2026-05-04):count = primary foreground + medium weight\n 理由:count 是 state-bearing 主資訊(「你在 selection mode + N items」),非裝飾\n World-class 共識:Linear/Notion/Carbon/Polaris 均用 primary foreground;muted 化弱化 state signal\n hiddenByFilter suffix 維持 muted(這是次資訊,視覺層次正確) */}\n {/* 2026-05-31 #3:aria-live 通知 SR 選取數變更;#20:補 font-medium 對齊 spec L90 + 上方 comment(state-bearing 主資訊) */}\n <span className=\"text-body text-foreground font-medium tabular-nums\" aria-live=\"polite\" aria-atomic=\"true\">\n {/* 2026-05-13:totalSelected override 走 dataset 擴選後真總數,否則 fallback page-level selection.length */}\n {labels.count(typeof totalSelected === 'number' ? totalSelected : selection.length)}\n {hiddenByFilter !== undefined && hiddenByFilter > 0 && (\n <span className=\"text-fg-muted font-normal\"> {labels.hiddenSuffix(hiddenByFilter)}</span>\n )}\n </span>\n\n {/* divider */}\n {actions && <ButtonDivider />}\n\n {/* batch actions slot(consumer 提供 md Buttons) */}\n {actions && (\n <div className=\"flex items-center gap-2 flex-1 min-w-0\">{actions}</div>\n )}\n </div>\n )\n }\n)\nBulkActionBar.displayName = 'BulkActionBar'\n\n// Story auto-compile metadata\nexport const bulkActionBarMeta = {\n component: 'BulkActionBar',\n family: null,\n variants: {},\n sizes: {},\n states: ['default'],\n tokens: {\n fg: ['text-foreground', 'text-fg-muted'],\n border: ['border-divider'],\n },\n} as const\n\nexport { BulkActionBar }\n"],"names":["BulkActionBar"],"mappings":";;;;;;AA2BO,MAAM,iCAAsD;AAAA,EACjE,OAAO,CAAC,MAAM,MAAM,CAAC;AAAA,EACrB,OAAO;AAAA,EACP,cAAc,CAAC,WAAW,KAAK,MAAM;AAAA,EACrC,kBAAkB;AACpB;AA0CA,MAAM,gBAAgB,MAAM;AAAA,EAC1B,SAASA,eACP,EAAE,WAAW,SAAS,SAAS,gBAAgB,eAAe,QAAQ,gBAAgB,WAAW,GAAG,MAAA,GACpG,KACA;AACA,UAAM,SAA8B,MAAM;AAAA,MACxC,OAAO,EAAE,GAAG,gCAAgC,GAAG;MAC/C,CAAC,cAAc;AAAA,IAAA;AAKjB,QAAI,UAAU,WAAW,MAAM,iBAAiB,OAAO,EAAG,QAAO;AAEjE,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,cAAY,OAAO;AAAA,QACnB,WAAW;AAAA,UACT;AAAA,UACA;AAAA;AAAA,UAEA;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAKH,UAAA;AAAA,UAAA,WACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,UAAQ;AAAA,cACR,SAAO;AAAA,cACP,WAAW;AAAA,cACX,cAAY,OAAO;AAAA,cACnB,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,+BAUZ,QAAA,EAAK,WAAU,sDAAqD,aAAU,UAAS,eAAY,QAEjG,UAAA;AAAA,YAAA,OAAO,MAAM,OAAO,kBAAkB,WAAW,gBAAgB,UAAU,MAAM;AAAA,YACjF,mBAAmB,UAAa,iBAAiB,KAChD,qBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA;AAAA,cAAA;AAAA,cAAE,OAAO,aAAa,cAAc;AAAA,YAAA,EAAA,CAAE;AAAA,UAAA,GAEtF;AAAA,UAGC,+BAAY,eAAA,EAAc;AAAA,UAG1B,WACC,oBAAC,OAAA,EAAI,WAAU,0CAA0C,UAAA,QAAA,CAAQ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIzE;AACF;AACA,cAAc,cAAc;AAGrB,MAAM,oBAAoB;AAAA,EAC/B,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU,CAAA;AAAA,EACV,OAAO,CAAA;AAAA,EACP,QAAQ,CAAC,SAAS;AAAA,EAClB,QAAQ;AAAA,IACN,IAAI,CAAC,mBAAmB,eAAe;AAAA,IACvC,QAAQ,CAAC,gBAAgB;AAAA,EAAA;AAE7B;"}
|
|
@@ -69,12 +69,14 @@ export interface DataTableProps<TData> extends Omit<React.HTMLAttributes<HTMLDiv
|
|
|
69
69
|
* (`--primary-subtle` bg fill)— per user「不要 dash 直接實的就好」+ codex Q2.2 token。
|
|
70
70
|
*/
|
|
71
71
|
spreadsheetMode?: boolean;
|
|
72
|
-
/**
|
|
73
|
-
selection?: string[];
|
|
74
|
-
/** 預設選取(uncontrolled) */
|
|
75
|
-
defaultSelection?: string[];
|
|
76
|
-
/** Selection 變更 callback */
|
|
77
|
-
onSelectionChange?: (next:
|
|
72
|
+
/** 已選列(controlled)。傳 string[] = include shorthand;傳 DataTableSelection 支援反向選取(all + excluded) */
|
|
73
|
+
selection?: string[] | DataTableSelection;
|
|
74
|
+
/** 預設選取(uncontrolled);同上接受 string[] 或 DataTableSelection */
|
|
75
|
+
defaultSelection?: string[] | DataTableSelection;
|
|
76
|
+
/** Selection 變更 callback(emit DataTableSelection union;include / all 兩模型) */
|
|
77
|
+
onSelectionChange?: (next: DataTableSelection) => void;
|
|
78
|
+
/** 全資料集筆數 M(server-side / filter 後);all 模式 count = totalCount − excluded.length(consumer 計算) */
|
|
79
|
+
totalCount?: number;
|
|
78
80
|
/** 是否啟用 selection / 模式;true 等同 'multi' */
|
|
79
81
|
selectable?: boolean | 'single' | 'multi';
|
|
80
82
|
/** Row 是否可選(disabled rows 只 disable checkbox,row 內容正常 render) */
|
|
@@ -161,6 +163,25 @@ export interface DataTableProps<TData> extends Omit<React.HTMLAttributes<HTMLDiv
|
|
|
161
163
|
*/
|
|
162
164
|
onColumnReorder?: (sourceId: string, targetId: string, position: 'before' | 'after') => void;
|
|
163
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* L2 選取模型(discriminated union,2026-06-22 對齊 MUI X DataGrid v8
|
|
168
|
+
* rowSelectionModel { type: include | exclude, ids } + AG Grid selectAll + toggledNodes)。
|
|
169
|
+
*
|
|
170
|
+
* - mode==='include' (ids):只選 ids 列(預設;= 載入/可見列逐一選取)。
|
|
171
|
+
* - mode==='all' (excluded):全資料集(filter 後)選取,扣掉 excluded —— 反向選取(inverted)。
|
|
172
|
+
* 解決「全選 10k 筆只載 50 筆 → 無法列舉其餘 ID」:all 模式「選取 = 全集 − excluded」,
|
|
173
|
+
* 任何 toggle 都只是 excluded 的 add/remove,對任意順序封閉、O(1)、不需列舉未載入 ID。
|
|
174
|
+
*
|
|
175
|
+
* 計數(consumer):mode==='all' ? totalCount − excluded.length : ids.length(需傳 totalCount)。
|
|
176
|
+
* 進入 all 模式:consumer 在「選取全部 M」hint 點擊時 setSelection({ mode: 'all', excluded: [] })。
|
|
177
|
+
*/
|
|
178
|
+
export type DataTableSelection = {
|
|
179
|
+
mode: 'include';
|
|
180
|
+
ids: string[];
|
|
181
|
+
} | {
|
|
182
|
+
mode: 'all';
|
|
183
|
+
excluded: string[];
|
|
184
|
+
};
|
|
164
185
|
export declare const MIN_COLUMN_WIDTH = 80;
|
|
165
186
|
export declare const DataTable: <TData>(props: DataTableProps<TData> & {
|
|
166
187
|
ref?: React.ForwardedRef<HTMLDivElement>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/data-table.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,EAML,KAAK,SAAS,EAEd,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AA4BjE,QAAA,MAAM,iBAAiB;;8EAGrB,CAAA;AAIF,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAEnC,MAAM,WAAW,cAAc,CAAC,KAAK,CACnC,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,EAC5D,YAAY,CAAC,OAAO,iBAAiB,CAAC;IAExC,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAA;IAChC,IAAI,EAAE,KAAK,EAAE,CAAA;IACb,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,CAAC,CAAC,CAAA;IACzF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAA;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAA;IAC9C,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B,yDAAyD;IACzD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAA;IACxC;;;;;;;OAOG;IACH,kCAAkC,CAAC,EAAE,OAAO,CAAA;IAC5C;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAGzB,
|
|
1
|
+
{"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/data-table.tsx"],"names":[],"mappings":"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"}
|
|
@@ -28,6 +28,27 @@ const dataTableVariants = cva("bg-surface rounded-md overflow-hidden", {
|
|
|
28
28
|
});
|
|
29
29
|
const cellEditId = (rowId, colId) => `${rowId}__${colId}`;
|
|
30
30
|
const SELECT_COL_ID = "__select__";
|
|
31
|
+
function normalizeSelection(input) {
|
|
32
|
+
if (input === void 0) return void 0;
|
|
33
|
+
if (Array.isArray(input)) return { mode: "include", ids: input };
|
|
34
|
+
return input;
|
|
35
|
+
}
|
|
36
|
+
function applySelectIds(sel, ids, willSelect) {
|
|
37
|
+
if (sel.mode === "include") {
|
|
38
|
+
const set2 = new Set(sel.ids);
|
|
39
|
+
ids.forEach((id) => {
|
|
40
|
+
if (willSelect) set2.add(id);
|
|
41
|
+
else set2.delete(id);
|
|
42
|
+
});
|
|
43
|
+
return { mode: "include", ids: Array.from(set2) };
|
|
44
|
+
}
|
|
45
|
+
const set = new Set(sel.excluded);
|
|
46
|
+
ids.forEach((id) => {
|
|
47
|
+
if (willSelect) set.delete(id);
|
|
48
|
+
else set.add(id);
|
|
49
|
+
});
|
|
50
|
+
return { mode: "all", excluded: Array.from(set) };
|
|
51
|
+
}
|
|
31
52
|
const cellPadding = { paddingBlock: "var(--table-cell-py)", paddingInline: "var(--table-cell-px)" };
|
|
32
53
|
const HEADER_BG = "bg-muted";
|
|
33
54
|
const MIN_COLUMN_WIDTH = 80;
|
|
@@ -314,6 +335,7 @@ function DataTableInner({
|
|
|
314
335
|
selection: selectionProp,
|
|
315
336
|
defaultSelection,
|
|
316
337
|
onSelectionChange,
|
|
338
|
+
totalCount,
|
|
317
339
|
selectable = false,
|
|
318
340
|
isRowSelectable,
|
|
319
341
|
getRowId,
|
|
@@ -401,8 +423,8 @@ function DataTableInner({
|
|
|
401
423
|
const enabled = selectable !== false;
|
|
402
424
|
const mode = selectable === "single" ? "single" : "multi";
|
|
403
425
|
const [selection, setSelection] = useControllable({
|
|
404
|
-
value: selectionProp,
|
|
405
|
-
defaultValue: defaultSelection ?? [],
|
|
426
|
+
value: normalizeSelection(selectionProp),
|
|
427
|
+
defaultValue: normalizeSelection(defaultSelection) ?? { mode: "include", ids: [] },
|
|
406
428
|
onChange: onSelectionChange
|
|
407
429
|
});
|
|
408
430
|
const anchorRowIdRef = React.useRef(null);
|
|
@@ -733,7 +755,7 @@ function DataTableInner({
|
|
|
733
755
|
const checkboxSize = size === "lg" ? "lg" : "md";
|
|
734
756
|
const onCellClick = isDisabled ? void 0 : (e) => {
|
|
735
757
|
e.stopPropagation();
|
|
736
|
-
if (mode === "single") setSelection([rowId]);
|
|
758
|
+
if (mode === "single") setSelection({ mode: "include", ids: [rowId] });
|
|
737
759
|
else toggleRow(rowId, rowOriginal, { shiftKey: e.shiftKey });
|
|
738
760
|
};
|
|
739
761
|
return /* @__PURE__ */ jsx(
|
|
@@ -757,7 +779,7 @@ function DataTableInner({
|
|
|
757
779
|
Checkbox,
|
|
758
780
|
{
|
|
759
781
|
size: checkboxSize,
|
|
760
|
-
checked:
|
|
782
|
+
checked: isSelectedId(rowId),
|
|
761
783
|
disabled: isDisabled,
|
|
762
784
|
"aria-label": ariaLabel,
|
|
763
785
|
onClick: (e) => {
|
|
@@ -967,33 +989,42 @@ function DataTableInner({
|
|
|
967
989
|
React.useEffect(() => {
|
|
968
990
|
if (!enabled || preserveSelectionOnFilter) return;
|
|
969
991
|
setSelection((prev) => {
|
|
970
|
-
|
|
971
|
-
|
|
992
|
+
if (prev.mode === "all") return prev;
|
|
993
|
+
const filtered = prev.ids.filter((id) => visibleRowIdsSet.has(id));
|
|
994
|
+
return filtered.length === prev.ids.length ? prev : { mode: "include", ids: filtered };
|
|
972
995
|
});
|
|
973
996
|
}, [visibleRowIdsKey, enabled, preserveSelectionOnFilter, visibleRowIdsSet, setSelection]);
|
|
974
997
|
const selectableVisibleIds = React.useMemo(() => {
|
|
975
998
|
if (!enabled) return [];
|
|
976
999
|
return rows.filter((r) => !isRowSelectable || isRowSelectable(r.original)).map((r) => r.id);
|
|
977
1000
|
}, [rows, enabled, isRowSelectable]);
|
|
978
|
-
const
|
|
979
|
-
|
|
1001
|
+
const includeSet = React.useMemo(
|
|
1002
|
+
() => selection.mode === "include" ? new Set(selection.ids) : /* @__PURE__ */ new Set(),
|
|
1003
|
+
[selection]
|
|
1004
|
+
);
|
|
1005
|
+
const excludeSet = React.useMemo(
|
|
1006
|
+
() => selection.mode === "all" ? new Set(selection.excluded) : /* @__PURE__ */ new Set(),
|
|
1007
|
+
[selection]
|
|
1008
|
+
);
|
|
1009
|
+
const isSelectedId = React.useCallback(
|
|
1010
|
+
(id) => selection.mode === "include" ? includeSet.has(id) : !excludeSet.has(id),
|
|
1011
|
+
[selection.mode, includeSet, excludeSet]
|
|
1012
|
+
);
|
|
1013
|
+
const hasAnySelection = selection.mode === "all" || includeSet.size > 0;
|
|
1014
|
+
const visibleSelectedCount = selectableVisibleIds.filter((id) => isSelectedId(id)).length;
|
|
980
1015
|
const headerCheckedState = selectableVisibleIds.length === 0 ? false : visibleSelectedCount === 0 ? false : visibleSelectedCount === selectableVisibleIds.length ? true : "indeterminate";
|
|
981
1016
|
const visibleIdToRow = React.useMemo(
|
|
982
1017
|
() => new Map(rows.map((r) => [r.id, r])),
|
|
983
1018
|
[rows]
|
|
984
1019
|
);
|
|
985
1020
|
const toggleHeaderCheckbox = React.useCallback(() => {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
setSelection((prev) => prev.filter((id) => !visibleSet.has(id)));
|
|
989
|
-
} else {
|
|
990
|
-
setSelection((prev) => Array.from(/* @__PURE__ */ new Set([...prev, ...selectableVisibleIds])));
|
|
991
|
-
}
|
|
1021
|
+
const willSelect = headerCheckedState !== true;
|
|
1022
|
+
setSelection((prev) => applySelectIds(prev, selectableVisibleIds, willSelect));
|
|
992
1023
|
}, [headerCheckedState, selectableVisibleIds, setSelection]);
|
|
993
1024
|
const toggleRow = React.useCallback((rowId, rowOriginal, opts) => {
|
|
994
1025
|
if (isRowSelectable && !isRowSelectable(rowOriginal)) return;
|
|
995
1026
|
if (mode === "single") {
|
|
996
|
-
setSelection(
|
|
1027
|
+
setSelection(isSelectedId(rowId) ? { mode: "include", ids: [] } : { mode: "include", ids: [rowId] });
|
|
997
1028
|
anchorRowIdRef.current = rowId;
|
|
998
1029
|
return;
|
|
999
1030
|
}
|
|
@@ -1008,23 +1039,15 @@ function DataTableInner({
|
|
|
1008
1039
|
const row = visibleIdToRow.get(id);
|
|
1009
1040
|
return row && (!isRowSelectable || isRowSelectable(row.original));
|
|
1010
1041
|
});
|
|
1011
|
-
const
|
|
1012
|
-
setSelection((prev) =>
|
|
1013
|
-
const set = new Set(prev);
|
|
1014
|
-
rangeIds.forEach((id) => willCheck ? set.add(id) : set.delete(id));
|
|
1015
|
-
return Array.from(set);
|
|
1016
|
-
});
|
|
1042
|
+
const willCheck2 = !isSelectedId(rowId);
|
|
1043
|
+
setSelection((prev) => applySelectIds(prev, rangeIds, willCheck2));
|
|
1017
1044
|
return;
|
|
1018
1045
|
}
|
|
1019
1046
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
if (set.has(rowId)) set.delete(rowId);
|
|
1023
|
-
else set.add(rowId);
|
|
1024
|
-
return Array.from(set);
|
|
1025
|
-
});
|
|
1047
|
+
const willCheck = !isSelectedId(rowId);
|
|
1048
|
+
setSelection((prev) => applySelectIds(prev, [rowId], willCheck));
|
|
1026
1049
|
anchorRowIdRef.current = rowId;
|
|
1027
|
-
}, [isRowSelectable, mode,
|
|
1050
|
+
}, [isRowSelectable, mode, isSelectedId, rows, visibleIdToRow, setSelection]);
|
|
1028
1051
|
const tableKeyboardHandler = React.useCallback(
|
|
1029
1052
|
(e) => {
|
|
1030
1053
|
var _a2;
|
|
@@ -1081,12 +1104,12 @@ function DataTableInner({
|
|
|
1081
1104
|
if (!enabled) return;
|
|
1082
1105
|
if ((e.metaKey || e.ctrlKey) && e.key === "a" && mode === "multi") {
|
|
1083
1106
|
e.preventDefault();
|
|
1084
|
-
setSelection((prev) =>
|
|
1107
|
+
setSelection((prev) => applySelectIds(prev, selectableVisibleIds, true));
|
|
1085
1108
|
return;
|
|
1086
1109
|
}
|
|
1087
|
-
if (e.key === "Escape" &&
|
|
1110
|
+
if (e.key === "Escape" && hasAnySelection) {
|
|
1088
1111
|
e.preventDefault();
|
|
1089
|
-
setSelection([]);
|
|
1112
|
+
setSelection({ mode: "include", ids: [] });
|
|
1090
1113
|
anchorRowIdRef.current = null;
|
|
1091
1114
|
return;
|
|
1092
1115
|
}
|
|
@@ -1094,7 +1117,7 @@ function DataTableInner({
|
|
|
1094
1117
|
[
|
|
1095
1118
|
enabled,
|
|
1096
1119
|
mode,
|
|
1097
|
-
|
|
1120
|
+
hasAnySelection,
|
|
1098
1121
|
selectableVisibleIds,
|
|
1099
1122
|
setSelection,
|
|
1100
1123
|
spreadsheetMode,
|
|
@@ -1826,8 +1849,8 @@ function DataTableInner({
|
|
|
1826
1849
|
return /* @__PURE__ */ jsx(
|
|
1827
1850
|
RadioGroupPrimitive.Root,
|
|
1828
1851
|
{
|
|
1829
|
-
value: selection[0] ?? "",
|
|
1830
|
-
onValueChange: (v) => v && setSelection([v]),
|
|
1852
|
+
value: selection.mode === "include" ? selection.ids[0] ?? "" : "",
|
|
1853
|
+
onValueChange: (v) => v && setSelection({ mode: "include", ids: [v] }),
|
|
1831
1854
|
children: wrapWithDnd(tableContent)
|
|
1832
1855
|
}
|
|
1833
1856
|
);
|