@comitor/ui 2.1.0 → 3.0.0
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/README.md +59 -24
- package/dist/chart/chart.d.ts +6 -1
- package/dist/chart/chart.js +3 -1
- package/dist/chart/chart.js.map +1 -1
- package/dist/chart/comitor-chart.d.ts +4 -1
- package/dist/chart/comitor-chart.js +3 -1
- package/dist/chart/comitor-chart.js.map +1 -1
- package/dist/components/composite/avatar-group.d.ts +9 -6
- package/dist/components/composite/avatar-group.js +6 -2
- package/dist/components/composite/avatar-group.js.map +1 -1
- package/dist/components/composite/combobox.d.ts +11 -11
- package/dist/components/composite/combobox.js +25 -11
- package/dist/components/composite/combobox.js.map +1 -1
- package/dist/components/composite/command-palette-constants.d.ts +15 -5
- package/dist/components/composite/command-palette-constants.js +3 -2
- package/dist/components/composite/command-palette-constants.js.map +1 -1
- package/dist/components/composite/command-palette.d.ts +2 -2
- package/dist/components/composite/command-palette.js +8 -5
- package/dist/components/composite/command-palette.js.map +1 -1
- package/dist/components/composite/confirm-dialog.d.ts +8 -8
- package/dist/components/composite/confirm-dialog.js +8 -3
- package/dist/components/composite/confirm-dialog.js.map +1 -1
- package/dist/components/composite/copy-button.d.ts +7 -3
- package/dist/components/composite/copy-button.js +7 -3
- package/dist/components/composite/copy-button.js.map +1 -1
- package/dist/components/composite/data-table-constants.d.ts +7 -3
- package/dist/components/composite/data-table-constants.js +2 -9
- package/dist/components/composite/data-table-constants.js.map +1 -1
- package/dist/components/composite/data-table.js +3 -3
- package/dist/components/composite/data-table.js.map +1 -1
- package/dist/components/composite/date-picker-constants.d.ts +32 -9
- package/dist/components/composite/date-picker-constants.js +16 -11
- package/dist/components/composite/date-picker-constants.js.map +1 -1
- package/dist/components/composite/date-picker.d.ts +19 -10
- package/dist/components/composite/date-picker.js +33 -9
- package/dist/components/composite/date-picker.js.map +1 -1
- package/dist/components/composite/filter-chips.d.ts +4 -3
- package/dist/components/composite/filter-chips.js +6 -2
- package/dist/components/composite/filter-chips.js.map +1 -1
- package/dist/components/composite/inline-edit.d.ts +14 -3
- package/dist/components/composite/inline-edit.js +8 -4
- package/dist/components/composite/inline-edit.js.map +1 -1
- package/dist/components/composite/relative-time.d.ts +4 -4
- package/dist/components/composite/relative-time.js +4 -2
- package/dist/components/composite/relative-time.js.map +1 -1
- package/dist/components/composite/route-tabs-nav.d.ts +9 -0
- package/dist/components/composite/route-tabs-nav.js +11 -0
- package/dist/components/composite/route-tabs-nav.js.map +1 -0
- package/dist/components/composite/route-tabs.d.ts +4 -1
- package/dist/components/composite/route-tabs.js +3 -2
- package/dist/components/composite/route-tabs.js.map +1 -1
- package/dist/components/composite/search-filter-bar-constants.d.ts +18 -8
- package/dist/components/composite/search-filter-bar-constants.js +2 -14
- package/dist/components/composite/search-filter-bar-constants.js.map +1 -1
- package/dist/components/composite/search-filter-bar.d.ts +5 -2
- package/dist/components/composite/search-filter-bar.js +2 -2
- package/dist/components/composite/search-filter-bar.js.map +1 -1
- package/dist/components/composite/stat-card.d.ts +1 -1
- package/dist/components/composite/table-pagination-constants.d.ts +16 -11
- package/dist/components/composite/table-pagination-constants.js +2 -12
- package/dist/components/composite/table-pagination-constants.js.map +1 -1
- package/dist/components/composite/table-pagination.d.ts +6 -3
- package/dist/components/composite/table-pagination.js +3 -5
- package/dist/components/composite/table-pagination.js.map +1 -1
- package/dist/components/composite/toast.d.ts +4 -2
- package/dist/components/composite/toast.js +9 -1
- package/dist/components/composite/toast.js.map +1 -1
- package/dist/components/ui/action-bar-constants.d.ts +8 -4
- package/dist/components/ui/action-bar-constants.js +2 -5
- package/dist/components/ui/action-bar-constants.js.map +1 -1
- package/dist/components/ui/action-bar.d.ts +5 -4
- package/dist/components/ui/action-bar.js +2 -2
- package/dist/components/ui/action-bar.js.map +1 -1
- package/dist/components/ui/alert.d.ts +1 -1
- package/dist/components/ui/badge-overflow.d.ts +2 -2
- package/dist/components/ui/badge-overflow.js +4 -2
- package/dist/components/ui/badge-overflow.js.map +1 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/banner.d.ts +6 -5
- package/dist/components/ui/banner.js +3 -1
- package/dist/components/ui/banner.js.map +1 -1
- package/dist/components/ui/breadcrumb.d.ts +11 -4
- package/dist/components/ui/breadcrumb.js +7 -2
- package/dist/components/ui/breadcrumb.js.map +1 -1
- package/dist/components/ui/button.d.ts +2 -2
- package/dist/components/ui/calendar.d.ts +12 -5
- package/dist/components/ui/calendar.js +38 -3
- package/dist/components/ui/calendar.js.map +1 -1
- package/dist/components/ui/carousel.d.ts +4 -3
- package/dist/components/ui/carousel.js +11 -4
- package/dist/components/ui/carousel.js.map +1 -1
- package/dist/components/ui/circular-progress.js.map +1 -1
- package/dist/components/ui/command.d.ts +5 -4
- package/dist/components/ui/command.js +6 -2
- package/dist/components/ui/command.js.map +1 -1
- package/dist/components/ui/cropper-constants.d.ts +8 -3
- package/dist/components/ui/cropper-constants.js +2 -6
- package/dist/components/ui/cropper-constants.js.map +1 -1
- package/dist/components/ui/cropper.d.ts +2 -1
- package/dist/components/ui/cropper.js +2 -2
- package/dist/components/ui/cropper.js.map +1 -1
- package/dist/components/ui/dialog.d.ts +2 -2
- package/dist/components/ui/dialog.js +4 -1
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/item.d.ts +2 -2
- package/dist/components/ui/kanban-constants.d.ts +7 -1
- package/dist/components/ui/kanban-constants.js +2 -17
- package/dist/components/ui/kanban-constants.js.map +1 -1
- package/dist/components/ui/kanban.d.ts +2 -2
- package/dist/components/ui/kanban.js +2 -2
- package/dist/components/ui/kanban.js.map +1 -1
- package/dist/components/ui/key-value-constants.d.ts +7 -3
- package/dist/components/ui/key-value-constants.js +2 -7
- package/dist/components/ui/key-value-constants.js.map +1 -1
- package/dist/components/ui/key-value.d.ts +5 -5
- package/dist/components/ui/key-value.js +2 -2
- package/dist/components/ui/key-value.js.map +1 -1
- package/dist/components/ui/mask-input-constants.d.ts +2 -3
- package/dist/components/ui/mask-input-constants.js +5 -6
- package/dist/components/ui/mask-input-constants.js.map +1 -1
- package/dist/components/ui/mask-input.d.ts +7 -4
- package/dist/components/ui/mask-input.js +4 -2
- package/dist/components/ui/mask-input.js.map +1 -1
- package/dist/components/ui/mention.d.ts +3 -2
- package/dist/components/ui/mention.js +4 -6
- package/dist/components/ui/mention.js.map +1 -1
- package/dist/components/ui/pagination.d.ts +13 -9
- package/dist/components/ui/pagination.js +15 -6
- package/dist/components/ui/pagination.js.map +1 -1
- package/dist/components/ui/phone-input-constants.d.ts +17 -6
- package/dist/components/ui/phone-input-constants.js +2 -5
- package/dist/components/ui/phone-input-constants.js.map +1 -1
- package/dist/components/ui/phone-input.d.ts +18 -11
- package/dist/components/ui/phone-input.js +9 -10
- package/dist/components/ui/phone-input.js.map +1 -1
- package/dist/components/ui/progress.d.ts +2 -2
- package/dist/components/ui/rating.d.ts +2 -1
- package/dist/components/ui/rating.js +4 -2
- package/dist/components/ui/rating.js.map +1 -1
- package/dist/components/ui/scroll-spy.js +3 -1
- package/dist/components/ui/scroll-spy.js.map +1 -1
- package/dist/components/ui/sheet.d.ts +2 -2
- package/dist/components/ui/sheet.js +10 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/spinner.js +4 -1
- package/dist/components/ui/spinner.js.map +1 -1
- package/dist/components/ui/stepper-constants.d.ts +7 -2
- package/dist/components/ui/stepper-constants.js +2 -5
- package/dist/components/ui/stepper-constants.js.map +1 -1
- package/dist/components/ui/stepper.d.ts +2 -1
- package/dist/components/ui/stepper.js +2 -2
- package/dist/components/ui/stepper.js.map +1 -1
- package/dist/components/ui/tags-input.d.ts +3 -2
- package/dist/components/ui/tags-input.js +4 -2
- package/dist/components/ui/tags-input.js.map +1 -1
- package/dist/components/ui/time-picker-constants.d.ts +12 -9
- package/dist/components/ui/time-picker-constants.js +2 -10
- package/dist/components/ui/time-picker-constants.js.map +1 -1
- package/dist/components/ui/time-picker.d.ts +11 -6
- package/dist/components/ui/time-picker.js +5 -7
- package/dist/components/ui/time-picker.js.map +1 -1
- package/dist/components/ui/toggle.d.ts +1 -1
- package/dist/components/ui/tour-constants.d.ts +7 -2
- package/dist/components/ui/tour-constants.js +2 -8
- package/dist/components/ui/tour-constants.js.map +1 -1
- package/dist/components/ui/tour.d.ts +3 -2
- package/dist/components/ui/tour.js +2 -2
- package/dist/components/ui/tour.js.map +1 -1
- package/dist/data-grid/data-grid-cell-variants.js +4 -4
- package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
- package/dist/data-grid/data-grid-column-header.js +1 -1
- package/dist/data-grid/data-grid-column-header.js.map +1 -1
- package/dist/data-grid/data-grid-filter-menu.d.ts +5 -4
- package/dist/data-grid/data-grid-filter-menu.js +13 -8
- package/dist/data-grid/data-grid-filter-menu.js.map +1 -1
- package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +4 -4
- package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -1
- package/dist/data-grid/data-grid-locale.d.ts +58 -6
- package/dist/data-grid/data-grid-locale.js +41 -9
- package/dist/data-grid/data-grid-locale.js.map +1 -1
- package/dist/data-grid/data-grid-row-height-menu.d.ts +4 -3
- package/dist/data-grid/data-grid-row-height-menu.js.map +1 -1
- package/dist/data-grid/data-grid-sort-menu.d.ts +4 -4
- package/dist/data-grid/data-grid-sort-menu.js +1 -1
- package/dist/data-grid/data-grid-sort-menu.js.map +1 -1
- package/dist/data-grid/data-grid-view-menu.d.ts +4 -4
- package/dist/data-grid/data-grid-view-menu.js.map +1 -1
- package/dist/data-grid/data-grid.d.ts +4 -3
- package/dist/data-grid/data-grid.js +2 -3
- package/dist/data-grid/data-grid.js.map +1 -1
- package/dist/data-grid/index.d.ts +1 -0
- package/dist/data-grid/index.js.map +1 -1
- package/dist/data-grid/messages-en.d.ts +23 -4
- package/dist/data-grid/messages-en.js +20 -0
- package/dist/data-grid/messages-en.js.map +1 -1
- package/dist/data-grid/messages-vi.d.ts +3 -1
- package/dist/data-grid/messages-vi.js +14 -0
- package/dist/data-grid/messages-vi.js.map +1 -1
- package/dist/data-grid/messages.d.ts +29 -3
- package/dist/data-grid/messages.js.map +1 -1
- package/dist/data-grid/sortable.js +16 -19
- package/dist/data-grid/sortable.js.map +1 -1
- package/dist/data-grid/types.d.ts +1 -1
- package/dist/data-grid/use-data-grid-undo-redo.d.ts +5 -4
- package/dist/data-grid/use-data-grid-undo-redo.js +2 -2
- package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
- package/dist/data-grid/use-data-grid.d.ts +21 -6
- package/dist/data-grid/use-data-grid.js +8 -6
- package/dist/data-grid/use-data-grid.js.map +1 -1
- package/dist/data-grid/utils.d.ts +24 -4
- package/dist/data-grid/utils.js +15 -5
- package/dist/data-grid/utils.js.map +1 -1
- package/dist/i18n/locale-provider.d.ts +109 -0
- package/dist/i18n/locale-provider.js +47 -0
- package/dist/i18n/locale-provider.js.map +1 -0
- package/dist/i18n/locale.d.ts +27 -0
- package/dist/i18n/locale.js +10 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/messages.d.ts +116 -0
- package/dist/i18n/messages.js +149 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/i18n/namespaces/action-bar.d.ts +7 -0
- package/dist/i18n/namespaces/action-bar.js +15 -0
- package/dist/i18n/namespaces/action-bar.js.map +1 -0
- package/dist/i18n/namespaces/avatar-group.d.ts +14 -0
- package/dist/i18n/namespaces/avatar-group.js +13 -0
- package/dist/i18n/namespaces/avatar-group.js.map +1 -0
- package/dist/i18n/namespaces/badge-overflow.d.ts +12 -0
- package/dist/i18n/namespaces/badge-overflow.js +11 -0
- package/dist/i18n/namespaces/badge-overflow.js.map +1 -0
- package/dist/i18n/namespaces/banner.d.ts +9 -0
- package/dist/i18n/namespaces/banner.js +7 -0
- package/dist/i18n/namespaces/banner.js.map +1 -0
- package/dist/i18n/namespaces/breadcrumb.d.ts +11 -0
- package/dist/i18n/namespaces/breadcrumb.js +13 -0
- package/dist/i18n/namespaces/breadcrumb.js.map +1 -0
- package/dist/i18n/namespaces/calendar.d.ts +40 -0
- package/dist/i18n/namespaces/calendar.js +25 -0
- package/dist/i18n/namespaces/calendar.js.map +1 -0
- package/dist/i18n/namespaces/carousel.d.ts +15 -0
- package/dist/i18n/namespaces/carousel.js +17 -0
- package/dist/i18n/namespaces/carousel.js.map +1 -0
- package/dist/i18n/namespaces/combobox.d.ts +21 -0
- package/dist/i18n/namespaces/combobox.js +23 -0
- package/dist/i18n/namespaces/combobox.js.map +1 -0
- package/dist/i18n/namespaces/command-palette.d.ts +20 -0
- package/dist/i18n/namespaces/command-palette.js +19 -0
- package/dist/i18n/namespaces/command-palette.js.map +1 -0
- package/dist/i18n/namespaces/command.d.ts +11 -0
- package/dist/i18n/namespaces/command.js +13 -0
- package/dist/i18n/namespaces/command.js.map +1 -0
- package/dist/i18n/namespaces/confirm-dialog.d.ts +13 -0
- package/dist/i18n/namespaces/confirm-dialog.js +15 -0
- package/dist/i18n/namespaces/confirm-dialog.js.map +1 -0
- package/dist/i18n/namespaces/contrast.d.ts +7 -0
- package/dist/i18n/namespaces/contrast.js +13 -0
- package/dist/i18n/namespaces/contrast.js.map +1 -0
- package/dist/i18n/namespaces/copy-button.d.ts +11 -0
- package/dist/i18n/namespaces/copy-button.js +13 -0
- package/dist/i18n/namespaces/copy-button.js.map +1 -0
- package/dist/i18n/namespaces/cropper.d.ts +7 -0
- package/dist/i18n/namespaces/cropper.js +17 -0
- package/dist/i18n/namespaces/cropper.js.map +1 -0
- package/dist/i18n/namespaces/data-table.d.ts +7 -0
- package/dist/i18n/namespaces/data-table.js +23 -0
- package/dist/i18n/namespaces/data-table.js.map +1 -0
- package/dist/i18n/namespaces/date-picker.d.ts +42 -0
- package/dist/i18n/namespaces/date-picker.js +35 -0
- package/dist/i18n/namespaces/date-picker.js.map +1 -0
- package/dist/i18n/namespaces/dialog.d.ts +9 -0
- package/dist/i18n/namespaces/dialog.js +7 -0
- package/dist/i18n/namespaces/dialog.js.map +1 -0
- package/dist/i18n/namespaces/display-axis.d.ts +25 -0
- package/dist/i18n/namespaces/display-axis.js +21 -0
- package/dist/i18n/namespaces/display-axis.js.map +1 -0
- package/dist/i18n/namespaces/file-upload.d.ts +11 -0
- package/dist/i18n/namespaces/file-upload.js +19 -0
- package/dist/i18n/namespaces/file-upload.js.map +1 -0
- package/dist/i18n/namespaces/filter-chips.d.ts +11 -0
- package/dist/i18n/namespaces/filter-chips.js +13 -0
- package/dist/i18n/namespaces/filter-chips.js.map +1 -0
- package/dist/i18n/namespaces/image-upload-field.d.ts +12 -0
- package/dist/i18n/namespaces/image-upload-field.js +23 -0
- package/dist/i18n/namespaces/image-upload-field.js.map +1 -0
- package/dist/i18n/namespaces/inline-edit.d.ts +15 -0
- package/dist/i18n/namespaces/inline-edit.js +17 -0
- package/dist/i18n/namespaces/inline-edit.js.map +1 -0
- package/dist/i18n/namespaces/kanban.d.ts +11 -0
- package/dist/i18n/namespaces/kanban.js +39 -0
- package/dist/i18n/namespaces/kanban.js.map +1 -0
- package/dist/i18n/namespaces/key-value.d.ts +10 -0
- package/dist/i18n/namespaces/key-value.js +19 -0
- package/dist/i18n/namespaces/key-value.js.map +1 -0
- package/dist/i18n/namespaces/mention.d.ts +9 -0
- package/dist/i18n/namespaces/mention.js +7 -0
- package/dist/i18n/namespaces/mention.js.map +1 -0
- package/dist/i18n/namespaces/pagination.d.ts +19 -0
- package/dist/i18n/namespaces/pagination.js +21 -0
- package/dist/i18n/namespaces/pagination.js.map +1 -0
- package/dist/i18n/namespaces/phone-input.d.ts +10 -0
- package/dist/i18n/namespaces/phone-input.js +17 -0
- package/dist/i18n/namespaces/phone-input.js.map +1 -0
- package/dist/i18n/namespaces/rating.d.ts +12 -0
- package/dist/i18n/namespaces/rating.js +11 -0
- package/dist/i18n/namespaces/rating.js.map +1 -0
- package/dist/i18n/namespaces/route-tabs.d.ts +9 -0
- package/dist/i18n/namespaces/route-tabs.js +7 -0
- package/dist/i18n/namespaces/route-tabs.js.map +1 -0
- package/dist/i18n/namespaces/scroll-spy.d.ts +9 -0
- package/dist/i18n/namespaces/scroll-spy.js +7 -0
- package/dist/i18n/namespaces/scroll-spy.js.map +1 -0
- package/dist/i18n/namespaces/search-filter-bar.d.ts +15 -0
- package/dist/i18n/namespaces/search-filter-bar.js +32 -0
- package/dist/i18n/namespaces/search-filter-bar.js.map +1 -0
- package/dist/i18n/namespaces/sheet.d.ts +9 -0
- package/dist/i18n/namespaces/sheet.js +7 -0
- package/dist/i18n/namespaces/sheet.js.map +1 -0
- package/dist/i18n/namespaces/shell.d.ts +17 -0
- package/dist/i18n/namespaces/shell.js +75 -0
- package/dist/i18n/namespaces/shell.js.map +1 -0
- package/dist/i18n/namespaces/spinner.d.ts +9 -0
- package/dist/i18n/namespaces/spinner.js +7 -0
- package/dist/i18n/namespaces/spinner.js.map +1 -0
- package/dist/i18n/namespaces/stepper.d.ts +7 -0
- package/dist/i18n/namespaces/stepper.js +15 -0
- package/dist/i18n/namespaces/stepper.js.map +1 -0
- package/dist/i18n/namespaces/table-pagination.d.ts +7 -0
- package/dist/i18n/namespaces/table-pagination.js +29 -0
- package/dist/i18n/namespaces/table-pagination.js.map +1 -0
- package/dist/i18n/namespaces/tags-input.d.ts +12 -0
- package/dist/i18n/namespaces/tags-input.js +11 -0
- package/dist/i18n/namespaces/tags-input.js.map +1 -0
- package/dist/i18n/namespaces/time-picker.d.ts +15 -0
- package/dist/i18n/namespaces/time-picker.js +25 -0
- package/dist/i18n/namespaces/time-picker.js.map +1 -0
- package/dist/i18n/namespaces/toast.d.ts +15 -0
- package/dist/i18n/namespaces/toast.js +7 -0
- package/dist/i18n/namespaces/toast.js.map +1 -0
- package/dist/i18n/namespaces/tour.d.ts +7 -0
- package/dist/i18n/namespaces/tour.js +21 -0
- package/dist/i18n/namespaces/tour.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/shell/app-header.js.map +1 -1
- package/dist/shell/app-shell.d.ts +20 -2
- package/dist/shell/app-shell.js +4 -1
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/contrast-constants.d.ts +9 -1
- package/dist/shell/contrast-constants.js +2 -4
- package/dist/shell/contrast-constants.js.map +1 -1
- package/dist/shell/contrast-toggle.d.ts +4 -1
- package/dist/shell/contrast-toggle.js +2 -2
- package/dist/shell/contrast-toggle.js.map +1 -1
- package/dist/shell/display-axis-constants.d.ts +2 -1
- package/dist/shell/display-axis-constants.js.map +1 -1
- package/dist/shell/display-axis-controls.d.ts +15 -6
- package/dist/shell/display-axis-controls.js +11 -9
- package/dist/shell/display-axis-controls.js.map +1 -1
- package/dist/shell/mobile-menu.js.map +1 -1
- package/dist/shell/notifications-button.d.ts +1 -1
- package/dist/shell/notifications-button.js +3 -2
- package/dist/shell/notifications-button.js.map +1 -1
- package/dist/shell/shell-context.d.ts +4 -1
- package/dist/shell/shell-context.js +2 -4
- package/dist/shell/shell-context.js.map +1 -1
- package/dist/shell/sidebar.d.ts +1 -1
- package/dist/shell/sidebar.js.map +1 -1
- package/dist/shell/theme-toggle.d.ts +6 -3
- package/dist/shell/theme-toggle.js +2 -2
- package/dist/shell/theme-toggle.js.map +1 -1
- package/dist/shell/types.d.ts +14 -8
- package/dist/shell/types.js +2 -35
- package/dist/shell/types.js.map +1 -1
- package/dist/shell/user-menu.d.ts +6 -6
- package/dist/shell/user-menu.js.map +1 -1
- package/dist/shell/utils.d.ts +1 -1
- package/dist/shell/utils.js.map +1 -1
- package/dist/shell/workspace-switcher.js +1 -1
- package/dist/shell/workspace-switcher.js.map +1 -1
- package/dist/uploader/file-upload-constants.d.ts +13 -0
- package/dist/uploader/file-upload-constants.js +4 -16
- package/dist/uploader/file-upload-constants.js.map +1 -1
- package/dist/uploader/file-upload.d.ts +4 -1
- package/dist/uploader/file-upload.js +2 -2
- package/dist/uploader/file-upload.js.map +1 -1
- package/dist/uploader/image-upload-field.d.ts +5 -1
- package/dist/uploader/image-upload-field.js +2 -2
- package/dist/uploader/image-upload-field.js.map +1 -1
- package/dist/uploader/use-file-upload.d.ts +4 -0
- package/dist/uploader/use-file-upload.js +6 -2
- package/dist/uploader/use-file-upload.js.map +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/rating.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/rating) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { Star } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { VisuallyHiddenInput } from \"../../lib/visually-hidden-input.js\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\ntype ActivationMode = \"automatic\" | \"manual\";\ntype Size = \"default\" | \"sm\" | \"lg\";\ntype Step = 0.5 | 1;\ntype DataState = \"full\" | \"partial\" | \"empty\";\ntype FocusIntent = \"first\" | \"last\" | \"prev\" | \"next\";\n\ntype RootElement = React.ComponentRef<typeof Rating>;\ntype ItemElement = React.ComponentRef<typeof RatingItem>;\n\nconst ROOT_NAME = \"Rating\";\nconst ITEM_NAME = \"RatingItem\";\n\nconst ENTRY_FOCUS = \"ratingFocusGroup.onEntryFocus\";\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\nfunction getItemId(id: string, value: number) {\n return `${id}-item-${value}`;\n}\n\nfunction getPartialFillGradientId(id: string, step: Step) {\n return `partial-fill-gradient-${id}-${step}`;\n}\n\nconst MAP_KEY_TO_FOCUS_INTENT: Record<string, FocusIntent> = {\n ArrowLeft: \"prev\",\n ArrowUp: \"prev\",\n ArrowRight: \"next\",\n ArrowDown: \"next\",\n Home: \"first\",\n End: \"last\"\n};\n\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\n if (dir !== \"rtl\") return key;\n return key === \"ArrowLeft\" ? \"ArrowRight\" : key === \"ArrowRight\" ? \"ArrowLeft\" : key;\n}\n\nfunction getFocusIntent(event: React.KeyboardEvent<ItemElement>, dir?: Direction, orientation?: Orientation) {\n const key = getDirectionAwareKey(event.key, dir);\n if (orientation === \"horizontal\" && [\"ArrowUp\", \"ArrowDown\"].includes(key)) return undefined;\n if (orientation === \"vertical\" && [\"ArrowLeft\", \"ArrowRight\"].includes(key)) return undefined;\n return MAP_KEY_TO_FOCUS_INTENT[key];\n}\n\nfunction focusFirst(candidates: React.RefObject<ItemElement | null>[], preventScroll = false) {\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\n for (const candidateRef of candidates) {\n const candidate = candidateRef.current;\n if (!candidate) continue;\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\n candidate.focus({ preventScroll });\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\n }\n}\n\ninterface StoreState {\n value: number;\n hoveredValue: number | null;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useRating\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n ref: React.RefObject<ItemElement | null>;\n value: number;\n disabled: boolean;\n}\n\ninterface RatingContextValue {\n rootId: string;\n dir: Direction;\n orientation: Orientation;\n activationMode: ActivationMode;\n size: Size;\n max: number;\n step: Step;\n clearable: boolean;\n disabled: boolean;\n readOnly: boolean;\n itemLabel: (value: number, max: number) => string;\n getAutoIndex: (instanceId: string) => number;\n}\n\nconst RatingContext = React.createContext<RatingContextValue | null>(null);\n\nfunction useRatingContext(consumerName: string) {\n const context = React.useContext(RatingContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface FocusContextValue {\n tabStopId: string | null;\n onItemFocus: (tabStopId: string) => void;\n onItemShiftTab: () => void;\n onFocusableItemAdd: () => void;\n onFocusableItemRemove: () => void;\n onItemRegister: (item: ItemData) => void;\n onItemUnregister: (id: string) => void;\n getItems: () => ItemData[];\n}\n\nconst FocusContext = React.createContext<FocusContextValue | null>(null);\n\nfunction useFocusContext(consumerName: string) {\n const context = React.useContext(FocusContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`FocusProvider\\``);\n }\n return context;\n}\n\ninterface RatingProps extends React.ComponentProps<\"div\"> {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n onHover?: (value: number | null) => void;\n max?: number;\n activationMode?: ActivationMode;\n dir?: Direction;\n orientation?: Orientation;\n size?: Size;\n asChild?: boolean;\n step?: Step;\n clearable?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n name?: string;\n /**\n * Tên của TỪNG ngôi sao cho trình đọc màn hình — mặc định `(value) => value + \" sao\"`.\n *\n * ⚠ Thêm so với thượng nguồn. Ở bản gốc mỗi `RatingItem` là một `role=\"radio\"` chỉ chứa một\n * `<svg>`, tức một nút KHÔNG TÊN: trình đọc màn hình đọc \"nút radio, 3 trên 5\" mà không nói 3\n * CÁI GÌ. Là HÀM chứ không phải chuỗi mẫu vì có số chèn vào giữa câu (README §5).\n *\n * Nhóm vẫn cần tên riêng: truyền `aria-label` (vd \"Mức hài lòng\") cho chính `Rating`.\n */\n itemLabel?: (value: number, max: number) => string;\n}\n\nconst defaultItemLabel = (value: number) => `${value} sao`;\n\nfunction Rating(props: RatingProps) {\n const {\n value: valueProp,\n defaultValue = 0,\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n dir: dirProp,\n orientation = \"horizontal\",\n activationMode = \"automatic\",\n size = \"default\",\n max = 5,\n step = 1,\n clearable = false,\n asChild,\n disabled = false,\n readOnly = false,\n required = false,\n itemLabel = defaultItemLabel,\n className,\n children,\n id,\n name,\n ref,\n ...rootProps\n } = props;\n\n const dir = useDirection(dirProp);\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<StoreState>(() => ({\n value: valueProp ?? defaultValue,\n hoveredValue: null\n }));\n\n const propsRef = useAsRef({\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n step\n });\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === \"value\" && typeof value === \"number\") {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === \"hoveredValue\") {\n stateRef.current.hoveredValue = value as number | null;\n propsRef.current.onHover?.(value as number | null);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n const value = useStore((state) => state.value, store);\n\n const [formTrigger, setFormTrigger] = React.useState<RootElement | null>(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\n\n const [tabStopId, setTabStopId] = React.useState<string | null>(null);\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\n const isClickFocusRef = React.useRef(false);\n const itemsRef = React.useRef<Map<string, ItemData>>(new Map());\n\n const autoIndexMapRef = React.useRef(new Map<string, number>());\n const nextAutoIndexRef = React.useRef(0);\n\n const getAutoIndex = React.useCallback((instanceId: string) => {\n const existingIndex = autoIndexMapRef.current.get(instanceId);\n if (existingIndex !== undefined) {\n return existingIndex;\n }\n\n const newIndex = nextAutoIndexRef.current++;\n autoIndexMapRef.current.set(instanceId, newIndex);\n return newIndex;\n }, []);\n\n const onItemFocus = React.useCallback((tabStopId: string) => {\n setTabStopId(tabStopId);\n }, []);\n\n const onItemShiftTab = React.useCallback(() => {\n setIsTabbingBackOut(true);\n }, []);\n\n const onFocusableItemAdd = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount + 1);\n }, []);\n\n const onFocusableItemRemove = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount - 1);\n }, []);\n\n const onItemRegister = React.useCallback((item: ItemData) => {\n itemsRef.current.set(item.id, item);\n }, []);\n\n const onItemUnregister = React.useCallback((id: string) => {\n itemsRef.current.delete(id);\n }, []);\n\n const getItems = React.useCallback(() => {\n return Array.from(itemsRef.current.values())\n .filter((item) => item.ref.current)\n .sort((a, b) => {\n const elementA = a.ref.current;\n const elementB = b.ref.current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n return -1;\n }\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n return 1;\n }\n return 0;\n });\n }, []);\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n rootProps.onBlur?.(event);\n if (event.defaultPrevented) return;\n\n setIsTabbingBackOut(false);\n },\n [rootProps.onBlur]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n const isKeyboardFocus = !isClickFocusRef.current;\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\n event.currentTarget.dispatchEvent(entryFocusEvent);\n\n if (!entryFocusEvent.defaultPrevented) {\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\n // Nửa sao: tìm mục đại diện cho giá trị đang chọn bằng giá trị TRẦN (3,5 → mục 4).\n const selectedItem =\n propsRef.current.step < 1\n ? items.find((item) => item.value === Math.ceil(value))\n : items.find((item) => item.value === value);\n const currentItem = items.find((item) => item.id === tabStopId);\n\n const candidateItems = [selectedItem, currentItem, ...items].filter(Boolean) as ItemData[];\n const candidateRefs = candidateItems.map((item) => item.ref);\n focusFirst(candidateRefs, false);\n }\n }\n isClickFocusRef.current = false;\n },\n [propsRef, isTabbingBackOut, value, tabStopId]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n propsRef.current.onMouseDown?.(event);\n\n if (event.defaultPrevented) return;\n\n isClickFocusRef.current = true;\n },\n [propsRef]\n );\n\n const contextValue = React.useMemo<RatingContextValue>(\n () => ({\n rootId,\n dir,\n orientation,\n activationMode,\n disabled,\n readOnly,\n size,\n max,\n step,\n clearable,\n itemLabel,\n getAutoIndex\n }),\n [rootId, dir, orientation, activationMode, disabled, readOnly, size, max, step, clearable, itemLabel, getAutoIndex]\n );\n\n const focusContextValue = React.useMemo<FocusContextValue>(\n () => ({\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n }),\n [\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n /*\n * ⚠ Khác thượng nguồn: gradient của NỬA SAO phải nằm BÊN TRONG phần tử gốc. Trong SVG,\n * `stopColor=\"currentColor\"` lấy `color` tính ở chỗ gradient được KHAI, không phải ở chỗ nó được\n * dùng — bản gốc đặt `<svg>` này làm anh em của phần tử gốc, nên nửa sao ăn màu chữ của trang\n * (đo trên trình duyệt: #0E1116 ở light, #F7F7F8 ở dark) thay vì màu sao `text-primary-ink`.\n * Với `asChild` thì Slot không nhận thêm con được, nên gradient đành đứng ngoài như bản gốc —\n * khi đó app phải tự đặt màu chữ cho phần tử BAO NGOÀI.\n */\n const partialFillGradient = (\n <svg width=\"0\" height=\"0\" aria-hidden=\"true\" focusable=\"false\" style={{ position: \"absolute\" }}>\n <defs>\n <linearGradient id={getPartialFillGradientId(rootId, step)}>\n {dir === \"rtl\" ? (\n <>\n <stop offset=\"50%\" stopColor=\"transparent\" />\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n </>\n ) : (\n <>\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n <stop offset=\"50%\" stopColor=\"transparent\" />\n </>\n )}\n </linearGradient>\n </defs>\n </svg>\n );\n\n return (\n <StoreContext.Provider value={store}>\n <RatingContext.Provider value={contextValue}>\n <FocusContext.Provider value={focusContextValue}>\n <RootPrimitive\n id={rootId}\n role=\"radiogroup\"\n aria-orientation={orientation}\n aria-disabled={disabled || undefined}\n aria-readonly={readOnly || undefined}\n aria-required={required || undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-orientation={orientation}\n data-slot=\"rating\"\n dir={dir}\n tabIndex={isTabbingBackOut || focusableItemCount === 0 ? -1 : 0}\n {...rootProps}\n ref={composedRef}\n className={cn(\n /*\n * Ngôi sao là thành phần PHI VĂN BẢN mang nghĩa (nó LÀ giá trị) → màu đi qua bậc\n * `-ink` của tone, không phải token tô nền. Bản gốc `text-primary` đọc thẳng màu\n * TÔ NỀN; ở bảng mặc định hai token trùng nhau (`--primary-ink: var(--primary)`),\n * nhưng bảng tương phản cao kéo `--primary-ink` xuống bậc tối — và chỉ `-ink` mới\n * được đổi theo. Hợp đồng §1 của docs/hop-dong-mau.md.\n */\n \"flex gap-1 text-primary-ink outline-none\",\n orientation === \"horizontal\" ? \"flex-row items-center\" : \"flex-col items-start\",\n className\n )}\n onBlur={onBlur}\n onFocus={onFocus}\n onMouseDown={onMouseDown}\n >\n {asChild ? (\n children\n ) : (\n <>\n {children}\n {partialFillGradient}\n </>\n )}\n </RootPrimitive>\n {asChild ? partialFillGradient : null}\n {isFormControl && (\n <VisuallyHiddenInput\n type=\"hidden\"\n control={formTrigger}\n name={name}\n value={value}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n />\n )}\n </FocusContext.Provider>\n </RatingContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface RatingItemProps extends Omit<React.ComponentProps<\"button\">, \"children\"> {\n index?: number;\n asChild?: boolean;\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\n}\n\nfunction RatingItem(props: RatingItemProps) {\n const {\n index,\n asChild,\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp,\n disabled,\n className,\n children,\n ref,\n ...itemProps\n } = props;\n\n const itemRef = React.useRef<ItemElement>(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n const context = useRatingContext(ITEM_NAME);\n\n const instanceId = React.useId();\n\n const actualIndex = React.useMemo(() => {\n if (index !== undefined) {\n return index;\n }\n\n return context.getAutoIndex(instanceId);\n }, [index, context, instanceId]);\n\n const itemValue = actualIndex + 1;\n const store = useStoreContext(ITEM_NAME);\n const focusContext = useFocusContext(ITEM_NAME);\n const value = useStore((state) => state.value);\n const hoveredValue = useStore((state) => state.hoveredValue);\n const clearable = context.clearable;\n const step = context.step;\n const activationMode = context.activationMode;\n\n const itemId = getItemId(context.rootId, itemValue);\n const isDisabled = context.disabled || disabled;\n const isReadOnly = context.readOnly;\n const isTabStop = focusContext.tabStopId === itemId;\n\n const displayValue = hoveredValue ?? value;\n const isFilled = displayValue >= itemValue;\n const isPartiallyFilled = step < 1 && displayValue >= itemValue - step && displayValue < itemValue;\n const isHovered = hoveredValue !== null && hoveredValue < itemValue;\n /*\n * ⚠ Khác thượng nguồn: bản gốc đặt `aria-checked={isFilled}`, tức chọn 4 sao thì BỐN nút radio\n * cùng báo \"đã chọn\" — một radiogroup có nhiều radio được chọn là trạng thái không tồn tại với\n * trình đọc màn hình. Ở đây chỉ đúng MỘT mục được chọn: mục chứa giá trị (3,5 → mục 4), và đọc\n * theo `value` thật chứ không theo sao đang được rê chuột (`displayValue`).\n */\n const isChecked = value > 0 && Math.ceil(value) === itemValue;\n\n const isMouseClickRef = React.useRef(false);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp\n });\n\n useIsomorphicLayoutEffect(() => {\n focusContext.onItemRegister({\n id: itemId,\n ref: itemRef,\n value: itemValue,\n disabled: !!isDisabled\n });\n\n if (!isDisabled) {\n focusContext.onFocusableItemAdd();\n }\n\n return () => {\n focusContext.onItemUnregister(itemId);\n if (!isDisabled) {\n focusContext.onFocusableItemRemove();\n }\n };\n }, [focusContext, itemId, itemValue, isDisabled]);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let newValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const clickX = event.clientX - rect.left;\n const isLeftHalf = clickX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n newValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n newValue = itemValue - step;\n }\n }\n }\n\n if (clearable && value === newValue) {\n newValue = 0;\n }\n\n store.setState(\"value\", newValue);\n }\n },\n [isDisabled, isReadOnly, clearable, step, value, itemValue, store, context.dir, propsRef]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<ItemElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n focusContext.onItemFocus(itemId);\n\n const isKeyboardFocus = !isMouseClickRef.current;\n\n if (!isDisabled && !isReadOnly && activationMode !== \"manual\" && isKeyboardFocus) {\n // Nửa sao: nếu giá trị đang là nửa-bậc THUỘC mục này (3,5 khi mục là 4) thì đừng đổi nó.\n const isHalfStepValue = step < 1 && value === itemValue - step;\n\n if (!isHalfStepValue) {\n const newValue = clearable && value === itemValue ? 0 : itemValue;\n store.setState(\"value\", newValue);\n }\n }\n\n isMouseClickRef.current = false;\n },\n [focusContext, itemId, activationMode, isDisabled, isReadOnly, clearable, value, itemValue, step, store, propsRef]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<ItemElement>) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if ((event.key === \"Enter\" || event.key === \" \") && activationMode === \"manual\") {\n event.preventDefault();\n if (!isDisabled && !isReadOnly && itemRef.current) {\n itemRef.current.click();\n }\n return;\n }\n\n if (event.key === \"Tab\" && event.shiftKey) {\n focusContext.onItemShiftTab();\n return;\n }\n\n if (event.target !== event.currentTarget) return;\n\n const focusIntent = getFocusIntent(event, context.dir, context.orientation);\n\n if (focusIntent !== undefined) {\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n event.preventDefault();\n\n // Nửa sao: mũi tên tăng/giảm đúng một `step` thay vì nhảy sang mục kế.\n if (step < 1 && (focusIntent === \"prev\" || focusIntent === \"next\")) {\n if (!isDisabled && !isReadOnly) {\n let newValue = value;\n\n if (focusIntent === \"next\") {\n newValue = Math.min(value + step, context.max);\n } else {\n newValue = Math.max(value - step, 0);\n }\n\n store.setState(\"value\", newValue);\n\n // Đưa focus tới mục đại diện cho giá trị mới.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n const targetItem = items.find((item) => item.value === Math.ceil(newValue));\n if (targetItem?.ref.current) {\n queueMicrotask(() => targetItem.ref.current?.focus());\n }\n }\n return;\n }\n\n // Bậc nguyên, hoặc Home/End: điều hướng như thường.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n let candidateRefs = items.map((item) => item.ref);\n\n if (focusIntent === \"last\") {\n candidateRefs.reverse();\n } else if (focusIntent === \"prev\" || focusIntent === \"next\") {\n if (focusIntent === \"prev\") candidateRefs.reverse();\n const currentIndex = candidateRefs.findIndex((ref) => ref.current === event.currentTarget);\n candidateRefs = candidateRefs.slice(currentIndex + 1);\n }\n\n queueMicrotask(() => focusFirst(candidateRefs));\n }\n },\n [\n focusContext,\n context.dir,\n context.orientation,\n activationMode,\n isDisabled,\n isReadOnly,\n step,\n value,\n context.max,\n store,\n propsRef\n ]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseDown?.(event);\n if (event.defaultPrevented) return;\n\n isMouseClickRef.current = true;\n\n if (isDisabled) {\n event.preventDefault();\n } else {\n focusContext.onItemFocus(itemId);\n }\n },\n [focusContext, itemId, isDisabled, propsRef]\n );\n\n const onMouseEnter = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseEnter?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let hoverValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n hoverValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n hoverValue = itemValue - step;\n }\n }\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const onMouseLeave = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseLeave?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n store.setState(\"hoveredValue\", null);\n }\n },\n [isDisabled, isReadOnly, store, propsRef]\n );\n\n const onMouseMove = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseMove?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly && step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n let hoverValue = itemValue;\n if (context.dir === \"rtl\") {\n hoverValue = !isLeftHalf ? itemValue - step : itemValue;\n } else {\n hoverValue = isLeftHalf ? itemValue - step : itemValue;\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const dataState: DataState = isFilled ? \"full\" : isPartiallyFilled ? \"partial\" : \"empty\";\n\n const ItemPrimitive = asChild ? Slot : \"button\";\n\n return (\n <ItemPrimitive\n role=\"radio\"\n type=\"button\"\n id={itemId}\n aria-label={context.itemLabel(itemValue, context.max)}\n aria-checked={isChecked}\n aria-posinset={itemValue}\n aria-setsize={context.max}\n data-disabled={isDisabled ? \"\" : undefined}\n data-readonly={isReadOnly ? \"\" : undefined}\n data-state={dataState}\n data-hovered={isHovered ? \"\" : undefined}\n data-slot=\"rating-item\"\n disabled={isDisabled}\n tabIndex={isTabStop ? 0 : -1}\n {...itemProps}\n ref={composedRef}\n style={{\n ...itemProps.style,\n ...(isPartiallyFilled && {\n \"--partial-fill\": `url(#${getPartialFillGradientId(context.rootId, step)})`\n })\n }}\n className={cn(\n // Viền focus cùng khuôn với `Checkbox`/`RadioGroup` của gói.\n \"inline-flex items-center justify-center rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:transition-colors [&_svg]:duration-200 data-[state=empty]:[&_svg]:fill-transparent data-[state=full]:[&_svg]:fill-current data-[state=partial]:[&_svg]:fill-(--partial-fill) [&_svg:not([class*='size-'])]:size-full\",\n context.size === \"sm\" ? \"size-4\" : context.size === \"lg\" ? \"size-6\" : \"size-5\",\n className\n )}\n onClick={onClick}\n onFocus={onFocus}\n onKeyDown={onKeyDown}\n onMouseDown={onMouseDown}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n {typeof children === \"function\" ? children(dataState) : (children ?? <Star aria-hidden=\"true\" />)}\n </ItemPrimitive>\n );\n}\n\nexport { Rating, RatingItem, type RatingItemProps, type RatingProps, useStore as useRating };\n"],"mappings":"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","names":["value","instanceId","tabStopId","id","items","ref"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/rating.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/rating) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { Star } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { VisuallyHiddenInput } from \"../../lib/visually-hidden-input.js\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\ntype ActivationMode = \"automatic\" | \"manual\";\ntype Size = \"default\" | \"sm\" | \"lg\";\ntype Step = 0.5 | 1;\ntype DataState = \"full\" | \"partial\" | \"empty\";\ntype FocusIntent = \"first\" | \"last\" | \"prev\" | \"next\";\n\ntype RootElement = React.ComponentRef<typeof Rating>;\ntype ItemElement = React.ComponentRef<typeof RatingItem>;\n\nconst ROOT_NAME = \"Rating\";\nconst ITEM_NAME = \"RatingItem\";\n\nconst ENTRY_FOCUS = \"ratingFocusGroup.onEntryFocus\";\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\nfunction getItemId(id: string, value: number) {\n return `${id}-item-${value}`;\n}\n\nfunction getPartialFillGradientId(id: string, step: Step) {\n return `partial-fill-gradient-${id}-${step}`;\n}\n\nconst MAP_KEY_TO_FOCUS_INTENT: Record<string, FocusIntent> = {\n ArrowLeft: \"prev\",\n ArrowUp: \"prev\",\n ArrowRight: \"next\",\n ArrowDown: \"next\",\n Home: \"first\",\n End: \"last\"\n};\n\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\n if (dir !== \"rtl\") return key;\n return key === \"ArrowLeft\" ? \"ArrowRight\" : key === \"ArrowRight\" ? \"ArrowLeft\" : key;\n}\n\nfunction getFocusIntent(event: React.KeyboardEvent<ItemElement>, dir?: Direction, orientation?: Orientation) {\n const key = getDirectionAwareKey(event.key, dir);\n if (orientation === \"horizontal\" && [\"ArrowUp\", \"ArrowDown\"].includes(key)) return undefined;\n if (orientation === \"vertical\" && [\"ArrowLeft\", \"ArrowRight\"].includes(key)) return undefined;\n return MAP_KEY_TO_FOCUS_INTENT[key];\n}\n\nfunction focusFirst(candidates: React.RefObject<ItemElement | null>[], preventScroll = false) {\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\n for (const candidateRef of candidates) {\n const candidate = candidateRef.current;\n if (!candidate) continue;\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\n candidate.focus({ preventScroll });\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\n }\n}\n\ninterface StoreState {\n value: number;\n hoveredValue: number | null;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useRating\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n ref: React.RefObject<ItemElement | null>;\n value: number;\n disabled: boolean;\n}\n\ninterface RatingContextValue {\n rootId: string;\n dir: Direction;\n orientation: Orientation;\n activationMode: ActivationMode;\n size: Size;\n max: number;\n step: Step;\n clearable: boolean;\n disabled: boolean;\n readOnly: boolean;\n itemLabel: (value: number, max: number) => string;\n getAutoIndex: (instanceId: string) => number;\n}\n\nconst RatingContext = React.createContext<RatingContextValue | null>(null);\n\nfunction useRatingContext(consumerName: string) {\n const context = React.useContext(RatingContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface FocusContextValue {\n tabStopId: string | null;\n onItemFocus: (tabStopId: string) => void;\n onItemShiftTab: () => void;\n onFocusableItemAdd: () => void;\n onFocusableItemRemove: () => void;\n onItemRegister: (item: ItemData) => void;\n onItemUnregister: (id: string) => void;\n getItems: () => ItemData[];\n}\n\nconst FocusContext = React.createContext<FocusContextValue | null>(null);\n\nfunction useFocusContext(consumerName: string) {\n const context = React.useContext(FocusContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`FocusProvider\\``);\n }\n return context;\n}\n\ninterface RatingProps extends React.ComponentProps<\"div\"> {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n onHover?: (value: number | null) => void;\n max?: number;\n activationMode?: ActivationMode;\n dir?: Direction;\n orientation?: Orientation;\n size?: Size;\n asChild?: boolean;\n step?: Step;\n clearable?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n name?: string;\n /**\n * Tên của TỪNG ngôi sao cho trình đọc màn hình — mặc định khoá `itemLabel` của namespace\n * `rating` (`ComitorLocaleProvider`).\n *\n * ⚠ Thêm so với thượng nguồn. Ở bản gốc mỗi `RatingItem` là một `role=\"radio\"` chỉ chứa một\n * `<svg>`, tức một nút KHÔNG TÊN: trình đọc màn hình đọc \"nút radio, 3 trên 5\" mà không nói 3\n * CÁI GÌ. Là HÀM chứ không phải chuỗi mẫu vì có số chèn vào giữa câu (README §5).\n *\n * Nhóm vẫn cần tên riêng: truyền `aria-label` (vd \"Mức hài lòng\") cho chính `Rating`.\n */\n itemLabel?: (value: number, max: number) => string;\n}\n\nfunction Rating(props: RatingProps) {\n const {\n value: valueProp,\n defaultValue = 0,\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n dir: dirProp,\n orientation = \"horizontal\",\n activationMode = \"automatic\",\n size = \"default\",\n max = 5,\n step = 1,\n clearable = false,\n asChild,\n disabled = false,\n readOnly = false,\n required = false,\n itemLabel: itemLabelProp,\n className,\n children,\n id,\n name,\n ref,\n ...rootProps\n } = props;\n\n const t = useComitorMessages(\"rating\");\n const itemLabel = itemLabelProp ?? t.itemLabel;\n const dir = useDirection(dirProp);\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<StoreState>(() => ({\n value: valueProp ?? defaultValue,\n hoveredValue: null\n }));\n\n const propsRef = useAsRef({\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n step\n });\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === \"value\" && typeof value === \"number\") {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === \"hoveredValue\") {\n stateRef.current.hoveredValue = value as number | null;\n propsRef.current.onHover?.(value as number | null);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n const value = useStore((state) => state.value, store);\n\n const [formTrigger, setFormTrigger] = React.useState<RootElement | null>(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\n\n const [tabStopId, setTabStopId] = React.useState<string | null>(null);\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\n const isClickFocusRef = React.useRef(false);\n const itemsRef = React.useRef<Map<string, ItemData>>(new Map());\n\n const autoIndexMapRef = React.useRef(new Map<string, number>());\n const nextAutoIndexRef = React.useRef(0);\n\n const getAutoIndex = React.useCallback((instanceId: string) => {\n const existingIndex = autoIndexMapRef.current.get(instanceId);\n if (existingIndex !== undefined) {\n return existingIndex;\n }\n\n const newIndex = nextAutoIndexRef.current++;\n autoIndexMapRef.current.set(instanceId, newIndex);\n return newIndex;\n }, []);\n\n const onItemFocus = React.useCallback((tabStopId: string) => {\n setTabStopId(tabStopId);\n }, []);\n\n const onItemShiftTab = React.useCallback(() => {\n setIsTabbingBackOut(true);\n }, []);\n\n const onFocusableItemAdd = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount + 1);\n }, []);\n\n const onFocusableItemRemove = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount - 1);\n }, []);\n\n const onItemRegister = React.useCallback((item: ItemData) => {\n itemsRef.current.set(item.id, item);\n }, []);\n\n const onItemUnregister = React.useCallback((id: string) => {\n itemsRef.current.delete(id);\n }, []);\n\n const getItems = React.useCallback(() => {\n return Array.from(itemsRef.current.values())\n .filter((item) => item.ref.current)\n .sort((a, b) => {\n const elementA = a.ref.current;\n const elementB = b.ref.current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n return -1;\n }\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n return 1;\n }\n return 0;\n });\n }, []);\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n rootProps.onBlur?.(event);\n if (event.defaultPrevented) return;\n\n setIsTabbingBackOut(false);\n },\n [rootProps.onBlur]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n const isKeyboardFocus = !isClickFocusRef.current;\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\n event.currentTarget.dispatchEvent(entryFocusEvent);\n\n if (!entryFocusEvent.defaultPrevented) {\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\n // Nửa sao: tìm mục đại diện cho giá trị đang chọn bằng giá trị TRẦN (3,5 → mục 4).\n const selectedItem =\n propsRef.current.step < 1\n ? items.find((item) => item.value === Math.ceil(value))\n : items.find((item) => item.value === value);\n const currentItem = items.find((item) => item.id === tabStopId);\n\n const candidateItems = [selectedItem, currentItem, ...items].filter(Boolean) as ItemData[];\n const candidateRefs = candidateItems.map((item) => item.ref);\n focusFirst(candidateRefs, false);\n }\n }\n isClickFocusRef.current = false;\n },\n [propsRef, isTabbingBackOut, value, tabStopId]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n propsRef.current.onMouseDown?.(event);\n\n if (event.defaultPrevented) return;\n\n isClickFocusRef.current = true;\n },\n [propsRef]\n );\n\n const contextValue = React.useMemo<RatingContextValue>(\n () => ({\n rootId,\n dir,\n orientation,\n activationMode,\n disabled,\n readOnly,\n size,\n max,\n step,\n clearable,\n itemLabel,\n getAutoIndex\n }),\n [rootId, dir, orientation, activationMode, disabled, readOnly, size, max, step, clearable, itemLabel, getAutoIndex]\n );\n\n const focusContextValue = React.useMemo<FocusContextValue>(\n () => ({\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n }),\n [\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n /*\n * ⚠ Khác thượng nguồn: gradient của NỬA SAO phải nằm BÊN TRONG phần tử gốc. Trong SVG,\n * `stopColor=\"currentColor\"` lấy `color` tính ở chỗ gradient được KHAI, không phải ở chỗ nó được\n * dùng — bản gốc đặt `<svg>` này làm anh em của phần tử gốc, nên nửa sao ăn màu chữ của trang\n * (đo trên trình duyệt: #0E1116 ở light, #F7F7F8 ở dark) thay vì màu sao `text-primary-ink`.\n * Với `asChild` thì Slot không nhận thêm con được, nên gradient đành đứng ngoài như bản gốc —\n * khi đó app phải tự đặt màu chữ cho phần tử BAO NGOÀI.\n */\n const partialFillGradient = (\n <svg width=\"0\" height=\"0\" aria-hidden=\"true\" focusable=\"false\" style={{ position: \"absolute\" }}>\n <defs>\n <linearGradient id={getPartialFillGradientId(rootId, step)}>\n {dir === \"rtl\" ? (\n <>\n <stop offset=\"50%\" stopColor=\"transparent\" />\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n </>\n ) : (\n <>\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n <stop offset=\"50%\" stopColor=\"transparent\" />\n </>\n )}\n </linearGradient>\n </defs>\n </svg>\n );\n\n return (\n <StoreContext.Provider value={store}>\n <RatingContext.Provider value={contextValue}>\n <FocusContext.Provider value={focusContextValue}>\n <RootPrimitive\n id={rootId}\n role=\"radiogroup\"\n aria-orientation={orientation}\n aria-disabled={disabled || undefined}\n aria-readonly={readOnly || undefined}\n aria-required={required || undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-orientation={orientation}\n data-slot=\"rating\"\n dir={dir}\n tabIndex={isTabbingBackOut || focusableItemCount === 0 ? -1 : 0}\n {...rootProps}\n ref={composedRef}\n className={cn(\n /*\n * Ngôi sao là thành phần PHI VĂN BẢN mang nghĩa (nó LÀ giá trị) → màu đi qua bậc\n * `-ink` của tone, không phải token tô nền. Bản gốc `text-primary` đọc thẳng màu\n * TÔ NỀN; ở bảng mặc định hai token trùng nhau (`--primary-ink: var(--primary)`),\n * nhưng bảng tương phản cao kéo `--primary-ink` xuống bậc tối — và chỉ `-ink` mới\n * được đổi theo. Hợp đồng §1 của docs/hop-dong-mau.md.\n */\n \"flex gap-1 text-primary-ink outline-none\",\n orientation === \"horizontal\" ? \"flex-row items-center\" : \"flex-col items-start\",\n className\n )}\n onBlur={onBlur}\n onFocus={onFocus}\n onMouseDown={onMouseDown}\n >\n {asChild ? (\n children\n ) : (\n <>\n {children}\n {partialFillGradient}\n </>\n )}\n </RootPrimitive>\n {asChild ? partialFillGradient : null}\n {isFormControl && (\n <VisuallyHiddenInput\n type=\"hidden\"\n control={formTrigger}\n name={name}\n value={value}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n />\n )}\n </FocusContext.Provider>\n </RatingContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface RatingItemProps extends Omit<React.ComponentProps<\"button\">, \"children\"> {\n index?: number;\n asChild?: boolean;\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\n}\n\nfunction RatingItem(props: RatingItemProps) {\n const {\n index,\n asChild,\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp,\n disabled,\n className,\n children,\n ref,\n ...itemProps\n } = props;\n\n const itemRef = React.useRef<ItemElement>(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n const context = useRatingContext(ITEM_NAME);\n\n const instanceId = React.useId();\n\n const actualIndex = React.useMemo(() => {\n if (index !== undefined) {\n return index;\n }\n\n return context.getAutoIndex(instanceId);\n }, [index, context, instanceId]);\n\n const itemValue = actualIndex + 1;\n const store = useStoreContext(ITEM_NAME);\n const focusContext = useFocusContext(ITEM_NAME);\n const value = useStore((state) => state.value);\n const hoveredValue = useStore((state) => state.hoveredValue);\n const clearable = context.clearable;\n const step = context.step;\n const activationMode = context.activationMode;\n\n const itemId = getItemId(context.rootId, itemValue);\n const isDisabled = context.disabled || disabled;\n const isReadOnly = context.readOnly;\n const isTabStop = focusContext.tabStopId === itemId;\n\n const displayValue = hoveredValue ?? value;\n const isFilled = displayValue >= itemValue;\n const isPartiallyFilled = step < 1 && displayValue >= itemValue - step && displayValue < itemValue;\n const isHovered = hoveredValue !== null && hoveredValue < itemValue;\n /*\n * ⚠ Khác thượng nguồn: bản gốc đặt `aria-checked={isFilled}`, tức chọn 4 sao thì BỐN nút radio\n * cùng báo \"đã chọn\" — một radiogroup có nhiều radio được chọn là trạng thái không tồn tại với\n * trình đọc màn hình. Ở đây chỉ đúng MỘT mục được chọn: mục chứa giá trị (3,5 → mục 4), và đọc\n * theo `value` thật chứ không theo sao đang được rê chuột (`displayValue`).\n */\n const isChecked = value > 0 && Math.ceil(value) === itemValue;\n\n const isMouseClickRef = React.useRef(false);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp\n });\n\n useIsomorphicLayoutEffect(() => {\n focusContext.onItemRegister({\n id: itemId,\n ref: itemRef,\n value: itemValue,\n disabled: !!isDisabled\n });\n\n if (!isDisabled) {\n focusContext.onFocusableItemAdd();\n }\n\n return () => {\n focusContext.onItemUnregister(itemId);\n if (!isDisabled) {\n focusContext.onFocusableItemRemove();\n }\n };\n }, [focusContext, itemId, itemValue, isDisabled]);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let newValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const clickX = event.clientX - rect.left;\n const isLeftHalf = clickX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n newValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n newValue = itemValue - step;\n }\n }\n }\n\n if (clearable && value === newValue) {\n newValue = 0;\n }\n\n store.setState(\"value\", newValue);\n }\n },\n [isDisabled, isReadOnly, clearable, step, value, itemValue, store, context.dir, propsRef]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<ItemElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n focusContext.onItemFocus(itemId);\n\n const isKeyboardFocus = !isMouseClickRef.current;\n\n if (!isDisabled && !isReadOnly && activationMode !== \"manual\" && isKeyboardFocus) {\n // Nửa sao: nếu giá trị đang là nửa-bậc THUỘC mục này (3,5 khi mục là 4) thì đừng đổi nó.\n const isHalfStepValue = step < 1 && value === itemValue - step;\n\n if (!isHalfStepValue) {\n const newValue = clearable && value === itemValue ? 0 : itemValue;\n store.setState(\"value\", newValue);\n }\n }\n\n isMouseClickRef.current = false;\n },\n [focusContext, itemId, activationMode, isDisabled, isReadOnly, clearable, value, itemValue, step, store, propsRef]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<ItemElement>) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if ((event.key === \"Enter\" || event.key === \" \") && activationMode === \"manual\") {\n event.preventDefault();\n if (!isDisabled && !isReadOnly && itemRef.current) {\n itemRef.current.click();\n }\n return;\n }\n\n if (event.key === \"Tab\" && event.shiftKey) {\n focusContext.onItemShiftTab();\n return;\n }\n\n if (event.target !== event.currentTarget) return;\n\n const focusIntent = getFocusIntent(event, context.dir, context.orientation);\n\n if (focusIntent !== undefined) {\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n event.preventDefault();\n\n // Nửa sao: mũi tên tăng/giảm đúng một `step` thay vì nhảy sang mục kế.\n if (step < 1 && (focusIntent === \"prev\" || focusIntent === \"next\")) {\n if (!isDisabled && !isReadOnly) {\n let newValue = value;\n\n if (focusIntent === \"next\") {\n newValue = Math.min(value + step, context.max);\n } else {\n newValue = Math.max(value - step, 0);\n }\n\n store.setState(\"value\", newValue);\n\n // Đưa focus tới mục đại diện cho giá trị mới.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n const targetItem = items.find((item) => item.value === Math.ceil(newValue));\n if (targetItem?.ref.current) {\n queueMicrotask(() => targetItem.ref.current?.focus());\n }\n }\n return;\n }\n\n // Bậc nguyên, hoặc Home/End: điều hướng như thường.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n let candidateRefs = items.map((item) => item.ref);\n\n if (focusIntent === \"last\") {\n candidateRefs.reverse();\n } else if (focusIntent === \"prev\" || focusIntent === \"next\") {\n if (focusIntent === \"prev\") candidateRefs.reverse();\n const currentIndex = candidateRefs.findIndex((ref) => ref.current === event.currentTarget);\n candidateRefs = candidateRefs.slice(currentIndex + 1);\n }\n\n queueMicrotask(() => focusFirst(candidateRefs));\n }\n },\n [\n focusContext,\n context.dir,\n context.orientation,\n activationMode,\n isDisabled,\n isReadOnly,\n step,\n value,\n context.max,\n store,\n propsRef\n ]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseDown?.(event);\n if (event.defaultPrevented) return;\n\n isMouseClickRef.current = true;\n\n if (isDisabled) {\n event.preventDefault();\n } else {\n focusContext.onItemFocus(itemId);\n }\n },\n [focusContext, itemId, isDisabled, propsRef]\n );\n\n const onMouseEnter = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseEnter?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let hoverValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n hoverValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n hoverValue = itemValue - step;\n }\n }\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const onMouseLeave = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseLeave?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n store.setState(\"hoveredValue\", null);\n }\n },\n [isDisabled, isReadOnly, store, propsRef]\n );\n\n const onMouseMove = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseMove?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly && step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n let hoverValue = itemValue;\n if (context.dir === \"rtl\") {\n hoverValue = !isLeftHalf ? itemValue - step : itemValue;\n } else {\n hoverValue = isLeftHalf ? itemValue - step : itemValue;\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const dataState: DataState = isFilled ? \"full\" : isPartiallyFilled ? \"partial\" : \"empty\";\n\n const ItemPrimitive = asChild ? Slot : \"button\";\n\n return (\n <ItemPrimitive\n role=\"radio\"\n type=\"button\"\n id={itemId}\n aria-label={context.itemLabel(itemValue, context.max)}\n aria-checked={isChecked}\n aria-posinset={itemValue}\n aria-setsize={context.max}\n data-disabled={isDisabled ? \"\" : undefined}\n data-readonly={isReadOnly ? \"\" : undefined}\n data-state={dataState}\n data-hovered={isHovered ? \"\" : undefined}\n data-slot=\"rating-item\"\n disabled={isDisabled}\n tabIndex={isTabStop ? 0 : -1}\n {...itemProps}\n ref={composedRef}\n style={{\n ...itemProps.style,\n ...(isPartiallyFilled && {\n \"--partial-fill\": `url(#${getPartialFillGradientId(context.rootId, step)})`\n })\n }}\n className={cn(\n // Viền focus cùng khuôn với `Checkbox`/`RadioGroup` của gói.\n \"inline-flex items-center justify-center rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:transition-colors [&_svg]:duration-200 data-[state=empty]:[&_svg]:fill-transparent data-[state=full]:[&_svg]:fill-current data-[state=partial]:[&_svg]:fill-(--partial-fill) [&_svg:not([class*='size-'])]:size-full\",\n context.size === \"sm\" ? \"size-4\" : context.size === \"lg\" ? \"size-6\" : \"size-5\",\n className\n )}\n onClick={onClick}\n onFocus={onFocus}\n onKeyDown={onKeyDown}\n onMouseDown={onMouseDown}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n {typeof children === \"function\" ? children(dataState) : (children ?? <Star aria-hidden=\"true\" />)}\n </ItemPrimitive>\n );\n}\n\nexport { Rating, RatingItem, type RatingItemProps, type RatingProps, useStore as useRating };\n"],"mappings":"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","names":["value","instanceId","tabStopId","id","items","ref"]}
|
|
@@ -3,6 +3,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useDirection } from "@radix-ui/react-direction";
|
|
4
4
|
import { Slot } from "@radix-ui/react-slot";
|
|
5
5
|
import * as React from "react";
|
|
6
|
+
import { useComitorMessages } from "../../i18n/locale-provider.js";
|
|
6
7
|
import { cn } from "../../lib/cn.js";
|
|
7
8
|
import { useComposedRefs } from "../../lib/compose-refs.js";
|
|
8
9
|
import { useAsRef } from "../../lib/use-as-ref.js";
|
|
@@ -227,11 +228,12 @@ function ScrollSpy(props) {
|
|
|
227
228
|
function ScrollSpyNav(props) {
|
|
228
229
|
const { asChild, className, ...navProps } = props;
|
|
229
230
|
const { dir, orientation } = useScrollSpyContext(NAV_NAME);
|
|
231
|
+
const t = useComitorMessages("scrollSpy");
|
|
230
232
|
const NavPrimitive = asChild ? Slot : "nav";
|
|
231
233
|
return /* @__PURE__ */ jsx(
|
|
232
234
|
NavPrimitive,
|
|
233
235
|
{
|
|
234
|
-
"aria-label":
|
|
236
|
+
"aria-label": t.navLabel,
|
|
235
237
|
"data-orientation": orientation,
|
|
236
238
|
"data-slot": "scroll-spy-nav",
|
|
237
239
|
dir,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/scroll-spy.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/scroll-spy) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n *\n * Dùng ở Comitor: trang cài đặt dài và trang chi tiết bản ghi (các mục của một liên hệ CRM) có\n * mục lục điều hướng trong trang.\n *\n * ⚠ `orientation` giữ NGHĨA của thượng nguồn — nó nói về hướng xếp của KHUNG, không phải của danh\n * sách link: `horizontal` (mặc định) = mục lục đứng bên cạnh nội dung, link xếp DỌC; `vertical` =\n * mục lục nằm trên nội dung, link xếp NGANG.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\n\nconst ROOT_NAME = \"ScrollSpy\";\nconst NAV_NAME = \"ScrollSpyNav\";\nconst LINK_NAME = \"ScrollSpyLink\";\nconst VIEWPORT_NAME = \"ScrollSpyViewport\";\nconst SECTION_NAME = \"ScrollSpySection\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\n\ntype LinkElement = React.ComponentRef<typeof ScrollSpyLink>;\ntype SectionElement = React.ComponentRef<typeof ScrollSpySection>;\n\nfunction getDefaultScrollBehavior(): ScrollBehavior {\n if (typeof window === \"undefined\") return \"smooth\";\n return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches ? \"auto\" : \"smooth\";\n}\n\ninterface StoreState {\n value: string;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useStore\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ScrollSpyContextValue {\n offset: number;\n scrollBehavior: ScrollBehavior | undefined;\n dir: Direction;\n orientation: Orientation;\n scrollContainer: HTMLElement | null;\n isScrollingRef: React.RefObject<boolean>;\n onSectionRegister: (id: string, element: SectionElement) => void;\n onSectionUnregister: (id: string) => void;\n onScrollToSection: (sectionId: string) => void;\n}\n\nconst ScrollSpyContext = React.createContext<ScrollSpyContextValue | null>(null);\n\nfunction useScrollSpyContext(consumerName: string) {\n const context = React.useContext(ScrollSpyContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface ScrollSpyProps extends React.ComponentProps<\"div\"> {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n rootMargin?: string;\n threshold?: number | number[];\n offset?: number;\n /**\n * Kiểu cuộn khi bấm link. Không truyền thì `\"smooth\"`, hoặc `\"auto\"` nếu người dùng bật giảm\n * chuyển động (`prefers-reduced-motion`).\n */\n scrollBehavior?: ScrollBehavior;\n scrollContainer?: HTMLElement | null;\n dir?: Direction;\n orientation?: Orientation;\n asChild?: boolean;\n}\n\nfunction ScrollSpy(props: ScrollSpyProps) {\n const {\n value,\n defaultValue,\n onValueChange,\n rootMargin,\n threshold = 0.1,\n offset = 0,\n /*\n * ⚠ Khác thượng nguồn: bản gốc tính mặc định NGAY TRONG render (`scrollBehavior =\n * getDefaultScrollBehavior()` → `window.matchMedia` mỗi lượt render). Ở đây để `undefined` và\n * chỉ phân giải lúc cuộn thật — không chạm `window` khi render, và đọc đúng thiết lập giảm\n * chuyển động TẠI THỜI ĐIỂM bấm chứ không phải lúc render gần nhất.\n */\n scrollBehavior,\n scrollContainer = null,\n dir: dirProp,\n orientation = \"horizontal\",\n asChild,\n className,\n ...rootProps\n } = props;\n\n const dir = useDirection(dirProp);\n\n const stateRef = useLazyRef<StoreState>(() => ({\n value: value ?? defaultValue ?? \"\"\n }));\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const onValueChangeRef = useAsRef(onValueChange);\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => {\n return stateRef.current;\n },\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n stateRef.current[key] = value;\n\n if (key === \"value\" && value) {\n onValueChangeRef.current?.(value);\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, onValueChangeRef]);\n\n const sectionMapRef = React.useRef(new Map<string, Element>());\n const isScrollingRef = React.useRef(false);\n const rafIdRef = React.useRef<number | null>(null);\n const isMountedRef = React.useRef(false);\n const scrollTimeoutRef = React.useRef<number | null>(null);\n\n const onSectionRegister = React.useCallback((id: string, element: SectionElement) => {\n sectionMapRef.current.set(id, element);\n }, []);\n\n const onSectionUnregister = React.useCallback((id: string) => {\n sectionMapRef.current.delete(id);\n }, []);\n\n const onScrollToSection = React.useCallback(\n (sectionId: string) => {\n /*\n * ⚠ Khác thượng nguồn: `CSS.escape` quanh id. Bản gốc ghép thẳng `#${sectionId}` vào\n * `querySelector`, nên id bắt đầu bằng chữ số (\"2024-hop-dong\") hay chứa ký tự đặc biệt\n * (id của `useId()` như \":r1:\") làm nó THROW `SyntaxError` ngay khi bấm link.\n */\n const section = scrollContainer\n ? scrollContainer.querySelector(`#${CSS.escape(sectionId)}`)\n : document.getElementById(sectionId);\n\n if (!section) {\n store.setState(\"value\", sectionId);\n return;\n }\n\n // Bật cờ để observer không ghi đè giá trị trong lúc đang cuộn theo lệnh.\n isScrollingRef.current = true;\n store.setState(\"value\", sectionId);\n\n const behavior = scrollBehavior ?? getDefaultScrollBehavior();\n\n if (scrollContainer) {\n const containerRect = scrollContainer.getBoundingClientRect();\n const sectionRect = section.getBoundingClientRect();\n const scrollTop = scrollContainer.scrollTop;\n const offsetPosition = sectionRect.top - containerRect.top + scrollTop - offset;\n\n scrollContainer.scrollTo({\n top: offsetPosition,\n behavior\n });\n } else {\n const sectionPosition = section.getBoundingClientRect().top;\n const offsetPosition = sectionPosition + window.scrollY - offset;\n\n window.scrollTo({\n top: offsetPosition,\n behavior\n });\n }\n\n if (scrollTimeoutRef.current !== null) {\n clearTimeout(scrollTimeoutRef.current);\n }\n\n scrollTimeoutRef.current = window.setTimeout(() => {\n isScrollingRef.current = false;\n }, 500);\n },\n [scrollContainer, offset, scrollBehavior, store]\n );\n\n /*\n * ⚠ Khác thượng nguồn: chỉ cuộn theo lệnh khi `value` đổi TỪ BÊN NGOÀI. Bản gốc cuộn mỗi khi\n * effect chạy lại sau lần đầu, với hai hệ quả đã đo trên trình duyệt:\n * · StrictMode chạy effect hai lần lúc mount → lần hai cuộn luôn CẢ TRANG tới mục (scrollY\n * 0 → ~5050) vì `scrollContainer` của app lúc đó còn `null`. Next dev bật StrictMode sẵn.\n * · Chế độ controlled tự kéo giật: observer đặt giá trị → `onValueChange` → app trả về prop\n * `value` → effect cuộn tới ĐẦU mục đó, ngay giữa lúc người dùng đang cuộn.\n * Luật mới: lúc mount không cuộn (đúng ý nhánh mount của bản gốc); về sau, `value` trùng với giá\n * trị store đang giữ nghĩa là nó chỉ là tiếng vọng của chính observer/link → không cuộn.\n * `defaultValue` đã vào store từ lúc khởi tạo (`stateRef`), không cần đồng bộ thêm.\n */\n const prevValuePropRef = React.useRef<string | undefined>(undefined);\n const onScrollToSectionRef = useAsRef(onScrollToSection);\n\n useIsomorphicLayoutEffect(() => {\n const previous = prevValuePropRef.current;\n prevValuePropRef.current = value;\n if (value === undefined || value === previous) return;\n\n if (previous === undefined || !isMountedRef.current) {\n isMountedRef.current = true;\n store.setState(\"value\", value);\n return;\n }\n\n if (value === store.getState().value) return;\n onScrollToSectionRef.current(value);\n }, [value, store, onScrollToSectionRef]);\n\n useIsomorphicLayoutEffect(() => {\n const sectionMap = sectionMapRef.current;\n if (sectionMap.size === 0) return;\n\n const observerRootMargin = rootMargin ?? `${-offset}px 0px -70% 0px`;\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (isScrollingRef.current) return;\n\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n\n rafIdRef.current = requestAnimationFrame(() => {\n const intersecting = entries.filter((entry) => entry.isIntersecting);\n\n if (intersecting.length === 0) return;\n\n const topmost = intersecting.reduce((prev, curr) => {\n return curr.boundingClientRect.top < prev.boundingClientRect.top ? curr : prev;\n });\n\n const id = topmost.target.id;\n if (id && sectionMap.has(id)) {\n store.setState(\"value\", id);\n }\n });\n },\n {\n root: scrollContainer,\n rootMargin: observerRootMargin,\n threshold\n }\n );\n\n for (const element of sectionMap.values()) {\n observer.observe(element);\n }\n\n return () => {\n observer.disconnect();\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n if (scrollTimeoutRef.current !== null) {\n clearTimeout(scrollTimeoutRef.current);\n }\n };\n }, [offset, rootMargin, threshold, scrollContainer]);\n\n const contextValue = React.useMemo<ScrollSpyContextValue>(\n () => ({\n dir,\n orientation,\n offset,\n scrollBehavior,\n scrollContainer,\n isScrollingRef,\n onSectionRegister,\n onSectionUnregister,\n onScrollToSection\n }),\n [\n dir,\n orientation,\n offset,\n scrollBehavior,\n scrollContainer,\n onSectionRegister,\n onSectionUnregister,\n onScrollToSection\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <StoreContext.Provider value={store}>\n <ScrollSpyContext.Provider value={contextValue}>\n <RootPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy\"\n dir={dir}\n {...rootProps}\n className={cn(\"flex\", orientation === \"horizontal\" ? \"flex-row\" : \"flex-col\", className)}\n />\n </ScrollSpyContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface ScrollSpyNavProps extends React.ComponentProps<\"nav\"> {\n asChild?: boolean;\n}\n\nfunction ScrollSpyNav(props: ScrollSpyNavProps) {\n const { asChild, className, ...navProps } = props;\n\n const { dir, orientation } = useScrollSpyContext(NAV_NAME);\n\n const NavPrimitive = asChild ? Slot : \"nav\";\n\n return (\n <NavPrimitive\n /*\n * ⚠ Khác thượng nguồn: `<nav>` có tên mặc định. Trong khung app Comitor đã có ít nhất MỘT\n * landmark `navigation` (sidebar), nên một `<nav>` thứ hai không tên là hai landmark trùng\n * loại không phân biệt được trong danh sách landmark của trình đọc màn hình. Ghi đè bằng\n * chính prop `aria-label` (cùng khuôn `Breadcrumb`/`Pagination` của gói).\n */\n aria-label=\"Các mục trong trang\"\n data-orientation={orientation}\n data-slot=\"scroll-spy-nav\"\n dir={dir}\n {...navProps}\n className={cn(\"flex gap-2\", orientation === \"horizontal\" ? \"flex-col\" : \"flex-row\", className)}\n />\n );\n}\n\ninterface ScrollSpyLinkProps extends React.ComponentProps<\"a\"> {\n value: string;\n asChild?: boolean;\n}\n\nfunction ScrollSpyLink(props: ScrollSpyLinkProps) {\n const { value: linkValue, asChild, onClick, className, ...linkProps } = props;\n\n const { orientation, onScrollToSection } = useScrollSpyContext(LINK_NAME);\n const value = useStore((state) => state.value);\n const isActive = value === linkValue;\n\n const onLinkClick = React.useCallback(\n (event: React.MouseEvent<LinkElement>) => {\n event.preventDefault();\n onClick?.(event);\n onScrollToSection(linkValue);\n },\n [linkValue, onClick, onScrollToSection]\n );\n\n const LinkPrimitive = asChild ? Slot : \"a\";\n\n return (\n <LinkPrimitive\n /*\n * ⚠ Khác thượng nguồn: bản gốc chỉ đặt `data-state=\"active\"` — thuộc tính mà trình đọc màn\n * hình KHÔNG đọc, nên mục đang xem chỉ được báo bằng hình. `aria-current=\"location\"` là giá\n * trị ARIA dành cho \"vị trí hiện tại\" trong một ngữ cảnh — ở đây là mục đang xem của trang.\n * Không dùng `\"page\"`: link trỏ tới một mục TRONG trang, không phải tới một trang khác.\n */\n aria-current={isActive ? \"location\" : undefined}\n data-orientation={orientation}\n data-slot=\"scroll-spy-link\"\n data-state={isActive ? \"active\" : \"inactive\"}\n {...linkProps}\n href={asChild ? undefined : `#${linkValue}`}\n className={cn(\n \"px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground data-[state=active]:bg-accent data-[state=active]:text-foreground\",\n /*\n * ⚠ Khác thượng nguồn: thêm VẠCH ACTIVE. Bản gốc báo mục đang xem chỉ bằng `bg-accent` —\n * #FBF5DC trên nền trắng là 1,09:1 (dark: 1,17:1), không đủ làm chỉ báo (WCAG 1.4.11 đòi\n * 3:1 cho thành phần phi văn bản mang nghĩa) — cộng đổi màu chữ, mà màu không được là tín\n * hiệu DUY NHẤT (1.4.1). Vạch 2px `border-primary-ink` là đúng khuôn `Tabs` biến thể\n * `default` (tabs-variants.ts): bậc `-ink` vì nó đi lên nền TRANG, và bảng tương phản cao\n * chỉ đổi được nó qua token này. Đặt ở cạnh ĐẦU (dọc) hoặc cạnh DƯỚI (ngang) theo hướng\n * xếp link; `border-s`/`rounded-e` là thuộc tính logic nên đúng cả RTL.\n */\n \"border-transparent data-[state=active]:border-primary-ink\",\n orientation === \"horizontal\" ? \"rounded-e-md border-s-2\" : \"rounded-t-md border-b-2\",\n // Vòng focus cùng khuôn `Tabs` của gói (TABS_TRIGGER_BASE).\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n className\n )}\n onClick={onLinkClick}\n />\n );\n}\n\ninterface ScrollSpyViewportProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction ScrollSpyViewport(props: ScrollSpyViewportProps) {\n const { asChild, className, ...viewportProps } = props;\n\n const { dir, orientation } = useScrollSpyContext(VIEWPORT_NAME);\n\n const ViewportPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ViewportPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy-viewport\"\n dir={dir}\n {...viewportProps}\n className={cn(\"flex flex-1 flex-col gap-8\", className)}\n />\n );\n}\n\ninterface ScrollSpySectionProps extends React.ComponentProps<\"div\"> {\n value: string;\n asChild?: boolean;\n}\n\nfunction ScrollSpySection(props: ScrollSpySectionProps) {\n const { asChild, ref, value, ...sectionProps } = props;\n\n const { orientation, onSectionRegister, onSectionUnregister } = useScrollSpyContext(SECTION_NAME);\n const sectionRef = React.useRef<SectionElement>(null);\n const composedRef = useComposedRefs(ref, sectionRef);\n\n useIsomorphicLayoutEffect(() => {\n const element = sectionRef.current;\n if (!element || !value) return;\n\n onSectionRegister(value, element);\n\n return () => {\n onSectionUnregister(value);\n };\n }, [value, onSectionRegister, onSectionUnregister]);\n\n const SectionPrimitive = asChild ? Slot : \"div\";\n\n return (\n <SectionPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy-section\"\n {...sectionProps}\n id={value}\n ref={composedRef}\n />\n );\n}\n\nexport {\n ScrollSpy,\n ScrollSpyLink,\n type ScrollSpyLinkProps,\n ScrollSpyNav,\n type ScrollSpyNavProps,\n type ScrollSpyProps,\n ScrollSpySection,\n type ScrollSpySectionProps,\n ScrollSpyViewport,\n type ScrollSpyViewportProps\n};\n"],"mappings":"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","names":["value"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/scroll-spy.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/scroll-spy) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n *\n * Dùng ở Comitor: trang cài đặt dài và trang chi tiết bản ghi (các mục của một liên hệ CRM) có\n * mục lục điều hướng trong trang.\n *\n * ⚠ `orientation` giữ NGHĨA của thượng nguồn — nó nói về hướng xếp của KHUNG, không phải của danh\n * sách link: `horizontal` (mặc định) = mục lục đứng bên cạnh nội dung, link xếp DỌC; `vertical` =\n * mục lục nằm trên nội dung, link xếp NGANG.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\n\nconst ROOT_NAME = \"ScrollSpy\";\nconst NAV_NAME = \"ScrollSpyNav\";\nconst LINK_NAME = \"ScrollSpyLink\";\nconst VIEWPORT_NAME = \"ScrollSpyViewport\";\nconst SECTION_NAME = \"ScrollSpySection\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\n\ntype LinkElement = React.ComponentRef<typeof ScrollSpyLink>;\ntype SectionElement = React.ComponentRef<typeof ScrollSpySection>;\n\nfunction getDefaultScrollBehavior(): ScrollBehavior {\n if (typeof window === \"undefined\") return \"smooth\";\n return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches ? \"auto\" : \"smooth\";\n}\n\ninterface StoreState {\n value: string;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useStore\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ScrollSpyContextValue {\n offset: number;\n scrollBehavior: ScrollBehavior | undefined;\n dir: Direction;\n orientation: Orientation;\n scrollContainer: HTMLElement | null;\n isScrollingRef: React.RefObject<boolean>;\n onSectionRegister: (id: string, element: SectionElement) => void;\n onSectionUnregister: (id: string) => void;\n onScrollToSection: (sectionId: string) => void;\n}\n\nconst ScrollSpyContext = React.createContext<ScrollSpyContextValue | null>(null);\n\nfunction useScrollSpyContext(consumerName: string) {\n const context = React.useContext(ScrollSpyContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface ScrollSpyProps extends React.ComponentProps<\"div\"> {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n rootMargin?: string;\n threshold?: number | number[];\n offset?: number;\n /**\n * Kiểu cuộn khi bấm link. Không truyền thì `\"smooth\"`, hoặc `\"auto\"` nếu người dùng bật giảm\n * chuyển động (`prefers-reduced-motion`).\n */\n scrollBehavior?: ScrollBehavior;\n scrollContainer?: HTMLElement | null;\n dir?: Direction;\n orientation?: Orientation;\n asChild?: boolean;\n}\n\nfunction ScrollSpy(props: ScrollSpyProps) {\n const {\n value,\n defaultValue,\n onValueChange,\n rootMargin,\n threshold = 0.1,\n offset = 0,\n /*\n * ⚠ Khác thượng nguồn: bản gốc tính mặc định NGAY TRONG render (`scrollBehavior =\n * getDefaultScrollBehavior()` → `window.matchMedia` mỗi lượt render). Ở đây để `undefined` và\n * chỉ phân giải lúc cuộn thật — không chạm `window` khi render, và đọc đúng thiết lập giảm\n * chuyển động TẠI THỜI ĐIỂM bấm chứ không phải lúc render gần nhất.\n */\n scrollBehavior,\n scrollContainer = null,\n dir: dirProp,\n orientation = \"horizontal\",\n asChild,\n className,\n ...rootProps\n } = props;\n\n const dir = useDirection(dirProp);\n\n const stateRef = useLazyRef<StoreState>(() => ({\n value: value ?? defaultValue ?? \"\"\n }));\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const onValueChangeRef = useAsRef(onValueChange);\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => {\n return stateRef.current;\n },\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n stateRef.current[key] = value;\n\n if (key === \"value\" && value) {\n onValueChangeRef.current?.(value);\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, onValueChangeRef]);\n\n const sectionMapRef = React.useRef(new Map<string, Element>());\n const isScrollingRef = React.useRef(false);\n const rafIdRef = React.useRef<number | null>(null);\n const isMountedRef = React.useRef(false);\n const scrollTimeoutRef = React.useRef<number | null>(null);\n\n const onSectionRegister = React.useCallback((id: string, element: SectionElement) => {\n sectionMapRef.current.set(id, element);\n }, []);\n\n const onSectionUnregister = React.useCallback((id: string) => {\n sectionMapRef.current.delete(id);\n }, []);\n\n const onScrollToSection = React.useCallback(\n (sectionId: string) => {\n /*\n * ⚠ Khác thượng nguồn: `CSS.escape` quanh id. Bản gốc ghép thẳng `#${sectionId}` vào\n * `querySelector`, nên id bắt đầu bằng chữ số (\"2024-hop-dong\") hay chứa ký tự đặc biệt\n * (id của `useId()` như \":r1:\") làm nó THROW `SyntaxError` ngay khi bấm link.\n */\n const section = scrollContainer\n ? scrollContainer.querySelector(`#${CSS.escape(sectionId)}`)\n : document.getElementById(sectionId);\n\n if (!section) {\n store.setState(\"value\", sectionId);\n return;\n }\n\n // Bật cờ để observer không ghi đè giá trị trong lúc đang cuộn theo lệnh.\n isScrollingRef.current = true;\n store.setState(\"value\", sectionId);\n\n const behavior = scrollBehavior ?? getDefaultScrollBehavior();\n\n if (scrollContainer) {\n const containerRect = scrollContainer.getBoundingClientRect();\n const sectionRect = section.getBoundingClientRect();\n const scrollTop = scrollContainer.scrollTop;\n const offsetPosition = sectionRect.top - containerRect.top + scrollTop - offset;\n\n scrollContainer.scrollTo({\n top: offsetPosition,\n behavior\n });\n } else {\n const sectionPosition = section.getBoundingClientRect().top;\n const offsetPosition = sectionPosition + window.scrollY - offset;\n\n window.scrollTo({\n top: offsetPosition,\n behavior\n });\n }\n\n if (scrollTimeoutRef.current !== null) {\n clearTimeout(scrollTimeoutRef.current);\n }\n\n scrollTimeoutRef.current = window.setTimeout(() => {\n isScrollingRef.current = false;\n }, 500);\n },\n [scrollContainer, offset, scrollBehavior, store]\n );\n\n /*\n * ⚠ Khác thượng nguồn: chỉ cuộn theo lệnh khi `value` đổi TỪ BÊN NGOÀI. Bản gốc cuộn mỗi khi\n * effect chạy lại sau lần đầu, với hai hệ quả đã đo trên trình duyệt:\n * · StrictMode chạy effect hai lần lúc mount → lần hai cuộn luôn CẢ TRANG tới mục (scrollY\n * 0 → ~5050) vì `scrollContainer` của app lúc đó còn `null`. Next dev bật StrictMode sẵn.\n * · Chế độ controlled tự kéo giật: observer đặt giá trị → `onValueChange` → app trả về prop\n * `value` → effect cuộn tới ĐẦU mục đó, ngay giữa lúc người dùng đang cuộn.\n * Luật mới: lúc mount không cuộn (đúng ý nhánh mount của bản gốc); về sau, `value` trùng với giá\n * trị store đang giữ nghĩa là nó chỉ là tiếng vọng của chính observer/link → không cuộn.\n * `defaultValue` đã vào store từ lúc khởi tạo (`stateRef`), không cần đồng bộ thêm.\n */\n const prevValuePropRef = React.useRef<string | undefined>(undefined);\n const onScrollToSectionRef = useAsRef(onScrollToSection);\n\n useIsomorphicLayoutEffect(() => {\n const previous = prevValuePropRef.current;\n prevValuePropRef.current = value;\n if (value === undefined || value === previous) return;\n\n if (previous === undefined || !isMountedRef.current) {\n isMountedRef.current = true;\n store.setState(\"value\", value);\n return;\n }\n\n if (value === store.getState().value) return;\n onScrollToSectionRef.current(value);\n }, [value, store, onScrollToSectionRef]);\n\n useIsomorphicLayoutEffect(() => {\n const sectionMap = sectionMapRef.current;\n if (sectionMap.size === 0) return;\n\n const observerRootMargin = rootMargin ?? `${-offset}px 0px -70% 0px`;\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (isScrollingRef.current) return;\n\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n\n rafIdRef.current = requestAnimationFrame(() => {\n const intersecting = entries.filter((entry) => entry.isIntersecting);\n\n if (intersecting.length === 0) return;\n\n const topmost = intersecting.reduce((prev, curr) => {\n return curr.boundingClientRect.top < prev.boundingClientRect.top ? curr : prev;\n });\n\n const id = topmost.target.id;\n if (id && sectionMap.has(id)) {\n store.setState(\"value\", id);\n }\n });\n },\n {\n root: scrollContainer,\n rootMargin: observerRootMargin,\n threshold\n }\n );\n\n for (const element of sectionMap.values()) {\n observer.observe(element);\n }\n\n return () => {\n observer.disconnect();\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n if (scrollTimeoutRef.current !== null) {\n clearTimeout(scrollTimeoutRef.current);\n }\n };\n }, [offset, rootMargin, threshold, scrollContainer]);\n\n const contextValue = React.useMemo<ScrollSpyContextValue>(\n () => ({\n dir,\n orientation,\n offset,\n scrollBehavior,\n scrollContainer,\n isScrollingRef,\n onSectionRegister,\n onSectionUnregister,\n onScrollToSection\n }),\n [\n dir,\n orientation,\n offset,\n scrollBehavior,\n scrollContainer,\n onSectionRegister,\n onSectionUnregister,\n onScrollToSection\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <StoreContext.Provider value={store}>\n <ScrollSpyContext.Provider value={contextValue}>\n <RootPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy\"\n dir={dir}\n {...rootProps}\n className={cn(\"flex\", orientation === \"horizontal\" ? \"flex-row\" : \"flex-col\", className)}\n />\n </ScrollSpyContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface ScrollSpyNavProps extends React.ComponentProps<\"nav\"> {\n asChild?: boolean;\n}\n\nfunction ScrollSpyNav(props: ScrollSpyNavProps) {\n const { asChild, className, ...navProps } = props;\n\n const { dir, orientation } = useScrollSpyContext(NAV_NAME);\n const t = useComitorMessages(\"scrollSpy\");\n\n const NavPrimitive = asChild ? Slot : \"nav\";\n\n return (\n <NavPrimitive\n /*\n * ⚠ Khác thượng nguồn: `<nav>` có tên mặc định (khoá `navLabel` của namespace `scrollSpy`,\n * theo `ComitorLocaleProvider`). Trong khung app Comitor đã có ít nhất MỘT landmark\n * `navigation` (sidebar), nên một `<nav>` thứ hai không tên là hai landmark trùng loại không\n * phân biệt được trong danh sách landmark của trình đọc màn hình. Ghi đè bằng chính prop\n * `aria-label` (cùng khuôn `Breadcrumb`/`Pagination` của gói).\n */\n aria-label={t.navLabel}\n data-orientation={orientation}\n data-slot=\"scroll-spy-nav\"\n dir={dir}\n {...navProps}\n className={cn(\"flex gap-2\", orientation === \"horizontal\" ? \"flex-col\" : \"flex-row\", className)}\n />\n );\n}\n\ninterface ScrollSpyLinkProps extends React.ComponentProps<\"a\"> {\n value: string;\n asChild?: boolean;\n}\n\nfunction ScrollSpyLink(props: ScrollSpyLinkProps) {\n const { value: linkValue, asChild, onClick, className, ...linkProps } = props;\n\n const { orientation, onScrollToSection } = useScrollSpyContext(LINK_NAME);\n const value = useStore((state) => state.value);\n const isActive = value === linkValue;\n\n const onLinkClick = React.useCallback(\n (event: React.MouseEvent<LinkElement>) => {\n event.preventDefault();\n onClick?.(event);\n onScrollToSection(linkValue);\n },\n [linkValue, onClick, onScrollToSection]\n );\n\n const LinkPrimitive = asChild ? Slot : \"a\";\n\n return (\n <LinkPrimitive\n /*\n * ⚠ Khác thượng nguồn: bản gốc chỉ đặt `data-state=\"active\"` — thuộc tính mà trình đọc màn\n * hình KHÔNG đọc, nên mục đang xem chỉ được báo bằng hình. `aria-current=\"location\"` là giá\n * trị ARIA dành cho \"vị trí hiện tại\" trong một ngữ cảnh — ở đây là mục đang xem của trang.\n * Không dùng `\"page\"`: link trỏ tới một mục TRONG trang, không phải tới một trang khác.\n */\n aria-current={isActive ? \"location\" : undefined}\n data-orientation={orientation}\n data-slot=\"scroll-spy-link\"\n data-state={isActive ? \"active\" : \"inactive\"}\n {...linkProps}\n href={asChild ? undefined : `#${linkValue}`}\n className={cn(\n \"px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground data-[state=active]:bg-accent data-[state=active]:text-foreground\",\n /*\n * ⚠ Khác thượng nguồn: thêm VẠCH ACTIVE. Bản gốc báo mục đang xem chỉ bằng `bg-accent` —\n * #FBF5DC trên nền trắng là 1,09:1 (dark: 1,17:1), không đủ làm chỉ báo (WCAG 1.4.11 đòi\n * 3:1 cho thành phần phi văn bản mang nghĩa) — cộng đổi màu chữ, mà màu không được là tín\n * hiệu DUY NHẤT (1.4.1). Vạch 2px `border-primary-ink` là đúng khuôn `Tabs` biến thể\n * `default` (tabs-variants.ts): bậc `-ink` vì nó đi lên nền TRANG, và bảng tương phản cao\n * chỉ đổi được nó qua token này. Đặt ở cạnh ĐẦU (dọc) hoặc cạnh DƯỚI (ngang) theo hướng\n * xếp link; `border-s`/`rounded-e` là thuộc tính logic nên đúng cả RTL.\n */\n \"border-transparent data-[state=active]:border-primary-ink\",\n orientation === \"horizontal\" ? \"rounded-e-md border-s-2\" : \"rounded-t-md border-b-2\",\n // Vòng focus cùng khuôn `Tabs` của gói (TABS_TRIGGER_BASE).\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n className\n )}\n onClick={onLinkClick}\n />\n );\n}\n\ninterface ScrollSpyViewportProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction ScrollSpyViewport(props: ScrollSpyViewportProps) {\n const { asChild, className, ...viewportProps } = props;\n\n const { dir, orientation } = useScrollSpyContext(VIEWPORT_NAME);\n\n const ViewportPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ViewportPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy-viewport\"\n dir={dir}\n {...viewportProps}\n className={cn(\"flex flex-1 flex-col gap-8\", className)}\n />\n );\n}\n\ninterface ScrollSpySectionProps extends React.ComponentProps<\"div\"> {\n value: string;\n asChild?: boolean;\n}\n\nfunction ScrollSpySection(props: ScrollSpySectionProps) {\n const { asChild, ref, value, ...sectionProps } = props;\n\n const { orientation, onSectionRegister, onSectionUnregister } = useScrollSpyContext(SECTION_NAME);\n const sectionRef = React.useRef<SectionElement>(null);\n const composedRef = useComposedRefs(ref, sectionRef);\n\n useIsomorphicLayoutEffect(() => {\n const element = sectionRef.current;\n if (!element || !value) return;\n\n onSectionRegister(value, element);\n\n return () => {\n onSectionUnregister(value);\n };\n }, [value, onSectionRegister, onSectionUnregister]);\n\n const SectionPrimitive = asChild ? Slot : \"div\";\n\n return (\n <SectionPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy-section\"\n {...sectionProps}\n id={value}\n ref={composedRef}\n />\n );\n}\n\nexport {\n ScrollSpy,\n ScrollSpyLink,\n type ScrollSpyLinkProps,\n ScrollSpyNav,\n type ScrollSpyNavProps,\n type ScrollSpyProps,\n ScrollSpySection,\n type ScrollSpySectionProps,\n ScrollSpyViewport,\n type ScrollSpyViewportProps\n};\n"],"mappings":"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","names":["value"]}
|
|
@@ -22,10 +22,10 @@ type SheetSide = "top" | "right" | "bottom" | "left";
|
|
|
22
22
|
type SheetContentProps = React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
23
23
|
/** Cạnh màn hình mà panel trượt ra. Mặc định "right". */
|
|
24
24
|
side?: SheetSide;
|
|
25
|
-
/** Nhãn cho trình đọc màn hình của nút đóng. Mặc định
|
|
25
|
+
/** Nhãn cho trình đọc màn hình của nút đóng. Mặc định: khoá `closeLabel` của namespace `sheet`. */
|
|
26
26
|
closeLabel?: string;
|
|
27
27
|
};
|
|
28
|
-
declare function SheetContent({ className, children, side, closeLabel, ...props }: SheetContentProps): React.JSX.Element;
|
|
28
|
+
declare function SheetContent({ className, children, side, closeLabel: closeLabelProp, ...props }: SheetContentProps): React.JSX.Element;
|
|
29
29
|
type SheetHeaderProps = React.ComponentProps<"div">;
|
|
30
30
|
declare function SheetHeader({ className, ...props }: SheetHeaderProps): React.JSX.Element;
|
|
31
31
|
type SheetFooterProps = React.ComponentProps<"div">;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
4
4
|
import { XIcon } from "lucide-react";
|
|
5
|
+
import { useComitorMessages } from "../../i18n/locale-provider.js";
|
|
5
6
|
import { cn } from "../../lib/cn.js";
|
|
6
7
|
function Sheet({ ...props }) {
|
|
7
8
|
return /* @__PURE__ */ jsx(SheetPrimitive.Root, { "data-slot": "sheet", ...props });
|
|
@@ -28,7 +29,15 @@ function SheetOverlay({ className, ...props }) {
|
|
|
28
29
|
}
|
|
29
30
|
);
|
|
30
31
|
}
|
|
31
|
-
function SheetContent({
|
|
32
|
+
function SheetContent({
|
|
33
|
+
className,
|
|
34
|
+
children,
|
|
35
|
+
side = "right",
|
|
36
|
+
closeLabel: closeLabelProp,
|
|
37
|
+
...props
|
|
38
|
+
}) {
|
|
39
|
+
const t = useComitorMessages("sheet");
|
|
40
|
+
const closeLabel = closeLabelProp ?? t.closeLabel;
|
|
32
41
|
return /* @__PURE__ */ jsxs(SheetPortal, { children: [
|
|
33
42
|
/* @__PURE__ */ jsx(SheetOverlay, {}),
|
|
34
43
|
/* @__PURE__ */ jsxs(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Sheet — panel trượt từ cạnh màn hình (dựng trên Radix Dialog).\n *\n * Dùng cho menu di động, bộ lọc, panel chi tiết. Shell Tier 3 dùng `side=\"left\"`\n * cho MobileMenu. Với panel kéo-thả kiểu mobile (có tay cầm, kéo để đóng) thì\n * dùng `Drawer` (vaul) thay vì Sheet.\n */\n\nexport type SheetProps = React.ComponentProps<typeof SheetPrimitive.Root>;\n\nfunction Sheet({ ...props }: SheetProps) {\n return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nexport type SheetTriggerProps = React.ComponentProps<typeof SheetPrimitive.Trigger>;\n\nfunction SheetTrigger({ ...props }: SheetTriggerProps) {\n return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nexport type SheetCloseProps = React.ComponentProps<typeof SheetPrimitive.Close>;\n\nfunction SheetClose({ ...props }: SheetCloseProps) {\n return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nexport type SheetPortalProps = React.ComponentProps<typeof SheetPrimitive.Portal>;\n\nfunction SheetPortal({ ...props }: SheetPortalProps) {\n return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nexport type SheetOverlayProps = React.ComponentProps<typeof SheetPrimitive.Overlay>;\n\nfunction SheetOverlay({ className, ...props }: SheetOverlayProps) {\n return (\n <SheetPrimitive.Overlay\n data-slot=\"sheet-overlay\"\n className={cn(\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-ink/60 dark:bg-ink/80 backdrop-blur-sm\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type SheetSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nexport type SheetContentProps = React.ComponentProps<typeof SheetPrimitive.Content> & {\n /** Cạnh màn hình mà panel trượt ra. Mặc định \"right\". */\n side?: SheetSide;\n /** Nhãn cho trình đọc màn hình của nút đóng. Mặc định
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Sheet — panel trượt từ cạnh màn hình (dựng trên Radix Dialog).\n *\n * Dùng cho menu di động, bộ lọc, panel chi tiết. Shell Tier 3 dùng `side=\"left\"`\n * cho MobileMenu. Với panel kéo-thả kiểu mobile (có tay cầm, kéo để đóng) thì\n * dùng `Drawer` (vaul) thay vì Sheet.\n */\n\nexport type SheetProps = React.ComponentProps<typeof SheetPrimitive.Root>;\n\nfunction Sheet({ ...props }: SheetProps) {\n return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nexport type SheetTriggerProps = React.ComponentProps<typeof SheetPrimitive.Trigger>;\n\nfunction SheetTrigger({ ...props }: SheetTriggerProps) {\n return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nexport type SheetCloseProps = React.ComponentProps<typeof SheetPrimitive.Close>;\n\nfunction SheetClose({ ...props }: SheetCloseProps) {\n return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nexport type SheetPortalProps = React.ComponentProps<typeof SheetPrimitive.Portal>;\n\nfunction SheetPortal({ ...props }: SheetPortalProps) {\n return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nexport type SheetOverlayProps = React.ComponentProps<typeof SheetPrimitive.Overlay>;\n\nfunction SheetOverlay({ className, ...props }: SheetOverlayProps) {\n return (\n <SheetPrimitive.Overlay\n data-slot=\"sheet-overlay\"\n className={cn(\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-ink/60 dark:bg-ink/80 backdrop-blur-sm\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type SheetSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nexport type SheetContentProps = React.ComponentProps<typeof SheetPrimitive.Content> & {\n /** Cạnh màn hình mà panel trượt ra. Mặc định \"right\". */\n side?: SheetSide;\n /** Nhãn cho trình đọc màn hình của nút đóng. Mặc định: khoá `closeLabel` của namespace `sheet`. */\n closeLabel?: string;\n};\n\nfunction SheetContent({\n className,\n children,\n side = \"right\",\n closeLabel: closeLabelProp,\n ...props\n}: SheetContentProps) {\n const t = useComitorMessages(\"sheet\");\n const closeLabel = closeLabelProp ?? t.closeLabel;\n return (\n <SheetPortal>\n <SheetOverlay />\n <SheetPrimitive.Content\n data-slot=\"sheet-content\"\n className={cn(\n /*\n * `overflow-y-auto`: cùng lỗi với `DialogContent` trước 1.3.2 — `position: fixed`,\n * `h-full`, mà không có `overflow` nào, nên nội dung dài hơn khung nhìn bị CẮT và\n * không cuộn được. `SheetFooter` mang `mt-auto` nên nó vẫn dính đáy khi nội dung ngắn.\n */\n \"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 overflow-y-auto shadow-overlay transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500\",\n side === \"right\" &&\n \"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm\",\n side === \"left\" &&\n \"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm\",\n side === \"top\" &&\n \"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b\",\n side === \"bottom\" &&\n \"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t\",\n className\n )}\n {...props}\n >\n {children}\n <SheetPrimitive.Close className=\"absolute top-4 right-4 rounded-md p-1 text-muted-foreground transition-colors duration-150 ease-out hover:text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none\">\n <XIcon className=\"size-4\" />\n <span className=\"sr-only\">{closeLabel}</span>\n </SheetPrimitive.Close>\n </SheetPrimitive.Content>\n </SheetPortal>\n );\n}\n\nexport type SheetHeaderProps = React.ComponentProps<\"div\">;\n\nfunction SheetHeader({ className, ...props }: SheetHeaderProps) {\n return <div data-slot=\"sheet-header\" className={cn(\"flex flex-col gap-1.5 p-4\", className)} {...props} />;\n}\n\nexport type SheetFooterProps = React.ComponentProps<\"div\">;\n\nfunction SheetFooter({ className, ...props }: SheetFooterProps) {\n return <div data-slot=\"sheet-footer\" className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)} {...props} />;\n}\n\nexport type SheetTitleProps = React.ComponentProps<typeof SheetPrimitive.Title>;\n\nfunction SheetTitle({ className, ...props }: SheetTitleProps) {\n return (\n <SheetPrimitive.Title\n data-slot=\"sheet-title\"\n className={cn(\"text-foreground font-semibold\", className)}\n {...props}\n />\n );\n}\n\nexport type SheetDescriptionProps = React.ComponentProps<typeof SheetPrimitive.Description>;\n\nfunction SheetDescription({ className, ...props }: SheetDescriptionProps) {\n return (\n <SheetPrimitive.Description\n data-slot=\"sheet-description\"\n className={cn(\"text-muted-foreground text-sm\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Sheet,\n SheetClose,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetOverlay,\n SheetPortal,\n SheetTitle,\n SheetTrigger\n};\n"],"mappings":";AAmBS,cA+ED,YA/EC;AAjBT,YAAY,oBAAoB;AAChC,SAAS,aAAa;AAEtB,SAAS,0BAA0B;AACnC,SAAS,UAAU;AAYnB,SAAS,MAAM,EAAE,GAAG,MAAM,GAAe;AACvC,SAAO,oBAAC,eAAe,MAAf,EAAoB,aAAU,SAAS,GAAG,OAAO;AAC3D;AAIA,SAAS,aAAa,EAAE,GAAG,MAAM,GAAsB;AACrD,SAAO,oBAAC,eAAe,SAAf,EAAuB,aAAU,iBAAiB,GAAG,OAAO;AACtE;AAIA,SAAS,WAAW,EAAE,GAAG,MAAM,GAAoB;AACjD,SAAO,oBAAC,eAAe,OAAf,EAAqB,aAAU,eAAe,GAAG,OAAO;AAClE;AAIA,SAAS,YAAY,EAAE,GAAG,MAAM,GAAqB;AACnD,SAAO,oBAAC,eAAe,QAAf,EAAsB,aAAU,gBAAgB,GAAG,OAAO;AACpE;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAWA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,GAAG;AACL,GAAsB;AACpB,QAAM,IAAI,mBAAmB,OAAO;AACpC,QAAM,aAAa,kBAAkB,EAAE;AACvC,SACE,qBAAC,eACC;AAAA,wBAAC,gBAAa;AAAA,IACd;AAAA,MAAC,eAAe;AAAA,MAAf;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMT;AAAA,UACA,SAAS,WACP;AAAA,UACF,SAAS,UACP;AAAA,UACF,SAAS,SACP;AAAA,UACF,SAAS,YACP;AAAA,UACF;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEH;AAAA;AAAA,UACD,qBAAC,eAAe,OAAf,EAAqB,WAAU,qTAC9B;AAAA,gCAAC,SAAM,WAAU,UAAS;AAAA,YAC1B,oBAAC,UAAK,WAAU,WAAW,sBAAW;AAAA,aACxC;AAAA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SAAO,oBAAC,SAAI,aAAU,gBAAe,WAAW,GAAG,6BAA6B,SAAS,GAAI,GAAG,OAAO;AACzG;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SAAO,oBAAC,SAAI,aAAU,gBAAe,WAAW,GAAG,mCAAmC,SAAS,GAAI,GAAG,OAAO;AAC/G;AAIA,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAAoB;AAC5D,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,iBAAiB,EAAE,WAAW,GAAG,MAAM,GAA0B;AACxE,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx } from "react/jsx-runtime";
|
|
2
3
|
import { Loader2Icon } from "lucide-react";
|
|
4
|
+
import { useComitorMessages } from "../../i18n/locale-provider.js";
|
|
3
5
|
import { cn } from "../../lib/cn.js";
|
|
4
6
|
function Spinner({ className, ...props }) {
|
|
5
|
-
|
|
7
|
+
const t = useComitorMessages("spinner");
|
|
8
|
+
return /* @__PURE__ */ jsx(Loader2Icon, { role: "status", "aria-label": t.label, className: cn("size-4 animate-spin", className), ...props });
|
|
6
9
|
}
|
|
7
10
|
export {
|
|
8
11
|
Spinner
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/spinner.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/spinner.tsx"],"sourcesContent":["\"use client\";\n\nimport { Loader2Icon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\n\nexport type SpinnerProps = React.ComponentProps<\"svg\">;\n\n/*\n * `\"use client\"` từ `3.0.0`: tên cho trình đọc màn hình theo `ComitorLocaleProvider` (namespace\n * `spinner`), mà context chỉ đọc được ở client. Trước đó file này tĩnh và ghim cứng \"Đang tải\".\n * `aria-label` truyền vào vẫn thắng vì `{...props}` trải sau.\n */\nfunction Spinner({ className, ...props }: SpinnerProps) {\n const t = useComitorMessages(\"spinner\");\n return <Loader2Icon role=\"status\" aria-label={t.label} className={cn(\"size-4 animate-spin\", className)} {...props} />;\n}\n\nexport { Spinner };\n"],"mappings":";AAgBS;AAdT,SAAS,mBAAmB;AAE5B,SAAS,0BAA0B;AACnC,SAAS,UAAU;AASnB,SAAS,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAiB;AACtD,QAAM,IAAI,mBAAmB,SAAS;AACtC,SAAO,oBAAC,eAAY,MAAK,UAAS,cAAY,EAAE,OAAO,WAAW,GAAG,uBAAuB,SAAS,GAAI,GAAG,OAAO;AACrH;","names":[]}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* `src/chart/chart-colors.ts`. Barrel xuất module này TRỰC TIẾP; `stepper.tsx` chỉ `import`.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* Chuỗi hiển thị và chuỗi a11y của `Stepper
|
|
9
|
+
* Chuỗi hiển thị và chuỗi a11y của `Stepper`. Theo `ComitorLocaleProvider` (mặc định tiếng Anh,
|
|
10
|
+
* bảng tiếng Việt dựng sẵn); truyền `labels` cho `Stepper` để ghi đè từng khoá.
|
|
10
11
|
*
|
|
11
12
|
* ⚠ Thượng nguồn (Dice UI) KHÔNG có chuỗi nào: ô chỉ báo của bước đã xong chỉ vẽ một dấu tick,
|
|
12
13
|
* còn `StepperPrev`/`StepperNext` là nút rỗng nếu app quên truyền `children`. Cả hai đều là nút
|
|
@@ -23,7 +24,11 @@ interface StepperLabels {
|
|
|
23
24
|
/** Nội dung mặc định của `StepperNext` khi không có `children`. */
|
|
24
25
|
next: string;
|
|
25
26
|
}
|
|
26
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Nhãn TIẾNG ANH mặc định (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:
|
|
29
|
+
* `comitorMessagesVi.stepper`. Component không đọc thẳng hằng này — nó đọc bảng của
|
|
30
|
+
* `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.
|
|
31
|
+
*/
|
|
27
32
|
declare const DEFAULT_STEPPER_LABELS: StepperLabels;
|
|
28
33
|
|
|
29
34
|
export { DEFAULT_STEPPER_LABELS, type StepperLabels };
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
prev: "Quay l\u1EA1i",
|
|
4
|
-
next: "Ti\u1EBFp theo"
|
|
5
|
-
};
|
|
1
|
+
import { stepperEn } from "../../i18n/namespaces/stepper.js";
|
|
2
|
+
const DEFAULT_STEPPER_LABELS = { ...stepperEn };
|
|
6
3
|
export {
|
|
7
4
|
DEFAULT_STEPPER_LABELS
|
|
8
5
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/stepper-constants.ts"],"sourcesContent":["/**\n * Nhãn mặc định của `Stepper`.\n *\n * Module **CỐ Ý không có `\"use client\"`**: mọi export của một file `\"use client\"` tới Server\n * Component là client-reference proxy, đọc ra `undefined` mà KHÔNG throw — xem\n * `src/chart/chart-colors.ts`. Barrel xuất module này TRỰC TIẾP; `stepper.tsx` chỉ `import`.\n */\n\n/**\n * Chuỗi hiển thị và chuỗi a11y của `Stepper`
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/stepper-constants.ts"],"sourcesContent":["/**\n * Nhãn mặc định của `Stepper`.\n *\n * Module **CỐ Ý không có `\"use client\"`**: mọi export của một file `\"use client\"` tới Server\n * Component là client-reference proxy, đọc ra `undefined` mà KHÔNG throw — xem\n * `src/chart/chart-colors.ts`. Barrel xuất module này TRỰC TIẾP; `stepper.tsx` chỉ `import`.\n */\n\nimport { stepperEn } from \"../../i18n/namespaces/stepper.js\";\n\n/**\n * Chuỗi hiển thị và chuỗi a11y của `Stepper`. Theo `ComitorLocaleProvider` (mặc định tiếng Anh,\n * bảng tiếng Việt dựng sẵn); truyền `labels` cho `Stepper` để ghi đè từng khoá.\n *\n * ⚠ Thượng nguồn (Dice UI) KHÔNG có chuỗi nào: ô chỉ báo của bước đã xong chỉ vẽ một dấu tick,\n * còn `StepperPrev`/`StepperNext` là nút rỗng nếu app quên truyền `children`. Cả hai đều là nút\n * KHÔNG TÊN với trình đọc màn hình — ba khoá dưới đây lấp đúng ba chỗ đó.\n */\nexport interface StepperLabels {\n /**\n * Chữ `sr-only` thay cho con số trong ô chỉ báo khi bước ĐÃ XONG (lúc đó ô chỉ vẽ dấu tick).\n * Là HÀM vì có số chèn vào giữa câu.\n */\n completedStep: (position: number) => string;\n /** Nội dung mặc định của `StepperPrev` khi không có `children`. */\n prev: string;\n /** Nội dung mặc định của `StepperNext` khi không có `children`. */\n next: string;\n}\n\n/**\n * Nhãn TIẾNG ANH mặc định (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:\n * `comitorMessagesVi.stepper`. Component không đọc thẳng hằng này — nó đọc bảng của\n * `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.\n */\nexport const DEFAULT_STEPPER_LABELS: StepperLabels = { ...stepperEn };\n"],"mappings":"AAQA,SAAS,iBAAiB;AA2BnB,MAAM,yBAAwC,EAAE,GAAG,UAAU;","names":[]}
|
|
@@ -37,7 +37,8 @@ interface StepperProps extends DivProps {
|
|
|
37
37
|
loop?: boolean;
|
|
38
38
|
nonInteractive?: boolean;
|
|
39
39
|
/**
|
|
40
|
-
* Chuỗi hiển thị/a11y — ghi đè từng
|
|
40
|
+
* Chuỗi hiển thị/a11y — ghi đè từng khoá. Không truyền thì theo namespace `stepper` của
|
|
41
|
+
* `ComitorLocaleProvider` (mặc định tiếng Anh).
|
|
41
42
|
*
|
|
42
43
|
* ⚠ Thêm so với thượng nguồn: bản gốc không có chuỗi nào, và chính vì thế có hai chỗ ra nút
|
|
43
44
|
* KHÔNG TÊN (ô chỉ báo bước đã xong, `StepperPrev`/`StepperNext` thiếu `children`).
|
|
@@ -4,12 +4,12 @@ import { useDirection } from "@radix-ui/react-direction";
|
|
|
4
4
|
import { Slot } from "@radix-ui/react-slot";
|
|
5
5
|
import { Check } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
|
+
import { useComitorLabels } from "../../i18n/locale-provider.js";
|
|
7
8
|
import { cn } from "../../lib/cn.js";
|
|
8
9
|
import { useComposedRefs } from "../../lib/compose-refs.js";
|
|
9
10
|
import { useAsRef } from "../../lib/use-as-ref.js";
|
|
10
11
|
import { useIsomorphicLayoutEffect } from "../../lib/use-isomorphic-layout-effect.js";
|
|
11
12
|
import { useLazyRef } from "../../lib/use-lazy-ref.js";
|
|
12
|
-
import { DEFAULT_STEPPER_LABELS } from "./stepper-constants.js";
|
|
13
13
|
const ROOT_NAME = "Stepper";
|
|
14
14
|
const LIST_NAME = "StepperList";
|
|
15
15
|
const ITEM_NAME = "StepperItem";
|
|
@@ -209,7 +209,7 @@ function Stepper(props) {
|
|
|
209
209
|
const dir = useDirection(dirProp);
|
|
210
210
|
const instanceId = React.useId();
|
|
211
211
|
const rootId = id ?? instanceId;
|
|
212
|
-
const labels =
|
|
212
|
+
const labels = useComitorLabels("stepper", labelsProp);
|
|
213
213
|
const contextValue = React.useMemo(
|
|
214
214
|
() => ({
|
|
215
215
|
rootId,
|