@eifi1/ui-kit 0.10.0 → 0.12.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 +66 -37
- package/dist/chart.d.ts +3 -1
- package/dist/chart.js +2 -0
- package/dist/chart.js.map +1 -1
- package/dist/components/account-settings-labels.d.ts +105 -0
- package/dist/components/account-settings-labels.js +81 -0
- package/dist/components/account-settings-labels.js.map +1 -0
- package/dist/components/account-settings.d.ts +36 -52
- package/dist/components/account-settings.js +44 -8
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/alert-banner.d.ts +26 -1
- package/dist/components/alert-banner.js +10 -7
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +26 -4
- package/dist/components/amount-input.js +5 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/authed-image.d.ts +68 -0
- package/dist/components/authed-image.js +117 -0
- package/dist/components/authed-image.js.map +1 -0
- package/dist/components/breadcrumbs.d.ts +2 -1
- package/dist/components/breadcrumbs.js +5 -2
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/bulk-action-bar.d.ts +51 -5
- package/dist/components/bulk-action-bar.js +88 -41
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button-group.d.ts +107 -3
- package/dist/components/button-group.js +60 -5
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +17 -4
- package/dist/components/calendar-heatmap.d.ts +162 -0
- package/dist/components/calendar-heatmap.js +336 -0
- package/dist/components/calendar-heatmap.js.map +1 -0
- package/dist/components/chip.d.ts +2 -1
- package/dist/components/chip.js +6 -3
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +66 -2
- package/dist/components/choice-card.js +108 -1
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/combobox-core.js +2 -0
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +2 -2
- package/dist/components/combobox.js +31 -9
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +52 -70
- package/dist/components/copy-button.js +1 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +63 -0
- package/dist/components/data-table-cells.js +64 -0
- package/dist/components/data-table-cells.js.map +1 -0
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +31 -1
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +13 -0
- package/dist/components/data-table-labels.js +8 -1
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +104 -12
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +3 -1
- package/dist/components/date-picker.js +15 -2
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +23 -3
- package/dist/components/description-list.js +23 -3
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +1 -0
- package/dist/components/entity-combobox.d.ts +1 -1
- package/dist/components/entity-combobox.js +18 -3
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/error-boundary.d.ts +92 -0
- package/dist/components/error-boundary.js +111 -0
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/components/field.d.ts +69 -0
- package/dist/components/field.js +75 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/file-button.d.ts +51 -208
- package/dist/components/file-dropzone.d.ts +50 -2
- package/dist/components/floating-panel.d.ts +156 -4
- package/dist/components/floating-panel.js +193 -29
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +53 -0
- package/dist/components/form-actions.js +98 -0
- package/dist/components/form-actions.js.map +1 -0
- package/dist/components/image-grid.d.ts +67 -0
- package/dist/components/image-grid.js +112 -0
- package/dist/components/image-grid.js.map +1 -0
- package/dist/components/lightbox.d.ts +104 -0
- package/dist/components/lightbox.js +229 -0
- package/dist/components/lightbox.js.map +1 -0
- package/dist/components/line-items.d.ts +114 -0
- package/dist/components/line-items.js +238 -0
- package/dist/components/line-items.js.map +1 -0
- package/dist/components/list.d.ts +37 -3
- package/dist/components/list.js +39 -14
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-state.d.ts +24 -0
- package/dist/components/loading-state.js +32 -0
- package/dist/components/loading-state.js.map +1 -0
- package/dist/components/menu-item.d.ts +13 -2
- package/dist/components/menu-item.js +11 -2
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +82 -5
- package/dist/components/mini-calendar.js +212 -64
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +20 -1
- package/dist/components/modal.js +26 -2
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +21 -1
- package/dist/components/month-picker.js +139 -26
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +1 -1
- package/dist/components/multi-entity-combobox.js +19 -3
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/nav-pills.d.ts +67 -0
- package/dist/components/nav-pills.js +69 -0
- package/dist/components/nav-pills.js.map +1 -0
- package/dist/components/number-field.d.ts +26 -5
- package/dist/components/number-field.js +5 -7
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +27 -5
- package/dist/components/number-input.js +7 -1
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +17 -4
- package/dist/components/passkeys-setting.d.ts +67 -0
- package/dist/components/passkeys-setting.js +214 -0
- package/dist/components/passkeys-setting.js.map +1 -0
- package/dist/components/picker-sheet.js +2 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart-labels.d.ts +20 -0
- package/dist/components/pie-chart-labels.js +12 -0
- package/dist/components/pie-chart-labels.js.map +1 -0
- package/dist/components/pie-chart.d.ts +101 -0
- package/dist/components/pie-chart.js +283 -0
- package/dist/components/pie-chart.js.map +1 -0
- package/dist/components/progress-bar.d.ts +73 -3
- package/dist/components/progress-bar.js +110 -8
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/qr-code.d.ts +34 -0
- package/dist/components/qr-code.js +57 -0
- package/dist/components/qr-code.js.map +1 -0
- package/dist/components/search-field.d.ts +15 -3
- package/dist/components/search-field.js +5 -1
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/series-chart.d.ts +16 -0
- package/dist/components/series-chart.js +4 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/settings-fields.d.ts +50 -1
- package/dist/components/signed-amount.d.ts +121 -0
- package/dist/components/signed-amount.js +152 -0
- package/dist/components/signed-amount.js.map +1 -0
- package/dist/components/skeleton.d.ts +10 -3
- package/dist/components/skeleton.js +12 -2
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/stat-tile.d.ts +2 -1
- package/dist/components/stat-tile.js +5 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/status-dot.d.ts +6 -2
- package/dist/components/status-dot.js +6 -1
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/table.d.ts +77 -11
- package/dist/components/table.js +85 -17
- package/dist/components/table.js.map +1 -1
- package/dist/components/text-link.d.ts +176 -0
- package/dist/components/text-link.js +109 -0
- package/dist/components/text-link.js.map +1 -0
- package/dist/components/text.d.ts +8 -1
- package/dist/components/text.js +1 -0
- package/dist/components/text.js.map +1 -1
- package/dist/components/time-input.d.ts +50 -1
- package/dist/components/toggle-group.d.ts +28 -0
- package/dist/components/toggle-group.js +58 -9
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +36 -3
- package/dist/components/toggle-legend.js +49 -0
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/tooltip.d.ts +11 -3
- package/dist/components/tooltip.js +2 -0
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/ui.d.ts +52 -729
- package/dist/components/ui.js +296 -27
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +3 -52
- package/dist/components/use-table-state.js +128 -30
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +25 -3
- package/dist/components/user-avatar.js +32 -3
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
- package/dist/data-table.d.ts +2 -1
- package/dist/data-table.js +5 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +26 -3
- package/dist/feedback/feedback-dialog.js +16 -2
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +133 -0
- package/dist/feedback/feedback-thread.js +237 -0
- package/dist/feedback/feedback-thread.js.map +1 -0
- package/dist/feedback.d.ts +2 -1
- package/dist/feedback.js +1 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-authed-src.d.ts +71 -0
- package/dist/hooks/use-authed-src.js +63 -0
- package/dist/hooks/use-authed-src.js.map +1 -0
- package/dist/hooks/use-file-drop.d.ts +50 -2
- package/dist/hooks/use-hotkey.d.ts +59 -0
- package/dist/hooks/use-hotkey.js +105 -0
- package/dist/hooks/use-hotkey.js.map +1 -0
- package/dist/hooks/use-search-param-state.d.ts +83 -0
- package/dist/hooks/use-search-param-state.js +94 -0
- package/dist/hooks/use-search-param-state.js.map +1 -0
- package/dist/i18n/defaults.d.ts +17 -4
- package/dist/i18n/defaults.js +33 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +47 -310
- package/dist/i18n/kit-labels.js +20 -5
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +17 -4
- package/dist/i18n/locales/de-CH.d.ts +17 -4
- package/dist/i18n/locales/de-informal.d.ts +17 -4
- package/dist/i18n/locales/de.d.ts +17 -4
- package/dist/i18n/locales/de.js +165 -4
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +17 -4
- package/dist/i18n/locales/es.js +162 -4
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +17 -4
- package/dist/i18n/locales/fr.js +162 -4
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +17 -4
- package/dist/i18n/locales/hu.js +162 -4
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +17 -4
- package/dist/i18n/locales/it.js +162 -4
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +17 -4
- package/dist/i18n/locales/zh.js +162 -4
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +45 -21
- package/dist/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
- package/dist/lib/clipping.d.ts +21 -5
- package/dist/lib/clipping.js +3 -0
- package/dist/lib/clipping.js.map +1 -1
- package/dist/lib/dates.d.ts +17 -1
- package/dist/lib/dates.js +19 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/format.d.ts +144 -0
- package/dist/lib/format.js +149 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/qr-encode.d.ts +44 -0
- package/dist/lib/qr-encode.js +338 -0
- package/dist/lib/qr-encode.js.map +1 -0
- package/dist/rhf/fields.d.ts +241 -0
- package/dist/rhf/fields.js +601 -0
- package/dist/rhf/fields.js.map +1 -0
- package/dist/rhf/form.d.ts +50 -1
- package/dist/rhf/line-items.d.ts +31 -0
- package/dist/rhf/line-items.js +59 -0
- package/dist/rhf/line-items.js.map +1 -0
- package/dist/rhf/use-rhf-wizard-step.d.ts +22 -0
- package/dist/rhf/use-rhf-wizard-step.js +38 -0
- package/dist/rhf/use-rhf-wizard-step.js.map +1 -0
- package/dist/rhf.d.ts +63 -1
- package/dist/rhf.js +3 -0
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +99 -2
- package/dist/shell/app-shell.js +58 -3
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/auth-layout.d.ts +74 -0
- package/dist/shell/auth-layout.js +97 -0
- package/dist/shell/auth-layout.js.map +1 -0
- package/dist/shell/top-bar-brand.d.ts +81 -0
- package/dist/shell/top-bar-brand.js +47 -0
- package/dist/shell/top-bar-brand.js.map +1 -0
- package/dist/shell/topbar-action-menu.d.ts +84 -3
- package/dist/shell/topbar-action-menu.js +115 -27
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +58 -2
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +49 -1
- package/dist/wizard/wizard-context.d.ts +30 -1
- package/dist/wizard/wizard-context.js +23 -2
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.d.ts +20 -5
- package/dist/wizard/wizard-step.js +16 -3
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard.d.ts +51 -3
- package/package.json +1 -1
- package/src/chart.ts +4 -0
- package/src/components/account-settings-labels.ts +190 -0
- package/src/components/account-settings.tsx +110 -63
- package/src/components/alert-banner.tsx +36 -7
- package/src/components/amount-input.tsx +13 -1
- package/src/components/authed-image.tsx +190 -0
- package/src/components/breadcrumbs.tsx +8 -2
- package/src/components/bulk-action-bar.tsx +163 -57
- package/src/components/button-group.tsx +129 -4
- package/src/components/calendar-heatmap.tsx +599 -0
- package/src/components/chip.tsx +8 -4
- package/src/components/choice-card.tsx +208 -1
- package/src/components/combobox-core.tsx +5 -0
- package/src/components/combobox.tsx +31 -9
- package/src/components/copy-button.tsx +3 -2
- package/src/components/data-table-cells.tsx +119 -0
- package/src/components/data-table-filters.ts +77 -0
- package/src/components/data-table-labels.ts +23 -0
- package/src/components/data-table.tsx +231 -13
- package/src/components/date-picker.tsx +19 -3
- package/src/components/description-list.tsx +52 -3
- package/src/components/entity-combobox.tsx +18 -3
- package/src/components/error-boundary.tsx +216 -0
- package/src/components/field.tsx +188 -0
- package/src/components/floating-panel.tsx +380 -16
- package/src/components/form-actions.tsx +191 -0
- package/src/components/image-grid.tsx +181 -0
- package/src/components/lightbox.tsx +374 -0
- package/src/components/line-items.tsx +385 -0
- package/src/components/list.tsx +93 -18
- package/src/components/loading-state.tsx +47 -0
- package/src/components/menu-item.tsx +29 -3
- package/src/components/mini-calendar.tsx +317 -57
- package/src/components/modal.tsx +61 -3
- package/src/components/month-picker.tsx +181 -26
- package/src/components/multi-entity-combobox.tsx +19 -3
- package/src/components/nav-pills.tsx +152 -0
- package/src/components/number-field.tsx +15 -10
- package/src/components/number-input.tsx +16 -1
- package/src/components/passkeys-setting.tsx +331 -0
- package/src/components/picker-sheet.tsx +7 -1
- package/src/components/pie-chart-labels.ts +32 -0
- package/src/components/pie-chart.tsx +479 -0
- package/src/components/progress-bar.tsx +241 -13
- package/src/components/qr-code.tsx +83 -0
- package/src/components/search-field.tsx +20 -4
- package/src/components/series-chart.tsx +21 -0
- package/src/components/signed-amount.tsx +286 -0
- package/src/components/skeleton.tsx +21 -3
- package/src/components/stat-tile.tsx +8 -2
- package/src/components/status-dot.tsx +10 -2
- package/src/components/table.tsx +186 -23
- package/src/components/text-link.tsx +248 -0
- package/src/components/text.tsx +9 -1
- package/src/components/toggle-group.tsx +106 -13
- package/src/components/toggle-legend.tsx +87 -2
- package/src/components/tooltip.tsx +17 -3
- package/src/components/ui.tsx +572 -44
- package/src/components/use-table-state.ts +265 -33
- package/src/components/user-avatar.tsx +57 -3
- package/src/data-table.ts +10 -0
- package/src/feedback/feedback-dialog.tsx +42 -3
- package/src/feedback/feedback-thread.tsx +434 -0
- package/src/feedback.ts +1 -0
- package/src/hooks/use-authed-src.ts +161 -0
- package/src/hooks/use-hotkey.ts +172 -0
- package/src/hooks/use-search-param-state.ts +187 -0
- package/src/i18n/defaults.ts +32 -0
- package/src/i18n/kit-labels.tsx +104 -8
- package/src/i18n/locales/de.ts +162 -0
- package/src/i18n/locales/es.ts +159 -0
- package/src/i18n/locales/fr.ts +162 -0
- package/src/i18n/locales/hu.ts +160 -0
- package/src/i18n/locales/it.ts +160 -0
- package/src/i18n/locales/zh.ts +159 -0
- package/src/index.ts +64 -0
- package/src/lib/clipping.ts +22 -4
- package/src/lib/dates.ts +37 -0
- package/src/lib/format.ts +325 -0
- package/src/lib/qr-encode.ts +420 -0
- package/src/rhf/fields.tsx +1054 -0
- package/src/rhf/line-items.tsx +130 -0
- package/src/rhf/use-rhf-wizard-step.ts +113 -0
- package/src/rhf.ts +8 -0
- package/src/shell/app-shell.tsx +147 -2
- package/src/shell/auth-layout.tsx +190 -0
- package/src/shell/top-bar-brand.tsx +76 -0
- package/src/shell/topbar-action-menu.tsx +260 -49
- package/src/shell.ts +2 -0
- package/src/wizard/wizard-context.tsx +50 -1
- package/src/wizard/wizard-step.tsx +43 -6
- package/tokens.css +12 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A key combination: modifiers and one key, joined by "+", case-insensitive —
|
|
5
|
+
* `"Ctrl+Shift+F"`, `"Mod+K"`, `"Alt+ArrowUp"`, `"Escape"`, `"?"`.
|
|
6
|
+
*
|
|
7
|
+
* Modifiers: `Ctrl` (`Control`), `Meta` (`Cmd`, `Command`), `Shift`, `Alt` (`Option`),
|
|
8
|
+
* and `Mod` — Cmd on Apple platforms, Ctrl everywhere else, the one to use for an
|
|
9
|
+
* app shortcut. The key is `KeyboardEvent.key` ("f", "Enter", "ArrowUp", "/"), or a
|
|
10
|
+
* `KeyboardEvent.code` ("KeyF", "Digit1") when the physical key is what matters.
|
|
11
|
+
* Several combos: pass an array.
|
|
12
|
+
*/
|
|
13
|
+
export type Hotkey = string;
|
|
14
|
+
|
|
15
|
+
export interface UseHotkeyOptions {
|
|
16
|
+
/** Off while `false` (default `true`). */
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
/** Call `preventDefault()` on a match (default `true`): Ctrl+Shift+F must not also
|
|
19
|
+
* open the browser's own dialog. */
|
|
20
|
+
preventDefault?: boolean;
|
|
21
|
+
/** Where to listen. Default `document`. A ref, an element, or `window`. */
|
|
22
|
+
target?: EventTarget | null | { current: EventTarget | null };
|
|
23
|
+
/**
|
|
24
|
+
* Fire while the focus is in a text field, select or contenteditable too. Default
|
|
25
|
+
* `false`: a shortcut must not steal the letters someone is typing. A combo with Ctrl,
|
|
26
|
+
* Meta or Alt is not typing — those still fire unless this is `"never"`.
|
|
27
|
+
*/
|
|
28
|
+
allowInInputs?: boolean | "never";
|
|
29
|
+
/** `keydown` (default) or `keyup`. */
|
|
30
|
+
event?: "keydown" | "keyup";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ParsedHotkey {
|
|
34
|
+
ctrl: boolean;
|
|
35
|
+
meta: boolean;
|
|
36
|
+
shift: boolean;
|
|
37
|
+
alt: boolean;
|
|
38
|
+
key: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Whether this is an Apple platform, where `Mod` means Cmd. */
|
|
42
|
+
export function isApplePlatform(): boolean {
|
|
43
|
+
if (typeof navigator === "undefined") return false;
|
|
44
|
+
const nav = navigator as Navigator & { userAgentData?: { platform?: string } };
|
|
45
|
+
return /mac|iphone|ipad|ipod/i.test(nav.userAgentData?.platform || nav.platform || nav.userAgent);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Parse one combo. `apple` decides what `Mod` is. Throws on an empty key. */
|
|
49
|
+
export function parseHotkey(combo: Hotkey, apple = isApplePlatform()): ParsedHotkey {
|
|
50
|
+
const parts = combo
|
|
51
|
+
.split("+")
|
|
52
|
+
.map((p) => p.trim())
|
|
53
|
+
// "Ctrl++" is Ctrl and "+": an empty part after a "+" is the plus key itself.
|
|
54
|
+
.map((p, i, all) => (p === "" && i === all.length - 1 ? "+" : p))
|
|
55
|
+
.filter((p) => p !== "");
|
|
56
|
+
const out: ParsedHotkey = { ctrl: false, meta: false, shift: false, alt: false, key: "" };
|
|
57
|
+
for (const part of parts) {
|
|
58
|
+
switch (part.toLowerCase()) {
|
|
59
|
+
case "ctrl":
|
|
60
|
+
case "control":
|
|
61
|
+
out.ctrl = true;
|
|
62
|
+
break;
|
|
63
|
+
case "meta":
|
|
64
|
+
case "cmd":
|
|
65
|
+
case "command":
|
|
66
|
+
out.meta = true;
|
|
67
|
+
break;
|
|
68
|
+
case "mod":
|
|
69
|
+
if (apple) out.meta = true;
|
|
70
|
+
else out.ctrl = true;
|
|
71
|
+
break;
|
|
72
|
+
case "shift":
|
|
73
|
+
out.shift = true;
|
|
74
|
+
break;
|
|
75
|
+
case "alt":
|
|
76
|
+
case "option":
|
|
77
|
+
out.alt = true;
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
out.key = part;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!out.key) throw new Error(`useHotkey: "${combo}" names no key`);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const KEY_ALIASES: Record<string, string> = {
|
|
88
|
+
esc: "escape",
|
|
89
|
+
space: " ",
|
|
90
|
+
del: "delete",
|
|
91
|
+
up: "arrowup",
|
|
92
|
+
down: "arrowdown",
|
|
93
|
+
left: "arrowleft",
|
|
94
|
+
right: "arrowright",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** Whether `e` is `combo` — every modifier as named (no more, no fewer), and the key. */
|
|
98
|
+
export function matchesHotkey(e: KeyboardEvent, combo: ParsedHotkey): boolean {
|
|
99
|
+
if (e.ctrlKey !== combo.ctrl || e.metaKey !== combo.meta || e.altKey !== combo.alt) return false;
|
|
100
|
+
const want = KEY_ALIASES[combo.key.toLowerCase()] ?? combo.key.toLowerCase();
|
|
101
|
+
const key = (e.key ?? "").toLowerCase();
|
|
102
|
+
const code = (e.code ?? "").toLowerCase();
|
|
103
|
+
const keyMatches = key === want || code === want;
|
|
104
|
+
if (!keyMatches) return false;
|
|
105
|
+
// Shift must match exactly for a letter or a named key; for a symbol it is how the
|
|
106
|
+
// symbol is typed ("?" is Shift+/ on a US layout, a plain key on others), so an
|
|
107
|
+
// unnamed Shift is not held against it.
|
|
108
|
+
const symbol = want.length === 1 && !/[a-z0-9]/.test(want);
|
|
109
|
+
return symbol && !combo.shift ? true : e.shiftKey === combo.shift;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Is the focus somewhere that takes typed characters? */
|
|
113
|
+
function isTypingTarget(target: EventTarget | null): boolean {
|
|
114
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
115
|
+
if (target.isContentEditable) return true;
|
|
116
|
+
if (target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) return true;
|
|
117
|
+
if (target instanceof HTMLInputElement) {
|
|
118
|
+
return !["button", "checkbox", "radio", "submit", "reset", "range", "color", "file", "image"].includes(target.type);
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Call `handler` when `combo` is pressed — kastlan's feedback button (Ctrl+Shift+F,
|
|
125
|
+
* feedback-button:60) wired it by hand, and so will every next shortcut.
|
|
126
|
+
*
|
|
127
|
+
* - `Mod` is Cmd on a Mac and Ctrl elsewhere, read once per mount.
|
|
128
|
+
* - Modifiers match EXACTLY: `"Ctrl+K"` does not fire on Ctrl+Shift+K.
|
|
129
|
+
* - Typing in a field is left alone unless `allowInInputs`, except for a combo with
|
|
130
|
+
* Ctrl / Meta / Alt, which no one types as text.
|
|
131
|
+
* - A key held down fires once, not on every repeat.
|
|
132
|
+
* - The newest `handler` is always called; changing it does not re-attach the listener.
|
|
133
|
+
*
|
|
134
|
+
* `useCommandKey` (the ⌘K palette hook) is unchanged; this is its general form.
|
|
135
|
+
*/
|
|
136
|
+
export function useHotkey(
|
|
137
|
+
combo: Hotkey | ReadonlyArray<Hotkey>,
|
|
138
|
+
handler: (event: KeyboardEvent) => void,
|
|
139
|
+
options: UseHotkeyOptions = {},
|
|
140
|
+
): void {
|
|
141
|
+
const { enabled = true, preventDefault = true, target, allowInInputs = false, event = "keydown" } = options;
|
|
142
|
+
const handlerRef = useRef(handler);
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
handlerRef.current = handler;
|
|
145
|
+
});
|
|
146
|
+
const key = typeof combo === "string" ? combo : combo.join("\u0000");
|
|
147
|
+
const targetEl = target && "current" in target ? undefined : target;
|
|
148
|
+
const targetRef = target && "current" in target ? target : undefined;
|
|
149
|
+
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (!enabled) return;
|
|
152
|
+
const node: EventTarget | null =
|
|
153
|
+
targetRef?.current ?? targetEl ?? (typeof document === "undefined" ? null : document);
|
|
154
|
+
if (!node) return;
|
|
155
|
+
const apple = isApplePlatform();
|
|
156
|
+
const parsed = key.split("\u0000").map((c) => parseHotkey(c, apple));
|
|
157
|
+
const listener = (raw: Event) => {
|
|
158
|
+
const e = raw as KeyboardEvent;
|
|
159
|
+
if (e.repeat) return;
|
|
160
|
+
const hit = parsed.find((p) => matchesHotkey(e, p));
|
|
161
|
+
if (!hit) return;
|
|
162
|
+
if (allowInInputs !== true && isTypingTarget(e.target)) {
|
|
163
|
+
const chorded = hit.ctrl || hit.meta || hit.alt;
|
|
164
|
+
if (allowInInputs === "never" || !chorded) return;
|
|
165
|
+
}
|
|
166
|
+
if (preventDefault) e.preventDefault();
|
|
167
|
+
handlerRef.current(e);
|
|
168
|
+
};
|
|
169
|
+
node.addEventListener(event, listener);
|
|
170
|
+
return () => node.removeEventListener(event, listener);
|
|
171
|
+
}, [key, enabled, preventDefault, allowInInputs, event, targetEl, targetRef]);
|
|
172
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
|
2
|
+
|
|
3
|
+
type Updater<T> = T | ((prev: T) => T);
|
|
4
|
+
|
|
5
|
+
export interface SearchParamStateOptions<T> {
|
|
6
|
+
/**
|
|
7
|
+
* The value of a param that IS present. Return `undefined` (or throw) for a value
|
|
8
|
+
* that does not parse — a hand-edited or stale link — and the default is used.
|
|
9
|
+
* Default: the raw string.
|
|
10
|
+
*/
|
|
11
|
+
parse?: (raw: string) => T | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* The param for a value, or `null` to remove it. Default: `String(value)`, and
|
|
14
|
+
* `null` for `null` / `undefined`. A value that serialises the same as the default
|
|
15
|
+
* removes the param too, so the default is always the clean URL.
|
|
16
|
+
*/
|
|
17
|
+
serialize?: (value: T) => string | null;
|
|
18
|
+
/** Shorthand for `history: "replace"`. */
|
|
19
|
+
replace?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* What a change does to the browser history:
|
|
22
|
+
* - `"push"` (default): a new entry, so Back undoes the change.
|
|
23
|
+
* - `"replace"`: the current entry is rewritten — a tab, a sort, a view toggle,
|
|
24
|
+
* which are not steps anyone wants Back to walk through.
|
|
25
|
+
* - `"replace-on-clear"`: push when a value is set, replace when it is cleared (the
|
|
26
|
+
* "open a form, close it" pattern).
|
|
27
|
+
*/
|
|
28
|
+
history?: "push" | "replace" | "replace-on-clear";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const defaultSerialize = (value: unknown): string | null =>
|
|
32
|
+
value === null || value === undefined ? null : String(value);
|
|
33
|
+
|
|
34
|
+
/** The route state of the current entry, carried over a `replace` so a query rewrite
|
|
35
|
+
* does not drop what a navigation put there (a dialog's opener mark, below). */
|
|
36
|
+
function carriedState(state: unknown): Record<string, unknown> | undefined {
|
|
37
|
+
return state && typeof state === "object" ? (state as Record<string, unknown>) : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* One URL search param as React state: `const [view, setView] = useSearchParamState("view", "list")`.
|
|
42
|
+
*
|
|
43
|
+
* Replaces the hand-rolled `setParams((prev) => { const p = new URLSearchParams(prev);
|
|
44
|
+
* …set/delete…; return p })` blocks that keksdose had in a dozen pages and kastlan ported
|
|
45
|
+
* as its own copy of this hook (shared/hooks/use-search-param-state.ts, both apps). The
|
|
46
|
+
* kit's version takes the DEFAULT as an argument rather than inside `parse`, so the
|
|
47
|
+
* two rules that follow from it cannot be forgotten at a call site:
|
|
48
|
+
*
|
|
49
|
+
* * an absent or unparseable param reads as the default, and
|
|
50
|
+
* * setting the default removes the param — the default view is the clean URL.
|
|
51
|
+
*
|
|
52
|
+
* Every other param in the query string is left as it is. Two updates in one tick do
|
|
53
|
+
* not compose — react-router resolves each against the params of the last render.
|
|
54
|
+
* Requires a react-router Router.
|
|
55
|
+
*/
|
|
56
|
+
export function useSearchParamState<T = string>(
|
|
57
|
+
key: string,
|
|
58
|
+
defaultValue: T,
|
|
59
|
+
options: SearchParamStateOptions<T> = {},
|
|
60
|
+
): [T, (next: Updater<T>) => void] {
|
|
61
|
+
const { parse, serialize = defaultSerialize as (value: T) => string | null } = options;
|
|
62
|
+
const history = options.history ?? (options.replace ? "replace" : "push");
|
|
63
|
+
const [params, setParams] = useSearchParams();
|
|
64
|
+
const location = useLocation();
|
|
65
|
+
|
|
66
|
+
const read = (raw: string | null): T => {
|
|
67
|
+
if (raw === null) return defaultValue;
|
|
68
|
+
if (!parse) return raw as unknown as T;
|
|
69
|
+
try {
|
|
70
|
+
const parsed = parse(raw);
|
|
71
|
+
return parsed === undefined ? defaultValue : parsed;
|
|
72
|
+
} catch {
|
|
73
|
+
return defaultValue;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const value = read(params.get(key));
|
|
77
|
+
const defaultSerialized = serialize(defaultValue);
|
|
78
|
+
const toParam = (v: T): string | null => {
|
|
79
|
+
const s = serialize(v);
|
|
80
|
+
return s === defaultSerialized ? null : s;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const setValue = (next: Updater<T>) => {
|
|
84
|
+
// Resolved against the rendered value only to choose the history mode; the write
|
|
85
|
+
// resolves again inside react-router's updater, against the params it hands over.
|
|
86
|
+
const preview = typeof next === "function" ? (next as (prev: T) => T)(value) : next;
|
|
87
|
+
const replace = history === "replace" || (history === "replace-on-clear" && toParam(preview) === null);
|
|
88
|
+
setParams(
|
|
89
|
+
(prev) => {
|
|
90
|
+
const p = new URLSearchParams(prev);
|
|
91
|
+
const resolved = typeof next === "function" ? (next as (prev: T) => T)(read(p.get(key))) : next;
|
|
92
|
+
const serialized = toParam(resolved);
|
|
93
|
+
if (serialized === null) p.delete(key);
|
|
94
|
+
else p.set(key, serialized);
|
|
95
|
+
return p;
|
|
96
|
+
},
|
|
97
|
+
replace ? { replace, state: carriedState(location.state) } : undefined,
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return [value, setValue];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The active tab in `?tab=` (or `key`), with the default tab as the clean URL, for the
|
|
106
|
+
* kit's `Tabs`:
|
|
107
|
+
*
|
|
108
|
+
* const [tab, setTab] = useTabParam("overview", { tabs: ["overview", "history"] });
|
|
109
|
+
* <Tabs tabs={…} active={tab} onChange={setTab} />
|
|
110
|
+
*
|
|
111
|
+
* Switching tabs REPLACES the history entry — a tab is a view of this page, not a
|
|
112
|
+
* place Back should step through. `tabs`, when given, turns a param naming no tab (a
|
|
113
|
+
* renamed tab, an old link) into the default rather than into a blank panel.
|
|
114
|
+
* kastlan's use-tab-param.ts, with the validation it did not have.
|
|
115
|
+
*/
|
|
116
|
+
export function useTabParam<T extends string>(
|
|
117
|
+
defaultTab: T,
|
|
118
|
+
options: { key?: string; tabs?: readonly T[] } = {},
|
|
119
|
+
): [T, (tab: T) => void] {
|
|
120
|
+
const { key = "tab", tabs } = options;
|
|
121
|
+
return useSearchParamState<T>(key, defaultTab, {
|
|
122
|
+
parse: (raw) => (!tabs || (tabs as readonly string[]).includes(raw) ? (raw as T) : undefined),
|
|
123
|
+
history: "replace",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Where a dialog's open state lives: a param's presence (`"edit"` → `?edit=1`), or one
|
|
128
|
+
* value of a shared param (`{ key: "dialog", value: "create-lease" }`). */
|
|
129
|
+
export type DialogParam = string | { key: string; value: string };
|
|
130
|
+
|
|
131
|
+
/** The route-state mark an opener leaves on the entry it pushes, so the close knows it
|
|
132
|
+
* may go BACK over it rather than add a second entry. */
|
|
133
|
+
const OPENED_BY_KIT = "__uiKitDialogParam";
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A dialog's open state in the URL, for `Modal`'s `urlParam` and anything else that
|
|
137
|
+
* opens one: `const [open, setOpen] = useDialogParam({ key: "dialog", value: "create" })`.
|
|
138
|
+
*
|
|
139
|
+
* * **Opening pushes** an entry, so the platform Back gesture closes the dialog — by
|
|
140
|
+
* leaving the entry — and Forward opens it again, like any other step.
|
|
141
|
+
* * **Closing goes back** over that entry when this hook pushed it, so a closed dialog
|
|
142
|
+
* leaves no dead entry behind. A dialog that arrived OPEN (a deep link, a reload, a
|
|
143
|
+
* plain `<Link>` to it) has nothing of ours to go back over, and its close removes the
|
|
144
|
+
* param in place instead — Back from there leaves the page, as it should.
|
|
145
|
+
*
|
|
146
|
+
* That is why it is not `useSearchParamState`: going back is the only close that
|
|
147
|
+
* leaves the history as it was before the dialog, and only the opener knows whether it
|
|
148
|
+
* may.
|
|
149
|
+
*/
|
|
150
|
+
export function useDialogParam(param: DialogParam): [boolean, (open: boolean) => void] {
|
|
151
|
+
const { key, value } = typeof param === "string" ? { key: param, value: "1" } : param;
|
|
152
|
+
const [params, setParams] = useSearchParams();
|
|
153
|
+
const location = useLocation();
|
|
154
|
+
const navigate = useNavigate();
|
|
155
|
+
const open = typeof param === "string" ? params.has(key) : params.get(key) === value;
|
|
156
|
+
const mark = `${key}=${value}`;
|
|
157
|
+
|
|
158
|
+
const setOpen = (next: boolean) => {
|
|
159
|
+
if (next === open) return;
|
|
160
|
+
const state = carriedState(location.state);
|
|
161
|
+
if (next) {
|
|
162
|
+
setParams(
|
|
163
|
+
(prev) => {
|
|
164
|
+
const p = new URLSearchParams(prev);
|
|
165
|
+
p.set(key, value);
|
|
166
|
+
return p;
|
|
167
|
+
},
|
|
168
|
+
{ state: { ...state, [OPENED_BY_KIT]: mark } },
|
|
169
|
+
);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (state?.[OPENED_BY_KIT] === mark) {
|
|
173
|
+
void navigate(-1);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
setParams(
|
|
177
|
+
(prev) => {
|
|
178
|
+
const p = new URLSearchParams(prev);
|
|
179
|
+
p.delete(key);
|
|
180
|
+
return p;
|
|
181
|
+
},
|
|
182
|
+
{ replace: true, state },
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return [open, setOpen];
|
|
187
|
+
}
|
package/src/i18n/defaults.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_DATA_TABLE_LABELS } from "../components/data-table-labels";
|
|
2
2
|
import { DEFAULT_MINI_CALENDAR_LABELS } from "../components/mini-calendar";
|
|
3
|
+
import { DEFAULT_CALENDAR_HEATMAP_LABELS } from "../components/calendar-heatmap";
|
|
3
4
|
import { DEFAULT_MONTH_PICKER_LABELS } from "../components/month-picker";
|
|
4
5
|
import { DEFAULT_PAGE_CONTENTS_LABELS } from "../components/page-contents";
|
|
5
6
|
import { DEFAULT_POPOVER_LABELS } from "../components/popover";
|
|
@@ -11,6 +12,7 @@ import { DEFAULT_TOUR_LABELS } from "../tour/tour";
|
|
|
11
12
|
import { DEFAULT_COMMAND_PALETTE_LABELS } from "../search/command-palette";
|
|
12
13
|
import { DEFAULT_GLOBAL_SEARCH_LABELS } from "../search/global-search";
|
|
13
14
|
import { DEFAULT_SERIES_CHART_LABELS } from "../components/series-chart-labels";
|
|
15
|
+
import { DEFAULT_PIE_CHART_LABELS } from "../components/pie-chart-labels";
|
|
14
16
|
import { DEFAULT_SPARKLINE_LABELS } from "../components/sparkline";
|
|
15
17
|
import { DEFAULT_STAT_TILE_LABELS } from "../components/stat-tile";
|
|
16
18
|
import { DEFAULT_SIGNATURE_PAD_LABELS } from "../components/signature-pad";
|
|
@@ -24,11 +26,26 @@ import { DEFAULT_MEASURED_GRID_LABELS } from "../components/measured-grid";
|
|
|
24
26
|
import { DEFAULT_CONFIRM_DIALOG_LABELS } from "../components/confirm-dialog";
|
|
25
27
|
import { DEFAULT_FLOATING_PANEL_LABELS } from "../components/floating-panel";
|
|
26
28
|
import { DEFAULT_FEEDBACK_ATTACHMENT_LABELS } from "../feedback/feedback-attachment";
|
|
29
|
+
import { DEFAULT_FEEDBACK_DIALOG_LABELS } from "../feedback/feedback-dialog";
|
|
30
|
+
import {
|
|
31
|
+
DEFAULT_FEEDBACK_COMPOSER_LABELS,
|
|
32
|
+
DEFAULT_FEEDBACK_THREAD_LABELS,
|
|
33
|
+
} from "../feedback/feedback-thread";
|
|
34
|
+
import { DEFAULT_ACCOUNT_SETTINGS_LABELS } from "../components/account-settings-labels";
|
|
27
35
|
import { DEFAULT_COPY_BUTTON_LABELS } from "../components/copy-button";
|
|
28
36
|
import { DEFAULT_BULK_ACTION_BAR_LABELS } from "../components/bulk-action-bar";
|
|
29
37
|
import { DEFAULT_LIST_LABELS } from "../components/list";
|
|
30
38
|
import { DEFAULT_BREADCRUMBS_LABELS } from "../components/breadcrumbs";
|
|
31
39
|
import { DEFAULT_TOAST_LABELS } from "../components/toast";
|
|
40
|
+
import { DEFAULT_FORM_ACTIONS_LABELS } from "../components/form-actions";
|
|
41
|
+
import { DEFAULT_DESCRIPTION_LIST_LABELS } from "../components/description-list";
|
|
42
|
+
import { DEFAULT_LINE_ITEMS_LABELS } from "../components/line-items";
|
|
43
|
+
import { DEFAULT_PROGRESS_BAR_LABELS } from "../components/progress-bar";
|
|
44
|
+
import { DEFAULT_SIGNED_AMOUNT_LABELS } from "../components/signed-amount";
|
|
45
|
+
import { DEFAULT_ERROR_BOUNDARY_LABELS } from "../components/error-boundary";
|
|
46
|
+
import { DEFAULT_AUTHED_IMAGE_LABELS } from "../components/authed-image";
|
|
47
|
+
import { DEFAULT_IMAGE_GRID_LABELS } from "../components/image-grid";
|
|
48
|
+
import { DEFAULT_LIGHTBOX_LABELS } from "../components/lightbox";
|
|
32
49
|
import {
|
|
33
50
|
DEFAULT_APP_SHELL_LABELS,
|
|
34
51
|
DEFAULT_CALCULATOR_LABELS,
|
|
@@ -56,6 +73,7 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
56
73
|
common: DEFAULT_COMMON_LABELS,
|
|
57
74
|
dataTable: DEFAULT_DATA_TABLE_LABELS,
|
|
58
75
|
miniCalendar: DEFAULT_MINI_CALENDAR_LABELS,
|
|
76
|
+
calendarHeatmap: DEFAULT_CALENDAR_HEATMAP_LABELS,
|
|
59
77
|
datePicker: DEFAULT_DATE_PICKER_LABELS,
|
|
60
78
|
monthPicker: DEFAULT_MONTH_PICKER_LABELS,
|
|
61
79
|
popover: DEFAULT_POPOVER_LABELS,
|
|
@@ -78,6 +96,7 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
78
96
|
commandPalette: DEFAULT_COMMAND_PALETTE_LABELS,
|
|
79
97
|
globalSearch: DEFAULT_GLOBAL_SEARCH_LABELS,
|
|
80
98
|
seriesChart: DEFAULT_SERIES_CHART_LABELS,
|
|
99
|
+
pieChart: DEFAULT_PIE_CHART_LABELS,
|
|
81
100
|
sparkline: DEFAULT_SPARKLINE_LABELS,
|
|
82
101
|
statTile: DEFAULT_STAT_TILE_LABELS,
|
|
83
102
|
signaturePad: DEFAULT_SIGNATURE_PAD_LABELS,
|
|
@@ -89,6 +108,10 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
89
108
|
filePicker: DEFAULT_FILE_PICKER_LABELS,
|
|
90
109
|
measuredGrid: DEFAULT_MEASURED_GRID_LABELS,
|
|
91
110
|
feedbackAttachment: DEFAULT_FEEDBACK_ATTACHMENT_LABELS,
|
|
111
|
+
feedbackDialog: DEFAULT_FEEDBACK_DIALOG_LABELS,
|
|
112
|
+
feedbackThread: DEFAULT_FEEDBACK_THREAD_LABELS,
|
|
113
|
+
feedbackComposer: DEFAULT_FEEDBACK_COMPOSER_LABELS,
|
|
114
|
+
accountSettings: DEFAULT_ACCOUNT_SETTINGS_LABELS,
|
|
92
115
|
confirmDialog: DEFAULT_CONFIRM_DIALOG_LABELS,
|
|
93
116
|
floatingPanel: DEFAULT_FLOATING_PANEL_LABELS,
|
|
94
117
|
copyButton: DEFAULT_COPY_BUTTON_LABELS,
|
|
@@ -96,4 +119,13 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
96
119
|
list: DEFAULT_LIST_LABELS,
|
|
97
120
|
breadcrumbs: DEFAULT_BREADCRUMBS_LABELS,
|
|
98
121
|
toast: DEFAULT_TOAST_LABELS,
|
|
122
|
+
form: DEFAULT_FORM_ACTIONS_LABELS,
|
|
123
|
+
descriptionList: DEFAULT_DESCRIPTION_LIST_LABELS,
|
|
124
|
+
lineItems: DEFAULT_LINE_ITEMS_LABELS,
|
|
125
|
+
progressBar: DEFAULT_PROGRESS_BAR_LABELS,
|
|
126
|
+
signedAmount: DEFAULT_SIGNED_AMOUNT_LABELS,
|
|
127
|
+
errorBoundary: DEFAULT_ERROR_BOUNDARY_LABELS,
|
|
128
|
+
authedImage: DEFAULT_AUTHED_IMAGE_LABELS,
|
|
129
|
+
imageGrid: DEFAULT_IMAGE_GRID_LABELS,
|
|
130
|
+
lightbox: DEFAULT_LIGHTBOX_LABELS,
|
|
99
131
|
};
|
package/src/i18n/kit-labels.tsx
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createContext, useContext, useMemo } from "react";
|
|
2
|
+
import type { AnchorHTMLAttributes, ReactElement, Ref } from "react";
|
|
2
3
|
import type { ReactNode } from "react";
|
|
3
4
|
import type { DataTableLabels } from "../components/data-table-labels";
|
|
4
5
|
import type { MiniCalendarLabels, WeekDay } from "../components/mini-calendar";
|
|
6
|
+
import type { CalendarHeatmapLabels } from "../components/calendar-heatmap";
|
|
5
7
|
import type { PopoverLabels } from "../components/popover";
|
|
6
8
|
import type { ChipInputLabels } from "../components/chip";
|
|
7
9
|
import type { FieldSyncLabels } from "../components/field-sync";
|
|
@@ -13,6 +15,7 @@ import type { GlobalSearchLabels } from "../search/global-search";
|
|
|
13
15
|
import type { MonthPickerLabels } from "../components/month-picker";
|
|
14
16
|
import type { PageContentsLabels } from "../components/page-contents";
|
|
15
17
|
import type { SeriesChartLabels } from "../components/series-chart-labels";
|
|
18
|
+
import type { PieChartLabels } from "../components/pie-chart-labels";
|
|
16
19
|
import type { SparklineLabels } from "../components/sparkline";
|
|
17
20
|
import type { StatTileLabels } from "../components/stat-tile";
|
|
18
21
|
import type { SignaturePadLabels } from "../components/signature-pad";
|
|
@@ -24,6 +27,9 @@ import type { DialogFrameLabels } from "../components/dialog-frame";
|
|
|
24
27
|
import type { FilePickerLabels } from "../components/file-button";
|
|
25
28
|
import type { MeasuredGridLabels } from "../components/measured-grid";
|
|
26
29
|
import type { FeedbackAttachmentFieldLabels } from "../feedback/feedback-attachment";
|
|
30
|
+
import type { FeedbackDialogTextLabels } from "../feedback/feedback-dialog";
|
|
31
|
+
import type { FeedbackComposerLabels, FeedbackThreadLabels } from "../feedback/feedback-thread";
|
|
32
|
+
import type { AccountSettingsLabels } from "../components/account-settings-labels";
|
|
27
33
|
import type { ConfirmDialogLabels } from "../components/confirm-dialog";
|
|
28
34
|
import type { FloatingPanelLabels } from "../components/floating-panel";
|
|
29
35
|
import type { CopyButtonLabels } from "../components/copy-button";
|
|
@@ -31,6 +37,15 @@ import type { BulkActionBarLabels } from "../components/bulk-action-bar";
|
|
|
31
37
|
import type { ListLabels } from "../components/list";
|
|
32
38
|
import type { BreadcrumbsLabels } from "../components/breadcrumbs";
|
|
33
39
|
import type { ToastLabels } from "../components/toast";
|
|
40
|
+
import type { FormActionsLabels } from "../components/form-actions";
|
|
41
|
+
import type { DescriptionListLabels } from "../components/description-list";
|
|
42
|
+
import type { LineItemsLabels } from "../components/line-items";
|
|
43
|
+
import type { ProgressBarLabels } from "../components/progress-bar";
|
|
44
|
+
import type { SignedAmountLabels } from "../components/signed-amount";
|
|
45
|
+
import type { ErrorBoundaryLabels } from "../components/error-boundary";
|
|
46
|
+
import type { AuthedImageLabels } from "../components/authed-image";
|
|
47
|
+
import type { ImageGridLabels } from "../components/image-grid";
|
|
48
|
+
import type { LightboxLabels } from "../components/lightbox";
|
|
34
49
|
|
|
35
50
|
/**
|
|
36
51
|
* EVERY string the kit renders, as one typed tree — and an optional provider that
|
|
@@ -83,6 +98,9 @@ export interface CommonLabels {
|
|
|
83
98
|
/** The × that puts away a banner or a notice (`AlertBanner onDismiss`). Not `close`:
|
|
84
99
|
* nothing opened, and "Close" on a banner reads as closing the page it sits on. */
|
|
85
100
|
dismiss: string;
|
|
101
|
+
/** Read after a link that opens a new tab (`<Button href external>`, TextLink):
|
|
102
|
+
* nothing else tells a screen reader the page is about to change tabs. */
|
|
103
|
+
opensInNewTab: string;
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
/** `DatePicker` / `DateRangePicker` chrome. The calendar inside has its own
|
|
@@ -197,6 +215,7 @@ export interface UiKitLabels {
|
|
|
197
215
|
common: CommonLabels;
|
|
198
216
|
dataTable: DataTableLabels;
|
|
199
217
|
miniCalendar: MiniCalendarLabels;
|
|
218
|
+
calendarHeatmap: CalendarHeatmapLabels;
|
|
200
219
|
datePicker: DatePickerLabels;
|
|
201
220
|
monthPicker: MonthPickerLabels;
|
|
202
221
|
popover: PopoverLabels;
|
|
@@ -219,6 +238,7 @@ export interface UiKitLabels {
|
|
|
219
238
|
commandPalette: CommandPaletteLabels;
|
|
220
239
|
globalSearch: GlobalSearchLabels;
|
|
221
240
|
seriesChart: SeriesChartLabels;
|
|
241
|
+
pieChart: PieChartLabels;
|
|
222
242
|
sparkline: SparklineLabels;
|
|
223
243
|
statTile: StatTileLabels;
|
|
224
244
|
signaturePad: SignaturePadLabels;
|
|
@@ -230,6 +250,14 @@ export interface UiKitLabels {
|
|
|
230
250
|
filePicker: FilePickerLabels;
|
|
231
251
|
measuredGrid: MeasuredGridLabels;
|
|
232
252
|
feedbackAttachment: FeedbackAttachmentFieldLabels;
|
|
253
|
+
/** 0.12.0: `FeedbackDialog`'s own strings; `FeedbackThread` / `FeedbackComposer`. */
|
|
254
|
+
feedbackDialog: FeedbackDialogTextLabels;
|
|
255
|
+
feedbackThread: FeedbackThreadLabels;
|
|
256
|
+
feedbackComposer: FeedbackComposerLabels;
|
|
257
|
+
/** 0.12.0: `ProfileSetting`, `PasswordSetting`, `TwoFactorSetting`, `PasskeysSetting`
|
|
258
|
+
* — one record per section. The provider takes a whole section record; the
|
|
259
|
+
* components merge it key by key, as a prop. */
|
|
260
|
+
accountSettings: AccountSettingsLabels;
|
|
233
261
|
confirmDialog: ConfirmDialogLabels;
|
|
234
262
|
floatingPanel: FloatingPanelLabels;
|
|
235
263
|
copyButton: CopyButtonLabels;
|
|
@@ -237,11 +265,36 @@ export interface UiKitLabels {
|
|
|
237
265
|
list: ListLabels;
|
|
238
266
|
breadcrumbs: BreadcrumbsLabels;
|
|
239
267
|
toast: ToastLabels;
|
|
268
|
+
form: FormActionsLabels;
|
|
269
|
+
descriptionList: DescriptionListLabels;
|
|
270
|
+
lineItems: LineItemsLabels;
|
|
271
|
+
progressBar: ProgressBarLabels;
|
|
272
|
+
signedAmount: SignedAmountLabels;
|
|
273
|
+
errorBoundary: ErrorBoundaryLabels;
|
|
274
|
+
authedImage: AuthedImageLabels;
|
|
275
|
+
imageGrid: ImageGridLabels;
|
|
276
|
+
lightbox: LightboxLabels;
|
|
240
277
|
}
|
|
241
278
|
|
|
242
|
-
/**
|
|
243
|
-
*
|
|
244
|
-
|
|
279
|
+
/**
|
|
280
|
+
* A label override, as deep as the labels go: an object of labels (a namespace, or a
|
|
281
|
+
* record inside one such as `accountSettings.passkeys` or `dataTable.presets`) may be
|
|
282
|
+
* partial at every level, because the provider merges it key by key at every level.
|
|
283
|
+
* A leaf stays as it is: a string, a function-valued label (`(n) => …`), and anything
|
|
284
|
+
* that is not purely an object (a `ReactNode` union) is given whole.
|
|
285
|
+
*/
|
|
286
|
+
export type LabelOverride<T> = [T] extends [(...args: never[]) => unknown]
|
|
287
|
+
? T
|
|
288
|
+
: [T] extends [readonly unknown[]]
|
|
289
|
+
? T
|
|
290
|
+
: [T] extends [object]
|
|
291
|
+
? { [K in keyof T]?: LabelOverride<T[K]> }
|
|
292
|
+
: T;
|
|
293
|
+
|
|
294
|
+
/** Any subset of the tree — each namespace may be partial, and so may any record
|
|
295
|
+
* inside one (`accountSettings.passkeys`, `dataTable.presets`): they are merged key
|
|
296
|
+
* by key, at every depth. */
|
|
297
|
+
export type UiKitLabelOverrides = { [K in keyof UiKitLabels]?: LabelOverride<UiKitLabels[K]> };
|
|
245
298
|
|
|
246
299
|
/* ── English defaults for the namespaces defined here ────────────────────── */
|
|
247
300
|
|
|
@@ -259,6 +312,7 @@ export const DEFAULT_COMMON_LABELS: CommonLabels = {
|
|
|
259
312
|
noResults: "No results",
|
|
260
313
|
fieldValue: (field, value) => `${field}: ${value}`,
|
|
261
314
|
dismiss: "Dismiss",
|
|
315
|
+
opensInNewTab: "opens in a new tab",
|
|
262
316
|
};
|
|
263
317
|
|
|
264
318
|
export const DEFAULT_DATE_PICKER_LABELS: DatePickerLabels = {
|
|
@@ -362,8 +416,20 @@ interface KitI18n {
|
|
|
362
416
|
labels?: UiKitLabelOverrides;
|
|
363
417
|
locale?: string;
|
|
364
418
|
weekStartsOn?: WeekDay;
|
|
419
|
+
linkComponent?: KitLinkComponent;
|
|
365
420
|
}
|
|
366
421
|
|
|
422
|
+
/**
|
|
423
|
+
* What every kit link receives: the anchor attributes the kit decided (href, class,
|
|
424
|
+
* aria-current, handlers, children, ref). An app's router link maps them —
|
|
425
|
+
* `({ href, ...p }) => <Link to={href} {...p} />` — ONCE, on the provider.
|
|
426
|
+
*/
|
|
427
|
+
export type KitLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
428
|
+
href: string;
|
|
429
|
+
ref?: Ref<HTMLAnchorElement>;
|
|
430
|
+
};
|
|
431
|
+
export type KitLinkComponent = (props: KitLinkProps) => ReactElement;
|
|
432
|
+
|
|
367
433
|
const KitI18nContext = createContext<KitI18n>({});
|
|
368
434
|
|
|
369
435
|
export interface UiKitProviderProps {
|
|
@@ -385,6 +451,14 @@ export interface UiKitProviderProps {
|
|
|
385
451
|
* and switching it to `en-GB` to get Monday changes every date and number format.
|
|
386
452
|
*/
|
|
387
453
|
weekStartsOn?: WeekDay;
|
|
454
|
+
/**
|
|
455
|
+
* The app's router link, used by every kit component that renders an in-app link
|
|
456
|
+
* and was not handed its own `renderLink` — so `renderLink` stops being repeated on
|
|
457
|
+
* each ListItem, Breadcrumbs, StatTile, Chip… (kastlan). A component's own
|
|
458
|
+
* `renderLink` wins; external links and in-page `#anchor`s stay plain `<a>`, and a
|
|
459
|
+
* hash-router `#/path` is handed to it like `/path` (see `pickLinkRenderer`).
|
|
460
|
+
*/
|
|
461
|
+
linkComponent?: KitLinkComponent;
|
|
388
462
|
children: ReactNode;
|
|
389
463
|
}
|
|
390
464
|
|
|
@@ -396,15 +470,22 @@ export interface UiKitProviderProps {
|
|
|
396
470
|
* it names, so a page can re-label one table's `dataTable.table` without restating
|
|
397
471
|
* the language.
|
|
398
472
|
*/
|
|
399
|
-
export function UiKitProvider({
|
|
473
|
+
export function UiKitProvider({
|
|
474
|
+
labels,
|
|
475
|
+
locale,
|
|
476
|
+
weekStartsOn,
|
|
477
|
+
linkComponent,
|
|
478
|
+
children,
|
|
479
|
+
}: UiKitProviderProps) {
|
|
400
480
|
const outer = useContext(KitI18nContext);
|
|
401
481
|
const value = useMemo<KitI18n>(
|
|
402
482
|
() => ({
|
|
403
483
|
locale: locale ?? outer.locale,
|
|
404
484
|
weekStartsOn: weekStartsOn ?? outer.weekStartsOn,
|
|
405
485
|
labels: mergeOverrides(outer.labels, labels),
|
|
486
|
+
linkComponent: linkComponent ?? outer.linkComponent,
|
|
406
487
|
}),
|
|
407
|
-
[outer, labels, locale, weekStartsOn],
|
|
488
|
+
[outer, labels, locale, weekStartsOn, linkComponent],
|
|
408
489
|
);
|
|
409
490
|
return <KitI18nContext.Provider value={value}>{children}</KitI18nContext.Provider>;
|
|
410
491
|
}
|
|
@@ -413,14 +494,23 @@ function isRecord(v: unknown): v is Record<string, unknown> {
|
|
|
413
494
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
414
495
|
}
|
|
415
496
|
|
|
416
|
-
/**
|
|
417
|
-
|
|
497
|
+
/** A record of labels to merge into — not a React element, which is an object too but
|
|
498
|
+
* is a label given whole. */
|
|
499
|
+
function isLabelRecord(v: unknown): v is Record<string, unknown> {
|
|
500
|
+
return isRecord(v) && !("$$typeof" in v);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Merged key by key at EVERY depth — a namespace, a record inside it
|
|
504
|
+
* (`accountSettings.passkeys`, `dataTable.presets`), and so on down — to match
|
|
505
|
+
* {@link LabelOverride}. An `undefined` is skipped at every depth, so it never blanks
|
|
506
|
+
* out what is under it. */
|
|
507
|
+
function mergeNamespace<T extends object>(base: T, over: LabelOverride<T> | Partial<T> | undefined): T {
|
|
418
508
|
if (!over) return base;
|
|
419
509
|
const out = { ...base } as Record<string, unknown>;
|
|
420
510
|
for (const [k, v] of Object.entries(over)) {
|
|
421
511
|
if (v === undefined) continue;
|
|
422
512
|
const prev = out[k];
|
|
423
|
-
out[k] =
|
|
513
|
+
out[k] = isLabelRecord(prev) && isLabelRecord(v) ? mergeNamespace(prev, v) : v;
|
|
424
514
|
}
|
|
425
515
|
return out as T;
|
|
426
516
|
}
|
|
@@ -473,6 +563,12 @@ export function useKitLocale(prop?: string): string | undefined {
|
|
|
473
563
|
/** The week start the nearest `<UiKitProvider weekStartsOn>` pins, else `undefined`
|
|
474
564
|
* (the caller then asks the locale). A component's own prop goes first:
|
|
475
565
|
* `prop ?? useKitWeekStart()`. */
|
|
566
|
+
/** The provider's router link, or `undefined` — a component's own `renderLink` should
|
|
567
|
+
* win over it: `const Link = renderLink ?? useKitLink()`. */
|
|
568
|
+
export function useKitLink(): KitLinkComponent | undefined {
|
|
569
|
+
return useContext(KitI18nContext).linkComponent;
|
|
570
|
+
}
|
|
571
|
+
|
|
476
572
|
export function useKitWeekStart(): WeekDay | undefined {
|
|
477
573
|
return useContext(KitI18nContext).weekStartsOn;
|
|
478
574
|
}
|