@eifi1/ui-kit 0.16.1 → 0.17.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.
Files changed (86) hide show
  1. package/README.md +7 -7
  2. package/dist/chart.d.ts +1 -1
  3. package/dist/components/amount-input.d.ts +9 -1
  4. package/dist/components/amount-input.js.map +1 -1
  5. package/dist/components/button-group.d.ts +1 -1
  6. package/dist/components/calculator.d.ts +1 -1
  7. package/dist/components/copy-button.d.ts +1 -1
  8. package/dist/components/data-table-cells.d.ts +1 -1
  9. package/dist/components/data-table-filter-popover.d.ts +1 -1
  10. package/dist/components/data-table-filters.d.ts +1 -1
  11. package/dist/components/data-table.d.ts +1 -1
  12. package/dist/components/data-table.js +91 -3
  13. package/dist/components/data-table.js.map +1 -1
  14. package/dist/components/facing-pair.d.ts +1 -1
  15. package/dist/components/file-button.d.ts +1 -1
  16. package/dist/components/file-dropzone.d.ts +1 -1
  17. package/dist/components/form-actions.d.ts +1 -1
  18. package/dist/components/form-actions.js +32 -1
  19. package/dist/components/form-actions.js.map +1 -1
  20. package/dist/components/line-items.d.ts +30 -6
  21. package/dist/components/line-items.js +77 -33
  22. package/dist/components/line-items.js.map +1 -1
  23. package/dist/components/list.d.ts +13 -1
  24. package/dist/components/list.js.map +1 -1
  25. package/dist/components/money-field.d.ts +1 -1
  26. package/dist/components/number-field.d.ts +1 -1
  27. package/dist/components/number-input.d.ts +1 -1
  28. package/dist/components/numpad-sheet.d.ts +1 -1
  29. package/dist/components/progress-bar.d.ts +11 -1
  30. package/dist/components/progress-bar.js +14 -4
  31. package/dist/components/progress-bar.js.map +1 -1
  32. package/dist/components/series-chart.d.ts +1 -1
  33. package/dist/components/settings-fields.d.ts +1 -1
  34. package/dist/components/signed-amount.d.ts +12 -3
  35. package/dist/components/signed-amount.js +6 -4
  36. package/dist/components/signed-amount.js.map +1 -1
  37. package/dist/components/text-link.d.ts +1 -1
  38. package/dist/components/time-input.d.ts +1 -1
  39. package/dist/components/ui.d.ts +1 -1
  40. package/dist/components/ui.js +25 -3
  41. package/dist/components/ui.js.map +1 -1
  42. package/dist/components/use-table-state.d.ts +1 -1
  43. package/dist/{data-table-CfVXtCeG.d.ts → data-table-CpkWUJBY.d.ts} +41 -1
  44. package/dist/data-table.d.ts +1 -1
  45. package/dist/hooks/use-file-drop.d.ts +1 -1
  46. package/dist/i18n/defaults.d.ts +1 -1
  47. package/dist/i18n/kit-labels.d.ts +1 -1
  48. package/dist/i18n/locales/de-CH-informal.d.ts +1 -1
  49. package/dist/i18n/locales/de-CH.d.ts +1 -1
  50. package/dist/i18n/locales/de-informal.d.ts +1 -1
  51. package/dist/i18n/locales/de.d.ts +1 -1
  52. package/dist/i18n/locales/en.d.ts +71 -0
  53. package/dist/i18n/locales/en.js +95 -0
  54. package/dist/i18n/locales/en.js.map +1 -0
  55. package/dist/i18n/locales/es.d.ts +1 -1
  56. package/dist/i18n/locales/fr.d.ts +1 -1
  57. package/dist/i18n/locales/hu.d.ts +1 -1
  58. package/dist/i18n/locales/it.d.ts +1 -1
  59. package/dist/i18n/locales/zh.d.ts +1 -1
  60. package/dist/index.d.ts +4 -4
  61. package/dist/{kit-labels-DE--LmCk.d.ts → kit-labels-C4uB_zmg.d.ts} +57 -5
  62. package/dist/rhf/fields.d.ts +1 -1
  63. package/dist/rhf/form.d.ts +1 -1
  64. package/dist/rhf/line-items.js.map +1 -1
  65. package/dist/rhf.d.ts +1 -1
  66. package/dist/search/global-search.d.ts +22 -4
  67. package/dist/search/global-search.js +31 -3
  68. package/dist/search/global-search.js.map +1 -1
  69. package/dist/search.d.ts +1 -1
  70. package/dist/shell/app-shell.d.ts +1 -1
  71. package/dist/shell/top-bar-brand.d.ts +1 -1
  72. package/dist/shell.d.ts +1 -1
  73. package/dist/wizard/stepper-nav.d.ts +1 -1
  74. package/dist/wizard.d.ts +1 -1
  75. package/package.json +1 -1
  76. package/src/components/amount-input.tsx +8 -0
  77. package/src/components/data-table.tsx +165 -3
  78. package/src/components/form-actions.tsx +73 -3
  79. package/src/components/line-items.tsx +116 -40
  80. package/src/components/list.tsx +13 -1
  81. package/src/components/progress-bar.tsx +20 -4
  82. package/src/components/signed-amount.tsx +16 -5
  83. package/src/components/ui.tsx +57 -3
  84. package/src/i18n/locales/en.ts +116 -0
  85. package/src/rhf/line-items.tsx +2 -1
  86. package/src/search/global-search.tsx +57 -6
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/signed-amount.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { ArrowDown, ArrowUp, Minus } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { formatMoney, formatNumber, formatPercent } from \"../lib/format\";\nimport { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport { DEFAULT_STAT_TILE_LABELS } from \"./stat-tile\";\nimport type { StatTileLabels } from \"./stat-tile\";\n\n/**\n * The text colours a figure can carry, token-backed — `StatTile`'s tones, as a class\n * any span can take.\n *\n * - `default` — the primary text colour. `muted` — the secondary, quiet one.\n * - `income` / `expense` / `net` — the money trio (CVD-safe teal / amber / violet).\n * - `success` / `warning` / `danger` / `info` — a verdict: a rent rise is the tenant's\n * bad news (kastlan lease-columns), a vote \"for\" is green (meeting-agenda-tab).\n * - `brand` — the app's accent.\n *\n * Colour is never the only carrier: pair a verdict with an arrow, a word, or a sign.\n */\nexport type TextTone =\n | \"default\"\n | \"muted\"\n | \"brand\"\n | \"income\"\n | \"expense\"\n | \"net\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"info\";\n\nconst TEXT_TONE: Record<TextTone, string> = {\n default: \"text-[var(--text-primary)]\",\n muted: \"text-[var(--text-muted)]\",\n brand: \"text-[var(--brand)]\",\n income: \"text-[var(--money-income)]\",\n expense: \"text-[var(--money-expense)]\",\n net: \"text-[var(--money-net)]\",\n success: \"text-[var(--success)]\",\n warning: \"text-[var(--warning)]\",\n danger: \"text-[var(--danger)]\",\n info: \"text-[var(--info)]\",\n};\n\n/**\n * The class that colours text in `tone` — for the spans kastlan colours by hand with\n * `text-[var(--danger)]` / `text-emerald-600` (settlement-table, changelog-page,\n * meeting-agenda-tab), so the palette they draw from is the kit's tokens.\n */\nexport function toneTextClass(tone: TextTone): string {\n return TEXT_TONE[tone];\n}\n\nexport interface ToneProps extends ComponentPropsWithoutRef<\"span\"> {\n tone: TextTone;\n}\n\n/** A `<span>` in a {@link TextTone} — `<Tone tone=\"success\">{votesFor}</Tone>`. */\nexport function Tone({ tone, className, ...rest }: ToneProps) {\n return <span {...rest} className={cn(TEXT_TONE[tone], className)} />;\n}\n\n/* ── Signed amounts ──────────────────────────────────────────────────────── */\n\n/** Which way is +1: `up` for a figure where more is good news (income, savings),\n * `down` where less is (a rent the tenant pays, a cost). */\nexport type GoodDirection = \"up\" | \"down\";\n\n/**\n * How a signed figure is coloured:\n * - `signed` (default) — money read by its own sign: above zero `income`, below zero\n * `expense`, zero muted. A balance, a net.\n * - `income` / `expense` / `neutral` — one colour whatever the sign (`neutral` is the\n * text's own colour).\n * - `verdict` — the change judged by {@link SignedAmountProps.goodDirection}: good\n * news `success`, bad news `danger`, zero muted. Setting `goodDirection` implies it.\n */\nexport type SignedAmountTone = \"signed\" | \"income\" | \"expense\" | \"neutral\" | \"verdict\";\n\n/**\n * The colours a VERDICT is painted in (`goodDirection` set, or `tone=\"verdict\"`):\n * - `status` (default) — `success` / `danger`: good news, bad news.\n * - `money` — `income` / `expense` (the `.text-money-pos` / `.text-money-neg` pair):\n * the app's ONE money palette (keksdose dev#434). A grocery price going UP is money\n * going out, so with `goodDirection=\"down\"` it wears the expense colour every other\n * outflow in the app wears — not a red that says \"error\". keksdose coloured its\n * three price-change sites by hand for want of this.\n */\nexport type VerdictPalette = \"status\" | \"money\";\n\n/** What a signed figure says to a screen reader, whose readers disagree about a\n * leading \"+\" (most skip it) and a \"−\" (some read \"dash\", some nothing). */\nexport interface SignedAmountLabels {\n /** A figure above zero. `amount` is formatted and unsigned. */\n positive: (amount: string) => string;\n /** A figure below zero. `amount` is formatted and unsigned. */\n negative: (amount: string) => string;\n}\n\nexport const DEFAULT_SIGNED_AMOUNT_LABELS: SignedAmountLabels = {\n positive: (amount) => `plus ${amount}`,\n negative: (amount) => `minus ${amount}`,\n};\n\ninterface SignedFormatProps {\n /** ISO 4217 code: format as money. */\n currency?: string;\n /** `\"percent\"` reads `value` as a RATIO (`0.12` → 12 %), as `StatTile`'s delta does. */\n unit?: \"value\" | \"percent\";\n /** Compact notation: 12 400 → \"12K\". */\n compact?: boolean;\n /** Fraction digits, as `formatNumber`'s `digits`. */\n digits?: number | { min?: number; max?: number };\n /** Formats the UNSIGNED magnitude, replacing the `Intl` default outright. The sign\n * is the component's. */\n format?: (magnitude: number) => string;\n locale?: string;\n}\n\nfunction useMagnitudeFormatter({ currency, unit, compact, digits, format, locale }: SignedFormatProps) {\n const kitLocale = useKitLocale(locale);\n return (magnitude: number): string => {\n if (format) return format(magnitude);\n if (unit === \"percent\") return formatPercent(magnitude, { locale: kitLocale, compact, digits });\n if (currency) return formatMoney(magnitude, currency, { locale: kitLocale, compact, digits });\n return formatNumber(magnitude, { locale: kitLocale, compact, digits });\n };\n}\n\n/** `flatWithin` widens \"no change\" from exactly zero to a band round it: a price that\n * moved by a fraction of a cent, a ratio that rounds to \"0.0 %\", is not a rise, and an\n * arrow and a colour for it claim one. Inclusive — `|value| ≤ flatWithin` is flat. */\nfunction directionOf(value: number, flatWithin = 0): \"up\" | \"down\" | \"flat\" {\n if (Math.abs(value) <= Math.max(0, flatWithin)) return \"flat\";\n return value > 0 ? \"up\" : \"down\";\n}\n\nfunction verdictClass(good: boolean, palette: VerdictPalette): string {\n if (palette === \"money\") return good ? TEXT_TONE.income : TEXT_TONE.expense;\n return good ? TEXT_TONE.success : TEXT_TONE.danger;\n}\n\nfunction signedToneClass(\n tone: SignedAmountTone,\n dir: \"up\" | \"down\" | \"flat\",\n goodDirection: GoodDirection | undefined,\n palette: VerdictPalette,\n): string {\n if (tone === \"verdict\" || (goodDirection && tone === \"signed\")) {\n if (dir === \"flat\" || !goodDirection) return TEXT_TONE.muted;\n return verdictClass(dir === goodDirection, palette);\n }\n if (tone === \"signed\") return dir === \"flat\" ? TEXT_TONE.muted : dir === \"up\" ? TEXT_TONE.income : TEXT_TONE.expense;\n if (tone === \"neutral\") return \"\";\n return TEXT_TONE[tone];\n}\n\nconst ARROW = { up: ArrowUp, down: ArrowDown, flat: Minus } as const;\n\nexport interface SignedAmountProps\n extends SignedFormatProps,\n Omit<ComponentPropsWithoutRef<\"span\">, \"children\"> {\n value: number;\n /** See {@link SignedAmountTone}. Default `signed`, or `verdict` with `goodDirection`. */\n tone?: SignedAmountTone;\n /** Which way is good news — colours by verdict (`success` / `danger`, or the money\n * pair with `palette=\"money\"`). */\n goodDirection?: GoodDirection;\n /** See {@link VerdictPalette}. Default `status`. Only a verdict reads it. */\n palette?: VerdictPalette;\n /** A magnitude up to which the figure counts as NO change: no sign, no arrow but the\n * flat dash, muted, spoken without \"plus\"/\"minus\" (inclusive; default 0). The digits\n * still print `|value|` — use a band below the display precision, where they round\n * to zero anyway (`flatWithin={0.005}` at two digits). */\n flatWithin?: number;\n /** Print \"+\" before a positive figure (default `true`). A negative always shows its\n * minus; zero shows neither. */\n showPlus?: boolean;\n /** An arrow before the figure (up / down / a dash at zero) — so the direction does not\n * ride on colour alone. Decorative; the words say the same. */\n arrow?: boolean;\n /** Tag the figure `data-private`, for the host's demo-mode blur. */\n sensitive?: boolean;\n labels?: Partial<SignedAmountLabels>;\n}\n\n/**\n * A figure with a sign that matters: a balance, a rent change, a net.\n *\n * kastlan writes it by hand in five places — `{change > 0 ? \"+\" : \"\"}{formatCurrency(\n * change)}` in a span coloured by a ternary (lease-columns:190, settlement-table:58,\n * the tenancy `formatPct`) — and each picks its own colours for the same verdict.\n *\n * The visible figure (sign, digits, arrow) is hidden from assistive tech and spoken as\n * words instead (\"plus CHF 50.00\" / \"minus CHF 50.00\", `signedAmount.*`): a \"+\" is\n * skipped by most screen readers, and `Intl`'s \"−\" is read as \"dash\" or not at all.\n */\nexport function SignedAmount({\n value,\n currency,\n unit,\n compact,\n digits,\n format,\n locale,\n tone = \"signed\",\n goodDirection,\n palette = \"status\",\n flatWithin,\n showPlus = true,\n arrow = false,\n sensitive = false,\n labels,\n className,\n ...rest\n}: SignedAmountProps) {\n const text = useKitLabels(\"signedAmount\", DEFAULT_SIGNED_AMOUNT_LABELS, labels);\n const fmt = useMagnitudeFormatter({ currency, unit, compact, digits, format, locale });\n const finite = Number.isFinite(value);\n const dir = finite ? directionOf(value, flatWithin) : \"flat\";\n const amount = finite ? fmt(Math.abs(value)) : \"—\";\n // U+2212, the typographic minus: the same width as \"+\", so a column of signed figures\n // lines up under `tabular-nums`.\n const sign = dir === \"up\" ? (showPlus ? \"+\" : \"\") : dir === \"down\" ? \"−\" : \"\";\n const spoken = dir === \"up\" ? text.positive(amount) : dir === \"down\" ? text.negative(amount) : amount;\n const Icon = ARROW[dir];\n return (\n <span\n {...rest}\n data-private={sensitive ? \"\" : undefined}\n data-direction={dir}\n className={cn(\n \"relative inline-flex items-center gap-0.5 whitespace-nowrap tabular-nums\",\n signedToneClass(tone, dir, goodDirection, palette),\n className,\n )}\n >\n {arrow && <Icon aria-hidden className=\"size-[1em] shrink-0\" />}\n <span aria-hidden>\n {sign}\n {amount}\n </span>\n <span className=\"sr-only\">{spoken}</span>\n </span>\n );\n}\n\nexport interface DeltaProps extends SignedFormatProps, Omit<ComponentPropsWithoutRef<\"span\">, \"children\"> {\n /** The change. Above zero is \"up\". */\n value: number;\n /** Which way is good news. Unset, the change is stated and not judged (muted); set,\n * it is coloured `success` / `danger` and its sentence says \"(better)\" / \"(worse)\". */\n goodDirection?: GoodDirection;\n /** See {@link VerdictPalette}: `money` paints good news `income` and bad news\n * `expense` instead of `success` / `danger`. Default `status`. */\n palette?: VerdictPalette;\n /** See {@link SignedAmountProps.flatWithin}: up to this magnitude the change is\n * \"unchanged\" — flat dash, muted, no verdict. Default 0. */\n flatWithin?: number;\n /** What it is measured against, after it: \"vs last month\". */\n label?: ReactNode;\n /** Hide the arrow (shown by default, as on a StatTile). At zero the arrow is a flat\n * dash BESIDE the figure (\"– 0.00 %\"), never in place of it. */\n arrow?: boolean;\n sensitive?: boolean;\n /** The `statTile` namespace's direction words — a delta says the same as a tile's. */\n labels?: Partial<Pick<StatTileLabels, \"increase\" | \"decrease\" | \"unchanged\" | \"better\" | \"worse\">>;\n}\n\n/**\n * `StatTile`'s delta on its own: an arrow, the unsigned magnitude, and a sentence for\n * screen readers (\"Up 12% (worse)\"), coloured only when `goodDirection` judges it —\n * users up and cost up are the same arrow until someone says which is good.\n *\n * For a change shown outside a tile: kastlan's rent-change column\n * (lease-columns:219, a \"+1.25%\" chip), the breakdown rows of the rent calculator\n * (breakdown-row:5, a trend icon coloured by hand).\n */\nexport function Delta({\n value,\n currency,\n unit,\n compact,\n digits,\n format,\n locale,\n goodDirection,\n palette = \"status\",\n flatWithin,\n label,\n arrow = true,\n sensitive = false,\n labels,\n className,\n ...rest\n}: DeltaProps) {\n const text = useKitLabels(\"statTile\", DEFAULT_STAT_TILE_LABELS, labels);\n const fmt = useMagnitudeFormatter({ currency, unit, compact, digits, format, locale });\n if (!Number.isFinite(value)) return null;\n const amount = fmt(Math.abs(value));\n const dir = directionOf(value, flatWithin);\n const Icon = ARROW[dir];\n let spoken = dir === \"up\" ? text.increase(amount) : dir === \"down\" ? text.decrease(amount) : text.unchanged;\n let tone = TEXT_TONE.muted;\n if (goodDirection && dir !== \"flat\") {\n const good = dir === goodDirection;\n spoken = good ? text.better(spoken) : text.worse(spoken);\n tone = verdictClass(good, palette);\n }\n return (\n <span\n {...rest}\n data-direction={dir}\n className={cn(\"relative inline-flex flex-wrap items-center gap-x-1.5 tabular-nums\", className)}\n >\n <span data-private={sensitive ? \"\" : undefined} className={cn(\"inline-flex items-center gap-0.5 font-medium\", tone)}>\n {arrow && <Icon className=\"size-[1em] shrink-0\" aria-hidden />}\n {/* The figure at zero too, beside the flat dash (kastlan feedback #48). Before\n this a flat delta with an arrow drew the dash ALONE, as a StatTile does — so\n a rent-change column that printed \"0.00 %\" for an unchanged lease showed a\n bare \"–\" in its place, and a bare dash is what a table prints for a MISSING\n value: \"unchanged\" and \"not known\" looked the same. The Minus icon is the\n neutral indicator, the muted tone the neutral colour, and the figure says\n how much (none) in the column's own digits. A value that is missing never\n reaches here — a non-finite one renders nothing, above. Visible change for\n every `<Delta value={0}>` with the default arrow: \"–\" became \"– 0 %\". */}\n <span aria-hidden>{amount}</span>\n <span className=\"sr-only\">{spoken}</span>\n </span>\n {label != null && <span className=\"text-[var(--text-muted)]\">{label}</span>}\n </span>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/signed-amount.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { ArrowDown, ArrowUp, Minus } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { formatMoney, formatNumber, formatPercent } from \"../lib/format\";\nimport { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport { DEFAULT_STAT_TILE_LABELS } from \"./stat-tile\";\nimport type { StatTileLabels } from \"./stat-tile\";\n\n/**\n * The text colours a figure can carry, token-backed — `StatTile`'s tones, as a class\n * any span can take.\n *\n * - `default` — the primary text colour. `muted` — the secondary, quiet one.\n * - `income` / `expense` / `net` — the money trio (CVD-safe teal / amber / violet).\n * - `success` / `warning` / `danger` / `info` — a verdict: a rent rise is the tenant's\n * bad news (kastlan lease-columns), a vote \"for\" is green (meeting-agenda-tab).\n * - `brand` — the app's accent.\n *\n * Colour is never the only carrier: pair a verdict with an arrow, a word, or a sign.\n */\nexport type TextTone =\n | \"default\"\n | \"muted\"\n | \"brand\"\n | \"income\"\n | \"expense\"\n | \"net\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"info\";\n\nconst TEXT_TONE: Record<TextTone, string> = {\n default: \"text-[var(--text-primary)]\",\n muted: \"text-[var(--text-muted)]\",\n brand: \"text-[var(--brand)]\",\n income: \"text-[var(--money-income)]\",\n expense: \"text-[var(--money-expense)]\",\n net: \"text-[var(--money-net)]\",\n success: \"text-[var(--success)]\",\n warning: \"text-[var(--warning)]\",\n danger: \"text-[var(--danger)]\",\n info: \"text-[var(--info)]\",\n};\n\n/**\n * The class that colours text in `tone` — for the spans kastlan colours by hand with\n * `text-[var(--danger)]` / `text-emerald-600` (settlement-table, changelog-page,\n * meeting-agenda-tab), so the palette they draw from is the kit's tokens.\n */\nexport function toneTextClass(tone: TextTone): string {\n return TEXT_TONE[tone];\n}\n\nexport interface ToneProps extends ComponentPropsWithoutRef<\"span\"> {\n tone: TextTone;\n}\n\n/** A `<span>` in a {@link TextTone} — `<Tone tone=\"success\">{votesFor}</Tone>`. */\nexport function Tone({ tone, className, ...rest }: ToneProps) {\n return <span {...rest} className={cn(TEXT_TONE[tone], className)} />;\n}\n\n/* ── Signed amounts ──────────────────────────────────────────────────────── */\n\n/** Which way is +1: `up` for a figure where more is good news (income, savings),\n * `down` where less is (a rent the tenant pays, a cost). */\nexport type GoodDirection = \"up\" | \"down\";\n\n/**\n * How a signed figure is coloured:\n * - `signed` (default) — money read by its own sign: above zero `income`, below zero\n * `expense`, zero muted. A balance, a net.\n * - `income` / `expense` / `neutral` — one colour whatever the sign (`neutral` is the\n * text's own colour).\n * - `verdict` — the change judged by {@link SignedAmountProps.goodDirection}: good\n * news `success`, bad news `danger`, zero muted. Setting `goodDirection` implies it.\n */\nexport type SignedAmountTone = \"signed\" | \"income\" | \"expense\" | \"neutral\" | \"verdict\";\n\n/**\n * The colours a VERDICT is painted in (`goodDirection` set, or `tone=\"verdict\"`):\n * - `status` (default) — `success` / `danger`: good news, bad news.\n * - `money` — `income` / `expense` (the `.text-money-pos` / `.text-money-neg` pair):\n * the app's ONE money palette (keksdose dev#434). A grocery price going UP is money\n * going out, so with `goodDirection=\"down\"` it wears the expense colour every other\n * outflow in the app wears — not a red that says \"error\". keksdose coloured its\n * three price-change sites by hand for want of this.\n */\nexport type VerdictPalette = \"status\" | \"money\";\n\n/** What a signed figure says to a screen reader, whose readers disagree about a\n * leading \"+\" (most skip it) and a \"−\" (some read \"dash\", some nothing). */\nexport interface SignedAmountLabels {\n /** A figure above zero. `amount` is formatted and unsigned. */\n positive: (amount: string) => string;\n /** A figure below zero. `amount` is formatted and unsigned. */\n negative: (amount: string) => string;\n}\n\nexport const DEFAULT_SIGNED_AMOUNT_LABELS: SignedAmountLabels = {\n positive: (amount) => `plus ${amount}`,\n negative: (amount) => `minus ${amount}`,\n};\n\ninterface SignedFormatProps {\n /** ISO 4217 code: format as money. */\n currency?: string;\n /** `\"percent\"` reads `value` as a RATIO (`0.12` → 12 %), as `StatTile`'s delta does\n * — or as percent POINTS with `ratio={false}`. */\n unit?: \"value\" | \"percent\";\n /** With `unit=\"percent\"`: `true` (default) — `value` is a ratio, `0.12` is 12 %.\n * `false` — `value` is already in percent POINTS, `12` is 12 %, as `formatPercent`'s\n * `ratio` option reads it. Backends send points (keksdose's price changes, kastlan's\n * `change_pct`), and without this every site divided by 100 — and wrote its\n * `flatWithin` as a ratio (`0.005`) beside a value it had to convert. With\n * `ratio={false}` the value AND `flatWithin` are in points (`flatWithin={0.5}` is\n * half a point). Ignored for any other unit. */\n ratio?: boolean;\n /** Compact notation: 12 400 → \"12K\". */\n compact?: boolean;\n /** Fraction digits, as `formatNumber`'s `digits`. */\n digits?: number | { min?: number; max?: number };\n /** Formats the UNSIGNED magnitude, replacing the `Intl` default outright. The sign\n * is the component's. */\n format?: (magnitude: number) => string;\n locale?: string;\n}\n\nfunction useMagnitudeFormatter({ currency, unit, ratio, compact, digits, format, locale }: SignedFormatProps) {\n const kitLocale = useKitLocale(locale);\n return (magnitude: number): string => {\n if (format) return format(magnitude);\n if (unit === \"percent\") return formatPercent(magnitude, { locale: kitLocale, compact, digits, ratio });\n if (currency) return formatMoney(magnitude, currency, { locale: kitLocale, compact, digits });\n return formatNumber(magnitude, { locale: kitLocale, compact, digits });\n };\n}\n\n/** `flatWithin` widens \"no change\" from exactly zero to a band round it: a price that\n * moved by a fraction of a cent, a ratio that rounds to \"0.0 %\", is not a rise, and an\n * arrow and a colour for it claim one. Inclusive — `|value| ≤ flatWithin` is flat. */\nfunction directionOf(value: number, flatWithin = 0): \"up\" | \"down\" | \"flat\" {\n if (Math.abs(value) <= Math.max(0, flatWithin)) return \"flat\";\n return value > 0 ? \"up\" : \"down\";\n}\n\nfunction verdictClass(good: boolean, palette: VerdictPalette): string {\n if (palette === \"money\") return good ? TEXT_TONE.income : TEXT_TONE.expense;\n return good ? TEXT_TONE.success : TEXT_TONE.danger;\n}\n\nfunction signedToneClass(\n tone: SignedAmountTone,\n dir: \"up\" | \"down\" | \"flat\",\n goodDirection: GoodDirection | undefined,\n palette: VerdictPalette,\n): string {\n if (tone === \"verdict\" || (goodDirection && tone === \"signed\")) {\n if (dir === \"flat\" || !goodDirection) return TEXT_TONE.muted;\n return verdictClass(dir === goodDirection, palette);\n }\n if (tone === \"signed\") return dir === \"flat\" ? TEXT_TONE.muted : dir === \"up\" ? TEXT_TONE.income : TEXT_TONE.expense;\n if (tone === \"neutral\") return \"\";\n return TEXT_TONE[tone];\n}\n\nconst ARROW = { up: ArrowUp, down: ArrowDown, flat: Minus } as const;\n\nexport interface SignedAmountProps\n extends SignedFormatProps,\n Omit<ComponentPropsWithoutRef<\"span\">, \"children\"> {\n value: number;\n /** See {@link SignedAmountTone}. Default `signed`, or `verdict` with `goodDirection`. */\n tone?: SignedAmountTone;\n /** Which way is good news — colours by verdict (`success` / `danger`, or the money\n * pair with `palette=\"money\"`). */\n goodDirection?: GoodDirection;\n /** See {@link VerdictPalette}. Default `status`. Only a verdict reads it. */\n palette?: VerdictPalette;\n /** A magnitude up to which the figure counts as NO change: no sign, no arrow but the\n * flat dash, muted, spoken without \"plus\"/\"minus\" (inclusive; default 0). The digits\n * still print `|value|` — use a band below the display precision, where they round\n * to zero anyway (`flatWithin={0.005}` at two digits). */\n flatWithin?: number;\n /** Print \"+\" before a positive figure (default `true`). A negative always shows its\n * minus; zero shows neither. */\n showPlus?: boolean;\n /** An arrow before the figure (up / down / a dash at zero) — so the direction does not\n * ride on colour alone. Decorative; the words say the same. */\n arrow?: boolean;\n /** Tag the figure `data-private`, for the host's demo-mode blur. */\n sensitive?: boolean;\n labels?: Partial<SignedAmountLabels>;\n}\n\n/**\n * A figure with a sign that matters: a balance, a rent change, a net.\n *\n * kastlan writes it by hand in five places — `{change > 0 ? \"+\" : \"\"}{formatCurrency(\n * change)}` in a span coloured by a ternary (lease-columns:190, settlement-table:58,\n * the tenancy `formatPct`) — and each picks its own colours for the same verdict.\n *\n * The visible figure (sign, digits, arrow) is hidden from assistive tech and spoken as\n * words instead (\"plus CHF 50.00\" / \"minus CHF 50.00\", `signedAmount.*`): a \"+\" is\n * skipped by most screen readers, and `Intl`'s \"−\" is read as \"dash\" or not at all.\n */\nexport function SignedAmount({\n value,\n currency,\n unit,\n ratio,\n compact,\n digits,\n format,\n locale,\n tone = \"signed\",\n goodDirection,\n palette = \"status\",\n flatWithin,\n showPlus = true,\n arrow = false,\n sensitive = false,\n labels,\n className,\n ...rest\n}: SignedAmountProps) {\n const text = useKitLabels(\"signedAmount\", DEFAULT_SIGNED_AMOUNT_LABELS, labels);\n const fmt = useMagnitudeFormatter({ currency, unit, ratio, compact, digits, format, locale });\n const finite = Number.isFinite(value);\n const dir = finite ? directionOf(value, flatWithin) : \"flat\";\n const amount = finite ? fmt(Math.abs(value)) : \"—\";\n // U+2212, the typographic minus: the same width as \"+\", so a column of signed figures\n // lines up under `tabular-nums`.\n const sign = dir === \"up\" ? (showPlus ? \"+\" : \"\") : dir === \"down\" ? \"−\" : \"\";\n const spoken = dir === \"up\" ? text.positive(amount) : dir === \"down\" ? text.negative(amount) : amount;\n const Icon = ARROW[dir];\n return (\n <span\n {...rest}\n data-private={sensitive ? \"\" : undefined}\n data-direction={dir}\n className={cn(\n \"relative inline-flex items-center gap-0.5 whitespace-nowrap tabular-nums\",\n signedToneClass(tone, dir, goodDirection, palette),\n className,\n )}\n >\n {arrow && <Icon aria-hidden className=\"size-[1em] shrink-0\" />}\n <span aria-hidden>\n {sign}\n {amount}\n </span>\n <span className=\"sr-only\">{spoken}</span>\n </span>\n );\n}\n\nexport interface DeltaProps extends SignedFormatProps, Omit<ComponentPropsWithoutRef<\"span\">, \"children\"> {\n /** The change. Above zero is \"up\". */\n value: number;\n /** Which way is good news. Unset, the change is stated and not judged (muted); set,\n * it is coloured `success` / `danger` and its sentence says \"(better)\" / \"(worse)\". */\n goodDirection?: GoodDirection;\n /** See {@link VerdictPalette}: `money` paints good news `income` and bad news\n * `expense` instead of `success` / `danger`. Default `status`. */\n palette?: VerdictPalette;\n /** See {@link SignedAmountProps.flatWithin}: up to this magnitude the change is\n * \"unchanged\" — flat dash, muted, no verdict. Default 0. */\n flatWithin?: number;\n /** What it is measured against, after it: \"vs last month\". */\n label?: ReactNode;\n /** Hide the arrow (shown by default, as on a StatTile). At zero the arrow is a flat\n * dash BESIDE the figure (\"– 0.00 %\"), never in place of it. */\n arrow?: boolean;\n sensitive?: boolean;\n /** The `statTile` namespace's direction words — a delta says the same as a tile's. */\n labels?: Partial<Pick<StatTileLabels, \"increase\" | \"decrease\" | \"unchanged\" | \"better\" | \"worse\">>;\n}\n\n/**\n * `StatTile`'s delta on its own: an arrow, the unsigned magnitude, and a sentence for\n * screen readers (\"Up 12% (worse)\"), coloured only when `goodDirection` judges it —\n * users up and cost up are the same arrow until someone says which is good.\n *\n * For a change shown outside a tile: kastlan's rent-change column\n * (lease-columns:219, a \"+1.25%\" chip), the breakdown rows of the rent calculator\n * (breakdown-row:5, a trend icon coloured by hand).\n */\nexport function Delta({\n value,\n currency,\n unit,\n ratio,\n compact,\n digits,\n format,\n locale,\n goodDirection,\n palette = \"status\",\n flatWithin,\n label,\n arrow = true,\n sensitive = false,\n labels,\n className,\n ...rest\n}: DeltaProps) {\n const text = useKitLabels(\"statTile\", DEFAULT_STAT_TILE_LABELS, labels);\n const fmt = useMagnitudeFormatter({ currency, unit, ratio, compact, digits, format, locale });\n if (!Number.isFinite(value)) return null;\n const amount = fmt(Math.abs(value));\n const dir = directionOf(value, flatWithin);\n const Icon = ARROW[dir];\n let spoken = dir === \"up\" ? text.increase(amount) : dir === \"down\" ? text.decrease(amount) : text.unchanged;\n let tone = TEXT_TONE.muted;\n if (goodDirection && dir !== \"flat\") {\n const good = dir === goodDirection;\n spoken = good ? text.better(spoken) : text.worse(spoken);\n tone = verdictClass(good, palette);\n }\n return (\n <span\n {...rest}\n data-direction={dir}\n className={cn(\"relative inline-flex flex-wrap items-center gap-x-1.5 tabular-nums\", className)}\n >\n <span data-private={sensitive ? \"\" : undefined} className={cn(\"inline-flex items-center gap-0.5 font-medium\", tone)}>\n {arrow && <Icon className=\"size-[1em] shrink-0\" aria-hidden />}\n {/* The figure at zero too, beside the flat dash (kastlan feedback #48). Before\n this a flat delta with an arrow drew the dash ALONE, as a StatTile does — so\n a rent-change column that printed \"0.00 %\" for an unchanged lease showed a\n bare \"–\" in its place, and a bare dash is what a table prints for a MISSING\n value: \"unchanged\" and \"not known\" looked the same. The Minus icon is the\n neutral indicator, the muted tone the neutral colour, and the figure says\n how much (none) in the column's own digits. A value that is missing never\n reaches here — a non-finite one renders nothing, above. Visible change for\n every `<Delta value={0}>` with the default arrow: \"–\" became \"– 0 %\". */}\n <span aria-hidden>{amount}</span>\n <span className=\"sr-only\">{spoken}</span>\n </span>\n {label != null && <span className=\"text-[var(--text-muted)]\">{label}</span>}\n </span>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactElement, ComponentPropsWithoutRef, ReactNode, Ref, MouseEvent, KeyboardEvent } from 'react';
3
3
  import { LucideIcon } from 'lucide-react';
4
- import { aD as KitLinkComponent } from '../kit-labels-DE--LmCk.js';
4
+ import { aE as KitLinkComponent } from '../kit-labels-C4uB_zmg.js';
5
5
  import './data-table-labels.js';
6
6
  import './mini-calendar.js';
7
7
  import './calendar-heatmap.js';
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { aC as InputProps } from '../kit-labels-DE--LmCk.js';
2
+ import { aD as InputProps } from '../kit-labels-C4uB_zmg.js';
3
3
  import './data-table-labels.js';
4
4
  import './mini-calendar.js';
5
5
  import './calendar-heatmap.js';
@@ -1,6 +1,6 @@
1
1
  import 'react';
2
2
  import './tooltip.js';
3
- export { B as Button, a as ButtonClassesOptions, b as ButtonLinkProps, c as ButtonProps, d as ButtonSize, e as ButtonTone, f as ButtonVariant, g as Card, h as CardAction, i as CardActionProps, j as CardContent, k as CardContentProps, l as CardDescription, m as CardDescriptionProps, n as CardFooter, o as CardFooterProps, p as CardHeader, q as CardHeaderProps, r as CardProps, s as CardTitle, t as CardTitleLevel, u as CardTitleProps, v as CardTone, R as DEFAULT_PASSWORD_REVEAL_LABELS, U as DEFAULT_TABS_LABELS, X as EmptyState, Y as EmptyStateProps, Z as FIELD_BASE, _ as FIELD_DISPLAY, $ as FIELD_FLOATING_PAD, a0 as FIELD_INVALID, a1 as FIELD_TRIGGER, a2 as FIELD_WRITABLE_LOOK, a3 as FLOATING_INPUT_CLASS, a4 as FLOATING_LABEL_CLASS, a5 as FLOATING_LABEL_STATIC, a6 as FieldChevron, a7 as FieldChevronProps, a8 as FieldHint, a9 as FieldHintProps, aa as FieldLabel, ab as FieldLabelProps, aj as FloatingField, ak as FloatingFieldProps, at as IconButton, au as IconButtonGlyphSize, av as IconButtonLinkProps, aw as IconButtonProps, ax as IconButtonSize, ay as IconButtonTone, az as IconButtonToneColor, aA as IconButtonVariant, aB as Input, aC as InputProps, aF as Label, aH as LabelProps, aJ as PHONE_QUERY, aK as PasswordRevealLabels, aM as Select, aN as SelectProps, aO as Spinner, aP as SpinnerProps, aR as TabItem, aS as Tabs, aT as TabsLabels, aU as TabsProps, aV as Textarea, aW as TextareaProps, b2 as buttonClasses, b6 as resolvePasswordRevealLabels } from '../kit-labels-DE--LmCk.js';
3
+ export { B as Button, a as ButtonClassesOptions, b as ButtonLinkProps, c as ButtonProps, d as ButtonSize, e as ButtonTone, f as ButtonVariant, g as Card, h as CardAction, i as CardActionProps, j as CardContent, k as CardContentProps, l as CardDensity, m as CardDescription, n as CardDescriptionProps, o as CardFooter, p as CardFooterProps, q as CardHeader, r as CardHeaderProps, s as CardProps, t as CardTitle, u as CardTitleLevel, v as CardTitleProps, w as CardTone, S as DEFAULT_PASSWORD_REVEAL_LABELS, V as DEFAULT_TABS_LABELS, Y as EmptyState, Z as EmptyStateProps, _ as FIELD_BASE, $ as FIELD_DISPLAY, a0 as FIELD_FLOATING_PAD, a1 as FIELD_INVALID, a2 as FIELD_TRIGGER, a3 as FIELD_WRITABLE_LOOK, a4 as FLOATING_INPUT_CLASS, a5 as FLOATING_LABEL_CLASS, a6 as FLOATING_LABEL_STATIC, a7 as FieldChevron, a8 as FieldChevronProps, a9 as FieldHint, aa as FieldHintProps, ab as FieldLabel, ac as FieldLabelProps, ak as FloatingField, al as FloatingFieldProps, au as IconButton, av as IconButtonGlyphSize, aw as IconButtonLinkProps, ax as IconButtonProps, ay as IconButtonSize, az as IconButtonTone, aA as IconButtonToneColor, aB as IconButtonVariant, aC as Input, aD as InputProps, aG as Label, aI as LabelProps, aK as PHONE_QUERY, aL as PasswordRevealLabels, aO as Select, aP as SelectProps, aQ as Spinner, aR as SpinnerProps, aT as TabItem, aU as Tabs, aV as TabsLabels, aW as TabsProps, aX as Textarea, aY as TextareaProps, b4 as buttonClasses, b8 as resolvePasswordRevealLabels } from '../kit-labels-C4uB_zmg.js';
4
4
  import '../hooks/use-anchored-rect.js';
5
5
  import '../lib/clipping.js';
6
6
  import './data-table-labels.js';
@@ -1016,6 +1016,12 @@ const CARD_PADDING_STRONG = {
1016
1016
  sm: "p-[11px]",
1017
1017
  md: "p-[15px]"
1018
1018
  };
1019
+ const CARD_TONE_WASH = {
1020
+ warning: "bg-[image:linear-gradient(var(--warning-bg),var(--warning-bg))]",
1021
+ danger: "bg-[image:linear-gradient(var(--danger-bg),var(--danger-bg))]",
1022
+ info: "bg-[image:linear-gradient(var(--info-bg),var(--info-bg))]",
1023
+ success: "bg-[image:linear-gradient(var(--success-bg),var(--success-bg))]"
1024
+ };
1019
1025
  const CARD_TONES_STRONG = {
1020
1026
  warning: "border-2 border-[var(--warning)]",
1021
1027
  danger: "border-2 border-[var(--danger-border-strong)]",
@@ -1023,6 +1029,7 @@ const CARD_TONES_STRONG = {
1023
1029
  success: "border-2 border-[var(--success)]"
1024
1030
  };
1025
1031
  const CardPaddedContext = createContext(false);
1032
+ const CardDensityContext = createContext("comfortable");
1026
1033
  function Card({
1027
1034
  className,
1028
1035
  children,
@@ -1030,6 +1037,8 @@ function Card({
1030
1037
  variant = "default",
1031
1038
  tone,
1032
1039
  toneStrength = "soft",
1040
+ toneFill = false,
1041
+ density = "comfortable",
1033
1042
  padding,
1034
1043
  ...rest
1035
1044
  }) {
@@ -1042,6 +1051,7 @@ function Card({
1042
1051
  {
1043
1052
  "data-tone": tone,
1044
1053
  "data-tone-strength": strong ? "strong" : void 0,
1054
+ "data-density": density === "compact" ? "compact" : void 0,
1045
1055
  ...rest,
1046
1056
  className: cn(
1047
1057
  variant === "inset" ? "rounded-md bg-[var(--bg-surface-2)] p-3" : variant === "outline" ? (
@@ -1056,16 +1066,18 @@ function Card({
1056
1066
  ),
1057
1067
  toned && (bordered ? toned.border : toned.fill),
1058
1068
  strong && tone && CARD_TONES_STRONG[tone],
1069
+ toneFill && bordered && tone && CARD_TONE_WASH[tone],
1059
1070
  toned?.title,
1060
1071
  ownPadding && (strong ? CARD_PADDING_STRONG : CARD_PADDING)[ownPadding],
1061
1072
  className
1062
1073
  ),
1063
- children: /* @__PURE__ */ jsx(CardPaddedContext.Provider, { value: ownPadding !== void 0, children })
1074
+ children: /* @__PURE__ */ jsx(CardPaddedContext.Provider, { value: ownPadding !== void 0, children: /* @__PURE__ */ jsx(CardDensityContext.Provider, { value: density, children }) })
1064
1075
  }
1065
1076
  );
1066
1077
  }
1067
1078
  function CardHeader({ className, stackAction = false, ...props }) {
1068
1079
  const padded = useContext(CardPaddedContext);
1080
+ const compact = useContext(CardDensityContext) === "compact";
1069
1081
  return /* @__PURE__ */ jsx(
1070
1082
  "div",
1071
1083
  {
@@ -1085,6 +1097,7 @@ function CardHeader({ className, stackAction = false, ...props }) {
1085
1097
  // never inherits an ancestor header's two columns.
1086
1098
  "grid auto-rows-min items-start gap-1.5 [--card-header-cols:1fr] has-data-[slot=card-action]:[--card-header-cols:1fr_auto] grid-cols-[var(--card-header-cols,1fr)]",
1087
1099
  padded ? "px-0 pt-0" : "px-6 pt-6",
1100
+ compact && "gap-0.5",
1088
1101
  // One column under `sm`, and the action back into the flow (DOM order, so
1089
1102
  // after the description when it is written after it), at the start edge.
1090
1103
  // The descendant selector outranks CardAction's own placement classes.
@@ -1096,14 +1109,23 @@ function CardHeader({ className, stackAction = false, ...props }) {
1096
1109
  );
1097
1110
  }
1098
1111
  function CardTitle({ className, as: Tag = "div", ...props }) {
1099
- return /* @__PURE__ */ jsx(Tag, { "data-slot": "card-title", className: cn("font-semibold leading-none", className), ...props });
1112
+ const compact = useContext(CardDensityContext) === "compact";
1113
+ return /* @__PURE__ */ jsx(
1114
+ Tag,
1115
+ {
1116
+ "data-slot": "card-title",
1117
+ className: cn(compact ? "text-sm font-medium" : "font-semibold", "leading-none", className),
1118
+ ...props
1119
+ }
1120
+ );
1100
1121
  }
1101
1122
  function CardDescription({ className, ...props }) {
1123
+ const compact = useContext(CardDensityContext) === "compact";
1102
1124
  return /* @__PURE__ */ jsx(
1103
1125
  "div",
1104
1126
  {
1105
1127
  "data-slot": "card-description",
1106
- className: cn("text-sm text-[var(--text-muted)]", className),
1128
+ className: cn(compact ? "text-xs" : "text-sm", "text-[var(--text-muted)]", className),
1107
1129
  ...props
1108
1130
  }
1109
1131
  );