@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
|
@@ -19,7 +19,7 @@ interface ComboboxSharedProps {
|
|
|
19
19
|
searchPlaceholder?: string;
|
|
20
20
|
emptyText?: string;
|
|
21
21
|
/**
|
|
22
|
-
* Chữ hiện trong panel khi `loading`.
|
|
22
|
+
* Chữ hiện trong panel khi `loading`.
|
|
23
23
|
* Là chữ THẤY ĐƯỢC (không phải sr-only) và cả `Combobox` lẫn `MultiCombobox` đều đi qua nó.
|
|
24
24
|
*/
|
|
25
25
|
loadingText?: string;
|
|
@@ -56,11 +56,11 @@ interface ComboboxProps extends ComboboxSharedProps {
|
|
|
56
56
|
value?: string | null;
|
|
57
57
|
onValueChange: (value: string | null) => void;
|
|
58
58
|
/**
|
|
59
|
-
* Nhãn `sr-only` của nút xóa (
|
|
59
|
+
* Nhãn `sr-only` của nút xóa (khoá `clearLabel` của namespace `combobox`).
|
|
60
60
|
*
|
|
61
|
-
* Nút xóa chỉ có mỗi icon `X`, nên chuỗi này CHÍNH LÀ tên của nút với trình đọc màn hình.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
61
|
+
* Nút xóa chỉ có mỗi icon `X`, nên chuỗi này CHÍNH LÀ tên của nút với trình đọc màn hình. Sai
|
|
62
|
+
* ngôn ngữ ở đây là lỗi không ảnh chụp nào thấy và cũng không có gì báo đỏ: giao diện nhìn thì
|
|
63
|
+
* đúng, mà ĐỌC ra lại là thứ tiếng khác.
|
|
64
64
|
*
|
|
65
65
|
* Nằm ở đây chứ không ở `ComboboxSharedProps` vì `MultiCombobox` xóa cả danh sách nên mặc định
|
|
66
66
|
* của nó là một câu khác.
|
|
@@ -76,15 +76,15 @@ interface MultiComboboxProps extends ComboboxSharedProps {
|
|
|
76
76
|
maxSelected?: number;
|
|
77
77
|
/**
|
|
78
78
|
* Dòng đếm ở đáy panel khi có `maxSelected`. HÀM vì nó chèn HAI số — nối tay ở component là
|
|
79
|
-
* khoá cứng trật tự
|
|
79
|
+
* khoá cứng trật tự từ của một ngôn ngữ ("Đã chọn 2/5" ⇄ "2/5 selected") vào mọi bản dịch.
|
|
80
80
|
*/
|
|
81
81
|
selectedCountLabel?: (selected: number, max: number) => string;
|
|
82
82
|
/**
|
|
83
|
-
* Nhãn `sr-only` của nút xóa (
|
|
84
|
-
* không phải bỏ một mục).
|
|
83
|
+
* Nhãn `sr-only` của nút xóa (khoá `clearAllLabel` của namespace `combobox` — nút bỏ SẠCH danh
|
|
84
|
+
* sách đang chọn, không phải bỏ một mục).
|
|
85
85
|
*
|
|
86
86
|
* Cùng lý do như `Combobox`, nhưng gấp hơn một bậc: ở đây `clearable` mặc định là `true`, nên
|
|
87
|
-
*
|
|
87
|
+
* chuỗi này có mặt ở MỌI MultiCombobox mà app không hề bật gì.
|
|
88
88
|
*/
|
|
89
89
|
clearLabel?: string;
|
|
90
90
|
}
|
|
@@ -94,13 +94,13 @@ interface MultiComboboxProps extends ComboboxSharedProps {
|
|
|
94
94
|
* Mặc định tìm KHÔNG DẤU tiếng Việt. Truyền `onSearchChange` để chuyển sang chế độ bất đồng bộ
|
|
95
95
|
* (app tự gọi API và cập nhật `options`).
|
|
96
96
|
*/
|
|
97
|
-
declare function Combobox({ options, value, onValueChange, placeholder, searchPlaceholder, emptyText, loadingText, disabled, onSearchChange, loading, clearable, clearLabel, className, contentClassName, id, name, ...aria }: ComboboxProps): React.JSX.Element;
|
|
97
|
+
declare function Combobox({ options, value, onValueChange, placeholder: placeholderProp, searchPlaceholder: searchPlaceholderProp, emptyText: emptyTextProp, loadingText: loadingTextProp, disabled, onSearchChange, loading, clearable, clearLabel: clearLabelProp, className, contentClassName, id, name, ...aria }: ComboboxProps): React.JSX.Element;
|
|
98
98
|
/**
|
|
99
99
|
* Ô chọn NHIỀU giá trị — cùng hạ tầng với `Combobox`, popover không tự đóng sau mỗi lần chọn.
|
|
100
100
|
*
|
|
101
101
|
* Trigger hiện tối đa `maxDisplay` nhãn rồi gộp phần dư thành "+k": danh sách 12 người thực hiện
|
|
102
102
|
* mà đổ hết ra trigger thì hàng form vỡ bố cục.
|
|
103
103
|
*/
|
|
104
|
-
declare function MultiCombobox({ options, value, onValueChange, placeholder, searchPlaceholder, emptyText, loadingText, disabled, onSearchChange, loading, clearable, clearLabel, selectedCountLabel, maxDisplay, maxSelected, className, contentClassName, id, name, ...aria }: MultiComboboxProps): React.JSX.Element;
|
|
104
|
+
declare function MultiCombobox({ options, value, onValueChange, placeholder: placeholderProp, searchPlaceholder: searchPlaceholderProp, emptyText: emptyTextProp, loadingText: loadingTextProp, disabled, onSearchChange, loading, clearable, clearLabel: clearLabelProp, selectedCountLabel: selectedCountLabelProp, maxDisplay, maxSelected, className, contentClassName, id, name, ...aria }: MultiComboboxProps): React.JSX.Element;
|
|
105
105
|
|
|
106
106
|
export { Combobox, type ComboboxOption, type ComboboxProps, MultiCombobox, type MultiComboboxProps };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Check, ChevronsUpDown, Loader2, X } from "lucide-react";
|
|
4
4
|
import { useMemo, useState } from "react";
|
|
5
|
+
import { useComitorMessages } from "../../i18n/locale-provider.js";
|
|
5
6
|
import { cn } from "../../lib/cn.js";
|
|
6
7
|
import { Badge } from "../ui/badge.js";
|
|
7
8
|
import { Button } from "../ui/button.js";
|
|
@@ -114,21 +115,27 @@ function Combobox({
|
|
|
114
115
|
options,
|
|
115
116
|
value,
|
|
116
117
|
onValueChange,
|
|
117
|
-
placeholder
|
|
118
|
-
searchPlaceholder
|
|
119
|
-
emptyText
|
|
120
|
-
loadingText
|
|
118
|
+
placeholder: placeholderProp,
|
|
119
|
+
searchPlaceholder: searchPlaceholderProp,
|
|
120
|
+
emptyText: emptyTextProp,
|
|
121
|
+
loadingText: loadingTextProp,
|
|
121
122
|
disabled = false,
|
|
122
123
|
onSearchChange,
|
|
123
124
|
loading = false,
|
|
124
125
|
clearable = false,
|
|
125
|
-
clearLabel
|
|
126
|
+
clearLabel: clearLabelProp,
|
|
126
127
|
className,
|
|
127
128
|
contentClassName,
|
|
128
129
|
id,
|
|
129
130
|
name,
|
|
130
131
|
...aria
|
|
131
132
|
}) {
|
|
133
|
+
const t = useComitorMessages("combobox");
|
|
134
|
+
const placeholder = placeholderProp ?? t.placeholder;
|
|
135
|
+
const searchPlaceholder = searchPlaceholderProp ?? t.searchPlaceholder;
|
|
136
|
+
const emptyText = emptyTextProp ?? t.emptyText;
|
|
137
|
+
const loadingText = loadingTextProp ?? t.loadingText;
|
|
138
|
+
const clearLabel = clearLabelProp ?? t.clearLabel;
|
|
132
139
|
const [open, setOpen] = useState(false);
|
|
133
140
|
const [search, setSearch] = useState("");
|
|
134
141
|
const isAsync = onSearchChange !== void 0;
|
|
@@ -207,16 +214,16 @@ function MultiCombobox({
|
|
|
207
214
|
options,
|
|
208
215
|
value,
|
|
209
216
|
onValueChange,
|
|
210
|
-
placeholder
|
|
211
|
-
searchPlaceholder
|
|
212
|
-
emptyText
|
|
213
|
-
loadingText
|
|
217
|
+
placeholder: placeholderProp,
|
|
218
|
+
searchPlaceholder: searchPlaceholderProp,
|
|
219
|
+
emptyText: emptyTextProp,
|
|
220
|
+
loadingText: loadingTextProp,
|
|
214
221
|
disabled = false,
|
|
215
222
|
onSearchChange,
|
|
216
223
|
loading = false,
|
|
217
224
|
clearable = true,
|
|
218
|
-
clearLabel
|
|
219
|
-
selectedCountLabel
|
|
225
|
+
clearLabel: clearLabelProp,
|
|
226
|
+
selectedCountLabel: selectedCountLabelProp,
|
|
220
227
|
maxDisplay = 2,
|
|
221
228
|
maxSelected,
|
|
222
229
|
className,
|
|
@@ -225,6 +232,13 @@ function MultiCombobox({
|
|
|
225
232
|
name,
|
|
226
233
|
...aria
|
|
227
234
|
}) {
|
|
235
|
+
const t = useComitorMessages("combobox");
|
|
236
|
+
const placeholder = placeholderProp ?? t.placeholder;
|
|
237
|
+
const searchPlaceholder = searchPlaceholderProp ?? t.searchPlaceholder;
|
|
238
|
+
const emptyText = emptyTextProp ?? t.emptyText;
|
|
239
|
+
const loadingText = loadingTextProp ?? t.loadingText;
|
|
240
|
+
const clearLabel = clearLabelProp ?? t.clearAllLabel;
|
|
241
|
+
const selectedCountLabel = selectedCountLabelProp ?? t.selectedCountLabel;
|
|
228
242
|
const [open, setOpen] = useState(false);
|
|
229
243
|
const [search, setSearch] = useState("");
|
|
230
244
|
const isAsync = onSearchChange !== void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/composite/combobox.tsx"],"sourcesContent":["\"use client\";\n\nimport { Check, ChevronsUpDown, Loader2, X } from \"lucide-react\";\nimport { type ReactNode, useMemo, useState } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Badge } from \"../ui/badge.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from \"../ui/command.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../ui/popover.js\";\nimport type { IconComponent } from \"./empty-state.js\";\nimport { removeVietnameseTones } from \"./text-utils.js\";\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n /** Dòng phụ mờ dưới nhãn (vd email của người dùng, mã của khách hàng) */\n description?: string;\n icon?: IconComponent;\n disabled?: boolean;\n /** Nhãn nhóm — các mục cùng nhóm được gom lại và có tiêu đề nhóm */\n group?: string;\n /** Từ khoá phụ để tìm ra mục này (vd mã số, tên viết tắt) */\n keywords?: string[];\n}\n\ninterface ComboboxSharedProps {\n options: ComboboxOption[];\n placeholder?: string;\n searchPlaceholder?: string;\n emptyText?: string;\n /**\n * Chữ hiện trong panel khi `loading`. Mặc định tiếng Việt như mọi chuỗi khác của gói.\n * Là chữ THẤY ĐƯỢC (không phải sr-only) và cả `Combobox` lẫn `MultiCombobox` đều đi qua nó.\n */\n loadingText?: string;\n disabled?: boolean;\n /**\n * Có mặt = chế độ BẤT ĐỒNG BỘ: tắt lọc nội bộ của cmdk, app tự lọc `options` (thường là gọi API).\n * Không truyền = lọc tại chỗ, không dấu tiếng Việt.\n */\n onSearchChange?: (search: string) => void;\n /** Đang tải kết quả (chỉ có nghĩa ở chế độ bất đồng bộ) */\n loading?: boolean;\n /** Cho phép xoá lựa chọn bằng nút X trên trigger */\n clearable?: boolean;\n className?: string;\n /** className của panel popover (vd đặt bề rộng cố định) */\n contentClassName?: string;\n id?: string;\n name?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n /**\n * Ô đang lỗi. Ngoài `aria-invalid` cho trình đọc màn hình, trigger còn chuyển sang viền\n * `--destructive-ink` — cùng tín hiệu thị giác mà `Input` đã có (nhánh nằm ở `button.tsx`).\n *\n * Nhận cả `boolean` lẫn `\"true\"`/`\"false\"` để dùng thẳng được `aria-invalid={!!errors.x}`\n * của react-hook-form. Giá trị falsy KHÔNG phát thuộc tính ra DOM.\n */\n \"aria-invalid\"?: boolean | \"true\" | \"false\";\n \"aria-required\"?: boolean | \"true\" | \"false\";\n}\n\n/** `aria-invalid` nhận nhiều hình dạng — quy về một boolean trước khi dùng làm điều kiện. */\nfunction isAriaInvalid(value: ComboboxSharedProps[\"aria-invalid\"]): boolean {\n return value === true || value === \"true\";\n}\n\nexport interface ComboboxProps extends ComboboxSharedProps {\n /** Giá trị đang chọn; `null`/`undefined` = chưa chọn */\n value?: string | null;\n onValueChange: (value: string | null) => void;\n /**\n * Nhãn `sr-only` của nút xóa (mặc định \"Xóa lựa chọn\").\n *\n * Nút xóa chỉ có mỗi icon `X`, nên chuỗi này CHÍNH LÀ tên của nút với trình đọc màn hình. Ghim\n * cứng tiếng Việt ở đây là lỗi không ảnh chụp nào thấy và cũng không có gì báo đỏ: app tiếng\n * Anh nhìn thì đúng, mà ĐỌC ra vẫn là tiếng Việt.\n *\n * Nằm ở đây chứ không ở `ComboboxSharedProps` vì `MultiCombobox` xóa cả danh sách nên mặc định\n * của nó là một câu khác.\n */\n clearLabel?: string;\n}\n\nexport interface MultiComboboxProps extends ComboboxSharedProps {\n value: string[];\n onValueChange: (value: string[]) => void;\n /** Số nhãn hiện trên trigger trước khi gộp thành \"+k\" (mặc định 2) */\n maxDisplay?: number;\n /** Giới hạn số lựa chọn; đạt ngưỡng thì các mục còn lại bị khoá */\n maxSelected?: number;\n /**\n * Dòng đếm ở đáy panel khi có `maxSelected`. HÀM vì nó chèn HAI số — nối tay ở component là\n * khoá cứng trật tự tiếng Việt (\"Đã chọn 2/5\") vào mọi bản dịch.\n */\n selectedCountLabel?: (selected: number, max: number) => string;\n /**\n * Nhãn `sr-only` của nút xóa (mặc định \"Xóa tất cả lựa chọn\" — nút bỏ SẠCH danh sách đang chọn,\n * không phải bỏ một mục).\n *\n * Cùng lý do như `Combobox`, nhưng gấp hơn một bậc: ở đây `clearable` mặc định là `true`, nên\n * một app tiếng Anh dính chuỗi tiếng Việt này mà không hề bật gì.\n */\n clearLabel?: string;\n}\n\n/**\n * Lọc không dấu cho cmdk: so khớp cả value, label lẫn keywords.\n *\n * cmdk mặc định chấm điểm theo thuật toán riêng có phân biệt dấu — người Việt gõ \"nguyen\"\n * sẽ không ra \"Nguyễn\". Trả 1/0 là đủ vì thứ tự hiển thị đã theo thứ tự app truyền vào.\n */\nfunction vietnameseFilter(value: string, search: string, keywords?: string[]): number {\n if (!search) return 1;\n const haystack = removeVietnameseTones(`${value} ${keywords?.join(\" \") ?? \"\"}`).toLowerCase();\n return haystack.includes(removeVietnameseTones(search).toLowerCase()) ? 1 : 0;\n}\n\n/** Gom mục theo `group`, giữ nguyên thứ tự xuất hiện đầu tiên của mỗi nhóm. */\nfunction groupOptions(options: ComboboxOption[]): Array<{ label: string | undefined; items: ComboboxOption[] }> {\n const groups = new Map<string, ComboboxOption[]>();\n for (const option of options) {\n const key = option.group ?? \"\";\n const bucket = groups.get(key);\n if (bucket) bucket.push(option);\n else groups.set(key, [option]);\n }\n return [...groups.entries()].map(([label, items]) => ({ label: label || undefined, items }));\n}\n\nfunction OptionRow({ option, selected }: { option: ComboboxOption; selected: boolean }) {\n const Icon = option.icon;\n return (\n <>\n <span\n aria-hidden=\"true\"\n className={cn(\"flex size-4 shrink-0 items-center justify-center\", !selected && \"invisible\")}\n >\n <Check className=\"size-4\" />\n </span>\n {Icon && <Icon aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />}\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate\">{option.label}</span>\n {option.description && (\n <span className=\"block truncate text-xs text-muted-foreground\">{option.description}</span>\n )}\n </span>\n </>\n );\n}\n\n/**\n * Hàng icon ĐÈ lên trigger: nút xóa bên trái, chevron neo cứng bên phải.\n *\n * Vì sao nút xóa phải là ANH EM của trigger chứ không nằm trong: `<button>` lồng `<button>` là\n * HTML không hợp lệ, và nếu hạ nó xuống `<span role=\"button\" tabIndex={-1}>` thì người dùng bàn\n * phím vĩnh viễn không tới được nút xóa.\n *\n * Vì sao CHEVRON cũng phải ra ngoài theo: nó là con cuối của trigger, nên chỗ trống dành cho nút\n * xóa chỉ có thể lấy từ `padding-right` của trigger — mà padding đó ĐẨY luôn chevron sang trái.\n * Kết quả là chọn xong một giá trị thì chevron nhảy chỗ, và hai ô cạnh nhau trong cùng một hàng\n * biểu mẫu (một ô đã chọn, một ô chưa) có chevron lệch nhau. Ra ngoài rồi thì chevron đứng yên ở\n * `right-4` như mọi control khác, nút xóa chèn vào BÊN TRÁI nó — đúng thứ tự của `DatePicker`.\n *\n * `pointer-events-none` ở hàng, `pointer-events-auto` ở nút xóa: chevron chỉ là trang trí, bấm\n * vào nó phải xuyên xuống trigger để mở popover (hồi nó còn là con của trigger thì miễn phí).\n *\n * Và hàng này phải TỰ mờ khi `disabled`: `disabled:opacity-50` của `Button` chỉ áp cho chính\n * trigger, không với tới anh em của nó.\n */\nfunction ComboboxAdornments({\n showClear,\n disabled,\n clearLabel,\n onClear\n}: {\n showClear: boolean;\n disabled: boolean;\n clearLabel: string;\n onClear: () => void;\n}) {\n return (\n <div\n className={cn(\n /* `border border-transparent pr-3` chứ không phải `right-3`: hàng này là ANH EM của\n trigger nên nó đo từ mép BORDER, trong khi chevron của `Select` nằm trong trigger nên\n đo từ mép CONTENT — lệch đúng 1px bề dày viền. Mượn lại mô hình hộp của trigger (viền\n 1px trong suốt + lề 12px) là hai bên rơi vào cùng một mốc, không phải bịa số lẻ. */\n \"pointer-events-none absolute inset-y-0 right-0 flex items-center border border-transparent pr-3\",\n disabled && \"opacity-50\"\n )}\n >\n {showClear && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-events-auto size-6 text-muted-foreground hover:text-foreground\"\n onClick={onClear}\n >\n <X aria-hidden=\"true\" className=\"size-3.5\" />\n <span className=\"sr-only\">{clearLabel}</span>\n </Button>\n )}\n {/* Chevron BÁO TRẠNG THÁI (aria-expanded) trên control còn bấm được → ngưỡng 3:1 của\n WCAG 1.4.11, `opacity-*` không miễn trừ. `opacity-50` chỉ được 2,03:1 (light) / 2,30:1\n (dark) khi trigger đang ở trạng thái placeholder (kế thừa `text-muted-foreground`) ✗.\n Nêu thẳng `text-muted-foreground` cho icon: 5,23:1 (light) / 5,70:1 (dark) ✓, và không\n đổi theo việc đã chọn hay chưa. */}\n <ChevronsUpDown aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n </div>\n );\n}\n\n/**\n * Lề của trigger. Trigger là `Button` nên nó mang sẵn `px-4` — mà Combobox nhìn là một Ô NHẬP chứ\n * không phải cái nút, và `Input`/`Select` (cả ở đây lẫn ở comitor-ds) đều `px-3`. Để nguyên `px-4`\n * là trong cùng một cột biểu mẫu, chữ của Combobox thụt vào 4px so với chữ của Input bên trên nó.\n * Nên `px-3`, và mọi icon phải neo ở 12px như các control còn lại.\n *\n * Chỗ trống bên phải phải chừa cho hàng icon tuyệt đối, vì trigger không còn tự đẩy chỗ nữa:\n * `pr-9` = 12 (lề) + 16 (chevron) + 8 (khoảng tới chữ); `pr-15` cộng thêm 24 cho nút xóa.\n */\nconst TRIGGER_PADDING = \"px-3 pr-9\";\nconst TRIGGER_PADDING_WITH_CLEAR = \"px-3 pr-15\";\n\nfunction useComboboxList(options: ComboboxOption[], isAsync: boolean) {\n const groups = useMemo(() => groupOptions(options), [options]);\n return { groups, shouldFilter: !isAsync };\n}\n\nfunction ComboboxPanel({\n options,\n isAsync,\n loading,\n search,\n onSearch,\n searchPlaceholder,\n emptyText,\n loadingText,\n renderItem\n}: {\n options: ComboboxOption[];\n isAsync: boolean;\n loading: boolean;\n search: string;\n onSearch: (value: string) => void;\n searchPlaceholder: string;\n emptyText: string;\n loadingText: string;\n renderItem: (option: ComboboxOption) => ReactNode;\n}) {\n const { groups, shouldFilter } = useComboboxList(options, isAsync);\n\n return (\n <Command shouldFilter={shouldFilter} filter={vietnameseFilter}>\n <CommandInput placeholder={searchPlaceholder} value={search} onValueChange={onSearch} />\n <CommandList>\n {loading ? (\n <div className=\"flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground\">\n <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n {loadingText}\n </div>\n ) : (\n <>\n <CommandEmpty>{emptyText}</CommandEmpty>\n {groups.map((group) => (\n <CommandGroup key={group.label ?? \"__default__\"} heading={group.label}>\n {group.items.map(renderItem)}\n </CommandGroup>\n ))}\n </>\n )}\n </CommandList>\n </Command>\n );\n}\n\n/**\n * Ô chọn một giá trị có tìm kiếm — cmdk trong Popover, rộng bằng trigger.\n *\n * Mặc định tìm KHÔNG DẤU tiếng Việt. Truyền `onSearchChange` để chuyển sang chế độ bất đồng bộ\n * (app tự gọi API và cập nhật `options`).\n */\nexport function Combobox({\n options,\n value,\n onValueChange,\n placeholder = \"Chọn…\",\n searchPlaceholder = \"Tìm kiếm…\",\n emptyText = \"Không tìm thấy kết quả.\",\n loadingText = \"Đang tải…\",\n disabled = false,\n onSearchChange,\n loading = false,\n clearable = false,\n clearLabel = \"Xóa lựa chọn\",\n className,\n contentClassName,\n id,\n name,\n ...aria\n}: ComboboxProps) {\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState(\"\");\n const isAsync = onSearchChange !== undefined;\n const selected = options.find((option) => option.value === value);\n const invalid = isAriaInvalid(aria[\"aria-invalid\"]);\n\n const handleSearch = (next: string) => {\n setSearch(next);\n onSearchChange?.(next);\n };\n\n const showClear = clearable && Boolean(selected) && !disabled;\n\n return (\n <div className=\"relative w-full\">\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n id={id}\n name={name}\n disabled={disabled}\n className={cn(\n \"w-full justify-start font-normal\",\n showClear ? TRIGGER_PADDING_WITH_CLEAR : TRIGGER_PADDING,\n !selected && \"text-muted-foreground\",\n className\n )}\n {...aria}\n // Sau `{...aria}`: chỉ phát thuộc tính khi THẬT SỰ lỗi, để `aria-invalid={false}`\n // không rơi ra DOM thành `aria-invalid=\"false\"` (hợp lệ nhưng thừa).\n aria-invalid={invalid || undefined}\n >\n <span className=\"truncate\">{selected ? selected.label : placeholder}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className={cn(\"w-(--radix-popover-trigger-width) p-0\", contentClassName)}>\n <ComboboxPanel\n options={options}\n isAsync={isAsync}\n loading={loading}\n search={search}\n onSearch={handleSearch}\n searchPlaceholder={searchPlaceholder}\n emptyText={emptyText}\n loadingText={loadingText}\n renderItem={(option) => (\n <CommandItem\n key={option.value}\n value={option.value}\n keywords={[option.label, option.description ?? \"\", ...(option.keywords ?? [])]}\n disabled={option.disabled}\n onSelect={() => {\n onValueChange(option.value === value ? null : option.value);\n setOpen(false);\n setSearch(\"\");\n if (isAsync) onSearchChange?.(\"\");\n }}\n >\n <OptionRow option={option} selected={option.value === value} />\n </CommandItem>\n )}\n />\n </PopoverContent>\n </Popover>\n\n <ComboboxAdornments\n showClear={showClear}\n disabled={disabled}\n clearLabel={clearLabel}\n onClear={() => onValueChange(null)}\n />\n </div>\n );\n}\n\n/**\n * Ô chọn NHIỀU giá trị — cùng hạ tầng với `Combobox`, popover không tự đóng sau mỗi lần chọn.\n *\n * Trigger hiện tối đa `maxDisplay` nhãn rồi gộp phần dư thành \"+k\": danh sách 12 người thực hiện\n * mà đổ hết ra trigger thì hàng form vỡ bố cục.\n */\nexport function MultiCombobox({\n options,\n value,\n onValueChange,\n placeholder = \"Chọn…\",\n searchPlaceholder = \"Tìm kiếm…\",\n emptyText = \"Không tìm thấy kết quả.\",\n loadingText = \"Đang tải…\",\n disabled = false,\n onSearchChange,\n loading = false,\n clearable = true,\n clearLabel = \"Xóa tất cả lựa chọn\",\n selectedCountLabel = (selected, max) => `Đã chọn ${selected}/${max}`,\n maxDisplay = 2,\n maxSelected,\n className,\n contentClassName,\n id,\n name,\n ...aria\n}: MultiComboboxProps) {\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState(\"\");\n const isAsync = onSearchChange !== undefined;\n const invalid = isAriaInvalid(aria[\"aria-invalid\"]);\n\n const selectedOptions = options.filter((option) => value.includes(option.value));\n const shown = selectedOptions.slice(0, Math.max(1, maxDisplay));\n const overflow = selectedOptions.length - shown.length;\n const limitReached = maxSelected !== undefined && value.length >= maxSelected;\n\n const toggle = (optionValue: string) => {\n if (value.includes(optionValue)) {\n onValueChange(value.filter((item) => item !== optionValue));\n return;\n }\n if (limitReached) return;\n onValueChange([...value, optionValue]);\n };\n\n const handleSearch = (next: string) => {\n setSearch(next);\n onSearchChange?.(next);\n };\n\n const showClear = clearable && value.length > 0 && !disabled;\n\n return (\n <div className=\"relative w-full\">\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n id={id}\n name={name}\n disabled={disabled}\n className={cn(\n \"h-auto min-h-9 w-full justify-start py-1.5 font-normal\",\n showClear ? TRIGGER_PADDING_WITH_CLEAR : TRIGGER_PADDING,\n value.length === 0 && \"text-muted-foreground\",\n className\n )}\n {...aria}\n // Cùng lý do như Combobox đơn.\n aria-invalid={invalid || undefined}\n >\n <span className=\"flex min-w-0 flex-wrap items-center gap-1\">\n {value.length === 0 ? (\n placeholder\n ) : (\n <>\n {shown.map((option) => (\n <Badge key={option.value} variant=\"secondary\" className=\"max-w-[10rem]\">\n <span className=\"truncate\">{option.label}</span>\n </Badge>\n ))}\n {overflow > 0 && <Badge variant=\"outline\">+{overflow}</Badge>}\n </>\n )}\n </span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className={cn(\"w-(--radix-popover-trigger-width) p-0\", contentClassName)}>\n <ComboboxPanel\n options={options}\n isAsync={isAsync}\n loading={loading}\n search={search}\n onSearch={handleSearch}\n searchPlaceholder={searchPlaceholder}\n emptyText={emptyText}\n loadingText={loadingText}\n renderItem={(option) => {\n const isSelected = value.includes(option.value);\n return (\n <CommandItem\n key={option.value}\n value={option.value}\n keywords={[option.label, option.description ?? \"\", ...(option.keywords ?? [])]}\n disabled={option.disabled || (limitReached && !isSelected)}\n onSelect={() => toggle(option.value)}\n >\n <OptionRow option={option} selected={isSelected} />\n </CommandItem>\n );\n }}\n />\n {maxSelected !== undefined && (\n <p className=\"border-t border-border px-3 py-2 text-xs text-muted-foreground\">\n {selectedCountLabel(value.length, maxSelected)}\n </p>\n )}\n </PopoverContent>\n </Popover>\n\n <ComboboxAdornments\n showClear={showClear}\n disabled={disabled}\n clearLabel={clearLabel}\n onClear={() => onValueChange([])}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/composite/combobox.tsx"],"sourcesContent":["\"use client\";\n\nimport { Check, ChevronsUpDown, Loader2, X } from \"lucide-react\";\nimport { type ReactNode, useMemo, useState } from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Badge } from \"../ui/badge.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from \"../ui/command.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../ui/popover.js\";\nimport type { IconComponent } from \"./empty-state.js\";\nimport { removeVietnameseTones } from \"./text-utils.js\";\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n /** Dòng phụ mờ dưới nhãn (vd email của người dùng, mã của khách hàng) */\n description?: string;\n icon?: IconComponent;\n disabled?: boolean;\n /** Nhãn nhóm — các mục cùng nhóm được gom lại và có tiêu đề nhóm */\n group?: string;\n /** Từ khoá phụ để tìm ra mục này (vd mã số, tên viết tắt) */\n keywords?: string[];\n}\n\ninterface ComboboxSharedProps {\n options: ComboboxOption[];\n /*\n * Mọi chuỗi dưới đây: không truyền thì lấy namespace `combobox` của `ComitorLocaleProvider`\n * (mặc định tiếng Anh). Truyền prop là đè riêng chỗ này.\n */\n placeholder?: string;\n searchPlaceholder?: string;\n emptyText?: string;\n /**\n * Chữ hiện trong panel khi `loading`.\n * Là chữ THẤY ĐƯỢC (không phải sr-only) và cả `Combobox` lẫn `MultiCombobox` đều đi qua nó.\n */\n loadingText?: string;\n disabled?: boolean;\n /**\n * Có mặt = chế độ BẤT ĐỒNG BỘ: tắt lọc nội bộ của cmdk, app tự lọc `options` (thường là gọi API).\n * Không truyền = lọc tại chỗ, không dấu tiếng Việt.\n */\n onSearchChange?: (search: string) => void;\n /** Đang tải kết quả (chỉ có nghĩa ở chế độ bất đồng bộ) */\n loading?: boolean;\n /** Cho phép xoá lựa chọn bằng nút X trên trigger */\n clearable?: boolean;\n className?: string;\n /** className của panel popover (vd đặt bề rộng cố định) */\n contentClassName?: string;\n id?: string;\n name?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n /**\n * Ô đang lỗi. Ngoài `aria-invalid` cho trình đọc màn hình, trigger còn chuyển sang viền\n * `--destructive-ink` — cùng tín hiệu thị giác mà `Input` đã có (nhánh nằm ở `button.tsx`).\n *\n * Nhận cả `boolean` lẫn `\"true\"`/`\"false\"` để dùng thẳng được `aria-invalid={!!errors.x}`\n * của react-hook-form. Giá trị falsy KHÔNG phát thuộc tính ra DOM.\n */\n \"aria-invalid\"?: boolean | \"true\" | \"false\";\n \"aria-required\"?: boolean | \"true\" | \"false\";\n}\n\n/** `aria-invalid` nhận nhiều hình dạng — quy về một boolean trước khi dùng làm điều kiện. */\nfunction isAriaInvalid(value: ComboboxSharedProps[\"aria-invalid\"]): boolean {\n return value === true || value === \"true\";\n}\n\nexport interface ComboboxProps extends ComboboxSharedProps {\n /** Giá trị đang chọn; `null`/`undefined` = chưa chọn */\n value?: string | null;\n onValueChange: (value: string | null) => void;\n /**\n * Nhãn `sr-only` của nút xóa (khoá `clearLabel` của namespace `combobox`).\n *\n * Nút xóa chỉ có mỗi icon `X`, nên chuỗi này CHÍNH LÀ tên của nút với trình đọc màn hình. Sai\n * ngôn ngữ ở đây là lỗi không ảnh chụp nào thấy và cũng không có gì báo đỏ: giao diện nhìn thì\n * đúng, mà ĐỌC ra lại là thứ tiếng khác.\n *\n * Nằm ở đây chứ không ở `ComboboxSharedProps` vì `MultiCombobox` xóa cả danh sách nên mặc định\n * của nó là một câu khác.\n */\n clearLabel?: string;\n}\n\nexport interface MultiComboboxProps extends ComboboxSharedProps {\n value: string[];\n onValueChange: (value: string[]) => void;\n /** Số nhãn hiện trên trigger trước khi gộp thành \"+k\" (mặc định 2) */\n maxDisplay?: number;\n /** Giới hạn số lựa chọn; đạt ngưỡng thì các mục còn lại bị khoá */\n maxSelected?: number;\n /**\n * Dòng đếm ở đáy panel khi có `maxSelected`. HÀM vì nó chèn HAI số — nối tay ở component là\n * khoá cứng trật tự từ của một ngôn ngữ (\"Đã chọn 2/5\" ⇄ \"2/5 selected\") vào mọi bản dịch.\n */\n selectedCountLabel?: (selected: number, max: number) => string;\n /**\n * Nhãn `sr-only` của nút xóa (khoá `clearAllLabel` của namespace `combobox` — nút bỏ SẠCH danh\n * sách đang chọn, không phải bỏ một mục).\n *\n * Cùng lý do như `Combobox`, nhưng gấp hơn một bậc: ở đây `clearable` mặc định là `true`, nên\n * chuỗi này có mặt ở MỌI MultiCombobox mà app không hề bật gì.\n */\n clearLabel?: string;\n}\n\n/**\n * Lọc không dấu cho cmdk: so khớp cả value, label lẫn keywords.\n *\n * cmdk mặc định chấm điểm theo thuật toán riêng có phân biệt dấu — người Việt gõ \"nguyen\"\n * sẽ không ra \"Nguyễn\". Trả 1/0 là đủ vì thứ tự hiển thị đã theo thứ tự app truyền vào.\n */\nfunction vietnameseFilter(value: string, search: string, keywords?: string[]): number {\n if (!search) return 1;\n const haystack = removeVietnameseTones(`${value} ${keywords?.join(\" \") ?? \"\"}`).toLowerCase();\n return haystack.includes(removeVietnameseTones(search).toLowerCase()) ? 1 : 0;\n}\n\n/** Gom mục theo `group`, giữ nguyên thứ tự xuất hiện đầu tiên của mỗi nhóm. */\nfunction groupOptions(options: ComboboxOption[]): Array<{ label: string | undefined; items: ComboboxOption[] }> {\n const groups = new Map<string, ComboboxOption[]>();\n for (const option of options) {\n const key = option.group ?? \"\";\n const bucket = groups.get(key);\n if (bucket) bucket.push(option);\n else groups.set(key, [option]);\n }\n return [...groups.entries()].map(([label, items]) => ({ label: label || undefined, items }));\n}\n\nfunction OptionRow({ option, selected }: { option: ComboboxOption; selected: boolean }) {\n const Icon = option.icon;\n return (\n <>\n <span\n aria-hidden=\"true\"\n className={cn(\"flex size-4 shrink-0 items-center justify-center\", !selected && \"invisible\")}\n >\n <Check className=\"size-4\" />\n </span>\n {Icon && <Icon aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />}\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate\">{option.label}</span>\n {option.description && (\n <span className=\"block truncate text-xs text-muted-foreground\">{option.description}</span>\n )}\n </span>\n </>\n );\n}\n\n/**\n * Hàng icon ĐÈ lên trigger: nút xóa bên trái, chevron neo cứng bên phải.\n *\n * Vì sao nút xóa phải là ANH EM của trigger chứ không nằm trong: `<button>` lồng `<button>` là\n * HTML không hợp lệ, và nếu hạ nó xuống `<span role=\"button\" tabIndex={-1}>` thì người dùng bàn\n * phím vĩnh viễn không tới được nút xóa.\n *\n * Vì sao CHEVRON cũng phải ra ngoài theo: nó là con cuối của trigger, nên chỗ trống dành cho nút\n * xóa chỉ có thể lấy từ `padding-right` của trigger — mà padding đó ĐẨY luôn chevron sang trái.\n * Kết quả là chọn xong một giá trị thì chevron nhảy chỗ, và hai ô cạnh nhau trong cùng một hàng\n * biểu mẫu (một ô đã chọn, một ô chưa) có chevron lệch nhau. Ra ngoài rồi thì chevron đứng yên ở\n * `right-4` như mọi control khác, nút xóa chèn vào BÊN TRÁI nó — đúng thứ tự của `DatePicker`.\n *\n * `pointer-events-none` ở hàng, `pointer-events-auto` ở nút xóa: chevron chỉ là trang trí, bấm\n * vào nó phải xuyên xuống trigger để mở popover (hồi nó còn là con của trigger thì miễn phí).\n *\n * Và hàng này phải TỰ mờ khi `disabled`: `disabled:opacity-50` của `Button` chỉ áp cho chính\n * trigger, không với tới anh em của nó.\n */\nfunction ComboboxAdornments({\n showClear,\n disabled,\n clearLabel,\n onClear\n}: {\n showClear: boolean;\n disabled: boolean;\n clearLabel: string;\n onClear: () => void;\n}) {\n return (\n <div\n className={cn(\n /* `border border-transparent pr-3` chứ không phải `right-3`: hàng này là ANH EM của\n trigger nên nó đo từ mép BORDER, trong khi chevron của `Select` nằm trong trigger nên\n đo từ mép CONTENT — lệch đúng 1px bề dày viền. Mượn lại mô hình hộp của trigger (viền\n 1px trong suốt + lề 12px) là hai bên rơi vào cùng một mốc, không phải bịa số lẻ. */\n \"pointer-events-none absolute inset-y-0 right-0 flex items-center border border-transparent pr-3\",\n disabled && \"opacity-50\"\n )}\n >\n {showClear && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-events-auto size-6 text-muted-foreground hover:text-foreground\"\n onClick={onClear}\n >\n <X aria-hidden=\"true\" className=\"size-3.5\" />\n <span className=\"sr-only\">{clearLabel}</span>\n </Button>\n )}\n {/* Chevron BÁO TRẠNG THÁI (aria-expanded) trên control còn bấm được → ngưỡng 3:1 của\n WCAG 1.4.11, `opacity-*` không miễn trừ. `opacity-50` chỉ được 2,03:1 (light) / 2,30:1\n (dark) khi trigger đang ở trạng thái placeholder (kế thừa `text-muted-foreground`) ✗.\n Nêu thẳng `text-muted-foreground` cho icon: 5,23:1 (light) / 5,70:1 (dark) ✓, và không\n đổi theo việc đã chọn hay chưa. */}\n <ChevronsUpDown aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n </div>\n );\n}\n\n/**\n * Lề của trigger. Trigger là `Button` nên nó mang sẵn `px-4` — mà Combobox nhìn là một Ô NHẬP chứ\n * không phải cái nút, và `Input`/`Select` (cả ở đây lẫn ở comitor-ds) đều `px-3`. Để nguyên `px-4`\n * là trong cùng một cột biểu mẫu, chữ của Combobox thụt vào 4px so với chữ của Input bên trên nó.\n * Nên `px-3`, và mọi icon phải neo ở 12px như các control còn lại.\n *\n * Chỗ trống bên phải phải chừa cho hàng icon tuyệt đối, vì trigger không còn tự đẩy chỗ nữa:\n * `pr-9` = 12 (lề) + 16 (chevron) + 8 (khoảng tới chữ); `pr-15` cộng thêm 24 cho nút xóa.\n */\nconst TRIGGER_PADDING = \"px-3 pr-9\";\nconst TRIGGER_PADDING_WITH_CLEAR = \"px-3 pr-15\";\n\nfunction useComboboxList(options: ComboboxOption[], isAsync: boolean) {\n const groups = useMemo(() => groupOptions(options), [options]);\n return { groups, shouldFilter: !isAsync };\n}\n\nfunction ComboboxPanel({\n options,\n isAsync,\n loading,\n search,\n onSearch,\n searchPlaceholder,\n emptyText,\n loadingText,\n renderItem\n}: {\n options: ComboboxOption[];\n isAsync: boolean;\n loading: boolean;\n search: string;\n onSearch: (value: string) => void;\n searchPlaceholder: string;\n emptyText: string;\n loadingText: string;\n renderItem: (option: ComboboxOption) => ReactNode;\n}) {\n const { groups, shouldFilter } = useComboboxList(options, isAsync);\n\n return (\n <Command shouldFilter={shouldFilter} filter={vietnameseFilter}>\n <CommandInput placeholder={searchPlaceholder} value={search} onValueChange={onSearch} />\n <CommandList>\n {loading ? (\n <div className=\"flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground\">\n <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n {loadingText}\n </div>\n ) : (\n <>\n <CommandEmpty>{emptyText}</CommandEmpty>\n {groups.map((group) => (\n <CommandGroup key={group.label ?? \"__default__\"} heading={group.label}>\n {group.items.map(renderItem)}\n </CommandGroup>\n ))}\n </>\n )}\n </CommandList>\n </Command>\n );\n}\n\n/**\n * Ô chọn một giá trị có tìm kiếm — cmdk trong Popover, rộng bằng trigger.\n *\n * Mặc định tìm KHÔNG DẤU tiếng Việt. Truyền `onSearchChange` để chuyển sang chế độ bất đồng bộ\n * (app tự gọi API và cập nhật `options`).\n */\nexport function Combobox({\n options,\n value,\n onValueChange,\n placeholder: placeholderProp,\n searchPlaceholder: searchPlaceholderProp,\n emptyText: emptyTextProp,\n loadingText: loadingTextProp,\n disabled = false,\n onSearchChange,\n loading = false,\n clearable = false,\n clearLabel: clearLabelProp,\n className,\n contentClassName,\n id,\n name,\n ...aria\n}: ComboboxProps) {\n const t = useComitorMessages(\"combobox\");\n const placeholder = placeholderProp ?? t.placeholder;\n const searchPlaceholder = searchPlaceholderProp ?? t.searchPlaceholder;\n const emptyText = emptyTextProp ?? t.emptyText;\n const loadingText = loadingTextProp ?? t.loadingText;\n const clearLabel = clearLabelProp ?? t.clearLabel;\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState(\"\");\n const isAsync = onSearchChange !== undefined;\n const selected = options.find((option) => option.value === value);\n const invalid = isAriaInvalid(aria[\"aria-invalid\"]);\n\n const handleSearch = (next: string) => {\n setSearch(next);\n onSearchChange?.(next);\n };\n\n const showClear = clearable && Boolean(selected) && !disabled;\n\n return (\n <div className=\"relative w-full\">\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n id={id}\n name={name}\n disabled={disabled}\n className={cn(\n \"w-full justify-start font-normal\",\n showClear ? TRIGGER_PADDING_WITH_CLEAR : TRIGGER_PADDING,\n !selected && \"text-muted-foreground\",\n className\n )}\n {...aria}\n // Sau `{...aria}`: chỉ phát thuộc tính khi THẬT SỰ lỗi, để `aria-invalid={false}`\n // không rơi ra DOM thành `aria-invalid=\"false\"` (hợp lệ nhưng thừa).\n aria-invalid={invalid || undefined}\n >\n <span className=\"truncate\">{selected ? selected.label : placeholder}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className={cn(\"w-(--radix-popover-trigger-width) p-0\", contentClassName)}>\n <ComboboxPanel\n options={options}\n isAsync={isAsync}\n loading={loading}\n search={search}\n onSearch={handleSearch}\n searchPlaceholder={searchPlaceholder}\n emptyText={emptyText}\n loadingText={loadingText}\n renderItem={(option) => (\n <CommandItem\n key={option.value}\n value={option.value}\n keywords={[option.label, option.description ?? \"\", ...(option.keywords ?? [])]}\n disabled={option.disabled}\n onSelect={() => {\n onValueChange(option.value === value ? null : option.value);\n setOpen(false);\n setSearch(\"\");\n if (isAsync) onSearchChange?.(\"\");\n }}\n >\n <OptionRow option={option} selected={option.value === value} />\n </CommandItem>\n )}\n />\n </PopoverContent>\n </Popover>\n\n <ComboboxAdornments\n showClear={showClear}\n disabled={disabled}\n clearLabel={clearLabel}\n onClear={() => onValueChange(null)}\n />\n </div>\n );\n}\n\n/**\n * Ô chọn NHIỀU giá trị — cùng hạ tầng với `Combobox`, popover không tự đóng sau mỗi lần chọn.\n *\n * Trigger hiện tối đa `maxDisplay` nhãn rồi gộp phần dư thành \"+k\": danh sách 12 người thực hiện\n * mà đổ hết ra trigger thì hàng form vỡ bố cục.\n */\nexport function MultiCombobox({\n options,\n value,\n onValueChange,\n placeholder: placeholderProp,\n searchPlaceholder: searchPlaceholderProp,\n emptyText: emptyTextProp,\n loadingText: loadingTextProp,\n disabled = false,\n onSearchChange,\n loading = false,\n clearable = true,\n clearLabel: clearLabelProp,\n selectedCountLabel: selectedCountLabelProp,\n maxDisplay = 2,\n maxSelected,\n className,\n contentClassName,\n id,\n name,\n ...aria\n}: MultiComboboxProps) {\n const t = useComitorMessages(\"combobox\");\n const placeholder = placeholderProp ?? t.placeholder;\n const searchPlaceholder = searchPlaceholderProp ?? t.searchPlaceholder;\n const emptyText = emptyTextProp ?? t.emptyText;\n const loadingText = loadingTextProp ?? t.loadingText;\n const clearLabel = clearLabelProp ?? t.clearAllLabel;\n const selectedCountLabel = selectedCountLabelProp ?? t.selectedCountLabel;\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState(\"\");\n const isAsync = onSearchChange !== undefined;\n const invalid = isAriaInvalid(aria[\"aria-invalid\"]);\n\n const selectedOptions = options.filter((option) => value.includes(option.value));\n const shown = selectedOptions.slice(0, Math.max(1, maxDisplay));\n const overflow = selectedOptions.length - shown.length;\n const limitReached = maxSelected !== undefined && value.length >= maxSelected;\n\n const toggle = (optionValue: string) => {\n if (value.includes(optionValue)) {\n onValueChange(value.filter((item) => item !== optionValue));\n return;\n }\n if (limitReached) return;\n onValueChange([...value, optionValue]);\n };\n\n const handleSearch = (next: string) => {\n setSearch(next);\n onSearchChange?.(next);\n };\n\n const showClear = clearable && value.length > 0 && !disabled;\n\n return (\n <div className=\"relative w-full\">\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n id={id}\n name={name}\n disabled={disabled}\n className={cn(\n \"h-auto min-h-9 w-full justify-start py-1.5 font-normal\",\n showClear ? TRIGGER_PADDING_WITH_CLEAR : TRIGGER_PADDING,\n value.length === 0 && \"text-muted-foreground\",\n className\n )}\n {...aria}\n // Cùng lý do như Combobox đơn.\n aria-invalid={invalid || undefined}\n >\n <span className=\"flex min-w-0 flex-wrap items-center gap-1\">\n {value.length === 0 ? (\n placeholder\n ) : (\n <>\n {shown.map((option) => (\n <Badge key={option.value} variant=\"secondary\" className=\"max-w-[10rem]\">\n <span className=\"truncate\">{option.label}</span>\n </Badge>\n ))}\n {overflow > 0 && <Badge variant=\"outline\">+{overflow}</Badge>}\n </>\n )}\n </span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className={cn(\"w-(--radix-popover-trigger-width) p-0\", contentClassName)}>\n <ComboboxPanel\n options={options}\n isAsync={isAsync}\n loading={loading}\n search={search}\n onSearch={handleSearch}\n searchPlaceholder={searchPlaceholder}\n emptyText={emptyText}\n loadingText={loadingText}\n renderItem={(option) => {\n const isSelected = value.includes(option.value);\n return (\n <CommandItem\n key={option.value}\n value={option.value}\n keywords={[option.label, option.description ?? \"\", ...(option.keywords ?? [])]}\n disabled={option.disabled || (limitReached && !isSelected)}\n onSelect={() => toggle(option.value)}\n >\n <OptionRow option={option} selected={isSelected} />\n </CommandItem>\n );\n }}\n />\n {maxSelected !== undefined && (\n <p className=\"border-t border-border px-3 py-2 text-xs text-muted-foreground\">\n {selectedCountLabel(value.length, maxSelected)}\n </p>\n )}\n </PopoverContent>\n </Popover>\n\n <ComboboxAdornments\n showClear={showClear}\n disabled={disabled}\n clearLabel={clearLabel}\n onClear={() => onValueChange([])}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -14,14 +14,16 @@
|
|
|
14
14
|
* Nhãn chỉ dành cho TRÌNH ĐỌC MÀN HÌNH của `CommandPalette` — cả hai đều nằm trong `sr-only`,
|
|
15
15
|
* không hiện thành chữ trên giao diện.
|
|
16
16
|
*
|
|
17
|
+
* Theo `ComitorLocaleProvider` (namespace `commandPalette`, mặc định tiếng Anh, bảng tiếng Việt
|
|
18
|
+
* dựng sẵn); override từng khoá qua prop `labels`.
|
|
19
|
+
*
|
|
17
20
|
* Vì sao vẫn phải override được: `DialogTitle` và `DialogDescription` là phần bắt buộc của một
|
|
18
|
-
* hộp thoại, luôn được xướng lên khi palette mở.
|
|
19
|
-
*
|
|
20
|
-
* cũng không có gì báo đỏ.
|
|
21
|
+
* hộp thoại, luôn được xướng lên khi palette mở. Sai ngôn ngữ ở đây là giao diện trông thì đúng
|
|
22
|
+
* nhưng ĐỌC ra lại là thứ tiếng khác — lỗi không ai thấy trên ảnh chụp và cũng không có gì báo đỏ.
|
|
21
23
|
*
|
|
22
24
|
* ⚠ Ba chuỗi CÓ HIỆN của palette (`placeholder`, `emptyText`, `recentLabel`) vốn đã là prop rời
|
|
23
25
|
* và vẫn ở nguyên đó, không gom vào đây: chúng thay đổi theo TỪNG chỗ đặt palette (mỗi app một
|
|
24
|
-
* cách gọi tên "trang", "người")
|
|
26
|
+
* cách gọi tên "trang", "người"). Mặc định của chúng cũng nằm ở namespace `commandPalette`.
|
|
25
27
|
*/
|
|
26
28
|
interface CommandPaletteLabels {
|
|
27
29
|
/** `DialogTitle` sr-only — tên hộp thoại, trình đọc màn hình xướng ngay khi palette mở */
|
|
@@ -29,7 +31,15 @@ interface CommandPaletteLabels {
|
|
|
29
31
|
/** `DialogDescription` sr-only — cách dùng bảng lệnh, đọc tiếp sau tiêu đề */
|
|
30
32
|
description: string;
|
|
31
33
|
}
|
|
32
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* 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:
|
|
36
|
+
* `comitorMessagesVi.commandPalette`. Component không đọc thẳng hằng này — nó đọc bảng của
|
|
37
|
+
* `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.
|
|
38
|
+
*
|
|
39
|
+
* Chỉ nhặt ĐÚNG hai khoá của `CommandPaletteLabels` chứ không gán cả bảng: namespace còn ba chuỗi
|
|
40
|
+
* có hiện (`recentLabel`, `placeholder`, `emptyText`), gán thẳng thì hằng công khai này lặng lẽ
|
|
41
|
+
* mang thêm ba khoá lúc chạy mà kiểu không hề khai.
|
|
42
|
+
*/
|
|
33
43
|
declare const DEFAULT_COMMAND_PALETTE_LABELS: CommandPaletteLabels;
|
|
34
44
|
|
|
35
45
|
export { type CommandPaletteLabels, DEFAULT_COMMAND_PALETTE_LABELS };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { commandPaletteEn } from "../../i18n/namespaces/command-palette.js";
|
|
1
2
|
const DEFAULT_COMMAND_PALETTE_LABELS = {
|
|
2
|
-
title:
|
|
3
|
-
description:
|
|
3
|
+
title: commandPaletteEn.title,
|
|
4
|
+
description: commandPaletteEn.description
|
|
4
5
|
};
|
|
5
6
|
export {
|
|
6
7
|
DEFAULT_COMMAND_PALETTE_LABELS
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/composite/command-palette-constants.ts"],"sourcesContent":["/**\n * Nhãn mặc định của `CommandPalette`.\n *\n * Module **CỐ Ý không có `\"use client\"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy\n * đủ: mọi export của một file `\"use client\"` đến tay Server Component dưới dạng\n * client-reference proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. Hằng dữ liệu thuần\n * (nhãn mặc định, preset, sentinel) là thứ trang server hay đọc nhất — spread để override một\n * khoá, đối chiếu giá trị, dựng danh sách — nên phải sống ngoài ranh giới client.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `command-palette.tsx` chỉ được `import`,\n * KHÔNG được `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).\n */\n\n/**\n * Nhãn chỉ dành cho TRÌNH ĐỌC MÀN HÌNH của `CommandPalette` — cả hai đều nằm trong `sr-only`,\n * không hiện thành chữ trên giao diện.\n *\n * Vì sao vẫn phải override được: `DialogTitle` và `DialogDescription` là phần bắt buộc của một\n * hộp thoại, luôn được xướng lên khi palette mở.
|
|
1
|
+
{"version":3,"sources":["../../../src/components/composite/command-palette-constants.ts"],"sourcesContent":["/**\n * Nhãn mặc định của `CommandPalette`.\n *\n * Module **CỐ Ý không có `\"use client\"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy\n * đủ: mọi export của một file `\"use client\"` đến tay Server Component dưới dạng\n * client-reference proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. Hằng dữ liệu thuần\n * (nhãn mặc định, preset, sentinel) là thứ trang server hay đọc nhất — spread để override một\n * khoá, đối chiếu giá trị, dựng danh sách — nên phải sống ngoài ranh giới client.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `command-palette.tsx` chỉ được `import`,\n * KHÔNG được `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).\n */\n\nimport { commandPaletteEn } from \"../../i18n/namespaces/command-palette.js\";\n\n/**\n * Nhãn chỉ dành cho TRÌNH ĐỌC MÀN HÌNH của `CommandPalette` — cả hai đều nằm trong `sr-only`,\n * không hiện thành chữ trên giao diện.\n *\n * Theo `ComitorLocaleProvider` (namespace `commandPalette`, mặc định tiếng Anh, bảng tiếng Việt\n * dựng sẵn); override từng khoá qua prop `labels`.\n *\n * Vì sao vẫn phải override được: `DialogTitle` và `DialogDescription` là phần bắt buộc của một\n * hộp thoại, luôn được xướng lên khi palette mở. Sai ngôn ngữ ở đây là giao diện trông thì đúng\n * nhưng ĐỌC ra lại là thứ tiếng khác — lỗi không ai thấy trên ảnh chụp và cũng không có gì báo đỏ.\n *\n * ⚠ Ba chuỗi CÓ HIỆN của palette (`placeholder`, `emptyText`, `recentLabel`) vốn đã là prop rời\n * và vẫn ở nguyên đó, không gom vào đây: chúng thay đổi theo TỪNG chỗ đặt palette (mỗi app một\n * cách gọi tên \"trang\", \"người\"). Mặc định của chúng cũng nằm ở namespace `commandPalette`.\n */\nexport interface CommandPaletteLabels {\n /** `DialogTitle` sr-only — tên hộp thoại, trình đọc màn hình xướng ngay khi palette mở */\n title: string;\n /** `DialogDescription` sr-only — cách dùng bảng lệnh, đọc tiếp sau tiêu đề */\n description: 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.commandPalette`. 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 *\n * Chỉ nhặt ĐÚNG hai khoá của `CommandPaletteLabels` chứ không gán cả bảng: namespace còn ba chuỗi\n * có hiện (`recentLabel`, `placeholder`, `emptyText`), gán thẳng thì hằng công khai này lặng lẽ\n * mang thêm ba khoá lúc chạy mà kiểu không hề khai.\n */\nexport const DEFAULT_COMMAND_PALETTE_LABELS: CommandPaletteLabels = {\n title: commandPaletteEn.title,\n description: commandPaletteEn.description\n};\n"],"mappings":"AAaA,SAAS,wBAAwB;AAiC1B,MAAM,iCAAuD;AAAA,EAClE,OAAO,iBAAiB;AAAA,EACxB,aAAa,iBAAiB;AAChC;","names":[]}
|
|
@@ -42,7 +42,7 @@ interface CommandPaletteProps {
|
|
|
42
42
|
* hai listener cùng lật một state (mở rồi đóng ngay trong một lần bấm).
|
|
43
43
|
*/
|
|
44
44
|
registerShortcut?: boolean;
|
|
45
|
-
/** Ghi đè hai chuỗi sr-only của hộp thoại (mặc định
|
|
45
|
+
/** Ghi đè hai chuỗi sr-only của hộp thoại (mặc định theo provider) — xem `CommandPaletteLabels` */
|
|
46
46
|
labels?: Partial<CommandPaletteLabels>;
|
|
47
47
|
className?: string;
|
|
48
48
|
}
|
|
@@ -54,6 +54,6 @@ interface CommandPaletteProps {
|
|
|
54
54
|
*
|
|
55
55
|
* Tìm kiếm mặc định KHÔNG DẤU tiếng Việt: gõ "tao viec" ra "Tạo việc mới".
|
|
56
56
|
*/
|
|
57
|
-
declare function CommandPalette({ open, onOpenChange, groups, recent, recentLabel, placeholder, emptyText, onSearchChange, loading, footer, registerShortcut, labels: labelsProp, className }: CommandPaletteProps): React.JSX.Element;
|
|
57
|
+
declare function CommandPalette({ open, onOpenChange, groups, recent, recentLabel: recentLabelProp, placeholder: placeholderProp, emptyText: emptyTextProp, onSearchChange, loading, footer, registerShortcut, labels: labelsProp, className }: CommandPaletteProps): React.JSX.Element;
|
|
58
58
|
|
|
59
59
|
export { CommandPalette, type CommandPaletteGroup, type CommandPaletteItem, type CommandPaletteProps };
|
|
@@ -3,10 +3,10 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { Command as CommandPrimitive } from "cmdk";
|
|
4
4
|
import { Loader2, Search } from "lucide-react";
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
|
+
import { useComitorLabels } from "../../i18n/locale-provider.js";
|
|
6
7
|
import { cn } from "../../lib/cn.js";
|
|
7
8
|
import { Command, CommandEmpty, CommandGroup, CommandItem, CommandList } from "../ui/command.js";
|
|
8
9
|
import { Dialog, DialogContent, DialogDescription, DialogTitle } from "../ui/dialog.js";
|
|
9
|
-
import { DEFAULT_COMMAND_PALETTE_LABELS } from "./command-palette-constants.js";
|
|
10
10
|
import { KeyboardHint } from "./keyboard-hint.js";
|
|
11
11
|
import { removeVietnameseTones } from "./text-utils.js";
|
|
12
12
|
function PaletteInput({
|
|
@@ -46,9 +46,9 @@ function CommandPalette({
|
|
|
46
46
|
onOpenChange,
|
|
47
47
|
groups,
|
|
48
48
|
recent = [],
|
|
49
|
-
recentLabel
|
|
50
|
-
placeholder
|
|
51
|
-
emptyText
|
|
49
|
+
recentLabel: recentLabelProp,
|
|
50
|
+
placeholder: placeholderProp,
|
|
51
|
+
emptyText: emptyTextProp,
|
|
52
52
|
onSearchChange,
|
|
53
53
|
loading = false,
|
|
54
54
|
footer,
|
|
@@ -56,7 +56,10 @@ function CommandPalette({
|
|
|
56
56
|
labels: labelsProp,
|
|
57
57
|
className
|
|
58
58
|
}) {
|
|
59
|
-
const labels =
|
|
59
|
+
const labels = useComitorLabels("commandPalette", labelsProp);
|
|
60
|
+
const recentLabel = recentLabelProp ?? labels.recentLabel;
|
|
61
|
+
const placeholder = placeholderProp ?? labels.placeholder;
|
|
62
|
+
const emptyText = emptyTextProp ?? labels.emptyText;
|
|
60
63
|
const [search, setSearch] = useState("");
|
|
61
64
|
const isAsync = onSearchChange !== void 0;
|
|
62
65
|
useEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/composite/command-palette.tsx"],"sourcesContent":["\"use client\";\n\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { Loader2, Search } from \"lucide-react\";\nimport { type ReactNode, useEffect, useState } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Command, CommandEmpty, CommandGroup, CommandItem, CommandList } from \"../ui/command.js\";\nimport { Dialog, DialogContent, DialogDescription, DialogTitle } from \"../ui/dialog.js\";\nimport { type CommandPaletteLabels, DEFAULT_COMMAND_PALETTE_LABELS } from \"./command-palette-constants.js\";\nimport type { IconComponent } from \"./empty-state.js\";\nimport { KeyboardHint } from \"./keyboard-hint.js\";\nimport { removeVietnameseTones } from \"./text-utils.js\";\n\nexport interface CommandPaletteItem {\n id: string;\n label: string;\n /** Dòng phụ mờ (đường dẫn, mô tả ngắn, tên workspace) */\n description?: string;\n icon?: IconComponent;\n /** Gợi ý phím tắt hiện bên phải, vd \"mod+shift+n\" */\n shortcut?: string;\n /** Từ khoá phụ để tìm ra mục (tên tiếng Anh, viết tắt, mã) */\n keywords?: string[];\n disabled?: boolean;\n onSelect: () => void;\n}\n\nexport interface CommandPaletteGroup {\n id: string;\n label?: string;\n items: CommandPaletteItem[];\n}\n\nexport interface CommandPaletteProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n groups: CommandPaletteGroup[];\n /** Nhóm \"Gần đây\" luôn nằm trên cùng khi chưa gõ gì */\n recent?: CommandPaletteItem[];\n recentLabel?: string;\n placeholder?: string;\n emptyText?: string;\n /** Có mặt = app tự tìm (gọi API) → tắt lọc nội bộ của cmdk */\n onSearchChange?: (search: string) => void;\n loading?: boolean;\n /** Nội dung chân bảng (gợi ý phím, trạng thái) */\n footer?: ReactNode;\n /**\n * Tự gắn phím tắt Cmd/Ctrl+K để mở (mặc định true).\n *\n * Shell của Comitor cũng gắn phím tắt này; khi dùng trong shell hãy đặt `false` để tránh\n * hai listener cùng lật một state (mở rồi đóng ngay trong một lần bấm).\n */\n registerShortcut?: boolean;\n /** Ghi đè hai chuỗi sr-only của hộp thoại (mặc định tiếng Việt) — xem `CommandPaletteLabels` */\n labels?: Partial<CommandPaletteLabels>;\n className?: string;\n}\n\n/**\n * Ô nhập của palette — dựng thẳng trên `cmdk` thay vì dùng `CommandInput` của tầng primitive.\n *\n * Lý do: cần thay icon kính lúp bằng vòng xoay khi đang tìm phía server, và cần chỗ đặt gợi ý\n * phím Esc bên phải — `CommandInput` khoá cứng cả hai chi tiết đó.\n */\nfunction PaletteInput({\n value,\n onValueChange,\n placeholder,\n loading\n}: {\n value: string;\n onValueChange: (value: string) => void;\n placeholder: string;\n loading: boolean;\n}) {\n return (\n <div className=\"flex h-12 items-center gap-2 border-b border-border px-3\">\n {loading ? (\n <Loader2 aria-hidden=\"true\" className=\"size-4 shrink-0 animate-spin text-muted-foreground\" />\n ) : (\n <Search aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n )}\n <CommandPrimitive.Input\n value={value}\n onValueChange={onValueChange}\n placeholder={placeholder}\n aria-label={placeholder}\n className=\"flex h-11 w-full bg-transparent text-sm outline-hidden placeholder:text-muted-foreground\"\n />\n <KeyboardHint keys=\"esc\" className=\"hidden sm:inline-flex\" />\n </div>\n );\n}\n\nfunction PaletteRow({ item }: { item: CommandPaletteItem }) {\n const Icon = item.icon;\n return (\n <>\n {Icon && <Icon aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />}\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate\">{item.label}</span>\n {item.description && <span className=\"block truncate text-xs text-muted-foreground\">{item.description}</span>}\n </span>\n {item.shortcut && <KeyboardHint keys={item.shortcut} className=\"ml-auto\" />}\n </>\n );\n}\n\n/**\n * Bảng lệnh (Cmd/Ctrl+K) — điều hướng nhanh và chạy lệnh, kiểu Linear/Raycast.\n *\n * Đặt ở tầng composite (không phải trong shell) vì app không dùng `AppShell` — trang cài đặt\n * độc lập, công cụ nội bộ — vẫn cần bảng lệnh. Shell chỉ giữ state mở/đóng và truyền xuống.\n *\n * Tìm kiếm mặc định KHÔNG DẤU tiếng Việt: gõ \"tao viec\" ra \"Tạo việc mới\".\n */\nexport function CommandPalette({\n open,\n onOpenChange,\n groups,\n recent = [],\n recentLabel = \"Gần đây\",\n placeholder = \"Tìm lệnh, trang, người…\",\n emptyText = \"Không tìm thấy kết quả.\",\n onSearchChange,\n loading = false,\n footer,\n registerShortcut = true,\n labels: labelsProp,\n className\n}: CommandPaletteProps) {\n const labels = { ...DEFAULT_COMMAND_PALETTE_LABELS, ...labelsProp };\n const [search, setSearch] = useState(\"\");\n const isAsync = onSearchChange !== undefined;\n\n // Reset ô tìm mỗi lần mở: mở lại palette mà còn nguyên từ khoá cũ là hành vi gây bất ngờ.\n useEffect(() => {\n if (!open) setSearch(\"\");\n }, [open]);\n\n useEffect(() => {\n if (!registerShortcut) return;\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"k\" || !(event.metaKey || event.ctrlKey)) return;\n event.preventDefault();\n onOpenChange(!open);\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [registerShortcut, open, onOpenChange]);\n\n const handleSearch = (value: string) => {\n setSearch(value);\n onSearchChange?.(value);\n };\n\n const runItem = (item: CommandPaletteItem) => {\n if (item.disabled) return;\n onOpenChange(false);\n item.onSelect();\n };\n\n const showRecent = recent.length > 0 && search.trim() === \"\";\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent size=\"lg\" showCloseButton={false} className={cn(\"overflow-hidden p-0\", className)}>\n <DialogTitle className=\"sr-only\">{labels.title}</DialogTitle>\n <DialogDescription className=\"sr-only\">{labels.description}</DialogDescription>\n\n <Command\n shouldFilter={!isAsync}\n filter={(value, query, keywords) => {\n if (!query) return 1;\n const haystack = removeVietnameseTones(`${value} ${keywords?.join(\" \") ?? \"\"}`).toLowerCase();\n return haystack.includes(removeVietnameseTones(query).toLowerCase()) ? 1 : 0;\n }}\n className=\"bg-transparent\"\n >\n <PaletteInput value={search} onValueChange={handleSearch} placeholder={placeholder} loading={loading} />\n\n <CommandList className=\"max-h-[22rem]\">\n {!loading && <CommandEmpty>{emptyText}</CommandEmpty>}\n\n {showRecent && (\n <CommandGroup heading={recentLabel}>\n {recent.map((item) => (\n <CommandItem\n key={`recent-${item.id}`}\n value={`recent-${item.id}`}\n keywords={[item.label, item.description ?? \"\", ...(item.keywords ?? [])]}\n disabled={item.disabled}\n onSelect={() => runItem(item)}\n >\n <PaletteRow item={item} />\n </CommandItem>\n ))}\n </CommandGroup>\n )}\n\n {groups.map((group) => (\n <CommandGroup key={group.id} heading={group.label}>\n {group.items.map((item) => (\n <CommandItem\n key={item.id}\n value={item.id}\n keywords={[item.label, item.description ?? \"\", ...(item.keywords ?? [])]}\n disabled={item.disabled}\n onSelect={() => runItem(item)}\n >\n <PaletteRow item={item} />\n </CommandItem>\n ))}\n </CommandGroup>\n ))}\n </CommandList>\n\n {footer && (\n <div className=\"flex items-center gap-2 border-t border-border px-3 py-2 text-xs text-muted-foreground\">\n {footer}\n </div>\n )}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/composite/command-palette.tsx"],"sourcesContent":["\"use client\";\n\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { Loader2, Search } from \"lucide-react\";\nimport { type ReactNode, useEffect, useState } from \"react\";\nimport { useComitorLabels } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Command, CommandEmpty, CommandGroup, CommandItem, CommandList } from \"../ui/command.js\";\nimport { Dialog, DialogContent, DialogDescription, DialogTitle } from \"../ui/dialog.js\";\nimport type { CommandPaletteLabels } from \"./command-palette-constants.js\";\nimport type { IconComponent } from \"./empty-state.js\";\nimport { KeyboardHint } from \"./keyboard-hint.js\";\nimport { removeVietnameseTones } from \"./text-utils.js\";\n\nexport interface CommandPaletteItem {\n id: string;\n label: string;\n /** Dòng phụ mờ (đường dẫn, mô tả ngắn, tên workspace) */\n description?: string;\n icon?: IconComponent;\n /** Gợi ý phím tắt hiện bên phải, vd \"mod+shift+n\" */\n shortcut?: string;\n /** Từ khoá phụ để tìm ra mục (tên tiếng Anh, viết tắt, mã) */\n keywords?: string[];\n disabled?: boolean;\n onSelect: () => void;\n}\n\nexport interface CommandPaletteGroup {\n id: string;\n label?: string;\n items: CommandPaletteItem[];\n}\n\nexport interface CommandPaletteProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n groups: CommandPaletteGroup[];\n /** Nhóm \"Gần đây\" luôn nằm trên cùng khi chưa gõ gì */\n recent?: CommandPaletteItem[];\n /*\n * Ba chuỗi dưới đây: không truyền thì lấy namespace `commandPalette` của `ComitorLocaleProvider`\n * (mặc định tiếng Anh). Truyền prop là đè riêng chỗ này.\n */\n recentLabel?: string;\n placeholder?: string;\n emptyText?: string;\n /** Có mặt = app tự tìm (gọi API) → tắt lọc nội bộ của cmdk */\n onSearchChange?: (search: string) => void;\n loading?: boolean;\n /** Nội dung chân bảng (gợi ý phím, trạng thái) */\n footer?: ReactNode;\n /**\n * Tự gắn phím tắt Cmd/Ctrl+K để mở (mặc định true).\n *\n * Shell của Comitor cũng gắn phím tắt này; khi dùng trong shell hãy đặt `false` để tránh\n * hai listener cùng lật một state (mở rồi đóng ngay trong một lần bấm).\n */\n registerShortcut?: boolean;\n /** Ghi đè hai chuỗi sr-only của hộp thoại (mặc định theo provider) — xem `CommandPaletteLabels` */\n labels?: Partial<CommandPaletteLabels>;\n className?: string;\n}\n\n/**\n * Ô nhập của palette — dựng thẳng trên `cmdk` thay vì dùng `CommandInput` của tầng primitive.\n *\n * Lý do: cần thay icon kính lúp bằng vòng xoay khi đang tìm phía server, và cần chỗ đặt gợi ý\n * phím Esc bên phải — `CommandInput` khoá cứng cả hai chi tiết đó.\n */\nfunction PaletteInput({\n value,\n onValueChange,\n placeholder,\n loading\n}: {\n value: string;\n onValueChange: (value: string) => void;\n placeholder: string;\n loading: boolean;\n}) {\n return (\n <div className=\"flex h-12 items-center gap-2 border-b border-border px-3\">\n {loading ? (\n <Loader2 aria-hidden=\"true\" className=\"size-4 shrink-0 animate-spin text-muted-foreground\" />\n ) : (\n <Search aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n )}\n <CommandPrimitive.Input\n value={value}\n onValueChange={onValueChange}\n placeholder={placeholder}\n aria-label={placeholder}\n className=\"flex h-11 w-full bg-transparent text-sm outline-hidden placeholder:text-muted-foreground\"\n />\n <KeyboardHint keys=\"esc\" className=\"hidden sm:inline-flex\" />\n </div>\n );\n}\n\nfunction PaletteRow({ item }: { item: CommandPaletteItem }) {\n const Icon = item.icon;\n return (\n <>\n {Icon && <Icon aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />}\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate\">{item.label}</span>\n {item.description && <span className=\"block truncate text-xs text-muted-foreground\">{item.description}</span>}\n </span>\n {item.shortcut && <KeyboardHint keys={item.shortcut} className=\"ml-auto\" />}\n </>\n );\n}\n\n/**\n * Bảng lệnh (Cmd/Ctrl+K) — điều hướng nhanh và chạy lệnh, kiểu Linear/Raycast.\n *\n * Đặt ở tầng composite (không phải trong shell) vì app không dùng `AppShell` — trang cài đặt\n * độc lập, công cụ nội bộ — vẫn cần bảng lệnh. Shell chỉ giữ state mở/đóng và truyền xuống.\n *\n * Tìm kiếm mặc định KHÔNG DẤU tiếng Việt: gõ \"tao viec\" ra \"Tạo việc mới\".\n */\nexport function CommandPalette({\n open,\n onOpenChange,\n groups,\n recent = [],\n recentLabel: recentLabelProp,\n placeholder: placeholderProp,\n emptyText: emptyTextProp,\n onSearchChange,\n loading = false,\n footer,\n registerShortcut = true,\n labels: labelsProp,\n className\n}: CommandPaletteProps) {\n // Cả hai đường chữ đọc CHUNG một namespace: `labels` (hai chuỗi sr-only) trộn lên bảng, còn ba\n // prop rời thì lui về đúng khoá cùng tên của bảng đã trộn đó.\n const labels = useComitorLabels(\"commandPalette\", labelsProp);\n const recentLabel = recentLabelProp ?? labels.recentLabel;\n const placeholder = placeholderProp ?? labels.placeholder;\n const emptyText = emptyTextProp ?? labels.emptyText;\n const [search, setSearch] = useState(\"\");\n const isAsync = onSearchChange !== undefined;\n\n // Reset ô tìm mỗi lần mở: mở lại palette mà còn nguyên từ khoá cũ là hành vi gây bất ngờ.\n useEffect(() => {\n if (!open) setSearch(\"\");\n }, [open]);\n\n useEffect(() => {\n if (!registerShortcut) return;\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"k\" || !(event.metaKey || event.ctrlKey)) return;\n event.preventDefault();\n onOpenChange(!open);\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [registerShortcut, open, onOpenChange]);\n\n const handleSearch = (value: string) => {\n setSearch(value);\n onSearchChange?.(value);\n };\n\n const runItem = (item: CommandPaletteItem) => {\n if (item.disabled) return;\n onOpenChange(false);\n item.onSelect();\n };\n\n const showRecent = recent.length > 0 && search.trim() === \"\";\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent size=\"lg\" showCloseButton={false} className={cn(\"overflow-hidden p-0\", className)}>\n <DialogTitle className=\"sr-only\">{labels.title}</DialogTitle>\n <DialogDescription className=\"sr-only\">{labels.description}</DialogDescription>\n\n <Command\n shouldFilter={!isAsync}\n filter={(value, query, keywords) => {\n if (!query) return 1;\n const haystack = removeVietnameseTones(`${value} ${keywords?.join(\" \") ?? \"\"}`).toLowerCase();\n return haystack.includes(removeVietnameseTones(query).toLowerCase()) ? 1 : 0;\n }}\n className=\"bg-transparent\"\n >\n <PaletteInput value={search} onValueChange={handleSearch} placeholder={placeholder} loading={loading} />\n\n <CommandList className=\"max-h-[22rem]\">\n {!loading && <CommandEmpty>{emptyText}</CommandEmpty>}\n\n {showRecent && (\n <CommandGroup heading={recentLabel}>\n {recent.map((item) => (\n <CommandItem\n key={`recent-${item.id}`}\n value={`recent-${item.id}`}\n keywords={[item.label, item.description ?? \"\", ...(item.keywords ?? [])]}\n disabled={item.disabled}\n onSelect={() => runItem(item)}\n >\n <PaletteRow item={item} />\n </CommandItem>\n ))}\n </CommandGroup>\n )}\n\n {groups.map((group) => (\n <CommandGroup key={group.id} heading={group.label}>\n {group.items.map((item) => (\n <CommandItem\n key={item.id}\n value={item.id}\n keywords={[item.label, item.description ?? \"\", ...(item.keywords ?? [])]}\n disabled={item.disabled}\n onSelect={() => runItem(item)}\n >\n <PaletteRow item={item} />\n </CommandItem>\n ))}\n </CommandGroup>\n ))}\n </CommandList>\n\n {footer && (\n <div className=\"flex items-center gap-2 border-t border-border px-3 py-2 text-xs text-muted-foreground\">\n {footer}\n </div>\n )}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -16,11 +16,11 @@ interface ConfirmDialogProps {
|
|
|
16
16
|
confirmLabel?: string;
|
|
17
17
|
cancelLabel?: string;
|
|
18
18
|
/**
|
|
19
|
-
* Nhãn nút xác nhận trong lúc `onConfirm` bất đồng bộ đang chạy (
|
|
19
|
+
* Nhãn nút xác nhận trong lúc `onConfirm` bất đồng bộ đang chạy (khoá `pendingLabel`).
|
|
20
20
|
*
|
|
21
|
-
* Phải
|
|
22
|
-
* đây KHÔNG phải nhánh hiếm — app
|
|
23
|
-
*
|
|
21
|
+
* Phải đè được chứ không được chôn cứng: tài liệu khuyến khích `onConfirm` trả về Promise, nên
|
|
22
|
+
* đây KHÔNG phải nhánh hiếm — app đặt `confirmLabel="Xoá dự án"` mà quên chuỗi này sẽ thấy nút
|
|
23
|
+
* nhảy sang câu chung chung của bảng đúng vào giây người dùng đang nhìn nó.
|
|
24
24
|
*/
|
|
25
25
|
pendingLabel?: string;
|
|
26
26
|
/** "destructive" tô nút xác nhận màu đỏ — dùng cho xoá/thu hồi */
|
|
@@ -40,10 +40,10 @@ interface ConfirmDialogProps {
|
|
|
40
40
|
/**
|
|
41
41
|
* Hộp thoại xác nhận cho hành động không hoàn tác được (xoá việc, gỡ thành viên, huỷ gói).
|
|
42
42
|
*
|
|
43
|
-
* Bọc `AlertDialog` để mọi app có cùng một cách hỏi lại; nút xác nhận tự vào trạng thái
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* Bọc `AlertDialog` để mọi app có cùng một cách hỏi lại; nút xác nhận tự vào trạng thái đang
|
|
44
|
+
* chạy (chữ theo `pendingLabel`) khi `onConfirm` bất đồng bộ, và lỗi ném ra sẽ giữ dialog mở để
|
|
45
|
+
* người dùng thử lại.
|
|
46
46
|
*/
|
|
47
|
-
declare function ConfirmDialog({ open, onOpenChange, trigger, title, description, children, icon: Icon, confirmLabel, cancelLabel, pendingLabel, variant, onConfirm, confirmDisabled, onError, className }: ConfirmDialogProps): React.JSX.Element;
|
|
47
|
+
declare function ConfirmDialog({ open, onOpenChange, trigger, title, description, children, icon: Icon, confirmLabel: confirmLabelProp, cancelLabel: cancelLabelProp, pendingLabel: pendingLabelProp, variant, onConfirm, confirmDisabled, onError, className }: ConfirmDialogProps): React.JSX.Element;
|
|
48
48
|
|
|
49
49
|
export { ConfirmDialog, type ConfirmDialogProps };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
+
import { useComitorMessages } from "../../i18n/locale-provider.js";
|
|
4
5
|
import { cn } from "../../lib/cn.js";
|
|
5
6
|
import {
|
|
6
7
|
AlertDialog,
|
|
@@ -22,15 +23,19 @@ function ConfirmDialog({
|
|
|
22
23
|
description,
|
|
23
24
|
children,
|
|
24
25
|
icon: Icon,
|
|
25
|
-
confirmLabel
|
|
26
|
-
cancelLabel
|
|
27
|
-
pendingLabel
|
|
26
|
+
confirmLabel: confirmLabelProp,
|
|
27
|
+
cancelLabel: cancelLabelProp,
|
|
28
|
+
pendingLabel: pendingLabelProp,
|
|
28
29
|
variant = "default",
|
|
29
30
|
onConfirm,
|
|
30
31
|
confirmDisabled = false,
|
|
31
32
|
onError,
|
|
32
33
|
className
|
|
33
34
|
}) {
|
|
35
|
+
const t = useComitorMessages("confirmDialog");
|
|
36
|
+
const confirmLabel = confirmLabelProp ?? t.confirmLabel;
|
|
37
|
+
const cancelLabel = cancelLabelProp ?? t.cancelLabel;
|
|
38
|
+
const pendingLabel = pendingLabelProp ?? t.pendingLabel;
|
|
34
39
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
35
40
|
const [pending, setPending] = useState(false);
|
|
36
41
|
const isOpen = open ?? internalOpen;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/composite/confirm-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { type ReactNode, useState } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger\n} from \"../ui/alert-dialog.js\";\nimport { buttonVariants } from \"../ui/button.js\";\nimport type { IconComponent } from \"./empty-state.js\";\n\nexport interface ConfirmDialogProps {\n /** Controlled — bỏ trống thì dialog tự quản trạng thái mở (dùng kèm `trigger`) */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Phần tử mở dialog (đặt trong `AlertDialogTrigger asChild`) */\n trigger?: ReactNode;\n\n title: ReactNode;\n description?: ReactNode;\n /** Nội dung phụ (danh sách bản ghi sẽ bị ảnh hưởng, ô nhập lý do…) */\n children?: ReactNode;\n icon?: IconComponent;\n\n confirmLabel?: string;\n cancelLabel?: string;\n /**\n * Nhãn nút xác nhận trong lúc `onConfirm` bất đồng bộ đang chạy (
|
|
1
|
+
{"version":3,"sources":["../../../src/components/composite/confirm-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { type ReactNode, useState } from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger\n} from \"../ui/alert-dialog.js\";\nimport { buttonVariants } from \"../ui/button.js\";\nimport type { IconComponent } from \"./empty-state.js\";\n\nexport interface ConfirmDialogProps {\n /** Controlled — bỏ trống thì dialog tự quản trạng thái mở (dùng kèm `trigger`) */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Phần tử mở dialog (đặt trong `AlertDialogTrigger asChild`) */\n trigger?: ReactNode;\n\n title: ReactNode;\n description?: ReactNode;\n /** Nội dung phụ (danh sách bản ghi sẽ bị ảnh hưởng, ô nhập lý do…) */\n children?: ReactNode;\n icon?: IconComponent;\n\n /*\n * Ba nhãn nút dưới đây: không truyền thì lấy namespace `confirmDialog` của\n * `ComitorLocaleProvider` (mặc định tiếng Anh). Truyền prop là đè riêng chỗ này.\n */\n confirmLabel?: string;\n cancelLabel?: string;\n /**\n * Nhãn nút xác nhận trong lúc `onConfirm` bất đồng bộ đang chạy (khoá `pendingLabel`).\n *\n * Phải đè được chứ không được chôn cứng: tài liệu khuyến khích `onConfirm` trả về Promise, nên\n * đây KHÔNG phải nhánh hiếm — app đặt `confirmLabel=\"Xoá dự án\"` mà quên chuỗi này sẽ thấy nút\n * nhảy sang câu chung chung của bảng đúng vào giây người dùng đang nhìn nó.\n */\n pendingLabel?: string;\n /** \"destructive\" tô nút xác nhận màu đỏ — dùng cho xoá/thu hồi */\n variant?: \"default\" | \"destructive\";\n /**\n * Hành động khi xác nhận. Trả về Promise thì dialog giữ nguyên và bật trạng thái đang chạy\n * cho tới khi Promise xong — đóng ngay rồi mới gọi API là cách chắc chắn nhất để người dùng\n * bấm xoá hai lần.\n */\n onConfirm: () => void | Promise<void>;\n /** Khoá nút xác nhận (vd chưa gõ đúng tên bản ghi cần xoá) */\n confirmDisabled?: boolean;\n /** Nhận lỗi từ `onConfirm` để app hiện toast; không truyền thì lỗi được ghi ra console */\n onError?: (error: unknown) => void;\n className?: string;\n}\n\n/**\n * Hộp thoại xác nhận cho hành động không hoàn tác được (xoá việc, gỡ thành viên, huỷ gói).\n *\n * Bọc `AlertDialog` để mọi app có cùng một cách hỏi lại; nút xác nhận tự vào trạng thái đang\n * chạy (chữ theo `pendingLabel`) khi `onConfirm` bất đồng bộ, và lỗi ném ra sẽ giữ dialog mở để\n * người dùng thử lại.\n */\nexport function ConfirmDialog({\n open,\n onOpenChange,\n trigger,\n title,\n description,\n children,\n icon: Icon,\n confirmLabel: confirmLabelProp,\n cancelLabel: cancelLabelProp,\n pendingLabel: pendingLabelProp,\n variant = \"default\",\n onConfirm,\n confirmDisabled = false,\n onError,\n className\n}: ConfirmDialogProps) {\n const t = useComitorMessages(\"confirmDialog\");\n const confirmLabel = confirmLabelProp ?? t.confirmLabel;\n const cancelLabel = cancelLabelProp ?? t.cancelLabel;\n const pendingLabel = pendingLabelProp ?? t.pendingLabel;\n const [internalOpen, setInternalOpen] = useState(false);\n const [pending, setPending] = useState(false);\n\n const isOpen = open ?? internalOpen;\n const setOpen = (next: boolean) => {\n if (pending && !next) return; // đang chạy thì không cho đóng ngang\n if (open === undefined) setInternalOpen(next);\n onOpenChange?.(next);\n };\n\n const handleConfirm = async () => {\n try {\n setPending(true);\n await onConfirm();\n setPending(false);\n if (open === undefined) setInternalOpen(false);\n onOpenChange?.(false);\n } catch (error) {\n // Giữ dialog mở để người dùng thấy toast lỗi của app và thử lại.\n // KHÔNG ném tiếp: lời gọi là `void handleConfirm()` trong handler nên ném ra chỉ tạo\n // unhandled rejection mà app không bắt được — lỗi đi ra ngoài qua `onError`.\n setPending(false);\n if (onError) onError(error);\n else console.error(\"[ConfirmDialog] onConfirm thất bại:\", error);\n }\n };\n\n return (\n <AlertDialog open={isOpen} onOpenChange={setOpen}>\n {trigger && <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger>}\n <AlertDialogContent className={className}>\n <AlertDialogHeader>\n <div className=\"flex items-start gap-3\">\n {Icon && (\n <span\n aria-hidden=\"true\"\n // Cùng khuôn với `statCardIconVariants`: nền tint `--x` + icon bậc `--x-ink`.\n // `text-destructive` trên `bg-destructive/15` chỉ 4,06:1 (light) / 3,14:1 (dark) —\n // qua ngưỡng 3:1 của icon nhưng hết biên an toàn. Ink: 5,76:1 / 5,71:1 ✓.\n className={cn(\n \"flex size-9 shrink-0 items-center justify-center rounded-md\",\n variant === \"destructive\"\n ? \"bg-destructive/15 text-destructive-ink\"\n : \"bg-muted text-muted-foreground\"\n )}\n >\n <Icon className=\"size-5\" />\n </span>\n )}\n <div className=\"min-w-0 space-y-1.5\">\n <AlertDialogTitle>{title}</AlertDialogTitle>\n {description && <AlertDialogDescription>{description}</AlertDialogDescription>}\n </div>\n </div>\n </AlertDialogHeader>\n\n {children}\n\n <AlertDialogFooter>\n <AlertDialogCancel disabled={pending}>{cancelLabel}</AlertDialogCancel>\n <AlertDialogAction\n disabled={pending || confirmDisabled}\n onClick={(event) => {\n // Chặn hành vi tự đóng của Radix: dialog chỉ đóng sau khi onConfirm chạy xong.\n event.preventDefault();\n void handleConfirm();\n }}\n className={cn(variant === \"destructive\" && buttonVariants({ variant: \"destructive\" }))}\n >\n {pending ? pendingLabel : confirmLabel}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":";AAsHkB,cAoBN,YApBM;AApHlB,SAAyB,gBAAgB;AACzC,SAAS,0BAA0B;AACnC,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB;AAoDxB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,UAAU;AAAA,EACV;AAAA,EACA,kBAAkB;AAAA,EAClB;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,IAAI,mBAAmB,eAAe;AAC5C,QAAM,eAAe,oBAAoB,EAAE;AAC3C,QAAM,cAAc,mBAAmB,EAAE;AACzC,QAAM,eAAe,oBAAoB,EAAE;AAC3C,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AACtD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,QAAM,SAAS,QAAQ;AACvB,QAAM,UAAU,CAAC,SAAkB;AACjC,QAAI,WAAW,CAAC,KAAM;AACtB,QAAI,SAAS,OAAW,iBAAgB,IAAI;AAC5C,mBAAe,IAAI;AAAA,EACrB;AAEA,QAAM,gBAAgB,YAAY;AAChC,QAAI;AACF,iBAAW,IAAI;AACf,YAAM,UAAU;AAChB,iBAAW,KAAK;AAChB,UAAI,SAAS,OAAW,iBAAgB,KAAK;AAC7C,qBAAe,KAAK;AAAA,IACtB,SAAS,OAAO;AAId,iBAAW,KAAK;AAChB,UAAI,QAAS,SAAQ,KAAK;AAAA,UACrB,SAAQ,MAAM,iDAAuC,KAAK;AAAA,IACjE;AAAA,EACF;AAEA,SACE,qBAAC,eAAY,MAAM,QAAQ,cAAc,SACtC;AAAA,eAAW,oBAAC,sBAAmB,SAAO,MAAE,mBAAQ;AAAA,IACjD,qBAAC,sBAAmB,WAClB;AAAA,0BAAC,qBACC,+BAAC,SAAI,WAAU,0BACZ;AAAA,gBACC;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YAIZ,WAAW;AAAA,cACT;AAAA,cACA,YAAY,gBACR,2CACA;AAAA,YACN;AAAA,YAEA,8BAAC,QAAK,WAAU,UAAS;AAAA;AAAA,QAC3B;AAAA,QAEF,qBAAC,SAAI,WAAU,uBACb;AAAA,8BAAC,oBAAkB,iBAAM;AAAA,UACxB,eAAe,oBAAC,0BAAwB,uBAAY;AAAA,WACvD;AAAA,SACF,GACF;AAAA,MAEC;AAAA,MAED,qBAAC,qBACC;AAAA,4BAAC,qBAAkB,UAAU,SAAU,uBAAY;AAAA,QACnD;AAAA,UAAC;AAAA;AAAA,YACC,UAAU,WAAW;AAAA,YACrB,SAAS,CAAC,UAAU;AAElB,oBAAM,eAAe;AACrB,mBAAK,cAAc;AAAA,YACrB;AAAA,YACA,WAAW,GAAG,YAAY,iBAAiB,eAAe,EAAE,SAAS,cAAc,CAAC,CAAC;AAAA,YAEpF,oBAAU,eAAe;AAAA;AAAA,QAC5B;AAAA,SACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -6,9 +6,13 @@ import 'class-variance-authority';
|
|
|
6
6
|
interface CopyButtonProps extends Omit<ButtonProps, "children" | "onCopy"> {
|
|
7
7
|
/** Nội dung được chép vào clipboard */
|
|
8
8
|
value: string;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Nhãn khi chưa chép. Không truyền (`undefined`) thì lấy khoá `label` của namespace `copyButton`
|
|
11
|
+
* (`ComitorLocaleProvider`, mặc định tiếng Anh). `null` = chỉ icon — khi đó khoá `label` vẫn là
|
|
12
|
+
* `aria-label` của nút.
|
|
13
|
+
*/
|
|
10
14
|
label?: string | null;
|
|
11
|
-
/** Nhãn sau khi chép thành công */
|
|
15
|
+
/** Nhãn sau khi chép thành công (khoá `copiedLabel` của namespace `copyButton`) */
|
|
12
16
|
copiedLabel?: string;
|
|
13
17
|
/** Thời gian giữ trạng thái "đã chép" (ms) */
|
|
14
18
|
resetAfterMs?: number;
|
|
@@ -22,6 +26,6 @@ interface CopyButtonProps extends Omit<ButtonProps, "children" | "onCopy"> {
|
|
|
22
26
|
* WebView cũ): tạo textarea ẩn rồi `execCommand("copy")`. Thiếu đường lui này thì nút im lặng
|
|
23
27
|
* không làm gì — người dùng tưởng đã chép và dán ra nội dung cũ.
|
|
24
28
|
*/
|
|
25
|
-
declare function CopyButton({ value, label, copiedLabel, resetAfterMs, onCopied, onCopyError, variant, size, className, ...props }: CopyButtonProps): React.JSX.Element;
|
|
29
|
+
declare function CopyButton({ value, label: labelProp, copiedLabel: copiedLabelProp, resetAfterMs, onCopied, onCopyError, variant, size, className, ...props }: CopyButtonProps): React.JSX.Element;
|
|
26
30
|
|
|
27
31
|
export { CopyButton, type CopyButtonProps };
|