@tensor_1/react 2.26.2 → 2.27.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 (68) hide show
  1. package/README.md +51 -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/EmojiPicker.d.ts +3 -0
  23. package/dist/components/EmojiPicker.js +109 -54
  24. package/dist/components/Kbd.d.ts +27 -0
  25. package/dist/components/Kbd.js +45 -0
  26. package/dist/components/Menu.d.ts +51 -0
  27. package/dist/components/Menu.js +177 -0
  28. package/dist/components/Modal.d.ts +1 -1
  29. package/dist/components/Modal.js +4 -1
  30. package/dist/components/Pagination.d.ts +36 -5
  31. package/dist/components/Pagination.js +59 -37
  32. package/dist/components/Peek.d.ts +1 -1
  33. package/dist/components/Peek.js +6 -1
  34. package/dist/components/Popover.d.ts +15 -5
  35. package/dist/components/Popover.js +66 -6
  36. package/dist/components/SchedulingCalendar.d.ts +47 -2
  37. package/dist/components/SchedulingCalendar.js +69 -22
  38. package/dist/components/SearchSuggest.d.ts +1 -1
  39. package/dist/components/SearchSuggest.js +4 -1
  40. package/dist/components/Sidebar.d.ts +7 -2
  41. package/dist/components/Sidebar.js +15 -4
  42. package/dist/components/Stat.d.ts +16 -0
  43. package/dist/components/Stat.js +12 -5
  44. package/dist/components/Steps.d.ts +48 -0
  45. package/dist/components/Steps.js +42 -0
  46. package/dist/components/Toast.d.ts +2 -2
  47. package/dist/components/Toast.js +7 -2
  48. package/dist/components/Tooltip.d.ts +10 -4
  49. package/dist/components/Tooltip.js +2 -2
  50. package/dist/{emoji-image-WQLh8ZQP.d.cts → emoji-image-6mPZ15Zy.d.cts} +4 -1
  51. package/dist/emoji.cjs +326 -76
  52. package/dist/emoji.d.cts +3 -3
  53. package/dist/hooks/useAnchoredPosition.d.ts +13 -5
  54. package/dist/hooks/useAnchoredPosition.js +146 -15
  55. package/dist/index.cjs +2278 -1372
  56. package/dist/index.d.cts +622 -176
  57. package/dist/index.d.ts +10 -4
  58. package/dist/index.js +24 -2
  59. package/dist/labels-context.d.ts +72 -0
  60. package/dist/labels-context.js +23 -0
  61. package/dist/link.d.ts +20 -0
  62. package/dist/link.js +9 -0
  63. package/dist/{overlay-position-CEGSz6Z2.d.cts → overlay-position-Cjv5E7mn.d.cts} +7 -0
  64. package/dist/overlay-position.d.ts +7 -0
  65. package/dist/overlay-position.js +7 -2
  66. package/dist/rich-text.cjs +324 -74
  67. package/dist/rich-text.d.cts +3 -3
  68. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo, useRef, useState } from "react";
4
4
  import { addCalendarDays, addCalendarMonths, buildMonthGrid, startOfMonth, zonedDateSpan } from "../calendar-model.js";
5
5
  import { cx } from "../cx.js";
6
+ import { useUixLabels } from "../labels-context.js";
6
7
  import { StatusPill } from "./StatusPill.js";
7
8
  import { fillLabel } from "../fill-label.js";
8
9
  const DEFAULT_SCHEDULING_CALENDAR_LABELS = {
@@ -20,10 +21,19 @@ const DEFAULT_SCHEDULING_CALENDAR_LABELS = {
20
21
  grid: "{view} schedule",
21
22
  entry: "{title}, {state}, {start} to {end}",
22
23
  legend: "Schedule state legend",
24
+ moreEntries: "+{count} more",
25
+ moreEntriesLabel: "{count} more entries on {date}",
26
+ fewerEntries: "Show fewer",
23
27
  states: { scheduled: "Scheduled", conflicted: "Conflicted", "in-progress": "In progress", "blackout-violation": "Blackout violation" }
24
28
  };
25
29
  const stateTone = (state) => state === "scheduled" ? "info" : state === "in-progress" ? "success" : "danger";
26
- const dateLabel = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
30
+ const utcDate = (date) => /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
31
+ const INTL_PARTS = {
32
+ day: { dateStyle: "full", timeZone: "UTC" },
33
+ weekday: { weekday: "short", timeZone: "UTC" },
34
+ month: { month: "long", year: "numeric", timeZone: "UTC" }
35
+ };
36
+ const intlDate = (date, part, locale) => new Intl.DateTimeFormat(locale, INTL_PARTS[part]).format(utcDate(date));
27
37
  const EMPTY_OVERLAYS = [];
28
38
  function SchedulingCalendar({
29
39
  entries,
@@ -37,28 +47,38 @@ function SchedulingCalendar({
37
47
  filter,
38
48
  renderEntry,
39
49
  locale,
50
+ weekStartsOn = 1,
51
+ formatDate,
52
+ formatInstant: formatInstantProp,
53
+ maxEntriesPerDay,
54
+ onShowMore,
55
+ renderDayBadge,
40
56
  loading,
41
57
  error,
42
58
  onRetry,
43
59
  className,
44
60
  labels: labelOverrides
45
61
  }) {
62
+ const uixLabels = useUixLabels();
46
63
  const labels = {
47
64
  ...DEFAULT_SCHEDULING_CALENDAR_LABELS,
65
+ ...uixLabels.schedulingCalendar,
48
66
  ...labelOverrides,
49
- states: { ...DEFAULT_SCHEDULING_CALENDAR_LABELS.states, ...labelOverrides?.states }
67
+ states: { ...DEFAULT_SCHEDULING_CALENDAR_LABELS.states, ...uixLabels.schedulingCalendar?.states, ...labelOverrides?.states }
50
68
  };
51
69
  const stateLabel = labels.states;
52
70
  const viewLabel = { month: labels.viewMonth, week: labels.viewWeek, agenda: labels.viewAgenda };
53
71
  const [internalView, setInternalView] = useState("month");
54
72
  const view = controlledView ?? internalView;
55
73
  const [activeDate, setActiveDate] = useState(anchorDate);
74
+ const [expandedDates, setExpandedDates] = useState(() => /* @__PURE__ */ new Set());
75
+ const dateText = (date, part) => formatDate?.(date, part) ?? intlDate(date, part, locale);
56
76
  const gridRef = useRef(null);
57
77
  const visibleEntries = useMemo(() => entries.filter((entry) => filter?.(entry) ?? true), [entries, filter]);
58
78
  const entrySpans = useMemo(() => new Map(visibleEntries.map((entry) => [entry.id, zonedDateSpan(entry.start, entry.end, timeZone)])), [visibleEntries, timeZone]);
59
79
  const overlaySpans = useMemo(() => new Map(overlays.map((overlay) => [overlay.id, { start: overlay.start.slice(0, 10), end: overlay.end.slice(0, 10) }])), [overlays]);
60
- const monthDays = useMemo(() => buildMonthGrid(startOfMonth(anchorDate)), [anchorDate]);
61
- const weekday = ((/* @__PURE__ */ new Date(`${anchorDate}T00:00:00Z`)).getUTCDay() + 6) % 7;
80
+ const monthDays = useMemo(() => buildMonthGrid(startOfMonth(anchorDate), weekStartsOn), [anchorDate, weekStartsOn]);
81
+ const weekday = (utcDate(anchorDate).getUTCDay() - weekStartsOn + 7) % 7;
62
82
  const weekStart = addCalendarDays(anchorDate, -weekday);
63
83
  const weekDays = useMemo(() => Array.from({ length: 7 }, (_, index) => addCalendarDays(weekStart, index)), [weekStart]);
64
84
  const days = useMemo(() => view === "month" ? monthDays.map((day) => day.date) : weekDays, [view, monthDays, weekDays]);
@@ -95,14 +115,26 @@ function SchedulingCalendar({
95
115
  const span = overlaySpans.get(overlay.id);
96
116
  return date >= span.start && date <= span.end;
97
117
  });
98
- const formatInstant = (instant) => new Intl.DateTimeFormat(locale, { timeZone, dateStyle: "medium", timeStyle: "short" }).format(new Date(instant));
118
+ const formatInstant = (instant) => formatInstantProp?.(instant) ?? new Intl.DateTimeFormat(locale, { timeZone, dateStyle: "medium", timeStyle: "short" }).format(new Date(instant));
119
+ const toggleMore = (date, dayEntries) => {
120
+ if (onShowMore) {
121
+ onShowMore(date, dayEntries);
122
+ return;
123
+ }
124
+ setExpandedDates((current) => {
125
+ const next = new Set(current);
126
+ if (next.has(date)) next.delete(date);
127
+ else next.add(date);
128
+ return next;
129
+ });
130
+ };
99
131
  return /* @__PURE__ */ jsxs("section", { className: cx("uix-scheduling-calendar", className), "aria-label": fillLabel(labels.region, { timeZone }), children: [
100
132
  /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__header", children: [
101
133
  /* @__PURE__ */ jsxs("div", { children: [
102
134
  /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(-1), disabled: !onAnchorDateChange, children: labels.previous }),
103
135
  /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(1), disabled: !onAnchorDateChange, children: labels.next })
104
136
  ] }),
105
- /* @__PURE__ */ jsx("strong", { children: view === "month" ? new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${anchorDate}T00:00:00Z`)) : `${dateLabel(weekStart, locale)} \u2013 ${dateLabel(weekDays[weekDays.length - 1], locale)}` }),
137
+ /* @__PURE__ */ jsx("strong", { children: view === "month" ? dateText(startOfMonth(anchorDate), "month") : `${dateText(weekStart, "day")} \u2013 ${dateText(weekDays[weekDays.length - 1], "day")}` }),
106
138
  /* @__PURE__ */ jsx("div", { className: "uix-segmented", role: "group", "aria-label": labels.viewGroup, children: ["month", "week", "agenda"].map((option) => /* @__PURE__ */ jsx("button", { type: "button", className: "uix-segmented__option", "aria-pressed": view === option, "data-selected": view === option || void 0, onClick: () => setView(option), children: viewLabel[option] }, option)) })
107
139
  ] }),
108
140
  loading ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__state", role: "status", children: labels.loading }) : error ? /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__state", role: "alert", children: [
@@ -122,22 +154,37 @@ function SchedulingCalendar({
122
154
  ] }),
123
155
  /* @__PURE__ */ jsx(StatusPill, { tone: stateTone(state), children: stateLabel[state] })
124
156
  ] }) }, entry.id);
125
- }) }) }) : /* @__PURE__ */ jsx("div", { ref: gridRef, className: cx("uix-scheduling-calendar__grid", view === "week" && "uix-scheduling-calendar__grid--week"), role: "group", "aria-label": fillLabel(labels.grid, { view: viewLabel[view] }), children: days.map((date, index) => {
126
- const dayEntries = entriesFor(date);
127
- const dayOverlays = overlaysFor(date);
128
- return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__day", "data-outside": view === "month" && !monthDays[index].inMonth || void 0, children: [
129
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__date", "data-calendar-date": date, tabIndex: activeDate === date ? 0 : -1, onFocus: () => setActiveDate(date), onKeyDown: (event) => onDayKeyDown(event, date), "aria-label": dateLabel(date, locale), children: Number(date.slice(-2)) }),
130
- dayOverlays.map((overlay) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__overlay", "data-kind": overlay.kind, children: overlay.label }, overlay.id)),
131
- /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__entries", children: dayEntries.map((entry) => {
132
- const span = entrySpans.get(entry.id);
133
- const state = entry.state ?? "scheduled";
134
- return /* @__PURE__ */ jsxs("button", { type: "button", className: "uix-scheduling-calendar__entry", "data-state": state, "data-range-start": date === span.start || void 0, "data-range-end": date === span.end || void 0, onClick: () => onSelectEntry?.(entry), "aria-label": fillLabel(labels.entry, { title: entry.title, state: stateLabel[state], start: formatInstant(entry.start), end: formatInstant(entry.end) }), children: [
135
- /* @__PURE__ */ jsx("span", { children: renderEntry?.(entry) ?? entry.title }),
136
- /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: stateLabel[state] })
137
- ] }, entry.id);
138
- }) })
139
- ] }, date);
140
- }) }),
157
+ }) }) }) : /* @__PURE__ */ jsxs("div", { ref: gridRef, className: cx("uix-scheduling-calendar__grid", view === "week" && "uix-scheduling-calendar__grid--week"), role: "group", "aria-label": fillLabel(labels.grid, { view: viewLabel[view] }), children: [
158
+ days.slice(0, 7).map((date) => /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__weekday", "aria-hidden": "true", children: dateText(date, "weekday") }, `weekday-${date}`)),
159
+ days.map((date, index) => {
160
+ const dayEntries = entriesFor(date);
161
+ const dayOverlays = overlaysFor(date);
162
+ const overflowing = maxEntriesPerDay !== void 0 && dayEntries.length > maxEntriesPerDay;
163
+ const collapsed = overflowing && (onShowMore !== void 0 || !expandedDates.has(date));
164
+ const shownEntries = collapsed ? dayEntries.slice(0, Math.max(0, maxEntriesPerDay)) : dayEntries;
165
+ const hiddenCount = dayEntries.length - shownEntries.length;
166
+ const badge = renderDayBadge?.(date, dayEntries);
167
+ const dateButton = /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__date", "data-calendar-date": date, tabIndex: activeDate === date ? 0 : -1, onFocus: () => setActiveDate(date), onKeyDown: (event) => onDayKeyDown(event, date), "aria-label": dateText(date, "day"), children: Number(date.slice(-2)) });
168
+ return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__day", "data-outside": view === "month" && !monthDays[index].inMonth || void 0, children: [
169
+ badge == null || badge === false ? dateButton : /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__dayhead", children: [
170
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__badge", children: badge }),
171
+ dateButton
172
+ ] }),
173
+ dayOverlays.map((overlay) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__overlay", "data-kind": overlay.kind, children: overlay.label }, overlay.id)),
174
+ /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__entries", children: [
175
+ shownEntries.map((entry) => {
176
+ const span = entrySpans.get(entry.id);
177
+ const state = entry.state ?? "scheduled";
178
+ return /* @__PURE__ */ jsxs("button", { type: "button", className: "uix-scheduling-calendar__entry", "data-state": state, "data-range-start": date === span.start || void 0, "data-range-end": date === span.end || void 0, onClick: () => onSelectEntry?.(entry), "aria-label": fillLabel(labels.entry, { title: entry.title, state: stateLabel[state], start: formatInstant(entry.start), end: formatInstant(entry.end) }), children: [
179
+ /* @__PURE__ */ jsx("span", { children: renderEntry?.(entry) ?? entry.title }),
180
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: stateLabel[state] })
181
+ ] }, entry.id);
182
+ }),
183
+ overflowing && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__more", "aria-expanded": onShowMore ? void 0 : !collapsed, "aria-label": collapsed ? fillLabel(labels.moreEntriesLabel, { count: hiddenCount, date: dateText(date, "day") }) : void 0, onClick: () => toggleMore(date, dayEntries), children: collapsed ? fillLabel(labels.moreEntries, { count: hiddenCount }) : labels.fewerEntries })
184
+ ] })
185
+ ] }, date);
186
+ })
187
+ ] }),
141
188
  /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__legend", "aria-label": labels.legend, children: ["scheduled", "conflicted", "in-progress", "blackout-violation"].map((state) => /* @__PURE__ */ jsx("span", { "data-state": state, children: stateLabel[state] }, state)) })
142
189
  ] });
143
190
  }
@@ -82,6 +82,6 @@ interface SearchSuggestProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onCha
82
82
  * (`aria-activedescendant`), Enter opens it (the first row when none is active), Escape closes the
83
83
  * list and a second Escape clears the text.
84
84
  */
85
- declare function SearchSuggest({ value, onValueChange, options, onSelect, label, placeholder, highlight, heading, headingAction, footer, loading, loadingLabel, empty, error, shortcutHint, clearLabel, status, size, open: openProp, strategy, onOpenChange, inputRef, autoFocus, name, className, onBlur, ...props }: SearchSuggestProps): react.JSX.Element;
85
+ declare function SearchSuggest({ value, onValueChange, options, onSelect, label, placeholder, highlight, heading, headingAction, footer, loading, loadingLabel, empty, error, shortcutHint, clearLabel: clearLabelProp, status, size, open: openProp, strategy, onOpenChange, inputRef, autoFocus, name, className, onBlur, ...props }: SearchSuggestProps): react.JSX.Element;
86
86
 
87
87
  export { SearchSuggest, type SearchSuggestAction, type SearchSuggestOption, type SearchSuggestProps };
@@ -2,6 +2,7 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
+ import { useUixLabels } from "../labels-context.js";
5
6
  import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
6
7
  import { searchSegments } from "../search-suggest-model.js";
7
8
  import { Spinner } from "./Spinner.js";
@@ -33,7 +34,7 @@ function SearchSuggest({
33
34
  empty,
34
35
  error,
35
36
  shortcutHint,
36
- clearLabel = "Clear search",
37
+ clearLabel: clearLabelProp,
37
38
  status,
38
39
  size = "md",
39
40
  open: openProp,
@@ -46,6 +47,8 @@ function SearchSuggest({
46
47
  onBlur,
47
48
  ...props
48
49
  }) {
50
+ const uixLabels = useUixLabels();
51
+ const clearLabel = clearLabelProp ?? uixLabels.searchSuggest?.clear ?? "Clear search";
49
52
  const id = useId();
50
53
  const listboxId = `${id}-listbox`;
51
54
  const headingId = `${id}-heading`;
@@ -11,7 +11,7 @@ interface SidebarProps extends HTMLAttributes<HTMLElement> {
11
11
  expandLabel?: string;
12
12
  collapseLabel?: string;
13
13
  }
14
- declare function Sidebar({ collapsed, nav, onToggle, brand, toggleIcon, children, expandLabel, collapseLabel, className, ...props }: SidebarProps): react.JSX.Element;
14
+ declare function Sidebar({ collapsed, nav, onToggle, brand, toggleIcon, children, expandLabel: expandLabelProp, collapseLabel: collapseLabelProp, className, ...props }: SidebarProps): react.JSX.Element;
15
15
  interface SidebarSectionProps {
16
16
  label?: string;
17
17
  children?: ReactNode;
@@ -22,9 +22,14 @@ interface NavItemProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
22
22
  badge?: ReactNode;
23
23
  active?: boolean;
24
24
  as?: 'a' | 'button';
25
+ /**
26
+ * A second line under the label (a sub-label, a count, a long German term); it wraps,
27
+ * and the row grows to fit (HAR-1355; TENSOR B31). Hidden in the rail.
28
+ */
29
+ description?: ReactNode;
25
30
  children?: ReactNode;
26
31
  }
27
- declare function NavItem({ icon, badge, active, as: Tag, children, className, ...props }: NavItemProps): react.JSX.Element;
32
+ declare function NavItem({ icon, badge, active, as: Tag, description, children, className, ...props }: NavItemProps): react.JSX.Element;
28
33
  interface NavGroupProps {
29
34
  icon?: ReactNode;
30
35
  label: ReactNode;
@@ -2,8 +2,12 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
+ import { useUixLabels } from "../labels-context.js";
5
6
  const ChevronIcon = () => /* @__PURE__ */ jsx("svg", { className: "uix-navgroup__chevron", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2 4l4 4 4-4" }) });
6
- function Sidebar({ collapsed, nav, onToggle, brand, toggleIcon, children, expandLabel = "Expand sidebar", collapseLabel = "Collapse sidebar", className, ...props }) {
7
+ function Sidebar({ collapsed, nav, onToggle, brand, toggleIcon, children, expandLabel: expandLabelProp, collapseLabel: collapseLabelProp, className, ...props }) {
8
+ const uixLabels = useUixLabels();
9
+ const expandLabel = expandLabelProp ?? uixLabels.sidebar?.expand ?? "Expand sidebar";
10
+ const collapseLabel = collapseLabelProp ?? uixLabels.sidebar?.collapse ?? "Collapse sidebar";
7
11
  const isCollapsed = collapsed ?? nav === "rail";
8
12
  return /* @__PURE__ */ jsxs("nav", { className: cx("uix-sidebar", className), "data-collapsed": isCollapsed || void 0, "data-nav": nav, ...props, children: [
9
13
  (brand != null || onToggle) && /* @__PURE__ */ jsxs("div", { className: "uix-sidebar__head", children: [
@@ -23,7 +27,12 @@ function SidebarSection({ label, children }) {
23
27
  children
24
28
  ] });
25
29
  }
26
- function NavItem({ icon, badge, active, as: Tag = "a", children, className, ...props }) {
30
+ function NavItem({ icon, badge, active, as: Tag = "a", description, children, className, ...props }) {
31
+ const text = description == null ? /* @__PURE__ */ jsx("span", { className: "uix-navitem__label", children }) : /* @__PURE__ */ jsxs("span", { className: "uix-navitem__text", children: [
32
+ /* @__PURE__ */ jsx("span", { className: "uix-navitem__label", children }),
33
+ /* @__PURE__ */ jsx("span", { className: "uix-navitem__desc", children: description })
34
+ ] });
35
+ const multiline = description == null ? void 0 : true;
27
36
  if (Tag === "button") {
28
37
  const { href: _href, ...btnProps } = props;
29
38
  return /* @__PURE__ */ jsxs(
@@ -32,10 +41,11 @@ function NavItem({ icon, badge, active, as: Tag = "a", children, className, ...p
32
41
  type: "button",
33
42
  className: cx("uix-navitem", className),
34
43
  "aria-current": active ? "page" : void 0,
44
+ "data-multiline": multiline,
35
45
  ...btnProps,
36
46
  children: [
37
47
  icon && /* @__PURE__ */ jsx("span", { className: "uix-navitem__icon", "aria-hidden": "true", children: icon }),
38
- /* @__PURE__ */ jsx("span", { className: "uix-navitem__label", children }),
48
+ text,
39
49
  badge != null && /* @__PURE__ */ jsx("span", { className: "uix-navitem__badge", children: badge })
40
50
  ]
41
51
  }
@@ -46,10 +56,11 @@ function NavItem({ icon, badge, active, as: Tag = "a", children, className, ...p
46
56
  {
47
57
  className: cx("uix-navitem", className),
48
58
  "aria-current": active ? "page" : void 0,
59
+ "data-multiline": multiline,
49
60
  ...props,
50
61
  children: [
51
62
  icon && /* @__PURE__ */ jsx("span", { className: "uix-navitem__icon", "aria-hidden": "true", children: icon }),
52
- /* @__PURE__ */ jsx("span", { className: "uix-navitem__label", children }),
63
+ text,
53
64
  badge != null && /* @__PURE__ */ jsx("span", { className: "uix-navitem__badge", children: badge })
54
65
  ]
55
66
  }
@@ -1,5 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
+ import { UixRenderLink } from '../link.js';
3
4
 
4
5
  interface StatTrend {
5
6
  direction: 'up' | 'down';
@@ -32,6 +33,21 @@ interface StatProps extends HTMLAttributes<HTMLElement> {
32
33
  };
33
34
  /** Whether the editor this tile opens is currently open (`aria-expanded`). */
34
35
  expanded?: boolean;
36
+ /**
37
+ * What `onActivate` opens, for `aria-haspopup` (HAR-1350; TENSOR B6). Default `'dialog'`;
38
+ * `'menu'` for a status menu; `false` for an in-place action (no chevron either).
39
+ */
40
+ haspopup?: 'dialog' | 'menu' | 'listbox' | false;
41
+ /**
42
+ * Makes the whole tile a link (HAR-1350; TENSOR B7, MOTUS B-A7). Server-renderable, unlike
43
+ * `onActivate`. Ignored when `onActivate` is set. The forwarded ref reaches the `<a>` only
44
+ * without `renderLink`.
45
+ */
46
+ href?: string;
47
+ /** The link tile is the current page (`aria-current="page"`). */
48
+ current?: boolean;
49
+ /** Your router's link for `href`; default a plain `<a>`. */
50
+ renderLink?: UixRenderLink;
35
51
  }
36
52
  /**
37
53
  * KPI / stat tile over `.uix-stat`. With `onActivate` the tile is a button; the forwarded
@@ -1,23 +1,26 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useId } from "react";
3
3
  import { cx } from "../cx.js";
4
+ import { renderUixLink } from "../link.js";
4
5
  const Chevron = () => /* @__PURE__ */ jsx("span", { className: "uix-stat__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) }) });
5
- const Stat = forwardRef(function Stat2({ label, value, icon, meta, trend, tone = "neutral", size = "hero", onActivate, activateLabel, expanded, trendLabels, className, onClick, ...props }, ref) {
6
+ const Stat = forwardRef(function Stat2({ label, value, icon, meta, trend, tone = "neutral", size = "hero", onActivate, activateLabel, expanded, trendLabels, haspopup = "dialog", href, current, renderLink, className, onClick, ...props }, ref) {
6
7
  const id = useId();
7
8
  const interactive = onActivate != null;
9
+ const linked = !interactive && href != null;
8
10
  const classes = cx(
9
11
  "uix-stat",
10
12
  tone !== "neutral" && `uix-stat--${tone}`,
11
13
  size === "compact" && "uix-stat--compact",
12
- interactive && "uix-stat--interactive",
14
+ (interactive || linked) && "uix-stat--interactive",
15
+ linked && "uix-stat--link",
13
16
  className
14
17
  );
15
- const Row = interactive ? "span" : "div";
18
+ const Row = interactive || linked ? "span" : "div";
16
19
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
17
20
  /* @__PURE__ */ jsxs(Row, { className: "uix-stat__label", children: [
18
21
  /* @__PURE__ */ jsx("span", { id: interactive ? `${id}-label` : void 0, children: label }),
19
22
  icon,
20
- interactive && /* @__PURE__ */ jsx(Chevron, {})
23
+ interactive && haspopup !== false && /* @__PURE__ */ jsx(Chevron, {})
21
24
  ] }),
22
25
  /* @__PURE__ */ jsx(Row, { className: "uix-stat__value", id: interactive ? `${id}-value` : void 0, children: value }),
23
26
  (meta != null || trend != null) && /* @__PURE__ */ jsxs(Row, { className: "uix-stat__meta", id: interactive ? `${id}-meta` : void 0, children: [
@@ -29,6 +32,10 @@ const Stat = forwardRef(function Stat2({ label, value, icon, meta, trend, tone =
29
32
  meta
30
33
  ] })
31
34
  ] });
35
+ if (linked) {
36
+ const linkProps = { ...props, href, className: classes, onClick, "aria-current": current ? "page" : void 0, children: body };
37
+ return /* @__PURE__ */ jsx(Fragment, { children: renderLink ? renderUixLink(renderLink, linkProps) : /* @__PURE__ */ jsx("a", { ref, ...linkProps }) });
38
+ }
32
39
  if (!interactive) {
33
40
  return /* @__PURE__ */ jsx("div", { ref, className: classes, onClick, ...props, children: body });
34
41
  }
@@ -39,7 +46,7 @@ const Stat = forwardRef(function Stat2({ label, value, icon, meta, trend, tone =
39
46
  ...props,
40
47
  ref,
41
48
  className: classes,
42
- "aria-haspopup": "dialog",
49
+ "aria-haspopup": haspopup === false ? void 0 : haspopup,
43
50
  "aria-expanded": expanded,
44
51
  "aria-labelledby": `${id}-label ${id}-sep ${id}-value${activateLabel ? ` ${id}-action` : ""}`,
45
52
  "aria-describedby": meta != null || trend != null ? `${id}-meta` : void 0,
@@ -0,0 +1,48 @@
1
+ import * as react from 'react';
2
+ import { LiHTMLAttributes, ReactNode, HTMLAttributes } from 'react';
3
+ import { UixRenderLink } from '../link.js';
4
+
5
+ type StepState = 'complete' | 'current' | 'upcoming' | 'waiting' | 'error';
6
+ /** The state words read after each step's title (state is never colour alone). */
7
+ interface StepStateLabels {
8
+ complete: string;
9
+ current: string;
10
+ upcoming: string;
11
+ waiting: string;
12
+ error: string;
13
+ }
14
+ declare const DEFAULT_STEP_STATE_LABELS: StepStateLabels;
15
+ interface StepsProps extends HTMLAttributes<HTMLOListElement> {
16
+ /**
17
+ * `horizontal` (default) lays the steps in a row and stacks them below 40rem;
18
+ * `vertical` always stacks, with descriptions under each title.
19
+ */
20
+ orientation?: 'horizontal' | 'vertical';
21
+ /** Names the list, e.g. "Intake progress". */
22
+ label?: string;
23
+ stateLabels?: Partial<StepStateLabels>;
24
+ /** `Step` elements, in order. */
25
+ children?: ReactNode;
26
+ }
27
+ interface StepProps extends Omit<LiHTMLAttributes<HTMLLIElement>, 'title'> {
28
+ title: ReactNode;
29
+ description?: ReactNode;
30
+ state?: StepState;
31
+ /** Marker content. Default: the step number; ✓ when complete; ! on error. */
32
+ marker?: ReactNode;
33
+ /** A navigable step (a wizard you can jump back in). */
34
+ href?: string;
35
+ renderLink?: UixRenderLink;
36
+ onSelect?: () => void;
37
+ /** Set by `Steps`. */
38
+ index?: number;
39
+ /** Set by `Steps`. */
40
+ last?: boolean;
41
+ /** Set by `Steps`. */
42
+ stateLabels?: StepStateLabels;
43
+ }
44
+ /** Numbered progress through a multi-step flow (HAR-1362; TENSOR C13, MOTUS C-9). */
45
+ declare function Steps({ orientation, label, stateLabels, className, children, ...props }: StepsProps): react.JSX.Element;
46
+ declare function Step({ title, description, state, marker, href, renderLink, onSelect, index, last, stateLabels, className, ...props }: StepProps): react.JSX.Element;
47
+
48
+ export { DEFAULT_STEP_STATE_LABELS, Step, type StepProps, type StepState, type StepStateLabels, Steps, type StepsProps };
@@ -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
+ };
@@ -12,12 +12,12 @@ interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
12
12
  onClose?: () => void;
13
13
  leaving?: boolean;
14
14
  }
15
- declare function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel, className, ...props }: ToastProps): react.JSX.Element;
15
+ declare function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }: ToastProps): react.JSX.Element;
16
16
  interface ToasterProps extends HTMLAttributes<HTMLDivElement> {
17
17
  /** TENSOR RX-125 (UIX-04): translatable; English default. */
18
18
  regionLabel?: string;
19
19
  children?: ReactNode;
20
20
  }
21
- declare function Toaster({ children, regionLabel, className, ...props }: ToasterProps): react.JSX.Element;
21
+ declare function Toaster({ children, regionLabel: regionLabelProp, className, ...props }: ToasterProps): react.JSX.Element;
22
22
 
23
23
  export { Toast, type ToastProps, type ToastTone, Toaster, type ToasterProps };
@@ -2,8 +2,11 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
+ import { useUixLabels } from "../labels-context.js";
5
6
  const ToasterContext = createContext(null);
6
- function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel = "Dismiss", className, ...props }) {
7
+ function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }) {
8
+ const uixLabels = useUixLabels();
9
+ const dismissLabel = dismissLabelProp ?? uixLabels.toast?.dismiss ?? "Dismiss";
7
10
  const assertive = tone === "danger";
8
11
  const toaster = useContext(ToasterContext);
9
12
  const bodyRef = useRef(null);
@@ -44,7 +47,9 @@ function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel = "D
44
47
  }
45
48
  );
46
49
  }
47
- function Toaster({ children, regionLabel = "Notifications", className, ...props }) {
50
+ function Toaster({ children, regionLabel: regionLabelProp, className, ...props }) {
51
+ const uixLabels = useUixLabels();
52
+ const regionLabel = regionLabelProp ?? uixLabels.toaster?.region ?? "Notifications";
48
53
  const [announced, setAnnounced] = useState("");
49
54
  const clearTimer = useRef(null);
50
55
  const announce = useCallback((text) => {
@@ -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
  ]
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactElement } from 'react';
3
- import { P as Placement } from './overlay-position-CEGSz6Z2.cjs';
3
+ import { P as Placement } from './overlay-position-Cjv5E7mn.cjs';
4
4
 
5
5
  type EmojiLocale = 'en' | 'de';
6
6
  interface EmojiEntry {
@@ -106,6 +106,9 @@ declare const EMOJI_GRID_COLUMNS = 8;
106
106
  * Emoji picker in a native popover: search, quick picks, recently used and the
107
107
  * Unicode categories, with names in English or German. The emoji dataset loads on
108
108
  * first open. The grid is one tab stop; arrow keys, Home and End move within it.
109
+ * The picker is one size in every state (loading, searching, failed), so it keeps the
110
+ * side it opened on; it stays attached to its trigger while the page scrolls and
111
+ * closes once the trigger is scrolled out of view.
109
112
  */
110
113
  declare function EmojiPicker({ onSelect, trigger, labels: labelOverrides, locale, quickPicks, loadData, recentStorageKey, placement, onOpenChange, className, emojiImageBaseUrl, }: EmojiPickerProps): react.JSX.Element;
111
114