@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
|
@@ -3,6 +3,19 @@ import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
import { DEFAULT_COMMON_LABELS, useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
5
5
|
|
|
6
|
+
/** The words a progress bar adds of its own. */
|
|
7
|
+
export interface ProgressBarLabels {
|
|
8
|
+
/** Shown after the value when `max={null}`: a quota with no ceiling. */
|
|
9
|
+
unlimited: string;
|
|
10
|
+
/** The `overage` line: how far past `max` the value is. `amount` is formatted. */
|
|
11
|
+
overLimit: (amount: string) => string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_PROGRESS_BAR_LABELS: ProgressBarLabels = {
|
|
15
|
+
unlimited: "Unlimited",
|
|
16
|
+
overLimit: (amount) => `${amount} over the limit`,
|
|
17
|
+
};
|
|
18
|
+
|
|
6
19
|
/**
|
|
7
20
|
* `income` / `expense` are the money pair (`--money-income` / `--money-expense`), the
|
|
8
21
|
* names `Chip`, `StatTile` and `Sparkline` already use for them: a share bar of what
|
|
@@ -41,12 +54,69 @@ const FILL: Record<ProgressBarTone, string> = {
|
|
|
41
54
|
|
|
42
55
|
const TRACK_HEIGHT: Record<ProgressBarSize, string> = { sm: "h-1", slim: "h-1.5", md: "h-2", lg: "h-3" };
|
|
43
56
|
|
|
57
|
+
// A segment with no tone of its own takes the next categorical chart colour, so a
|
|
58
|
+
// stacked bar of unnamed parts is still a set of tellable parts rather than one fill.
|
|
59
|
+
const SERIES_FILL = [
|
|
60
|
+
"bg-[var(--chart-1)]",
|
|
61
|
+
"bg-[var(--chart-2)]",
|
|
62
|
+
"bg-[var(--chart-3)]",
|
|
63
|
+
"bg-[var(--chart-4)]",
|
|
64
|
+
"bg-[var(--chart-5)]",
|
|
65
|
+
"bg-[var(--chart-6)]",
|
|
66
|
+
"bg-[var(--chart-7)]",
|
|
67
|
+
"bg-[var(--chart-8)]",
|
|
68
|
+
"bg-[var(--chart-9)]",
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
/** Each segment's fill classes: its own classes, else its tone, else (with no `color`)
|
|
72
|
+
* the next chart colour. "Next" counts the UNCOLOURED segments only — a segment with
|
|
73
|
+
* its own `className`/`tone`/`color` does not use up `--chart-1`, so an "Other" in grey
|
|
74
|
+
* ahead of two plain parts leaves them `--chart-1` and `--chart-2`. */
|
|
75
|
+
function segmentFills(segments: ProgressBarSegment[]): Array<string | undefined> {
|
|
76
|
+
let next = 0;
|
|
77
|
+
return segments.map((seg) => {
|
|
78
|
+
if (seg.className) return seg.className;
|
|
79
|
+
if (seg.tone) return FILL[seg.tone];
|
|
80
|
+
return seg.color ? undefined : SERIES_FILL[next++ % SERIES_FILL.length];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One part of a stacked bar. See {@link ProgressBarProps.segments}. */
|
|
85
|
+
export interface ProgressBarSegment {
|
|
86
|
+
/** Its size, on the bar's own `min`–`max` scale. */
|
|
87
|
+
value: number;
|
|
88
|
+
/** A kit tone. Left out (and no `color`/`className`), the next `--chart-N` colour. */
|
|
89
|
+
tone?: ProgressBarTone;
|
|
90
|
+
/** Any CSS colour (`var(--token)`), for a palette the tones do not cover. Wins over `tone`. */
|
|
91
|
+
color?: string;
|
|
92
|
+
/** Classes for the segment's fill — keksdose's `STEP_CLASS` ramp. Wins over `tone`. */
|
|
93
|
+
className?: string;
|
|
94
|
+
/** The part's name: said in the bar's `aria-valuetext` and shown in the legend. */
|
|
95
|
+
label?: ReactNode;
|
|
96
|
+
/** A stable React key, when `label` is not a string. */
|
|
97
|
+
key?: string;
|
|
98
|
+
/**
|
|
99
|
+
* List it in the `legend` but leave it out of the bar — its value, its share of the
|
|
100
|
+
* `aria-valuetext`, and the sum. keksdose's cut card (reports/cut-card:132) names
|
|
101
|
+
* EVERY bucket under its bar, the empty ones included, while the bar draws only the
|
|
102
|
+
* ones with a share: pass the empty buckets as `legendOnly` entries.
|
|
103
|
+
*/
|
|
104
|
+
legendOnly?: boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
44
107
|
export interface ProgressBarProps extends Omit<ComponentPropsWithoutRef<"div">, "children" | "role"> {
|
|
45
108
|
/** Where it stands. Leave it undefined for an INDETERMINATE bar — work is under way
|
|
46
109
|
* and nobody knows how much is left (an import that reports no count). */
|
|
47
110
|
value?: number;
|
|
48
111
|
min?: number;
|
|
49
|
-
|
|
112
|
+
/**
|
|
113
|
+
* The top of the scale (default 100). `null` is a quota with NO ceiling — kastlan's
|
|
114
|
+
* billing usage on an unlimited plan (usage-progress-bar:25): nothing to fill, so no
|
|
115
|
+
* bar is drawn; the label row shows the value and "Unlimited" (`progressBar.unlimited`)
|
|
116
|
+
* as plain text. `formatValue` is then called with `max` = `Infinity`; without it,
|
|
117
|
+
* the value is a plain number in the kit's locale.
|
|
118
|
+
*/
|
|
119
|
+
max?: number | null;
|
|
50
120
|
/**
|
|
51
121
|
* `progress` (default): a task moving toward completion — `role="progressbar"`.
|
|
52
122
|
* `meter`: a share of a known whole that is not going anywhere (budget used, disk
|
|
@@ -69,6 +139,39 @@ export interface ProgressBarProps extends Omit<ComponentPropsWithoutRef<"div">,
|
|
|
69
139
|
tone?: ProgressBarTone;
|
|
70
140
|
size?: ProgressBarSize;
|
|
71
141
|
locale?: string;
|
|
142
|
+
/**
|
|
143
|
+
* Draw a METER OF PARTS: one bar, several fills side by side in order, a 2px gap
|
|
144
|
+
* between them — keksdose's discretionary-spending bar (cut-card:133), a whole
|
|
145
|
+
* split into the steps of a scale. Forces `variant="meter"`: parts of a whole are
|
|
146
|
+
* never "busy". `aria-valuenow` is the sum; `aria-valuetext` names every part
|
|
147
|
+
* ("Rent: 40%, Food: 25%"), each part's value through `formatValue` and joined in the
|
|
148
|
+
* kit's locale — the words keksdose's hand-drawn bar had to hide from a reader
|
|
149
|
+
* altogether, leaving the legend as the only way in. The sum is clamped to `max`,
|
|
150
|
+
* and parts past it are cut off at the track's end.
|
|
151
|
+
*/
|
|
152
|
+
segments?: ProgressBarSegment[];
|
|
153
|
+
/** With `segments`: a list under the bar naming each part with its swatch and value
|
|
154
|
+
* — the table view of the bar, in text tokens rather than the segment colour. */
|
|
155
|
+
legend?: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* What a legend row shows at its end, in place of the part's formatted value — which
|
|
158
|
+
* it is handed, with the segment and its index. keksdose's cut card shows the share
|
|
159
|
+
* AND a private outflow amount per bucket (cut-card:150), which one figure could not:
|
|
160
|
+
* `legendValue={(seg, share) => <>{share} <Amount … /></>}`.
|
|
161
|
+
*/
|
|
162
|
+
legendValue?: (segment: ProgressBarSegment, formatted: string, index: number) => ReactNode;
|
|
163
|
+
/** A muted line under the bar — the plan's reset date, "3 of 5 seats in use". It
|
|
164
|
+
* describes the bar (`aria-describedby`). */
|
|
165
|
+
hint?: ReactNode;
|
|
166
|
+
/**
|
|
167
|
+
* When `value` is past `max`, say by how much, in a danger-coloured line under the bar
|
|
168
|
+
* (kastlan usage-progress-bar:46): `true` prints `progressBar.overLimit` with the
|
|
169
|
+
* excess as a plain number; a function renders the line itself from the excess (a
|
|
170
|
+
* currency, "2 seats"). The bar stays full at `max` either way — colour it with
|
|
171
|
+
* `tone="danger"` if it should read as over. Ignored for `segments` and `max={null}`.
|
|
172
|
+
*/
|
|
173
|
+
overage?: boolean | ((over: number) => ReactNode);
|
|
174
|
+
labels?: Partial<ProgressBarLabels>;
|
|
72
175
|
}
|
|
73
176
|
|
|
74
177
|
/**
|
|
@@ -91,7 +194,7 @@ export interface ProgressBarProps extends Omit<ComponentPropsWithoutRef<"div">,
|
|
|
91
194
|
export function ProgressBar({
|
|
92
195
|
value,
|
|
93
196
|
min = 0,
|
|
94
|
-
max = 100,
|
|
197
|
+
max: maxProp = 100,
|
|
95
198
|
variant = "progress",
|
|
96
199
|
label,
|
|
97
200
|
showValue = false,
|
|
@@ -99,28 +202,105 @@ export function ProgressBar({
|
|
|
99
202
|
tone = "brand",
|
|
100
203
|
size = "md",
|
|
101
204
|
locale,
|
|
205
|
+
segments,
|
|
206
|
+
legend = false,
|
|
207
|
+
legendValue,
|
|
208
|
+
hint,
|
|
209
|
+
overage,
|
|
210
|
+
labels,
|
|
102
211
|
className,
|
|
103
212
|
"aria-label": ariaLabel,
|
|
104
213
|
"aria-labelledby": ariaLabelledBy,
|
|
214
|
+
"aria-describedby": ariaDescribedBy,
|
|
105
215
|
...rest
|
|
106
216
|
}: ProgressBarProps) {
|
|
107
217
|
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
218
|
+
const text = useKitLabels("progressBar", DEFAULT_PROGRESS_BAR_LABELS, labels);
|
|
108
219
|
const kitLocale = useKitLocale(locale);
|
|
109
220
|
const labelId = useId();
|
|
221
|
+
const hintId = useId();
|
|
222
|
+
const hintNode =
|
|
223
|
+
hint != null ? (
|
|
224
|
+
<p id={hintId} data-part="hint" className="mt-1 text-xs text-[var(--text-muted)]">
|
|
225
|
+
{hint}
|
|
226
|
+
</p>
|
|
227
|
+
) : null;
|
|
110
228
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
229
|
+
if (maxProp === null) {
|
|
230
|
+
// No ceiling: no bar, no role — a meter with no maximum is not one. The value and
|
|
231
|
+
// "Unlimited" are the content, so they are plain visible text.
|
|
232
|
+
const shown =
|
|
233
|
+
value === undefined || !Number.isFinite(value)
|
|
234
|
+
? undefined
|
|
235
|
+
: formatValue
|
|
236
|
+
? formatValue(value, Infinity, min)
|
|
237
|
+
: new Intl.NumberFormat(kitLocale).format(value);
|
|
238
|
+
return (
|
|
239
|
+
<div {...rest} className={cn("min-w-0", className)} data-state="unlimited">
|
|
240
|
+
<div className="flex items-baseline gap-2 text-sm">
|
|
241
|
+
{label != null && (
|
|
242
|
+
<span id={labelId} className="min-w-0 text-[var(--text-secondary)]">
|
|
243
|
+
{label}
|
|
244
|
+
</span>
|
|
245
|
+
)}
|
|
246
|
+
<span data-part="unlimited" className="ms-auto shrink-0 tabular-nums text-[var(--text-muted)]">
|
|
247
|
+
{shown !== undefined && `${shown} · `}
|
|
248
|
+
{text.unlimited}
|
|
249
|
+
</span>
|
|
250
|
+
</div>
|
|
251
|
+
{hintNode}
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
const max = maxProp;
|
|
256
|
+
|
|
257
|
+
const stacked = segments !== undefined;
|
|
258
|
+
const meter = stacked || variant === "meter";
|
|
114
259
|
const span = max - min;
|
|
260
|
+
const partValue = (v: number) => (Number.isFinite(v) ? Math.max(0, v - min) : 0);
|
|
261
|
+
// Colours are dealt over EVERY segment, legend-only ones included, so an entry keeps
|
|
262
|
+
// its swatch whether or not it is in the bar this time.
|
|
263
|
+
const fills = stacked ? segmentFills(segments) : [];
|
|
264
|
+
const barParts = stacked ? segments.filter((seg) => !seg.legendOnly) : [];
|
|
265
|
+
const partTotal = stacked ? min + barParts.reduce((sum, seg) => sum + partValue(seg.value), 0) : undefined;
|
|
266
|
+
const format = (v: number) =>
|
|
267
|
+
formatValue
|
|
268
|
+
? formatValue(v, max, min)
|
|
269
|
+
: new Intl.NumberFormat(kitLocale, { style: "percent", maximumFractionDigits: 0 }).format(
|
|
270
|
+
span <= 0 ? 0 : (Math.min(max, Math.max(min, v)) - min) / span,
|
|
271
|
+
);
|
|
272
|
+
const partTexts = stacked
|
|
273
|
+
? barParts.map((seg) => {
|
|
274
|
+
const text = format(min + partValue(seg.value));
|
|
275
|
+
return typeof seg.label === "string" || typeof seg.label === "number"
|
|
276
|
+
? common.fieldValue(String(seg.label), text)
|
|
277
|
+
: text;
|
|
278
|
+
})
|
|
279
|
+
: [];
|
|
280
|
+
const current =
|
|
281
|
+
partTotal ?? (value === undefined || !Number.isFinite(value) ? (meter ? min : undefined) : value);
|
|
282
|
+
const indeterminate = current === undefined;
|
|
115
283
|
const clamped = indeterminate ? undefined : Math.min(max, Math.max(min, current));
|
|
116
284
|
const fraction = clamped === undefined || span <= 0 ? 0 : (clamped - min) / span;
|
|
117
285
|
|
|
118
|
-
const valueText =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
:
|
|
122
|
-
|
|
123
|
-
|
|
286
|
+
const valueText = clamped === undefined ? undefined : format(clamped);
|
|
287
|
+
const ariaValueText =
|
|
288
|
+
stacked && partTexts.length > 0
|
|
289
|
+
? new Intl.ListFormat(kitLocale, { style: "short", type: "unit" }).format(partTexts)
|
|
290
|
+
: valueText;
|
|
291
|
+
|
|
292
|
+
const over =
|
|
293
|
+
overage && !stacked && value !== undefined && Number.isFinite(value) && value > max ? value - max : 0;
|
|
294
|
+
const overageNode =
|
|
295
|
+
over > 0 ? (
|
|
296
|
+
<p id={`${hintId}-over`} data-part="overage" className="mt-1 text-xs text-[var(--danger)]">
|
|
297
|
+
{typeof overage === "function"
|
|
298
|
+
? overage(over)
|
|
299
|
+
: text.overLimit(new Intl.NumberFormat(kitLocale).format(over))}
|
|
300
|
+
</p>
|
|
301
|
+
) : null;
|
|
302
|
+
const describedBy =
|
|
303
|
+
[ariaDescribedBy, hintNode && hintId, overageNode && `${hintId}-over`].filter(Boolean).join(" ") || undefined;
|
|
124
304
|
|
|
125
305
|
const labelledBy = ariaLabelledBy ?? (label != null ? labelId : undefined);
|
|
126
306
|
const name = labelledBy ? undefined : (ariaLabel ?? (indeterminate ? common.loading : undefined));
|
|
@@ -148,16 +328,38 @@ export function ProgressBar({
|
|
|
148
328
|
role={meter ? "meter" : "progressbar"}
|
|
149
329
|
aria-label={name}
|
|
150
330
|
aria-labelledby={labelledBy}
|
|
331
|
+
aria-describedby={describedBy}
|
|
151
332
|
aria-valuemin={indeterminate ? undefined : min}
|
|
152
333
|
aria-valuemax={indeterminate ? undefined : max}
|
|
153
334
|
aria-valuenow={clamped}
|
|
154
|
-
aria-valuetext={
|
|
335
|
+
aria-valuetext={ariaValueText}
|
|
155
336
|
aria-busy={!meter && indeterminate ? true : undefined}
|
|
156
337
|
// `relative` + `overflow-hidden`: the indeterminate segment is positioned in
|
|
157
338
|
// here and must not paint past the rounded ends.
|
|
158
339
|
className={cn("relative w-full overflow-hidden rounded-full bg-[var(--bg-active)]", TRACK_HEIGHT[size])}
|
|
159
340
|
>
|
|
160
|
-
{
|
|
341
|
+
{stacked ? (
|
|
342
|
+
// The parts in a flex row with a 2px gap, so two neighbouring steps of one
|
|
343
|
+
// hue are parted by the track rather than by their difference in lightness.
|
|
344
|
+
<div className="flex h-full w-full gap-0.5">
|
|
345
|
+
{segments.map((seg, i) =>
|
|
346
|
+
seg.legendOnly ? null : (
|
|
347
|
+
<div
|
|
348
|
+
key={seg.key ?? i}
|
|
349
|
+
data-part="segment"
|
|
350
|
+
className={cn(
|
|
351
|
+
"h-full shrink-0 rounded-full transition-[width] duration-300 motion-reduce:transition-none",
|
|
352
|
+
fills[i],
|
|
353
|
+
)}
|
|
354
|
+
style={{
|
|
355
|
+
width: `${span <= 0 ? 0 : (partValue(seg.value) / span) * 100}%`,
|
|
356
|
+
backgroundColor: seg.className ? undefined : seg.color,
|
|
357
|
+
}}
|
|
358
|
+
/>
|
|
359
|
+
),
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
362
|
+
) : indeterminate ? (
|
|
161
363
|
<div
|
|
162
364
|
data-part="fill"
|
|
163
365
|
className={cn(
|
|
@@ -182,6 +384,32 @@ export function ProgressBar({
|
|
|
182
384
|
/>
|
|
183
385
|
)}
|
|
184
386
|
</div>
|
|
387
|
+
{stacked && legend && segments.length > 0 && (
|
|
388
|
+
<ul className="mt-2 space-y-1 text-sm" data-part="legend">
|
|
389
|
+
{segments.map((seg, i) => (
|
|
390
|
+
<li key={seg.key ?? i} className="flex items-center gap-2">
|
|
391
|
+
<span
|
|
392
|
+
aria-hidden
|
|
393
|
+
className={cn(
|
|
394
|
+
"size-2.5 shrink-0 rounded-sm",
|
|
395
|
+
fills[i],
|
|
396
|
+
)}
|
|
397
|
+
style={{ backgroundColor: seg.className ? undefined : seg.color }}
|
|
398
|
+
/>
|
|
399
|
+
{seg.label != null && (
|
|
400
|
+
<span className="min-w-0 flex-1 truncate text-[var(--text-secondary)]">{seg.label}</span>
|
|
401
|
+
)}
|
|
402
|
+
<span className="ms-auto shrink-0 text-xs tabular-nums text-[var(--text-muted)]">
|
|
403
|
+
{legendValue
|
|
404
|
+
? legendValue(seg, format(min + partValue(seg.value)), i)
|
|
405
|
+
: format(min + partValue(seg.value))}
|
|
406
|
+
</span>
|
|
407
|
+
</li>
|
|
408
|
+
))}
|
|
409
|
+
</ul>
|
|
410
|
+
)}
|
|
411
|
+
{overageNode}
|
|
412
|
+
{hintNode}
|
|
185
413
|
</div>
|
|
186
414
|
);
|
|
187
415
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
import { encodeQr } from "../lib/qr-encode";
|
|
5
|
+
import type { QrErrorCorrection } from "../lib/qr-encode";
|
|
6
|
+
|
|
7
|
+
export interface QrCodeProps extends Omit<ComponentPropsWithoutRef<"svg">, "children" | "viewBox"> {
|
|
8
|
+
/** The text to encode (UTF-8) — an `otpauth://` URI, a URL, a pairing code. */
|
|
9
|
+
value: string;
|
|
10
|
+
/** The accessible name. Without it the code is decorative (`aria-hidden`): say what
|
|
11
|
+
* it is for, not what it encodes — nobody wants a URI read out. */
|
|
12
|
+
label?: string;
|
|
13
|
+
/** Default `"M"`, raised automatically where the size allows it. */
|
|
14
|
+
errorCorrection?: QrErrorCorrection;
|
|
15
|
+
/** The quiet zone, in modules. The spec asks for 4; less reads worse. Default 4. */
|
|
16
|
+
margin?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A QR code as one inline SVG — no canvas, no data URL, no dependency (see
|
|
21
|
+
* `lib/qr-encode.ts` for why the encoder lives in the kit).
|
|
22
|
+
*
|
|
23
|
+
* Sized by CSS (`className="size-48"`); the SVG scales without blurring because every
|
|
24
|
+
* module is a whole unit of the viewBox and `shape-rendering` is `crispEdges`.
|
|
25
|
+
*
|
|
26
|
+
* **Black on white, always — the one literal colour pair in the kit.** A camera looks
|
|
27
|
+
* for the contrast between the modules and their quiet zone; on the dark theme a
|
|
28
|
+
* surface-coloured background makes a code slow to acquire, and an inverted one is not
|
|
29
|
+
* read at all by several authenticator apps. The quiet zone is part of the SVG, so the
|
|
30
|
+
* code carries its own white border wherever it is placed.
|
|
31
|
+
*
|
|
32
|
+
* A value too long for any QR version (≈2.9 kB) renders nothing rather than throwing
|
|
33
|
+
* in render.
|
|
34
|
+
*/
|
|
35
|
+
export function QrCode({
|
|
36
|
+
value,
|
|
37
|
+
label,
|
|
38
|
+
errorCorrection = "M",
|
|
39
|
+
margin = 4,
|
|
40
|
+
className,
|
|
41
|
+
...rest
|
|
42
|
+
}: QrCodeProps) {
|
|
43
|
+
const path = useMemo(() => {
|
|
44
|
+
try {
|
|
45
|
+
const { modules, size } = encodeQr(value, { errorCorrection });
|
|
46
|
+
// One path, one sub-path per horizontal run of dark modules: a few hundred
|
|
47
|
+
// commands rather than one <rect> per module.
|
|
48
|
+
let d = "";
|
|
49
|
+
for (let y = 0; y < size; y++) {
|
|
50
|
+
for (let x = 0; x < size; ) {
|
|
51
|
+
if (!modules[y][x]) {
|
|
52
|
+
x++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
let run = 1;
|
|
56
|
+
while (x + run < size && modules[y][x + run]) run++;
|
|
57
|
+
d += `M${x + margin} ${y + margin}h${run}v1h-${run}z`;
|
|
58
|
+
x += run;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { d, extent: size + margin * 2 };
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}, [value, errorCorrection, margin]);
|
|
66
|
+
|
|
67
|
+
if (!path) return null;
|
|
68
|
+
return (
|
|
69
|
+
<svg
|
|
70
|
+
{...rest}
|
|
71
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
72
|
+
viewBox={`0 0 ${path.extent} ${path.extent}`}
|
|
73
|
+
shapeRendering="crispEdges"
|
|
74
|
+
role={label ? "img" : undefined}
|
|
75
|
+
aria-label={label}
|
|
76
|
+
aria-hidden={label ? undefined : true}
|
|
77
|
+
className={cn("block", className)}
|
|
78
|
+
>
|
|
79
|
+
<rect width={path.extent} height={path.extent} fill="#ffffff" />
|
|
80
|
+
<path d={path.d} fill="#000000" />
|
|
81
|
+
</svg>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { InputHTMLAttributes } from "react";
|
|
|
3
3
|
import { Search, X } from "lucide-react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
5
|
import { FIELD_BASE } from "./ui";
|
|
6
|
+
import { DEFAULT_COMMON_LABELS, useKitLabels } from "../i18n/kit-labels";
|
|
6
7
|
|
|
7
8
|
interface SearchFieldOwnProps
|
|
8
9
|
extends Omit<
|
|
@@ -23,10 +24,22 @@ interface SearchFieldOwnProps
|
|
|
23
24
|
* absent, so a consumer can migrate one call site at a time.
|
|
24
25
|
*/
|
|
25
26
|
label?: string;
|
|
26
|
-
/** Names the clear button.
|
|
27
|
-
*
|
|
28
|
-
* complaint that produced the button on three of these four screens. */
|
|
27
|
+
/** Names the clear button. Default: the catalogue's `common.clear`, in the
|
|
28
|
+
* provider's language. */
|
|
29
29
|
clearLabel?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Draw the "×" that empties the field once there is something in it. Default
|
|
32
|
+
* `true`.
|
|
33
|
+
*
|
|
34
|
+
* It used to appear only when a `clearLabel` was passed, and every kastlan call
|
|
35
|
+
* site passed one — lease-list-page.tsx:91, trial-balance-page.tsx:~196,
|
|
36
|
+
* account-ledger-page.tsx:~131 and data-table/resource-list-page.tsx:256 each
|
|
37
|
+
* spelled `clearLabel={t("table.clear")}` — so the opt-in was boilerplate: a filter
|
|
38
|
+
* you can type into and not untype is the complaint that produced the button in
|
|
39
|
+
* the first place, and the catalogue already names it. `false` is for a field
|
|
40
|
+
* whose clearing lives elsewhere (a "Reset filters" beside it).
|
|
41
|
+
*/
|
|
42
|
+
clearable?: boolean;
|
|
30
43
|
/**
|
|
31
44
|
* `"field"` (default): the bordered filter box that sits on a page.
|
|
32
45
|
*
|
|
@@ -96,7 +109,8 @@ export const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(functi
|
|
|
96
109
|
value,
|
|
97
110
|
onChange,
|
|
98
111
|
label,
|
|
99
|
-
clearLabel,
|
|
112
|
+
clearLabel: clearLabelProp,
|
|
113
|
+
clearable = true,
|
|
100
114
|
className,
|
|
101
115
|
inputClassName,
|
|
102
116
|
placeholder,
|
|
@@ -107,6 +121,8 @@ export const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(functi
|
|
|
107
121
|
ref,
|
|
108
122
|
) {
|
|
109
123
|
const innerRef = useRef<HTMLInputElement>(null);
|
|
124
|
+
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
125
|
+
const clearLabel = clearable ? (clearLabelProp ?? common.clear) : undefined;
|
|
110
126
|
// Whichever spelling the caller used. It is the accessible name AND the placeholder
|
|
111
127
|
// fallback, so a box named only by `aria-label` still says what it is before anyone
|
|
112
128
|
// has typed in it — the same deal `label` has always had.
|
|
@@ -129,6 +129,18 @@ export interface SeriesChartSeries {
|
|
|
129
129
|
* `{ r }` sets its radius. Default: recharts' dot. Bars have none, and ignore it.
|
|
130
130
|
*/
|
|
131
131
|
activeDot?: boolean | { r?: number };
|
|
132
|
+
/**
|
|
133
|
+
* The shortest a bar of this series is drawn, in px — recharts' `minPointSize`. A
|
|
134
|
+
* zero or a value too small to see keeps a visible stub, so an empty slot still reads
|
|
135
|
+
* as a slot with a value rather than a gap: keksdose's weekday pattern, where a
|
|
136
|
+
* Sunday with no spending drew a 2% stub by hand. Only the DRAWING changes: the axis
|
|
137
|
+
* is fitted to the values, and the tooltip, the keyboard stops and every accessible
|
|
138
|
+
* name still say 0. A hole (no value in the row) stays a hole. Overrides the chart's
|
|
139
|
+
* {@link SeriesChartProps.minBarLength}; `0` turns it off for this series. Meant for
|
|
140
|
+
* unstacked bars — in a `stack` the stub is drawn over the layer above it. Lines and
|
|
141
|
+
* areas ignore it.
|
|
142
|
+
*/
|
|
143
|
+
minBarLength?: number;
|
|
132
144
|
}
|
|
133
145
|
|
|
134
146
|
/** What a series draws. See {@link SeriesChartSeries.type}. */
|
|
@@ -507,6 +519,10 @@ export interface SeriesChartProps {
|
|
|
507
519
|
locale?: string;
|
|
508
520
|
/** Onto the chart's root (`ChartContainer`) — e.g. an app's own grid ink. */
|
|
509
521
|
className?: string;
|
|
522
|
+
/** The shortest bar drawn, in px, for every bar series that does not set its own
|
|
523
|
+
* {@link SeriesChartSeries.minBarLength}. Drawing only: domain, tooltip and names
|
|
524
|
+
* keep the real value. Default: none — a zero draws nothing. */
|
|
525
|
+
minBarLength?: number;
|
|
510
526
|
}
|
|
511
527
|
|
|
512
528
|
/** How much of an axis band the rotated title takes, in px. Reserved rather than
|
|
@@ -1011,6 +1027,7 @@ function SeriesPlot({
|
|
|
1011
1027
|
labels: labelsProp,
|
|
1012
1028
|
locale: localeProp,
|
|
1013
1029
|
className,
|
|
1030
|
+
minBarLength,
|
|
1014
1031
|
}: PlotProps) {
|
|
1015
1032
|
const labels = useKitLabels("seriesChart", DEFAULT_SERIES_CHART_LABELS, labelsProp);
|
|
1016
1033
|
const locale = useKitLocale(localeProp);
|
|
@@ -1418,6 +1435,7 @@ function SeriesPlot({
|
|
|
1418
1435
|
const curve = entry.step ? "stepAfter" : (entry.curve ?? "monotone");
|
|
1419
1436
|
const width = entry.strokeWidth ?? (entry.step ? STEP_WIDTH : LINE_WIDTH);
|
|
1420
1437
|
if (type === "bar") {
|
|
1438
|
+
const stub = entry.minBarLength ?? minBarLength;
|
|
1421
1439
|
return (
|
|
1422
1440
|
<Bar
|
|
1423
1441
|
key={entry.key}
|
|
@@ -1427,6 +1445,9 @@ function SeriesPlot({
|
|
|
1427
1445
|
fill={color}
|
|
1428
1446
|
fillOpacity={entry.fillOpacity}
|
|
1429
1447
|
radius={entry.stack === undefined ? BAR_RADIUS : 0}
|
|
1448
|
+
// Read off the row rather than recharts' `value`: in a stack that is the
|
|
1449
|
+
// layer's running top, which a hole has too — and a hole gets no stub.
|
|
1450
|
+
{...(stub ? { minPointSize: (_value: unknown, index: number) => (finite(plotted[index]?.[entry.key]) === undefined ? 0 : stub) } : {})}
|
|
1430
1451
|
// Only the focused stop's series swaps its shape, and only for as long
|
|
1431
1452
|
// as it has focus: every other bar keeps recharts' own.
|
|
1432
1453
|
{...(focusedStop?.key === entry.key ? { shape: focusedBarShape(focusedStop.index) } : {})}
|