@tensor_1/react 2.5.0 → 2.7.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.
@@ -1,12 +1,26 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
3
 
4
+ type ShellNav = 'full' | 'rail' | 'hidden';
4
5
  interface AppShellProps extends HTMLAttributes<HTMLDivElement> {
6
+ /** Sidebar width tier: 'full' (default) → 'rail' (icon only) → 'hidden' (0). */
7
+ nav?: ShellNav;
8
+ /** Back-compat alias for nav="rail". Ignored when `nav` is set. */
5
9
  collapsed?: boolean;
10
+ /**
11
+ * Immersive focus mode: hides the sidebar AND the topbar so a wide, many-column
12
+ * table uses the whole frame. Provide `onExitFocus` to enable Esc-to-exit and
13
+ * the floating exit control.
14
+ */
15
+ focus?: boolean;
16
+ /** Called when the user presses Esc (or the exit control) while in focus mode. */
17
+ onExitFocus?: () => void;
18
+ /** Drop the content gutter so a genuinely wide table runs edge-to-edge. */
19
+ mainBleed?: boolean;
6
20
  sidebar?: ReactNode;
7
21
  topbar?: ReactNode;
8
22
  children?: ReactNode;
9
23
  }
10
- declare function AppShell({ collapsed, sidebar, topbar, children, className, ...props }: AppShellProps): react.JSX.Element;
24
+ declare function AppShell({ nav, collapsed, focus, onExitFocus, mainBleed, sidebar, topbar, children, className, ...props }: AppShellProps): react.JSX.Element;
11
25
 
12
- export { AppShell, type AppShellProps };
26
+ export { AppShell, type AppShellProps, type ShellNav };
@@ -1,11 +1,44 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect } from "react";
2
4
  import { cx } from "../cx.js";
3
- function AppShell({ collapsed, sidebar, topbar, children, className, ...props }) {
4
- return /* @__PURE__ */ jsxs("div", { className: cx("uix-shell", className), "data-collapsed": collapsed || void 0, ...props, children: [
5
- sidebar && /* @__PURE__ */ jsx("div", { className: "uix-shell__sidebar", children: sidebar }),
6
- topbar && /* @__PURE__ */ jsx("div", { className: "uix-shell__topbar", children: topbar }),
7
- /* @__PURE__ */ jsx("main", { className: "uix-shell__main", children })
8
- ] });
5
+ function AppShell({
6
+ nav,
7
+ collapsed,
8
+ focus,
9
+ onExitFocus,
10
+ mainBleed,
11
+ sidebar,
12
+ topbar,
13
+ children,
14
+ className,
15
+ ...props
16
+ }) {
17
+ const navState = nav ?? (collapsed ? "rail" : "full");
18
+ useEffect(() => {
19
+ if (!focus || !onExitFocus) return;
20
+ const onKey = (e) => {
21
+ if (e.key === "Escape") onExitFocus();
22
+ };
23
+ document.addEventListener("keydown", onKey);
24
+ return () => document.removeEventListener("keydown", onKey);
25
+ }, [focus, onExitFocus]);
26
+ return /* @__PURE__ */ jsxs(
27
+ "div",
28
+ {
29
+ className: cx("uix-shell", className),
30
+ "data-nav": navState !== "full" ? navState : void 0,
31
+ "data-collapsed": collapsed && nav == null ? "" : void 0,
32
+ "data-focus": focus || void 0,
33
+ ...props,
34
+ children: [
35
+ sidebar && /* @__PURE__ */ jsx("div", { className: "uix-shell__sidebar", children: sidebar }),
36
+ 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 }),
38
+ 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
+ ]
40
+ }
41
+ );
9
42
  }
10
43
  export {
11
44
  AppShell
@@ -0,0 +1,21 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
+
4
+ interface ComposerProps extends HTMLAttributes<HTMLDivElement> {
5
+ children?: ReactNode;
6
+ }
7
+ /**
8
+ * Composer surface over `.uix-composer` — a single box that owns the
9
+ * `:focus-within` accent ring. Drop a borderless `<textarea>` and a
10
+ * `<ComposerBar>` inside; the descendant `textarea` picks up the composer's
11
+ * padding/typography automatically, so the input never reads as a second
12
+ * nested box.
13
+ */
14
+ declare function Composer({ children, className, ...props }: ComposerProps): react.JSX.Element;
15
+ interface ComposerBarProps extends HTMLAttributes<HTMLDivElement> {
16
+ children?: ReactNode;
17
+ }
18
+ /** Action footer for a `<Composer>` over `.uix-composer__bar` (top-bordered, right-aligned). */
19
+ declare function ComposerBar({ children, className, ...props }: ComposerBarProps): react.JSX.Element;
20
+
21
+ export { Composer, ComposerBar, type ComposerBarProps, type ComposerProps };
@@ -0,0 +1,12 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { cx } from "../cx.js";
3
+ function Composer({ children, className, ...props }) {
4
+ return /* @__PURE__ */ jsx("div", { className: cx("uix-composer", className), ...props, children });
5
+ }
6
+ function ComposerBar({ children, className, ...props }) {
7
+ return /* @__PURE__ */ jsx("div", { className: cx("uix-composer__bar", className), ...props, children });
8
+ }
9
+ export {
10
+ Composer,
11
+ ComposerBar
12
+ };
@@ -1,12 +1,18 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cloneElement, isValidElement, useId } from "react";
2
3
  import { cx } from "../cx.js";
3
4
  function Field({ label, hint, error, success, required, children, className, htmlFor }) {
5
+ const uid = useId();
6
+ const msgId = `${uid}-msg`;
7
+ const describedBy = error || success || hint ? msgId : void 0;
8
+ const control = isValidElement(children) ? cloneElement(children, {
9
+ "aria-describedby": [children.props["aria-describedby"], describedBy].filter(Boolean).join(" ") || void 0,
10
+ "aria-invalid": error ? true : children.props["aria-invalid"]
11
+ }) : children;
4
12
  return /* @__PURE__ */ jsxs("div", { className: cx("uix-field", className), children: [
5
13
  label && /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor, "data-required": required || void 0, children: label }),
6
- children,
7
- error && /* @__PURE__ */ jsx("span", { className: "uix-field__error", children: error }),
8
- success && !error && /* @__PURE__ */ jsx("span", { className: "uix-field__success", children: success }),
9
- hint && !error && !success && /* @__PURE__ */ jsx("span", { className: "uix-field__hint", children: hint })
14
+ 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 })
10
16
  ] });
11
17
  }
12
18
  export {
@@ -1,5 +1,6 @@
1
1
  import * as react from 'react';
2
- import { HTMLAttributes, ReactNode } from 'react';
2
+ import { HTMLAttributes, RefObject, ReactNode } from 'react';
3
+ import { Placement } from '../overlay-position.js';
3
4
 
4
5
  interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
5
6
  /**
@@ -7,12 +8,25 @@ interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
7
8
  * wires to it via `popoverTarget={id}`. Pass `"manual"` to control open state yourself.
8
9
  */
9
10
  popover?: 'auto' | 'manual';
11
+ /**
12
+ * Element (or ref) to anchor against. When set, the popover is placed with
13
+ * cross-browser JS positioning — flip when it won't fit, shift to stay on-screen —
14
+ * instead of relying on CSS `anchor()`, which is Chromium-only and detaches the
15
+ * popover from its trigger everywhere else. The native Popover API still provides
16
+ * the top layer (so it escapes `overflow` clipping) and light-dismiss.
17
+ */
18
+ anchor?: RefObject<HTMLElement | null> | HTMLElement | null;
19
+ /** Preferred placement when `anchor` is set. Default `'bottom-start'`. */
20
+ placement?: Placement;
21
+ /** Gap between the trigger and the popover, in px. Default 6. */
22
+ offset?: number;
10
23
  children?: ReactNode;
11
24
  }
12
25
  /**
13
- * Anchored surface over `.uix-popover`, using the native Popover API.
14
- * Compose ITSM things like a filter popover by putting controls inside.
26
+ * Anchored surface over `.uix-popover`, using the native Popover API. Compose ITSM
27
+ * things like a filter popover, menu, or rich select by putting controls inside.
28
+ * Pass `anchor` to get collision-aware, cross-browser placement (UIX-FIX-02).
15
29
  */
16
- declare function Popover({ popover, children, className, ...props }: PopoverProps): react.JSX.Element;
30
+ declare function Popover({ popover, anchor, placement, offset, className, children, ...props }: PopoverProps): react.JSX.Element;
17
31
 
18
32
  export { Popover, type PopoverProps };
@@ -1,8 +1,37 @@
1
+ "use client";
1
2
  import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
2
4
  import { cx } from "../cx.js";
3
- function Popover({ popover = "auto", children, className, ...props }) {
5
+ import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
6
+ function Popover({
7
+ popover = "auto",
8
+ anchor,
9
+ placement = "bottom-start",
10
+ offset = 6,
11
+ className,
12
+ children,
13
+ ...props
14
+ }) {
15
+ const ref = useRef(null);
16
+ const [open, setOpen] = useState(false);
4
17
  const popoverAttr = { popover };
5
- return /* @__PURE__ */ jsx("div", { className: cx("uix-popover", className), ...popoverAttr, ...props, children });
18
+ const reposition = useAnchoredPosition(anchor ?? null, ref, {
19
+ open: open && !!anchor,
20
+ placement,
21
+ offset
22
+ });
23
+ useEffect(() => {
24
+ const el = ref.current;
25
+ if (!el || !anchor) return;
26
+ const sync = () => {
27
+ const isOpen = el.matches(":popover-open");
28
+ if (isOpen) reposition();
29
+ setOpen(isOpen);
30
+ };
31
+ el.addEventListener("toggle", sync);
32
+ return () => el.removeEventListener("toggle", sync);
33
+ }, [anchor, reposition]);
34
+ return /* @__PURE__ */ jsx("div", { ref, className: cx("uix-popover", className), ...popoverAttr, ...props, children });
6
35
  }
7
36
  export {
8
37
  Popover
@@ -0,0 +1,29 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode, ButtonHTMLAttributes } from 'react';
3
+
4
+ interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
5
+ /** The currently-selected option value. */
6
+ value?: string;
7
+ /** Fires with the option's value when a different option is chosen. */
8
+ onChange?: (value: string) => void;
9
+ children?: ReactNode;
10
+ }
11
+ /**
12
+ * Segmented single-select over `.uix-segmented` — the compact 2–3 way toggle
13
+ * (density, audience, view mode …) that reads as one pill with a lifted active
14
+ * segment. Selection state is shared with `<SegmentedOption>` via context, so
15
+ * the caller only wires `value` + `onChange` once.
16
+ */
17
+ declare function Segmented({ value, onChange, children, className, ...props }: SegmentedProps): react.JSX.Element;
18
+ interface SegmentedOptionProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'value' | 'onChange'> {
19
+ value: string;
20
+ children?: ReactNode;
21
+ }
22
+ /**
23
+ * One option in a `<Segmented>`. Active state is derived from the parent's
24
+ * `value`; the pressed segment lifts to the surface via
25
+ * `.uix-segmented__option[aria-pressed="true"]`.
26
+ */
27
+ declare function SegmentedOption({ value, children, className, onClick, ...props }: SegmentedOptionProps): react.JSX.Element;
28
+
29
+ export { Segmented, SegmentedOption, type SegmentedOptionProps, type SegmentedProps };
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from "react";
4
+ import { cx } from "../cx.js";
5
+ const SegmentedCtx = createContext({});
6
+ function Segmented({ value, onChange, children, className, ...props }) {
7
+ return /* @__PURE__ */ jsx(SegmentedCtx.Provider, { value: { value, onChange }, children: /* @__PURE__ */ jsx("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
8
+ }
9
+ function SegmentedOption({
10
+ value,
11
+ children,
12
+ className,
13
+ onClick,
14
+ ...props
15
+ }) {
16
+ const ctx = useContext(SegmentedCtx);
17
+ const selected = ctx.value === value;
18
+ return /* @__PURE__ */ jsx(
19
+ "button",
20
+ {
21
+ type: "button",
22
+ className: cx("uix-segmented__option", className),
23
+ "aria-pressed": selected,
24
+ onClick: (e) => {
25
+ ctx.onChange?.(value);
26
+ onClick?.(e);
27
+ },
28
+ ...props,
29
+ children
30
+ }
31
+ );
32
+ }
33
+ export {
34
+ Segmented,
35
+ SegmentedOption
36
+ };
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { TableHTMLAttributes, HTMLAttributes, ReactNode, TdHTMLAttributes, ThHTMLAttributes } from 'react';
2
+ import { HTMLAttributes, ReactNode, ButtonHTMLAttributes, TdHTMLAttributes, TableHTMLAttributes, ThHTMLAttributes } from 'react';
3
3
 
4
4
  interface TableWrapProps extends HTMLAttributes<HTMLDivElement> {
5
5
  children?: ReactNode;
@@ -11,16 +11,20 @@ interface TableProps extends TableHTMLAttributes<HTMLTableElement> {
11
11
  density?: TableDensity;
12
12
  zebra?: boolean;
13
13
  pinnedCol?: boolean;
14
+ /** table-layout: fixed — required for reliable column widths + primary-column truncation */
15
+ fixed?: boolean;
14
16
  rowPeek?: boolean;
15
17
  v2?: boolean;
16
18
  }
17
- declare function Table({ density, zebra, pinnedCol, rowPeek, v2, className, children, ...props }: TableProps): react.JSX.Element;
19
+ declare function Table({ density, zebra, pinnedCol, fixed, rowPeek, v2, className, children, ...props }: TableProps): react.JSX.Element;
18
20
  interface ThProps extends ThHTMLAttributes<HTMLTableCellElement> {
19
21
  sortable?: boolean;
20
22
  sortDirection?: SortDirection;
23
+ /** 1-based ordinal shown when this column is a secondary key in a multi-sort */
24
+ sortOrder?: number;
21
25
  onSort?: () => void;
22
26
  }
23
- declare function Th({ sortable, sortDirection, onSort, className, children, onClick, ...props }: ThProps): react.JSX.Element;
27
+ declare function Th({ sortable, sortDirection, sortOrder, onSort, className, children, onClick, ...props }: ThProps): react.JSX.Element;
24
28
  interface TdProps extends TdHTMLAttributes<HTMLTableCellElement> {
25
29
  pinned?: boolean;
26
30
  }
@@ -30,5 +34,42 @@ interface TrProps extends HTMLAttributes<HTMLTableRowElement> {
30
34
  pinned?: boolean;
31
35
  }
32
36
  declare function Tr({ selected, pinned, className, children, ...props }: TrProps): react.JSX.Element;
37
+ interface BulkBarProps extends HTMLAttributes<HTMLDivElement> {
38
+ /** number of selected rows, rendered as "N selected" */
39
+ count?: number;
40
+ children?: ReactNode;
41
+ }
42
+ /** Contextual action bar shown above the table when ≥1 row is selected. */
43
+ declare function BulkBar({ count, children, className, ...props }: BulkBarProps): react.JSX.Element;
44
+ interface RowActionsProps extends TdHTMLAttributes<HTMLTableCellElement> {
45
+ children?: ReactNode;
46
+ }
47
+ /** Trailing cell that holds the row's hover actions (kebab, quick actions). */
48
+ declare function RowActions({ children, className, ...props }: RowActionsProps): react.JSX.Element;
49
+ interface RowActionProps extends ButtonHTMLAttributes<HTMLButtonElement> {
50
+ children?: ReactNode;
51
+ }
52
+ /** A single hover-revealed row action button (icon inside). */
53
+ declare function RowAction({ children, className, type, ...props }: RowActionProps): react.JSX.Element;
54
+ interface ExpandToggleProps extends ButtonHTMLAttributes<HTMLButtonElement> {
55
+ expanded?: boolean;
56
+ }
57
+ /** Chevron button that expands/collapses an inline detail row. */
58
+ declare function ExpandToggle({ expanded, className, type, ...props }: ExpandToggleProps): react.JSX.Element;
59
+ /** Emphasised primary cell text. */
60
+ declare function CellStrong({ children, className, ...props }: HTMLAttributes<HTMLSpanElement>): react.JSX.Element;
61
+ /** Secondary line under a primary cell value (muted, meta size). */
62
+ declare function CellSub({ children, className, ...props }: HTMLAttributes<HTMLSpanElement>): react.JSX.Element;
63
+ interface MarkProps extends HTMLAttributes<HTMLElement> {
64
+ children?: ReactNode;
65
+ }
66
+ /** A search-match highlight span. */
67
+ declare function Mark({ children, className, ...props }: MarkProps): react.JSX.Element;
68
+ interface HighlightedProps {
69
+ text: string;
70
+ query: string;
71
+ }
72
+ /** Render `text` with case-insensitive matches of `query` wrapped in <mark>. */
73
+ declare function Highlighted({ text, query }: HighlightedProps): react.JSX.Element;
33
74
 
34
- export { type SortDirection, Table, type TableDensity, type TableProps, TableWrap, type TableWrapProps, Td, type TdProps, Th, type ThProps, Tr, type TrProps };
75
+ export { BulkBar, type BulkBarProps, CellStrong, CellSub, ExpandToggle, type ExpandToggleProps, Highlighted, type HighlightedProps, Mark, type MarkProps, RowAction, type RowActionProps, RowActions, type RowActionsProps, type SortDirection, Table, type TableDensity, type TableProps, TableWrap, type TableWrapProps, Td, type TdProps, Th, type ThProps, Tr, type TrProps };
@@ -1,5 +1,6 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cx } from "../cx.js";
3
+ import { highlightSegments } from "../table-engine.js";
3
4
  function TableWrap({ children, className, ...props }) {
4
5
  return /* @__PURE__ */ jsx("div", { className: cx("uix-table-wrap", className), ...props, children });
5
6
  }
@@ -7,6 +8,7 @@ function Table({
7
8
  density = "standard",
8
9
  zebra = true,
9
10
  pinnedCol,
11
+ fixed,
10
12
  rowPeek,
11
13
  v2,
12
14
  className,
@@ -16,7 +18,13 @@ function Table({
16
18
  return /* @__PURE__ */ jsx(
17
19
  "table",
18
20
  {
19
- className: cx("uix-table", !zebra && "uix-table--no-zebra", pinnedCol && "uix-table--pinned-col", className),
21
+ className: cx(
22
+ "uix-table",
23
+ !zebra && "uix-table--no-zebra",
24
+ pinnedCol && "uix-table--pinned-col",
25
+ fixed && "uix-table--fixed",
26
+ className
27
+ ),
20
28
  "data-density": density !== "standard" ? density : void 0,
21
29
  "data-uix-rowpeek": rowPeek || void 0,
22
30
  "data-uix-table-v2": v2 || void 0,
@@ -25,12 +33,13 @@ function Table({
25
33
  }
26
34
  );
27
35
  }
28
- function Th({ sortable, sortDirection, onSort, className, children, onClick, ...props }) {
36
+ function Th({ sortable, sortDirection, sortOrder, onSort, className, children, onClick, ...props }) {
29
37
  return /* @__PURE__ */ jsx(
30
38
  "th",
31
39
  {
32
40
  className,
33
41
  "data-sort": sortable || void 0,
42
+ "data-sort-order": sortOrder != null && sortOrder > 0 ? sortOrder : void 0,
34
43
  "aria-sort": sortDirection && sortDirection !== "none" ? sortDirection : void 0,
35
44
  onClick: sortable ? onSort ?? onClick : onClick,
36
45
  ...props,
@@ -53,7 +62,58 @@ function Tr({ selected, pinned, className, children, ...props }) {
53
62
  }
54
63
  );
55
64
  }
65
+ function BulkBar({ count, children, className, ...props }) {
66
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-bulkbar", className), role: "toolbar", "aria-label": "Bulk actions", ...props, children: [
67
+ count != null && /* @__PURE__ */ jsxs("span", { className: "uix-bulkbar__count", children: [
68
+ count,
69
+ " selected"
70
+ ] }),
71
+ /* @__PURE__ */ jsx("span", { className: "uix-bulkbar__spacer" }),
72
+ children
73
+ ] });
74
+ }
75
+ function RowActions({ children, className, ...props }) {
76
+ return /* @__PURE__ */ jsx("td", { className: cx("uix-table__actions", className), ...props, children });
77
+ }
78
+ function RowAction({ children, className, type = "button", ...props }) {
79
+ return /* @__PURE__ */ jsx("button", { type, className: cx("uix-rowact", className), ...props, children });
80
+ }
81
+ function ExpandToggle({ expanded, className, type = "button", ...props }) {
82
+ return /* @__PURE__ */ jsx(
83
+ "button",
84
+ {
85
+ type,
86
+ className: cx("uix-table__expand", className),
87
+ "aria-expanded": expanded,
88
+ "aria-label": expanded ? "Collapse row" : "Expand row",
89
+ ...props,
90
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 4l4 4-4 4" }) })
91
+ }
92
+ );
93
+ }
94
+ function CellStrong({ children, className, ...props }) {
95
+ return /* @__PURE__ */ jsx("span", { className: cx("uix-cell-strong", className), ...props, children });
96
+ }
97
+ function CellSub({ children, className, ...props }) {
98
+ return /* @__PURE__ */ jsx("span", { className: cx("uix-cell-sub", className), ...props, children });
99
+ }
100
+ function Mark({ children, className, ...props }) {
101
+ return /* @__PURE__ */ jsx("mark", { className: cx("uix-mark", className), ...props, children });
102
+ }
103
+ function Highlighted({ text, query }) {
104
+ return /* @__PURE__ */ jsx(Fragment, { children: highlightSegments(text, query).map(
105
+ (seg, i) => seg.match ? /* @__PURE__ */ jsx("mark", { className: "uix-mark", children: seg.text }, i) : /* @__PURE__ */ jsx("span", { children: seg.text }, i)
106
+ ) });
107
+ }
56
108
  export {
109
+ BulkBar,
110
+ CellStrong,
111
+ CellSub,
112
+ ExpandToggle,
113
+ Highlighted,
114
+ Mark,
115
+ RowAction,
116
+ RowActions,
57
117
  Table,
58
118
  TableWrap,
59
119
  Td,
@@ -7,10 +7,16 @@ interface TimelineProps extends HTMLAttributes<HTMLDivElement> {
7
7
  /** Activity timeline container over `.uix-timeline`. */
8
8
  declare function Timeline({ children, className, ...props }: TimelineProps): react.JSX.Element;
9
9
  interface TimelineItemProps extends HTMLAttributes<HTMLDivElement> {
10
+ /**
11
+ * Custom rail node — an icon puck, an avatar, a tinted dot. Defaults to the
12
+ * accent `.uix-timeline__node` dot. Supply this to make a heterogeneous feed
13
+ * (comments + audit events) read on one rail with per-row node semantics.
14
+ */
15
+ node?: ReactNode;
10
16
  meta?: ReactNode;
11
17
  children?: ReactNode;
12
18
  }
13
19
  /** A timeline entry over `.uix-timeline__item` (node + connecting line + body). */
14
- declare function TimelineItem({ meta, children, className, ...props }: TimelineItemProps): react.JSX.Element;
20
+ declare function TimelineItem({ node, meta, children, className, ...props }: TimelineItemProps): react.JSX.Element;
15
21
 
16
22
  export { Timeline, TimelineItem, type TimelineItemProps, type TimelineProps };
@@ -3,10 +3,10 @@ import { cx } from "../cx.js";
3
3
  function Timeline({ children, className, ...props }) {
4
4
  return /* @__PURE__ */ jsx("div", { className: cx("uix-timeline", className), ...props, children });
5
5
  }
6
- function TimelineItem({ meta, children, className, ...props }) {
6
+ function TimelineItem({ node, meta, children, className, ...props }) {
7
7
  return /* @__PURE__ */ jsxs("div", { className: cx("uix-timeline__item", className), ...props, children: [
8
8
  /* @__PURE__ */ jsxs("div", { className: "uix-timeline__rail", children: [
9
- /* @__PURE__ */ jsx("span", { className: "uix-timeline__node" }),
9
+ node ?? /* @__PURE__ */ jsx("span", { className: "uix-timeline__node" }),
10
10
  /* @__PURE__ */ jsx("span", { className: "uix-timeline__line" })
11
11
  ] }),
12
12
  /* @__PURE__ */ jsxs("div", { className: "uix-timeline__body", children: [
@@ -1,16 +1,17 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cx } from "../cx.js";
3
3
  function Toast({ title, message, tone, icon, onClose, leaving, className, ...props }) {
4
+ const assertive = tone === "danger";
4
5
  return /* @__PURE__ */ jsxs(
5
6
  "div",
6
7
  {
7
8
  className: cx("uix-toast", tone && `uix-toast--${tone}`, className),
8
9
  "data-leaving": leaving || void 0,
9
- role: "status",
10
- "aria-live": "polite",
10
+ role: assertive ? "alert" : "status",
11
+ "aria-live": assertive ? "assertive" : "polite",
11
12
  ...props,
12
13
  children: [
13
- icon && /* @__PURE__ */ jsx("div", { className: "uix-toast__icon", children: icon }),
14
+ icon && /* @__PURE__ */ jsx("div", { className: "uix-toast__icon", "aria-hidden": "true", children: icon }),
14
15
  /* @__PURE__ */ jsxs("div", { className: "uix-toast__body", children: [
15
16
  title && /* @__PURE__ */ jsx("div", { className: "uix-toast__title", children: title }),
16
17
  message && /* @__PURE__ */ jsx("div", { className: "uix-toast__msg", children: message })
@@ -21,7 +22,7 @@ function Toast({ title, message, tone, icon, onClose, leaving, className, ...pro
21
22
  );
22
23
  }
23
24
  function Toaster({ children, className, ...props }) {
24
- return /* @__PURE__ */ jsx("div", { className: cx("uix-toaster", className), "aria-live": "polite", "aria-atomic": "false", ...props, children });
25
+ return /* @__PURE__ */ jsx("div", { className: cx("uix-toaster", className), ...props, children });
25
26
  }
26
27
  export {
27
28
  Toast,
@@ -1,16 +1,20 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
+ import { Placement } from '../overlay-position.js';
3
4
 
4
5
  interface TooltipProps extends HTMLAttributes<HTMLSpanElement> {
5
- /** Tooltip text. Rendered via the CSS `[data-uix-tip]` hover/focus tooltip. */
6
+ /** Tooltip text. */
6
7
  label: string;
8
+ /** Preferred placement. Default `'top'`. */
9
+ placement?: Placement;
7
10
  children?: ReactNode;
8
11
  }
9
12
  /**
10
- * Thin wrapper over the CSS-only tooltip (`[data-uix-tip]`). Wraps its children
11
- * in a span carrying the attribute; the bubble is drawn entirely in CSS on
12
- * hover/focus. For keyboard reveal, ensure a focusable child or pass `tabIndex`.
13
+ * Tooltip whose bubble renders in the top layer via the native Popover API, so it
14
+ * escapes any `overflow: hidden/scroll` ancestor that would clip the old CSS-only
15
+ * `[data-uix-tip]` tooltip (UIX-FIX-02). Positioned by the shared overlay engine
16
+ * (flip/shift), shown on hover and keyboard focus, dismissed on blur or Escape.
13
17
  */
14
- declare function Tooltip({ label, children, ...props }: TooltipProps): react.JSX.Element;
18
+ declare function Tooltip({ label, placement, className, children, ...props }: TooltipProps): react.JSX.Element;
15
19
 
16
20
  export { Tooltip, type TooltipProps };
@@ -1,6 +1,64 @@
1
- import { jsx } from "react/jsx-runtime";
2
- function Tooltip({ label, children, ...props }) {
3
- return /* @__PURE__ */ jsx("span", { "data-uix-tip": label, ...props, children });
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import { cx } from "../cx.js";
5
+ import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
6
+ function Tooltip({ label, placement = "top", className, children, ...props }) {
7
+ const triggerRef = useRef(null);
8
+ const bubbleRef = useRef(null);
9
+ const [open, setOpen] = useState(false);
10
+ const id = useId();
11
+ useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
12
+ const show = () => {
13
+ const b = bubbleRef.current;
14
+ if (b && !b.matches(":popover-open")) {
15
+ try {
16
+ b.showPopover();
17
+ } catch {
18
+ }
19
+ }
20
+ setOpen(true);
21
+ };
22
+ const hide = () => {
23
+ const b = bubbleRef.current;
24
+ if (b && b.matches(":popover-open")) {
25
+ try {
26
+ b.hidePopover();
27
+ } catch {
28
+ }
29
+ }
30
+ setOpen(false);
31
+ };
32
+ useEffect(() => () => {
33
+ const b = bubbleRef.current;
34
+ if (b && b.matches(":popover-open")) {
35
+ try {
36
+ b.hidePopover();
37
+ } catch {
38
+ }
39
+ }
40
+ }, []);
41
+ const bubbleAttr = { popover: "manual" };
42
+ return /* @__PURE__ */ jsxs(
43
+ "span",
44
+ {
45
+ ref: triggerRef,
46
+ className: cx("uix-tooltip", className),
47
+ "aria-describedby": id,
48
+ onMouseEnter: show,
49
+ onMouseLeave: hide,
50
+ onFocus: show,
51
+ onBlur: hide,
52
+ onKeyDown: (e) => {
53
+ if (e.key === "Escape") hide();
54
+ },
55
+ ...props,
56
+ children: [
57
+ children,
58
+ /* @__PURE__ */ jsx("span", { ref: bubbleRef, id, role: "tooltip", className: "uix-tooltip__bubble", ...bubbleAttr, children: label })
59
+ ]
60
+ }
61
+ );
4
62
  }
5
63
  export {
6
64
  Tooltip
@@ -11,8 +11,11 @@ interface TreeNodeProps {
11
11
  node: TreeNodeData;
12
12
  expanded: Set<string>;
13
13
  selected?: string;
14
+ selectable: boolean;
15
+ tabbableId?: string;
14
16
  onToggle: (id: string) => void;
15
17
  onSelect?: (id: string) => void;
18
+ onFocusNode: (id: string) => void;
16
19
  level?: number;
17
20
  }
18
21
  interface TreeProps extends Omit<HTMLAttributes<HTMLUListElement>, 'onSelect' | 'onChange'> {
@@ -22,7 +25,18 @@ interface TreeProps extends Omit<HTMLAttributes<HTMLUListElement>, 'onSelect' |
22
25
  selected?: string;
23
26
  onToggle?: (id: string) => void;
24
27
  onSelect?: (id: string) => void;
28
+ /**
29
+ * Virtualize a large tree — render only the rows in view (UIX-FIX-05). Defaults to auto
30
+ * (on past the engine's `shouldVirtualize` threshold of visible rows). When on, the tree is
31
+ * flattened to a scrollable list of `maxHeight` with fixed-height rows; a11y is preserved via
32
+ * `aria-level` / `aria-setsize` / `aria-posinset` instead of nested `role="group"`s.
33
+ */
34
+ virtualize?: boolean;
35
+ /** Fixed row height (px) used for virtualization. Default 32. */
36
+ rowHeight?: number;
37
+ /** Scroll-viewport height (px) when virtualizing. Default 384. */
38
+ maxHeight?: number;
25
39
  }
26
- declare function Tree({ nodes, expanded: controlledExpanded, defaultExpanded, selected, onToggle, onSelect, className, ...props }: TreeProps): react.JSX.Element;
40
+ declare function Tree({ nodes, expanded: controlledExpanded, defaultExpanded, selected, onToggle, onSelect, virtualize, rowHeight, maxHeight, className, ...props }: TreeProps): react.JSX.Element;
27
41
 
28
42
  export { Tree, type TreeNodeData, type TreeNodeProps, type TreeProps };