@eifi1/ui-kit 0.4.1 → 0.5.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/LICENSE +32 -0
- package/README.md +498 -57
- package/dist/chart.d.ts +10 -0
- package/dist/chart.js +10 -0
- package/dist/chart.js.map +1 -0
- package/dist/components/account-settings.d.ts +11 -0
- package/dist/components/account-settings.js +19 -12
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/alert-banner.d.ts +10 -6
- package/dist/components/alert-banner.js +20 -16
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +30 -4
- package/dist/components/amount-input.js +36 -14
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calculator.d.ts +24 -7
- package/dist/components/calculator.js +37 -20
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +156 -0
- package/dist/components/chart-zoom.js +262 -0
- package/dist/components/chart-zoom.js.map +1 -0
- package/dist/components/chart.d.ts +28 -3
- package/dist/components/chart.js +71 -32
- package/dist/components/chart.js.map +1 -1
- package/dist/components/checkbox.d.ts +51 -0
- package/dist/components/checkbox.js +123 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip.d.ts +128 -0
- package/dist/components/chip.js +357 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/choice-parts.d.ts +24 -0
- package/dist/components/choice-parts.js +18 -0
- package/dist/components/choice-parts.js.map +1 -0
- package/dist/components/combobox-core.d.ts +52 -9
- package/dist/components/combobox-core.js +89 -19
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +43 -33
- package/dist/components/combobox.js +409 -340
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/currency-select.d.ts +48 -8
- package/dist/components/currency-select.js +107 -65
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +15 -3
- package/dist/components/data-table-filter-popover.js +23 -17
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +3 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +53 -2
- package/dist/components/data-table-labels.js +31 -2
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table-pagination.js +16 -10
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-sort.js +1 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +201 -85
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +25 -9
- package/dist/components/date-picker.js +232 -94
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/dropdown.d.ts +74 -19
- package/dist/components/dropdown.js +72 -22
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +5 -4
- package/dist/components/entity-combobox.js +107 -76
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +36 -0
- package/dist/components/facing-pair.js +40 -0
- package/dist/components/facing-pair.js.map +1 -0
- package/dist/components/field-sync.d.ts +164 -0
- package/dist/components/field-sync.js +264 -0
- package/dist/components/field-sync.js.map +1 -0
- package/dist/components/file-dropzone.d.ts +21 -7
- package/dist/components/file-dropzone.js +28 -7
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +37 -26
- package/dist/components/full-bleed-dialog.js +20 -5
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/grouped-picker.d.ts +21 -10
- package/dist/components/grouped-picker.js +66 -52
- package/dist/components/grouped-picker.js.map +1 -1
- package/dist/components/hover-menu.d.ts +20 -4
- package/dist/components/hover-menu.js +15 -4
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +102 -14
- package/dist/components/mini-calendar.js +256 -70
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +9 -3
- package/dist/components/modal.js +34 -45
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +79 -0
- package/dist/components/month-picker.js +342 -0
- package/dist/components/month-picker.js.map +1 -0
- package/dist/components/multi-entity-combobox.d.ts +7 -6
- package/dist/components/multi-entity-combobox.js +112 -72
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.d.ts +13 -11
- package/dist/components/multi-select.js +161 -84
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +92 -0
- package/dist/components/number-field.js +111 -0
- package/dist/components/number-field.js.map +1 -0
- package/dist/components/number-input.d.ts +21 -2
- package/dist/components/number-input.js +2 -1
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +25 -4
- package/dist/components/numpad-sheet.js +30 -13
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-contents.d.ts +90 -0
- package/dist/components/page-contents.js +170 -0
- package/dist/components/page-contents.js.map +1 -0
- package/dist/components/password-strength.d.ts +106 -0
- package/dist/components/password-strength.js +132 -0
- package/dist/components/password-strength.js.map +1 -0
- package/dist/components/picker-sheet.d.ts +23 -15
- package/dist/components/picker-sheet.js +34 -14
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/popover.d.ts +62 -4
- package/dist/components/popover.js +52 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/search-field.d.ts +25 -7
- package/dist/components/search-field.js +7 -5
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +14 -0
- package/dist/components/series-chart-labels.js +11 -0
- package/dist/components/series-chart-labels.js.map +1 -0
- package/dist/components/series-chart.d.ts +207 -0
- package/dist/components/series-chart.js +223 -0
- package/dist/components/series-chart.js.map +1 -0
- package/dist/components/settings-fields.d.ts +14 -4
- package/dist/components/settings-fields.js +19 -8
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/signature-pad.d.ts +119 -0
- package/dist/components/signature-pad.js +405 -0
- package/dist/components/signature-pad.js.map +1 -0
- package/dist/components/slider.d.ts +80 -0
- package/dist/components/slider.js +155 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sparkline.d.ts +93 -0
- package/dist/components/sparkline.js +205 -0
- package/dist/components/sparkline.js.map +1 -0
- package/dist/components/stat-tile.d.ts +151 -0
- package/dist/components/stat-tile.js +236 -0
- package/dist/components/stat-tile.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +25 -2
- package/dist/components/swipeable-row.js +31 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/switch.d.ts +60 -0
- package/dist/components/switch.js +81 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/time-input.d.ts +58 -0
- package/dist/components/time-input.js +50 -0
- package/dist/components/time-input.js.map +1 -0
- package/dist/components/toggle-group.d.ts +18 -3
- package/dist/components/toggle-group.js +10 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +115 -0
- package/dist/components/toggle-legend.js +114 -0
- package/dist/components/toggle-legend.js.map +1 -0
- package/dist/components/tooltip.d.ts +40 -11
- package/dist/components/tooltip.js +77 -20
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/treemap.d.ts +132 -0
- package/dist/components/treemap.js +179 -0
- package/dist/components/treemap.js.map +1 -0
- package/dist/components/ui.d.ts +159 -46
- package/dist/components/ui.js +220 -115
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-mobile-reveal.js +1 -0
- package/dist/components/use-mobile-reveal.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/use-table-state.js +38 -17
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +5 -3
- package/dist/components/user-avatar.js +4 -2
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.js +3 -2
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
- package/dist/data-table.d.ts +7 -0
- package/dist/data-table.js +12 -0
- package/dist/data-table.js.map +1 -0
- package/dist/feedback/feedback-attachment.js +12 -11
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.js +2 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +33 -7
- package/dist/feedback/feedback-inbox.js +49 -37
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback.d.ts +5 -0
- package/dist/feedback.js +5 -0
- package/dist/feedback.js.map +1 -0
- package/dist/hooks/use-anchored-panel.js +1 -0
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-anchored-rect.js +1 -0
- package/dist/hooks/use-anchored-rect.js.map +1 -1
- package/dist/hooks/use-announce.d.ts +46 -0
- package/dist/hooks/use-announce.js +37 -0
- package/dist/hooks/use-announce.js.map +1 -0
- package/dist/hooks/use-body-scroll-lock.js +1 -0
- package/dist/hooks/use-body-scroll-lock.js.map +1 -1
- package/dist/hooks/use-close-transition.js +1 -0
- package/dist/hooks/use-close-transition.js.map +1 -1
- package/dist/hooks/use-dismiss.js +1 -0
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +16 -0
- package/dist/hooks/use-focus-trap.js +67 -0
- package/dist/hooks/use-focus-trap.js.map +1 -0
- package/dist/hooks/use-media-query.js +1 -0
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-overlay-history.js +27 -1
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/hooks/use-row-swipe.js +1 -0
- package/dist/hooks/use-row-swipe.js.map +1 -1
- package/dist/i18n/defaults.d.ts +31 -0
- package/dist/i18n/defaults.js +63 -0
- package/dist/i18n/defaults.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +250 -0
- package/dist/i18n/kit-labels.js +184 -0
- package/dist/i18n/kit-labels.js.map +1 -0
- package/dist/index.d.ts +47 -22
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/calc.js +1 -0
- package/dist/lib/calc.js.map +1 -1
- package/dist/lib/cn.js +1 -0
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/dates.js +1 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/logger.js +1 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/safe-storage.js +1 -0
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/search/command-palette.d.ts +11 -1
- package/dist/search/command-palette.js +21 -15
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search.d.ts +2 -0
- package/dist/search.js +3 -0
- package/dist/search.js.map +1 -0
- package/dist/shell/app-shell.d.ts +33 -4
- package/dist/shell/app-shell.js +275 -78
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/option-switcher-menu.js +3 -2
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +6 -0
- package/dist/shell/role-switcher.js +7 -4
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/top-bar.d.ts +15 -7
- package/dist/shell/top-bar.js +3 -5
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/shell/topbar-action-menu.js +3 -2
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +5 -2
- package/dist/shell/topbar-controls.js +20 -15
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/shell.d.ts +13 -0
- package/dist/shell.js +8 -0
- package/dist/shell.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +19 -12
- package/dist/theme/chart-palette.js +24 -30
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/color.d.ts +78 -0
- package/dist/theme/color.js +148 -0
- package/dist/theme/color.js.map +1 -0
- package/dist/theme/palette-derive.d.ts +140 -0
- package/dist/theme/palette-derive.js +376 -0
- package/dist/theme/palette-derive.js.map +1 -0
- package/dist/theme/palette-presets.d.ts +14 -1
- package/dist/theme/palette-presets.js +231 -3
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js +1 -0
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/theme-store.js +1 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour.d.ts +2 -1
- package/dist/tour/tour.js +15 -9
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -0
- package/dist/wizard/stepper-nav.js +10 -8
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +18 -2
- package/dist/wizard/types.js +4 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +12 -4
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.js +1 -0
- package/dist/wizard/validation.js.map +1 -1
- package/dist/wizard/wizard-context.js +1 -0
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.js +1 -0
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard/wizard-summary.js +5 -3
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +9 -0
- package/dist/wizard.js.map +1 -0
- package/package.json +66 -13
- package/src/chart.ts +22 -0
- package/src/components/account-settings.tsx +275 -0
- package/src/components/alert-banner.tsx +75 -0
- package/src/components/amount-input.tsx +471 -0
- package/src/components/calculator.tsx +252 -0
- package/src/components/chart-zoom.tsx +499 -0
- package/src/components/chart.tsx +363 -0
- package/src/components/checkbox.tsx +222 -0
- package/src/components/chip.tsx +570 -0
- package/src/components/choice-parts.ts +33 -0
- package/src/components/combobox-core.tsx +540 -0
- package/src/components/combobox.tsx +1026 -0
- package/src/components/currency-select.tsx +269 -0
- package/src/components/data-table-filter-popover.tsx +252 -0
- package/src/components/data-table-filters.ts +176 -0
- package/src/components/data-table-labels.ts +190 -0
- package/src/components/data-table-pagination.tsx +126 -0
- package/src/components/data-table-sort.ts +70 -0
- package/src/components/data-table.tsx +1740 -0
- package/src/components/date-picker.tsx +631 -0
- package/src/components/dropdown.tsx +341 -0
- package/src/components/entity-combobox.tsx +215 -0
- package/src/components/facing-pair.ts +86 -0
- package/src/components/field-sync.tsx +472 -0
- package/src/components/file-dropzone.tsx +181 -0
- package/src/components/full-bleed-dialog.tsx +179 -0
- package/src/components/grouped-picker.tsx +157 -0
- package/src/components/hover-menu.tsx +183 -0
- package/src/components/mini-calendar.tsx +554 -0
- package/src/components/modal.tsx +287 -0
- package/src/components/month-picker.tsx +549 -0
- package/src/components/multi-entity-combobox.tsx +226 -0
- package/src/components/multi-select.tsx +303 -0
- package/src/components/number-field.tsx +225 -0
- package/src/components/number-input.tsx +211 -0
- package/src/components/numpad-sheet.tsx +264 -0
- package/src/components/page-contents.tsx +289 -0
- package/src/components/password-strength.tsx +258 -0
- package/src/components/picker-sheet.tsx +251 -0
- package/src/components/popover.tsx +195 -0
- package/src/components/search-field.tsx +146 -0
- package/src/components/series-chart-labels.ts +26 -0
- package/src/components/series-chart.tsx +481 -0
- package/src/components/settings-fields.tsx +68 -0
- package/src/components/signature-pad.tsx +630 -0
- package/src/components/slider.tsx +313 -0
- package/src/components/sparkline.tsx +329 -0
- package/src/components/stat-tile.tsx +448 -0
- package/src/components/swipeable-row.tsx +271 -0
- package/src/components/switch.tsx +170 -0
- package/src/components/time-input.tsx +107 -0
- package/src/components/toggle-group.tsx +153 -0
- package/src/components/toggle-legend.tsx +229 -0
- package/src/components/tooltip.tsx +477 -0
- package/src/components/treemap.tsx +390 -0
- package/src/components/ui.tsx +1284 -0
- package/src/components/use-mobile-reveal.ts +54 -0
- package/src/components/use-table-state.ts +295 -0
- package/src/components/user-avatar.tsx +61 -0
- package/src/components/wizard-stepper.tsx +37 -0
- package/src/data-table.ts +19 -0
- package/src/feedback/feedback-attachment.tsx +246 -0
- package/src/feedback/feedback-dialog.tsx +209 -0
- package/src/feedback/feedback-inbox.tsx +615 -0
- package/src/feedback.ts +8 -0
- package/src/hooks/use-anchored-panel.ts +169 -0
- package/src/hooks/use-anchored-rect.ts +78 -0
- package/src/hooks/use-announce.ts +85 -0
- package/src/hooks/use-body-scroll-lock.ts +94 -0
- package/src/hooks/use-close-transition.ts +99 -0
- package/src/hooks/use-dismiss.ts +77 -0
- package/src/hooks/use-focus-trap.ts +160 -0
- package/src/hooks/use-media-query.ts +23 -0
- package/src/hooks/use-overlay-history.ts +345 -0
- package/src/hooks/use-row-swipe.ts +212 -0
- package/src/i18n/defaults.ts +68 -0
- package/src/i18n/kit-labels.tsx +447 -0
- package/src/index.ts +169 -0
- package/src/lib/calc.ts +309 -0
- package/src/lib/cn.ts +5 -0
- package/src/lib/dates.ts +187 -0
- package/src/lib/logger.ts +73 -0
- package/src/lib/safe-storage.ts +37 -0
- package/src/search/command-palette.tsx +342 -0
- package/src/search.ts +3 -0
- package/src/shell/app-shell.tsx +772 -0
- package/src/shell/option-switcher-menu.tsx +72 -0
- package/src/shell/role-switcher.tsx +46 -0
- package/src/shell/top-bar.tsx +39 -0
- package/src/shell/topbar-action-menu.tsx +91 -0
- package/src/shell/topbar-controls.tsx +229 -0
- package/src/shell.ts +10 -0
- package/src/theme/chart-palette.ts +145 -0
- package/src/theme/color.ts +266 -0
- package/src/theme/palette-derive.ts +681 -0
- package/src/theme/palette-presets.ts +760 -0
- package/src/theme/palette-store.ts +94 -0
- package/src/theme/theme-store.ts +145 -0
- package/src/tour/tour.tsx +586 -0
- package/src/tour.ts +3 -0
- package/src/vite-env.d.ts +26 -0
- package/src/wizard/stepper-nav.tsx +221 -0
- package/src/wizard/types.ts +178 -0
- package/src/wizard/use-wizard.ts +345 -0
- package/src/wizard/validation.ts +37 -0
- package/src/wizard/wizard-context.tsx +29 -0
- package/src/wizard/wizard-step.tsx +14 -0
- package/src/wizard/wizard-summary.tsx +69 -0
- package/src/wizard.ts +14 -0
- package/tokens.css +222 -20
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
+
import { useId, useMemo } from "react";
|
|
3
4
|
import { X } from "lucide-react";
|
|
4
5
|
import { cn } from "../lib/cn.js";
|
|
5
6
|
import { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from "./ui.js";
|
|
6
7
|
import { ComboboxPanel, useComboboxCore } from "./combobox-core.js";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_COMBOBOX_LABELS,
|
|
10
|
+
DEFAULT_COMMON_LABELS,
|
|
11
|
+
useKitLabels,
|
|
12
|
+
useKitLocale
|
|
13
|
+
} from "../i18n/kit-labels.js";
|
|
7
14
|
function MultiEntityCombobox({
|
|
8
15
|
value,
|
|
9
16
|
onChange,
|
|
@@ -12,8 +19,8 @@ function MultiEntityCombobox({
|
|
|
12
19
|
loading,
|
|
13
20
|
label,
|
|
14
21
|
placeholder,
|
|
15
|
-
searchPlaceholder
|
|
16
|
-
emptyLabel
|
|
22
|
+
searchPlaceholder,
|
|
23
|
+
emptyLabel,
|
|
17
24
|
itemLabel,
|
|
18
25
|
clearable,
|
|
19
26
|
clearLabel,
|
|
@@ -22,16 +29,31 @@ function MultiEntityCombobox({
|
|
|
22
29
|
onCreate,
|
|
23
30
|
createLabel,
|
|
24
31
|
className,
|
|
25
|
-
invalid
|
|
32
|
+
invalid,
|
|
33
|
+
"aria-label": ariaLabel,
|
|
34
|
+
...rest
|
|
26
35
|
}) {
|
|
27
36
|
const core = useComboboxCore({ options, loadOptions, loading });
|
|
28
|
-
const
|
|
37
|
+
const labels = useKitLabels("combobox", DEFAULT_COMBOBOX_LABELS, {
|
|
38
|
+
search: searchPlaceholder,
|
|
39
|
+
noResults: emptyLabel,
|
|
40
|
+
clear: clearLabel,
|
|
41
|
+
create: createLabel
|
|
42
|
+
});
|
|
43
|
+
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
44
|
+
const { open, results, resolve, setOpen } = core;
|
|
45
|
+
const listboxId = useId();
|
|
29
46
|
const valueSet = useMemo(() => new Set(value), [value]);
|
|
47
|
+
const locale = useKitLocale();
|
|
48
|
+
const listFormat = useMemo(
|
|
49
|
+
() => new Intl.ListFormat(locale, { type: "conjunction", style: "narrow" }),
|
|
50
|
+
[locale]
|
|
51
|
+
);
|
|
30
52
|
const summary = (() => {
|
|
31
53
|
if (value.length === 0) return placeholder ?? "";
|
|
32
54
|
if (itemLabel) return itemLabel(value.length);
|
|
33
55
|
const chosen = value.map(resolve);
|
|
34
|
-
return chosen.every(Boolean) ? chosen.map((o) => o.label)
|
|
56
|
+
return chosen.every(Boolean) ? listFormat.format(chosen.map((o) => o.label)) : labels.selectedCount(value.length);
|
|
35
57
|
})();
|
|
36
58
|
const q = core.query.trim();
|
|
37
59
|
const showCreate = Boolean(onCreate) && q.length > 0 && !results.some((o) => o.label.toLowerCase() === q.toLowerCase());
|
|
@@ -41,74 +63,92 @@ function MultiEntityCombobox({
|
|
|
41
63
|
if (valueSet.has(o.value)) onChange(value.filter((v) => v !== o.value));
|
|
42
64
|
else onChange([...value, o.value]);
|
|
43
65
|
};
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// Inherit FIELD_BASE's ink for a value, keep the placeholder grey — the
|
|
69
|
-
// same rule every field on a form row follows (Keksdose dev#477).
|
|
70
|
-
!value.length && "text-slate-400 dark:text-slate-500"
|
|
71
|
-
),
|
|
72
|
-
children: summary
|
|
66
|
+
return (
|
|
67
|
+
// `rest` dresses the wrapper, which has no role; the accessible NAME goes on
|
|
68
|
+
// the trigger, which has one. Spread FIRST so the trigger's ARIA and the
|
|
69
|
+
// handlers that open the panel cannot be clobbered from outside.
|
|
70
|
+
/* @__PURE__ */ jsxs("div", { ...rest, className: cn("relative", className), children: [
|
|
71
|
+
label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
|
|
72
|
+
/* @__PURE__ */ jsxs(
|
|
73
|
+
"button",
|
|
74
|
+
{
|
|
75
|
+
ref: core.triggerRef,
|
|
76
|
+
type: "button",
|
|
77
|
+
role: "combobox",
|
|
78
|
+
"aria-expanded": open,
|
|
79
|
+
"aria-controls": listboxId,
|
|
80
|
+
"aria-haspopup": "listbox",
|
|
81
|
+
"aria-label": ariaLabel ?? (typeof label === "string" ? common.fieldValue(label, summary) : void 0),
|
|
82
|
+
disabled,
|
|
83
|
+
"aria-invalid": invalid || void 0,
|
|
84
|
+
onClick: () => !disabled && setOpen((o) => !o),
|
|
85
|
+
onKeyDown: (e) => {
|
|
86
|
+
if (disabled) return;
|
|
87
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
setOpen(true);
|
|
73
90
|
}
|
|
91
|
+
},
|
|
92
|
+
className: cn(
|
|
93
|
+
FIELD_TRIGGER,
|
|
94
|
+
"pr-9",
|
|
95
|
+
label !== void 0 && FIELD_FLOATING_PAD,
|
|
96
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
97
|
+
invalid && FIELD_INVALID
|
|
74
98
|
),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ jsx(
|
|
101
|
+
"span",
|
|
102
|
+
{
|
|
103
|
+
className: cn(
|
|
104
|
+
"min-w-0 truncate",
|
|
105
|
+
// Inherit FIELD_BASE's ink for a value, keep the placeholder tone
|
|
106
|
+
// (`--text-placeholder`) — the same rule every field on a form row
|
|
107
|
+
// follows (Keksdose dev#477).
|
|
108
|
+
!value.length && "text-[var(--text-placeholder)]"
|
|
109
|
+
),
|
|
110
|
+
children: summary
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
showClear ? /* @__PURE__ */ jsx(
|
|
114
|
+
"span",
|
|
115
|
+
{
|
|
116
|
+
role: "button",
|
|
117
|
+
tabIndex: -1,
|
|
118
|
+
"aria-label": labels.clear,
|
|
119
|
+
onClick: (e) => {
|
|
120
|
+
e.stopPropagation();
|
|
121
|
+
onChange([]);
|
|
122
|
+
},
|
|
123
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]",
|
|
124
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
125
|
+
}
|
|
126
|
+
) : /* @__PURE__ */ jsx(FieldChevron, {})
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
),
|
|
130
|
+
/* @__PURE__ */ jsx(
|
|
131
|
+
ComboboxPanel,
|
|
132
|
+
{
|
|
133
|
+
core,
|
|
134
|
+
listboxId,
|
|
135
|
+
multi: true,
|
|
136
|
+
sheetTitle: label ?? placeholder,
|
|
137
|
+
searchPlaceholder: labels.search,
|
|
138
|
+
emptyLabel: labels.noResults,
|
|
139
|
+
closeLabel,
|
|
140
|
+
isSelected: (v) => valueSet.has(v),
|
|
141
|
+
onChoose: toggle,
|
|
142
|
+
showCreate,
|
|
143
|
+
onCreate: () => {
|
|
144
|
+
onCreate?.(q);
|
|
145
|
+
core.setQuery("");
|
|
146
|
+
},
|
|
147
|
+
createContent: labels.create(q)
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
] })
|
|
151
|
+
);
|
|
112
152
|
}
|
|
113
153
|
export {
|
|
114
154
|
MultiEntityCombobox
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/multi-entity-combobox.tsx"],"sourcesContent":["import { useMemo, type ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from \"./ui\";\nimport { ComboboxPanel, useComboboxCore, type ComboOption } from \"./combobox-core\";\n\nexport interface MultiEntityComboboxProps<V extends string | number> {\n /** Selected ids. */\n value: V[];\n onChange: (value: V[]) => void;\n /** Already-loaded options (client-side filtered); also resolves selected labels. */\n options?: ComboOption<V>[];\n /** Async option source, debounced + race-safe. When set, `options` resolves\n * labels only. */\n loadOptions?: (query: string) => Promise<ComboOption<V>[]>;\n loading?: boolean;\n label?: ReactNode;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyLabel?: string;\n /** Trigger summary when 1+ selected (default: the labels joined by \", \", or\n * \"N selected\" when a label can't be resolved). */\n itemLabel?: (count: number) => string;\n clearable?: boolean;\n clearLabel?: string;\n /** The phone sheet's close button. Its own prop rather than a reuse of\n * `clearLabel`: \"clear the selection\" and \"close this screen\" are different\n * actions, and on a full-screen sheet the close button is the only way out —\n * so it is the one control here that MUST be in the reader's language. */\n closeLabel?: string;\n disabled?: boolean;\n /** When set, a \"create\" row appears for a non-empty query with no exact match.\n * The panel stays open (adding the new entity to `value` is the caller's job). */\n onCreate?: (query: string) => void;\n createLabel?: (query: string) => string;\n className?: string;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n}\n\n/**\n * Multi-value sibling of {@link EntityCombobox}: pick several entities by search,\n * with the same async `loadOptions`/`onCreate` support. Rows show a checkbox and\n * toggle without closing the panel; the trigger summarises the selection.\n */\nexport function MultiEntityCombobox<V extends string | number>({\n value,\n onChange,\n options,\n loadOptions,\n loading,\n label,\n placeholder,\n searchPlaceholder = \"Search\",\n emptyLabel = \"No results\",\n itemLabel,\n clearable,\n clearLabel,\n closeLabel,\n disabled,\n onCreate,\n createLabel,\n className,\n invalid,\n}: MultiEntityComboboxProps<V>) {\n const core = useComboboxCore<V>({ options, loadOptions, loading });\n const { results, resolve, setOpen } = core;\n\n const valueSet = useMemo(() => new Set(value), [value]);\n const summary = (() => {\n if (value.length === 0) return placeholder ?? \"\";\n if (itemLabel) return itemLabel(value.length);\n const chosen = value.map(resolve);\n return chosen.every(Boolean)\n ? chosen.map((o) => o!.label).join(\", \")\n : `${value.length} selected`;\n })();\n\n const q = core.query.trim();\n const showCreate =\n Boolean(onCreate) && q.length > 0 && !results.some((o) => o.label.toLowerCase() === q.toLowerCase());\n const showClear = Boolean(clearable && value.length > 0 && !disabled);\n\n const toggle = (o: ComboOption<V>) => {\n core.cacheRef.current.set(o.value, o);\n if (valueSet.has(o.value)) onChange(value.filter((v) => v !== o.value));\n else onChange([...value, o.value]);\n // Stay open — multi-select keeps adding.\n };\n\n return (\n <div className={cn(\"relative\", className)}>\n {label !== undefined && <FieldLabel>{label}</FieldLabel>}\n <button\n ref={core.triggerRef}\n type=\"button\"\n // The label is a floating <span>, not a <label for>, so without this the\n // trigger's accessible name is whatever value happens to be selected —\n // \"Checking\" with nothing saying it is the account. The label AND the\n // value, because `aria-label` replaces the content rather than adding to\n // it, and a control that announces only its name has lost the answer.\n aria-label={\n typeof label === \"string\" ? `${label}: ${summary}` : undefined\n }\n disabled={disabled}\n aria-invalid={invalid || undefined}\n onClick={() => !disabled && setOpen((o) => !o)}\n className={cn(\n FIELD_TRIGGER,\n \"pr-9\",\n label !== undefined && FIELD_FLOATING_PAD,\n disabled && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n )}\n >\n <span\n className={cn(\n \"min-w-0 truncate\",\n // Inherit FIELD_BASE's ink for a value, keep the placeholder grey — the\n // same rule every field on a form row follows (Keksdose dev#477).\n !value.length && \"text-slate-400 dark:text-slate-500\",\n )}\n >\n {summary}\n </span>\n {showClear ? (\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label={clearLabel ?? \"Clear\"}\n onClick={(e) => {\n e.stopPropagation();\n onChange([]);\n }}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-300\"\n >\n <X className=\"size-4\" />\n </span>\n ) : (\n <FieldChevron />\n )}\n </button>\n <ComboboxPanel\n core={core}\n multi\n // On a phone the panel becomes a full-screen sheet, which needs the field's\n // own label to say what it is asking for (live #200).\n sheetTitle={label ?? placeholder}\n searchPlaceholder={searchPlaceholder}\n emptyLabel={emptyLabel}\n closeLabel={closeLabel}\n isSelected={(v) => valueSet.has(v)}\n onChoose={toggle}\n showCreate={showCreate}\n onCreate={() => {\n onCreate?.(q);\n core.setQuery(\"\");\n }}\n createContent={createLabel ? createLabel(q) : `Create “${q}”`}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/multi-entity-combobox.tsx"],"sourcesContent":["import { useId, useMemo, type ComponentPropsWithoutRef, type ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from \"./ui\";\nimport { ComboboxPanel, useComboboxCore, type ComboOption } from \"./combobox-core\";\nimport {\n DEFAULT_COMBOBOX_LABELS,\n DEFAULT_COMMON_LABELS,\n useKitLabels,\n useKitLocale,\n} from \"../i18n/kit-labels\";\n\n/** `onChange` is the kit's — \"a selection was made\", carrying values — rather\n * than the div's form event, so the DOM's spelling of it is omitted. */\nexport interface MultiEntityComboboxProps<V extends string | number>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n /** Selected ids. */\n value: V[];\n onChange: (value: V[]) => void;\n /** Already-loaded options (client-side filtered); also resolves selected labels. */\n options?: ComboOption<V>[];\n /** Async option source, debounced + race-safe. When set, `options` resolves\n * labels only. */\n loadOptions?: (query: string) => Promise<ComboOption<V>[]>;\n loading?: boolean;\n label?: ReactNode;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyLabel?: string;\n /** Trigger summary when 1+ selected (default: the labels as a locale-formatted\n * list, or `combobox.selectedCount` when a label can't be resolved). */\n itemLabel?: (count: number) => string;\n clearable?: boolean;\n clearLabel?: string;\n /** The phone sheet's close button. Its own prop rather than a reuse of\n * `clearLabel`: \"clear the selection\" and \"close this screen\" are different\n * actions, and on a full-screen sheet the close button is the only way out —\n * so it is the one control here that MUST be in the reader's language. */\n closeLabel?: string;\n disabled?: boolean;\n /** When set, a \"create\" row appears for a non-empty query with no exact match.\n * The panel stays open (adding the new entity to `value` is the caller's job). */\n onCreate?: (query: string) => void;\n createLabel?: (query: string) => string;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n}\n\n/**\n * Multi-value sibling of {@link EntityCombobox}: pick several entities by search,\n * with the same async `loadOptions`/`onCreate` support. Rows show a checkbox and\n * toggle without closing the panel; the trigger summarises the selection.\n */\nexport function MultiEntityCombobox<V extends string | number>({\n value,\n onChange,\n options,\n loadOptions,\n loading,\n label,\n placeholder,\n searchPlaceholder,\n emptyLabel,\n itemLabel,\n clearable,\n clearLabel,\n closeLabel,\n disabled,\n onCreate,\n createLabel,\n className,\n invalid,\n \"aria-label\": ariaLabel,\n ...rest\n}: MultiEntityComboboxProps<V>) {\n const core = useComboboxCore<V>({ options, loadOptions, loading });\n // The props are the per-instance overrides, the provider the app-wide ones; a\n // prop left `undefined` falls through to the provider rather than masking it.\n const labels = useKitLabels(\"combobox\", DEFAULT_COMBOBOX_LABELS, {\n search: searchPlaceholder,\n noResults: emptyLabel,\n clear: clearLabel,\n create: createLabel,\n });\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const { open, results, resolve, setOpen } = core;\n // One id per instance, generated here rather than in the core: `aria-controls` on\n // the trigger has to name the list while the list is still closed, so the id\n // belongs to whoever renders both ends of it.\n const listboxId = useId();\n\n const valueSet = useMemo(() => new Set(value), [value]);\n // A list of NAMES, not a sentence, so the narrow conjunction: English gets\n // \"Food, Rent, Travel\" (no \"and\" squeezed into a truncating trigger), German and\n // French still get their \"und\"/\"et\" where the language insists on it, and Japanese\n // and Chinese get the ideographic comma \"、\". The `unit` type looked like the\n // natural fit for a summary, but in ja/zh it concatenates the names with nothing\n // between them at all.\n const locale = useKitLocale();\n const listFormat = useMemo(\n () => new Intl.ListFormat(locale, { type: \"conjunction\", style: \"narrow\" }),\n [locale],\n );\n const summary = (() => {\n if (value.length === 0) return placeholder ?? \"\";\n if (itemLabel) return itemLabel(value.length);\n const chosen = value.map(resolve);\n return chosen.every(Boolean)\n ? listFormat.format(chosen.map((o) => o!.label))\n : labels.selectedCount(value.length);\n })();\n\n const q = core.query.trim();\n const showCreate =\n Boolean(onCreate) && q.length > 0 && !results.some((o) => o.label.toLowerCase() === q.toLowerCase());\n const showClear = Boolean(clearable && value.length > 0 && !disabled);\n\n const toggle = (o: ComboOption<V>) => {\n core.cacheRef.current.set(o.value, o);\n if (valueSet.has(o.value)) onChange(value.filter((v) => v !== o.value));\n else onChange([...value, o.value]);\n // Stay open — multi-select keeps adding.\n };\n\n return (\n // `rest` dresses the wrapper, which has no role; the accessible NAME goes on\n // the trigger, which has one. Spread FIRST so the trigger's ARIA and the\n // handlers that open the panel cannot be clobbered from outside.\n <div {...rest} className={cn(\"relative\", className)}>\n {label !== undefined && <FieldLabel>{label}</FieldLabel>}\n <button\n ref={core.triggerRef}\n type=\"button\"\n // A combobox, not a button. The distinction is not pedantry: this control\n // carried `aria-invalid`, which `button` does not support, so a required\n // field left empty painted a rose border and told a reader nothing at all —\n // and ESLint flagged it as exactly that (`role-supports-aria-props`). The\n // fix the audit asked for is the role that describes what this IS: a closed\n // choice that expands into the list named below. `combobox` supports\n // `aria-invalid`, so the border and the announcement finally agree.\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-haspopup=\"listbox\"\n // The label is a floating <span>, not a <label for>, so without this the\n // trigger's accessible name is whatever value happens to be selected —\n // \"Checking\" with nothing saying it is the account. The label AND the\n // value, because `aria-label` replaces the content rather than adding to\n // it, and a control that announces only its name has lost the answer.\n // A caller's own name wins over the composition: two fields labelled\n // \"Account\" on a transfer form are the from and the to, and only the\n // caller knows which is which.\n aria-label={\n ariaLabel ??\n (typeof label === \"string\" ? common.fieldValue(label, summary) : undefined)\n }\n disabled={disabled}\n aria-invalid={invalid || undefined}\n onClick={() => !disabled && setOpen((o) => !o)}\n // Down/Up opens the list from the closed trigger, per the APG. Enter and\n // Space already do it through the button's own click.\n onKeyDown={(e) => {\n if (disabled) return;\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n setOpen(true);\n }\n }}\n className={cn(\n FIELD_TRIGGER,\n \"pr-9\",\n label !== undefined && FIELD_FLOATING_PAD,\n disabled && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n )}\n >\n <span\n className={cn(\n \"min-w-0 truncate\",\n // Inherit FIELD_BASE's ink for a value, keep the placeholder tone\n // (`--text-placeholder`) — the same rule every field on a form row\n // follows (Keksdose dev#477).\n !value.length && \"text-[var(--text-placeholder)]\",\n )}\n >\n {summary}\n </span>\n {showClear ? (\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label={labels.clear}\n onClick={(e) => {\n e.stopPropagation();\n onChange([]);\n }}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]\"\n >\n <X className=\"size-4\" />\n </span>\n ) : (\n <FieldChevron />\n )}\n </button>\n <ComboboxPanel\n core={core}\n listboxId={listboxId}\n multi\n // On a phone the panel becomes a full-screen sheet, which needs the field's\n // own label to say what it is asking for (live #200).\n sheetTitle={label ?? placeholder}\n searchPlaceholder={labels.search}\n emptyLabel={labels.noResults}\n closeLabel={closeLabel}\n isSelected={(v) => valueSet.has(v)}\n onChoose={toggle}\n showCreate={showCreate}\n onCreate={() => {\n onCreate?.(q);\n core.setQuery(\"\");\n }}\n createContent={labels.create(q)}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
interface MultiSelectOption {
|
|
5
5
|
value: string | number;
|
|
6
6
|
label: string;
|
|
7
7
|
hint?: string;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
/** `onChange` is the kit's — "the selection changed", carrying the new values —
|
|
10
|
+
* rather than the div's form event. */
|
|
11
|
+
interface MultiSelectProps extends Omit<ComponentPropsWithoutRef<"div">, "onChange"> {
|
|
10
12
|
options: MultiSelectOption[];
|
|
11
13
|
values: (string | number)[];
|
|
12
14
|
onChange: (next: (string | number)[]) => void;
|
|
13
|
-
/** Displayed when no value is selected (i.e. "all"). Defaults to
|
|
14
|
-
*
|
|
15
|
+
/** Displayed when no value is selected (i.e. "all"). Defaults to `placeholder`,
|
|
16
|
+
* then to `multiSelect.all` from the {@link UiKitProvider}, then "All". */
|
|
15
17
|
allLabel?: string;
|
|
16
|
-
/** Singular/plural countable label, used when 1+ items are selected.
|
|
18
|
+
/** Singular/plural countable label, used when 1+ items are selected. Default:
|
|
19
|
+
* `multiSelect.selectedCount` from the provider — the bare count in English. */
|
|
17
20
|
itemLabel?: (count: number) => string;
|
|
18
21
|
placeholder?: string;
|
|
19
22
|
/** Embedded top-boundary label, matching the native Input/Select fields. */
|
|
20
23
|
label?: ReactNode;
|
|
21
|
-
/** Search-box placeholder
|
|
24
|
+
/** Search-box placeholder. Default: `multiSelect.search` from the provider, else "Search". */
|
|
22
25
|
searchLabel?: string;
|
|
23
|
-
/** "Select all" action label
|
|
26
|
+
/** "Select all" action label. Default: `multiSelect.selectAll`, else "Select all". */
|
|
24
27
|
selectAllLabel?: string;
|
|
25
|
-
/** "Clear" action label
|
|
28
|
+
/** "Clear" action label. Default: `multiSelect.clear`, else "Clear". */
|
|
26
29
|
clearLabel?: string;
|
|
27
|
-
className?: string;
|
|
28
30
|
/** Extra classes for the open dropdown PANEL — the way to widen it past its
|
|
29
31
|
* 16rem default when the rows carry more than a label (Keksdose feedback #147). */
|
|
30
32
|
panelClassName?: string;
|
|
@@ -34,6 +36,6 @@ interface Props {
|
|
|
34
36
|
* only the one that happened to have it first. */
|
|
35
37
|
invalid?: boolean;
|
|
36
38
|
}
|
|
37
|
-
declare function MultiSelect({ options, values, onChange, allLabel, itemLabel, placeholder, label, searchLabel, selectAllLabel, clearLabel, className, panelClassName, invalid, }:
|
|
39
|
+
declare function MultiSelect({ options, values, onChange, allLabel, itemLabel, placeholder, label, searchLabel, selectAllLabel, clearLabel, className, panelClassName, invalid, "aria-label": ariaLabel, ...rest }: MultiSelectProps): react.JSX.Element;
|
|
38
40
|
|
|
39
|
-
export { MultiSelect, type MultiSelectOption };
|
|
41
|
+
export { MultiSelect, type MultiSelectOption, type MultiSelectProps };
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
+
import { useId, useMemo, useState } from "react";
|
|
3
4
|
import { Check } from "lucide-react";
|
|
4
5
|
import { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_INVALID, FIELD_FLOATING_PAD } from "./ui.js";
|
|
5
6
|
import { cn } from "../lib/cn.js";
|
|
6
7
|
import { DropdownPanel, DropdownSearchHeader, useDropdownSearch } from "./dropdown.js";
|
|
8
|
+
import { useActiveOptionScroll } from "./combobox-core.js";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_COMMON_LABELS,
|
|
11
|
+
DEFAULT_MULTI_SELECT_LABELS,
|
|
12
|
+
useKitLabels
|
|
13
|
+
} from "../i18n/kit-labels.js";
|
|
7
14
|
function MultiSelect({
|
|
8
15
|
options,
|
|
9
16
|
values,
|
|
@@ -12,14 +19,27 @@ function MultiSelect({
|
|
|
12
19
|
itemLabel,
|
|
13
20
|
placeholder,
|
|
14
21
|
label,
|
|
15
|
-
searchLabel
|
|
16
|
-
selectAllLabel
|
|
17
|
-
clearLabel
|
|
22
|
+
searchLabel,
|
|
23
|
+
selectAllLabel,
|
|
24
|
+
clearLabel,
|
|
18
25
|
className,
|
|
19
26
|
panelClassName,
|
|
20
|
-
invalid
|
|
27
|
+
invalid,
|
|
28
|
+
"aria-label": ariaLabel,
|
|
29
|
+
...rest
|
|
21
30
|
}) {
|
|
22
|
-
const { open, setOpen, wrapperRef, query, setQuery, inputRef } = useDropdownSearch();
|
|
31
|
+
const { open, setOpen, wrapperRef, triggerRef, closeToTrigger, query, setQuery, inputRef } = useDropdownSearch();
|
|
32
|
+
const labels = useKitLabels("multiSelect", DEFAULT_MULTI_SELECT_LABELS, {
|
|
33
|
+
search: searchLabel,
|
|
34
|
+
selectAll: selectAllLabel,
|
|
35
|
+
clear: clearLabel,
|
|
36
|
+
selectedCount: itemLabel
|
|
37
|
+
});
|
|
38
|
+
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
39
|
+
const uid = useId();
|
|
40
|
+
const listboxId = `${uid}-listbox`;
|
|
41
|
+
const optionId = (index) => `${uid}-option-${index}`;
|
|
42
|
+
const [active, setActive] = useState(0);
|
|
23
43
|
const filtered = useMemo(() => {
|
|
24
44
|
const q = query.trim().toLowerCase();
|
|
25
45
|
if (!q) return options;
|
|
@@ -31,9 +51,8 @@ function MultiSelect({
|
|
|
31
51
|
const allChecked = options.length > 0 && options.every((o) => valueSet.has(o.value));
|
|
32
52
|
const anyChecked = values.length > 0;
|
|
33
53
|
const selectedSummary = (() => {
|
|
34
|
-
if (!anyChecked) return allLabel ?? placeholder ??
|
|
35
|
-
|
|
36
|
-
return itemLabel ? itemLabel(values.length) : `${values.length}`;
|
|
54
|
+
if (!anyChecked || allChecked) return allLabel ?? placeholder ?? labels.all;
|
|
55
|
+
return labels.selectedCount(values.length);
|
|
37
56
|
})();
|
|
38
57
|
const toggle = (val) => {
|
|
39
58
|
if (valueSet.has(val)) onChange(values.filter((v) => v !== val));
|
|
@@ -41,90 +60,148 @@ function MultiSelect({
|
|
|
41
60
|
};
|
|
42
61
|
const selectAll = () => onChange(options.map((o) => o.value));
|
|
43
62
|
const clearAll = () => onChange([]);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
const activeId = active >= 0 && active < filtered.length ? optionId(active) : void 0;
|
|
64
|
+
useActiveOptionScroll(activeId);
|
|
65
|
+
const onListKeyDown = (e) => {
|
|
66
|
+
if (e.key === "ArrowDown") {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
setActive((i) => Math.min(i + 1, filtered.length - 1));
|
|
69
|
+
} else if (e.key === "ArrowUp") {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
setActive((i) => Math.max(i - 1, 0));
|
|
72
|
+
} else if (e.key === "Home") {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
setActive(0);
|
|
75
|
+
} else if (e.key === "End") {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
setActive(filtered.length - 1);
|
|
78
|
+
} else if (e.key === "Enter") {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
if (filtered[active]) toggle(filtered[active].value);
|
|
81
|
+
} else if (e.key === "Tab") {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
closeToTrigger();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return (
|
|
87
|
+
// `rest` dresses the wrapper; the name belongs to the trigger below it, which is
|
|
88
|
+
// the thing with a role. Spread FIRST so the ARIA the trigger composes, and the
|
|
89
|
+
// handlers that open the list, cannot be replaced by accident from outside.
|
|
90
|
+
/* @__PURE__ */ jsxs("div", { ...rest, ref: wrapperRef, className: cn("relative", className), children: [
|
|
91
|
+
label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
|
|
92
|
+
/* @__PURE__ */ jsxs(
|
|
93
|
+
"button",
|
|
94
|
+
{
|
|
95
|
+
ref: triggerRef,
|
|
96
|
+
type: "button",
|
|
97
|
+
role: "combobox",
|
|
98
|
+
"aria-expanded": open,
|
|
99
|
+
"aria-controls": listboxId,
|
|
100
|
+
"aria-haspopup": "listbox",
|
|
101
|
+
"aria-label": ariaLabel ?? (typeof label === "string" ? common.fieldValue(label, selectedSummary) : void 0),
|
|
102
|
+
onClick: () => {
|
|
103
|
+
setOpen((v) => !v);
|
|
104
|
+
setActive(0);
|
|
105
|
+
},
|
|
106
|
+
onKeyDown: (e) => {
|
|
107
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
setOpen(true);
|
|
110
|
+
setActive(0);
|
|
72
111
|
}
|
|
73
|
-
|
|
74
|
-
|
|
112
|
+
},
|
|
113
|
+
"aria-invalid": invalid || void 0,
|
|
114
|
+
className: cn(FIELD_TRIGGER, "pr-9", label !== void 0 && FIELD_FLOATING_PAD, invalid && FIELD_INVALID),
|
|
115
|
+
children: [
|
|
116
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: selectedSummary }),
|
|
117
|
+
/* @__PURE__ */ jsx(FieldChevron, {})
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
),
|
|
121
|
+
open && /* @__PURE__ */ jsx(
|
|
122
|
+
DropdownPanel,
|
|
123
|
+
{
|
|
124
|
+
className: cn("w-64", panelClassName),
|
|
125
|
+
empty: filtered.length === 0,
|
|
126
|
+
listProps: { id: listboxId, role: "listbox", "aria-multiselectable": true },
|
|
127
|
+
header: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
75
128
|
/* @__PURE__ */ jsx(
|
|
76
|
-
|
|
129
|
+
DropdownSearchHeader,
|
|
77
130
|
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
131
|
+
query,
|
|
132
|
+
onQueryChange: (v) => {
|
|
133
|
+
setQuery(v);
|
|
134
|
+
setActive(0);
|
|
135
|
+
},
|
|
136
|
+
inputRef,
|
|
137
|
+
placeholder: labels.search,
|
|
138
|
+
listboxId,
|
|
139
|
+
activeId,
|
|
140
|
+
onKeyDown: onListKeyDown
|
|
83
141
|
}
|
|
84
142
|
),
|
|
85
|
-
/* @__PURE__ */
|
|
143
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 py-1.5 text-xs", children: [
|
|
144
|
+
/* @__PURE__ */ jsx(
|
|
145
|
+
"button",
|
|
146
|
+
{
|
|
147
|
+
type: "button",
|
|
148
|
+
onClick: selectAll,
|
|
149
|
+
disabled: allChecked,
|
|
150
|
+
className: "rounded px-2 py-0.5 text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] disabled:opacity-40",
|
|
151
|
+
children: labels.selectAll
|
|
152
|
+
}
|
|
153
|
+
),
|
|
154
|
+
/* @__PURE__ */ jsx(
|
|
155
|
+
"button",
|
|
156
|
+
{
|
|
157
|
+
type: "button",
|
|
158
|
+
onClick: clearAll,
|
|
159
|
+
disabled: !anyChecked,
|
|
160
|
+
className: "rounded px-2 py-0.5 text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] disabled:opacity-40",
|
|
161
|
+
children: labels.clear
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
] })
|
|
165
|
+
] }),
|
|
166
|
+
children: filtered.map((o, i) => {
|
|
167
|
+
const checked = valueSet.has(o.value);
|
|
168
|
+
return /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
|
|
86
169
|
"button",
|
|
87
170
|
{
|
|
88
171
|
type: "button",
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
100
|
-
"button",
|
|
101
|
-
{
|
|
102
|
-
type: "button",
|
|
103
|
-
onClick: () => toggle(o.value),
|
|
104
|
-
className: cn(
|
|
105
|
-
"flex w-full items-center justify-between gap-2 px-3 py-1.5 text-sm text-left hover:bg-slate-50 dark:hover:bg-slate-800",
|
|
106
|
-
checked && "bg-slate-100/60 dark:bg-slate-800/60"
|
|
107
|
-
),
|
|
108
|
-
children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 min-w-0", children: [
|
|
109
|
-
/* @__PURE__ */ jsx(
|
|
110
|
-
"span",
|
|
111
|
-
{
|
|
112
|
-
className: cn(
|
|
113
|
-
"flex size-4 shrink-0 items-center justify-center rounded border",
|
|
114
|
-
checked ? "border-slate-900 bg-slate-900 text-white dark:border-slate-100 dark:bg-slate-100 dark:text-slate-900" : "border-slate-300 bg-white dark:border-slate-600 dark:bg-slate-900"
|
|
115
|
-
),
|
|
116
|
-
children: checked && /* @__PURE__ */ jsx(Check, { className: "size-3" })
|
|
117
|
-
}
|
|
172
|
+
id: optionId(i),
|
|
173
|
+
role: "option",
|
|
174
|
+
"aria-selected": checked,
|
|
175
|
+
tabIndex: -1,
|
|
176
|
+
onClick: () => toggle(o.value),
|
|
177
|
+
onMouseEnter: () => setActive(i),
|
|
178
|
+
className: cn(
|
|
179
|
+
"flex w-full items-center justify-between gap-2 px-3 py-1.5 text-sm text-left hover:bg-[var(--bg-hover)]",
|
|
180
|
+
checked && "bg-[var(--bg-active)]",
|
|
181
|
+
i === active && "bg-[var(--bg-hover)]"
|
|
118
182
|
),
|
|
119
|
-
/* @__PURE__ */
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
183
|
+
children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 min-w-0", children: [
|
|
184
|
+
/* @__PURE__ */ jsx(
|
|
185
|
+
"span",
|
|
186
|
+
{
|
|
187
|
+
"aria-hidden": true,
|
|
188
|
+
className: cn(
|
|
189
|
+
"flex size-4 shrink-0 items-center justify-center rounded border",
|
|
190
|
+
checked ? "border-[var(--bg-inverse)] bg-[var(--bg-inverse)] text-[var(--text-inverse)]" : "border-[var(--border-strong)] bg-[var(--bg-surface)]"
|
|
191
|
+
),
|
|
192
|
+
children: checked && /* @__PURE__ */ jsx(Check, { className: "size-3" })
|
|
193
|
+
}
|
|
194
|
+
),
|
|
195
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-[var(--text-primary)]", children: o.label }),
|
|
196
|
+
o.hint && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-[var(--text-placeholder)]", children: o.hint })
|
|
197
|
+
] })
|
|
198
|
+
}
|
|
199
|
+
) }, o.value);
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
] })
|
|
204
|
+
);
|
|
128
205
|
}
|
|
129
206
|
export {
|
|
130
207
|
MultiSelect
|