@qijenchen/design-system 0.1.0-beta.80 → 0.1.0-beta.82
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/Alert/alert.d.ts +5 -0
- package/dist/components/Alert/alert.d.ts.map +1 -1
- package/dist/components/Alert/alert.js +4 -1
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.d.ts +2 -1
- package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts +3 -2
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +8 -3
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Badge/badge.d.ts.map +1 -1
- package/dist/components/Badge/badge.js +2 -1
- package/dist/components/Badge/badge.js.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts +14 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +9 -3
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +4 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +2 -2
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts +4 -4
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +73 -44
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Button/index.d.ts +1 -0
- package/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/Button/index.js +3 -0
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +3 -1
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +40 -17
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +3 -1
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +1 -1
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +6 -3
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +26 -4
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -1
- package/dist/components/Checkbox/index.js +3 -0
- package/dist/components/Checkbox/index.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +9 -3
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +46 -13
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +5 -3
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js +10 -7
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +3 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +11 -7
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +2 -2
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +4 -2
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
- package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +3 -0
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +38 -7
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +213 -63
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-tree.d.ts +5 -0
- package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.d.ts +3 -3
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +52 -6
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +2 -8
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +139 -109
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
- package/dist/components/DescriptionList/description-list.js +1 -1
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts +1 -1
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +2 -1
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -4
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +25 -8
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js +1 -1
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +1 -0
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +27 -7
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +1 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +2 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/use-form-validation.d.ts +5 -1
- package/dist/components/Field/use-form-validation.d.ts.map +1 -1
- package/dist/components/Field/use-form-validation.js +31 -21
- package/dist/components/Field/use-form-validation.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +3 -2
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts +1 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +22 -10
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/can-render-image.d.ts +6 -0
- package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
- package/dist/components/FileViewer/can-render-image.js +11 -0
- package/dist/components/FileViewer/can-render-image.js.map +1 -0
- package/dist/components/FileViewer/file-viewer.d.ts +2 -2
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +24 -16
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts +0 -5
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +1 -9
- package/dist/components/FileViewer/image-renderer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +3 -1
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/Input/input.d.ts +26 -3
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +12 -8
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +3 -3
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +29 -28
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +5 -4
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/Notice/notice.d.ts +5 -0
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +11 -5
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts +1 -1
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +17 -8
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/index.d.ts +2 -0
- package/dist/components/Pagination/index.d.ts.map +1 -0
- package/dist/components/Pagination/index.js +6 -0
- package/dist/components/Pagination/index.js.map +1 -0
- package/dist/components/Pagination/pagination.d.ts +49 -0
- package/dist/components/Pagination/pagination.d.ts.map +1 -0
- package/dist/components/Pagination/pagination.js +182 -0
- package/dist/components/Pagination/pagination.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker.d.ts +3 -3
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +38 -36
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +4 -0
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +3 -3
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +58 -32
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +11 -3
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js +14 -4
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +28 -3
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/rating.js +3 -3
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
- package/dist/components/ScrollArea/scroll-area.js +2 -2
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js +23 -10
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts +10 -4
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +57 -14
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.js +4 -2
- package/dist/components/SelectMenu/select-menu.d.ts +31 -6
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +169 -85
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/Separator/separator.js +1 -1
- package/dist/components/Separator/separator.js.map +1 -1
- package/dist/components/Sheet/sheet.d.ts +1 -1
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +3 -2
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +2 -2
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +28 -10
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Skeleton/skeleton.d.ts +1 -1
- package/dist/components/Skeleton/skeleton.js +2 -2
- package/dist/components/Skeleton/skeleton.js.map +1 -1
- package/dist/components/Slider/slider.d.ts +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js +7 -3
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +6 -3
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +2 -2
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +47 -9
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +8 -5
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +45 -33
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +20 -4
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +2 -1
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +24 -13
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +2 -2
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +23 -12
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Toast/toast.d.ts +1 -1
- package/dist/components/Toast/toast.d.ts.map +1 -1
- package/dist/components/Toast/toast.js +7 -3
- package/dist/components/Toast/toast.js.map +1 -1
- package/dist/components/Tooltip/tooltip.js +1 -1
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +1 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +11 -5
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/hooks/use-controllable.d.ts.map +1 -1
- package/dist/hooks/use-controllable.js +4 -2
- package/dist/hooks/use-controllable.js.map +1 -1
- package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
- package/dist/hooks/use-is-touch-device.js +3 -1
- package/dist/hooks/use-is-touch-device.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -2
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/dist/tokens/categorical-color.d.ts +1 -1
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/uiSize/icon-size.d.ts +2 -2
- package/ds-canonical/fork/governance.lock +1 -1
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
- package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
- package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
- package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
- package/ds-canonical/hooks/check_story_invariants.sh +17 -5
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
- package/ds-canonical/references/principle-dim-map.json +100 -0
- package/ds-canonical/references/story-baseline-registry.json +4 -3
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
- package/ds-story-manifest.json +41 -18
- package/llms-full.txt +11 -1
- package/llms.txt +3 -1
- package/package.json +1 -1
- package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
- package/src/components/Alert/alert.spec.md +3 -3
- package/src/components/Alert/alert.tsx +9 -1
- package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
- package/src/components/AppShell/app-shell.spec.md +3 -2
- package/src/components/AppShell/app-shell.tsx +1 -1
- package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
- package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
- package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
- package/src/components/Avatar/avatar.spec.md +10 -30
- package/src/components/Avatar/avatar.stories.tsx +1 -1
- package/src/components/Avatar/avatar.tsx +30 -16
- package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
- package/src/components/Badge/badge.spec.md +5 -4
- package/src/components/Badge/badge.tsx +4 -1
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
- package/src/components/Breadcrumb/breadcrumb.spec.md +5 -3
- package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +3 -2
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
- package/src/components/BulkActionBar/bulk-action-bar.tsx +10 -4
- package/src/components/Button/button.anatomy.stories.tsx +6 -6
- package/src/components/Button/button.spec.md +3 -2
- package/src/components/Button/button.tsx +67 -27
- package/src/components/Button/index.ts +1 -0
- package/src/components/Calendar/calendar.anatomy.stories.tsx +5 -3
- package/src/components/Calendar/calendar.principles.stories.tsx +3 -1
- package/src/components/Calendar/calendar.spec.md +9 -2
- package/src/components/Calendar/calendar.stories.tsx +3 -1
- package/src/components/Calendar/calendar.tsx +53 -19
- package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
- package/src/components/Carousel/carousel.tsx +4 -1
- package/src/components/Chart/chart.spec.md +1 -1
- package/src/components/Chart/chart.tsx +2 -1
- package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.spec.md +1 -1
- package/src/components/Checkbox/checkbox.tsx +41 -7
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Chip/chip.anatomy.stories.tsx +9 -9
- package/src/components/Chip/chip.principles.stories.tsx +3 -3
- package/src/components/Chip/chip.spec.md +7 -4
- package/src/components/Chip/chip.tsx +60 -15
- package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
- package/src/components/CircularProgress/circular-progress.spec.md +2 -0
- package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.spec.md +3 -3
- package/src/components/Coachmark/coachmark.tsx +23 -13
- package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
- package/src/components/Combobox/combobox.spec.md +1 -1
- package/src/components/Combobox/combobox.tsx +30 -17
- package/src/components/Command/command.principles.stories.tsx +10 -12
- package/src/components/Command/command.spec.md +5 -5
- package/src/components/Command/command.stories.tsx +4 -4
- package/src/components/Command/command.tsx +3 -2
- package/src/components/DataTable/active-editor-controller.ts +4 -3
- package/src/components/DataTable/cell-registry.tsx +8 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
- package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
- package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
- package/src/components/DataTable/data-table.css +2 -2
- package/src/components/DataTable/data-table.spec.md +72 -115
- package/src/components/DataTable/data-table.stories.tsx +51 -3
- package/src/components/DataTable/data-table.tsx +348 -63
- package/src/components/DataTable/filter-operators.spec.md +4 -4
- package/src/components/DataTable/filter-tree.ts +192 -42
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +11 -8
- package/src/components/DateGrid/date-grid.tsx +63 -8
- package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
- package/src/components/DatePicker/date-picker.spec.md +1 -0
- package/src/components/DatePicker/date-picker.stories.tsx +19 -0
- package/src/components/DatePicker/date-picker.tsx +52 -15
- package/src/components/DescriptionList/description-list.spec.md +1 -1
- package/src/components/DescriptionList/description-list.tsx +3 -1
- package/src/components/Dialog/dialog.spec.md +1 -1
- package/src/components/Dialog/dialog.tsx +2 -1
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
- package/src/components/DropdownMenu/dropdown-menu.spec.md +8 -5
- package/src/components/DropdownMenu/dropdown-menu.tsx +33 -7
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -2
- package/src/components/Empty/empty.stories.tsx +4 -15
- package/src/components/Empty/empty.tsx +4 -1
- package/src/components/Field/field-controls.spec.md +2 -1
- package/src/components/Field/field-wrapper.tsx +28 -8
- package/src/components/Field/field.anatomy.stories.tsx +7 -7
- package/src/components/Field/field.principles.stories.tsx +1 -1
- package/src/components/Field/field.spec.md +9 -7
- package/src/components/Field/field.tsx +2 -1
- package/src/components/Field/form-validation.spec.md +6 -3
- package/src/components/Field/use-form-validation.ts +49 -21
- package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
- package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
- package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
- package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
- package/src/components/FileItem/file-item.tsx +3 -2
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
- package/src/components/FileUpload/file-upload.spec.md +4 -1
- package/src/components/FileUpload/file-upload.tsx +36 -10
- package/src/components/FileViewer/can-render-image.ts +15 -0
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
- package/src/components/FileViewer/file-viewer.spec.md +2 -2
- package/src/components/FileViewer/file-viewer.tsx +47 -21
- package/src/components/FileViewer/image-renderer.tsx +3 -8
- package/src/components/HoverCard/hover-card.spec.md +1 -1
- package/src/components/HoverCard/hover-card.tsx +3 -1
- package/src/components/Input/input.anatomy.stories.tsx +4 -4
- package/src/components/Input/input.principles.stories.tsx +1 -1
- package/src/components/Input/input.spec.md +1 -1
- package/src/components/Input/input.stories.tsx +11 -5
- package/src/components/Input/input.tsx +11 -9
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
- package/src/components/LinkInput/link-input.tsx +49 -34
- package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
- package/src/components/Menu/menu-item.spec.md +4 -3
- package/src/components/Menu/menu-item.tsx +15 -4
- package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
- package/src/components/Notice/notice.spec.md +4 -3
- package/src/components/Notice/notice.tsx +22 -5
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
- package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
- package/src/components/NumberInput/number-input.spec.md +2 -1
- package/src/components/NumberInput/number-input.tsx +25 -9
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
- package/src/components/Pagination/index.ts +5 -0
- package/src/components/Pagination/pagination.anatomy.stories.tsx +143 -0
- package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
- package/src/components/Pagination/pagination.spec.md +150 -0
- package/src/components/Pagination/pagination.stories.tsx +98 -0
- package/src/components/Pagination/pagination.tsx +263 -0
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.spec.md +4 -4
- package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.tsx +65 -55
- package/src/components/PeoplePicker/person-display.tsx +10 -4
- package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
- package/src/components/Popover/popover.spec.md +13 -14
- package/src/components/Popover/popover.tsx +88 -39
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
- package/src/components/ProfileCard/profile-card.spec.md +1 -2
- package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +29 -8
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
- package/src/components/RadioGroup/radio-group.spec.md +2 -2
- package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
- package/src/components/RadioGroup/radio-group.tsx +37 -3
- package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
- package/src/components/Rating/rating.principles.stories.tsx +1 -1
- package/src/components/Rating/rating.spec.md +2 -2
- package/src/components/Rating/rating.stories.tsx +1 -1
- package/src/components/Rating/rating.tsx +3 -3
- package/src/components/ScrollArea/scroll-area.spec.md +1 -0
- package/src/components/ScrollArea/scroll-area.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +3 -3
- package/src/components/SegmentedControl/segmented-control.spec.md +7 -3
- package/src/components/SegmentedControl/segmented-control.tsx +29 -11
- package/src/components/Select/select.anatomy.stories.tsx +2 -2
- package/src/components/Select/select.spec.md +3 -2
- package/src/components/Select/select.tsx +62 -19
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.spec.md +12 -8
- package/src/components/SelectMenu/select-menu.tsx +176 -20
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.spec.md +2 -1
- package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
- package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
- package/src/components/Separator/separator.tsx +1 -1
- package/src/components/Sheet/sheet.spec.md +2 -1
- package/src/components/Sheet/sheet.tsx +4 -2
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
- package/src/components/Sidebar/sidebar.spec.md +2 -1
- package/src/components/Sidebar/sidebar.stories.tsx +6 -4
- package/src/components/Sidebar/sidebar.tsx +41 -13
- package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
- package/src/components/Skeleton/skeleton.spec.md +1 -2
- package/src/components/Skeleton/skeleton.tsx +1 -1
- package/src/components/Slider/slider.anatomy.stories.tsx +4 -4
- package/src/components/Slider/slider.spec.md +19 -85
- package/src/components/Slider/slider.tsx +13 -5
- package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +6 -4
- package/src/components/Steps/steps.tsx +11 -3
- package/src/components/Switch/switch.spec.md +4 -3
- package/src/components/Switch/switch.tsx +59 -10
- package/src/components/Tabs/tabs.anatomy.stories.tsx +7 -6
- package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
- package/src/components/Tabs/tabs.spec.md +15 -48
- package/src/components/Tabs/tabs.tsx +70 -34
- package/src/components/Tag/tag.principles.stories.tsx +2 -2
- package/src/components/Tag/tag.spec.md +11 -43
- package/src/components/Tag/tag.tsx +30 -4
- package/src/components/Textarea/textarea.principles.stories.tsx +2 -1
- package/src/components/Textarea/textarea.spec.md +2 -1
- package/src/components/Textarea/textarea.tsx +20 -10
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
- package/src/components/TimePicker/time-picker.spec.md +2 -2
- package/src/components/TimePicker/time-picker.tsx +35 -16
- package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
- package/src/components/Toast/toast.principles.stories.tsx +10 -11
- package/src/components/Toast/toast.spec.md +5 -5
- package/src/components/Toast/toast.stories.tsx +5 -1
- package/src/components/Toast/toast.tsx +13 -7
- package/src/components/Tooltip/tooltip.spec.md +3 -3
- package/src/components/Tooltip/tooltip.tsx +1 -1
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
- package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
- package/src/components/TreeView/tree-view.spec.md +2 -2
- package/src/components/TreeView/tree-view.tsx +27 -5
- package/src/hooks/use-controllable.ts +7 -2
- package/src/hooks/use-is-touch-device.ts +8 -1
- package/src/index.ts +2 -1
- package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
- package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.spec.md +5 -14
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +5 -6
- package/src/patterns/header-canonical/header-canonical.css +1 -2
- package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
- package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +137 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
- package/src/patterns/overlay-surface/overlay-surface.stories.tsx +116 -0
- package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
- package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
- package/src/tokens/categorical-color.ts +1 -1
- package/src/tokens/color/color.spec.md +11 -13
- package/src/tokens/color/color.stories.tsx +1 -1
- package/src/tokens/color/primitives.css +7 -5
- package/src/tokens/color/semantic.css +8 -2
- package/src/tokens/density/density.spec.md +2 -0
- package/src/tokens/elevation/elevation.spec.md +1 -1
- package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -1
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
- package/src/tokens/motion/motion.spec.md +1 -1
- package/src/tokens/motion/motion.stories.tsx +142 -0
- package/src/tokens/opacity/opacity.spec.md +1 -1
- package/src/tokens/opacity/opacity.stories.tsx +7 -11
- package/src/tokens/radius/radius.spec.md +1 -1
- package/src/tokens/typography/typography.css +1 -1
- package/src/tokens/typography/typography.spec.md +1 -1
- package/src/tokens/uiSize/icon-size.ts +2 -2
- package/src/tokens/uiSize/uiSize.spec.md +12 -11
- package/src/tokens/uiSize/uiSize.stories.tsx +103 -0
- package/src/tokens/utility-registry.json +5 -4
- package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
- package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
3
|
+
import { Pagination } from './pagination'
|
|
4
|
+
import { SegmentedControl, SegmentedControlItem } from '@/design-system/components/SegmentedControl/segmented-control'
|
|
5
|
+
import { Button } from '@/design-system/components/Button/button'
|
|
6
|
+
|
|
7
|
+
const meta: Meta = {
|
|
8
|
+
title: 'Design System/Components/Pagination/設計原則',
|
|
9
|
+
parameters: { layout: 'padded' },
|
|
10
|
+
}
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj
|
|
13
|
+
|
|
14
|
+
// ── Helpers(同 Breadcrumb principles idiom) ──────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const Rule = ({
|
|
17
|
+
title, note, children,
|
|
18
|
+
}: {
|
|
19
|
+
title: string; note?: string; children: React.ReactNode
|
|
20
|
+
}) => (
|
|
21
|
+
<div className="mb-14">
|
|
22
|
+
<h3 className="text-body font-bold text-foreground mb-1">{title}</h3>
|
|
23
|
+
{note && <p className="text-caption text-fg-muted mb-5 max-w-[720px] leading-relaxed">{note}</p>}
|
|
24
|
+
<div className="flex flex-col gap-3">{children}</div>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }) => (
|
|
29
|
+
<p className={`text-footnote leading-normal ${warn ? 'text-error font-medium' : 'text-fg-muted'}`}>{children}</p>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// ── UsageGuidance — 整合何時用 / 何時不用 / vs 近親(v3 canonical,Polaris/Material/Ant 共識)──
|
|
33
|
+
|
|
34
|
+
export const UsageGuidance: Story = {
|
|
35
|
+
name: '使用指引',
|
|
36
|
+
render: () => {
|
|
37
|
+
const [page, setPage] = React.useState(2)
|
|
38
|
+
const [view, setView] = React.useState('list')
|
|
39
|
+
return (
|
|
40
|
+
<div>
|
|
41
|
+
<Rule
|
|
42
|
+
title="何時用:大量資料切頁後的位置導覽"
|
|
43
|
+
note="使用者需要知道總頁數、且可能直接跳到某頁的順序瀏覽 —— 訂單列表、搜尋結果、SKU 目錄。表格情境優先用 <DataTable pagination> 內建(內部就是消費本元件),standalone 留給非 DataTable 的列表頁。"
|
|
44
|
+
>
|
|
45
|
+
<Pagination total={128} page={page} onPageChange={setPage} />
|
|
46
|
+
<Label>✓ 訂單管理列表底部:128 筆訂單 / 每頁 20 筆,置於列表下方、間距 tight</Label>
|
|
47
|
+
</Rule>
|
|
48
|
+
|
|
49
|
+
<Rule
|
|
50
|
+
title="何時不用:無盡瀏覽 feed → load-more / 虛擬滾動"
|
|
51
|
+
note="activity stream、通知列表這類「往下讀」的流,頁碼是錯的心智模型 —— 使用者不在乎第幾頁,只在乎更多內容。與虛擬滾動互斥:兩者是互斥的大資料策略(TanStack 官方定位),同一份資料不可同時用。"
|
|
52
|
+
>
|
|
53
|
+
<Button variant="secondary">載入更多通知</Button>
|
|
54
|
+
<Label>✓ feed 尾端用 load-more;超大資料集在 DataTable 內建虛擬滾動</Label>
|
|
55
|
+
<Label warn>✗ 給 activity feed 加頁碼 —— 讀流被切斷,回頭找內容也記不住頁碼</Label>
|
|
56
|
+
</Rule>
|
|
57
|
+
|
|
58
|
+
<Rule
|
|
59
|
+
title="vs SegmentedControl:位置導覽 ≠ 互斥選值"
|
|
60
|
+
note="兩者都是「多顆亮一顆」,語意完全不同:SegmentedControl 選項固定 2-5 個、每顆有語意(清單/看板/時間軸),恆有一值;Pagination 頁數動態、可摺疊、數字本身無語意,當前頁是「你在哪」不是「你選了什麼」。"
|
|
61
|
+
>
|
|
62
|
+
<SegmentedControl value={view} onValueChange={(v) => v && setView(v)} aria-label="檢視切換">
|
|
63
|
+
<SegmentedControlItem value="list">清單</SegmentedControlItem>
|
|
64
|
+
<SegmentedControlItem value="board">看板</SegmentedControlItem>
|
|
65
|
+
<SegmentedControlItem value="timeline">時間軸</SegmentedControlItem>
|
|
66
|
+
</SegmentedControl>
|
|
67
|
+
<Label>✓ 檢視切換用 SegmentedControl(互斥選值)</Label>
|
|
68
|
+
<Label warn>✗ 用 Pagination 做檢視切換,或用 SegmentedControl 排頁碼</Label>
|
|
69
|
+
</Rule>
|
|
70
|
+
|
|
71
|
+
<Rule
|
|
72
|
+
title="流程推進不是翻頁"
|
|
73
|
+
note="精靈流程「上一步 / 下一步」走 Steps(進度指示)+ Button(推進),不是頁碼 —— 頁碼可任意跳,流程有順序依賴;混用會讓使用者以為可以跳過必填步驟。"
|
|
74
|
+
>
|
|
75
|
+
<Label>✓ 註冊精靈 / 建立專案引導 → Steps + Button</Label>
|
|
76
|
+
<Label warn>✗ 精靈底部放 1 2 3 4 頁碼</Label>
|
|
77
|
+
</Rule>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
},
|
|
81
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Pagination
|
|
3
|
+
family: composite
|
|
4
|
+
variants: {}
|
|
5
|
+
sizes: {}
|
|
6
|
+
traits:
|
|
7
|
+
- hasInteractiveStates
|
|
8
|
+
- isStructural
|
|
9
|
+
benchmark:
|
|
10
|
+
- Ant Design Pagination: github.com/ant-design/ant-design/tree/master/components/pagination
|
|
11
|
+
- MUI Pagination: github.com/mui/material-ui/tree/master/packages/mui-material/src/Pagination
|
|
12
|
+
- Atlassian Pagination: atlassian.design/components/pagination
|
|
13
|
+
- shadcn Pagination: ui.shadcn.com/docs/components/pagination
|
|
14
|
+
- TanStack Table Pagination: tanstack.com/table/latest/docs/guide/pagination
|
|
15
|
+
- Carbon Pagination: github.com/carbon-design-system/carbon/tree/main/packages/react/src/components/Pagination
|
|
16
|
+
- Polaris Pagination: polaris-react.shopify.com/components/navigation/pagination
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Pagination 設計原則
|
|
20
|
+
|
|
21
|
+
## 定位
|
|
22
|
+
|
|
23
|
+
Pagination 是「大量資料切成多頁後的位置導覽」——顯示當前頁、允許直接跳到任一頁。
|
|
24
|
+
|
|
25
|
+
基於 shadcn/ui Pagination 的 DOM 骨架(`nav >(操作群 div)> ul > li` + 當前頁 `aria-current="page"`),但**按鈕消費 DS `Button`、事件驅動、資料模型 = `total`(總筆數)+ `pageSize`(Ant Pagination 同款,總頁數內部推導),非 shadcn 原生的 `<a href>` 連結型**——本 DS 服務 app 產品場景;需要 URL 同步時由 consumer 在 `onPageChange` 接 router,不是把按鈕換成連結。`page` / `pageSize` 皆 dual-mode(傳值 = controlled,不傳 = uncontrolled)。
|
|
26
|
+
|
|
27
|
+
**本元件是分頁的完整功能 SSOT**(2026-07-06 user 拍板):頁碼、總筆數資訊(`showTotal`)、每頁筆數選單(`pageSizeOptions`)全部 own 在此——Ant(showTotal / showSizeChanger 內建於 Pagination)/ Carbon(page-size select 內建)/ MUI TablePagination(rows-per-page + range 內建)同派。DataTable 等 consumer **轉發 config 消費本元件**,不自拼分頁列。
|
|
28
|
+
|
|
29
|
+
**Layout Family**:非 4-family — self-contained composite(nav 橫排 control row);個別按鈕消費 Family 3(Button Pill)。
|
|
30
|
+
|
|
31
|
+
**樣式派系**(2026-07-05 user 拍板):數字頁碼派(上下頁箭頭 + 數字 + ellipsis 摺疊)——shadcn / MUI / Ant / Atlassian 同派;非 Polaris(純上下頁)/ Carbon(page Select 可跳頁 + 上下頁,無數字鈕列)派。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 何時用
|
|
36
|
+
|
|
37
|
+
- 資料表翻頁(**優先用 `<DataTable pagination>` 內建**,見下方分界)
|
|
38
|
+
- server 分批載入的列表頁 / 搜尋結果頁(消費端自行 slice 或 server 端分頁)
|
|
39
|
+
- 任何「使用者需要知道總頁數、且可能想直接跳到某頁」的順序瀏覽
|
|
40
|
+
|
|
41
|
+
## 何時不用
|
|
42
|
+
|
|
43
|
+
| 情境 | 改用 |
|
|
44
|
+
|------|------|
|
|
45
|
+
| 無盡瀏覽 feed(activity stream / 通知列表) | 虛擬滾動或 load-more,不是頁碼 |
|
|
46
|
+
| 資料表且已用 `<DataTable>` | `<DataTable pagination>` 內建(不要自己在表格下手排 standalone Pagination) |
|
|
47
|
+
| 精靈流程「上一步 / 下一步」 | `Steps`(流程進度)+ Button——頁碼是**位置導覽**,不是流程推進 |
|
|
48
|
+
| 檢視切換(清單 / 看板 / 時間軸) | `SegmentedControl`(互斥選值) |
|
|
49
|
+
| 資料不足 2 頁且未來也不會成長 | 不放 Pagination(元件本身單頁仍照常渲染;`total <= 0` 才不渲染——見邊界案例) |
|
|
50
|
+
|
|
51
|
+
## 近親分界
|
|
52
|
+
|
|
53
|
+
- **vs `SegmentedControl`**:兩者都是「多顆中亮一顆」,但語意不同——SegmentedControl 是**互斥選值**(選項固定 2-5 個、每個選項有語意),Pagination 是**位置導覽**(頁數動態、可摺疊、數字本身無語意)。SegmentedControl 恆有一值且不可空;Pagination 的當前頁是「你在哪」不是「你選了什麼」。
|
|
54
|
+
- **vs `<DataTable pagination>`**:DataTable 把 TanStack 分頁模型(`getPaginationRowModel`)封裝在內部並消費本元件 render 分頁列(Ant Table 消費 Pagination / Atlassian DynamicTable `rowsPerPage` 同模式)。表格場景一律走內建;standalone Pagination 留給非 DataTable 的列表 / 搜尋結果頁。
|
|
55
|
+
- **vs 虛擬滾動**:兩者是互斥的大資料策略(TanStack 官方定位為 alternative strategies)——分頁 = 使用者主動換頁、每頁固定筆數;虛擬滾動 = 連續滾動、只渲染可視列。同一份資料不可同時用兩者。
|
|
56
|
+
- **vs `Button secondary`(語意分界)**:當前頁「視覺上」= secondary 一比一(直接消費),但**語意兩條線**——secondary 是「次要動作」(action emphasis),當前頁是「持續選中」(selection state,由 `aria-current="page"` 承載)。在一般 UI 不得反過來拿 secondary 冒充「選中」表達(選中語意元件走 Chip / SegmentedControl / Pagination);此處是 selection 元件消費 action variant 當視覺 host,方向合法。
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 內部結構與摺疊規則
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
◀ 上一頁 1 … 4 [5] 6 … 12 下一頁 ▶
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **格位配置**:boundary 1 + sibling 1(= MUI `boundaryCount` / `siblingCount` 預設 1/1;內部常數,v1 不開 props——未來若開放,命名必沿用 MUI)。最大格位 = 首尾各 1 + 當前頁左右各 1 + 當前頁 + 2 顆 ellipsis = **7 格**,超過即摺疊。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
67
|
+
- **Ellipsis 非互動**:純指示符號(`MoreHorizontal` icon + `aria-hidden`),不可點——對齊 Ant / MUI / shadcn;Breadcrumb 的可點 ellipsis 是 collapse dropdown 場景,語意不同。
|
|
68
|
+
- **上下頁按鈕**:iconOnly(`ChevronLeft` / `ChevronRight`),在第一頁 / 最後一頁時 `disabled`。
|
|
69
|
+
- 頁碼格位由內部演算法產生;兩顆省略號各自身分穩定,翻頁時不互相 remount(演算法簽名與 React key 等實作細節由 `pagination.tsx` 檔內註解單一持有,spec 不重述——職責分離)。
|
|
70
|
+
|
|
71
|
+
## 視覺
|
|
72
|
+
|
|
73
|
+
- **全部按鈕 = Button variant 純組裝,零視覺客製**(2026-07-06 user 抓出後精煉):上下頁 = `text` iconOnly、未選數字 = `text`、**當前頁 = `secondary`**——secondary 的 rest(primary 描邊+染字+不染底)/ hover(升 hover 階)/ active(深一階)與 `semantic.css`「選中狀態」canonical 完全同拼寫,直接消費不手寫平行副本(M17/M23)。唯一的 className = 等寬幾何(min-width 方形節奏對齊 Ant,屬 bundled family 合法)。
|
|
74
|
+
- **語意與視覺分工**:「選中」語意由 `aria-current="page"` 承載;Button secondary 僅為視覺 host(action 元件本身無選中語意,見近親分界)。與 Chip / SegmentedControl 選中同一套 token 語言(它們非 Button、自建 element 鏡射同拼寫);同時正是 Ant 當前頁做法(`colorPrimary` 描邊+字+不染底、hover `colorPrimaryHover`)。
|
|
75
|
+
- **間距兩層**:頁碼列內部(數字鈕之間)屬 bundled family(`tokens/layoutSpace/layoutSpace.spec.md` 已明文「Pagination 內部」元件自管),元件固定;完整形態的「資訊 ↔ 操作群」與「頁碼 ↔ 每頁選單」群組間距消費 `--layout-space-tight`(density-aware,與 DataTable 表格→分頁列同 token)。
|
|
76
|
+
|
|
77
|
+
## 完整形態(showTotal / pageSizeOptions)
|
|
78
|
+
|
|
79
|
+
**Layout =「資訊左、操作右」**(對齊 Ant 源碼結構:total 文字是最左元素、size changer 是最右元素——資訊「你在哪」與操作「換頁/換每頁筆數」分離):
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
第 1–20 筆,共 85 筆 ◀ [1] 2 3 4 5 ▶ [20 筆/頁 ▾]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `showTotal`(opt-in):左側 range 格式「第 x–y 筆,共 N 筆」——Ant「1-20 of 85 items」/ MUI「1–5 of 13」/ Carbon「1–10 of 128 items」三家共識,非單純總數。
|
|
86
|
+
- `pageSizeOptions`(opt-in):頁碼右側「N 筆/頁」選單(文案內嵌 = Ant「20 / page」同款;消費既有 `Select size="sm"`);變更每頁筆數自動回第 1 頁——MUI TablePagination / TanStack `autoResetPageIndex` 派;Ant 為 preserve-position(clamp)派,本 DS 採 reset-to-1 避免換大 pageSize 後停在超界頁的 clamp 歧義。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
87
|
+
- 兩者皆未開 = 純頁碼 inline 形態(consumer layout 決定對齊;表格情境靠右由 DataTable 處理)。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 邊界案例
|
|
92
|
+
|
|
93
|
+
| 情境 | 行為 |
|
|
94
|
+
|------|------|
|
|
95
|
+
| `total <= 0` | 不渲染(回傳 null)——沒有資料可導覽 |
|
|
96
|
+
| 單頁(`total <= pageSize`) | **照常渲染**(單顆當前頁 + disabled 上下頁)——對齊 Ant `hideOnSinglePage` 預設 false + MUI;保留版面穩定,避免資料量在 1 頁邊界抖動時整條列出現消失 |
|
|
97
|
+
| `page` 越界(< 1 或 > 總頁數) | 防禦性 clamp 後渲染(顯示層不炸);糾正責任在 consumer / DataTable(接資料變動時同步 clamp) |
|
|
98
|
+
| `pageSize <= 0`(consumer contract violation) | 防禦 clamp 至 1(不渲染垃圾格位、totalPages 不會變 Infinity/NaN) |
|
|
99
|
+
| 極大總頁數(500+) | 摺疊規則不變(恆 ≤ 7 格);數字鈕寬度容納 3-4 位數自然撐開 |
|
|
100
|
+
| 邊界頁 disabled 時 focus 落點 | 接受 focus 掉落(對齊 Ant / MUI 行為);不做 focus 轉移 |
|
|
101
|
+
|
|
102
|
+
## Loading / 空值
|
|
103
|
+
|
|
104
|
+
本元件無 loading 態——換頁中的資料 loading 由列表 / DataTable 層表達(skeleton / CircularProgress),頁碼列維持可互動或由 consumer disable。無資料(`total <= 0`)即不渲染,空狀態由 `Empty` 表達。
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 與 DataTable 的關係
|
|
109
|
+
|
|
110
|
+
分頁列本體(頁碼、showTotal、每頁筆數選單、資訊左操作右 layout)**SSOT 在本元件**;DataTable 只做:轉發 config(`pagination` prop → 本元件 props)、own TanStack state(controlled 消費)、表格→分頁列 tight 間距、純頁碼形態靠右、與虛擬滾動互斥——這些表格側規則 → `../DataTable/data-table.spec.md`「L5:分頁」段。
|
|
111
|
+
|
|
112
|
+
## 禁止事項
|
|
113
|
+
|
|
114
|
+
- ❌ **手刻當前頁選中樣式**——必消費選中 canonical(semantic.css SSOT);選中態不得用 primary-hover(那是瞬時 hover 專用階)
|
|
115
|
+
- ❌ **把按鈕換成 `<a href>`**——URL 同步由 consumer 在 `onPageChange` 接 router;連結型會破壞 controlled 契約與 Button 消費
|
|
116
|
+
- ❌ **與虛擬滾動並用**(同一份資料同時分頁 + 虛擬化)
|
|
117
|
+
- ❌ **把 ellipsis 做成可點按鈕**(快速跳頁需求走未來 `showQuickJumper` 類擴充,不是 ellipsis)
|
|
118
|
+
- ❌ **用 Pagination 做檢視切換 / 流程推進**(見何時不用)
|
|
119
|
+
- ❌ **在 DataTable 下方自行手排 standalone Pagination**(走 `<DataTable pagination>` 內建)
|
|
120
|
+
|
|
121
|
+
## A11y 預設
|
|
122
|
+
|
|
123
|
+
**ARIA / Pattern**:WAI-ARIA 無專門 pagination pattern,公認做法(shadcn / MUI / Atlassian 一致)= landmark + current 標記:
|
|
124
|
+
|
|
125
|
+
- root `<nav aria-label="Pagination">`(landmark;label 用英文 pattern 名,follow Breadcrumb `aria-label="Breadcrumb"` 慣例;consumer 可覆寫)
|
|
126
|
+
- 當前頁按鈕 `aria-current="page"`(**不用** `aria-pressed`——pressed 是可取消的 toggle 語意,當前頁不可「取消」)
|
|
127
|
+
- ellipsis `aria-hidden`(純視覺指示)
|
|
128
|
+
- 上下頁 iconOnly 按鈕帶 `aria-label`(預設「上一頁」/「下一頁」,`prevAriaLabel` / `nextAriaLabel` props 可覆寫)
|
|
129
|
+
|
|
130
|
+
**鍵盤**:Tab 逐鈕聚焦(原生 button 序)、Enter / Space 觸發——全部繼承 Button 原生語意,無自訂 key handler;不做 roving tabindex(頁碼是 nav landmark 內的獨立按鈕群,非 composite widget,對齊 shadcn / Ant / MUI)。
|
|
131
|
+
|
|
132
|
+
**驗證**:Storybook a11y 面板 0 critical violation;純鍵盤可完成翻頁與跳頁。
|
|
133
|
+
|
|
134
|
+
**SizeMatrix N/A rationale**:本元件無 size 軸(單一尺寸,按鈕固定 `size="sm"`)——頁碼列是 chrome 級導覽,不隨 Field 密度縮放(Ant / MUI / Carbon 的 size 變體服務其自家 density 系統;本 DS 表格密度由 DataTable size 控制、分頁列不連動)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
135
|
+
|
|
136
|
+
## 相關
|
|
137
|
+
|
|
138
|
+
- `../Button/button.spec.md` — 按鈕消費(text variant / iconOnly / disabled)
|
|
139
|
+
- `../Chip/chip.spec.md` — 「持續選中」視覺 canonical 同族(描邊+染字、不染底)
|
|
140
|
+
- `../SegmentedControl/segmented-control.spec.md` — 近親分界(互斥選值 vs 位置導覽)
|
|
141
|
+
- `../DataTable/data-table.spec.md` — 表格內建分頁(L5 段,SSOT)
|
|
142
|
+
- `../Steps/steps.spec.md` — 流程進度 vs 位置導覽分界
|
|
143
|
+
- `../../tokens/color/semantic.css` — 選中狀態 token SSOT
|
|
144
|
+
|
|
145
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
146
|
+
|
|
147
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
148
|
+
|
|
149
|
+
- `data-table.spec.md`
|
|
150
|
+
- `segmented-control.spec.md`
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
3
|
+
import { Pagination } from './pagination'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Pagination> = {
|
|
6
|
+
title: 'Design System/Components/Pagination/展示',
|
|
7
|
+
component: Pagination,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
}
|
|
10
|
+
export default meta
|
|
11
|
+
type Story = StoryObj<typeof Pagination>
|
|
12
|
+
|
|
13
|
+
/* ── 預設 ── */
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
name: '預設',
|
|
16
|
+
render: () => {
|
|
17
|
+
const [page, setPage] = React.useState(3)
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex flex-col gap-3">
|
|
20
|
+
<p className="text-caption text-fg-muted">
|
|
21
|
+
客服工單佇列:total=128 / 每頁預設 20 → 7 頁(總頁數由 total/pageSize 內部推導,
|
|
22
|
+
Ant 同款資料模型)。當前頁 `aria-current="page"`,選中 = 主色描邊+染字不染底;
|
|
23
|
+
選中之上 hover 升 hover 階、按壓 active 階。
|
|
24
|
+
</p>
|
|
25
|
+
<Pagination total={128} page={page} onPageChange={setPage} />
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ── 完整形態(資訊左、操作右) ── */
|
|
32
|
+
export const FullBar: Story = {
|
|
33
|
+
name: '完整形態',
|
|
34
|
+
render: () => {
|
|
35
|
+
const [page, setPage] = React.useState(1)
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex flex-col gap-3">
|
|
38
|
+
<p className="text-caption text-fg-muted">
|
|
39
|
+
搜尋結果頁:showTotal(「第 x–y 筆,共 N 筆」range 格式,Ant/MUI/Carbon 共識)+
|
|
40
|
+
pageSizeOptions(「N 筆/頁」選單)—— 分頁完整功能 own 在本元件(SSOT),layout =
|
|
41
|
+
資訊左、操作群(頁碼+每頁筆數)右,對齊 Ant 源碼結構;換每頁筆數自動回第 1 頁。
|
|
42
|
+
</p>
|
|
43
|
+
<Pagination
|
|
44
|
+
total={85}
|
|
45
|
+
page={page}
|
|
46
|
+
onPageChange={setPage}
|
|
47
|
+
showTotal
|
|
48
|
+
pageSizeOptions={[10, 20, 50]}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── 大量頁數摺疊 ── */
|
|
56
|
+
export const ManyPages: Story = {
|
|
57
|
+
name: '大量頁數摺疊',
|
|
58
|
+
render: () => {
|
|
59
|
+
const [page, setPage] = React.useState(42)
|
|
60
|
+
return (
|
|
61
|
+
<div className="flex flex-col gap-3">
|
|
62
|
+
<p className="text-caption text-fg-muted">
|
|
63
|
+
商品 SKU 目錄 10,000 筆 / 每頁 20 = 500 頁:恆 ≤ 7 格位、雙側 … 摺疊(boundary/sibling
|
|
64
|
+
= 1/1,MUI 預設同款);… 為純指示不可點。翻到頭尾附近時摺疊自動變單側。
|
|
65
|
+
</p>
|
|
66
|
+
<Pagination total={10000} page={page} onPageChange={setPage} />
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ── 邊界狀態(R3 hasInteractiveStates) ── */
|
|
73
|
+
export const States: Story = {
|
|
74
|
+
name: '邊界狀態',
|
|
75
|
+
render: () => {
|
|
76
|
+
const [first, setFirst] = React.useState(1)
|
|
77
|
+
const [last, setLast] = React.useState(7)
|
|
78
|
+
return (
|
|
79
|
+
<div className="flex flex-col gap-6">
|
|
80
|
+
<div>
|
|
81
|
+
<p className="text-caption text-fg-muted mb-2">第一頁 — 上一頁按鈕 disabled</p>
|
|
82
|
+
<Pagination total={128} page={first} onPageChange={setFirst} />
|
|
83
|
+
</div>
|
|
84
|
+
<div>
|
|
85
|
+
<p className="text-caption text-fg-muted mb-2">最後一頁 — 下一頁按鈕 disabled</p>
|
|
86
|
+
<Pagination total={128} page={last} onPageChange={setLast} />
|
|
87
|
+
</div>
|
|
88
|
+
<div>
|
|
89
|
+
<p className="text-caption text-fg-muted mb-2">
|
|
90
|
+
單頁(total ≤ pageSize)— 照常渲染保持版面穩定(Ant hideOnSinglePage=false + MUI 同);
|
|
91
|
+
total=0 則整個不渲染
|
|
92
|
+
</p>
|
|
93
|
+
<Pagination total={15} page={1} onPageChange={() => {}} />
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
},
|
|
98
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// ────────────────────────────── 消費的 SSOT ──────────────────────────────
|
|
2
|
+
// - Button:../Button/button.tsx —— 全部按鈕純 variant 組裝零視覺客製(2026-07-06 user 抓出):
|
|
3
|
+
// 上下頁 = text iconOnly / 未選數字 = text / 當前頁 = secondary(rest primary 描邊染字不染底、
|
|
4
|
+
// hover 升 hover 階、active 深一階 —— 與 semantic.css「選中」canonical + 拍板 #8 完全同拼寫);
|
|
5
|
+
// 選中「語意」由 aria-current="page" 承載(非 pressed —— pressed 是可取消 toggle,當前頁不可取消)
|
|
6
|
+
// - Select(size="sm"):../Select/select.tsx —— 完整形態的「N 筆/頁」選單
|
|
7
|
+
// - Ellipsis icon:MoreHorizontal(.claude/rules/ui-development.md「Icon canonical」)
|
|
8
|
+
// - DOM 骨架:shadcn Pagination(nav > ul > li + aria-current="page");
|
|
9
|
+
// controlled 事件驅動 + total/pageSize 資料模型對齊 Ant Pagination(2026-07-06 user 拍板:
|
|
10
|
+
// Pagination 本體 = 完整功能 SSOT —— showTotal / 每頁筆數皆本元件 own,Table 轉發 config 消費)
|
|
11
|
+
// 設計規則:./pagination.spec.md
|
|
12
|
+
import * as React from 'react'
|
|
13
|
+
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react'
|
|
14
|
+
import { cn } from '@/lib/utils'
|
|
15
|
+
import { Button } from '@/design-system/components/Button/button'
|
|
16
|
+
import { Select } from '@/design-system/components/Select/select'
|
|
17
|
+
import { useControllable } from '@/design-system/hooks/use-controllable'
|
|
18
|
+
|
|
19
|
+
// MUI usePagination 預設同款(boundaryCount/siblingCount = 1/1)。內部常數不開 props(M21
|
|
20
|
+
// 最小 API);未來若開放,命名必沿用 MUI siblingCount / boundaryCount。
|
|
21
|
+
// 最大格位 = 首尾各 1 + 當前頁左右各 1 + 當前頁 + 2 顆 ellipsis = 7,超過即摺疊。
|
|
22
|
+
const BOUNDARY_COUNT = 1
|
|
23
|
+
const SIBLING_COUNT = 1
|
|
24
|
+
|
|
25
|
+
type PaginationSlot = number | 'ellipsis-start' | 'ellipsis-end'
|
|
26
|
+
|
|
27
|
+
// 摺疊演算法照 MUI usePagination(1/1 配置);兩顆 ellipsis 用 distinct 穩定 key
|
|
28
|
+
// (Breadcrumb Math.random key remount 前車之鑑,breadcrumb.tsx:208-210)。
|
|
29
|
+
function getPaginationRange(page: number, totalPages: number): PaginationSlot[] {
|
|
30
|
+
const range = (start: number, end: number) =>
|
|
31
|
+
Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
|
32
|
+
|
|
33
|
+
// 全部放得下(≤ 7 頁)→ 不摺疊
|
|
34
|
+
if (totalPages <= BOUNDARY_COUNT * 2 + SIBLING_COUNT * 2 + 3) return range(1, totalPages)
|
|
35
|
+
|
|
36
|
+
const startPages = range(1, BOUNDARY_COUNT)
|
|
37
|
+
const endPages = range(totalPages - BOUNDARY_COUNT + 1, totalPages)
|
|
38
|
+
const siblingsStart = Math.max(
|
|
39
|
+
Math.min(page - SIBLING_COUNT, totalPages - BOUNDARY_COUNT - SIBLING_COUNT * 2 - 1),
|
|
40
|
+
BOUNDARY_COUNT + 2,
|
|
41
|
+
)
|
|
42
|
+
const siblingsEnd = Math.min(
|
|
43
|
+
Math.max(page + SIBLING_COUNT, BOUNDARY_COUNT + SIBLING_COUNT * 2 + 2),
|
|
44
|
+
totalPages - BOUNDARY_COUNT - 1,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return [
|
|
48
|
+
...startPages,
|
|
49
|
+
...(siblingsStart > BOUNDARY_COUNT + 2
|
|
50
|
+
? (['ellipsis-start'] as const)
|
|
51
|
+
: [BOUNDARY_COUNT + 1]),
|
|
52
|
+
...range(siblingsStart, siblingsEnd),
|
|
53
|
+
...(siblingsEnd < totalPages - BOUNDARY_COUNT - 1
|
|
54
|
+
? (['ellipsis-end'] as const)
|
|
55
|
+
: [totalPages - BOUNDARY_COUNT]),
|
|
56
|
+
...endPages,
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface PaginationProps extends React.ComponentPropsWithoutRef<'nav'> {
|
|
61
|
+
/** 資料總筆數(Ant `total` 同款資料模型;<= 0 不渲染)。總頁數 = ceil(total / pageSize) 內部推導。 */
|
|
62
|
+
total: number
|
|
63
|
+
/** 每頁筆數(controlled;搭配 onPageSizeChange)。不傳 = uncontrolled(defaultPageSize 起始)。 */
|
|
64
|
+
pageSize?: number
|
|
65
|
+
/** uncontrolled 每頁筆數初始值(預設 20)。 */
|
|
66
|
+
defaultPageSize?: number
|
|
67
|
+
/** 當前頁(1-based,controlled)。不傳 = uncontrolled(defaultPage 起始)。越界防禦性 clamp。 */
|
|
68
|
+
page?: number
|
|
69
|
+
/** uncontrolled 初始頁(1-based,預設 1)。 */
|
|
70
|
+
defaultPage?: number
|
|
71
|
+
/** 換頁 callback(1-based)。URL 同步在此接 router,不把按鈕換 <a>。每頁筆數變更時自動回第 1 頁並 fire。 */
|
|
72
|
+
onPageChange?: (page: number) => void
|
|
73
|
+
/** 每頁筆數變更 callback。 */
|
|
74
|
+
onPageSizeChange?: (pageSize: number) => void
|
|
75
|
+
/** 完整形態:左側顯示「第 x–y 筆,共 N 筆」range 資訊(Ant showTotal / MUI / Carbon 同款,opt-in)。 */
|
|
76
|
+
showTotal?: boolean
|
|
77
|
+
/** 完整形態:頁碼右側渲染「N 筆/頁」選單(消費 Select sm;Ant showSizeChanger 同款,opt-in)。 */
|
|
78
|
+
pageSizeOptions?: number[]
|
|
79
|
+
/** 上一頁按鈕 aria-label(iconOnly 必有名)。 */
|
|
80
|
+
prevAriaLabel?: string
|
|
81
|
+
/** 下一頁按鈕 aria-label。 */
|
|
82
|
+
nextAriaLabel?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Pagination —— 大量資料切頁後的位置導覽(數字頁碼派,2026-07-05 user 拍板)。
|
|
87
|
+
*
|
|
88
|
+
* 本元件是分頁的**完整功能 SSOT**(2026-07-06 user 拍板):頁碼 + 總筆數資訊(showTotal)
|
|
89
|
+
* + 每頁筆數選單(pageSizeOptions)全部 own 在此;完整形態 layout =「資訊左、操作右」
|
|
90
|
+
* (Ant 源碼結構同款:total 文字最左、size changer 最右)。DataTable 等 consumer 轉發
|
|
91
|
+
* config 消費本元件,不自拼分頁列。與虛擬滾動互斥。詳 ./pagination.spec.md。
|
|
92
|
+
*/
|
|
93
|
+
const Pagination = React.forwardRef<HTMLElement, PaginationProps>(
|
|
94
|
+
(
|
|
95
|
+
{
|
|
96
|
+
total,
|
|
97
|
+
pageSize: pageSizeProp,
|
|
98
|
+
defaultPageSize = 20,
|
|
99
|
+
page: pageProp,
|
|
100
|
+
defaultPage = 1,
|
|
101
|
+
onPageChange,
|
|
102
|
+
onPageSizeChange,
|
|
103
|
+
showTotal = false,
|
|
104
|
+
pageSizeOptions,
|
|
105
|
+
prevAriaLabel = '上一頁', // i18n-allow: DS default; consumer override via prop(precedent calendar.tsx)
|
|
106
|
+
nextAriaLabel = '下一頁', // i18n-allow: DS default; consumer override via prop
|
|
107
|
+
className,
|
|
108
|
+
...props
|
|
109
|
+
},
|
|
110
|
+
ref,
|
|
111
|
+
) => {
|
|
112
|
+
// Hooks 必在 early return 之前(React #310 canonical,select.tsx:503 同慣例)
|
|
113
|
+
const [pageSize, setPageSize] = useControllable<number>({
|
|
114
|
+
value: pageSizeProp,
|
|
115
|
+
defaultValue: defaultPageSize,
|
|
116
|
+
onChange: onPageSizeChange,
|
|
117
|
+
})
|
|
118
|
+
const [page, setPage] = useControllable<number>({
|
|
119
|
+
value: pageProp,
|
|
120
|
+
defaultValue: defaultPage,
|
|
121
|
+
onChange: onPageChange,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// 無資料 → 不渲染(空狀態由 Empty / 列表層表達,詳 spec 邊界案例)
|
|
125
|
+
if (total <= 0) return null
|
|
126
|
+
|
|
127
|
+
// pageSize <= 0 防禦 clamp(consumer contract violation)——防 totalPages 變 Infinity/NaN
|
|
128
|
+
// 渲染垃圾格位(spec 邊界案例表有對應 row)
|
|
129
|
+
const safePageSize = Math.max(1, Math.floor(pageSize))
|
|
130
|
+
const totalPages = Math.max(1, Math.ceil(total / safePageSize))
|
|
131
|
+
const current = Math.min(Math.max(page, 1), totalPages)
|
|
132
|
+
const slots = getPaginationRange(current, totalPages)
|
|
133
|
+
const hasSizeChanger = !!pageSizeOptions && pageSizeOptions.length > 0
|
|
134
|
+
const hasExtras = showTotal || hasSizeChanger
|
|
135
|
+
const rangeStart = (current - 1) * safePageSize + 1
|
|
136
|
+
const rangeEnd = Math.min(current * safePageSize, total)
|
|
137
|
+
// 當前 pageSize 不在 options 內時補進清單頭(否則 Select trigger 顯示裸值失去「N 筆/頁」文案)
|
|
138
|
+
const sizeOptions = hasSizeChanger
|
|
139
|
+
? (pageSizeOptions!.includes(safePageSize) ? pageSizeOptions! : [safePageSize, ...pageSizeOptions!])
|
|
140
|
+
: []
|
|
141
|
+
|
|
142
|
+
const pageList = (
|
|
143
|
+
<ul className="flex items-center gap-1">
|
|
144
|
+
<li>
|
|
145
|
+
<Button
|
|
146
|
+
variant="text"
|
|
147
|
+
size="sm"
|
|
148
|
+
iconOnly
|
|
149
|
+
startIcon={ChevronLeft}
|
|
150
|
+
aria-label={prevAriaLabel}
|
|
151
|
+
disabled={current <= 1}
|
|
152
|
+
onClick={() => setPage(current - 1)}
|
|
153
|
+
/>
|
|
154
|
+
</li>
|
|
155
|
+
{slots.map((slot) =>
|
|
156
|
+
typeof slot === 'number' ? (
|
|
157
|
+
<li key={slot}>
|
|
158
|
+
{/* 當前頁 = <Button variant="secondary"> 一比一零客製(2026-07-06 user 抓出可純組裝):
|
|
159
|
+
secondary 的 rest(primary 描邊+染字+不染底)/ hover(升 hover 階)/ active(深一階)
|
|
160
|
+
與 semantic.css「選中」canonical + 拍板 #8 完全同拼寫,且無 neutral 灰底 hover —
|
|
161
|
+
消費既有 variant 取代手寫平行拼寫(M17/M23 SSOT)。選中「語意」由 aria-current="page"
|
|
162
|
+
承載,Button 僅為視覺 host;未選 = text variant。className 只剩等寬幾何
|
|
163
|
+
(蓋掉 size sm 的 min-w-14/px-3,方形 item 對齊 Ant;兩 variant 同 border 寬零位移)。 */}
|
|
164
|
+
<Button
|
|
165
|
+
variant={slot === current ? 'secondary' : 'text'}
|
|
166
|
+
size="sm"
|
|
167
|
+
aria-current={slot === current ? 'page' : undefined}
|
|
168
|
+
className="min-w-[var(--field-height-sm)] px-1 justify-center tabular-nums"
|
|
169
|
+
onClick={() => setPage(slot)}
|
|
170
|
+
>
|
|
171
|
+
{slot}
|
|
172
|
+
</Button>
|
|
173
|
+
</li>
|
|
174
|
+
) : (
|
|
175
|
+
// Ellipsis 純指示不可點(Ant / MUI / shadcn 同);寬度對齊數字鈕保持節奏
|
|
176
|
+
<li
|
|
177
|
+
key={slot}
|
|
178
|
+
aria-hidden
|
|
179
|
+
className="flex min-w-[var(--field-height-sm)] items-center justify-center"
|
|
180
|
+
>
|
|
181
|
+
<MoreHorizontal size={16} className="text-fg-muted" />
|
|
182
|
+
</li>
|
|
183
|
+
),
|
|
184
|
+
)}
|
|
185
|
+
<li>
|
|
186
|
+
<Button
|
|
187
|
+
variant="text"
|
|
188
|
+
size="sm"
|
|
189
|
+
iconOnly
|
|
190
|
+
startIcon={ChevronRight}
|
|
191
|
+
aria-label={nextAriaLabel}
|
|
192
|
+
disabled={current >= totalPages}
|
|
193
|
+
onClick={() => setPage(current + 1)}
|
|
194
|
+
/>
|
|
195
|
+
</li>
|
|
196
|
+
</ul>
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<nav
|
|
201
|
+
ref={ref}
|
|
202
|
+
aria-label="Pagination"
|
|
203
|
+
className={cn(
|
|
204
|
+
'flex items-center',
|
|
205
|
+
// 完整形態 =「資訊左、操作右」(Ant 源碼結構:total 文字最左 li、size changer 最右 li)
|
|
206
|
+
hasExtras && 'w-full justify-between gap-[var(--layout-space-tight)]',
|
|
207
|
+
className,
|
|
208
|
+
)}
|
|
209
|
+
{...props}
|
|
210
|
+
>
|
|
211
|
+
{hasExtras && (
|
|
212
|
+
// i18n-allow: DS default(range 格式 = Ant/MUI/Carbon 共識);showTotal=false 時渲染
|
|
213
|
+
// 空 span 佔 justify-between 左位,不含 stray 文字節點
|
|
214
|
+
<span className="text-body text-fg-secondary">
|
|
215
|
+
{showTotal ? `第 ${rangeStart}–${rangeEnd} 筆,共 ${total} 筆` : null}
|
|
216
|
+
</span>
|
|
217
|
+
)}
|
|
218
|
+
<div className="flex items-center gap-[var(--layout-space-tight)]">
|
|
219
|
+
{pageList}
|
|
220
|
+
{hasSizeChanger && (
|
|
221
|
+
<Select
|
|
222
|
+
size="sm"
|
|
223
|
+
aria-label="每頁筆數" // i18n-allow: DS default
|
|
224
|
+
className="w-28"
|
|
225
|
+
value={String(safePageSize)}
|
|
226
|
+
onChange={(v) => {
|
|
227
|
+
const next = Number(v)
|
|
228
|
+
// 同值重選 / 非法值不動作——防「重選 20 筆/頁」誤把頁碼重置回第 1 頁
|
|
229
|
+
if (!Number.isFinite(next) || next <= 0 || next === safePageSize) return
|
|
230
|
+
setPageSize(next)
|
|
231
|
+
// 換每頁筆數回第 1 頁——MUI TablePagination / TanStack autoResetPageIndex 派;
|
|
232
|
+
// Ant 為 preserve-position(clamp)派,本 DS 採 reset-to-1(spec「完整形態」段有註記)
|
|
233
|
+
setPage(1)
|
|
234
|
+
}}
|
|
235
|
+
options={sizeOptions.map((n) => ({ value: String(n), label: `${n} 筆/頁` }))} // i18n-allow: DS default(Ant「20 / page」內嵌同款)
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
</nav>
|
|
240
|
+
)
|
|
241
|
+
},
|
|
242
|
+
)
|
|
243
|
+
Pagination.displayName = 'Pagination'
|
|
244
|
+
|
|
245
|
+
export const paginationMeta = {
|
|
246
|
+
component: 'Pagination',
|
|
247
|
+
family: null, // non-family composite(nav 橫排 control row;按鈕消費 Family 3 Button Pill)
|
|
248
|
+
variants: {},
|
|
249
|
+
// 單一尺寸(按鈕固定 Button sm;無 size 軸)—— 頁碼列是 chrome 級導覽不隨 Field 密度縮放,
|
|
250
|
+
// spec「SizeMatrix N/A rationale」段有完整理由
|
|
251
|
+
sizes: {},
|
|
252
|
+
// 'selected' = 當前頁(語意由 aria-current="page" 承載;2026-07-07 meta 詞彙統一:持續選中一律 'selected')
|
|
253
|
+
states: ['default', 'hover', 'active', 'selected', 'focus-visible', 'disabled'],
|
|
254
|
+
tokens: {
|
|
255
|
+
// bg:未選/上下頁(Button text)hover 灰底 + active 深一階;當前頁(secondary)bg-surface
|
|
256
|
+
bg: ['bg-surface', 'bg-neutral-hover', 'bg-neutral-active'],
|
|
257
|
+
fg: ['text-foreground', 'text-primary', 'text-primary-hover', 'text-primary-active', 'text-fg-muted', 'text-fg-secondary', 'text-fg-disabled'],
|
|
258
|
+
border: ['border-primary', 'border-primary-hover', 'border-primary-active'],
|
|
259
|
+
ring: ['ring-ring'],
|
|
260
|
+
},
|
|
261
|
+
} as const
|
|
262
|
+
|
|
263
|
+
export { Pagination }
|
|
@@ -252,7 +252,7 @@ export const ColorMatrix: Story = {
|
|
|
252
252
|
<tr>
|
|
253
253
|
<Td mono>disabled</Td>
|
|
254
254
|
<Td><TokenCell token="--bg-disabled" display="bg-disabled" /></Td>
|
|
255
|
-
<Td
|
|
255
|
+
<Td>—(border transparent)</Td>
|
|
256
256
|
<Td><TokenCell token="--fg-disabled" display="fg-disabled" /></Td>
|
|
257
257
|
</tr>
|
|
258
258
|
<tr>
|
|
@@ -88,7 +88,7 @@ PeoplePicker 永遠支援搜尋(內部使用 `Command` / cmdk)——因為
|
|
|
88
88
|
| 資料類型 | **人員**(含 Avatar) | 任意(純文字)| 任意(純文字)|
|
|
89
89
|
| 單選 / 多選 | 兩者皆支援(value 類型決定)| 單選 | 多選 |
|
|
90
90
|
| 視覺 prefix | Avatar(必備) | 可選 startIcon | 無 |
|
|
91
|
-
| 底層實作 | Popover + Command + SelectMenu | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu,`select.tsx:
|
|
91
|
+
| 底層實作 | Popover + Command + SelectMenu | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu,`select.tsx:582`)/ 觸控 native `<select>`(`select.tsx` NativeSelect) | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu + Tag overlay,`combobox.tsx:728`)/ 觸控 native `<select>`(`combobox.tsx` NativeCombobox) |
|
|
92
92
|
| 搜尋 | 永遠啟用(人名本質);single = inline-trigger(wrap `<Select searchable>`),multi 預設 panel-top(`searchIn='menu'`,`searchIn='trigger'` opt-in)— 對齊本 spec L91-92 SSOT 表 | 可選 `searchable`(單選短列表 → inline-trigger only)| 可選 `searchable` + `searchIn='menu' \| 'trigger'`(多選 → 兩種模式)|
|
|
93
93
|
|
|
94
94
|
**搜尋型態 SSOT canonical**(A1-A5 spec,2026-05-11;2026-05-12 補 PeoplePicker multi `searchIn` opt-in):
|
|
@@ -147,7 +147,7 @@ PeoplePicker 永遠支援搜尋(內部使用 `Command` / cmdk)——因為
|
|
|
147
147
|
|
|
148
148
|
| 共享項 | SSOT owner |
|
|
149
149
|
|---|---|
|
|
150
|
-
| Avatar 左 inset 距 trigger.left = 12px 固定(不隨 size/density 漂移)| 本 spec
|
|
150
|
+
| Avatar 左 inset 距 trigger.left = 12px 固定(不隨 size/density 漂移)| 本 spec L175「Avatar 左 inset SSOT」段 + `people-picker.tsx:371` `!px-[var(--field-px)]` inject(form context)/ `!px-[var(--table-cell-px)]`(naked variant table cell)|
|
|
151
151
|
| Avatar+人名視覺 | `person-display.tsx:137-152` `PersonDisplay`(共享 renderer)|
|
|
152
152
|
| 人名按空間 ellipsis | `person-display.tsx:150` `truncate flex-1 min-w-0` |
|
|
153
153
|
| Placeholder ellipsis(inline-search active 時)| `field-wrapper.tsx:244` `bareInputStyles` 含 `truncate` |
|
|
@@ -237,7 +237,7 @@ PeoplePicker 是 **composite Field Control**(Field shell + Avatar + SelectMenu),
|
|
|
237
237
|
|
|
238
238
|
## shadcn passthrough 例外說明
|
|
239
239
|
|
|
240
|
-
PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox>`(multi)),其主 API 為 declarative(value / onChange / size / mode / options...)。**有套 `forwardRef` + HTML root props spread**(codex P2 fix 2026-05-07 v15.10,`people-picker.tsx:
|
|
240
|
+
PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox>`(multi)),其主 API 為 declarative(value / onChange / size / mode / options...)。**有套 `forwardRef` + HTML root props spread**(codex P2 fix 2026-05-07 v15.10,`people-picker.tsx:111` / L53 / L131 `...rest`):
|
|
241
241
|
|
|
242
242
|
- **forwardRef + 單一 root**:`PeoplePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>`,ref 與 `...rest`(`id` / `data-testid` / `onBlur` / `onFocus` / `aria-*` 等 HTML root props)forward 到內部 trigger 容器(readonly/disabled root div、或 wrapped Select / Combobox 的 root),對齊 DS 既有 Combobox / Select 慣例
|
|
243
243
|
- **`onChange` 走 Omit**:本元件 onChange 用 `(value: PersonValue[]) => void` custom signature,跟 HTMLAttributes onChange 衝突,故 Omit
|
|
@@ -251,7 +251,7 @@ PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox
|
|
|
251
251
|
|
|
252
252
|
- **Disabled**:`disabled` → `resolvedMode='disabled'`(`useResolvedFieldMode`),走獨立 static-display 分支(`people-picker.tsx`「readonly / disabled」段)——渲染靜態 `<div>` 包 `MultiPersonDisplay` / `PersonDisplay` + `ItemSuffix` ChevronDown 類型身份 indicator(2026-06-26:disabled 保留 chevron、readonly 不顯示;naked variant 依 `showDisplayEndIcon`),**不** wrap Select / Combobox、無 dismiss X、無 inline-search input。token 走 M24 state precedence(`text-fg-disabled`,含 chevron)。
|
|
253
253
|
- **Loading(async people fetch)**:目前 PeoplePicker **未暴露 `loading` prop**(`PeoplePickerProps` 無此欄位,內部也未 forward 給 wrapped Select / Combobox)。consumer 若需 loading 態,在 fetch 完成前自行控制 `people=[]`(走 emptyText 空態)。底層 Select / Combobox / SelectMenu 各有自己的 loading 機制,但尚未經 PeoplePicker API 層轉發。
|
|
254
|
-
- **Empty(no search results)**:`emptyText` 預設「沒有符合的人員」(本 spec
|
|
254
|
+
- **Empty(no search results)**:`emptyText` 預設「沒有符合的人員」(本 spec L80 已 codify);無 creatable mode(人員不可建立)。
|
|
255
255
|
- **Empty(no value selected)**:single mode → trigger 顯 placeholder「請選擇人員」;multi mode `value=[]` → trigger 同 placeholder(無 avatar stack 渲);詳「Trigger display SSOT canonical table」B-D 段。
|
|
256
256
|
- **`people` 清單變動(async fetch 後更新)**:已選 value 顯示不依賴 `people` 查找 — display 路徑直接渲染 value 自帶資料;edit 路徑以人名回查 `people`,查不到降級為純名字 string(initials fallback),不報錯(`people-picker-helpers.ts:74-75`)。選單選項即時跟隨 props 重渲。
|
|
257
257
|
- **RTL**:未實作方向鏡像(avatar stack overlap `-ml-0.5` / 12px inset 皆以 LTR physical 方向設計);RTL 屬 DS-wide 決策,未定(與 Chip / Breadcrumb 同口徑)。
|