@tensor_1/react 2.16.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/chart-preset.cjs +15 -3
- package/dist/chart.cjs +15 -3
- package/dist/components/Alert.js +15 -7
- package/dist/components/AppShell.d.ts +3 -1
- package/dist/components/AppShell.js +3 -1
- package/dist/components/Avatar.js +2 -1
- package/dist/components/Button.js +32 -16
- package/dist/components/Card.d.ts +3 -1
- package/dist/components/Card.js +2 -2
- package/dist/components/ChartCore.js +15 -3
- package/dist/components/CommandPalette.d.ts +4 -2
- package/dist/components/CommandPalette.js +66 -13
- package/dist/components/Comments.d.ts +5 -1
- package/dist/components/Comments.js +1 -1
- package/dist/components/Composer.d.ts +8 -1
- package/dist/components/Composer.js +7 -2
- package/dist/components/DetailLayout.js +1 -1
- package/dist/components/Drawer.d.ts +3 -4
- package/dist/components/Drawer.js +6 -4
- package/dist/components/Field.js +6 -3
- package/dist/components/Inbox.d.ts +13 -3
- package/dist/components/Inbox.js +62 -3
- package/dist/components/Input.d.ts +13 -2
- package/dist/components/Input.js +20 -5
- package/dist/components/Kanban.d.ts +15 -3
- package/dist/components/Kanban.js +42 -10
- package/dist/components/Meter.d.ts +3 -1
- package/dist/components/Meter.js +9 -1
- package/dist/components/Modal.d.ts +4 -5
- package/dist/components/Modal.js +4 -4
- package/dist/components/NavFavourites.js +11 -0
- package/dist/components/PageHeader.d.ts +3 -1
- package/dist/components/PageHeader.js +2 -2
- package/dist/components/Peek.d.ts +3 -4
- package/dist/components/Peek.js +9 -6
- package/dist/components/Progress.d.ts +3 -1
- package/dist/components/Progress.js +5 -3
- package/dist/components/Radio.d.ts +3 -1
- package/dist/components/Radio.js +6 -2
- package/dist/components/RelationshipGraph.d.ts +81 -3
- package/dist/components/RelationshipGraph.js +81 -57
- package/dist/components/RelationshipGraphLayered.d.ts +8 -0
- package/dist/components/RelationshipGraphLayered.js +420 -0
- package/dist/components/RelationshipGraphList.d.ts +16 -0
- package/dist/components/RelationshipGraphList.js +33 -0
- package/dist/components/Spinner.d.ts +3 -1
- package/dist/components/Spinner.js +3 -3
- package/dist/components/Stat.js +5 -1
- package/dist/components/States.d.ts +1 -0
- package/dist/components/States.js +7 -1
- package/dist/components/Table.d.ts +8 -1
- package/dist/components/Table.js +10 -6
- package/dist/components/Tabs.d.ts +13 -1
- package/dist/components/Tabs.js +79 -4
- package/dist/components/Timeline.d.ts +3 -3
- package/dist/components/Timeline.js +3 -3
- package/dist/components/Toast.js +42 -5
- package/dist/components/Tooltip.d.ts +3 -1
- package/dist/components/Tooltip.js +44 -5
- package/dist/components/Tree.d.ts +2 -0
- package/dist/components/Tree.js +26 -4
- package/dist/hooks/useDialog.d.ts +1 -1
- package/dist/hooks/useDialog.js +17 -2
- package/dist/hooks/useTable.d.ts +5 -0
- package/dist/hooks/useTable.js +20 -1
- package/dist/index.cjs +1791 -446
- package/dist/index.d.cts +311 -36
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -3
- package/dist/relationship-graph-model.d.ts +112 -1
- package/dist/relationship-graph-model.js +420 -1
- package/dist/tree-model.d.ts +4 -0
- package/dist/tree-model.js +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,4 +61,33 @@ traversal, three-way JSON diffing, metric parsing/stepping, color conversion and
|
|
|
61
61
|
contrast checks, plus brand-profile apply/restore helpers. No graph or date
|
|
62
62
|
runtime dependency is required.
|
|
63
63
|
|
|
64
|
+
### RelationshipGraph layouts
|
|
65
|
+
|
|
66
|
+
`RelationshipGraph` has two layouts:
|
|
67
|
+
- `radial` (the default): a small neighbourhood ringed around the selection.
|
|
68
|
+
- `layered` (ADR-0003): reads left to right by distance from `rootId`.
|
|
69
|
+
|
|
70
|
+
Layered mode draws shared nodes once, marks cycles, collapses leaf fan-outs into clusters and labels edges. It adds structural keyboard traversal (←/→ along edges, ↑/↓ in a column, Home, End), pan and Ctrl/⌘-wheel zoom.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<RelationshipGraph
|
|
74
|
+
layout="layered"
|
|
75
|
+
rootId={root.id}
|
|
76
|
+
nodes={nodes} // { id, label, type?, description?, depth? }
|
|
77
|
+
edges={edges} // { id, source, target, type?, label?, arrow?, emphasis? }
|
|
78
|
+
labels={translatedLabels} // Partial<RelationshipGraphLabels>
|
|
79
|
+
renderNode={(node) => <MyNodeBody node={node} />}
|
|
80
|
+
nodeAriaLabel={(node) => `${node.label}, ${riskText(node)}`}
|
|
81
|
+
onSelect={openPeek}
|
|
82
|
+
height="clamp(360px, 60vh, 640px)"
|
|
83
|
+
/>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Rules the component cannot enforce for you:
|
|
87
|
+
|
|
88
|
+
- **Whatever `renderNode` / `renderCluster` shows must also be in the accessible name.** Pass `nodeAriaLabel` whenever the node body carries meaning beyond `label`, `type` and `description`.
|
|
89
|
+
- **Keep the equivalent list.** `showList={false}` is allowed only when the same labelled region renders a complete equivalent: every node, its relations and its state.
|
|
90
|
+
- **Dimming is not a signal.** `dimmedNodeIds` must be accompanied by text that says what is dimmed and why.
|
|
91
|
+
- **Localise everything.** Every visible and announced string comes from `labels`; the English defaults live in `DEFAULT_RELATIONSHIP_GRAPH_LABELS`.
|
|
92
|
+
|
|
64
93
|
Ships **ESM + CJS + types**, with per-file `"use client"` so it's safe under React Server Components. Part of the **[UIx v2 styleguide](https://github.com/COx-ORG-H/UIx)**.
|
package/dist/chart-preset.cjs
CHANGED
|
@@ -60,6 +60,12 @@ function readToken(name) {
|
|
|
60
60
|
function uixChartPalette() {
|
|
61
61
|
return [1, 2, 3, 4, 5, 6, 7, 8].map((i) => readToken(`--uix-chart-${i}`));
|
|
62
62
|
}
|
|
63
|
+
function motionSafe(option) {
|
|
64
|
+
if (typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
65
|
+
return { ...option, animation: false };
|
|
66
|
+
}
|
|
67
|
+
return option;
|
|
68
|
+
}
|
|
63
69
|
function ChartCore({
|
|
64
70
|
engine,
|
|
65
71
|
option,
|
|
@@ -85,16 +91,22 @@ function ChartCore({
|
|
|
85
91
|
const onReadyRef = (0, import_react.useRef)(onReady);
|
|
86
92
|
const tableId = (0, import_react.useId)();
|
|
87
93
|
const hasEmpty = empty != null;
|
|
94
|
+
const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length);
|
|
95
|
+
const hasTextAlternativeRef = (0, import_react.useRef)(hasTextAlternative);
|
|
88
96
|
latestOptionRef.current = option;
|
|
89
97
|
onReadyRef.current = onReady;
|
|
98
|
+
hasTextAlternativeRef.current = hasTextAlternative;
|
|
90
99
|
(0, import_react.useEffect)(() => {
|
|
91
100
|
const element = containerRef.current;
|
|
92
101
|
if (!element) return;
|
|
93
102
|
const chart = engine.init(element, null, { renderer: "svg" });
|
|
94
103
|
chartRef.current = chart;
|
|
95
|
-
chart.setOption(latestOptionRef.current);
|
|
104
|
+
chart.setOption(motionSafe(latestOptionRef.current));
|
|
96
105
|
appliedOptionRef.current = latestOptionRef.current;
|
|
97
106
|
onReadyRef.current?.(chart);
|
|
107
|
+
if (!hasTextAlternativeRef.current) {
|
|
108
|
+
console.warn("[uix] Chart has no text alternative \u2014 provide ariaLabel, title, or tableData.");
|
|
109
|
+
}
|
|
98
110
|
let frame = 0;
|
|
99
111
|
let width = element.clientWidth;
|
|
100
112
|
let height2 = element.clientHeight;
|
|
@@ -122,7 +134,7 @@ function ChartCore({
|
|
|
122
134
|
(0, import_react.useEffect)(() => {
|
|
123
135
|
const chart = chartRef.current;
|
|
124
136
|
if (!chart || appliedOptionRef.current === option) return;
|
|
125
|
-
chart.setOption(option, { notMerge: false });
|
|
137
|
+
chart.setOption(motionSafe(option), { notMerge: false });
|
|
126
138
|
appliedOptionRef.current = option;
|
|
127
139
|
}, [option]);
|
|
128
140
|
const heightValue = typeof height === "number" ? `${height}px` : height;
|
|
@@ -147,7 +159,7 @@ function ChartCore({
|
|
|
147
159
|
"aria-describedby": hasTable ? tableId : void 0
|
|
148
160
|
}
|
|
149
161
|
) }),
|
|
150
|
-
hasTable ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { id: tableId, className: "
|
|
162
|
+
hasTable ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { id: tableId, className: "uix-visually-hidden", "aria-label": `${effectiveAriaLabel} \u2014 data table`, children: [
|
|
151
163
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: label }, label)) }) }),
|
|
152
164
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: tableData.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: row[label] }, label)) }, index)) })
|
|
153
165
|
] }) : null,
|
package/dist/chart.cjs
CHANGED
|
@@ -58,6 +58,12 @@ function readToken(name) {
|
|
|
58
58
|
function uixChartPalette() {
|
|
59
59
|
return [1, 2, 3, 4, 5, 6, 7, 8].map((i) => readToken(`--uix-chart-${i}`));
|
|
60
60
|
}
|
|
61
|
+
function motionSafe(option) {
|
|
62
|
+
if (typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
63
|
+
return { ...option, animation: false };
|
|
64
|
+
}
|
|
65
|
+
return option;
|
|
66
|
+
}
|
|
61
67
|
function ChartCore({
|
|
62
68
|
engine,
|
|
63
69
|
option,
|
|
@@ -83,16 +89,22 @@ function ChartCore({
|
|
|
83
89
|
const onReadyRef = (0, import_react.useRef)(onReady);
|
|
84
90
|
const tableId = (0, import_react.useId)();
|
|
85
91
|
const hasEmpty = empty != null;
|
|
92
|
+
const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length);
|
|
93
|
+
const hasTextAlternativeRef = (0, import_react.useRef)(hasTextAlternative);
|
|
86
94
|
latestOptionRef.current = option;
|
|
87
95
|
onReadyRef.current = onReady;
|
|
96
|
+
hasTextAlternativeRef.current = hasTextAlternative;
|
|
88
97
|
(0, import_react.useEffect)(() => {
|
|
89
98
|
const element = containerRef.current;
|
|
90
99
|
if (!element) return;
|
|
91
100
|
const chart = engine.init(element, null, { renderer: "svg" });
|
|
92
101
|
chartRef.current = chart;
|
|
93
|
-
chart.setOption(latestOptionRef.current);
|
|
102
|
+
chart.setOption(motionSafe(latestOptionRef.current));
|
|
94
103
|
appliedOptionRef.current = latestOptionRef.current;
|
|
95
104
|
onReadyRef.current?.(chart);
|
|
105
|
+
if (!hasTextAlternativeRef.current) {
|
|
106
|
+
console.warn("[uix] Chart has no text alternative \u2014 provide ariaLabel, title, or tableData.");
|
|
107
|
+
}
|
|
96
108
|
let frame = 0;
|
|
97
109
|
let width = element.clientWidth;
|
|
98
110
|
let height2 = element.clientHeight;
|
|
@@ -120,7 +132,7 @@ function ChartCore({
|
|
|
120
132
|
(0, import_react.useEffect)(() => {
|
|
121
133
|
const chart = chartRef.current;
|
|
122
134
|
if (!chart || appliedOptionRef.current === option) return;
|
|
123
|
-
chart.setOption(option, { notMerge: false });
|
|
135
|
+
chart.setOption(motionSafe(option), { notMerge: false });
|
|
124
136
|
appliedOptionRef.current = option;
|
|
125
137
|
}, [option]);
|
|
126
138
|
const heightValue = typeof height === "number" ? `${height}px` : height;
|
|
@@ -145,7 +157,7 @@ function ChartCore({
|
|
|
145
157
|
"aria-describedby": hasTable ? tableId : void 0
|
|
146
158
|
}
|
|
147
159
|
) }),
|
|
148
|
-
hasTable ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { id: tableId, className: "
|
|
160
|
+
hasTable ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { id: tableId, className: "uix-visually-hidden", "aria-label": `${effectiveAriaLabel} \u2014 data table`, children: [
|
|
149
161
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: label }, label)) }) }),
|
|
150
162
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: tableData.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: row[label] }, label)) }, index)) })
|
|
151
163
|
] }) : null,
|
package/dist/components/Alert.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from "../cx.js";
|
|
3
3
|
function Alert({ tone, title, icon, children, className, ...props }) {
|
|
4
|
-
return /* @__PURE__ */ jsxs(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
return /* @__PURE__ */ jsxs(
|
|
5
|
+
"div",
|
|
6
|
+
{
|
|
7
|
+
className: cx("uix-alert", tone && tone !== "neutral" && `uix-alert--${tone}`, className),
|
|
8
|
+
role: tone === "danger" || tone === "warning" ? "alert" : "status",
|
|
9
|
+
...props,
|
|
10
|
+
children: [
|
|
11
|
+
icon && /* @__PURE__ */ jsx("div", { className: "uix-alert__icon", children: icon }),
|
|
12
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
13
|
+
title && /* @__PURE__ */ jsx("div", { className: "uix-alert__title", children: title }),
|
|
14
|
+
children && /* @__PURE__ */ jsx("div", { className: "uix-alert__body", children })
|
|
15
|
+
] })
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
);
|
|
11
19
|
}
|
|
12
20
|
export {
|
|
13
21
|
Alert
|
|
@@ -17,10 +17,12 @@ interface AppShellProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
17
17
|
onExitFocus?: () => void;
|
|
18
18
|
/** Drop the content gutter so a genuinely wide table runs edge-to-edge. */
|
|
19
19
|
mainBleed?: boolean;
|
|
20
|
+
/** id given to `<main>` and targeted by the skip link. Override on collision (UIX-A11Y-2). */
|
|
21
|
+
mainId?: string;
|
|
20
22
|
sidebar?: ReactNode;
|
|
21
23
|
topbar?: ReactNode;
|
|
22
24
|
children?: ReactNode;
|
|
23
25
|
}
|
|
24
|
-
declare function AppShell({ nav, collapsed, focus, onExitFocus, mainBleed, sidebar, topbar, children, className, ...props }: AppShellProps): react.JSX.Element;
|
|
26
|
+
declare function AppShell({ nav, collapsed, focus, onExitFocus, mainBleed, mainId, sidebar, topbar, children, className, ...props }: AppShellProps): react.JSX.Element;
|
|
25
27
|
|
|
26
28
|
export { AppShell, type AppShellProps, type ShellNav };
|
|
@@ -8,6 +8,7 @@ function AppShell({
|
|
|
8
8
|
focus,
|
|
9
9
|
onExitFocus,
|
|
10
10
|
mainBleed,
|
|
11
|
+
mainId = "uix-main",
|
|
11
12
|
sidebar,
|
|
12
13
|
topbar,
|
|
13
14
|
children,
|
|
@@ -32,9 +33,10 @@ function AppShell({
|
|
|
32
33
|
"data-focus": focus || void 0,
|
|
33
34
|
...props,
|
|
34
35
|
children: [
|
|
36
|
+
/* @__PURE__ */ jsx("a", { className: "uix-skiplink", href: `#${mainId}`, children: "Skip to content" }),
|
|
35
37
|
sidebar && /* @__PURE__ */ jsx("div", { className: "uix-shell__sidebar", children: sidebar }),
|
|
36
38
|
topbar && /* @__PURE__ */ jsx("div", { className: "uix-shell__topbar", children: topbar }),
|
|
37
|
-
/* @__PURE__ */ jsx("main", { className: cx("uix-shell__main", mainBleed && "uix-shell__main--bleed"), children }),
|
|
39
|
+
/* @__PURE__ */ jsx("main", { id: mainId, tabIndex: -1, className: cx("uix-shell__main", mainBleed && "uix-shell__main--bleed"), children }),
|
|
38
40
|
focus && onExitFocus && /* @__PURE__ */ jsx("div", { className: "uix-shell__focus-exit", children: /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: onExitFocus, children: "Esc \xB7 Exit focus" }) })
|
|
39
41
|
]
|
|
40
42
|
}
|
|
@@ -3,7 +3,8 @@ import { cx } from "../cx.js";
|
|
|
3
3
|
function Avatar({ size = "md", src, alt, children, status, className, ...props }) {
|
|
4
4
|
return /* @__PURE__ */ jsxs("span", { className: cx("uix-avatar", size !== "md" && `uix-avatar--${size}`, className), ...props, children: [
|
|
5
5
|
src ? /* @__PURE__ */ jsx("img", { src, alt: alt ?? "" }) : children,
|
|
6
|
-
status && /* @__PURE__ */ jsx("span", { className: "uix-avatar__status" })
|
|
6
|
+
status && /* @__PURE__ */ jsx("span", { className: "uix-avatar__status", "aria-hidden": "true" }),
|
|
7
|
+
status && /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: "online" })
|
|
7
8
|
] });
|
|
8
9
|
}
|
|
9
10
|
function AvatarGroup({ children, className, ...props }) {
|
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import { cx } from "../cx.js";
|
|
4
|
+
let warnedIconButton = false;
|
|
4
5
|
const Button = forwardRef(
|
|
5
|
-
({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, ...props }, ref) =>
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
className: cx(
|
|
10
|
-
"uix-btn",
|
|
11
|
-
`uix-btn--${variant}`,
|
|
12
|
-
size !== "md" && `uix-btn--${size}`,
|
|
13
|
-
icon && "uix-btn--icon",
|
|
14
|
-
className
|
|
15
|
-
),
|
|
16
|
-
disabled: disabled || loading,
|
|
17
|
-
"data-loading": loading || void 0,
|
|
18
|
-
...props,
|
|
19
|
-
children
|
|
6
|
+
({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, onClick, ...props }, ref) => {
|
|
7
|
+
if (!warnedIconButton && icon && children == null && !props["aria-label"] && !props["aria-labelledby"] && typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
|
|
8
|
+
warnedIconButton = true;
|
|
9
|
+
console.warn("uix: icon-only <Button> has no accessible name \u2014 pass aria-label or aria-labelledby.");
|
|
20
10
|
}
|
|
21
|
-
|
|
11
|
+
return /* @__PURE__ */ jsx(
|
|
12
|
+
"button",
|
|
13
|
+
{
|
|
14
|
+
ref,
|
|
15
|
+
className: cx(
|
|
16
|
+
"uix-btn",
|
|
17
|
+
`uix-btn--${variant}`,
|
|
18
|
+
size !== "md" && `uix-btn--${size}`,
|
|
19
|
+
icon && "uix-btn--icon",
|
|
20
|
+
className
|
|
21
|
+
),
|
|
22
|
+
disabled,
|
|
23
|
+
"aria-disabled": loading || void 0,
|
|
24
|
+
"aria-busy": loading || void 0,
|
|
25
|
+
"data-loading": loading || void 0,
|
|
26
|
+
onClick: loading || onClick ? (e) => {
|
|
27
|
+
if (loading) {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
onClick?.(e);
|
|
32
|
+
} : void 0,
|
|
33
|
+
...props,
|
|
34
|
+
children
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
22
38
|
);
|
|
23
39
|
Button.displayName = "Button";
|
|
24
40
|
function ButtonGroup({ children, className }) {
|
|
@@ -3,12 +3,14 @@ import { HTMLAttributes, ReactNode, AnchorHTMLAttributes } from 'react';
|
|
|
3
3
|
|
|
4
4
|
interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
|
|
5
5
|
title?: ReactNode;
|
|
6
|
+
/** Element for the title — pick a real heading to slot the card into the page outline (UIX-A11Y-4). Default `div` keeps legacy rendering. */
|
|
7
|
+
titleAs?: 'h2' | 'h3' | 'h4' | 'div';
|
|
6
8
|
subtitle?: ReactNode;
|
|
7
9
|
headerAction?: ReactNode;
|
|
8
10
|
footer?: ReactNode;
|
|
9
11
|
children?: ReactNode;
|
|
10
12
|
}
|
|
11
|
-
declare function Card({ title, subtitle, headerAction, footer, children, className, ...props }: CardProps): react.JSX.Element;
|
|
13
|
+
declare function Card({ title, titleAs: TitleTag, subtitle, headerAction, footer, children, className, ...props }: CardProps): react.JSX.Element;
|
|
12
14
|
interface CardLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'title'> {
|
|
13
15
|
title?: ReactNode;
|
|
14
16
|
subtitle?: ReactNode;
|
package/dist/components/Card.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from "../cx.js";
|
|
3
|
-
function Card({ title, subtitle, headerAction, footer, children, className, ...props }) {
|
|
3
|
+
function Card({ title, titleAs: TitleTag = "div", subtitle, headerAction, footer, children, className, ...props }) {
|
|
4
4
|
const hasHeader = title != null || subtitle != null || headerAction != null;
|
|
5
5
|
return /* @__PURE__ */ jsxs("div", { className: cx("uix-card", className), ...props, children: [
|
|
6
6
|
hasHeader && /* @__PURE__ */ jsxs("div", { className: "uix-card__header", children: [
|
|
7
7
|
/* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
|
|
8
|
-
title && /* @__PURE__ */ jsx(
|
|
8
|
+
title && /* @__PURE__ */ jsx(TitleTag, { className: "uix-card__title", children: title }),
|
|
9
9
|
subtitle && /* @__PURE__ */ jsx("div", { className: "uix-card__subtitle", children: subtitle })
|
|
10
10
|
] }),
|
|
11
11
|
headerAction
|
|
@@ -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,
|
|
@@ -34,16 +40,22 @@ function ChartCore({
|
|
|
34
40
|
const onReadyRef = useRef(onReady);
|
|
35
41
|
const tableId = useId();
|
|
36
42
|
const hasEmpty = empty != null;
|
|
43
|
+
const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length);
|
|
44
|
+
const hasTextAlternativeRef = useRef(hasTextAlternative);
|
|
37
45
|
latestOptionRef.current = option;
|
|
38
46
|
onReadyRef.current = onReady;
|
|
47
|
+
hasTextAlternativeRef.current = hasTextAlternative;
|
|
39
48
|
useEffect(() => {
|
|
40
49
|
const element = containerRef.current;
|
|
41
50
|
if (!element) return;
|
|
42
51
|
const chart = engine.init(element, null, { renderer: "svg" });
|
|
43
52
|
chartRef.current = chart;
|
|
44
|
-
chart.setOption(latestOptionRef.current);
|
|
53
|
+
chart.setOption(motionSafe(latestOptionRef.current));
|
|
45
54
|
appliedOptionRef.current = latestOptionRef.current;
|
|
46
55
|
onReadyRef.current?.(chart);
|
|
56
|
+
if (!hasTextAlternativeRef.current) {
|
|
57
|
+
console.warn("[uix] Chart has no text alternative \u2014 provide ariaLabel, title, or tableData.");
|
|
58
|
+
}
|
|
47
59
|
let frame = 0;
|
|
48
60
|
let width = element.clientWidth;
|
|
49
61
|
let height2 = element.clientHeight;
|
|
@@ -71,7 +83,7 @@ function ChartCore({
|
|
|
71
83
|
useEffect(() => {
|
|
72
84
|
const chart = chartRef.current;
|
|
73
85
|
if (!chart || appliedOptionRef.current === option) return;
|
|
74
|
-
chart.setOption(option, { notMerge: false });
|
|
86
|
+
chart.setOption(motionSafe(option), { notMerge: false });
|
|
75
87
|
appliedOptionRef.current = option;
|
|
76
88
|
}, [option]);
|
|
77
89
|
const heightValue = typeof height === "number" ? `${height}px` : height;
|
|
@@ -96,7 +108,7 @@ function ChartCore({
|
|
|
96
108
|
"aria-describedby": hasTable ? tableId : void 0
|
|
97
109
|
}
|
|
98
110
|
) }),
|
|
99
|
-
hasTable ? /* @__PURE__ */ jsxs("table", { id: tableId, className: "
|
|
111
|
+
hasTable ? /* @__PURE__ */ jsxs("table", { id: tableId, className: "uix-visually-hidden", "aria-label": `${effectiveAriaLabel} \u2014 data table`, children: [
|
|
100
112
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ jsx("th", { scope: "col", children: label }, label)) }) }),
|
|
101
113
|
/* @__PURE__ */ jsx("tbody", { children: tableData.map((row, index) => /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ jsx("td", { children: row[label] }, label)) }, index)) })
|
|
102
114
|
] }) : null,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
/**
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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
|
{
|
package/dist/components/Field.js
CHANGED
|
@@ -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 {
|