@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,286 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import { ArrowDown, ArrowUp, Minus } from "lucide-react";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
import { formatMoney, formatNumber, formatPercent } from "../lib/format";
|
|
5
|
+
import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
6
|
+
import { DEFAULT_STAT_TILE_LABELS } from "./stat-tile";
|
|
7
|
+
import type { StatTileLabels } from "./stat-tile";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The text colours a figure can carry, token-backed — `StatTile`'s tones, as a class
|
|
11
|
+
* any span can take.
|
|
12
|
+
*
|
|
13
|
+
* - `default` — the primary text colour. `muted` — the secondary, quiet one.
|
|
14
|
+
* - `income` / `expense` / `net` — the money trio (CVD-safe teal / amber / violet).
|
|
15
|
+
* - `success` / `warning` / `danger` / `info` — a verdict: a rent rise is the tenant's
|
|
16
|
+
* bad news (kastlan lease-columns), a vote "for" is green (meeting-agenda-tab).
|
|
17
|
+
* - `brand` — the app's accent.
|
|
18
|
+
*
|
|
19
|
+
* Colour is never the only carrier: pair a verdict with an arrow, a word, or a sign.
|
|
20
|
+
*/
|
|
21
|
+
export type TextTone =
|
|
22
|
+
| "default"
|
|
23
|
+
| "muted"
|
|
24
|
+
| "brand"
|
|
25
|
+
| "income"
|
|
26
|
+
| "expense"
|
|
27
|
+
| "net"
|
|
28
|
+
| "success"
|
|
29
|
+
| "warning"
|
|
30
|
+
| "danger"
|
|
31
|
+
| "info";
|
|
32
|
+
|
|
33
|
+
const TEXT_TONE: Record<TextTone, string> = {
|
|
34
|
+
default: "text-[var(--text-primary)]",
|
|
35
|
+
muted: "text-[var(--text-muted)]",
|
|
36
|
+
brand: "text-[var(--brand)]",
|
|
37
|
+
income: "text-[var(--money-income)]",
|
|
38
|
+
expense: "text-[var(--money-expense)]",
|
|
39
|
+
net: "text-[var(--money-net)]",
|
|
40
|
+
success: "text-[var(--success)]",
|
|
41
|
+
warning: "text-[var(--warning)]",
|
|
42
|
+
danger: "text-[var(--danger)]",
|
|
43
|
+
info: "text-[var(--info)]",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The class that colours text in `tone` — for the spans kastlan colours by hand with
|
|
48
|
+
* `text-[var(--danger)]` / `text-emerald-600` (settlement-table, changelog-page,
|
|
49
|
+
* meeting-agenda-tab), so the palette they draw from is the kit's tokens.
|
|
50
|
+
*/
|
|
51
|
+
export function toneTextClass(tone: TextTone): string {
|
|
52
|
+
return TEXT_TONE[tone];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ToneProps extends ComponentPropsWithoutRef<"span"> {
|
|
56
|
+
tone: TextTone;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A `<span>` in a {@link TextTone} — `<Tone tone="success">{votesFor}</Tone>`. */
|
|
60
|
+
export function Tone({ tone, className, ...rest }: ToneProps) {
|
|
61
|
+
return <span {...rest} className={cn(TEXT_TONE[tone], className)} />;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ── Signed amounts ──────────────────────────────────────────────────────── */
|
|
65
|
+
|
|
66
|
+
/** Which way is +1: `up` for a figure where more is good news (income, savings),
|
|
67
|
+
* `down` where less is (a rent the tenant pays, a cost). */
|
|
68
|
+
export type GoodDirection = "up" | "down";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* How a signed figure is coloured:
|
|
72
|
+
* - `signed` (default) — money read by its own sign: above zero `income`, below zero
|
|
73
|
+
* `expense`, zero muted. A balance, a net.
|
|
74
|
+
* - `income` / `expense` / `neutral` — one colour whatever the sign (`neutral` is the
|
|
75
|
+
* text's own colour).
|
|
76
|
+
* - `verdict` — the change judged by {@link SignedAmountProps.goodDirection}: good
|
|
77
|
+
* news `success`, bad news `danger`, zero muted. Setting `goodDirection` implies it.
|
|
78
|
+
*/
|
|
79
|
+
export type SignedAmountTone = "signed" | "income" | "expense" | "neutral" | "verdict";
|
|
80
|
+
|
|
81
|
+
/** What a signed figure says to a screen reader, whose readers disagree about a
|
|
82
|
+
* leading "+" (most skip it) and a "−" (some read "dash", some nothing). */
|
|
83
|
+
export interface SignedAmountLabels {
|
|
84
|
+
/** A figure above zero. `amount` is formatted and unsigned. */
|
|
85
|
+
positive: (amount: string) => string;
|
|
86
|
+
/** A figure below zero. `amount` is formatted and unsigned. */
|
|
87
|
+
negative: (amount: string) => string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const DEFAULT_SIGNED_AMOUNT_LABELS: SignedAmountLabels = {
|
|
91
|
+
positive: (amount) => `plus ${amount}`,
|
|
92
|
+
negative: (amount) => `minus ${amount}`,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
interface SignedFormatProps {
|
|
96
|
+
/** ISO 4217 code: format as money. */
|
|
97
|
+
currency?: string;
|
|
98
|
+
/** `"percent"` reads `value` as a RATIO (`0.12` → 12 %), as `StatTile`'s delta does. */
|
|
99
|
+
unit?: "value" | "percent";
|
|
100
|
+
/** Compact notation: 12 400 → "12K". */
|
|
101
|
+
compact?: boolean;
|
|
102
|
+
/** Fraction digits, as `formatNumber`'s `digits`. */
|
|
103
|
+
digits?: number | { min?: number; max?: number };
|
|
104
|
+
/** Formats the UNSIGNED magnitude, replacing the `Intl` default outright. The sign
|
|
105
|
+
* is the component's. */
|
|
106
|
+
format?: (magnitude: number) => string;
|
|
107
|
+
locale?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function useMagnitudeFormatter({ currency, unit, compact, digits, format, locale }: SignedFormatProps) {
|
|
111
|
+
const kitLocale = useKitLocale(locale);
|
|
112
|
+
return (magnitude: number): string => {
|
|
113
|
+
if (format) return format(magnitude);
|
|
114
|
+
if (unit === "percent") return formatPercent(magnitude, { locale: kitLocale, compact, digits });
|
|
115
|
+
if (currency) return formatMoney(magnitude, currency, { locale: kitLocale, compact, digits });
|
|
116
|
+
return formatNumber(magnitude, { locale: kitLocale, compact, digits });
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function directionOf(value: number): "up" | "down" | "flat" {
|
|
121
|
+
return value > 0 ? "up" : value < 0 ? "down" : "flat";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function signedToneClass(
|
|
125
|
+
tone: SignedAmountTone,
|
|
126
|
+
dir: "up" | "down" | "flat",
|
|
127
|
+
goodDirection: GoodDirection | undefined,
|
|
128
|
+
): string {
|
|
129
|
+
if (tone === "verdict" || (goodDirection && tone === "signed")) {
|
|
130
|
+
if (dir === "flat" || !goodDirection) return TEXT_TONE.muted;
|
|
131
|
+
return dir === goodDirection ? TEXT_TONE.success : TEXT_TONE.danger;
|
|
132
|
+
}
|
|
133
|
+
if (tone === "signed") return dir === "flat" ? TEXT_TONE.muted : dir === "up" ? TEXT_TONE.income : TEXT_TONE.expense;
|
|
134
|
+
if (tone === "neutral") return "";
|
|
135
|
+
return TEXT_TONE[tone];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const ARROW = { up: ArrowUp, down: ArrowDown, flat: Minus } as const;
|
|
139
|
+
|
|
140
|
+
export interface SignedAmountProps
|
|
141
|
+
extends SignedFormatProps,
|
|
142
|
+
Omit<ComponentPropsWithoutRef<"span">, "children"> {
|
|
143
|
+
value: number;
|
|
144
|
+
/** See {@link SignedAmountTone}. Default `signed`, or `verdict` with `goodDirection`. */
|
|
145
|
+
tone?: SignedAmountTone;
|
|
146
|
+
/** Which way is good news — colours by verdict (`success` / `danger`). */
|
|
147
|
+
goodDirection?: GoodDirection;
|
|
148
|
+
/** Print "+" before a positive figure (default `true`). A negative always shows its
|
|
149
|
+
* minus; zero shows neither. */
|
|
150
|
+
showPlus?: boolean;
|
|
151
|
+
/** An arrow before the figure (up / down / a dash at zero) — so the direction does not
|
|
152
|
+
* ride on colour alone. Decorative; the words say the same. */
|
|
153
|
+
arrow?: boolean;
|
|
154
|
+
/** Tag the figure `data-private`, for the host's demo-mode blur. */
|
|
155
|
+
sensitive?: boolean;
|
|
156
|
+
labels?: Partial<SignedAmountLabels>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A figure with a sign that matters: a balance, a rent change, a net.
|
|
161
|
+
*
|
|
162
|
+
* kastlan writes it by hand in five places — `{change > 0 ? "+" : ""}{formatCurrency(
|
|
163
|
+
* change)}` in a span coloured by a ternary (lease-columns:190, settlement-table:58,
|
|
164
|
+
* the tenancy `formatPct`) — and each picks its own colours for the same verdict.
|
|
165
|
+
*
|
|
166
|
+
* The visible figure (sign, digits, arrow) is hidden from assistive tech and spoken as
|
|
167
|
+
* words instead ("plus CHF 50.00" / "minus CHF 50.00", `signedAmount.*`): a "+" is
|
|
168
|
+
* skipped by most screen readers, and `Intl`'s "−" is read as "dash" or not at all.
|
|
169
|
+
*/
|
|
170
|
+
export function SignedAmount({
|
|
171
|
+
value,
|
|
172
|
+
currency,
|
|
173
|
+
unit,
|
|
174
|
+
compact,
|
|
175
|
+
digits,
|
|
176
|
+
format,
|
|
177
|
+
locale,
|
|
178
|
+
tone = "signed",
|
|
179
|
+
goodDirection,
|
|
180
|
+
showPlus = true,
|
|
181
|
+
arrow = false,
|
|
182
|
+
sensitive = false,
|
|
183
|
+
labels,
|
|
184
|
+
className,
|
|
185
|
+
...rest
|
|
186
|
+
}: SignedAmountProps) {
|
|
187
|
+
const text = useKitLabels("signedAmount", DEFAULT_SIGNED_AMOUNT_LABELS, labels);
|
|
188
|
+
const fmt = useMagnitudeFormatter({ currency, unit, compact, digits, format, locale });
|
|
189
|
+
const finite = Number.isFinite(value);
|
|
190
|
+
const dir = finite ? directionOf(value) : "flat";
|
|
191
|
+
const amount = finite ? fmt(Math.abs(value)) : "—";
|
|
192
|
+
// U+2212, the typographic minus: the same width as "+", so a column of signed figures
|
|
193
|
+
// lines up under `tabular-nums`.
|
|
194
|
+
const sign = dir === "up" ? (showPlus ? "+" : "") : dir === "down" ? "−" : "";
|
|
195
|
+
const spoken = dir === "up" ? text.positive(amount) : dir === "down" ? text.negative(amount) : amount;
|
|
196
|
+
const Icon = ARROW[dir];
|
|
197
|
+
return (
|
|
198
|
+
<span
|
|
199
|
+
{...rest}
|
|
200
|
+
data-private={sensitive ? "" : undefined}
|
|
201
|
+
data-direction={dir}
|
|
202
|
+
className={cn(
|
|
203
|
+
"relative inline-flex items-center gap-0.5 whitespace-nowrap tabular-nums",
|
|
204
|
+
signedToneClass(tone, dir, goodDirection),
|
|
205
|
+
className,
|
|
206
|
+
)}
|
|
207
|
+
>
|
|
208
|
+
{arrow && <Icon aria-hidden className="size-[1em] shrink-0" />}
|
|
209
|
+
<span aria-hidden>
|
|
210
|
+
{sign}
|
|
211
|
+
{amount}
|
|
212
|
+
</span>
|
|
213
|
+
<span className="sr-only">{spoken}</span>
|
|
214
|
+
</span>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface DeltaProps extends SignedFormatProps, Omit<ComponentPropsWithoutRef<"span">, "children"> {
|
|
219
|
+
/** The change. Above zero is "up". */
|
|
220
|
+
value: number;
|
|
221
|
+
/** Which way is good news. Unset, the change is stated and not judged (muted); set,
|
|
222
|
+
* it is coloured `success` / `danger` and its sentence says "(better)" / "(worse)". */
|
|
223
|
+
goodDirection?: GoodDirection;
|
|
224
|
+
/** What it is measured against, after it: "vs last month". */
|
|
225
|
+
label?: ReactNode;
|
|
226
|
+
/** Hide the arrow (shown by default, as on a StatTile). */
|
|
227
|
+
arrow?: boolean;
|
|
228
|
+
sensitive?: boolean;
|
|
229
|
+
/** The `statTile` namespace's direction words — a delta says the same as a tile's. */
|
|
230
|
+
labels?: Partial<Pick<StatTileLabels, "increase" | "decrease" | "unchanged" | "better" | "worse">>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* `StatTile`'s delta on its own: an arrow, the unsigned magnitude, and a sentence for
|
|
235
|
+
* screen readers ("Up 12% (worse)"), coloured only when `goodDirection` judges it —
|
|
236
|
+
* users up and cost up are the same arrow until someone says which is good.
|
|
237
|
+
*
|
|
238
|
+
* For a change shown outside a tile: kastlan's rent-change column
|
|
239
|
+
* (lease-columns:219, a "+1.25%" chip), the breakdown rows of the rent calculator
|
|
240
|
+
* (breakdown-row:5, a trend icon coloured by hand).
|
|
241
|
+
*/
|
|
242
|
+
export function Delta({
|
|
243
|
+
value,
|
|
244
|
+
currency,
|
|
245
|
+
unit,
|
|
246
|
+
compact,
|
|
247
|
+
digits,
|
|
248
|
+
format,
|
|
249
|
+
locale,
|
|
250
|
+
goodDirection,
|
|
251
|
+
label,
|
|
252
|
+
arrow = true,
|
|
253
|
+
sensitive = false,
|
|
254
|
+
labels,
|
|
255
|
+
className,
|
|
256
|
+
...rest
|
|
257
|
+
}: DeltaProps) {
|
|
258
|
+
const text = useKitLabels("statTile", DEFAULT_STAT_TILE_LABELS, labels);
|
|
259
|
+
const fmt = useMagnitudeFormatter({ currency, unit, compact, digits, format, locale });
|
|
260
|
+
if (!Number.isFinite(value)) return null;
|
|
261
|
+
const amount = fmt(Math.abs(value));
|
|
262
|
+
const dir = directionOf(value);
|
|
263
|
+
const Icon = ARROW[dir];
|
|
264
|
+
let spoken = dir === "up" ? text.increase(amount) : dir === "down" ? text.decrease(amount) : text.unchanged;
|
|
265
|
+
let tone = TEXT_TONE.muted;
|
|
266
|
+
if (goodDirection && dir !== "flat") {
|
|
267
|
+
const good = dir === goodDirection;
|
|
268
|
+
spoken = good ? text.better(spoken) : text.worse(spoken);
|
|
269
|
+
tone = good ? TEXT_TONE.success : TEXT_TONE.danger;
|
|
270
|
+
}
|
|
271
|
+
return (
|
|
272
|
+
<span
|
|
273
|
+
{...rest}
|
|
274
|
+
data-direction={dir}
|
|
275
|
+
className={cn("relative inline-flex flex-wrap items-center gap-x-1.5 tabular-nums", className)}
|
|
276
|
+
>
|
|
277
|
+
<span data-private={sensitive ? "" : undefined} className={cn("inline-flex items-center gap-0.5 font-medium", tone)}>
|
|
278
|
+
{arrow && <Icon className="size-[1em] shrink-0" aria-hidden />}
|
|
279
|
+
{/* Flat with an arrow is the dash alone, as on a tile; without one, the zero. */}
|
|
280
|
+
<span aria-hidden>{dir === "flat" && arrow ? null : amount}</span>
|
|
281
|
+
<span className="sr-only">{spoken}</span>
|
|
282
|
+
</span>
|
|
283
|
+
{label != null && <span className="text-[var(--text-muted)]">{label}</span>}
|
|
284
|
+
</span>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
@@ -21,6 +21,13 @@ export interface SkeletonProps extends Omit<ComponentPropsWithoutRef<"div">, "ch
|
|
|
21
21
|
shape?: SkeletonShape;
|
|
22
22
|
/** For `line`: this many lines, the last one shorter — a paragraph's outline. */
|
|
23
23
|
lines?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Say what is loading, once: wraps the placeholder in a `role="status"` region with
|
|
26
|
+
* this as its (visually hidden) text — kastlan's activity feed (activity-feed:52)
|
|
27
|
+
* built exactly that by hand around `<Skeleton lines={5} />`. The placeholder itself
|
|
28
|
+
* stays hidden. Left out, nothing is announced, as before.
|
|
29
|
+
*/
|
|
30
|
+
label?: string;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
const SHAPE: Record<SkeletonShape, string> = {
|
|
@@ -37,10 +44,21 @@ const SHAPE: Record<SkeletonShape, string> = {
|
|
|
37
44
|
*
|
|
38
45
|
* ALWAYS `aria-hidden`. A placeholder is not content, and a reader that met twelve
|
|
39
46
|
* unnamed grey boxes would have learned nothing. Say "loading" once, where it
|
|
40
|
-
* belongs: `aria-busy` on the region being filled,
|
|
41
|
-
* beside the skeletons
|
|
47
|
+
* belongs: `aria-busy` on the region being filled, a `Spinner` / sr-only line
|
|
48
|
+
* beside the skeletons, or this component's own `label`.
|
|
42
49
|
*/
|
|
43
|
-
export function Skeleton({
|
|
50
|
+
export function Skeleton({ label, ...props }: SkeletonProps) {
|
|
51
|
+
if (!label) return <SkeletonFill {...props} />;
|
|
52
|
+
return (
|
|
53
|
+
// `relative`: the containing block for the sr-only text (sr-only-containment.test).
|
|
54
|
+
<div role="status" className="relative" data-skeleton-status="">
|
|
55
|
+
<span className="sr-only">{label}</span>
|
|
56
|
+
<SkeletonFill {...props} />
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function SkeletonFill({ shape = "line", lines, className, ...rest }: Omit<SkeletonProps, "label">) {
|
|
44
62
|
if (shape === "line" && lines !== undefined && lines > 1) {
|
|
45
63
|
return (
|
|
46
64
|
<div {...rest} aria-hidden className={cn("flex flex-col gap-2", className)} data-skeleton="lines">
|
|
@@ -2,7 +2,8 @@ import { isValidElement, useId, useLayoutEffect, useState } from "react";
|
|
|
2
2
|
import type { ComponentPropsWithoutRef, CSSProperties, ReactElement, ReactNode } from "react";
|
|
3
3
|
import { ArrowDown, ArrowUp, Minus } from "lucide-react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
|
-
import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
5
|
+
import { useKitLabels, useKitLink, useKitLocale } from "../i18n/kit-labels";
|
|
6
|
+
import { pickLinkRenderer, RenderedKitLink } from "./text-link";
|
|
6
7
|
import { Card, FieldHint } from "./ui";
|
|
7
8
|
import { Sparkline } from "./sparkline";
|
|
8
9
|
import { Tooltip } from "./tooltip";
|
|
@@ -219,7 +220,8 @@ export interface StatTileProps
|
|
|
219
220
|
/** Makes the tile a link. See {@link renderLink} for a router. */
|
|
220
221
|
href?: string;
|
|
221
222
|
/** Renders the link for `href` — pass your router's `<Link>` here, since a plain
|
|
222
|
-
* `<a>` reloads a single-page app. Default: `<
|
|
223
|
+
* `<a>` reloads a single-page app. Default: the `<UiKitProvider linkComponent>`
|
|
224
|
+
* (for an in-app `href`), then `<a>`. */
|
|
223
225
|
renderLink?: (props: StatTileLinkProps) => ReactElement;
|
|
224
226
|
/** Makes the tile a button (ignored when `href` is given). */
|
|
225
227
|
onClick?: () => void;
|
|
@@ -297,6 +299,7 @@ export function StatTile({
|
|
|
297
299
|
...rest
|
|
298
300
|
}: StatTileProps) {
|
|
299
301
|
const text = useKitLabels("statTile", DEFAULT_STAT_TILE_LABELS, labels);
|
|
302
|
+
const kitLink = useKitLink();
|
|
300
303
|
const [labelEl, setLabelEl] = useState<HTMLElement | null>(null);
|
|
301
304
|
const labelCut = useIsTruncated(truncateLabel ? labelEl : null);
|
|
302
305
|
const kitLocale = useKitLocale(locale);
|
|
@@ -334,8 +337,11 @@ export function StatTile({
|
|
|
334
337
|
"aria-describedby": describedBy,
|
|
335
338
|
children: label,
|
|
336
339
|
};
|
|
340
|
+
const kit = pickLinkRenderer<StatTileLinkProps>(undefined, kitLink, href);
|
|
337
341
|
labelNode = renderLink ? (
|
|
338
342
|
renderLink(linkProps)
|
|
343
|
+
) : kit ? (
|
|
344
|
+
<RenderedKitLink render={kit} props={linkProps} />
|
|
339
345
|
) : (
|
|
340
346
|
<a href={href} className={stretched} aria-describedby={describedBy}>
|
|
341
347
|
{label}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
2
|
import { cn } from "../lib/cn";
|
|
3
3
|
import type { ProgressBarTone } from "./progress-bar";
|
|
4
|
+
import type { ChipHue } from "./chip";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The tones are `ProgressBar`'s, name for name — which are `Chip`'s, less nothing — so a
|
|
7
8
|
* dot in a legend, the bar it explains and the badge beside it can be given the same
|
|
8
9
|
* word and agree. The money pair is here for the same reason it is there: a legend
|
|
9
|
-
* swatch for income is money, not "success".
|
|
10
|
+
* swatch for income is money, not "success". Chip's categorical hues are here too
|
|
11
|
+
* (0.11): a unit-status dot in a combobox (kastlan lease-unit-step.tsx:35, "rented"
|
|
12
|
+
* is blue) must match the Chip that states the same status elsewhere.
|
|
10
13
|
*/
|
|
11
|
-
export type StatusDotTone = ProgressBarTone;
|
|
14
|
+
export type StatusDotTone = ProgressBarTone | ChipHue;
|
|
12
15
|
|
|
13
16
|
/** `sm` 8px (keksdose's unread dot on the support trigger), `md` 10px (the avatar's
|
|
14
17
|
* unread dot), `lg` 12px (kastlan's occupancy legend swatch). */
|
|
@@ -23,6 +26,11 @@ const FILL: Record<StatusDotTone, string> = {
|
|
|
23
26
|
info: "bg-[var(--info)]",
|
|
24
27
|
income: "bg-[var(--money-income)]",
|
|
25
28
|
expense: "bg-[var(--money-expense)]",
|
|
29
|
+
blue: "bg-[var(--hue-blue)]",
|
|
30
|
+
indigo: "bg-[var(--hue-indigo)]",
|
|
31
|
+
purple: "bg-[var(--hue-purple)]",
|
|
32
|
+
teal: "bg-[var(--hue-teal)]",
|
|
33
|
+
orange: "bg-[var(--hue-orange)]",
|
|
26
34
|
};
|
|
27
35
|
|
|
28
36
|
const SIZE: Record<StatusDotSize, string> = { sm: "size-2", md: "size-2.5", lg: "size-3" };
|