@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,176 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactElement, ComponentPropsWithoutRef, ReactNode, Ref, MouseEvent, KeyboardEvent } from 'react';
|
|
3
|
+
import { LucideIcon } from 'lucide-react';
|
|
4
|
+
import { ax as KitLinkComponent } from '../kit-labels-v3biUF1L.js';
|
|
5
|
+
import './data-table-labels.js';
|
|
6
|
+
import './mini-calendar.js';
|
|
7
|
+
import './calendar-heatmap.js';
|
|
8
|
+
import './popover.js';
|
|
9
|
+
import './chip.js';
|
|
10
|
+
import './field-sync.js';
|
|
11
|
+
import './tooltip.js';
|
|
12
|
+
import '../hooks/use-anchored-rect.js';
|
|
13
|
+
import '../lib/clipping.js';
|
|
14
|
+
import '../wizard/types.js';
|
|
15
|
+
import '../tour/tour.js';
|
|
16
|
+
import '../search/command-palette.js';
|
|
17
|
+
import '../search/global-search.js';
|
|
18
|
+
import '../search/search-index.js';
|
|
19
|
+
import './month-picker.js';
|
|
20
|
+
import './page-contents.js';
|
|
21
|
+
import './series-chart-labels.js';
|
|
22
|
+
import './pie-chart-labels.js';
|
|
23
|
+
import './sparkline.js';
|
|
24
|
+
import './stat-tile.js';
|
|
25
|
+
import './signature-pad.js';
|
|
26
|
+
import './password-strength.js';
|
|
27
|
+
import './danger-confirm.js';
|
|
28
|
+
import './swatch-picker.js';
|
|
29
|
+
import './tile-radio.js';
|
|
30
|
+
import './icon-picker.js';
|
|
31
|
+
import './dialog-frame.js';
|
|
32
|
+
import './modal.js';
|
|
33
|
+
import '../hooks/use-search-param-state.js';
|
|
34
|
+
import './measured-grid.js';
|
|
35
|
+
import '../feedback/feedback-attachment.js';
|
|
36
|
+
import '../feedback/feedback-dialog.js';
|
|
37
|
+
import '../feedback/feedback-thread.js';
|
|
38
|
+
import '../feedback/feedback-inbox.js';
|
|
39
|
+
import './account-settings-labels.js';
|
|
40
|
+
import './confirm-dialog.js';
|
|
41
|
+
import './floating-panel.js';
|
|
42
|
+
import './bulk-action-bar.js';
|
|
43
|
+
import './list.js';
|
|
44
|
+
import './breadcrumbs.js';
|
|
45
|
+
import './toast.js';
|
|
46
|
+
import './description-list.js';
|
|
47
|
+
import './line-items.js';
|
|
48
|
+
import './progress-bar.js';
|
|
49
|
+
import './signed-amount.js';
|
|
50
|
+
import './error-boundary.js';
|
|
51
|
+
import './authed-image.js';
|
|
52
|
+
import '../hooks/use-authed-src.js';
|
|
53
|
+
import './image-grid.js';
|
|
54
|
+
import './lightbox.js';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* An `href` that leaves the app: a scheme (`https:`, `mailto:`, `tel:`) or a
|
|
58
|
+
* protocol-relative `//host`. A router's link is for paths inside the app, so the
|
|
59
|
+
* provider's `linkComponent` is never handed one of these — they stay a plain `<a>`.
|
|
60
|
+
*
|
|
61
|
+
* NOT external, and so handed to the router link: a path (`/x`, `x`, `?q=1`) and the
|
|
62
|
+
* hash-router form `#/x` — that IS a route in a `HashRouter` app, which maps it to
|
|
63
|
+
* `to="/x"`. A plain in-page anchor (`#section`) is not external either, but it is not
|
|
64
|
+
* a route: see {@link isInPageAnchor}.
|
|
65
|
+
*/
|
|
66
|
+
declare function isExternalHref(href: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* An `href` that only scrolls the current page: `#section` (or a bare `#`). The
|
|
69
|
+
* hash-router form `#/x` is a ROUTE, not an anchor, and is not one of these. A router
|
|
70
|
+
* link must not get an anchor — under a `HashRouter` `#section` would navigate to the
|
|
71
|
+
* path `/section` — so every kit link draws it as a plain `<a>`.
|
|
72
|
+
*/
|
|
73
|
+
declare function isInPageAnchor(href: string): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Whether the provider's `linkComponent` (or a kit component's router fallback) draws
|
|
76
|
+
* `href`: an in-app path, `#/x` included — not an external `href`
|
|
77
|
+
* ({@link isExternalHref}) and not an in-page `#anchor` ({@link isInPageAnchor}).
|
|
78
|
+
*/
|
|
79
|
+
declare function isRoutableHref(href: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* What draws a kit link to `href`: the component's own `renderLink` when it was given
|
|
82
|
+
* one (it always wins — the caller asked for it by name), else the
|
|
83
|
+
* `<UiKitProvider linkComponent>` for a routable `href` ({@link isRoutableHref}), else
|
|
84
|
+
* `undefined` — a plain `<a>`. The one rule every kit link follows: `https:` /
|
|
85
|
+
* `mailto:` / `//host` and `#section` stay plain anchors; `/x`, `x` and a hash-router
|
|
86
|
+
* `#/x` go to the router link. Every kit link props type is a subset of the anchor
|
|
87
|
+
* attributes `KitLinkProps` describes, which is what makes the cast sound.
|
|
88
|
+
* @internal Shared by the kit's link-bearing components; not part of the barrel.
|
|
89
|
+
*/
|
|
90
|
+
declare function pickLinkRenderer<P>(own: ((props: P) => ReactElement) | undefined, kitLink: KitLinkComponent | undefined, href: string | undefined): ((props: P) => ReactElement) | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Draws `props` through `render` as a component of its own, so a router link's hooks
|
|
93
|
+
* belong to it and not to the kit component calling it — for the kit components that
|
|
94
|
+
* call their own `renderLink` inline and reach the provider's link through here.
|
|
95
|
+
* @internal
|
|
96
|
+
*/
|
|
97
|
+
declare function RenderedKitLink<P>({ render, props }: {
|
|
98
|
+
render: (props: P) => ReactElement;
|
|
99
|
+
props: P;
|
|
100
|
+
}): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
101
|
+
/** `brand` (default) for a link in running text, `muted` for footer and meta links
|
|
102
|
+
* that should not compete with the content, `danger` for a destructive one ("Leave
|
|
103
|
+
* group"), `inherit` for a link that takes its surroundings' colour. */
|
|
104
|
+
type TextLinkTone = "brand" | "muted" | "danger" | "inherit";
|
|
105
|
+
/** `hover` (default): underlined under the pointer — the idiom all three apps draw.
|
|
106
|
+
* `always` for a link inside prose, where colour alone must not be the only mark
|
|
107
|
+
* (WCAG 1.4.1). `none` where the context already says "link" (a footer nav). */
|
|
108
|
+
type TextLinkUnderline = "hover" | "always" | "none";
|
|
109
|
+
/** The `aria-current` token. `true` on {@link TextLinkProps.current} means `page`. */
|
|
110
|
+
type TextLinkCurrent = "page" | "step" | "location" | "date" | "time" | "true";
|
|
111
|
+
/** What {@link TextLinkProps.renderLink} (and the provider's `linkComponent`) is
|
|
112
|
+
* handed. Spread it onto your router's link — `({ href, ...p }) => <Link to={href} {...p} />`. */
|
|
113
|
+
interface TextLinkRenderProps {
|
|
114
|
+
href: string;
|
|
115
|
+
/** The link's look — keep it, or the tone, underline and focus ring go. */
|
|
116
|
+
className: string;
|
|
117
|
+
children: ReactNode;
|
|
118
|
+
ref?: Ref<HTMLAnchorElement>;
|
|
119
|
+
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
120
|
+
onKeyDown?: (event: KeyboardEvent<HTMLAnchorElement>) => void;
|
|
121
|
+
"aria-current"?: TextLinkCurrent;
|
|
122
|
+
id?: string;
|
|
123
|
+
title?: string;
|
|
124
|
+
[key: `aria-${string}`]: string | boolean | number | undefined;
|
|
125
|
+
[key: `data-${string}`]: unknown;
|
|
126
|
+
}
|
|
127
|
+
interface TextLinkProps extends Omit<ComponentPropsWithoutRef<"a">, "href" | "children" | "aria-current" | "onClick" | "onKeyDown"> {
|
|
128
|
+
href: string;
|
|
129
|
+
children: ReactNode;
|
|
130
|
+
/** Default `brand`. */
|
|
131
|
+
tone?: TextLinkTone;
|
|
132
|
+
/** Default `hover`. */
|
|
133
|
+
underline?: TextLinkUnderline;
|
|
134
|
+
/**
|
|
135
|
+
* The link leaves the app: a plain `<a target="_blank" rel="noopener noreferrer">`
|
|
136
|
+
* (never the router's), an external-link mark after the text (mirrored in RTL), and
|
|
137
|
+
* "opens in a new tab" (`common.opensInNewTab`) read after it — the mark says it
|
|
138
|
+
* only to the eye. kastlan's BWO / BFS source links (rent-increase-calculator-page).
|
|
139
|
+
*/
|
|
140
|
+
external?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Keep the click (and the Enter that activates the link) from reaching an ancestor —
|
|
143
|
+
* a link in a DataTable row with `onRowClick` goes to its own target instead of
|
|
144
|
+
* running the row's action. kastlan's `CellLink` did this by hand.
|
|
145
|
+
*/
|
|
146
|
+
stopPropagation?: boolean;
|
|
147
|
+
/** Mark the link as the current one of a set — `true` is `aria-current="page"`, the
|
|
148
|
+
* legal footer's link to the page you are on. Drawn in semibold. */
|
|
149
|
+
current?: boolean | TextLinkCurrent;
|
|
150
|
+
/** A Lucide icon before the text, at the text's size — kastlan's "← Back" link. */
|
|
151
|
+
icon?: LucideIcon;
|
|
152
|
+
/** Your router's link for an in-app `href`. Default: the `<UiKitProvider
|
|
153
|
+
* linkComponent>`, then a plain `<a>`. Ignored on an `external` link. */
|
|
154
|
+
renderLink?: (props: TextLinkRenderProps) => ReactElement;
|
|
155
|
+
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
156
|
+
onKeyDown?: (event: KeyboardEvent<HTMLAnchorElement>) => void;
|
|
157
|
+
ref?: Ref<HTMLAnchorElement>;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The one inline link: a real `<a href>` (middle click, "open in new tab", link
|
|
161
|
+
* semantics) through the app's router for an in-app path, in one of four tones.
|
|
162
|
+
*
|
|
163
|
+
* kastlan draws this link a dozen ways by hand — `EntityLink` and `CellLink` (text-primary,
|
|
164
|
+
* hover underline, one stopping the row click), the legal pages' back link and footer,
|
|
165
|
+
* the feedback page's URL links, the calculator's external sources with a hand-placed
|
|
166
|
+
* ↗ and no word for a screen reader — and the lease wizard's unit step (lease-unit-step)
|
|
167
|
+
* used a raw `<a>` to an in-app route, which reloads the whole app. Here the router
|
|
168
|
+
* link comes from `<UiKitProvider linkComponent>`, set once.
|
|
169
|
+
*
|
|
170
|
+
* Inline, not `inline-flex`: a link inside a sentence must wrap with it. The icons sit
|
|
171
|
+
* on the text's baseline at the text's size, so they scale with whatever type the link
|
|
172
|
+
* is set in. The focus ring is an outline, which follows every line of a wrapped link.
|
|
173
|
+
*/
|
|
174
|
+
declare function TextLink({ href, children, tone, underline, external, stopPropagation, current, icon: Icon, renderLink, className, onClick, onKeyDown, ref, ...rest }: TextLinkProps): react.JSX.Element;
|
|
175
|
+
|
|
176
|
+
export { RenderedKitLink, TextLink, type TextLinkCurrent, type TextLinkProps, type TextLinkRenderProps, type TextLinkTone, type TextLinkUnderline, isExternalHref, isInPageAnchor, isRoutableHref, pickLinkRenderer };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ExternalLink } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { DEFAULT_COMMON_LABELS, useKitLabels, useKitLink } from "../i18n/kit-labels.js";
|
|
6
|
+
function isExternalHref(href) {
|
|
7
|
+
return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href);
|
|
8
|
+
}
|
|
9
|
+
function isInPageAnchor(href) {
|
|
10
|
+
return href.startsWith("#") && !href.startsWith("#/");
|
|
11
|
+
}
|
|
12
|
+
function isRoutableHref(href) {
|
|
13
|
+
return !isExternalHref(href) && !isInPageAnchor(href);
|
|
14
|
+
}
|
|
15
|
+
function pickLinkRenderer(own, kitLink, href) {
|
|
16
|
+
if (own) return own;
|
|
17
|
+
if (!kitLink || href === void 0 || !isRoutableHref(href)) return void 0;
|
|
18
|
+
return kitLink;
|
|
19
|
+
}
|
|
20
|
+
function RenderedKitLink({ render, props }) {
|
|
21
|
+
return render(props);
|
|
22
|
+
}
|
|
23
|
+
const TONE = {
|
|
24
|
+
brand: "text-[var(--brand)] hover:text-[var(--brand-hover)]",
|
|
25
|
+
muted: "text-[var(--text-muted)] hover:text-[var(--text-primary)]",
|
|
26
|
+
danger: "text-[var(--danger)] hover:text-[var(--danger-hover)]",
|
|
27
|
+
inherit: ""
|
|
28
|
+
};
|
|
29
|
+
const UNDERLINE = {
|
|
30
|
+
hover: "no-underline hover:underline",
|
|
31
|
+
always: "underline",
|
|
32
|
+
none: "no-underline"
|
|
33
|
+
};
|
|
34
|
+
function TextLink({
|
|
35
|
+
href,
|
|
36
|
+
children,
|
|
37
|
+
tone = "brand",
|
|
38
|
+
underline = "hover",
|
|
39
|
+
external = false,
|
|
40
|
+
stopPropagation = false,
|
|
41
|
+
current,
|
|
42
|
+
icon: Icon,
|
|
43
|
+
renderLink,
|
|
44
|
+
className,
|
|
45
|
+
onClick,
|
|
46
|
+
onKeyDown,
|
|
47
|
+
ref,
|
|
48
|
+
...rest
|
|
49
|
+
}) {
|
|
50
|
+
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
51
|
+
const kitLink = useKitLink();
|
|
52
|
+
const ariaCurrent = current === true ? "page" : current === false ? void 0 : current;
|
|
53
|
+
const look = cn(
|
|
54
|
+
"rounded-sm underline-offset-2 transition-colors",
|
|
55
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--brand)]",
|
|
56
|
+
TONE[tone],
|
|
57
|
+
UNDERLINE[underline],
|
|
58
|
+
ariaCurrent && "font-semibold",
|
|
59
|
+
className
|
|
60
|
+
);
|
|
61
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
62
|
+
Icon && /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "me-1 inline size-[1em] shrink-0 align-[-0.125em]" }),
|
|
63
|
+
children,
|
|
64
|
+
external && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
65
|
+
/* @__PURE__ */ jsx(ExternalLink, { "aria-hidden": true, className: "ms-1 inline size-[0.85em] align-[-0.1em] rtl:-scale-x-100" }),
|
|
66
|
+
" ",
|
|
67
|
+
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
68
|
+
"(",
|
|
69
|
+
common.opensInNewTab,
|
|
70
|
+
")"
|
|
71
|
+
] })
|
|
72
|
+
] })
|
|
73
|
+
] });
|
|
74
|
+
const props = {
|
|
75
|
+
...rest,
|
|
76
|
+
ref,
|
|
77
|
+
href,
|
|
78
|
+
className: look,
|
|
79
|
+
"aria-current": ariaCurrent,
|
|
80
|
+
onClick: stopPropagation ? (event) => {
|
|
81
|
+
event.stopPropagation();
|
|
82
|
+
onClick?.(event);
|
|
83
|
+
} : onClick,
|
|
84
|
+
onKeyDown: stopPropagation ? (event) => {
|
|
85
|
+
if (event.key === "Enter") event.stopPropagation();
|
|
86
|
+
onKeyDown?.(event);
|
|
87
|
+
} : onKeyDown,
|
|
88
|
+
children: body
|
|
89
|
+
};
|
|
90
|
+
const render = external ? void 0 : pickLinkRenderer(renderLink, kitLink, href);
|
|
91
|
+
if (render) return /* @__PURE__ */ jsx(RenderedTextLink, { render, ...props });
|
|
92
|
+
const { children: content, ...anchor } = props;
|
|
93
|
+
return /* @__PURE__ */ jsx("a", { ...anchor, ...external ? { target: "_blank", rel: "noopener noreferrer" } : null, children: content });
|
|
94
|
+
}
|
|
95
|
+
function RenderedTextLink({
|
|
96
|
+
render,
|
|
97
|
+
...props
|
|
98
|
+
}) {
|
|
99
|
+
return render(props);
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
RenderedKitLink,
|
|
103
|
+
TextLink,
|
|
104
|
+
isExternalHref,
|
|
105
|
+
isInPageAnchor,
|
|
106
|
+
isRoutableHref,
|
|
107
|
+
pickLinkRenderer
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=text-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/text-link.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, KeyboardEvent, MouseEvent, ReactElement, ReactNode, Ref } from \"react\";\nimport { ExternalLink } from \"lucide-react\";\nimport type { LucideIcon } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { DEFAULT_COMMON_LABELS, useKitLabels, useKitLink } from \"../i18n/kit-labels\";\nimport type { KitLinkComponent } from \"../i18n/kit-labels\";\n\n/* ── Choosing what draws a link ──────────────────────────────────────────── */\n\n/**\n * An `href` that leaves the app: a scheme (`https:`, `mailto:`, `tel:`) or a\n * protocol-relative `//host`. A router's link is for paths inside the app, so the\n * provider's `linkComponent` is never handed one of these — they stay a plain `<a>`.\n *\n * NOT external, and so handed to the router link: a path (`/x`, `x`, `?q=1`) and the\n * hash-router form `#/x` — that IS a route in a `HashRouter` app, which maps it to\n * `to=\"/x\"`. A plain in-page anchor (`#section`) is not external either, but it is not\n * a route: see {@link isInPageAnchor}.\n */\nexport function isExternalHref(href: string): boolean {\n return /^(?:[a-z][a-z\\d+.-]*:|\\/\\/)/i.test(href);\n}\n\n/**\n * An `href` that only scrolls the current page: `#section` (or a bare `#`). The\n * hash-router form `#/x` is a ROUTE, not an anchor, and is not one of these. A router\n * link must not get an anchor — under a `HashRouter` `#section` would navigate to the\n * path `/section` — so every kit link draws it as a plain `<a>`.\n */\nexport function isInPageAnchor(href: string): boolean {\n return href.startsWith(\"#\") && !href.startsWith(\"#/\");\n}\n\n/**\n * Whether the provider's `linkComponent` (or a kit component's router fallback) draws\n * `href`: an in-app path, `#/x` included — not an external `href`\n * ({@link isExternalHref}) and not an in-page `#anchor` ({@link isInPageAnchor}).\n */\nexport function isRoutableHref(href: string): boolean {\n return !isExternalHref(href) && !isInPageAnchor(href);\n}\n\n/**\n * What draws a kit link to `href`: the component's own `renderLink` when it was given\n * one (it always wins — the caller asked for it by name), else the\n * `<UiKitProvider linkComponent>` for a routable `href` ({@link isRoutableHref}), else\n * `undefined` — a plain `<a>`. The one rule every kit link follows: `https:` /\n * `mailto:` / `//host` and `#section` stay plain anchors; `/x`, `x` and a hash-router\n * `#/x` go to the router link. Every kit link props type is a subset of the anchor\n * attributes `KitLinkProps` describes, which is what makes the cast sound.\n * @internal Shared by the kit's link-bearing components; not part of the barrel.\n */\nexport function pickLinkRenderer<P>(\n own: ((props: P) => ReactElement) | undefined,\n kitLink: KitLinkComponent | undefined,\n href: string | undefined,\n): ((props: P) => ReactElement) | undefined {\n if (own) return own;\n if (!kitLink || href === undefined || !isRoutableHref(href)) return undefined;\n return kitLink as unknown as (props: P) => ReactElement;\n}\n\n/**\n * Draws `props` through `render` as a component of its own, so a router link's hooks\n * belong to it and not to the kit component calling it — for the kit components that\n * call their own `renderLink` inline and reach the provider's link through here.\n * @internal\n */\nexport function RenderedKitLink<P>({ render, props }: { render: (props: P) => ReactElement; props: P }) {\n return render(props);\n}\n\n/* ── TextLink ─────────────────────────────────────────────────────────────── */\n\n/** `brand` (default) for a link in running text, `muted` for footer and meta links\n * that should not compete with the content, `danger` for a destructive one (\"Leave\n * group\"), `inherit` for a link that takes its surroundings' colour. */\nexport type TextLinkTone = \"brand\" | \"muted\" | \"danger\" | \"inherit\";\n\n/** `hover` (default): underlined under the pointer — the idiom all three apps draw.\n * `always` for a link inside prose, where colour alone must not be the only mark\n * (WCAG 1.4.1). `none` where the context already says \"link\" (a footer nav). */\nexport type TextLinkUnderline = \"hover\" | \"always\" | \"none\";\n\n/** The `aria-current` token. `true` on {@link TextLinkProps.current} means `page`. */\nexport type TextLinkCurrent = \"page\" | \"step\" | \"location\" | \"date\" | \"time\" | \"true\";\n\n/** What {@link TextLinkProps.renderLink} (and the provider's `linkComponent`) is\n * handed. Spread it onto your router's link — `({ href, ...p }) => <Link to={href} {...p} />`. */\nexport interface TextLinkRenderProps {\n href: string;\n /** The link's look — keep it, or the tone, underline and focus ring go. */\n className: string;\n children: ReactNode;\n ref?: Ref<HTMLAnchorElement>;\n onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;\n onKeyDown?: (event: KeyboardEvent<HTMLAnchorElement>) => void;\n \"aria-current\"?: TextLinkCurrent;\n id?: string;\n title?: string;\n [key: `aria-${string}`]: string | boolean | number | undefined;\n [key: `data-${string}`]: unknown;\n}\n\nexport interface TextLinkProps\n extends Omit<ComponentPropsWithoutRef<\"a\">, \"href\" | \"children\" | \"aria-current\" | \"onClick\" | \"onKeyDown\"> {\n href: string;\n children: ReactNode;\n /** Default `brand`. */\n tone?: TextLinkTone;\n /** Default `hover`. */\n underline?: TextLinkUnderline;\n /**\n * The link leaves the app: a plain `<a target=\"_blank\" rel=\"noopener noreferrer\">`\n * (never the router's), an external-link mark after the text (mirrored in RTL), and\n * \"opens in a new tab\" (`common.opensInNewTab`) read after it — the mark says it\n * only to the eye. kastlan's BWO / BFS source links (rent-increase-calculator-page).\n */\n external?: boolean;\n /**\n * Keep the click (and the Enter that activates the link) from reaching an ancestor —\n * a link in a DataTable row with `onRowClick` goes to its own target instead of\n * running the row's action. kastlan's `CellLink` did this by hand.\n */\n stopPropagation?: boolean;\n /** Mark the link as the current one of a set — `true` is `aria-current=\"page\"`, the\n * legal footer's link to the page you are on. Drawn in semibold. */\n current?: boolean | TextLinkCurrent;\n /** A Lucide icon before the text, at the text's size — kastlan's \"← Back\" link. */\n icon?: LucideIcon;\n /** Your router's link for an in-app `href`. Default: the `<UiKitProvider\n * linkComponent>`, then a plain `<a>`. Ignored on an `external` link. */\n renderLink?: (props: TextLinkRenderProps) => ReactElement;\n onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;\n onKeyDown?: (event: KeyboardEvent<HTMLAnchorElement>) => void;\n ref?: Ref<HTMLAnchorElement>;\n}\n\nconst TONE: Record<TextLinkTone, string> = {\n brand: \"text-[var(--brand)] hover:text-[var(--brand-hover)]\",\n muted: \"text-[var(--text-muted)] hover:text-[var(--text-primary)]\",\n danger: \"text-[var(--danger)] hover:text-[var(--danger-hover)]\",\n inherit: \"\",\n};\n\nconst UNDERLINE: Record<TextLinkUnderline, string> = {\n hover: \"no-underline hover:underline\",\n always: \"underline\",\n none: \"no-underline\",\n};\n\n/**\n * The one inline link: a real `<a href>` (middle click, \"open in new tab\", link\n * semantics) through the app's router for an in-app path, in one of four tones.\n *\n * kastlan draws this link a dozen ways by hand — `EntityLink` and `CellLink` (text-primary,\n * hover underline, one stopping the row click), the legal pages' back link and footer,\n * the feedback page's URL links, the calculator's external sources with a hand-placed\n * ↗ and no word for a screen reader — and the lease wizard's unit step (lease-unit-step)\n * used a raw `<a>` to an in-app route, which reloads the whole app. Here the router\n * link comes from `<UiKitProvider linkComponent>`, set once.\n *\n * Inline, not `inline-flex`: a link inside a sentence must wrap with it. The icons sit\n * on the text's baseline at the text's size, so they scale with whatever type the link\n * is set in. The focus ring is an outline, which follows every line of a wrapped link.\n */\nexport function TextLink({\n href,\n children,\n tone = \"brand\",\n underline = \"hover\",\n external = false,\n stopPropagation = false,\n current,\n icon: Icon,\n renderLink,\n className,\n onClick,\n onKeyDown,\n ref,\n ...rest\n}: TextLinkProps) {\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const kitLink = useKitLink();\n const ariaCurrent: TextLinkCurrent | undefined =\n current === true ? \"page\" : current === false ? undefined : current;\n\n const look = cn(\n \"rounded-sm underline-offset-2 transition-colors\",\n \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--brand)]\",\n TONE[tone],\n UNDERLINE[underline],\n ariaCurrent && \"font-semibold\",\n className,\n );\n\n const body = (\n <>\n {Icon && <Icon aria-hidden className=\"me-1 inline size-[1em] shrink-0 align-[-0.125em]\" />}\n {children}\n {external && (\n <>\n <ExternalLink aria-hidden className=\"ms-1 inline size-[0.85em] align-[-0.1em] rtl:-scale-x-100\" />\n {\" \"}\n <span className=\"sr-only\">({common.opensInNewTab})</span>\n </>\n )}\n </>\n );\n\n const props: TextLinkRenderProps = {\n ...(rest as Partial<TextLinkRenderProps>),\n ref,\n href,\n className: look,\n \"aria-current\": ariaCurrent,\n onClick: stopPropagation\n ? (event) => {\n event.stopPropagation();\n onClick?.(event);\n }\n : onClick,\n onKeyDown: stopPropagation\n ? (event) => {\n if (event.key === \"Enter\") event.stopPropagation();\n onKeyDown?.(event);\n }\n : onKeyDown,\n children: body,\n };\n\n const render = external ? undefined : pickLinkRenderer(renderLink, kitLink, href);\n if (render) return <RenderedTextLink render={render} {...props} />;\n const { children: content, ...anchor } = props;\n return (\n <a {...anchor} {...(external ? { target: \"_blank\", rel: \"noopener noreferrer\" } : null)}>\n {content}\n </a>\n );\n}\n\n/** Calls the link renderer as a component, so a router link's hooks are its own. */\nfunction RenderedTextLink({\n render,\n ...props\n}: TextLinkRenderProps & { render: (props: TextLinkRenderProps) => ReactElement }) {\n return render(props);\n}\n"],"mappings":";AAsMe,SAGP,UAHO,KAML,YANK;AArMf,SAAS,oBAAoB;AAE7B,SAAS,UAAU;AACnB,SAAS,uBAAuB,cAAc,kBAAkB;AAezD,SAAS,eAAe,MAAuB;AACpD,SAAO,+BAA+B,KAAK,IAAI;AACjD;AAQO,SAAS,eAAe,MAAuB;AACpD,SAAO,KAAK,WAAW,GAAG,KAAK,CAAC,KAAK,WAAW,IAAI;AACtD;AAOO,SAAS,eAAe,MAAuB;AACpD,SAAO,CAAC,eAAe,IAAI,KAAK,CAAC,eAAe,IAAI;AACtD;AAYO,SAAS,iBACd,KACA,SACA,MAC0C;AAC1C,MAAI,IAAK,QAAO;AAChB,MAAI,CAAC,WAAW,SAAS,UAAa,CAAC,eAAe,IAAI,EAAG,QAAO;AACpE,SAAO;AACT;AAQO,SAAS,gBAAmB,EAAE,QAAQ,MAAM,GAAqD;AACtG,SAAO,OAAO,KAAK;AACrB;AAoEA,MAAM,OAAqC;AAAA,EACzC,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AACX;AAEA,MAAM,YAA+C;AAAA,EACnD,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AACR;AAiBO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,SAAS,aAAa,UAAU,qBAAqB;AAC3D,QAAM,UAAU,WAAW;AAC3B,QAAM,cACJ,YAAY,OAAO,SAAS,YAAY,QAAQ,SAAY;AAE9D,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA,KAAK,IAAI;AAAA,IACT,UAAU,SAAS;AAAA,IACnB,eAAe;AAAA,IACf;AAAA,EACF;AAEA,QAAM,OACJ,iCACG;AAAA,YAAQ,oBAAC,QAAK,eAAW,MAAC,WAAU,oDAAmD;AAAA,IACvF;AAAA,IACA,YACC,iCACE;AAAA,0BAAC,gBAAa,eAAW,MAAC,WAAU,6DAA4D;AAAA,MAC/F;AAAA,MACD,qBAAC,UAAK,WAAU,WAAU;AAAA;AAAA,QAAE,OAAO;AAAA,QAAc;AAAA,SAAC;AAAA,OACpD;AAAA,KAEJ;AAGF,QAAM,QAA6B;AAAA,IACjC,GAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,SAAS,kBACL,CAAC,UAAU;AACT,YAAM,gBAAgB;AACtB,gBAAU,KAAK;AAAA,IACjB,IACA;AAAA,IACJ,WAAW,kBACP,CAAC,UAAU;AACT,UAAI,MAAM,QAAQ,QAAS,OAAM,gBAAgB;AACjD,kBAAY,KAAK;AAAA,IACnB,IACA;AAAA,IACJ,UAAU;AAAA,EACZ;AAEA,QAAM,SAAS,WAAW,SAAY,iBAAiB,YAAY,SAAS,IAAI;AAChF,MAAI,OAAQ,QAAO,oBAAC,oBAAiB,QAAiB,GAAG,OAAO;AAChE,QAAM,EAAE,UAAU,SAAS,GAAG,OAAO,IAAI;AACzC,SACE,oBAAC,OAAG,GAAG,QAAS,GAAI,WAAW,EAAE,QAAQ,UAAU,KAAK,sBAAsB,IAAI,MAC/E,mBACH;AAEJ;AAGA,SAAS,iBAAiB;AAAA,EACxB;AAAA,EACA,GAAG;AACL,GAAmF;AACjF,SAAO,OAAO,KAAK;AACrB;","names":[]}
|
|
@@ -7,8 +7,15 @@ import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
|
7
7
|
* (control-page.tsx:119/123/127) draw. Two sizes because the label sits either inside a
|
|
8
8
|
* dense panel (a legend, a menu) or above a block of page content, and one size looked
|
|
9
9
|
* wrong in the other place.
|
|
10
|
+
*
|
|
11
|
+
* `md` (0.11.0) is 11px, the rung between them: keksdose's budget-summary card labels
|
|
12
|
+
* its figures ("Assigned", "Activity", "Upcoming", "Available" —
|
|
13
|
+
* budget-summary-card:258/264/286/297) and its support thread its day divider and a message's meta line
|
|
14
|
+
* (support-thread:153/223) at `text-[11px]` by hand — 10px read as a footnote under a
|
|
15
|
+
* figure that size, 12px competed with it. Named `md` rather than slotted in order
|
|
16
|
+
* because `sm` was already the default and renaming it would move every caller.
|
|
10
17
|
*/
|
|
11
|
-
type SectionLabelSize = "xs" | "sm";
|
|
18
|
+
type SectionLabelSize = "xs" | "md" | "sm";
|
|
12
19
|
/**
|
|
13
20
|
* The small, uppercase, tracked, muted label — as a class string, per size.
|
|
14
21
|
*
|
package/dist/components/text.js
CHANGED
|
@@ -3,6 +3,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
const SECTION_LABEL_CLASS = {
|
|
5
5
|
xs: "text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]",
|
|
6
|
+
md: "text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)]",
|
|
6
7
|
sm: "text-xs font-semibold uppercase tracking-wide text-[var(--text-muted)]"
|
|
7
8
|
};
|
|
8
9
|
function SectionLabel({ as = "h3", size = "sm", className, children, ...rest }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/text.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ElementType, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\n\n/* ── SectionLabel ─────────────────────────────────────────────────────────── */\n\n/**\n * `xs` is 10px — the size `LegendGroup`'s title and the top-bar menus' heading row draw;\n * `sm` (default) is 12px — the size `StatTile`'s label and lenkbank's section headings\n * (control-page.tsx:119/123/127) draw. Two sizes because the label sits either inside a\n * dense panel (a legend, a menu) or above a block of page content, and one size looked\n * wrong in the other place.\n */\nexport type SectionLabelSize = \"xs\" | \"sm\";\n\n/**\n * The small, uppercase, tracked, muted label — as a class string, per size.\n *\n * Exported because `LegendGroup` and `StatTile` already draw it inline, and a class a\n * caller has to position itself (a `<th>`, a `<legend>`, a menu `<li>`) is better\n * reached as a string than wrapped in a component that only takes a `className`. The\n * uppercase is CSS, so a screen reader reads the text as written: write it in normal\n * case.\n */\nexport const SECTION_LABEL_CLASS: Record<SectionLabelSize, string> = {\n xs: \"text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]\",\n sm: \"text-xs font-semibold uppercase tracking-wide text-[var(--text-muted)]\",\n};\n\ntype SectionLabelElement = \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"span\" | \"div\" | \"legend\";\n\nexport interface SectionLabelProps extends ComponentPropsWithoutRef<\"h3\"> {\n /**\n * The element — which is the heading LEVEL, and so is the caller's to pick: the kit\n * cannot know where in the outline the label sits. Default `h3`, which is what\n * lenkbank's control page writes under its `h2` card title. Pass `span` or `p` for a\n * label that is not a heading at all (a legend group's title).\n */\n as?: SectionLabelElement;\n size?: SectionLabelSize;\n children: ReactNode;\n}\n\n/**\n * A section's small uppercase label. lenkbank spells\n * `text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400`\n * on every `<h3>` of its control page and more; keksdose's budget switcher writes the\n * 10px version on a menu row. One component, the token colour, and the level chosen\n * where the outline is known.\n */\nexport function SectionLabel({ as = \"h3\", size = \"sm\", className, children, ...rest }: SectionLabelProps) {\n const Tag = as as ElementType;\n return (\n <Tag {...rest} className={cn(SECTION_LABEL_CLASS[size], className)}>\n {children}\n </Tag>\n );\n}\n\n/* ── Caption ──────────────────────────────────────────────────────────────── */\n\n/**\n * The caption type as a class string: 11px, snug, muted. lenkbank's `CAPTION`\n * (shared/ui/caption.ts:23) — written out 31 times before it was a constant — as a\n * token colour rather than `slate-500 dark:slate-400`, so it follows the palette.\n * Exported for the same reason lenkbank's is a string: every caller positions it\n * differently (`mt-1`, `self-end pb-2`, `border-t`), and what must be one thing is the\n * type, not the box.\n */\nexport const CAPTION_CLASS = \"text-[11px] leading-snug text-[var(--text-muted)]\";\n\nexport interface CaptionProps extends ComponentPropsWithoutRef<\"p\"> {\n /** Default `p`; `span` for a caption inside a line of other content. */\n as?: \"p\" | \"span\" | \"div\";\n children: ReactNode;\n}\n\n/**\n * The small grey sentence under a field, a table or a button — prose ABOUT the thing\n * above it. Not `FieldHint`, which hides behind a field label's \"?\" so a paragraph does\n * not make one field taller than its neighbour (lenkbank feedback #27).\n */\nexport function Caption({ as = \"p\", className, children, ...rest }: CaptionProps) {\n const Tag = as as ElementType;\n return (\n <Tag {...rest} className={cn(CAPTION_CLASS, className)}>\n {children}\n </Tag>\n );\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/components/text.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ElementType, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\n\n/* ── SectionLabel ─────────────────────────────────────────────────────────── */\n\n/**\n * `xs` is 10px — the size `LegendGroup`'s title and the top-bar menus' heading row draw;\n * `sm` (default) is 12px — the size `StatTile`'s label and lenkbank's section headings\n * (control-page.tsx:119/123/127) draw. Two sizes because the label sits either inside a\n * dense panel (a legend, a menu) or above a block of page content, and one size looked\n * wrong in the other place.\n *\n * `md` (0.11.0) is 11px, the rung between them: keksdose's budget-summary card labels\n * its figures (\"Assigned\", \"Activity\", \"Upcoming\", \"Available\" —\n * budget-summary-card:258/264/286/297) and its support thread its day divider and a message's meta line\n * (support-thread:153/223) at `text-[11px]` by hand — 10px read as a footnote under a\n * figure that size, 12px competed with it. Named `md` rather than slotted in order\n * because `sm` was already the default and renaming it would move every caller.\n */\nexport type SectionLabelSize = \"xs\" | \"md\" | \"sm\";\n\n/**\n * The small, uppercase, tracked, muted label — as a class string, per size.\n *\n * Exported because `LegendGroup` and `StatTile` already draw it inline, and a class a\n * caller has to position itself (a `<th>`, a `<legend>`, a menu `<li>`) is better\n * reached as a string than wrapped in a component that only takes a `className`. The\n * uppercase is CSS, so a screen reader reads the text as written: write it in normal\n * case.\n */\nexport const SECTION_LABEL_CLASS: Record<SectionLabelSize, string> = {\n xs: \"text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]\",\n md: \"text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)]\",\n sm: \"text-xs font-semibold uppercase tracking-wide text-[var(--text-muted)]\",\n};\n\ntype SectionLabelElement = \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"span\" | \"div\" | \"legend\";\n\nexport interface SectionLabelProps extends ComponentPropsWithoutRef<\"h3\"> {\n /**\n * The element — which is the heading LEVEL, and so is the caller's to pick: the kit\n * cannot know where in the outline the label sits. Default `h3`, which is what\n * lenkbank's control page writes under its `h2` card title. Pass `span` or `p` for a\n * label that is not a heading at all (a legend group's title).\n */\n as?: SectionLabelElement;\n size?: SectionLabelSize;\n children: ReactNode;\n}\n\n/**\n * A section's small uppercase label. lenkbank spells\n * `text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400`\n * on every `<h3>` of its control page and more; keksdose's budget switcher writes the\n * 10px version on a menu row. One component, the token colour, and the level chosen\n * where the outline is known.\n */\nexport function SectionLabel({ as = \"h3\", size = \"sm\", className, children, ...rest }: SectionLabelProps) {\n const Tag = as as ElementType;\n return (\n <Tag {...rest} className={cn(SECTION_LABEL_CLASS[size], className)}>\n {children}\n </Tag>\n );\n}\n\n/* ── Caption ──────────────────────────────────────────────────────────────── */\n\n/**\n * The caption type as a class string: 11px, snug, muted. lenkbank's `CAPTION`\n * (shared/ui/caption.ts:23) — written out 31 times before it was a constant — as a\n * token colour rather than `slate-500 dark:slate-400`, so it follows the palette.\n * Exported for the same reason lenkbank's is a string: every caller positions it\n * differently (`mt-1`, `self-end pb-2`, `border-t`), and what must be one thing is the\n * type, not the box.\n */\nexport const CAPTION_CLASS = \"text-[11px] leading-snug text-[var(--text-muted)]\";\n\nexport interface CaptionProps extends ComponentPropsWithoutRef<\"p\"> {\n /** Default `p`; `span` for a caption inside a line of other content. */\n as?: \"p\" | \"span\" | \"div\";\n children: ReactNode;\n}\n\n/**\n * The small grey sentence under a field, a table or a button — prose ABOUT the thing\n * above it. Not `FieldHint`, which hides behind a field label's \"?\" so a paragraph does\n * not make one field taller than its neighbour (lenkbank feedback #27).\n */\nexport function Caption({ as = \"p\", className, children, ...rest }: CaptionProps) {\n const Tag = as as ElementType;\n return (\n <Tag {...rest} className={cn(CAPTION_CLASS, className)}>\n {children}\n </Tag>\n );\n}\n"],"mappings":";AA4DI;AA3DJ,SAAS,UAAU;AA6BZ,MAAM,sBAAwD;AAAA,EACnE,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAuBO,SAAS,aAAa,EAAE,KAAK,MAAM,OAAO,MAAM,WAAW,UAAU,GAAG,KAAK,GAAsB;AACxG,QAAM,MAAM;AACZ,SACE,oBAAC,OAAK,GAAG,MAAM,WAAW,GAAG,oBAAoB,IAAI,GAAG,SAAS,GAC9D,UACH;AAEJ;AAYO,MAAM,gBAAgB;AAatB,SAAS,QAAQ,EAAE,KAAK,KAAK,WAAW,UAAU,GAAG,KAAK,GAAiB;AAChF,QAAM,MAAM;AACZ,SACE,oBAAC,OAAK,GAAG,MAAM,WAAW,GAAG,eAAe,SAAS,GAClD,UACH;AAEJ;","names":[]}
|
|
@@ -1,7 +1,56 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { InputProps } from '
|
|
2
|
+
import { aw as InputProps } from '../kit-labels-v3biUF1L.js';
|
|
3
|
+
import './data-table-labels.js';
|
|
4
|
+
import './mini-calendar.js';
|
|
5
|
+
import './calendar-heatmap.js';
|
|
6
|
+
import './popover.js';
|
|
7
|
+
import './chip.js';
|
|
8
|
+
import 'lucide-react';
|
|
9
|
+
import './field-sync.js';
|
|
3
10
|
import './tooltip.js';
|
|
4
11
|
import '../hooks/use-anchored-rect.js';
|
|
12
|
+
import '../lib/clipping.js';
|
|
13
|
+
import '../wizard/types.js';
|
|
14
|
+
import '../tour/tour.js';
|
|
15
|
+
import '../search/command-palette.js';
|
|
16
|
+
import '../search/global-search.js';
|
|
17
|
+
import '../search/search-index.js';
|
|
18
|
+
import './month-picker.js';
|
|
19
|
+
import './page-contents.js';
|
|
20
|
+
import './series-chart-labels.js';
|
|
21
|
+
import './pie-chart-labels.js';
|
|
22
|
+
import './sparkline.js';
|
|
23
|
+
import './stat-tile.js';
|
|
24
|
+
import './signature-pad.js';
|
|
25
|
+
import './password-strength.js';
|
|
26
|
+
import './danger-confirm.js';
|
|
27
|
+
import './swatch-picker.js';
|
|
28
|
+
import './tile-radio.js';
|
|
29
|
+
import './icon-picker.js';
|
|
30
|
+
import './dialog-frame.js';
|
|
31
|
+
import './modal.js';
|
|
32
|
+
import '../hooks/use-search-param-state.js';
|
|
33
|
+
import './measured-grid.js';
|
|
34
|
+
import '../feedback/feedback-attachment.js';
|
|
35
|
+
import '../feedback/feedback-dialog.js';
|
|
36
|
+
import '../feedback/feedback-thread.js';
|
|
37
|
+
import '../feedback/feedback-inbox.js';
|
|
38
|
+
import './account-settings-labels.js';
|
|
39
|
+
import './confirm-dialog.js';
|
|
40
|
+
import './floating-panel.js';
|
|
41
|
+
import './bulk-action-bar.js';
|
|
42
|
+
import './list.js';
|
|
43
|
+
import './breadcrumbs.js';
|
|
44
|
+
import './toast.js';
|
|
45
|
+
import './description-list.js';
|
|
46
|
+
import './line-items.js';
|
|
47
|
+
import './progress-bar.js';
|
|
48
|
+
import './signed-amount.js';
|
|
49
|
+
import './error-boundary.js';
|
|
50
|
+
import './authed-image.js';
|
|
51
|
+
import '../hooks/use-authed-src.js';
|
|
52
|
+
import './image-grid.js';
|
|
53
|
+
import './lightbox.js';
|
|
5
54
|
|
|
6
55
|
/**
|
|
7
56
|
* A time of day, as a `"HH:mm"` string (`"HH:mm:ss"` once `step` asks for seconds).
|
|
@@ -61,6 +61,19 @@ interface ToggleGroupBaseProps<T extends string> extends Omit<ComponentPropsWith
|
|
|
61
61
|
* group's own box is dropped: two nested borders read as a control in a control.
|
|
62
62
|
*/
|
|
63
63
|
label?: ReactNode;
|
|
64
|
+
/**
|
|
65
|
+
* Where `label` goes. `"field"` (default): the field chrome described under `label`.
|
|
66
|
+
* `"above"`: the kit's {@link Label} over the bare group — the shape of a {@link Field}
|
|
67
|
+
* — with `hint` beside the label and `error` under the group, for a form that sets
|
|
68
|
+
* its labels above its fields (kastlan's international-rent-calculator.tsx, whose DE
|
|
69
|
+
* cap pair sits in a `Field` column between two labelled-above inputs, where the
|
|
70
|
+
* chrome's inner label would be the only one of its kind).
|
|
71
|
+
*
|
|
72
|
+
* Inside a `Field`, pass no `label` at all and spread the render-prop instead —
|
|
73
|
+
* `{(ids, { labelId }) => <ToggleGroup {...ids} aria-labelledby={labelId} … />}` —
|
|
74
|
+
* so the Field's label names the group and its hint and error describe it.
|
|
75
|
+
*/
|
|
76
|
+
labelPlacement?: "field" | "above";
|
|
64
77
|
/** A {@link FieldHint} on the label line, as on a labelled {@link Select}. Only with
|
|
65
78
|
* `label`. */
|
|
66
79
|
hint?: ReactNode;
|
|
@@ -69,11 +82,24 @@ interface ToggleGroupBaseProps<T extends string> extends Omit<ComponentPropsWith
|
|
|
69
82
|
* {@link Select}'s `error` does. Only with `label`. */
|
|
70
83
|
error?: ReactNode;
|
|
71
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* A line under the group saying what the CHOSEN option means — lenkbank's ToggleField
|
|
87
|
+
* `hint` (features/gear/common.tsx:97): not help behind a "?" but a caption, and one
|
|
88
|
+
* that changes as the choice does. Pass a function of the value for that; it is
|
|
89
|
+
* attached with `aria-describedby`, and a function caption is also a polite live
|
|
90
|
+
* region, because a description is read when the group is entered and not again when
|
|
91
|
+
* an arrow key changes the choice underneath it.
|
|
92
|
+
*
|
|
93
|
+
* In muted 11px text under the field (or the bare group), above an `error`.
|
|
94
|
+
*/
|
|
95
|
+
type ToggleGroupCaption<V> = ReactNode | ((value: V) => ReactNode);
|
|
72
96
|
/** The group as it has always been: one option is always the answer. */
|
|
73
97
|
interface ToggleGroupRequiredProps<T extends string> extends ToggleGroupBaseProps<T> {
|
|
74
98
|
allowEmpty?: false;
|
|
75
99
|
value: T;
|
|
76
100
|
onChange: (value: T) => void;
|
|
101
|
+
/** See {@link ToggleGroupCaption}. */
|
|
102
|
+
caption?: ToggleGroupCaption<T>;
|
|
77
103
|
}
|
|
78
104
|
/**
|
|
79
105
|
* A group that can be emptied: clicking the active option clears it, and `onChange`
|
|
@@ -89,6 +115,8 @@ interface ToggleGroupClearableProps<T extends string> extends ToggleGroupBasePro
|
|
|
89
115
|
allowEmpty: true;
|
|
90
116
|
value: T | null;
|
|
91
117
|
onChange: (value: T | null) => void;
|
|
118
|
+
/** See {@link ToggleGroupCaption}. `null` while nothing is chosen. */
|
|
119
|
+
caption?: ToggleGroupCaption<T | null>;
|
|
92
120
|
}
|
|
93
121
|
/** `allowEmpty` picks the shape, so `onChange` is typed `(T) => void` unless the group
|
|
94
122
|
* can actually emit `null` — no existing caller has a `null` to handle. */
|
|
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { cn } from "../lib/cn.js";
|
|
5
5
|
import { horizontalStep } from "../lib/direction.js";
|
|
6
|
-
import { FIELD_INVALID, FloatingField } from "./ui.js";
|
|
6
|
+
import { FIELD_INVALID, FloatingField, Label } from "./ui.js";
|
|
7
7
|
function ToggleGroup(props) {
|
|
8
8
|
const {
|
|
9
9
|
value,
|
|
@@ -14,6 +14,7 @@ function ToggleGroup(props) {
|
|
|
14
14
|
disabled = false,
|
|
15
15
|
size = "md",
|
|
16
16
|
label,
|
|
17
|
+
labelPlacement = "field",
|
|
17
18
|
hint,
|
|
18
19
|
error,
|
|
19
20
|
"aria-label": ariaLabelAttr,
|
|
@@ -21,9 +22,16 @@ function ToggleGroup(props) {
|
|
|
21
22
|
} = props;
|
|
22
23
|
const labelId = useId();
|
|
23
24
|
const errorId = useId();
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
25
|
+
const labelled = label !== void 0 && label !== null && label !== false && label !== "";
|
|
26
|
+
const field = labelled && labelPlacement === "field";
|
|
27
|
+
const above = labelled && labelPlacement === "above";
|
|
28
|
+
const hasError = labelled && error !== void 0 && error !== null && error !== false && error !== "";
|
|
29
|
+
const { allowEmpty: _allowEmpty, onChange: _onChange, caption, ...rest } = restWithMode;
|
|
30
|
+
const outsideInvalid = rest["aria-invalid"] === true || rest["aria-invalid"] === "true";
|
|
31
|
+
const captionId = useId();
|
|
32
|
+
const captionIsLive = typeof caption === "function";
|
|
33
|
+
const captionNode = captionIsLive ? caption(value) : caption;
|
|
34
|
+
const hasCaption = captionNode !== void 0 && captionNode !== null && captionNode !== false && captionNode !== "";
|
|
27
35
|
const choose = (next) => {
|
|
28
36
|
if (props.allowEmpty) props.onChange(next === value ? null : next);
|
|
29
37
|
else props.onChange(next);
|
|
@@ -52,9 +60,9 @@ function ToggleGroup(props) {
|
|
|
52
60
|
...rest,
|
|
53
61
|
role: clearable ? "group" : "radiogroup",
|
|
54
62
|
"aria-label": ariaLabelAttr ?? ariaLabel,
|
|
55
|
-
"aria-labelledby":
|
|
63
|
+
"aria-labelledby": labelled && ariaLabelAttr === void 0 && ariaLabel === void 0 ? labelId : rest["aria-labelledby"],
|
|
56
64
|
"aria-invalid": hasError || rest["aria-invalid"] || void 0,
|
|
57
|
-
"aria-describedby":
|
|
65
|
+
"aria-describedby": [rest["aria-describedby"], hasCaption && captionId, hasError && errorId].filter(Boolean).join(" ") || void 0,
|
|
58
66
|
"aria-disabled": disabled || void 0,
|
|
59
67
|
className: cn(
|
|
60
68
|
// `gap-0.5` — the same 2px as the container's own padding, so EVERY segment
|
|
@@ -74,10 +82,12 @@ function ToggleGroup(props) {
|
|
|
74
82
|
// package does; `cursor-not-allowed` is on the buttons, which is what a
|
|
75
83
|
// pointer is actually over.
|
|
76
84
|
disabled && "opacity-60",
|
|
85
|
+
// The bare group (no chrome to paint) wears the invalid border itself.
|
|
86
|
+
!field && (outsideInvalid || above && hasError) && FIELD_INVALID,
|
|
77
87
|
// Inside the field's chrome the group is only a row of segments: no border, no
|
|
78
88
|
// surface, no padding of its own, and the field (not the group) is what dims.
|
|
79
89
|
field && "border-0 bg-transparent p-0 shadow-none opacity-100",
|
|
80
|
-
!
|
|
90
|
+
!labelled && className
|
|
81
91
|
),
|
|
82
92
|
children: options.map((opt, index) => {
|
|
83
93
|
const active = opt.value === value;
|
|
@@ -135,7 +145,45 @@ function ToggleGroup(props) {
|
|
|
135
145
|
})
|
|
136
146
|
}
|
|
137
147
|
);
|
|
138
|
-
|
|
148
|
+
const captionEl = hasCaption || captionIsLive ? /* @__PURE__ */ jsx(
|
|
149
|
+
"p",
|
|
150
|
+
{
|
|
151
|
+
id: captionId,
|
|
152
|
+
"aria-live": captionIsLive ? "polite" : void 0,
|
|
153
|
+
className: cn("text-[11px] leading-snug text-[var(--text-muted)]", hasCaption && "mt-1"),
|
|
154
|
+
children: hasCaption ? captionNode : null
|
|
155
|
+
}
|
|
156
|
+
) : null;
|
|
157
|
+
const errorEl = hasError ? /* @__PURE__ */ jsx("p", { id: errorId, className: "mt-1 text-[11px] leading-tight text-[var(--danger)]", children: error }) : null;
|
|
158
|
+
if (above) {
|
|
159
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative grid min-w-0 gap-1.5", className), children: [
|
|
160
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
161
|
+
/* @__PURE__ */ jsx(
|
|
162
|
+
Label,
|
|
163
|
+
{
|
|
164
|
+
id: labelId,
|
|
165
|
+
disabled,
|
|
166
|
+
"data-error": hasError || void 0,
|
|
167
|
+
className: "data-[error=true]:text-[var(--danger)]",
|
|
168
|
+
children: label
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
hint
|
|
172
|
+
] }),
|
|
173
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
174
|
+
group,
|
|
175
|
+
captionEl,
|
|
176
|
+
errorEl
|
|
177
|
+
] })
|
|
178
|
+
] });
|
|
179
|
+
}
|
|
180
|
+
if (!field) {
|
|
181
|
+
if (!captionEl) return group;
|
|
182
|
+
return /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
183
|
+
group,
|
|
184
|
+
captionEl
|
|
185
|
+
] });
|
|
186
|
+
}
|
|
139
187
|
return (
|
|
140
188
|
// `h-full` + `flex-1`: the chrome fills a grid or stretched flex row, which is what
|
|
141
189
|
// levels it with a select beside it whatever the browser makes of the select.
|
|
@@ -160,7 +208,8 @@ function ToggleGroup(props) {
|
|
|
160
208
|
)
|
|
161
209
|
}
|
|
162
210
|
),
|
|
163
|
-
|
|
211
|
+
captionEl,
|
|
212
|
+
errorEl
|
|
164
213
|
] })
|
|
165
214
|
);
|
|
166
215
|
}
|