@tensor_1/react 2.15.0 → 2.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 (82) hide show
  1. package/README.md +5 -0
  2. package/dist/ChartCore-Do6gRBN3.d.cts +44 -0
  3. package/dist/chart-preset.cjs +56 -12
  4. package/dist/chart-preset.d.cts +2 -2
  5. package/dist/chart-preset.d.ts +1 -1
  6. package/dist/chart-preset.js +4 -1
  7. package/dist/chart.cjs +56 -12
  8. package/dist/chart.d.cts +2 -2
  9. package/dist/chart.d.ts +1 -1
  10. package/dist/chart.js +4 -1
  11. package/dist/components/Alert.js +15 -7
  12. package/dist/components/AppShell.d.ts +3 -1
  13. package/dist/components/AppShell.js +3 -1
  14. package/dist/components/Avatar.js +2 -1
  15. package/dist/components/Button.js +32 -16
  16. package/dist/components/Card.d.ts +3 -1
  17. package/dist/components/Card.js +2 -2
  18. package/dist/components/Chart.d.ts +1 -1
  19. package/dist/components/Chart.js +4 -1
  20. package/dist/components/ChartCore.d.ts +24 -3
  21. package/dist/components/ChartCore.js +53 -12
  22. package/dist/components/CommandPalette.d.ts +4 -2
  23. package/dist/components/CommandPalette.js +66 -13
  24. package/dist/components/Comments.d.ts +5 -1
  25. package/dist/components/Comments.js +1 -1
  26. package/dist/components/Composer.d.ts +8 -1
  27. package/dist/components/Composer.js +7 -2
  28. package/dist/components/DetailLayout.js +1 -1
  29. package/dist/components/Drawer.d.ts +3 -4
  30. package/dist/components/Drawer.js +6 -4
  31. package/dist/components/Field.js +6 -3
  32. package/dist/components/Flow.d.ts +26 -0
  33. package/dist/components/Flow.js +48 -0
  34. package/dist/components/Inbox.d.ts +13 -3
  35. package/dist/components/Inbox.js +62 -3
  36. package/dist/components/Input.d.ts +13 -2
  37. package/dist/components/Input.js +20 -5
  38. package/dist/components/Kanban.d.ts +15 -3
  39. package/dist/components/Kanban.js +42 -10
  40. package/dist/components/Meter.d.ts +3 -1
  41. package/dist/components/Meter.js +9 -1
  42. package/dist/components/Modal.d.ts +4 -5
  43. package/dist/components/Modal.js +4 -4
  44. package/dist/components/NavFavourites.js +11 -0
  45. package/dist/components/PageHeader.d.ts +3 -1
  46. package/dist/components/PageHeader.js +2 -2
  47. package/dist/components/Peek.d.ts +3 -4
  48. package/dist/components/Peek.js +9 -6
  49. package/dist/components/Pipeline.d.ts +25 -0
  50. package/dist/components/Pipeline.js +65 -0
  51. package/dist/components/Progress.d.ts +3 -1
  52. package/dist/components/Progress.js +5 -3
  53. package/dist/components/Radio.d.ts +3 -1
  54. package/dist/components/Radio.js +6 -2
  55. package/dist/components/Spinner.d.ts +3 -1
  56. package/dist/components/Spinner.js +3 -3
  57. package/dist/components/Stat.js +5 -1
  58. package/dist/components/States.d.ts +1 -0
  59. package/dist/components/States.js +7 -1
  60. package/dist/components/Table.d.ts +8 -1
  61. package/dist/components/Table.js +10 -6
  62. package/dist/components/Tabs.d.ts +13 -1
  63. package/dist/components/Tabs.js +79 -4
  64. package/dist/components/Timeline.d.ts +3 -3
  65. package/dist/components/Timeline.js +3 -3
  66. package/dist/components/Toast.js +42 -5
  67. package/dist/components/Tooltip.d.ts +3 -1
  68. package/dist/components/Tooltip.js +44 -5
  69. package/dist/components/Tree.d.ts +2 -0
  70. package/dist/components/Tree.js +26 -4
  71. package/dist/hooks/useDialog.d.ts +1 -1
  72. package/dist/hooks/useDialog.js +17 -2
  73. package/dist/hooks/useTable.d.ts +5 -0
  74. package/dist/hooks/useTable.js +20 -1
  75. package/dist/index.cjs +1191 -626
  76. package/dist/index.d.cts +168 -35
  77. package/dist/index.d.ts +4 -2
  78. package/dist/index.js +8 -1
  79. package/dist/tree-model.d.ts +4 -0
  80. package/dist/tree-model.js +8 -0
  81. package/package.json +1 -1
  82. package/dist/ChartCore-Bt1C84ty.d.cts +0 -22
@@ -9,6 +9,12 @@ function readToken(name) {
9
9
  function uixChartPalette() {
10
10
  return [1, 2, 3, 4, 5, 6, 7, 8].map((i) => readToken(`--uix-chart-${i}`));
11
11
  }
12
+ function motionSafe(option) {
13
+ if (typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches) {
14
+ return { ...option, animation: false };
15
+ }
16
+ return option;
17
+ }
12
18
  function ChartCore({
13
19
  engine,
14
20
  option,
@@ -21,7 +27,11 @@ function ChartCore({
21
27
  tableData,
22
28
  tableHeaders,
23
29
  onReady,
24
- header
30
+ header,
31
+ headerAction,
32
+ footer,
33
+ loading = false,
34
+ empty
25
35
  }) {
26
36
  const containerRef = useRef(null);
27
37
  const chartRef = useRef(null);
@@ -29,16 +39,23 @@ function ChartCore({
29
39
  const latestOptionRef = useRef(option);
30
40
  const onReadyRef = useRef(onReady);
31
41
  const tableId = useId();
42
+ const hasEmpty = empty != null;
43
+ const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length);
44
+ const hasTextAlternativeRef = useRef(hasTextAlternative);
32
45
  latestOptionRef.current = option;
33
46
  onReadyRef.current = onReady;
47
+ hasTextAlternativeRef.current = hasTextAlternative;
34
48
  useEffect(() => {
35
49
  const element = containerRef.current;
36
50
  if (!element) return;
37
51
  const chart = engine.init(element, null, { renderer: "svg" });
38
52
  chartRef.current = chart;
39
- chart.setOption(latestOptionRef.current);
53
+ chart.setOption(motionSafe(latestOptionRef.current));
40
54
  appliedOptionRef.current = latestOptionRef.current;
41
55
  onReadyRef.current?.(chart);
56
+ if (!hasTextAlternativeRef.current) {
57
+ console.warn("[uix] Chart has no text alternative \u2014 provide ariaLabel, title, or tableData.");
58
+ }
42
59
  let frame = 0;
43
60
  let width = element.clientWidth;
44
61
  let height2 = element.clientHeight;
@@ -62,23 +79,26 @@ function ChartCore({
62
79
  chartRef.current = null;
63
80
  appliedOptionRef.current = null;
64
81
  };
65
- }, [engine]);
82
+ }, [engine, hasEmpty]);
66
83
  useEffect(() => {
67
84
  const chart = chartRef.current;
68
85
  if (!chart || appliedOptionRef.current === option) return;
69
- chart.setOption(option, { notMerge: false });
86
+ chart.setOption(motionSafe(option), { notMerge: false });
70
87
  appliedOptionRef.current = option;
71
88
  }, [option]);
72
89
  const heightValue = typeof height === "number" ? `${height}px` : height;
73
90
  const effectiveAriaLabel = ariaLabel ?? title ?? "Chart";
74
91
  const hasTable = Boolean(tableData?.length && tableHeaders?.length);
75
92
  return /* @__PURE__ */ jsxs("div", { className: cx("uix-chart", className), style, children: [
76
- title != null || subtitle != null || header != null ? /* @__PURE__ */ jsxs("div", { children: [
77
- header,
78
- title ? /* @__PURE__ */ jsx("div", { className: "uix-chart__title", children: title }) : null,
79
- subtitle ? /* @__PURE__ */ jsx("div", { className: "uix-chart__subtitle", children: subtitle }) : null
93
+ title != null || subtitle != null || header != null || headerAction != null ? /* @__PURE__ */ jsxs("div", { className: "uix-chart__header", children: [
94
+ /* @__PURE__ */ jsxs("div", { className: "uix-chart__heading", children: [
95
+ header,
96
+ title ? /* @__PURE__ */ jsx("div", { className: "uix-chart__title", children: title }) : null,
97
+ subtitle ? /* @__PURE__ */ jsx("div", { className: "uix-chart__subtitle", children: subtitle }) : null
98
+ ] }),
99
+ headerAction
80
100
  ] }) : null,
81
- /* @__PURE__ */ jsx(
101
+ hasEmpty ? /* @__PURE__ */ jsx("div", { className: "uix-chart__plot", "data-empty": true, children: empty }) : /* @__PURE__ */ jsx("div", { className: "uix-chart__plot", "data-loading": loading || void 0, "aria-busy": loading || void 0, children: /* @__PURE__ */ jsx(
82
102
  "div",
83
103
  {
84
104
  ref: containerRef,
@@ -87,14 +107,35 @@ function ChartCore({
87
107
  "aria-label": effectiveAriaLabel,
88
108
  "aria-describedby": hasTable ? tableId : void 0
89
109
  }
90
- ),
91
- hasTable ? /* @__PURE__ */ jsxs("table", { id: tableId, className: "sr-only", "aria-label": `${effectiveAriaLabel} \u2014 data table`, children: [
110
+ ) }),
111
+ hasTable ? /* @__PURE__ */ jsxs("table", { id: tableId, className: "uix-visually-hidden", "aria-label": `${effectiveAriaLabel} \u2014 data table`, children: [
92
112
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ jsx("th", { scope: "col", children: label }, label)) }) }),
93
113
  /* @__PURE__ */ jsx("tbody", { children: tableData.map((row, index) => /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ jsx("td", { children: row[label] }, label)) }, index)) })
94
- ] }) : null
114
+ ] }) : null,
115
+ footer != null && /* @__PURE__ */ jsx("div", { className: "uix-chart__footer", children: footer })
116
+ ] });
117
+ }
118
+ function ChartMetric({ value, label, delta, deltaTone = "neutral", className, ...props }) {
119
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-chart__metric", className), ...props, children: [
120
+ /* @__PURE__ */ jsx("div", { className: "uix-chart__metric-value", children: value }),
121
+ label != null && /* @__PURE__ */ jsx("div", { className: "uix-chart__metric-label", children: label }),
122
+ delta != null && /* @__PURE__ */ jsx("div", { className: "uix-chart__delta", "data-tone": deltaTone, children: delta })
123
+ ] });
124
+ }
125
+ function ChartLegend({ children, className, ...props }) {
126
+ return /* @__PURE__ */ jsx("ul", { className: cx("uix-legend", className), ...props, children });
127
+ }
128
+ function ChartLegendItem({ label, color, swatch = "block", className, style, ...props }) {
129
+ const mergedStyle = color ? { "--legend-color": color, ...style } : style;
130
+ return /* @__PURE__ */ jsxs("li", { className: cx("uix-legend__item", className), style: mergedStyle, ...props, children: [
131
+ /* @__PURE__ */ jsx("span", { className: cx("uix-legend__swatch", swatch !== "block" && `uix-legend__swatch--${swatch}`), "aria-hidden": "true" }),
132
+ label
95
133
  ] });
96
134
  }
97
135
  export {
98
136
  ChartCore,
137
+ ChartLegend,
138
+ ChartLegendItem,
139
+ ChartMetric,
99
140
  uixChartPalette
100
141
  };
@@ -8,7 +8,9 @@ interface CommandPaletteProps extends HTMLAttributes<HTMLDivElement> {
8
8
  }
9
9
  /**
10
10
  * ⌘K command palette surface over `.uix-cmdk`. Presentational: mount it inside a
11
- * Modal/Popover and wire search + keyboard selection in the consumer.
11
+ * Modal/Popover and wire search + keyboard selection in the consumer. The input is
12
+ * a combobox over the listbox of `CommandItem`s; spreads override every default, so
13
+ * consumers already passing their own roles/labels keep working.
12
14
  */
13
15
  declare function CommandPalette({ inputProps, children, className, ...props }: CommandPaletteProps): react.JSX.Element;
14
16
  interface CommandGroupProps {
@@ -25,6 +27,6 @@ interface CommandItemProps extends HTMLAttributes<HTMLDivElement> {
25
27
  children?: ReactNode;
26
28
  }
27
29
  /** A single command row over `.uix-cmdk__item`. */
28
- declare function CommandItem({ icon, shortcut, active, children, className, ...props }: CommandItemProps): react.JSX.Element;
30
+ declare function CommandItem({ icon, shortcut, active, children, className, id, ...props }: CommandItemProps): react.JSX.Element;
29
31
 
30
32
  export { CommandGroup, type CommandGroupProps, CommandItem, type CommandItemProps, CommandPalette, type CommandPaletteProps };
@@ -1,23 +1,76 @@
1
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useEffect, useId, useMemo, useState } from "react";
2
4
  import { cx } from "../cx.js";
5
+ const CmdkContext = createContext(null);
3
6
  function CommandPalette({ inputProps, children, className, ...props }) {
4
- return /* @__PURE__ */ jsxs("div", { className: cx("uix-cmdk", className), ...props, children: [
5
- /* @__PURE__ */ jsx("input", { className: "uix-cmdk__input", ...inputProps }),
6
- /* @__PURE__ */ jsx("div", { className: "uix-cmdk__list", children })
7
- ] });
7
+ const listId = useId();
8
+ const [activeId, setActiveId] = useState(null);
9
+ const [count, setCount] = useState(0);
10
+ const [announced, setAnnounced] = useState("");
11
+ const setActive = useCallback((id, active) => {
12
+ setActiveId((cur) => active ? id : cur === id ? null : cur);
13
+ }, []);
14
+ const registerItem = useCallback(() => {
15
+ setCount((c) => c + 1);
16
+ return () => setCount((c) => c - 1);
17
+ }, []);
18
+ useEffect(() => {
19
+ const t = window.setTimeout(() => setAnnounced(`${count} result${count === 1 ? "" : "s"}`), 150);
20
+ return () => clearTimeout(t);
21
+ }, [count]);
22
+ const ctx = useMemo(() => ({ listId, setActive, registerItem }), [listId, setActive, registerItem]);
23
+ return /* @__PURE__ */ jsx(CmdkContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs("div", { className: cx("uix-cmdk", className), ...props, children: [
24
+ /* @__PURE__ */ jsx(
25
+ "input",
26
+ {
27
+ className: "uix-cmdk__input",
28
+ role: "combobox",
29
+ "aria-expanded": "true",
30
+ "aria-autocomplete": "list",
31
+ "aria-controls": listId,
32
+ "aria-activedescendant": activeId ?? void 0,
33
+ "aria-label": "Command palette",
34
+ ...inputProps
35
+ }
36
+ ),
37
+ /* @__PURE__ */ jsx("div", { className: "uix-cmdk__list", role: "listbox", id: listId, children }),
38
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", children: announced })
39
+ ] }) });
8
40
  }
9
41
  function CommandGroup({ label, children }) {
10
- return /* @__PURE__ */ jsxs(Fragment, { children: [
11
- label != null && /* @__PURE__ */ jsx("div", { className: "uix-cmdk__group", children: label }),
42
+ const labelId = useId();
43
+ return /* @__PURE__ */ jsxs("div", { role: "group", "aria-labelledby": label != null ? labelId : void 0, children: [
44
+ label != null && /* @__PURE__ */ jsx("div", { className: "uix-cmdk__group", id: labelId, children: label }),
12
45
  children
13
46
  ] });
14
47
  }
15
- function CommandItem({ icon, shortcut, active, children, className, ...props }) {
16
- return /* @__PURE__ */ jsxs("div", { className: cx("uix-cmdk__item", className), "data-active": active || void 0, ...props, children: [
17
- icon,
18
- /* @__PURE__ */ jsx("span", { children }),
19
- shortcut
20
- ] });
48
+ function CommandItem({ icon, shortcut, active, children, className, id, ...props }) {
49
+ const ctx = useContext(CmdkContext);
50
+ const autoId = useId();
51
+ const itemId = id ?? autoId;
52
+ useEffect(() => ctx?.registerItem(), [ctx]);
53
+ useEffect(() => {
54
+ if (!ctx || !active) return;
55
+ ctx.setActive(itemId, true);
56
+ return () => ctx.setActive(itemId, false);
57
+ }, [ctx, itemId, active]);
58
+ return /* @__PURE__ */ jsxs(
59
+ "div",
60
+ {
61
+ className: cx("uix-cmdk__item", className),
62
+ "data-active": active || void 0,
63
+ id: itemId,
64
+ role: "option",
65
+ "aria-selected": !!active,
66
+ ...props,
67
+ children: [
68
+ icon,
69
+ /* @__PURE__ */ jsx("span", { children }),
70
+ shortcut
71
+ ]
72
+ }
73
+ );
21
74
  }
22
75
  export {
23
76
  CommandGroup,
@@ -4,7 +4,11 @@ import { HTMLAttributes, ReactNode } from 'react';
4
4
  interface CommentsProps extends HTMLAttributes<HTMLDivElement> {
5
5
  children?: ReactNode;
6
6
  }
7
- /** Comment thread container over `.uix-comments`. */
7
+ /**
8
+ * Comment thread container over `.uix-comments`. role="log" is an implicit polite
9
+ * live region: comments appended after initial render are announced, the initial
10
+ * list is not (UIX-A11Y-3). Pass `role` to override.
11
+ */
8
12
  declare function Comments({ children, className, ...props }: CommentsProps): react.JSX.Element;
9
13
  interface CommentProps extends HTMLAttributes<HTMLDivElement> {
10
14
  /** Usually an `<Avatar />` (the grid reserves the leading column). */
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cx } from "../cx.js";
3
3
  function Comments({ children, className, ...props }) {
4
- return /* @__PURE__ */ jsx("div", { className: cx("uix-comments", className), ...props, children });
4
+ return /* @__PURE__ */ jsx("div", { role: "log", className: cx("uix-comments", className), ...props, children });
5
5
  }
6
6
  function Comment({ avatar, author, meta, children, className, ...props }) {
7
7
  return /* @__PURE__ */ jsxs("div", { className: cx("uix-comment", className), ...props, children: [
@@ -3,6 +3,8 @@ import { HTMLAttributes, ReactNode } from 'react';
3
3
 
4
4
  interface ComposerProps extends HTMLAttributes<HTMLDivElement> {
5
5
  children?: ReactNode;
6
+ /** Accessible name for the child `<textarea>`; applied as its `aria-label`. */
7
+ label?: string;
6
8
  }
7
9
  /**
8
10
  * Composer surface over `.uix-composer` — a single box that owns the
@@ -10,8 +12,13 @@ interface ComposerProps extends HTMLAttributes<HTMLDivElement> {
10
12
  * `<ComposerBar>` inside; the descendant `textarea` picks up the composer's
11
13
  * padding/typography automatically, so the input never reads as a second
12
14
  * nested box.
15
+ *
16
+ * A placeholder is not an accessible name, so the composer labels a bare child
17
+ * `<textarea>` with `label` (default "Add a comment"); a textarea that brings its
18
+ * own `aria-label`/`aria-labelledby` — or an `id` (likely tied to an external
19
+ * `<label htmlFor>`) — is left untouched (UIX-A11Y-3).
13
20
  */
14
- declare function Composer({ children, className, ...props }: ComposerProps): react.JSX.Element;
21
+ declare function Composer({ children, className, label, ...props }: ComposerProps): react.JSX.Element;
15
22
  interface ComposerBarProps extends HTMLAttributes<HTMLDivElement> {
16
23
  children?: ReactNode;
17
24
  }
@@ -1,7 +1,12 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
+ import { Children, cloneElement, isValidElement } from "react";
2
3
  import { cx } from "../cx.js";
3
- function Composer({ children, className, ...props }) {
4
- return /* @__PURE__ */ jsx("div", { className: cx("uix-composer", className), ...props, children });
4
+ function Composer({ children, className, label = "Add a comment", ...props }) {
5
+ return /* @__PURE__ */ jsx("div", { className: cx("uix-composer", className), ...props, children: Children.map(children, (child) => {
6
+ if (!isValidElement(child) || child.type !== "textarea") return child;
7
+ const p = child.props;
8
+ return p["aria-label"] || p["aria-labelledby"] || p.id ? child : cloneElement(child, { "aria-label": label });
9
+ }) });
5
10
  }
6
11
  function ComposerBar({ children, className, ...props }) {
7
12
  return /* @__PURE__ */ jsx("div", { className: cx("uix-composer__bar", className), ...props, children });
@@ -3,7 +3,7 @@ import { cx } from "../cx.js";
3
3
  function DetailLayout({ side, children, className, ...props }) {
4
4
  return /* @__PURE__ */ jsxs("div", { className: cx("uix-detail", className), ...props, children: [
5
5
  /* @__PURE__ */ jsx("div", { className: "uix-detail__main", children }),
6
- side != null && /* @__PURE__ */ jsx("div", { className: "uix-detail__side", children: side })
6
+ side != null && /* @__PURE__ */ jsx("aside", { className: "uix-detail__side", children: side })
7
7
  ] });
8
8
  }
9
9
  export {
@@ -1,14 +1,13 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
3
 
4
- interface DrawerProps {
4
+ interface DrawerProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
5
5
  open: boolean;
6
6
  onClose?: () => void;
7
7
  title?: ReactNode;
8
8
  children?: ReactNode;
9
9
  footer?: ReactNode;
10
- className?: string;
11
10
  }
12
- declare function Drawer({ open, onClose, title, children, footer, className }: DrawerProps): react.JSX.Element;
11
+ declare function Drawer({ open, onClose, title, children, footer, className, ...rest }: DrawerProps): react.JSX.Element;
13
12
 
14
13
  export { Drawer, type DrawerProps };
@@ -1,13 +1,15 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useId } from "react";
3
4
  import { cx } from "../cx.js";
4
5
  import { useDialog } from "../hooks/useDialog.js";
5
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" }) });
6
- function Drawer({ open, onClose, title, children, footer, className }) {
7
- const ref = useDialog(open);
8
- return /* @__PURE__ */ jsxs("dialog", { ref, className: cx("uix-drawer", className), onClose, children: [
7
+ function Drawer({ open, onClose, title, children, footer, className, ...rest }) {
8
+ const ref = useDialog(open, onClose);
9
+ const titleId = useId();
10
+ return /* @__PURE__ */ jsxs("dialog", { ref, className: cx("uix-drawer", className), "aria-labelledby": title ? titleId : void 0, ...rest, children: [
9
11
  (title != null || onClose) && /* @__PURE__ */ jsxs("div", { className: "uix-drawer__header", children: [
10
- title && /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, fontSize: "var(--uix-text-h3)" }, children: title }),
12
+ title && /* @__PURE__ */ jsx("h2", { id: titleId, style: { fontWeight: 600, fontSize: "var(--uix-text-h3)", fontFamily: "inherit", letterSpacing: "inherit", lineHeight: "inherit" }, children: title }),
11
13
  onClose && /* @__PURE__ */ jsx(
12
14
  "button",
13
15
  {
@@ -5,14 +5,17 @@ function Field({ label, hint, error, success, required, children, className, htm
5
5
  const uid = useId();
6
6
  const msgId = `${uid}-msg`;
7
7
  const describedBy = error || success || hint ? msgId : void 0;
8
+ const controlId = isValidElement(children) ? children.props.id ?? `${uid}-ctl` : void 0;
8
9
  const control = isValidElement(children) ? cloneElement(children, {
10
+ id: controlId,
9
11
  "aria-describedby": [children.props["aria-describedby"], describedBy].filter(Boolean).join(" ") || void 0,
10
- "aria-invalid": error ? true : children.props["aria-invalid"]
12
+ "aria-invalid": error ? true : children.props["aria-invalid"],
13
+ "aria-required": children.props["aria-required"] ?? (required || void 0)
11
14
  }) : children;
12
15
  return /* @__PURE__ */ jsxs("div", { className: cx("uix-field", className), children: [
13
- label && /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor, "data-required": required || void 0, children: label }),
16
+ label && /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor: htmlFor ?? controlId, "data-required": required || void 0, children: label }),
14
17
  control,
15
- /* @__PURE__ */ jsx("div", { className: "uix-field__msg", id: msgId, children: error ? /* @__PURE__ */ jsx("span", { className: "uix-field__error", role: "alert", children: error }) : success ? /* @__PURE__ */ jsx("span", { className: "uix-field__success", children: success }) : hint ? /* @__PURE__ */ jsx("span", { className: "uix-field__hint", children: hint }) : null })
18
+ /* @__PURE__ */ jsx("div", { className: "uix-field__msg", id: msgId, children: error ? /* @__PURE__ */ jsx("span", { className: "uix-field__error", role: "alert", children: error }) : success ? /* @__PURE__ */ jsx("span", { className: "uix-field__success", role: "status", children: success }) : hint ? /* @__PURE__ */ jsx("span", { className: "uix-field__hint", children: hint }) : null })
16
19
  ] });
17
20
  }
18
21
  export {
@@ -0,0 +1,26 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
+
4
+ type FlowVariant = 'linear' | 'branch' | 'loop' | 'mindmap' | 'canvas';
5
+ type FlowNodeState = 'pending' | 'active' | 'running' | 'done' | 'error';
6
+ interface FlowProps extends HTMLAttributes<HTMLDivElement> {
7
+ variant?: FlowVariant;
8
+ panel?: boolean;
9
+ children?: ReactNode;
10
+ }
11
+ /** Presentational workflow surface. Consumers provide graph geometry and connectors. */
12
+ declare function Flow({ variant, panel, children, className, role, ...props }: FlowProps): react.JSX.Element;
13
+ interface FlowNodeProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
14
+ title: ReactNode;
15
+ state?: FlowNodeState;
16
+ stateLabel?: ReactNode;
17
+ eyebrow?: ReactNode;
18
+ meta?: ReactNode;
19
+ icon?: ReactNode;
20
+ footer?: ReactNode;
21
+ wide?: boolean;
22
+ }
23
+ /** Workflow node with explicit text state and optional operational metadata. */
24
+ declare function FlowNode({ title, state, stateLabel, eyebrow, meta, icon, footer, wide, className, ...props }: FlowNodeProps): react.JSX.Element;
25
+
26
+ export { Flow, FlowNode, type FlowNodeProps, type FlowNodeState, type FlowProps, type FlowVariant };
@@ -0,0 +1,48 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../cx.js";
3
+ function Flow({ variant = "linear", panel = false, children, className, role = "group", ...props }) {
4
+ return /* @__PURE__ */ jsx(
5
+ "div",
6
+ {
7
+ className: cx("uix-flow", `uix-flow--${variant}`, panel && "uix-flow--panel", className),
8
+ role,
9
+ ...props,
10
+ children
11
+ }
12
+ );
13
+ }
14
+ const FLOW_STATE_LABELS = {
15
+ pending: "Pending",
16
+ active: "Active",
17
+ running: "Running",
18
+ done: "Done",
19
+ error: "Error"
20
+ };
21
+ function FlowNode({
22
+ title,
23
+ state = "pending",
24
+ stateLabel,
25
+ eyebrow,
26
+ meta,
27
+ icon,
28
+ footer,
29
+ wide = false,
30
+ className,
31
+ ...props
32
+ }) {
33
+ const resolvedStateLabel = stateLabel ?? FLOW_STATE_LABELS[state];
34
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-node", `uix-node--${state}`, icon == null && "uix-node--no-icon", wide && "uix-node--wide", className), ...props, children: [
35
+ icon != null && /* @__PURE__ */ jsx("span", { className: "uix-node__icon", "aria-hidden": "true", children: icon }),
36
+ /* @__PURE__ */ jsxs("div", { className: "uix-node__body", children: [
37
+ eyebrow != null && /* @__PURE__ */ jsx("div", { className: "uix-node__eyebrow", children: eyebrow }),
38
+ /* @__PURE__ */ jsx("div", { className: "uix-node__title", children: title }),
39
+ meta != null && /* @__PURE__ */ jsx("div", { className: "uix-node__meta", children: meta }),
40
+ /* @__PURE__ */ jsx("div", { className: "uix-node__status", children: resolvedStateLabel })
41
+ ] }),
42
+ footer != null && /* @__PURE__ */ jsx("div", { className: "uix-node__footer", children: footer })
43
+ ] });
44
+ }
45
+ export {
46
+ Flow,
47
+ FlowNode
48
+ };
@@ -2,14 +2,24 @@ import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
3
 
4
4
  interface InboxProps extends HTMLAttributes<HTMLDivElement> {
5
+ /**
6
+ * Which pane a narrow viewport (<700px) shows: 'list' (default, matches the old
7
+ * behaviour) or 'detail'. Consumers should render a Back control in the detail pane
8
+ * on narrow viewports to return to the list (UIX-A11Y-2).
9
+ */
10
+ view?: 'list' | 'detail';
5
11
  children?: ReactNode;
6
12
  }
7
13
  /** Two-pane inbox master-detail container backed by `.uix-inbox`. Place `<InboxList>` and `<InboxDetail>` as children. */
8
- declare function Inbox({ children, className, ...props }: InboxProps): react.JSX.Element;
14
+ declare function Inbox({ view, children, className, ...props }: InboxProps): react.JSX.Element;
9
15
  interface InboxListProps extends HTMLAttributes<HTMLDivElement> {
10
16
  children?: ReactNode;
11
17
  }
12
- /** Left-pane item list. Place `<InboxItem>` children here. */
18
+ /**
19
+ * Left-pane item list. Place `<InboxItem>` children here. A keyboard listbox
20
+ * (UIX-A11Y-2): the list is the single tab stop; ArrowUp/ArrowDown/Home/End move the
21
+ * active option via `aria-activedescendant`, Enter/Space click it.
22
+ */
13
23
  declare function InboxList({ children, className, ...props }: InboxListProps): react.JSX.Element;
14
24
  interface InboxItemProps extends HTMLAttributes<HTMLDivElement> {
15
25
  /** Primary subject / title line. */
@@ -25,7 +35,7 @@ interface InboxItemProps extends HTMLAttributes<HTMLDivElement> {
25
35
  children?: ReactNode;
26
36
  }
27
37
  /** A single inbox row with subject, preview excerpt, optional leading slot, and unread/selected states. */
28
- declare function InboxItem({ subject, preview, leading, unread, selected, children, className, ...props }: InboxItemProps): react.JSX.Element;
38
+ declare function InboxItem({ subject, preview, leading, unread, selected, children, className, id, ...props }: InboxItemProps): react.JSX.Element;
29
39
  interface InboxDetailProps extends HTMLAttributes<HTMLDivElement> {
30
40
  children?: ReactNode;
31
41
  }
@@ -1,10 +1,63 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext, useId, useMemo, useRef, useState } from "react";
2
4
  import { cx } from "../cx.js";
3
- function Inbox({ children, className, ...props }) {
4
- return /* @__PURE__ */ jsx("div", { className: cx("uix-inbox", className), ...props, children });
5
+ function Inbox({ view, children, className, ...props }) {
6
+ return /* @__PURE__ */ jsx("div", { className: cx("uix-inbox", className), "data-view": view, ...props, children });
5
7
  }
8
+ const InboxListCtx = createContext({});
6
9
  function InboxList({ children, className, ...props }) {
7
- return /* @__PURE__ */ jsx("div", { className: cx("uix-inbox__list", className), role: "listbox", ...props, children });
10
+ const listRef = useRef(null);
11
+ const [activeId, setActiveId] = useState(void 0);
12
+ const ctx = useMemo(() => ({ activeId }), [activeId]);
13
+ const onKeyDown = (e) => {
14
+ const opts = Array.from(listRef.current?.querySelectorAll('[role="option"]') ?? []);
15
+ if (opts.length === 0) return;
16
+ const idx = opts.findIndex((el2) => el2.id === activeId);
17
+ let next;
18
+ switch (e.key) {
19
+ case "ArrowDown":
20
+ next = Math.min(idx + 1, opts.length - 1);
21
+ break;
22
+ case "ArrowUp":
23
+ next = idx < 0 ? 0 : Math.max(idx - 1, 0);
24
+ break;
25
+ case "Home":
26
+ next = 0;
27
+ break;
28
+ case "End":
29
+ next = opts.length - 1;
30
+ break;
31
+ case "Enter":
32
+ case " ":
33
+ if (idx >= 0) {
34
+ e.preventDefault();
35
+ opts[idx]?.click();
36
+ }
37
+ return;
38
+ default:
39
+ return;
40
+ }
41
+ e.preventDefault();
42
+ const el = opts[next];
43
+ if (!el || !el.id) return;
44
+ setActiveId(el.id);
45
+ el.scrollIntoView({ block: "nearest" });
46
+ };
47
+ return /* @__PURE__ */ jsx(InboxListCtx.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
48
+ "div",
49
+ {
50
+ ref: listRef,
51
+ className: cx("uix-inbox__list", className),
52
+ role: "listbox",
53
+ tabIndex: 0,
54
+ "aria-label": "Inbox",
55
+ "aria-activedescendant": activeId,
56
+ onKeyDown,
57
+ ...props,
58
+ children
59
+ }
60
+ ) });
8
61
  }
9
62
  function InboxItem({
10
63
  subject,
@@ -14,13 +67,19 @@ function InboxItem({
14
67
  selected,
15
68
  children,
16
69
  className,
70
+ id,
17
71
  ...props
18
72
  }) {
73
+ const autoId = useId();
74
+ const itemId = id ?? autoId;
75
+ const { activeId } = useContext(InboxListCtx);
19
76
  return /* @__PURE__ */ jsxs(
20
77
  "div",
21
78
  {
22
79
  role: "option",
80
+ id: itemId,
23
81
  "aria-selected": selected ?? false,
82
+ "data-active": activeId === itemId ? "" : void 0,
24
83
  className: cx("uix-inbox__item", className),
25
84
  ...unread ? { "data-unread": "" } : {},
26
85
  ...props,
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { InputHTMLAttributes, ReactNode } from 'react';
2
+ import { InputHTMLAttributes, ReactNode, AriaAttributes } from 'react';
3
3
 
4
4
  interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
5
5
  size?: 'sm' | 'md';
@@ -12,7 +12,18 @@ interface InputGroupProps {
12
12
  leadingIcon?: ReactNode;
13
13
  trailingIcon?: ReactNode;
14
14
  className?: string;
15
+ /** Injected by `<Field>` when it wraps the group; forwarded onto the child control. */
16
+ id?: string;
17
+ 'aria-describedby'?: string;
18
+ 'aria-invalid'?: AriaAttributes['aria-invalid'];
19
+ 'aria-required'?: AriaAttributes['aria-required'];
15
20
  }
16
- declare function InputGroup({ children, leadingIcon, trailingIcon, className }: InputGroupProps): react.JSX.Element;
21
+ /**
22
+ * Icon-adorned wrapper for a child `<Input>`. The control is passed as `children`, so
23
+ * `<Field>`'s injected `id`/`aria-*` land on this wrapper — they're forwarded onto the
24
+ * single child element (the child's own props win) so label/message association survives
25
+ * the extra div (UIX-A11Y-3).
26
+ */
27
+ declare function InputGroup({ children, leadingIcon, trailingIcon, className, id, 'aria-describedby': describedBy, 'aria-invalid': invalid, 'aria-required': required, }: InputGroupProps): react.JSX.Element;
17
28
 
18
29
  export { Input, InputGroup, type InputGroupProps, type InputProps };
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { forwardRef } from "react";
2
+ import { cloneElement, forwardRef, isValidElement } from "react";
3
3
  import { cx } from "../cx.js";
4
4
  const Input = forwardRef(
5
5
  ({ size = "md", invalid, valid, className, ...props }, ref) => /* @__PURE__ */ jsx(
@@ -14,7 +14,22 @@ const Input = forwardRef(
14
14
  )
15
15
  );
16
16
  Input.displayName = "Input";
17
- function InputGroup({ children, leadingIcon, trailingIcon, className }) {
17
+ function InputGroup({
18
+ children,
19
+ leadingIcon,
20
+ trailingIcon,
21
+ className,
22
+ id,
23
+ "aria-describedby": describedBy,
24
+ "aria-invalid": invalid,
25
+ "aria-required": required
26
+ }) {
27
+ const control = isValidElement(children) && (id ?? describedBy ?? invalid ?? required) != null ? cloneElement(children, {
28
+ id: children.props.id ?? id,
29
+ "aria-describedby": children.props["aria-describedby"] ?? describedBy,
30
+ "aria-invalid": children.props["aria-invalid"] ?? invalid,
31
+ "aria-required": children.props["aria-required"] ?? required
32
+ }) : children;
18
33
  return /* @__PURE__ */ jsxs(
19
34
  "div",
20
35
  {
@@ -25,9 +40,9 @@ function InputGroup({ children, leadingIcon, trailingIcon, className }) {
25
40
  className
26
41
  ),
27
42
  children: [
28
- leadingIcon && /* @__PURE__ */ jsx("span", { className: "uix-input-group__icon", children: leadingIcon }),
29
- children,
30
- trailingIcon && /* @__PURE__ */ jsx("span", { className: "uix-input-group__icon uix-input-group__icon--trailing", children: trailingIcon })
43
+ leadingIcon && /* @__PURE__ */ jsx("span", { className: "uix-input-group__icon", "aria-hidden": "true", children: leadingIcon }),
44
+ control,
45
+ trailingIcon && /* @__PURE__ */ jsx("span", { className: "uix-input-group__icon uix-input-group__icon--trailing", "aria-hidden": "true", children: trailingIcon })
31
46
  ]
32
47
  }
33
48
  );