@sincpro/web-ui 0.1.0 → 0.2.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 (119) hide show
  1. package/README.md +37 -10
  2. package/dist/components/activity/Activity.d.ts +69 -0
  3. package/dist/components/activity/Activity.js +279 -0
  4. package/dist/components/{chatter → activity}/Messages.d.ts +2 -2
  5. package/dist/components/{chatter → activity}/Messages.js +9 -6
  6. package/dist/components/{chatter → activity}/index.d.ts +1 -1
  7. package/dist/components/activity/index.js +4 -0
  8. package/dist/components/assist/AssistMark.d.ts +3 -3
  9. package/dist/components/assist/AssistMark.js +1 -1
  10. package/dist/components/assist/assist.d.ts +1 -1
  11. package/dist/components/assist/assist.js +1 -1
  12. package/dist/components/form/Sheet.d.ts +4 -0
  13. package/dist/components/form/Sheet.js +11 -4
  14. package/dist/components/index.d.ts +1 -1
  15. package/dist/components/index.js +1 -1
  16. package/dist/components/lines/LineItems.d.ts +42 -5
  17. package/dist/components/lines/LineItems.js +106 -18
  18. package/dist/components/listing/Listing.js +1 -1
  19. package/dist/components/listing/ListingBar.d.ts +41 -2
  20. package/dist/components/listing/ListingBar.js +43 -4
  21. package/dist/components/listing/Row.d.ts +5 -1
  22. package/dist/components/listing/Row.js +3 -2
  23. package/dist/components/record/RecordHeader.d.ts +21 -17
  24. package/dist/components/record/RecordHeader.js +46 -14
  25. package/dist/components/record/RecordNotices.d.ts +37 -0
  26. package/dist/components/record/RecordNotices.js +75 -0
  27. package/dist/components/record/ThumbBar.js +1 -1
  28. package/dist/foundations/copy.d.ts +96 -12
  29. package/dist/foundations/copy.es.js +63 -10
  30. package/dist/foundations/copy.js +55 -7
  31. package/dist/primitives/choice/ComboBox.d.ts +3 -0
  32. package/dist/primitives/choice/ComboBox.js +2 -2
  33. package/dist/primitives/choice/SearchField.d.ts +10 -1
  34. package/dist/primitives/choice/SearchField.js +21 -2
  35. package/dist/primitives/date/Calendar.js +30 -6
  36. package/dist/primitives/date/DatePicker.d.ts +11 -3
  37. package/dist/primitives/date/DatePicker.js +12 -8
  38. package/dist/primitives/date/DateTime.d.ts +35 -0
  39. package/dist/primitives/date/DateTime.js +100 -0
  40. package/dist/primitives/date/index.d.ts +1 -0
  41. package/dist/primitives/date/index.js +4 -2
  42. package/dist/primitives/dialog/Drill.d.ts +90 -0
  43. package/dist/primitives/dialog/Drill.js +172 -0
  44. package/dist/primitives/dialog/index.d.ts +1 -0
  45. package/dist/primitives/dialog/index.js +2 -0
  46. package/dist/primitives/field/DropZone.js +1 -1
  47. package/dist/primitives/field/Field.d.ts +14 -2
  48. package/dist/primitives/field/Field.js +25 -14
  49. package/dist/primitives/field/copy.d.ts +16 -0
  50. package/dist/primitives/field/copy.js +69 -0
  51. package/dist/primitives/field/index.d.ts +2 -0
  52. package/dist/primitives/field/index.js +4 -0
  53. package/dist/primitives/field/note.d.ts +55 -0
  54. package/dist/primitives/field/note.js +117 -0
  55. package/dist/primitives/index.d.ts +1 -0
  56. package/dist/primitives/index.js +1 -0
  57. package/dist/primitives/menu/Menu.d.ts +28 -0
  58. package/dist/primitives/menu/Menu.js +47 -17
  59. package/dist/primitives/notice/Callout.js +1 -1
  60. package/dist/primitives/notice/Flag.d.ts +13 -0
  61. package/dist/primitives/notice/Flag.js +31 -0
  62. package/dist/primitives/notice/index.d.ts +1 -0
  63. package/dist/primitives/notice/index.js +1 -0
  64. package/dist/primitives/overlay/Preview.d.ts +19 -0
  65. package/dist/primitives/overlay/Preview.js +60 -0
  66. package/dist/primitives/overlay/index.d.ts +1 -0
  67. package/dist/primitives/overlay/index.js +3 -1
  68. package/dist/primitives/picker/Picker.d.ts +41 -0
  69. package/dist/primitives/picker/Picker.js +62 -8
  70. package/dist/primitives/table/CellCopy.d.ts +20 -0
  71. package/dist/primitives/table/CellCopy.js +23 -0
  72. package/dist/primitives/table/DataTable.d.ts +55 -1
  73. package/dist/primitives/table/DataTable.js +126 -40
  74. package/dist/primitives/table/EditableTable.d.ts +97 -2
  75. package/dist/primitives/table/EditableTable.js +223 -29
  76. package/dist/primitives/table/SheetTable.d.ts +4 -1
  77. package/dist/primitives/table/SheetTable.js +38 -5
  78. package/dist/primitives/table/index.d.ts +2 -2
  79. package/dist/primitives/table/index.js +1 -1
  80. package/dist/primitives/tabs/Tabs.d.ts +2 -2
  81. package/dist/primitives/tabs/Tabs.js +1 -1
  82. package/dist/primitives/tag/TagsField.d.ts +3 -0
  83. package/dist/primitives/tag/TagsField.js +6 -2
  84. package/dist/primitives/tree/Tree.d.ts +56 -0
  85. package/dist/primitives/tree/Tree.js +74 -0
  86. package/dist/primitives/tree/index.d.ts +1 -0
  87. package/dist/primitives/tree/index.js +3 -0
  88. package/dist/primitives/value/Copyable.d.ts +11 -0
  89. package/dist/primitives/value/Copyable.js +27 -6
  90. package/dist/primitives/value/Fecha.js +10 -1
  91. package/dist/primitives/value/ProgressBar.d.ts +31 -0
  92. package/dist/primitives/value/ProgressBar.js +53 -0
  93. package/dist/primitives/value/Relation.d.ts +6 -1
  94. package/dist/primitives/value/Relation.js +27 -10
  95. package/dist/primitives/value/Totals.d.ts +102 -7
  96. package/dist/primitives/value/Totals.js +52 -2
  97. package/dist/primitives/value/Value.d.ts +0 -37
  98. package/dist/primitives/value/Value.js +0 -39
  99. package/dist/primitives/value/index.d.ts +4 -3
  100. package/dist/primitives/value/index.js +4 -3
  101. package/dist/tokens.css +44 -7
  102. package/dist/views/list/List.d.ts +8 -2
  103. package/dist/views/list/List.js +6 -2
  104. package/dist/views/list/ListView.d.ts +1 -1
  105. package/dist/views/list/ListView.js +34 -8
  106. package/dist/views/list/derive.d.ts +12 -1
  107. package/dist/views/list/derive.js +29 -3
  108. package/dist/views/list/presentations.d.ts +2 -2
  109. package/dist/views/list/presentations.js +5 -3
  110. package/dist/views/list/types.d.ts +13 -1
  111. package/dist/views/record/Record.d.ts +27 -8
  112. package/dist/views/record/Record.js +97 -27
  113. package/dist/views/record/index.d.ts +1 -1
  114. package/package.json +5 -1
  115. package/dist/components/chatter/Chatter.d.ts +0 -161
  116. package/dist/components/chatter/Chatter.js +0 -232
  117. package/dist/components/chatter/index.js +0 -4
  118. /package/dist/components/{chatter → activity}/Timeline.d.ts +0 -0
  119. /package/dist/components/{chatter → activity}/Timeline.js +0 -0
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { AriaLocale } from "../../foundations/aria.js";
4
+ import { useUiCopy } from "../../foundations/copy.js";
5
+ import { byDensity, useDensity } from "../../foundations/density.js";
6
+ import { cn, focusRing, tv } from "../../foundations/variants.js";
7
+ import { Check, ChevronRight } from "lucide-react";
8
+ import { Button as AriaButton, Checkbox as AriaCheckbox, Tree as AriaTree, TreeItem as AriaTreeItem, TreeItemContent, } from "react-aria-components";
9
+ const row = tv({
10
+ extend: focusRing,
11
+ base: "group relative flex min-w-0 cursor-default items-center rounded-control text-meta text-ink -outline-offset-2 select-none forced-color-adjust-none",
12
+ variants: {
13
+ isSelected: {
14
+ true: "bg-primary/10 forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]",
15
+ },
16
+ isHovered: { true: "" },
17
+ },
18
+ compoundVariants: [{ isSelected: false, isHovered: true, class: "bg-ink/4" }],
19
+ });
20
+ /* The chevron column is exactly one indent step wide, so a child's chevron lands under its
21
+ parent's name and the levels read as columns, not as a ragged edge. */
22
+ const chevronButton = tv({
23
+ extend: focusRing,
24
+ base: "inline-flex size-(--spacing-indent) shrink-0 items-center justify-center rounded-mark text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink pressed:bg-ink/10",
25
+ });
26
+ /** Every id of the hierarchy that can be chosen — what «select all» means here. */
27
+ function choosable(items) {
28
+ return items.flatMap((item) => [
29
+ ...(item.disabled === true ? [] : [item.id]),
30
+ ...choosable(item.children ?? []),
31
+ ]);
32
+ }
33
+ /**
34
+ * A hierarchy of records — the chart of accounts, product categories, warehouse locations —
35
+ * with its groups that fold, a figure per row, and one or many rows to choose.
36
+ *
37
+ * Context: the keyboard is React Aria's `Tree`: arrows walk the visible rows, → opens a group
38
+ * and ← folds it (or goes up to the parent), Home and End jump, and typing a name moves to it.
39
+ * Escape does NOT clear the selection: the tree often lives in a dialog or a popover, and
40
+ * Escape there means «close» — clearing the choice on the way out would lose it without a word.
41
+ */
42
+ export function Tree({ items, label, labelledBy, expanded, defaultExpanded, onExpandedChange, selectionMode = "none", selected, defaultSelected, onSelectedChange, onAction, empty, }) {
43
+ const copy = useUiCopy();
44
+ const density = useDensity();
45
+ const pad = byDensity(density, "py-1.5 pe-2", "py-0.5 pe-1.5");
46
+ const many = selectionMode === "multiple";
47
+ const onSelectionChange = (keys) => onSelectedChange?.(keys === "all" ? choosable(items) : [...keys].map(String));
48
+ const render = (node, level) => {
49
+ const children = node.children ?? [];
50
+ const group = children.length > 0;
51
+ const Icon = node.icon;
52
+ /* Each level is one indent step; the first one keeps a sliver of the row's own padding so
53
+ the chevron is not glued to the edge of its tint. */
54
+ const indent = {
55
+ paddingInlineStart: `calc(var(--spacing) * 1 + var(--spacing-indent) * ${level - 1})`,
56
+ };
57
+ return (_jsxs(AriaTreeItem, { id: node.id, textValue: node.label, isDisabled: node.disabled === true, className: ({ isSelected, isHovered, isFocusVisible }) => cn(row({ isSelected, isHovered, isFocusVisible }), pad), style: indent, children: [_jsx(TreeItemContent, { children: ({ isExpanded, isSelected }) => (_jsxs(_Fragment, { children: [group ? (_jsx(AriaButton, { slot: "chevron", className: ({ isFocusVisible }) => chevronButton({ isFocusVisible }), children: _jsx(ChevronRight, { className: `size-(--size-icon-sm) transition-transform duration-(--duration-quick) ease-(--ease-out) ${isExpanded ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true }) })) : (_jsx("span", { "aria-hidden": true, className: "size-(--spacing-indent) shrink-0" })), _jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [many ? _jsx(SelectionCheck, {}) : null, Icon === undefined ? null : (_jsx(Icon, { className: "size-(--size-icon-md) shrink-0 text-muted", strokeWidth: 2, "aria-hidden": true })), _jsx("span", { className: cn("min-w-0 flex-1 truncate", group ? "font-medium" : "", isSelected ? "font-medium" : "", node.disabled === true && selectionMode !== "none" ? "text-muted" : ""), children: node.label }), node.meta === undefined ? null : (_jsx("span", { className: "shrink-0 ps-3 text-right tabular-nums whitespace-nowrap text-muted", children: node.meta })), selectionMode === "single" && isSelected ? (_jsx(Check, { className: "size-(--size-icon-sm) shrink-0 text-primary forced-colors:text-[HighlightText]", strokeWidth: 2.5, "aria-hidden": true })) : null] })] })) }), children.map((child) => render(child, level + 1))] }, node.id));
58
+ };
59
+ return (_jsx(AriaLocale, { children: _jsx(AriaTree, { ...(labelledBy === undefined
60
+ ? { "aria-label": label }
61
+ : { "aria-labelledby": labelledBy }), selectionMode: selectionMode,
62
+ /* One to choose: a click chooses and a double click opens. Many: the click opens and
63
+ the check chooses. See the header. */
64
+ selectionBehavior: selectionMode === "single" ? "replace" : "toggle", disabledBehavior: "selection", escapeKeyBehavior: "none", ...(selected === undefined ? {} : { selectedKeys: selected }), ...(defaultSelected === undefined ? {} : { defaultSelectedKeys: defaultSelected }), ...(expanded === undefined ? {} : { expandedKeys: expanded }), ...(defaultExpanded === undefined ? {} : { defaultExpandedKeys: defaultExpanded }), ...(onExpandedChange === undefined
65
+ ? {}
66
+ : { onExpandedChange: (keys) => onExpandedChange([...keys].map(String)) }), ...(onSelectedChange === undefined ? {} : { onSelectionChange }), ...(onAction === undefined ? {} : { onAction: (key) => onAction(String(key)) }), renderEmptyState: () => (_jsx("p", { className: "px-2 py-3 text-meta text-muted", children: empty ?? copy.tree.empty })), className: "flex w-full min-w-0 flex-col gap-px outline-0", children: items.map((item) => render(item, 1)) }) }));
67
+ }
68
+ /* The check of a row when many can be chosen. It is React Aria's `selection` slot: it toggles
69
+ the row and never opens it. Drawn like `Checkbox`'s box so a check reads as one thing. */
70
+ function SelectionCheck() {
71
+ return (_jsx(AriaCheckbox, { slot: "selection", className: "group/check inline-flex shrink-0 outline-0", children: ({ isSelected, isFocusVisible }) => (_jsx("span", { "aria-hidden": true, className: cn("inline-flex size-4 items-center justify-center rounded-mark border transition-colors duration-(--duration-instant) forced-color-adjust-none", isSelected
72
+ ? "border-primary bg-primary text-surface forced-colors:border-[Highlight] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]"
73
+ : "border-line-strong bg-surface text-transparent forced-colors:border-[ButtonBorder]", focusRing({ isFocusVisible })), children: _jsx(Check, { className: "size-3", strokeWidth: 2.5 }) })) }));
74
+ }
@@ -0,0 +1 @@
1
+ export { Tree, type TreeItem, type TreeProps } from "./Tree.js";
@@ -0,0 +1,3 @@
1
+ /* A hierarchy of records — the chart of accounts, categories, locations: read, folded and
2
+ chosen from. Not the app's menu: that is `NavigationTree`. */
3
+ export { Tree } from "./Tree.js";
@@ -34,4 +34,15 @@ export type CopyableProps = {
34
34
  * el valor ya está dibujado. */
35
35
  bare?: boolean;
36
36
  };
37
+ /**
38
+ * The button alone, for a value that is only known when it is pressed — what a field shows
39
+ * at that moment (`Field`'s copy button reads it from the screen). `Copyable` is this
40
+ * button next to its value.
41
+ */
42
+ export declare function CopyButton({ value, label, }: {
43
+ /** The text, or how to get it at the moment of the press. */
44
+ value: string | (() => string);
45
+ /** Its name at rest. Defaults to the copy table's «Copiar». */
46
+ label?: string;
47
+ }): import("react").JSX.Element;
37
48
  export declare function Copyable({ value, children, mono, bare }: CopyableProps): import("react").JSX.Element;
@@ -6,7 +6,12 @@ import { useEffect, useRef, useState } from "react";
6
6
  /** Cuánto dura el tilde. Dos segundos: alcanza para verlo sin que el botón se
7
7
  * quede en un estado que ya no es cierto. */
8
8
  const CONFIRMED_FOR = 2000;
9
- export function Copyable({ value, children, mono = false, bare = false }) {
9
+ /**
10
+ * The button alone, for a value that is only known when it is pressed — what a field shows
11
+ * at that moment (`Field`'s copy button reads it from the screen). `Copyable` is this
12
+ * button next to its value.
13
+ */
14
+ export function CopyButton({ value, label, }) {
10
15
  const copy = useUiCopy();
11
16
  const [state, setState] = useState("idle");
12
17
  const timer = useRef(null);
@@ -19,8 +24,15 @@ export function Copyable({ value, children, mono = false, bare = false }) {
19
24
  async function run() {
20
25
  if (timer.current !== null)
21
26
  clearTimeout(timer.current);
27
+ const text = typeof value === "function" ? value() : value;
28
+ if (text.trim() === "") {
29
+ /* Nothing on screen to copy: said, not a check mark over an empty clipboard. */
30
+ setState("empty");
31
+ timer.current = setTimeout(() => setState("idle"), CONFIRMED_FOR);
32
+ return;
33
+ }
22
34
  try {
23
- await navigator.clipboard.writeText(value);
35
+ await navigator.clipboard.writeText(text);
24
36
  setState("done");
25
37
  }
26
38
  catch {
@@ -30,16 +42,25 @@ export function Copyable({ value, children, mono = false, bare = false }) {
30
42
  }
31
43
  timer.current = setTimeout(() => setState("idle"), CONFIRMED_FOR);
32
44
  }
33
- const label = state === "done"
45
+ const said = state === "done"
34
46
  ? copy.copyable.done
35
47
  : state === "failed"
36
48
  ? copy.copyable.failed
37
- : copy.copyable.label;
38
- const button = (_jsx("button", { type: "button", onClick: run, "aria-label": label, title: label, className: `inline-flex shrink-0 items-center justify-center rounded-control p-0.5 transition-colors duration-(--duration-instant) ${state === "done"
49
+ : state === "empty"
50
+ ? copy.copyable.empty
51
+ : (label ?? copy.copyable.label);
52
+ return (_jsx("button", { type: "button", onClick: (e) => {
53
+ /* Inside a row that opens or a link: copying is not opening. */
54
+ e.stopPropagation();
55
+ void run();
56
+ }, "aria-label": said, title: said, className: `inline-flex shrink-0 items-center justify-center rounded-control p-0.5 transition-colors duration-(--duration-instant) ${state === "done"
39
57
  ? "text-good"
40
- : state === "failed"
58
+ : state === "failed" || state === "empty"
41
59
  ? "text-bad"
42
60
  : "text-muted hover:bg-ink/6 hover:text-ink"}`, children: state === "done" ? (_jsx(Check, { className: "size-(--size-icon-sm)", strokeWidth: 2.5 })) : (_jsx(Copy, { className: "size-(--size-icon-sm)", strokeWidth: 2 })) }));
61
+ }
62
+ export function Copyable({ value, children, mono = false, bare = false }) {
63
+ const button = _jsx(CopyButton, { value: value });
43
64
  if (bare)
44
65
  return button;
45
66
  return (
@@ -61,12 +61,21 @@ export function formatDateTime(when) {
61
61
  const locale = currentCopyLocale();
62
62
  return `${dateFormat(locale).format(when)} ${timeFormat(locale).format(when)}`;
63
63
  }
64
+ /** A day written as `YYYY-MM-DD` (what a date field stores) is that day HERE. `new Date()`
65
+ * reads it as midnight in UTC, and west of Greenwich that is the evening before: a due date
66
+ * of the 15th was drawn as the 14th in La Paz. Anything else goes through `Date` as is. */
67
+ function toDate(when) {
68
+ const day = typeof when === "string" ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(when) : null;
69
+ if (day === null)
70
+ return new Date(when);
71
+ return new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3]));
72
+ }
64
73
  export function DateValue({ when, format = "relative",
65
74
  /** So it can be tested: without this, a story with «hace 2 min» changes its
66
75
  * text every minute and no snapshot works as a reference. */
67
76
  now, }) {
68
77
  const copy = useUiCopy();
69
- const d = when instanceof Date ? when : new Date(when);
78
+ const d = when instanceof Date ? when : toDate(when);
70
79
  if (Number.isNaN(d.getTime())) {
71
80
  return (_jsx("span", { className: "text-meta text-muted italic", title: copy.time.invalid, children: copy.time.noDate }));
72
81
  }
@@ -0,0 +1,31 @@
1
+ import type { Tone } from "../../foundations/types.js";
2
+ import type { ReactNode } from "react";
3
+ export type ProgressBarProps = {
4
+ /** Where the task is, from `minValue` to `maxValue`. Absent = under way and it cannot say
5
+ * how far: the bar sweeps and draws no figure. */
6
+ value?: number;
7
+ /** What is running: «Importación de clientes», «Cierre de octubre». Always its accessible
8
+ * name; drawn above the bar only with `showLabel`. */
9
+ label: string;
10
+ minValue?: number;
11
+ /** The size of the task — 4000 for an import of 4.000 rows. 100 by default, so a plain
12
+ * percentage needs nothing else. */
13
+ maxValue?: number;
14
+ /** How the figure is written. Absent: the percentage of the task. */
15
+ formatOptions?: Intl.NumberFormatOptions;
16
+ /** The figure written by hand, when a percentage does not say it: «1.240 de 4.000 filas».
17
+ * It is also what a screen reader reads. Indeterminate, it is the only figure drawn —
18
+ * «leyendo el archivo…». */
19
+ valueLabel?: ReactNode;
20
+ /** Draws the label above the bar: the bar a screen is about (an import, the closing of
21
+ * the month). Off in a cell or a row, where the column or the line already says it. */
22
+ showLabel?: boolean;
23
+ /** Off when the figure is already beside it — a cell with its column. */
24
+ showValue?: boolean;
25
+ /** The width of the track (inline) or of the whole piece (with `showLabel`). A bar in a
26
+ * list row is shorter than the bar of an import. */
27
+ width?: string;
28
+ /** The state of the task. `brand` — the gradient — is the long bar of a live process. */
29
+ tone?: Tone | "brand";
30
+ };
31
+ export declare function ProgressBar({ value, label, minValue, maxValue, formatOptions, valueLabel, showLabel, showValue, width, tone, }: ProgressBarProps): import("react").JSX.Element;
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { AriaLocale } from "../../foundations/aria.js";
4
+ import { cn } from "../../foundations/variants.js";
5
+ import { ProgressBar as AriaProgressBar } from "react-aria-components";
6
+ /**
7
+ * A task under way: the import of 4.000 rows, the closing of the month, a file going up. A
8
+ * real bar, never a spinner — a spinner says «wait» and does not say how much; a bar says both.
9
+ *
10
+ * Context: it looks like `Meter` and it is the opposite thing. A meter is a QUANTITY in a
11
+ * range that just is — the stock against the warehouse, the credit used — and full is often
12
+ * the alarm. A progress bar is something HAPPENING, moving toward done, and full is the good
13
+ * news. That is why their color comes from different places: the meter's is the reading of
14
+ * its value; this one's is the state of the task, said by whoever runs it — `good` finished,
15
+ * `warn` is running slow, `bad` failed — and `brand` by default, the blue→gold gradient of
16
+ * the long bar of a live import. (At 6 px and 62 % in a list row a gradient reads as a
17
+ * broken bar, so a short bar there asks for a flat tone.)
18
+ *
19
+ * ── Determinate and indeterminate ─────────────────────────────────────
20
+ *
21
+ * With a `value` it fills to it, and the figure next to it is in tabular figures so a column
22
+ * of bars keeps its digits aligned. Without one the task is under way and cannot say how far
23
+ * — the server has not counted the rows yet — and the bar SAYS so instead of inventing a
24
+ * number: a segment sweeps along the track and no figure is drawn. That sweep is the only
25
+ * loop of the system (`--animate-progress-sweep`), and it runs behind `motion-safe:`: with
26
+ * `prefers-reduced-motion` the segment stands still, the whole track faded — «busy» without
27
+ * movement, and without a fill that could be read as a percentage.
28
+ *
29
+ * React Aria writes `role="progressbar"` and the `aria-value*` — no `aria-valuenow` while it
30
+ * is indeterminate, which is how a screen reader tells the two apart — and formats the figure
31
+ * in the copy table's locale (`AriaLocale`).
32
+ *
33
+ * The track is the system's `--size-track`: at 3 px it was a line that could not be told
34
+ * from a separator; two bars of different thickness on one screen read as two things.
35
+ */
36
+ const FILL = {
37
+ neutral: "bg-muted",
38
+ info: "bg-info",
39
+ good: "bg-good",
40
+ warn: "bg-warn",
41
+ bad: "bg-bad",
42
+ accent: "bg-accent",
43
+ };
44
+ export function ProgressBar({ value, label, minValue = 0, maxValue = 100, formatOptions, valueLabel, showLabel = false, showValue = true, width = "w-24", tone = "brand", }) {
45
+ const fill = tone === "brand" ? "bar-brand" : FILL[tone];
46
+ return (_jsx(AriaLocale, { children: _jsx(AriaProgressBar, { "aria-label": label, ...(value === undefined ? {} : { value }), isIndeterminate: value === undefined, minValue: minValue, maxValue: maxValue, formatOptions: formatOptions, valueLabel: valueLabel, className: showLabel
47
+ ? `flex max-w-full flex-col gap-1.5 ${width}`
48
+ : "flex min-w-0 items-center gap-2", children: ({ percentage, valueText, isIndeterminate }) => {
49
+ const figure = showValue && valueText !== undefined ? (_jsx("span", { className: "shrink-0 text-meta tabular-nums text-muted", children: valueText })) : null;
50
+ const track = (_jsx("span", { className: cn("relative block h-(--size-track) overflow-hidden rounded-pill bg-ink/10 forced-colors:outline forced-colors:outline-[ButtonBorder]", showLabel ? "w-full" : width), children: isIndeterminate ? (_jsx("span", { "data-indeterminate": true, className: cn("absolute inset-y-0 left-0 w-2/5 rounded-pill forced-colors:bg-[Highlight]", "motion-safe:animate-progress-sweep motion-reduce:w-full motion-reduce:opacity-40", fill) })) : (_jsx("span", { className: cn("block h-full rounded-pill transition-[width] duration-(--duration-panel) ease-(--ease-out) forced-colors:bg-[Highlight]", fill), style: { width: `${percentage ?? 0}%` } })) }));
51
+ return showLabel ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "truncate text-meta text-ink", children: label }), figure] }), track] })) : (_jsxs(_Fragment, { children: [track, figure] }));
52
+ } }) }));
53
+ }
@@ -55,6 +55,11 @@ export type RelationValueProps = {
55
55
  href?: string;
56
56
  /** Present = an info button next to the name opens the `RelationCard`. */
57
57
  summary?: RelationSummary;
58
+ /** Floats the summary from the NAME on hover, focus or long press, instead of from the
59
+ * info button. `true` = the `RelationCard` (with `summary` when given); a node = that
60
+ * content instead — the product of a line, a consumer's own card. Needs the name to be a
61
+ * link (`href` or `onOpen`); without one the content opens from the info button. */
62
+ preview?: boolean | ReactNode;
58
63
  /** The control that EDITS this relation — a `Picker` — drawn instead in edit mode. */
59
64
  editor?: ReactNode;
60
65
  /** Forces the mode; by default the form's. Only matters with `editor`. */
@@ -65,5 +70,5 @@ export type RelationValueProps = {
65
70
  * picture, and its summary one press away. Works in a table cell, a list row, a `Record.Facts`
66
71
  * value and a form field (read mode; pass the `Picker` as `editor` for edit mode).
67
72
  */
68
- export declare function RelationValue({ name, secondary, media, onOpen, href, summary, editor, mode, }: RelationValueProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
73
+ export declare function RelationValue({ name, secondary, media, onOpen, href, summary, preview, editor, mode, }: RelationValueProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
69
74
  export {};
@@ -7,6 +7,7 @@ import { CellStack, Thumb } from "../cell/CellStack.js";
7
7
  import { FieldIdsContext } from "../field/context.js";
8
8
  import { useFormMode } from "../field/mode.js";
9
9
  import { PopoverTrigger } from "../overlay/PopoverTrigger.js";
10
+ import { Preview } from "../overlay/Preview.js";
10
11
  import { Tag } from "../tag/Tag.js";
11
12
  import { ArrowRight, Info } from "lucide-react";
12
13
  import { useContext } from "react";
@@ -32,7 +33,7 @@ export function RelationCard({ name, secondary, media, summary = {}, onOpen, hre
32
33
  * picture, and its summary one press away. Works in a table cell, a list row, a `Record.Facts`
33
34
  * value and a form field (read mode; pass the `Picker` as `editor` for edit mode).
34
35
  */
35
- export function RelationValue({ name, secondary, media, onOpen, href, summary, editor, mode, }) {
36
+ export function RelationValue({ name, secondary, media, onOpen, href, summary, preview, editor, mode, }) {
36
37
  const copy = useUiCopy();
37
38
  const formMode = useFormMode(mode);
38
39
  /* Inside a `Field` the value takes the control's height, so a form that goes from
@@ -45,13 +46,29 @@ export function RelationValue({ name, secondary, media, onOpen, href, summary, e
45
46
  return frame(_jsx("span", { className: "text-meta text-muted italic", children: copy.relation.none }));
46
47
  }
47
48
  const goes = href !== undefined || onOpen !== undefined;
48
- const primary = goes ? (_jsx(Link, { ...(href === undefined ? {} : { href }), ...(onOpen === undefined ? {} : { onPress: onOpen }), className: ({ isFocusVisible }) => cn("cursor-pointer rounded-mark text-primary no-underline underline-offset-2 hover:underline", focusRing({ isFocusVisible })), children: name })) : (name);
49
- return frame(_jsxs("span", { className: "inline-flex max-w-full min-w-0 items-center gap-1", children: [_jsx(CellStack, { primary: primary, ...(secondary === undefined ? {} : { secondary }), ...(media === undefined ? {} : { media }) }), summary === undefined ? null : (_jsx(PopoverTrigger, { label: fillCopy(copy.relationCard.summary, { name }), trigger: _jsx(IconButton, { icon: Info, label: fillCopy(copy.relationCard.summary, { name }), size: "sm" }), children: (close) => (_jsx(RelationCard, { name: name, summary: summary, ...(secondary === undefined ? {} : { secondary }), ...(media === undefined ? {} : { media }), ...(href === undefined ? {} : { href }), ...(onOpen === undefined
50
- ? {}
51
- : {
52
- onOpen: () => {
53
- close();
54
- onOpen();
55
- },
56
- }) })) }))] }));
49
+ const summaryLabel = fillCopy(copy.relationCard.summary, { name });
50
+ const card = (open) => (_jsx(RelationCard, { name: name, ...(summary === undefined ? {} : { summary }), ...(secondary === undefined ? {} : { secondary }), ...(media === undefined ? {} : { media }), ...(href === undefined ? {} : { href }), ...(open === undefined ? {} : { onOpen: open }) }));
51
+ /* What floats from the name: the card, or the consumer's own content. */
52
+ const previewed = preview === undefined || preview === null || preview === false
53
+ ? undefined
54
+ : preview === true
55
+ ? card(onOpen)
56
+ : preview;
57
+ const link = goes ? (_jsx(Link, { ...(href === undefined ? {} : { href }), ...(onOpen === undefined ? {} : { onPress: onOpen }), className: ({ isFocusVisible }) => cn("cursor-pointer rounded-mark text-primary no-underline underline-offset-2 hover:underline", focusRing({ isFocusVisible })), children: name })) : undefined;
58
+ const primary = link === undefined ? (name) : previewed === undefined ? (link) : (_jsx(Preview, { trigger: link, label: summaryLabel, children: previewed }));
59
+ /* The `i`: the summary when there is no preview on the name; the preview's content when
60
+ there is no link for it to ride on. */
61
+ const info = previewed !== undefined
62
+ ? goes
63
+ ? undefined
64
+ : () => previewed
65
+ : summary === undefined
66
+ ? undefined
67
+ : (close) => card(onOpen === undefined
68
+ ? undefined
69
+ : () => {
70
+ close();
71
+ onOpen();
72
+ });
73
+ return frame(_jsxs("span", { className: "inline-flex max-w-full min-w-0 items-center gap-1", children: [_jsx(CellStack, { primary: primary, ...(secondary === undefined ? {} : { secondary }), ...(media === undefined ? {} : { media }) }), info === undefined ? null : (_jsx(PopoverTrigger, { label: summaryLabel, trigger: _jsx(IconButton, { icon: Info, label: summaryLabel, size: "sm" }), children: info }))] }));
57
74
  }
@@ -1,7 +1,8 @@
1
- import type { ReactNode } from "react";
1
+ import { type ReactNode } from "react";
2
2
  /**
3
3
  * The money summary of a document — subtotal, taxes, discounts and the TOTAL — under its
4
- * lines, aligned to the right.
4
+ * lines, aligned to the right. And, once the document starts being settled, what was already
5
+ * taken off it, what is still owed, and what could still be taken off.
5
6
  *
6
7
  * Context: written as two more rows of the lines table, «IVA» and «Total» were the same size
7
8
  * and weight as a product line, and the one number the document exists for had to be found.
@@ -10,17 +11,111 @@ import type { ReactNode } from "react";
10
11
  * read (Jakob's law).
11
12
  *
12
13
  * Labels left and figures right of a narrow column, both in tabular figures, so the amounts
13
- * line up digit by digit. The `total` row closes the block: a rule above it, and the figure
14
- * at the title size.
14
+ * line up digit by digit. The `total` row closes the breakdown: a rule above it.
15
+ *
16
+ * ── Deductions, balance and credits: one block, not three ────────────
17
+ *
18
+ * A posted invoice is read as one sum: the total, minus each payment, gives what is owed. Drawn
19
+ * as a second component under this one, the payments would be a second column of figures that
20
+ * the screen has to line up with the first by hand — which a template cannot do without
21
+ * styling — and the decision below, WHICH figure leads, needs to see the three at once. So it
22
+ * is this block, extended, and a block with only `rows` draws exactly what it drew before.
23
+ *
24
+ * It still does not know what an invoice is. It speaks of `deductions` (a payment, a credit
25
+ * note, a retention: anything already applied against the total), a `balance` (what remains
26
+ * after them), `settled` (the word for «nothing remains»: «Pagada», «Conciliado») and
27
+ * `credits` (what could still be applied). The domain formats the money and owns the actions;
28
+ * this only lays them out and names them.
29
+ *
30
+ * ── The figure that leads moves with the state ───────────────────────
31
+ *
32
+ * There is ONE figure at the title size, and which one it is depends on where the document is:
33
+ *
34
+ * nothing applied yet (a draft, a posted invoice) the total
35
+ * something applied, something owed the balance — the total steps down
36
+ * settled the total, with the `settled` mark
37
+ *
38
+ * Once a payment is in, the question the person brings is «how much is left?», and a total
39
+ * at the title size next to a balance at the body size answers the wrong question first.
40
+ * That is the default; `lead` says it explicitly when the screen knows better.
41
+ *
42
+ * ── A deduction is HEARD as one: «Pagado el 12/10/2026, menos Bs. 1.000,00» ──
43
+ *
44
+ * The amount arrives formatted and WITHOUT a sign; the block writes the minus. The visible
45
+ * «−» is hidden from assistive technology — screen readers skip it or say «dash» — and a
46
+ * visually hidden «menos» (`copy.totals.less`) is read in its place, so the label and the
47
+ * figure are one sentence and not two loose numbers.
48
+ *
49
+ * ── The detail of a deduction: an `i`, asked for ─────────────────────
50
+ *
51
+ * Where it came from (journal, reference, method), going to it, and unlinking it open from an
52
+ * `i` before the label — `PopoverTrigger`, the same `i` a `RelationValue` uses for its
53
+ * summary. Not a `Preview` on hover: the card holds an action that changes the document
54
+ * («Desvincular»), and that is asked for with a press, which the mouse, the keyboard and the
55
+ * finger all have. The `i` sits BEFORE the label so the label and its amount stay together
56
+ * in the reading order.
57
+ *
58
+ * ── None of these actions is the primary ─────────────────────────────
59
+ *
60
+ * The screen's one primary is somewhere else («Registrar pago», «Confirmar»). Unlinking is
61
+ * `secondary` and not `danger`: it does not destroy — the payment comes back as a credit and
62
+ * is applied again with one press, which is the undo. «Aplicar» and «Abrir» are `ghost`.
15
63
  */
16
64
  export type TotalsRow = {
17
65
  label: ReactNode;
18
66
  value: ReactNode;
19
67
  /** A note under the label — «incluido en el precio», «sobre Bs. 3.601,80». */
20
68
  detail?: ReactNode;
21
- /** The row the block exists for. Usually one, and last. */
69
+ /** The row that closes the breakdown. Usually one, and last. */
22
70
  total?: boolean;
23
71
  };
24
- export declare function Totals({ rows }: {
72
+ /** A fact of a deduction's detail: «Diario · Banco BNB», «Referencia · TRF-88213». */
73
+ export type TotalsFact = {
74
+ label: string;
75
+ value: ReactNode;
76
+ };
77
+ /** Something already applied against the total: a payment, a credit note, a retention. */
78
+ export type TotalsDeduction = {
79
+ /** What it is, as one phrase — «Pagado el 12/10/2026». A string: it also names the `i`
80
+ * and the floating detail. */
81
+ label: string;
82
+ /** The amount taken off, formatted and WITHOUT a sign: the block writes the minus. */
83
+ value: ReactNode;
84
+ /** A quiet note under the label — «Transferencia · Banco BNB». */
85
+ detail?: ReactNode;
86
+ /** What the `i` shows: two to five facts — journal, reference, method. */
87
+ facts?: readonly TotalsFact[];
88
+ /** Go to the applied record. The detail shows «Abrir». */
89
+ onOpen?: () => void;
90
+ /** Undo the application — «Desvincular». Present = allowed; absent = not offered. */
91
+ onRemove?: () => void;
92
+ };
93
+ /** Something that COULD be applied and is not yet: an unused credit of the same party. */
94
+ export type TotalsCredit = {
95
+ /** What it is — «NC/2026/0012». A string: it also names its «Aplicar». */
96
+ label: string;
97
+ /** The amount available, formatted. */
98
+ value: ReactNode;
99
+ /** A quiet note — «del 02/10/2026», «anticipo». */
100
+ detail?: ReactNode;
101
+ onApply: () => void;
102
+ };
103
+ export type TotalsProps = {
104
+ /** The breakdown and its `total`: subtotal, each tax group, discounts, TOTAL. */
25
105
  rows: readonly TotalsRow[];
26
- }): import("react").JSX.Element;
106
+ /** What was already applied against the total, one row each, after it. */
107
+ deductions?: readonly TotalsDeduction[];
108
+ /** What remains once the deductions are taken off — «Importe adeudado». */
109
+ balance?: Omit<TotalsRow, "total">;
110
+ /** Nothing remains: the word for it, drawn as a mark next to the leading figure —
111
+ * «Pagada». */
112
+ settled?: string;
113
+ /** Which figure leads, at the title size. By default the balance when something was
114
+ * deducted and the document is not settled; otherwise the total. */
115
+ lead?: "total" | "balance";
116
+ /** What could still be applied, apart and under the sum: it is not part of it. */
117
+ credits?: readonly TotalsCredit[];
118
+ /** The title of the credits. `copy.totals.credits` by default. */
119
+ creditsLabel?: string;
120
+ };
121
+ export declare function Totals({ rows, deductions, balance, settled, lead, credits, creditsLabel, }: TotalsProps): import("react").JSX.Element;
@@ -1,4 +1,54 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export function Totals({ rows }) {
3
- return (_jsx("dl", { className: "ml-auto flex w-full max-w-(--container-search) flex-col gap-1.5", children: rows.map((row, i) => row.total === true ? (_jsxs("div", { className: "mt-1 flex items-baseline justify-between gap-4 border-t border-line-strong pt-2.5", children: [_jsx("dt", { className: "text-body font-semibold text-ink", children: row.label }), _jsx("dd", { className: "text-title font-semibold tabular-nums text-ink", children: row.value })] }, i)) : (_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsxs("dt", { className: "flex flex-col text-meta text-muted", children: [row.label, row.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: row.detail }))] }), _jsx("dd", { className: "text-meta tabular-nums text-ink", children: row.value })] }, i))) }));
3
+ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
4
+ import { Button, IconButton } from "../button/Button.js";
5
+ import { PopoverTrigger } from "../overlay/PopoverTrigger.js";
6
+ import { Tag } from "../tag/Tag.js";
7
+ import { ArrowRight, Info, Plus, Unlink } from "lucide-react";
8
+ import { useId } from "react";
9
+ /** The figure the block exists for: a rule above it and the figure at the title size. The
10
+ * `data-lead` marks it for the tests and for whoever reads the DOM. */
11
+ function LeadRow({ row, mark }) {
12
+ return (_jsxs("div", { "data-lead": "", className: "mt-1 flex flex-wrap items-baseline justify-between gap-x-4 border-t border-line-strong pt-2.5", children: [_jsxs("dt", { className: "flex min-w-0 flex-col", children: [_jsxs("span", { className: "flex flex-wrap items-center gap-2 text-body font-semibold text-ink", children: [row.label, mark === undefined ? null : _jsx(Tag, { tone: "good", children: mark })] }), row.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: row.detail }))] }), _jsx("dd", { className: "ml-auto text-title font-semibold whitespace-nowrap tabular-nums text-ink", children: row.value })] }));
13
+ }
14
+ /** A figure that closes a part of the sum without leading it: the total once the balance
15
+ * leads, or the balance while the total does. Ruled, in the body size. */
16
+ function ClosingRow({ row }) {
17
+ return (_jsxs("div", { className: "mt-1 flex flex-wrap items-baseline justify-between gap-x-4 border-t border-line pt-2", children: [_jsxs("dt", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "text-body font-semibold text-ink", children: row.label }), row.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: row.detail }))] }), _jsx("dd", { className: "ml-auto text-body font-semibold whitespace-nowrap tabular-nums text-ink", children: row.value })] }));
18
+ }
19
+ function PlainRow({ row }) {
20
+ return (_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsxs("dt", { className: "flex min-w-0 flex-col text-meta text-muted", children: [row.label, row.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: row.detail }))] }), _jsx("dd", { className: "text-meta whitespace-nowrap tabular-nums text-ink", children: row.value })] }));
21
+ }
22
+ /** What floats from a deduction's `i`: its facts and, when allowed, «Desvincular» and
23
+ * «Abrir». Both close the card first — the row may be gone once the action runs. */
24
+ function DeductionDetail({ item, close }) {
25
+ const copy = useUiCopy();
26
+ const facts = item.facts ?? [];
27
+ const run = (action) => () => {
28
+ close();
29
+ action();
30
+ };
31
+ return (_jsxs("div", { className: "flex w-64 max-w-full flex-col gap-3", children: [facts.length === 0 ? null : (_jsx("dl", { className: "grid grid-cols-2 gap-x-4 gap-y-2", children: facts.map((fact) => (_jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("dt", { className: "truncate text-micro text-muted", children: fact.label }), _jsx("dd", { className: "text-meta tabular-nums break-words text-ink", children: fact.value })] }, fact.label))) })), item.onRemove === undefined && item.onOpen === undefined ? null : (_jsxs("div", { className: "flex flex-wrap justify-end gap-2 border-t border-line pt-2", children: [item.onRemove === undefined ? null : (_jsx(Button, { role: "secondary", size: "sm", icon: Unlink, onClick: run(item.onRemove), children: copy.totals.unlink })), item.onOpen === undefined ? null : (_jsx(Button, { role: "ghost", size: "sm", icon: ArrowRight, onClick: run(item.onOpen), children: copy.totals.open }))] }))] }));
32
+ }
33
+ function DeductionRow({ item }) {
34
+ const copy = useUiCopy();
35
+ const asks = (item.facts !== undefined && item.facts.length > 0) ||
36
+ item.onRemove !== undefined ||
37
+ item.onOpen !== undefined;
38
+ return (_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsxs("dt", { className: "flex min-w-0 items-baseline gap-1 text-meta text-muted", children: [asks ? (_jsx(PopoverTrigger, { title: item.label, placement: "bottom end", trigger: _jsx(IconButton, { icon: Info, label: fillCopy(copy.totals.details, { label: item.label }) }), children: (close) => _jsx(DeductionDetail, { item: item, close: close }) })) : null, _jsxs("span", { className: "flex min-w-0 flex-col", children: [item.label, item.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: item.detail }))] })] }), _jsxs("dd", { className: "text-meta whitespace-nowrap tabular-nums text-ink", children: [_jsx("span", { "aria-hidden": true, children: "− " }), _jsxs("span", { className: "sr-only", children: [copy.totals.less, " "] }), item.value] })] }));
39
+ }
40
+ function Credits({ items, label }) {
41
+ const copy = useUiCopy();
42
+ const id = useId();
43
+ return (_jsxs("div", { className: "flex flex-col gap-1.5 border-t border-line pt-3", children: [_jsx("p", { id: id, className: "text-micro font-semibold text-muted", children: label }), _jsx("ul", { "aria-labelledby": id, className: "flex flex-col gap-1", children: items.map((credit, i) => (_jsxs("li", { className: "flex flex-wrap items-center justify-between gap-x-3 gap-y-1", children: [_jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "text-meta text-ink", children: credit.label }), credit.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: credit.detail }))] }), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [_jsx("span", { className: "text-meta whitespace-nowrap tabular-nums text-ink", children: credit.value }), _jsx(Button, { role: "ghost", size: "sm", icon: Plus, "aria-label": fillCopy(copy.totals.applyCredit, { label: credit.label }), onClick: credit.onApply, children: copy.totals.apply })] })] }, i))) })] }));
44
+ }
45
+ export function Totals({ rows, deductions = [], balance, settled, lead, credits = [], creditsLabel, }) {
46
+ const copy = useUiCopy();
47
+ const leads = lead ??
48
+ (balance !== undefined && deductions.length > 0 && settled === undefined
49
+ ? "balance"
50
+ : "total");
51
+ /* With nothing to lead it, the balance cannot lead: the total keeps the title size. */
52
+ const balanceLeads = leads === "balance" && balance !== undefined;
53
+ return (_jsxs("div", { className: "ml-auto flex w-full max-w-(--container-search) flex-col gap-3", children: [_jsxs("dl", { className: "flex flex-col gap-1.5", children: [rows.map((row, i) => row.total === true ? (balanceLeads ? (_jsx(ClosingRow, { row: row }, i)) : (_jsx(LeadRow, { row: row, ...(settled === undefined ? {} : { mark: settled }) }, i))) : (_jsx(PlainRow, { row: row }, i))), deductions.map((item, i) => (_jsx(DeductionRow, { item: item }, i))), balance === undefined ? null : balanceLeads ? (_jsx(LeadRow, { row: balance, ...(settled === undefined ? {} : { mark: settled }) })) : (_jsx(ClosingRow, { row: balance }))] }), credits.length === 0 ? null : (_jsx(Credits, { items: credits, label: creditsLabel ?? copy.totals.credits }))] }));
4
54
  }
@@ -1,4 +1,3 @@
1
- import type { Tone } from "../../foundations/types.js";
2
1
  import type { ReactNode } from "react";
3
2
  export declare const formatCount: (value: number, locale?: string) => string;
4
3
  export declare const formatNumber: (value: number, locale?: string) => string;
@@ -21,42 +20,6 @@ export declare function Numeric({ value, unit, format, }: {
21
20
  unit?: string;
22
21
  format?: "integer" | "decimal" | "distance";
23
22
  }): import("react").JSX.Element;
24
- /**
25
- * A real bar, never a spinner.
26
- *
27
- * Context: a spinner says «wait» and doesn't say how much; a bar says both
28
- * things.
29
- *
30
- * ── Why the fill is FLAT and the gradient is asked for ──────────────
31
- *
32
- * The bar always lived with `bar-brand`, the blue→gold gradient. At 3 px and at
33
- * 62 % that doesn't read as a brand: it reads as a blue bar with a broken tip.
34
- * A gradient needs length to be a gradient, and a bar inside a list row doesn't
35
- * have it.
36
- *
37
- * Now the fill is a flat tone —and the tone SAYS something: `good` finished,
38
- * `warn` is running slow, `bad` failed— and the gradient is asked for with
39
- * `tone="brand"`, which is the long bar of a live import: there it does have room
40
- * to be read.
41
- *
42
- * And it got fatter, from 3 px to 6 px. At 3 px it isn't a bar, it's a line: it
43
- * can't be told apart from a separator and the fill isn't visible unless it goes
44
- * past 20 %.
45
- */
46
- export declare function ProgressBar({ value, label, width, tone,
47
- /** The percentage next to it. It is turned off when the bar lives inside
48
- * something that already says the figure —a cell with its column— and it
49
- * would only mean reading it twice. */
50
- showValue, }: {
51
- /** 0 to 100. */
52
- value: number;
53
- label: string;
54
- /** The activity bar wants to be longer than a list row; there is no reason for
55
- * it to be the same number on both sides. */
56
- width?: string;
57
- tone?: Tone | "brand";
58
- showValue?: boolean;
59
- }): import("react").JSX.Element;
60
23
  /**
61
24
  * The content of a data cell, with what the system decides and the view does not.
62
25
  *