@tensor_1/react 2.20.0 → 2.22.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 (65) hide show
  1. package/dist/{ChartCore-Do6gRBN3.d.cts → ChartCore-DzWe10bN.d.cts} +16 -1
  2. package/dist/chart-preset.cjs +25 -13
  3. package/dist/chart-preset.d.cts +2 -2
  4. package/dist/chart-preset.d.ts +1 -1
  5. package/dist/chart-preset.js +2 -1
  6. package/dist/chart.cjs +25 -13
  7. package/dist/chart.d.cts +2 -2
  8. package/dist/chart.d.ts +1 -1
  9. package/dist/chart.js +2 -1
  10. package/dist/components/AppShell.d.ts +5 -1
  11. package/dist/components/AppShell.js +4 -2
  12. package/dist/components/Avatar.d.ts +4 -2
  13. package/dist/components/Avatar.js +2 -2
  14. package/dist/components/BrandProfiles.d.ts +29 -2
  15. package/dist/components/BrandProfiles.js +45 -20
  16. package/dist/components/BuilderCanvas.d.ts +27 -2
  17. package/dist/components/BuilderCanvas.js +45 -22
  18. package/dist/components/Chart.d.ts +1 -1
  19. package/dist/components/Chart.js +2 -1
  20. package/dist/components/ChartCore.d.ts +17 -2
  21. package/dist/components/ChartCore.js +24 -13
  22. package/dist/components/ColorPicker.d.ts +24 -2
  23. package/dist/components/ColorPicker.js +38 -20
  24. package/dist/components/CommandPalette.d.ts +3 -1
  25. package/dist/components/CommandPalette.js +2 -2
  26. package/dist/components/DateRangePicker.d.ts +21 -2
  27. package/dist/components/DateRangePicker.js +26 -6
  28. package/dist/components/DetailPage.d.ts +22 -3
  29. package/dist/components/DetailPage.js +33 -3
  30. package/dist/components/DiffViewer.d.ts +34 -3
  31. package/dist/components/DiffViewer.js +60 -40
  32. package/dist/components/Drawer.d.ts +3 -1
  33. package/dist/components/Drawer.js +2 -2
  34. package/dist/components/Inbox.d.ts +3 -1
  35. package/dist/components/Inbox.js +2 -2
  36. package/dist/components/MatchReview.d.ts +49 -2
  37. package/dist/components/MatchReview.js +72 -30
  38. package/dist/components/Meter.d.ts +3 -1
  39. package/dist/components/Meter.js +3 -2
  40. package/dist/components/Pagination.d.ts +10 -2
  41. package/dist/components/Pagination.js +11 -4
  42. package/dist/components/Peek.d.ts +7 -1
  43. package/dist/components/Peek.js +4 -4
  44. package/dist/components/RelationshipGraph.js +6 -2
  45. package/dist/components/RuleBuilder.d.ts +42 -2
  46. package/dist/components/RuleBuilder.js +71 -37
  47. package/dist/components/SchedulingCalendar.d.ts +25 -2
  48. package/dist/components/SchedulingCalendar.js +38 -15
  49. package/dist/components/Stat.d.ts +5 -0
  50. package/dist/components/Stat.js +2 -2
  51. package/dist/components/Table.d.ts +11 -3
  52. package/dist/components/Table.js +5 -8
  53. package/dist/components/Tabs.d.ts +8 -1
  54. package/dist/components/Tabs.js +4 -5
  55. package/dist/components/Toast.d.ts +6 -2
  56. package/dist/components/Toast.js +4 -4
  57. package/dist/components/Tree.d.ts +9 -1
  58. package/dist/components/Tree.js +55 -8
  59. package/dist/fill-label.d.ts +7 -0
  60. package/dist/fill-label.js +9 -0
  61. package/dist/index.cjs +765 -463
  62. package/dist/index.d.cts +341 -28
  63. package/dist/index.d.ts +10 -10
  64. package/dist/index.js +18 -9
  65. package/package.json +1 -1
@@ -1,16 +1,46 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment } from "react";
2
3
  import { cx } from "../cx.js";
3
4
  import { DetailLayout } from "./DetailLayout.js";
4
5
  import { PageHeader } from "./PageHeader.js";
5
- function DetailPage({ back, eyebrow, title, subtitle, actions, tabs, metrics, side, children, tabsLabel, className, ...props }) {
6
+ const plainLink = ({ href, className, children, onClick, ...rest }) => /* @__PURE__ */ jsx("a", { href, className, onClick, ...rest, children });
7
+ const isPlainLeftClick = (e) => e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
8
+ function DetailPage({
9
+ back,
10
+ eyebrow,
11
+ title,
12
+ subtitle,
13
+ actions,
14
+ tabs,
15
+ metrics,
16
+ side,
17
+ children,
18
+ tabsLabel,
19
+ renderLink = plainLink,
20
+ onTabSelect,
21
+ className,
22
+ ...props
23
+ }) {
6
24
  return /* @__PURE__ */ jsxs("section", { className: cx("uix-detail-page", className), ...props, children: [
7
- back && /* @__PURE__ */ jsx("a", { className: "uix-link uix-link--quiet uix-detail-page__back", href: back.href, children: back.label }),
25
+ back && renderLink({ href: back.href, className: "uix-link uix-link--quiet uix-detail-page__back", children: back.label }),
8
26
  /* @__PURE__ */ jsx(PageHeader, { eyebrow, title, subtitle, actions }),
9
27
  metrics && metrics.length > 0 && /* @__PURE__ */ jsx("dl", { className: "uix-detail-page__metrics", children: metrics.map((metric) => /* @__PURE__ */ jsxs("div", { children: [
10
28
  /* @__PURE__ */ jsx("dt", { children: metric.label }),
11
29
  /* @__PURE__ */ jsx("dd", { children: metric.value })
12
30
  ] }, metric.id)) }),
13
- tabs && tabs.length > 0 && /* @__PURE__ */ jsx("nav", { className: "uix-tabs", "aria-label": tabsLabel, children: tabs.map((tab) => /* @__PURE__ */ jsx("a", { className: "uix-tab", "aria-current": tab.active ? "page" : void 0, href: tab.href, children: tab.label }, tab.id)) }),
31
+ tabs && tabs.length > 0 && /* @__PURE__ */ jsx("nav", { className: "uix-tabs", "aria-label": tabsLabel, children: tabs.map((tab) => /* @__PURE__ */ jsx(Fragment, { children: renderLink({
32
+ href: tab.href,
33
+ className: "uix-tab",
34
+ children: tab.label,
35
+ ...tab.active ? { "aria-current": "page" } : {},
36
+ ...onTabSelect ? {
37
+ onClick: (e) => {
38
+ if (!isPlainLeftClick(e)) return;
39
+ e.preventDefault();
40
+ onTabSelect(tab.id);
41
+ }
42
+ } : {}
43
+ }) }, tab.id)) }),
14
44
  /* @__PURE__ */ jsx(DetailLayout, { side, children })
15
45
  ] });
16
46
  }
@@ -2,6 +2,37 @@ import * as react from 'react';
2
2
  import { JsonValue } from '../json-value.js';
3
3
  import { DiffEntry, DiffResolution } from '../diff-model.js';
4
4
 
5
+ /**
6
+ * Every word the diff viewer renders (TENSOR RX-125, UIX-12). `base` / `current` /
7
+ * `incoming` keep their meaning from before; `{path}`, `{version}`, `{resolved}` and
8
+ * `{total}` are placeholders.
9
+ */
10
+ interface DiffViewerLabels {
11
+ base: string;
12
+ current: string;
13
+ incoming: string;
14
+ region: string;
15
+ summary: string;
16
+ noDifferences: string;
17
+ noDifferencesDetail: string;
18
+ conflicted: string;
19
+ changed: string;
20
+ added: string;
21
+ removed: string;
22
+ resolved: string;
23
+ pending: string;
24
+ valueFor: string;
25
+ resolve: string;
26
+ acceptIncoming: string;
27
+ keepCurrent: string;
28
+ markPending: string;
29
+ resolutionAccept: string;
30
+ resolutionSkip: string;
31
+ resolutionPending: string;
32
+ notPresent: string;
33
+ progress: string;
34
+ }
35
+ declare const DEFAULT_DIFF_VIEWER_LABELS: DiffViewerLabels;
5
36
  interface DiffViewerProps {
6
37
  base?: JsonValue;
7
38
  current?: JsonValue;
@@ -9,10 +40,10 @@ interface DiffViewerProps {
9
40
  entries?: DiffEntry[];
10
41
  resolutions?: Record<string, DiffResolution>;
11
42
  onResolutionChange?: (path: string, resolution: DiffResolution) => void;
12
- labels?: Partial<Record<'base' | 'current' | 'incoming', string>>;
43
+ labels?: Partial<DiffViewerLabels>;
13
44
  className?: string;
14
45
  }
15
46
  /** Three-way configuration diff with per-entry keyboard-operable resolution. */
16
- declare function DiffViewer({ base, current, incoming, entries: providedEntries, resolutions: controlledResolutions, onResolutionChange, labels, className, }: DiffViewerProps): react.JSX.Element;
47
+ declare function DiffViewer({ base, current, incoming, entries: providedEntries, resolutions: controlledResolutions, onResolutionChange, labels: labelOverrides, className, }: DiffViewerProps): react.JSX.Element;
17
48
 
18
- export { DiffViewer, type DiffViewerProps };
49
+ export { DEFAULT_DIFF_VIEWER_LABELS, DiffViewer, type DiffViewerLabels, type DiffViewerProps };
@@ -4,14 +4,34 @@ import { useId, useMemo, useState } from "react";
4
4
  import { buildThreeWayDiff, summarizeDiff } from "../diff-model.js";
5
5
  import { cx } from "../cx.js";
6
6
  import { StatusPill } from "./StatusPill.js";
7
- const GROUPS = [
8
- { kind: "conflicted", label: "Conflicted" },
9
- { kind: "changed", label: "Changed" },
10
- { kind: "added", label: "Added" },
11
- { kind: "removed", label: "Removed" }
12
- ];
13
- const DEFAULT_LABELS = {};
14
- const renderValue = (value) => value === void 0 ? "Not present" : JSON.stringify(value, null, 2);
7
+ import { fillLabel } from "../fill-label.js";
8
+ const DEFAULT_DIFF_VIEWER_LABELS = {
9
+ base: "Base",
10
+ current: "Current",
11
+ incoming: "Incoming",
12
+ region: "Configuration differences",
13
+ summary: "Difference summary",
14
+ noDifferences: "No differences",
15
+ noDifferencesDetail: "Base, current, and incoming values are aligned.",
16
+ conflicted: "Conflicted",
17
+ changed: "Changed",
18
+ added: "Added",
19
+ removed: "Removed",
20
+ resolved: "resolved",
21
+ pending: "pending",
22
+ valueFor: "{version} value for {path}",
23
+ resolve: "Resolve {path}",
24
+ acceptIncoming: "Accept incoming",
25
+ keepCurrent: "Keep current",
26
+ markPending: "Mark pending",
27
+ resolutionAccept: "accepted",
28
+ resolutionSkip: "kept",
29
+ resolutionPending: "pending",
30
+ notPresent: "Not present",
31
+ progress: "{resolved} of {total} differences resolved."
32
+ };
33
+ const GROUPS = ["conflicted", "changed", "added", "removed"];
34
+ const renderValue = (value, notPresent) => value === void 0 ? notPresent : JSON.stringify(value, null, 2);
15
35
  function DiffViewer({
16
36
  base,
17
37
  current,
@@ -19,9 +39,11 @@ function DiffViewer({
19
39
  entries: providedEntries,
20
40
  resolutions: controlledResolutions,
21
41
  onResolutionChange,
22
- labels = DEFAULT_LABELS,
42
+ labels: labelOverrides,
23
43
  className
24
44
  }) {
45
+ const labels = { ...DEFAULT_DIFF_VIEWER_LABELS, ...labelOverrides };
46
+ const resolutionLabel = { accept: labels.resolutionAccept, skip: labels.resolutionSkip, pending: labels.resolutionPending };
25
47
  const id = useId();
26
48
  const entries = useMemo(() => providedEntries ?? buildThreeWayDiff(base, current, incoming), [providedEntries, base, current, incoming]);
27
49
  const [internalResolutions, setInternalResolutions] = useState({});
@@ -31,32 +53,34 @@ function DiffViewer({
31
53
  if (controlledResolutions === void 0) setInternalResolutions((currentState) => ({ ...currentState, [path]: resolution }));
32
54
  onResolutionChange?.(path, resolution);
33
55
  };
34
- if (entries.length === 0) return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": "Configuration differences", children: [
35
- /* @__PURE__ */ jsx("h3", { children: "No differences" }),
36
- /* @__PURE__ */ jsx("p", { children: "Base, current, and incoming values are aligned." })
56
+ if (entries.length === 0) return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": labels.region, children: [
57
+ /* @__PURE__ */ jsx("h3", { children: labels.noDifferences }),
58
+ /* @__PURE__ */ jsx("p", { children: labels.noDifferencesDetail })
37
59
  ] });
38
- return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer", className), "aria-label": "Configuration differences", children: [
39
- /* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__summary", "aria-label": "Difference summary", children: [
40
- GROUPS.map((group) => /* @__PURE__ */ jsxs("span", { children: [
41
- /* @__PURE__ */ jsx("b", { children: summary[group.kind] }),
60
+ return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer", className), "aria-label": labels.region, children: [
61
+ /* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__summary", "aria-label": labels.summary, children: [
62
+ GROUPS.map((kind) => /* @__PURE__ */ jsxs("span", { children: [
63
+ /* @__PURE__ */ jsx("b", { children: summary[kind] }),
42
64
  " ",
43
- group.label.toLowerCase()
44
- ] }, group.kind)),
65
+ labels[kind].toLowerCase()
66
+ ] }, kind)),
45
67
  /* @__PURE__ */ jsxs("span", { children: [
46
68
  /* @__PURE__ */ jsx("b", { children: summary.resolved }),
47
- " resolved"
69
+ " ",
70
+ labels.resolved
48
71
  ] }),
49
72
  /* @__PURE__ */ jsxs("span", { children: [
50
73
  /* @__PURE__ */ jsx("b", { children: summary.pending }),
51
- " pending"
74
+ " ",
75
+ labels.pending
52
76
  ] })
53
77
  ] }),
54
- GROUPS.map((group) => {
55
- const grouped = entries.filter((entry) => entry.kind === group.kind);
78
+ GROUPS.map((kind) => {
79
+ const grouped = entries.filter((entry) => entry.kind === kind);
56
80
  if (!grouped.length) return null;
57
- return /* @__PURE__ */ jsxs("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${group.kind}`, children: [
58
- /* @__PURE__ */ jsxs("h3", { id: `${id}-${group.kind}`, children: [
59
- group.label,
81
+ return /* @__PURE__ */ jsxs("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${kind}`, children: [
82
+ /* @__PURE__ */ jsxs("h3", { id: `${id}-${kind}`, children: [
83
+ labels[kind],
60
84
  " ",
61
85
  /* @__PURE__ */ jsx("span", { children: grouped.length })
62
86
  ] }),
@@ -65,29 +89,25 @@ function DiffViewer({
65
89
  return /* @__PURE__ */ jsxs("details", { className: "uix-diff-viewer__entry", "data-kind": entry.kind, open: entry.kind === "conflicted", children: [
66
90
  /* @__PURE__ */ jsxs("summary", { children: [
67
91
  /* @__PURE__ */ jsx("code", { children: entry.path }),
68
- /* @__PURE__ */ jsx(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolution })
92
+ /* @__PURE__ */ jsx(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolutionLabel[resolution] })
69
93
  ] }),
70
- /* @__PURE__ */ jsx("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ jsxs("div", { role: "region", "aria-label": `${labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}`} value for ${entry.path}`, children: [
71
- /* @__PURE__ */ jsx("strong", { children: labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}` }),
72
- /* @__PURE__ */ jsx("pre", { children: renderValue(entry[version]) })
94
+ /* @__PURE__ */ jsx("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ jsxs("div", { role: "region", "aria-label": fillLabel(labels.valueFor, { version: labels[version], path: entry.path }), children: [
95
+ /* @__PURE__ */ jsx("strong", { children: labels[version] }),
96
+ /* @__PURE__ */ jsx("pre", { children: renderValue(entry[version], labels.notPresent) })
73
97
  ] }, version)) }),
74
- /* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__actions", "aria-label": `Resolve ${entry.path}`, children: [
75
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve(entry.path, "accept"), children: "Accept incoming" }),
76
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve(entry.path, "skip"), children: "Keep current" }),
77
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve(entry.path, "pending"), children: "Mark pending" })
98
+ /* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__actions", "aria-label": fillLabel(labels.resolve, { path: entry.path }), children: [
99
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve(entry.path, "accept"), children: labels.acceptIncoming }),
100
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve(entry.path, "skip"), children: labels.keepCurrent }),
101
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve(entry.path, "pending"), children: labels.markPending })
78
102
  ] })
79
103
  ] }, entry.path);
80
104
  })
81
- ] }, group.kind);
105
+ ] }, kind);
82
106
  }),
83
- /* @__PURE__ */ jsxs("p", { className: "uix-visually-hidden", "aria-live": "polite", children: [
84
- summary.resolved,
85
- " of ",
86
- entries.length,
87
- " differences resolved."
88
- ] })
107
+ /* @__PURE__ */ jsx("p", { className: "uix-visually-hidden", "aria-live": "polite", children: fillLabel(labels.progress, { resolved: summary.resolved, total: entries.length }) })
89
108
  ] });
90
109
  }
91
110
  export {
111
+ DEFAULT_DIFF_VIEWER_LABELS,
92
112
  DiffViewer
93
113
  };
@@ -2,12 +2,14 @@ import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
3
 
4
4
  interface DrawerProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
5
+ /** TENSOR RX-125 (UIX-04): translatable; English default. */
6
+ closeLabel?: string;
5
7
  open: boolean;
6
8
  onClose?: () => void;
7
9
  title?: ReactNode;
8
10
  children?: ReactNode;
9
11
  footer?: ReactNode;
10
12
  }
11
- declare function Drawer({ open, onClose, title, children, footer, className, ...rest }: DrawerProps): react.JSX.Element;
13
+ declare function Drawer({ open, onClose, title, children, footer, closeLabel, className, ...rest }: DrawerProps): react.JSX.Element;
12
14
 
13
15
  export { Drawer, type DrawerProps };
@@ -4,7 +4,7 @@ import { useId } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { useDialog } from "../hooks/useDialog.js";
6
6
  const CloseIcon = () => /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4 4l8 8M12 4l-8 8" }) });
7
- function Drawer({ open, onClose, title, children, footer, className, ...rest }) {
7
+ function Drawer({ open, onClose, title, children, footer, closeLabel = "Close drawer", className, ...rest }) {
8
8
  const ref = useDialog(open, onClose);
9
9
  const titleId = useId();
10
10
  return /* @__PURE__ */ jsxs("dialog", { ref, className: cx("uix-drawer", className), "aria-labelledby": title ? titleId : void 0, ...rest, children: [
@@ -15,7 +15,7 @@ function Drawer({ open, onClose, title, children, footer, className, ...rest })
15
15
  {
16
16
  style: { marginLeft: "auto", border: 0, background: "transparent", color: "var(--uix-text-muted)", cursor: "pointer", width: 30, height: 30, display: "grid", placeItems: "center", borderRadius: "var(--uix-radius-sm)" },
17
17
  onClick: onClose,
18
- "aria-label": "Close drawer",
18
+ "aria-label": closeLabel,
19
19
  children: /* @__PURE__ */ jsx(CloseIcon, {})
20
20
  }
21
21
  )
@@ -13,6 +13,8 @@ interface InboxProps extends HTMLAttributes<HTMLDivElement> {
13
13
  /** Two-pane inbox master-detail container backed by `.uix-inbox`. Place `<InboxList>` and `<InboxDetail>` as children. */
14
14
  declare function Inbox({ view, children, className, ...props }: InboxProps): react.JSX.Element;
15
15
  interface InboxListProps extends HTMLAttributes<HTMLDivElement> {
16
+ /** TENSOR RX-125 (UIX-04): translatable; English default. */
17
+ listLabel?: string;
16
18
  children?: ReactNode;
17
19
  }
18
20
  /**
@@ -20,7 +22,7 @@ interface InboxListProps extends HTMLAttributes<HTMLDivElement> {
20
22
  * (UIX-A11Y-2): the list is the single tab stop; ArrowUp/ArrowDown/Home/End move the
21
23
  * active option via `aria-activedescendant`, Enter/Space click it.
22
24
  */
23
- declare function InboxList({ children, className, ...props }: InboxListProps): react.JSX.Element;
25
+ declare function InboxList({ children, listLabel, className, ...props }: InboxListProps): react.JSX.Element;
24
26
  interface InboxItemProps extends HTMLAttributes<HTMLDivElement> {
25
27
  /** Primary subject / title line. */
26
28
  subject?: ReactNode;
@@ -6,7 +6,7 @@ function Inbox({ view, children, className, ...props }) {
6
6
  return /* @__PURE__ */ jsx("div", { className: cx("uix-inbox", className), "data-view": view, ...props, children });
7
7
  }
8
8
  const InboxListCtx = createContext({});
9
- function InboxList({ children, className, ...props }) {
9
+ function InboxList({ children, listLabel = "Inbox", className, ...props }) {
10
10
  const listRef = useRef(null);
11
11
  const [activeId, setActiveId] = useState(void 0);
12
12
  const ctx = useMemo(() => ({ activeId }), [activeId]);
@@ -51,7 +51,7 @@ function InboxList({ children, className, ...props }) {
51
51
  className: cx("uix-inbox__list", className),
52
52
  role: "listbox",
53
53
  tabIndex: 0,
54
- "aria-label": "Inbox",
54
+ "aria-label": listLabel,
55
55
  "aria-activedescendant": activeId,
56
56
  onKeyDown,
57
57
  ...props,
@@ -19,6 +19,52 @@ interface MatchBulkResult {
19
19
  succeeded: number;
20
20
  failed?: number;
21
21
  }
22
+ /**
23
+ * Every word the match review renders or announces (TENSOR RX-125, UIX-12). Plural
24
+ * pairs are `…One` / `…Many`; `{count}`, `{pending}`, `{total}`, `{id}` are placeholders.
25
+ */
26
+ interface MatchReviewLabels {
27
+ region: string;
28
+ loading: string;
29
+ retry: string;
30
+ empty: string;
31
+ emptyDetail: string;
32
+ pendingOf: string;
33
+ bulk: string;
34
+ selectedCount: string;
35
+ applying: string;
36
+ acceptSelected: string;
37
+ dismissSelected: string;
38
+ caption: string;
39
+ select: string;
40
+ confidence: string;
41
+ status: string;
42
+ actions: string;
43
+ selectCandidate: string;
44
+ notScored: string;
45
+ resolved: string;
46
+ pending: string;
47
+ decisionAccepted: string;
48
+ decisionDismissed: string;
49
+ decisionSplit: string;
50
+ decisionOther: string;
51
+ accept: string;
52
+ dismiss: string;
53
+ split: string;
54
+ pickOther: string;
55
+ acceptingOne: string;
56
+ acceptingMany: string;
57
+ dismissingOne: string;
58
+ dismissingMany: string;
59
+ acceptedOne: string;
60
+ acceptedMany: string;
61
+ dismissedOne: string;
62
+ dismissedMany: string;
63
+ failedSuffix: string;
64
+ bulkAcceptFailed: string;
65
+ bulkDismissFailed: string;
66
+ }
67
+ declare const DEFAULT_MATCH_REVIEW_LABELS: MatchReviewLabels;
22
68
  interface MatchReviewProps {
23
69
  incoming: Record<string, JsonValue>;
24
70
  incomingLabel?: string;
@@ -32,8 +78,9 @@ interface MatchReviewProps {
32
78
  error?: string;
33
79
  onRetry?: () => void;
34
80
  className?: string;
81
+ labels?: Partial<MatchReviewLabels>;
35
82
  }
36
83
  /** Generic descriptor-driven incoming-record to candidate-match decision surface. */
37
- declare function MatchReview({ incoming, incomingLabel, fields, candidates, selectedIds, onSelectionChange, onDecision, onBulkDecision, loading, error, onRetry, className, }: MatchReviewProps): react.JSX.Element;
84
+ declare function MatchReview({ incoming, incomingLabel, fields, candidates, selectedIds, onSelectionChange, onDecision, onBulkDecision, loading, error, onRetry, className, labels: labelOverrides, }: MatchReviewProps): react.JSX.Element;
38
85
 
39
- export { type MatchBulkResult, MatchReview, type MatchReviewAction, type MatchReviewCandidate, type MatchReviewField, type MatchReviewProps };
86
+ export { DEFAULT_MATCH_REVIEW_LABELS, type MatchBulkResult, MatchReview, type MatchReviewAction, type MatchReviewCandidate, type MatchReviewField, type MatchReviewLabels, type MatchReviewProps };
@@ -3,6 +3,48 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useMemo, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { StatusPill } from "./StatusPill.js";
6
+ import { fillLabel } from "../fill-label.js";
7
+ const DEFAULT_MATCH_REVIEW_LABELS = {
8
+ region: "Match review",
9
+ loading: "Loading match candidates\u2026",
10
+ retry: "Try again",
11
+ empty: "No candidate matches found.",
12
+ emptyDetail: "Choose another record source or continue without a match.",
13
+ pendingOf: "{pending} pending of {total}",
14
+ bulk: "Bulk candidate actions",
15
+ selectedCount: "{count} selected",
16
+ applying: "Applying\u2026",
17
+ acceptSelected: "Accept selected",
18
+ dismissSelected: "Dismiss selected",
19
+ caption: "Candidate matches and available decisions",
20
+ select: "Select",
21
+ confidence: "Confidence",
22
+ status: "Status",
23
+ actions: "Actions",
24
+ selectCandidate: "Select candidate {id}",
25
+ notScored: "Not scored",
26
+ resolved: "Resolved",
27
+ pending: "Pending",
28
+ decisionAccepted: "Accepted",
29
+ decisionDismissed: "Dismissed",
30
+ decisionSplit: "Split",
31
+ decisionOther: "Other match",
32
+ accept: "Accept",
33
+ dismiss: "Dismiss",
34
+ split: "Split",
35
+ pickOther: "Pick other",
36
+ acceptingOne: "Accepting {count} candidate\u2026",
37
+ acceptingMany: "Accepting {count} candidates\u2026",
38
+ dismissingOne: "Dismissing {count} candidate\u2026",
39
+ dismissingMany: "Dismissing {count} candidates\u2026",
40
+ acceptedOne: "{count} candidate accepted",
41
+ acceptedMany: "{count} candidates accepted",
42
+ dismissedOne: "{count} candidate dismissed",
43
+ dismissedMany: "{count} candidates dismissed",
44
+ failedSuffix: "; {count} failed",
45
+ bulkAcceptFailed: "Bulk accept failed. Try again.",
46
+ bulkDismissFailed: "Bulk dismiss failed. Try again."
47
+ };
6
48
  const valueText = (value) => value == null ? "\u2014" : typeof value === "string" ? value : JSON.stringify(value);
7
49
  function MatchReview({
8
50
  incoming,
@@ -16,8 +58,13 @@ function MatchReview({
16
58
  loading,
17
59
  error,
18
60
  onRetry,
19
- className
61
+ className,
62
+ labels: labelOverrides
20
63
  }) {
64
+ const labels = { ...DEFAULT_MATCH_REVIEW_LABELS, ...labelOverrides };
65
+ const plural = (n, one, many) => fillLabel(n === 1 ? one : many, { count: n });
66
+ const decisionLabel = { accepted: labels.decisionAccepted, dismissed: labels.decisionDismissed, split: labels.decisionSplit, other: labels.decisionOther };
67
+ const actionLabel = { accept: labels.accept, dismiss: labels.dismiss, split: labels.split, "pick-other": labels.pickOther };
21
68
  const [internalSelection, setInternalSelection] = useState(/* @__PURE__ */ new Set());
22
69
  const [announcement, setAnnouncement] = useState("");
23
70
  const [bulkPending, setBulkPending] = useState(false);
@@ -37,67 +84,61 @@ function MatchReview({
37
84
  if (!onBulkDecision || selection.size === 0 || bulkPending) return;
38
85
  const ids = [...selection];
39
86
  setBulkPending(true);
40
- setAnnouncement(`${action === "accept" ? "Accepting" : "Dismissing"} ${ids.length} candidate${ids.length === 1 ? "" : "s"}\u2026`);
87
+ setAnnouncement(action === "accept" ? plural(ids.length, labels.acceptingOne, labels.acceptingMany) : plural(ids.length, labels.dismissingOne, labels.dismissingMany));
41
88
  try {
42
89
  const result = await onBulkDecision(ids, action);
43
90
  const succeeded = result?.succeeded ?? ids.length;
44
91
  const failed = result?.failed ?? 0;
45
- setAnnouncement(`${succeeded} candidate${succeeded === 1 ? "" : "s"} ${action === "accept" ? "accepted" : "dismissed"}${failed ? `; ${failed} failed` : ""}.`);
92
+ const done = action === "accept" ? plural(succeeded, labels.acceptedOne, labels.acceptedMany) : plural(succeeded, labels.dismissedOne, labels.dismissedMany);
93
+ setAnnouncement(`${done}${failed ? fillLabel(labels.failedSuffix, { count: failed }) : ""}.`);
46
94
  if (!failed) setSelection(/* @__PURE__ */ new Set());
47
95
  } catch {
48
- setAnnouncement(`Bulk ${action} failed. Try again.`);
96
+ setAnnouncement(action === "accept" ? labels.bulkAcceptFailed : labels.bulkDismissFailed);
49
97
  } finally {
50
98
  setBulkPending(false);
51
99
  }
52
100
  };
53
- if (loading) return /* @__PURE__ */ jsx("div", { className: cx("uix-match-review uix-match-review--state", className), role: "status", children: "Loading match candidates\u2026" });
101
+ if (loading) return /* @__PURE__ */ jsx("div", { className: cx("uix-match-review uix-match-review--state", className), role: "status", children: labels.loading });
54
102
  if (error) return /* @__PURE__ */ jsxs("div", { className: cx("uix-match-review uix-match-review--state", className), role: "alert", children: [
55
103
  /* @__PURE__ */ jsx("p", { children: error }),
56
- onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
104
+ onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: labels.retry })
57
105
  ] });
58
106
  if (candidates.length === 0) return /* @__PURE__ */ jsxs("div", { className: cx("uix-match-review uix-match-review--state", className), children: [
59
- /* @__PURE__ */ jsx("p", { children: "No candidate matches found." }),
60
- /* @__PURE__ */ jsx("p", { children: "Choose another record source or continue without a match." })
107
+ /* @__PURE__ */ jsx("p", { children: labels.empty }),
108
+ /* @__PURE__ */ jsx("p", { children: labels.emptyDetail })
61
109
  ] });
62
- return /* @__PURE__ */ jsxs("section", { className: cx("uix-match-review", className), "aria-label": "Match review", children: [
110
+ return /* @__PURE__ */ jsxs("section", { className: cx("uix-match-review", className), "aria-label": labels.region, children: [
63
111
  /* @__PURE__ */ jsxs("div", { className: "uix-match-review__summary", children: [
64
112
  /* @__PURE__ */ jsxs("div", { children: [
65
113
  /* @__PURE__ */ jsx("strong", { children: incomingLabel }),
66
- /* @__PURE__ */ jsxs("span", { children: [
67
- pending.length,
68
- " pending of ",
69
- candidates.length
70
- ] })
114
+ /* @__PURE__ */ jsx("span", { children: fillLabel(labels.pendingOf, { pending: pending.length, total: candidates.length }) })
71
115
  ] }),
72
116
  /* @__PURE__ */ jsx("div", { className: "uix-match-review__incoming", children: fields.map((field) => /* @__PURE__ */ jsxs("span", { children: [
73
117
  /* @__PURE__ */ jsx("b", { children: field.label }),
74
118
  field.render?.(incoming[field.id], incoming) ?? valueText(incoming[field.id])
75
119
  ] }, field.id)) })
76
120
  ] }),
77
- onBulkDecision && /* @__PURE__ */ jsxs("div", { className: "uix-match-review__bulk", "aria-label": "Bulk candidate actions", children: [
78
- /* @__PURE__ */ jsxs("span", { children: [
79
- selection.size,
80
- " selected"
81
- ] }),
82
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("accept"), children: bulkPending ? "Applying\u2026" : "Accept selected" }),
83
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("dismiss"), children: bulkPending ? "Applying\u2026" : "Dismiss selected" })
121
+ onBulkDecision && /* @__PURE__ */ jsxs("div", { className: "uix-match-review__bulk", "aria-label": labels.bulk, children: [
122
+ /* @__PURE__ */ jsx("span", { children: fillLabel(labels.selectedCount, { count: selection.size }) }),
123
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("accept"), children: bulkPending ? labels.applying : labels.acceptSelected }),
124
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("dismiss"), children: bulkPending ? labels.applying : labels.dismissSelected })
84
125
  ] }),
85
126
  /* @__PURE__ */ jsx("div", { className: "uix-match-review__table-wrap", children: /* @__PURE__ */ jsxs("table", { className: "uix-table uix-match-review__table", children: [
86
- /* @__PURE__ */ jsx("caption", { className: "uix-visually-hidden", children: "Candidate matches and available decisions" }),
127
+ /* @__PURE__ */ jsx("caption", { className: "uix-visually-hidden", children: labels.caption }),
87
128
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
88
- /* @__PURE__ */ jsx("th", { scope: "col", children: /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: "Select" }) }),
89
- /* @__PURE__ */ jsx("th", { scope: "col", children: "Confidence" }),
129
+ /* @__PURE__ */ jsx("th", { scope: "col", children: /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: labels.select }) }),
130
+ /* @__PURE__ */ jsx("th", { scope: "col", children: labels.confidence }),
90
131
  fields.map((field) => /* @__PURE__ */ jsx("th", { scope: "col", children: field.label }, field.id)),
91
- /* @__PURE__ */ jsx("th", { scope: "col", children: "Status" }),
92
- /* @__PURE__ */ jsx("th", { scope: "col", children: "Actions" })
132
+ /* @__PURE__ */ jsx("th", { scope: "col", children: labels.status }),
133
+ /* @__PURE__ */ jsx("th", { scope: "col", children: labels.actions })
93
134
  ] }) }),
94
135
  /* @__PURE__ */ jsx("tbody", { children: candidates.map((candidate) => {
95
136
  const resolved = candidate.status === "resolved";
96
137
  return /* @__PURE__ */ jsxs("tr", { "data-status": candidate.status ?? "pending", children: [
97
- /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("input", { type: "checkbox", checked: selection.has(candidate.id), onChange: () => toggle(candidate.id), "aria-label": `Select candidate ${candidate.id}` }) }),
98
- /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: "uix-match-review__confidence", children: candidate.confidence == null ? "Not scored" : `${Math.round(candidate.confidence * 100)}%` }) }),
138
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("input", { type: "checkbox", checked: selection.has(candidate.id), onChange: () => toggle(candidate.id), "aria-label": fillLabel(labels.selectCandidate, { id: candidate.id }) }) }),
139
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: "uix-match-review__confidence", children: candidate.confidence == null ? labels.notScored : `${Math.round(candidate.confidence * 100)}%` }) }),
99
140
  fields.map((field) => /* @__PURE__ */ jsx("td", { children: field.render?.(candidate.record[field.id], candidate.record) ?? valueText(candidate.record[field.id]) }, field.id)),
100
- /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(StatusPill, { tone: resolved ? "success" : "warning", children: resolved ? candidate.decision ?? "Resolved" : "Pending" }) }),
141
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(StatusPill, { tone: resolved ? "success" : "warning", children: resolved ? candidate.decision ? decisionLabel[candidate.decision] : labels.resolved : labels.pending }) }),
101
142
  /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("div", { className: "uix-match-review__actions", children: ["accept", "dismiss", "split", "pick-other"].map((action) => /* @__PURE__ */ jsx(
102
143
  "button",
103
144
  {
@@ -105,7 +146,7 @@ function MatchReview({
105
146
  className: "uix-btn uix-btn--ghost uix-btn--sm",
106
147
  disabled: resolved,
107
148
  onClick: () => onDecision(candidate.id, action),
108
- children: action === "pick-other" ? "Pick other" : `${action[0].toUpperCase()}${action.slice(1)}`
149
+ children: actionLabel[action]
109
150
  },
110
151
  action
111
152
  )) }) })
@@ -116,5 +157,6 @@ function MatchReview({
116
157
  ] });
117
158
  }
118
159
  export {
160
+ DEFAULT_MATCH_REVIEW_LABELS,
119
161
  MatchReview
120
162
  };
@@ -10,8 +10,10 @@ interface MeterProps extends HTMLAttributes<HTMLDivElement> {
10
10
  tone?: MeterTone;
11
11
  /** Accessible name — rendered as `aria-label` (an explicit `aria-label` prop wins). */
12
12
  label?: string;
13
+ /** TENSOR RX-125 (UIX-04): the spoken tone words; English defaults. */
14
+ toneLabels?: Partial<Record<Exclude<MeterTone, 'success'>, string>>;
13
15
  }
14
16
  /** Horizontal utilization / threshold bar backed by `.uix-meter`. */
15
- declare function Meter({ value, tone, label, className, ...props }: MeterProps): react.JSX.Element;
17
+ declare function Meter({ value, tone, label, toneLabels, className, ...props }: MeterProps): react.JSX.Element;
16
18
 
17
19
  export { Meter, type MeterProps, type MeterTone };
@@ -6,7 +6,8 @@ const toneText = {
6
6
  attention: "needs attention",
7
7
  overdue: "overdue"
8
8
  };
9
- function Meter({ value = 0, tone, label, className, ...props }) {
9
+ function Meter({ value = 0, tone, label, toneLabels, className, ...props }) {
10
+ const spoken = { ...toneText, ...toneLabels };
10
11
  const pct = Math.max(0, Math.min(100, value));
11
12
  return /* @__PURE__ */ jsx(
12
13
  "div",
@@ -15,7 +16,7 @@ function Meter({ value = 0, tone, label, className, ...props }) {
15
16
  "aria-valuenow": pct,
16
17
  "aria-valuemin": 0,
17
18
  "aria-valuemax": 100,
18
- "aria-valuetext": tone && tone !== "success" ? `${pct}%, ${toneText[tone]}` : void 0,
19
+ "aria-valuetext": tone && tone !== "success" ? `${pct}%, ${spoken[tone]}` : void 0,
19
20
  "aria-label": label,
20
21
  className: cx("uix-meter", className),
21
22
  ...props,
@@ -1,11 +1,19 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes } from 'react';
3
3
 
4
+ /** TENSOR RX-125 (UIX-04): the pager's accessible names. */
5
+ interface PaginationLabels {
6
+ region: string;
7
+ previous: string;
8
+ next: string;
9
+ }
10
+ declare const DEFAULT_PAGINATION_LABELS: PaginationLabels;
4
11
  interface PaginationProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
5
12
  page: number;
6
13
  pageCount: number;
7
14
  onChange: (page: number) => void;
15
+ labels?: Partial<PaginationLabels>;
8
16
  }
9
- declare function Pagination({ page, pageCount, onChange, className, ...props }: PaginationProps): react.JSX.Element;
17
+ declare function Pagination({ page, pageCount, onChange, labels: labelOverrides, className, ...props }: PaginationProps): react.JSX.Element;
10
18
 
11
- export { Pagination, type PaginationProps };
19
+ export { DEFAULT_PAGINATION_LABELS, Pagination, type PaginationLabels, type PaginationProps };