@tensor_1/react 2.26.3 → 2.28.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 (57) hide show
  1. package/README.md +74 -0
  2. package/dist/calendar-model.d.ts +4 -2
  3. package/dist/components/AppShell.d.ts +1 -1
  4. package/dist/components/AppShell.js +6 -2
  5. package/dist/components/Button.d.ts +28 -4
  6. package/dist/components/Button.js +19 -9
  7. package/dist/components/Card.d.ts +7 -1
  8. package/dist/components/Card.js +3 -3
  9. package/dist/components/Chip.d.ts +43 -0
  10. package/dist/components/Chip.js +73 -0
  11. package/dist/components/CollapsibleSection.d.ts +19 -1
  12. package/dist/components/CollapsibleSection.js +5 -2
  13. package/dist/components/CollapsibleSectionState.d.ts +11 -0
  14. package/dist/components/CollapsibleSectionState.js +67 -0
  15. package/dist/components/CommandPalette.d.ts +1 -1
  16. package/dist/components/CommandPalette.js +4 -1
  17. package/dist/components/DateRangePicker.js +3 -1
  18. package/dist/components/Dialogs.d.ts +54 -3
  19. package/dist/components/Dialogs.js +167 -6
  20. package/dist/components/Drawer.d.ts +6 -1
  21. package/dist/components/Drawer.js +5 -2
  22. package/dist/components/Kbd.d.ts +27 -0
  23. package/dist/components/Kbd.js +45 -0
  24. package/dist/components/Menu.d.ts +51 -0
  25. package/dist/components/Menu.js +177 -0
  26. package/dist/components/Modal.d.ts +1 -1
  27. package/dist/components/Modal.js +4 -1
  28. package/dist/components/Pagination.d.ts +36 -5
  29. package/dist/components/Pagination.js +59 -37
  30. package/dist/components/Peek.d.ts +1 -1
  31. package/dist/components/Peek.js +6 -1
  32. package/dist/components/SchedulingCalendar.d.ts +47 -2
  33. package/dist/components/SchedulingCalendar.js +69 -22
  34. package/dist/components/SearchSuggest.d.ts +1 -1
  35. package/dist/components/SearchSuggest.js +4 -1
  36. package/dist/components/Sidebar.d.ts +7 -2
  37. package/dist/components/Sidebar.js +15 -4
  38. package/dist/components/Stat.d.ts +16 -0
  39. package/dist/components/Stat.js +12 -5
  40. package/dist/components/Steps.d.ts +48 -0
  41. package/dist/components/Steps.js +42 -0
  42. package/dist/components/Toast.d.ts +15 -3
  43. package/dist/components/Toast.js +158 -9
  44. package/dist/components/Tooltip.d.ts +10 -4
  45. package/dist/components/Tooltip.js +2 -2
  46. package/dist/emoji.cjs +2 -2
  47. package/dist/index.cjs +2322 -1352
  48. package/dist/index.d.cts +703 -166
  49. package/dist/index.d.ts +11 -4
  50. package/dist/index.js +29 -2
  51. package/dist/labels-context.d.ts +72 -0
  52. package/dist/labels-context.js +23 -0
  53. package/dist/link.d.ts +20 -0
  54. package/dist/link.js +9 -0
  55. package/dist/toast-store.d.ts +98 -0
  56. package/dist/toast-store.js +110 -0
  57. package/package.json +1 -1
@@ -0,0 +1,42 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Children, cloneElement, isValidElement } from "react";
3
+ import { cx } from "../cx.js";
4
+ import { renderUixLink } from "../link.js";
5
+ const DEFAULT_STEP_STATE_LABELS = {
6
+ complete: "Completed",
7
+ current: "Current step",
8
+ upcoming: "Not started",
9
+ waiting: "Waiting",
10
+ error: "Needs attention"
11
+ };
12
+ const CSS_STATE = { complete: "done", current: "active", upcoming: void 0, waiting: "waiting", error: "error" };
13
+ const Check = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "12", height: "12", fill: "none", stroke: "currentColor", strokeWidth: 2.25, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M3.5 8.5l3 3 6-7" }) });
14
+ function Steps({ orientation = "horizontal", label, stateLabels, className, children, ...props }) {
15
+ const labels = { ...DEFAULT_STEP_STATE_LABELS, ...stateLabels };
16
+ const steps = Children.toArray(children).filter(isValidElement);
17
+ return /* @__PURE__ */ jsx("ol", { "aria-label": label, className: cx("uix-steps", "uix-steps--list", `uix-steps--${orientation}`, className), ...props, children: steps.map((step, index) => cloneElement(step, { index, last: index === steps.length - 1, stateLabels: labels })) });
18
+ }
19
+ function Step({ title, description, state = "upcoming", marker, href, renderLink, onSelect, index = 0, last = false, stateLabels = DEFAULT_STEP_STATE_LABELS, className, ...props }) {
20
+ const glyph = marker ?? (state === "complete" ? /* @__PURE__ */ jsx(Check, {}) : state === "error" ? "!" : index + 1);
21
+ const name = /* @__PURE__ */ jsxs(Fragment, { children: [
22
+ /* @__PURE__ */ jsx("span", { className: "uix-step__title", children: title }),
23
+ /* @__PURE__ */ jsxs("span", { className: "uix-visually-hidden", children: [
24
+ ", ",
25
+ stateLabels[state]
26
+ ] })
27
+ ] });
28
+ const titled = href != null ? renderUixLink(renderLink, { href, className: "uix-step__action", children: name }) : onSelect ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-step__action", onClick: onSelect, children: name }) : /* @__PURE__ */ jsx("span", { className: "uix-step__action", children: name });
29
+ return /* @__PURE__ */ jsxs("li", { className: cx("uix-step", className), "data-state": CSS_STATE[state], "aria-current": state === "current" ? "step" : void 0, ...props, children: [
30
+ /* @__PURE__ */ jsx("span", { className: "uix-step__marker", "aria-hidden": "true", children: glyph }),
31
+ /* @__PURE__ */ jsxs("span", { className: "uix-step__text", children: [
32
+ titled,
33
+ description != null && /* @__PURE__ */ jsx("span", { className: "uix-step__desc", children: description })
34
+ ] }),
35
+ !last && /* @__PURE__ */ jsx("span", { className: "uix-step__connector", "aria-hidden": "true", "data-done": state === "complete" || void 0 })
36
+ ] });
37
+ }
38
+ export {
39
+ DEFAULT_STEP_STATE_LABELS,
40
+ Step,
41
+ Steps
42
+ };
@@ -1,7 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
+ import { ToastStore } from '../toast-store.js';
3
4
 
4
- type ToastTone = 'success' | 'danger' | 'info';
5
+ type ToastTone = 'success' | 'danger' | 'info' | 'warning';
5
6
  interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
6
7
  /** TENSOR RX-125 (UIX-04): translatable; English default. */
7
8
  dismissLabel?: string;
@@ -9,15 +10,26 @@ interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
9
10
  message?: ReactNode;
10
11
  tone?: ToastTone;
11
12
  icon?: ReactNode;
13
+ /** A button (or link) after the text, e.g. "Undo" or "Copy details" (HAR-1363). */
14
+ action?: ReactNode;
12
15
  onClose?: () => void;
13
16
  leaving?: boolean;
14
17
  }
15
- declare function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel, className, ...props }: ToastProps): react.JSX.Element;
18
+ declare function Toast({ title, message, tone, icon, action, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }: ToastProps): react.JSX.Element;
16
19
  interface ToasterProps extends HTMLAttributes<HTMLDivElement> {
17
20
  /** TENSOR RX-125 (UIX-04): translatable; English default. */
18
21
  regionLabel?: string;
22
+ /**
23
+ * The queue to render (HAR-1363). Default: the store behind the exported `toast()`.
24
+ * Children still render first, so a hand-managed `<Toast>` keeps working.
25
+ */
26
+ store?: ToastStore;
27
+ /** At most this many store toasts on screen; later ones wait their turn. Default 3. */
28
+ limit?: number;
29
+ /** Corner of the viewport. Default `bottom-end`. */
30
+ position?: 'bottom-end' | 'bottom-start' | 'bottom-center' | 'top-end' | 'top-start' | 'top-center';
19
31
  children?: ReactNode;
20
32
  }
21
- declare function Toaster({ children, regionLabel, className, ...props }: ToasterProps): react.JSX.Element;
33
+ declare function Toaster({ children, regionLabel: regionLabelProp, store, limit, position, className, onMouseEnter, onMouseLeave, onFocus, onBlur, onKeyDown, ...props }: ToasterProps): react.JSX.Element;
22
34
 
23
35
  export { Toast, type ToastProps, type ToastTone, Toaster, type ToasterProps };
@@ -1,9 +1,13 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { cx } from "../cx.js";
5
+ import { useUixLabels } from "../labels-context.js";
6
+ import { toast as defaultToast } from "../toast-store.js";
5
7
  const ToasterContext = createContext(null);
6
- function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel = "Dismiss", className, ...props }) {
8
+ function Toast({ title, message, tone, icon, action, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }) {
9
+ const uixLabels = useUixLabels();
10
+ const dismissLabel = dismissLabelProp ?? uixLabels.toast?.dismiss ?? "Dismiss";
7
11
  const assertive = tone === "danger";
8
12
  const toaster = useContext(ToasterContext);
9
13
  const bodyRef = useRef(null);
@@ -39,12 +43,108 @@ function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel = "D
39
43
  title && /* @__PURE__ */ jsx("div", { className: "uix-toast__title", children: title }),
40
44
  message && /* @__PURE__ */ jsx("div", { className: "uix-toast__msg", children: message })
41
45
  ] }),
42
- onClose && /* @__PURE__ */ jsx("button", { className: "uix-toast__close", onClick: handleClose, "aria-label": dismissLabel, children: "\xD7" })
46
+ action != null && /* @__PURE__ */ jsx("div", { className: "uix-toast__action-slot", children: action }),
47
+ onClose && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-toast__close", onClick: handleClose, "aria-label": dismissLabel, children: "\xD7" })
43
48
  ]
44
49
  }
45
50
  );
46
51
  }
47
- function Toaster({ children, regionLabel = "Notifications", className, ...props }) {
52
+ const KIND_TONE = {
53
+ success: "success",
54
+ error: "danger",
55
+ warning: "warning",
56
+ info: "info",
57
+ loading: "info",
58
+ default: void 0
59
+ };
60
+ const svg = (path) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", children: path });
61
+ const KIND_GLYPH = {
62
+ success: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
63
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "7.25" }),
64
+ /* @__PURE__ */ jsx("path", { d: "M6.75 10.25l2.25 2.25 4.25-4.75" })
65
+ ] })),
66
+ error: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "7.25" }),
68
+ /* @__PURE__ */ jsx("path", { d: "M10 6.25v4.5M10 13.5v.25" })
69
+ ] })),
70
+ warning: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
71
+ /* @__PURE__ */ jsx("path", { d: "M10 3.25l7.25 13H2.75z" }),
72
+ /* @__PURE__ */ jsx("path", { d: "M10 8.25v3.5M10 14v.25" })
73
+ ] })),
74
+ info: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
75
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "7.25" }),
76
+ /* @__PURE__ */ jsx("path", { d: "M10 9.25v4.5M10 6.5v.25" })
77
+ ] })),
78
+ loading: /* @__PURE__ */ jsx("span", { className: "uix-spinner uix-toast__spinner" })
79
+ };
80
+ function StoreToast({ record, store }) {
81
+ const action = record.action && /* @__PURE__ */ jsx(
82
+ "button",
83
+ {
84
+ type: "button",
85
+ className: "uix-toast__action",
86
+ onClick: () => {
87
+ record.action.onClick();
88
+ store.dismiss(record.id, "action");
89
+ },
90
+ children: record.action.label
91
+ }
92
+ );
93
+ return /* @__PURE__ */ jsx(
94
+ Toast,
95
+ {
96
+ "data-toast-id": record.id,
97
+ tone: KIND_TONE[record.kind],
98
+ icon: record.icon ?? KIND_GLYPH[record.kind],
99
+ title: record.message,
100
+ message: record.description,
101
+ action,
102
+ leaving: record.leaving,
103
+ "aria-busy": record.kind === "loading" || void 0,
104
+ onClose: () => store.dismiss(record.id, "close")
105
+ }
106
+ );
107
+ }
108
+ function useToastTimers(store, visible, paused) {
109
+ const timers = useRef(/* @__PURE__ */ new Map());
110
+ useEffect(() => {
111
+ const now = Date.now();
112
+ const map = timers.current;
113
+ const live = new Map(visible.filter((r) => !r.leaving && r.duration != null).map((r) => [r.id, r]));
114
+ for (const [id, timer] of map) {
115
+ const record = live.get(id);
116
+ if (!record || record.version !== timer.version) {
117
+ if (timer.handle != null) clearTimeout(timer.handle);
118
+ map.delete(id);
119
+ }
120
+ }
121
+ for (const record of live.values()) {
122
+ let timer = map.get(record.id);
123
+ if (!timer) {
124
+ timer = { version: record.version, remaining: record.duration, start: now };
125
+ map.set(record.id, timer);
126
+ }
127
+ if (paused) {
128
+ if (timer.handle != null) {
129
+ clearTimeout(timer.handle);
130
+ timer.handle = void 0;
131
+ timer.remaining -= now - timer.start;
132
+ }
133
+ } else if (timer.handle == null) {
134
+ timer.start = now;
135
+ const id = record.id;
136
+ timer.handle = window.setTimeout(() => store.dismiss(id, "timeout"), Math.max(0, timer.remaining));
137
+ }
138
+ }
139
+ });
140
+ useEffect(() => () => {
141
+ for (const timer of timers.current.values()) if (timer.handle != null) clearTimeout(timer.handle);
142
+ timers.current.clear();
143
+ }, []);
144
+ }
145
+ function Toaster({ children, regionLabel: regionLabelProp, store = defaultToast.store, limit = 3, position = "bottom-end", className, onMouseEnter, onMouseLeave, onFocus, onBlur, onKeyDown, ...props }) {
146
+ const uixLabels = useUixLabels();
147
+ const regionLabel = regionLabelProp ?? uixLabels.toaster?.region ?? "Notifications";
48
148
  const [announced, setAnnounced] = useState("");
49
149
  const clearTimer = useRef(null);
50
150
  const announce = useCallback((text) => {
@@ -56,10 +156,59 @@ function Toaster({ children, regionLabel = "Notifications", className, ...props
56
156
  if (clearTimer.current != null) clearTimeout(clearTimer.current);
57
157
  }, []);
58
158
  const ctx = useMemo(() => ({ announce }), [announce]);
59
- return /* @__PURE__ */ jsx(ToasterContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs("div", { className: cx("uix-toaster", className), role: "region", "aria-label": regionLabel, ...props, children: [
60
- /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announced }),
61
- children
62
- ] }) });
159
+ const records = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
160
+ const visible = useMemo(() => records.slice(-Math.max(1, limit)), [records, limit]);
161
+ const [hovered, setHovered] = useState(false);
162
+ const [focused, setFocused] = useState(false);
163
+ const [hidden, setHidden] = useState(false);
164
+ useEffect(() => {
165
+ const sync = () => setHidden(document.visibilityState === "hidden");
166
+ sync();
167
+ document.addEventListener("visibilitychange", sync);
168
+ return () => document.removeEventListener("visibilitychange", sync);
169
+ }, []);
170
+ useToastTimers(store, visible, hovered || focused || hidden);
171
+ const handleKeyDown = (event) => {
172
+ onKeyDown?.(event);
173
+ if (event.defaultPrevented || event.key !== "Escape") return;
174
+ const id = event.target.closest("[data-toast-id]")?.dataset.toastId;
175
+ if (!id) return;
176
+ event.preventDefault();
177
+ event.stopPropagation();
178
+ store.dismiss(id, "close");
179
+ };
180
+ return /* @__PURE__ */ jsx(ToasterContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs(
181
+ "div",
182
+ {
183
+ className: cx("uix-toaster", className),
184
+ role: "region",
185
+ "aria-label": regionLabel,
186
+ "data-position": position === "bottom-end" ? void 0 : position,
187
+ onMouseEnter: (e) => {
188
+ setHovered(true);
189
+ onMouseEnter?.(e);
190
+ },
191
+ onMouseLeave: (e) => {
192
+ setHovered(false);
193
+ onMouseLeave?.(e);
194
+ },
195
+ onFocus: (e) => {
196
+ setFocused(true);
197
+ onFocus?.(e);
198
+ },
199
+ onBlur: (e) => {
200
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocused(false);
201
+ onBlur?.(e);
202
+ },
203
+ onKeyDown: handleKeyDown,
204
+ ...props,
205
+ children: [
206
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announced }),
207
+ children,
208
+ visible.map((record) => /* @__PURE__ */ jsx(StoreToast, { record, store }, `${record.id}:${record.kind}`))
209
+ ]
210
+ }
211
+ ) });
63
212
  }
64
213
  export {
65
214
  Toast,
@@ -2,9 +2,15 @@ import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
3
  import { Placement } from '../overlay-position.js';
4
4
 
5
- interface TooltipProps extends HTMLAttributes<HTMLSpanElement> {
6
- /** Tooltip text. */
7
- label: string;
5
+ interface TooltipProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'content'> {
6
+ /** Tooltip text. Pass this or `content`. */
7
+ label?: string;
8
+ /**
9
+ * Rich, non-interactive tooltip content, e.g. a short list (HAR-1349; TENSOR A3). It is
10
+ * still a `role="tooltip"` description: no links or buttons inside — use `InfoTip` or
11
+ * `Popover` for content the user must reach. Wins over `label` when both are set.
12
+ */
13
+ content?: ReactNode;
8
14
  /** Preferred placement. Default `'top'`. */
9
15
  placement?: Placement;
10
16
  children?: ReactNode;
@@ -17,6 +23,6 @@ interface TooltipProps extends HTMLAttributes<HTMLSpanElement> {
17
23
  * (document-level, per WCAG 1.4.13), and hoverable: the bubble stays open while
18
24
  * the pointer is over it (UIX-A11Y-1).
19
25
  */
20
- declare function Tooltip({ label, placement, className, children, ...props }: TooltipProps): react.JSX.Element;
26
+ declare function Tooltip({ label, content, placement, className, children, ...props }: TooltipProps): react.JSX.Element;
21
27
 
22
28
  export { Tooltip, type TooltipProps };
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { cloneElement, isValidElement, useEffect, useId, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
6
- function Tooltip({ label, placement = "top", className, children, ...props }) {
6
+ function Tooltip({ label, content, placement = "top", className, children, ...props }) {
7
7
  const triggerRef = useRef(null);
8
8
  const bubbleRef = useRef(null);
9
9
  const closeTimer = useRef(null);
@@ -92,7 +92,7 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
92
92
  onMouseEnter: cancelClose,
93
93
  onMouseLeave: scheduleHide,
94
94
  ...bubbleAttr,
95
- children: label
95
+ children: content ?? label
96
96
  }
97
97
  )
98
98
  ]
package/dist/emoji.cjs CHANGED
@@ -939,7 +939,7 @@ function EditorIcon({ name, ...props }) {
939
939
  // src/components/Tooltip.tsx
940
940
  var import_react5 = require("react");
941
941
  var import_jsx_runtime5 = require("react/jsx-runtime");
942
- function Tooltip({ label, placement = "top", className, children, ...props }) {
942
+ function Tooltip({ label, content, placement = "top", className, children, ...props }) {
943
943
  const triggerRef = (0, import_react5.useRef)(null);
944
944
  const bubbleRef = (0, import_react5.useRef)(null);
945
945
  const closeTimer = (0, import_react5.useRef)(null);
@@ -1028,7 +1028,7 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
1028
1028
  onMouseEnter: cancelClose,
1029
1029
  onMouseLeave: scheduleHide,
1030
1030
  ...bubbleAttr,
1031
- children: label
1031
+ children: content ?? label
1032
1032
  }
1033
1033
  )
1034
1034
  ]