@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
package/README.md CHANGED
@@ -257,3 +257,54 @@ A small ? next to a title or label opens a plain-text explanation. Use it for wh
257
257
  - **`helpLabel`** is the ? button's accessible name. It defaults to `"About: <title>"` (English), so pass a translated one.
258
258
  - **Placement:** the ? is a fixed 9 px glyph, superscript at the top right of the title or label, whatever the text size. It sits beside the heading or `<label>`, never inside it, so the heading's name stays the title and clicking a field label still focuses the input.
259
259
  - **Behaviour:** hover opens it after about 300 ms, keyboard focus opens it at once, and a click or tap keeps it open. Esc closes it and keeps focus on the ?, and a press outside also closes it. The panel is the button's `aria-describedby`, so screen readers announce it on focus. It uses `popover="manual"`, so it never closes another open popover.
260
+
261
+ ### Small gaps from the UIx reuse audits (2.27.0)
262
+
263
+ TENSOR and MOTUS were hand-building these because the kit lacked them (workspace ADR-0038; UIx HAR-1346). Every
264
+ addition is optional; existing calls render as before.
265
+
266
+ ```tsx
267
+ // One link adapter for every component that renders a link
268
+ const toRoute = (p: UixLinkProps) => <NextLink {...p} />;
269
+ <ButtonLink href="/tickets/new" variant="primary" renderLink={toRoute}>New ticket</ButtonLink>
270
+ <Button size="xs" icon aria-label="Remove row"><XIcon /></Button> // 24 px, the WCAG 2.5.8 floor
271
+ <Stat label="Open" value={12} href="/incidents?state=open" current renderLink={toRoute} />
272
+ <Stat label="Status" value="New" onActivate={openMenu} haspopup="menu" />
273
+ <Pagination page={page} pageCount={9} hrefFor={(p) => `/katalog?page=${p}`} /> // server-safe links
274
+ <Pagination mode="cursor" hasPrevious={!!before} hasNext={!!after} onFirst={first} onPrevious={prev} onNext={next} summary="21–40" />
275
+
276
+ // SchedulingCalendar: week start, product date format, busy days, a per-day badge
277
+ <SchedulingCalendar weekStartsOn={0} formatDate={(d, part) => part === 'day' ? toDDMMYYYY(d) : intl(d, part)}
278
+ maxEntriesPerDay={3} onShowMore={(date, entries) => openDay(date)}
279
+ renderDayBadge={(date, entries) => collisions(entries) || null} … />
280
+
281
+ // Overlays and disclosure
282
+ <Drawer side="start" …/> <Drawer side="bottom" …/>
283
+ <Tooltip content={<ul><li>Angry 2</li><li>Calm 5</li></ul>}><button>Mood</button></Tooltip>
284
+ <CollapsibleSection title="SLA" lazy persistKey="incident-sla" openRequest={jumpToSla}>…</CollapsibleSection>
285
+ <ConfirmDialog … destructive typeToConfirm="web-01" compensation="Restore it from the archive within 30 days."
286
+ error={failure} tertiaryLabel="Archive instead" onTertiary={archive}><dl>…</dl></ConfirmDialog>
287
+ <Popconfirm open={asking} anchor={buttonRef} title="Unlink this CI?" … />
288
+
289
+ // New components
290
+ <Menu trigger={<Button>Actions</Button>}>
291
+ <MenuItem onSelect={assign} shortcut={<KbdCombo keys={['Mod', 'M']} />}>Assign to me</MenuItem>
292
+ <MenuSeparator />
293
+ <MenuGroup label="Danger zone"><MenuItem tone="danger" onSelect={remove}>Delete</MenuItem></MenuGroup>
294
+ </Menu>
295
+ <ChipGroup label="Active filters">
296
+ <Chip pressed={mine} onPressedChange={setMine}>Assigned to me</Chip>
297
+ <Chip count={12} onRemove={clearState}>State: Open</Chip>
298
+ <Chip variant="add" onClick={addFilter}>+ Add filter</Chip>
299
+ </ChipGroup>
300
+ <Steps label="Intake"><Step title="Scan" state="complete" /><Step title="Review" state="current" /><Step title="Shelve" state="waiting" /></Steps>
301
+
302
+ // Translate kit chrome once
303
+ <UixLabelsProvider labels={{ drawer: { close: 'Schließen' }, peek: { previous: 'Vorheriger Datensatz' } }}>…</UixLabelsProvider>
304
+ ```
305
+
306
+ - **`renderLink`** (`UixRenderLink`) receives `UixLinkProps` (`href`, `className`, `children`, ARIA). A nullish return falls back to a plain `<a>`.
307
+ - **`UixLabelsProvider`** reaches the client components (`Modal`, `Drawer`, `Peek`, `Toast`, `Toaster`, `AppShell`, `Sidebar`, `SearchSuggest`, `CommandPalette`, `ConfirmDialog`, `Popconfirm`, `SchedulingCalendar`, `DateRangePicker`). `Pagination` and `BulkBar` stay server-renderable, so they cannot read context: pass `labels={useUixLabels().pagination}` from a client parent, or translate on the server. An explicit prop always wins.
308
+ - **`CollapsibleSection`** stays a JS-free `<details>` unless you pass `lazy`, `persistKey` or `openRequest`. `lazy` unmounts a closed body; `lazy="keep"` keeps it after the first open.
309
+ - **`ConfirmDialog`** focuses the type-to-confirm field, else Cancel when `destructive`. `Popconfirm` is for low-risk, easily undone actions; use `ConfirmDialog` for anything hard to undo.
310
+ - **`KbdCombo`** renders the non-Apple glyphs on the server and switches to ⌘/⇧/⌥ after mount, so hydration matches. Pass `platform` to pin it.
@@ -16,7 +16,9 @@ declare function toDateKey(date: Date): string;
16
16
  declare function addCalendarDays(value: string, amount: number): string;
17
17
  declare function addCalendarMonths(value: string, amount: number): string;
18
18
  declare function startOfMonth(value: string): string;
19
- declare function buildMonthGrid(month: string, weekStartsOn?: 0 | 1): CalendarDay[];
19
+ /** A weekday in `Date.getUTCDay()` numbering: 0 = Sunday … 6 = Saturday. */
20
+ type CalendarWeekday = 0 | 1 | 2 | 3 | 4 | 5 | 6;
21
+ declare function buildMonthGrid(month: string, weekStartsOn?: CalendarWeekday): CalendarDay[];
20
22
  declare function zonedDateKey(instant: string | Date, timeZone: string): string;
21
23
  declare function zonedDateSpan(start: string, end: string, timeZone: string): ZonedDateSpan;
22
24
  declare function enumerateDateSpan(span: ZonedDateSpan, limit?: number): string[];
@@ -28,4 +30,4 @@ declare function isDateUnavailable(date: string, options: {
28
30
  isDisabled?: (date: string) => boolean;
29
31
  }): boolean;
30
32
 
31
- export { type CalendarDay, type DateRangeValue, type ZonedDateSpan, addCalendarDays, addCalendarMonths, buildMonthGrid, enumerateDateSpan, isDateInRange, isDateUnavailable, selectRangeDate, startOfMonth, toDateKey, zonedDateKey, zonedDateSpan };
33
+ export { type CalendarDay, type CalendarWeekday, type DateRangeValue, type ZonedDateSpan, addCalendarDays, addCalendarMonths, buildMonthGrid, enumerateDateSpan, isDateInRange, isDateUnavailable, selectRangeDate, startOfMonth, toDateKey, zonedDateKey, zonedDateSpan };
@@ -27,6 +27,6 @@ interface AppShellProps extends HTMLAttributes<HTMLDivElement> {
27
27
  /** TENSOR RX-125 (UIX-04): translatable; English default. */
28
28
  exitFocusLabel?: ReactNode;
29
29
  }
30
- declare function AppShell({ nav, collapsed, focus, onExitFocus, mainBleed, mainId, sidebar, topbar, children, skipToContentLabel, exitFocusLabel, className, ...props }: AppShellProps): react.JSX.Element;
30
+ declare function AppShell({ nav, collapsed, focus, onExitFocus, mainBleed, mainId, sidebar, topbar, children, skipToContentLabel: skipToContentLabelProp, exitFocusLabel: exitFocusLabelProp, className, ...props }: AppShellProps): react.JSX.Element;
31
31
 
32
32
  export { AppShell, type AppShellProps, type ShellNav };
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect } from "react";
4
4
  import { cx } from "../cx.js";
5
+ import { useUixLabels } from "../labels-context.js";
5
6
  function AppShell({
6
7
  nav,
7
8
  collapsed,
@@ -12,11 +13,14 @@ function AppShell({
12
13
  sidebar,
13
14
  topbar,
14
15
  children,
15
- skipToContentLabel = "Skip to content",
16
- exitFocusLabel = "Esc \xB7 Exit focus",
16
+ skipToContentLabel: skipToContentLabelProp,
17
+ exitFocusLabel: exitFocusLabelProp,
17
18
  className,
18
19
  ...props
19
20
  }) {
21
+ const uixLabels = useUixLabels();
22
+ const skipToContentLabel = skipToContentLabelProp ?? uixLabels.appShell?.skipToContent ?? "Skip to content";
23
+ const exitFocusLabel = exitFocusLabelProp ?? uixLabels.appShell?.exitFocus ?? "Esc \xB7 Exit focus";
20
24
  const navState = nav ?? (collapsed ? "rail" : "full");
21
25
  useEffect(() => {
22
26
  if (!focus || !onExitFocus) return;
@@ -1,18 +1,42 @@
1
1
  import * as react from 'react';
2
- import { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ import { ButtonHTMLAttributes, ReactNode, AnchorHTMLAttributes } from 'react';
3
+ import { UixRenderLink } from '../link.js';
3
4
 
5
+ type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'link';
6
+ /** `xs` is 24 px: the smallest hit area WCAG 2.5.8 allows, for dense tool rows (HAR-1348). */
7
+ type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
4
8
  interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
5
- variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'link';
6
- size?: 'sm' | 'md' | 'lg';
9
+ variant?: ButtonVariant;
10
+ size?: ButtonSize;
7
11
  icon?: boolean;
8
12
  loading?: boolean;
9
13
  children?: ReactNode;
10
14
  }
11
15
  declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<HTMLButtonElement>>;
16
+ interface ButtonLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
17
+ href: string;
18
+ variant?: ButtonVariant;
19
+ size?: ButtonSize;
20
+ /** Icon-only link: square, and needs `aria-label`. */
21
+ icon?: boolean;
22
+ /**
23
+ * Renders the link without `href`, with `aria-disabled="true"`, so it is announced
24
+ * as an unavailable link and cannot be followed or focused.
25
+ */
26
+ disabled?: boolean;
27
+ /** Your router's link (`(p) => <NextLink {...p} />`); default a plain `<a>`. */
28
+ renderLink?: UixRenderLink;
29
+ children?: ReactNode;
30
+ }
31
+ /**
32
+ * A link that looks like a `Button` (HAR-1348; TENSOR A4/B5, MOTUS C-3). Use it when the
33
+ * action navigates; use `Button` when it acts in place.
34
+ */
35
+ declare function ButtonLink({ href, variant, size, icon, disabled, renderLink, className, children, onClick, ...props }: ButtonLinkProps): react.JSX.Element;
12
36
  interface ButtonGroupProps {
13
37
  children?: ReactNode;
14
38
  className?: string;
15
39
  }
16
40
  declare function ButtonGroup({ children, className }: ButtonGroupProps): react.JSX.Element;
17
41
 
18
- export { Button, ButtonGroup, type ButtonGroupProps, type ButtonProps };
42
+ export { Button, ButtonGroup, type ButtonGroupProps, ButtonLink, type ButtonLinkProps, type ButtonProps, type ButtonSize, type ButtonVariant };
@@ -1,6 +1,14 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { Fragment, jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { cx } from "../cx.js";
4
+ import { renderUixLink } from "../link.js";
5
+ const buttonClasses = (variant, size, icon, className) => cx(
6
+ "uix-btn",
7
+ `uix-btn--${variant}`,
8
+ size !== "md" && `uix-btn--${size}`,
9
+ icon && "uix-btn--icon",
10
+ className
11
+ );
4
12
  let warnedIconButton = false;
5
13
  const Button = forwardRef(
6
14
  ({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, onClick, ...props }, ref) => {
@@ -12,13 +20,7 @@ const Button = forwardRef(
12
20
  "button",
13
21
  {
14
22
  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
- ),
23
+ className: buttonClasses(variant, size, icon, className),
22
24
  disabled,
23
25
  "aria-disabled": loading || void 0,
24
26
  "aria-busy": loading || void 0,
@@ -37,10 +39,18 @@ const Button = forwardRef(
37
39
  }
38
40
  );
39
41
  Button.displayName = "Button";
42
+ function ButtonLink({ href, variant = "secondary", size = "md", icon = false, disabled = false, renderLink, className, children, onClick, ...props }) {
43
+ const classes = buttonClasses(variant, size, icon, className);
44
+ if (disabled) {
45
+ return /* @__PURE__ */ jsx("a", { ...props, className: classes, role: "link", "aria-disabled": "true", children });
46
+ }
47
+ return /* @__PURE__ */ jsx(Fragment, { children: renderUixLink(renderLink, { ...props, href, onClick, className: classes, children }) });
48
+ }
40
49
  function ButtonGroup({ children, className }) {
41
50
  return /* @__PURE__ */ jsx("div", { className: cx("uix-btn-group", className), children });
42
51
  }
43
52
  export {
44
53
  Button,
45
- ButtonGroup
54
+ ButtonGroup,
55
+ ButtonLink
46
56
  };
@@ -5,6 +5,12 @@ interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
5
5
  title?: ReactNode;
6
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
7
  titleAs?: 'h2' | 'h3' | 'h4' | 'div';
8
+ /**
9
+ * Id of the title element (HAR-1354; MOTUS B-A5), so a form, table or region elsewhere
10
+ * can point `aria-labelledby` at it. With a `role` (e.g. `role="region"`), the card is
11
+ * labelled by its title too; a plain card has no role, and ARIA forbids naming it.
12
+ */
13
+ titleId?: string;
8
14
  subtitle?: ReactNode;
9
15
  /**
10
16
  * Plain-text help behind a ? button after the title. Blank lines separate paragraphs;
@@ -17,7 +23,7 @@ interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
17
23
  footer?: ReactNode;
18
24
  children?: ReactNode;
19
25
  }
20
- declare function Card({ title, titleAs: TitleTag, subtitle, help, helpLabel, headerAction, footer, children, className, ...props }: CardProps): react.JSX.Element;
26
+ declare function Card({ title, titleAs: TitleTag, titleId, subtitle, help, helpLabel, headerAction, footer, children, className, ...props }: CardProps): react.JSX.Element;
21
27
  interface CardLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'title'> {
22
28
  title?: ReactNode;
23
29
  subtitle?: ReactNode;
@@ -2,10 +2,10 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cx } from "../cx.js";
3
3
  import { InfoTip } from "./InfoTip.js";
4
4
  import { hasHelp, helpLabelFor } from "../info-tip-model.js";
5
- function Card({ title, titleAs: TitleTag = "div", subtitle, help, helpLabel, headerAction, footer, children, className, ...props }) {
5
+ function Card({ title, titleAs: TitleTag = "div", titleId, subtitle, help, helpLabel, headerAction, footer, children, className, ...props }) {
6
6
  const hasHeader = title != null || subtitle != null || headerAction != null;
7
- const heading = title ? /* @__PURE__ */ jsx(TitleTag, { className: "uix-card__title", children: title }) : null;
8
- return /* @__PURE__ */ jsxs("div", { className: cx("uix-card", className), ...props, children: [
7
+ const heading = title ? /* @__PURE__ */ jsx(TitleTag, { className: "uix-card__title", id: titleId, children: title }) : null;
8
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-card", className), "aria-labelledby": heading && titleId && props.role ? titleId : void 0, ...props, children: [
9
9
  hasHeader && /* @__PURE__ */ jsxs("div", { className: "uix-card__header", children: [
10
10
  /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
11
11
  heading && hasHelp(help) ? /* @__PURE__ */ jsxs("div", { className: "uix-card__title-row", children: [
@@ -0,0 +1,43 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
+ import { UixRenderLink } from '../link.js';
4
+
5
+ interface ChipProps extends Omit<HTMLAttributes<HTMLElement>, 'onClick'> {
6
+ /** The chip's text. */
7
+ children?: ReactNode;
8
+ icon?: ReactNode;
9
+ /** A trailing count, e.g. the number of matching rows. */
10
+ count?: ReactNode;
11
+ /** Toggle chip: `aria-pressed`, filled while on. Pair with `onPressedChange`. */
12
+ pressed?: boolean;
13
+ onPressedChange?: (pressed: boolean) => void;
14
+ /** A chip that acts in place (e.g. opens the filter it shows for editing). */
15
+ onClick?: () => void;
16
+ /** A link chip. */
17
+ href?: string;
18
+ renderLink?: UixRenderLink;
19
+ /** Adds a × button that removes the chip. */
20
+ onRemove?: () => void;
21
+ /**
22
+ * Accessible name of the × button. `{label}` is replaced by the chip text when it is a
23
+ * string. Default "Remove {label}"; pass a full name when the text is not a string.
24
+ */
25
+ removeLabel?: string;
26
+ /** `add` is the dashed "+ Add filter" chip. */
27
+ variant?: 'default' | 'add';
28
+ disabled?: boolean;
29
+ }
30
+ /**
31
+ * Filter, tag and token chip over `.uix-chip` (HAR-1360; TENSOR C7/B34, MOTUS C-6/B-A18).
32
+ * Plain text, a toggle, a link or a button, optionally with a × that removes it.
33
+ */
34
+ declare function Chip({ children, icon, count, pressed, onPressedChange, onClick, href, renderLink, onRemove, removeLabel, variant, disabled, className, ...props }: ChipProps): react.JSX.Element;
35
+ interface ChipGroupProps extends HTMLAttributes<HTMLDivElement> {
36
+ /** Names the group, e.g. "Active filters". */
37
+ label?: string;
38
+ children?: ReactNode;
39
+ }
40
+ /** A wrapping row of chips with a group name. */
41
+ declare function ChipGroup({ label, children, className, ...props }: ChipGroupProps): react.JSX.Element;
42
+
43
+ export { Chip, ChipGroup, type ChipGroupProps, type ChipProps };
@@ -0,0 +1,73 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../cx.js";
3
+ import { fillLabel } from "../fill-label.js";
4
+ import { renderUixLink } from "../link.js";
5
+ function Chip({
6
+ children,
7
+ icon,
8
+ count,
9
+ pressed,
10
+ onPressedChange,
11
+ onClick,
12
+ href,
13
+ renderLink,
14
+ onRemove,
15
+ removeLabel = "Remove {label}",
16
+ variant = "default",
17
+ disabled,
18
+ className,
19
+ ...props
20
+ }) {
21
+ const toggle = pressed !== void 0;
22
+ const interactive = toggle || onClick != null || href != null;
23
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
24
+ icon != null && /* @__PURE__ */ jsx("span", { className: "uix-chip__icon", "aria-hidden": "true", children: icon }),
25
+ /* @__PURE__ */ jsx("span", { className: "uix-chip__label", children }),
26
+ count != null && /* @__PURE__ */ jsx("span", { className: "uix-chip__count", children: count })
27
+ ] });
28
+ const handleClick = () => {
29
+ if (disabled) return;
30
+ if (toggle) onPressedChange?.(!pressed);
31
+ onClick?.();
32
+ };
33
+ const outer = cx(
34
+ "uix-chip",
35
+ variant === "add" && "uix-chip--add",
36
+ onRemove && "uix-chip--removable",
37
+ !interactive && "uix-chip--static",
38
+ className
39
+ );
40
+ const main = (classes, extra) => {
41
+ if (href != null && !disabled) return renderUixLink(renderLink, { href, className: classes, children: body, ...extra });
42
+ if (interactive) {
43
+ return /* @__PURE__ */ jsx(
44
+ "button",
45
+ {
46
+ type: "button",
47
+ className: classes,
48
+ "aria-pressed": toggle ? pressed : void 0,
49
+ disabled,
50
+ onClick: handleClick,
51
+ ...extra,
52
+ children: body
53
+ }
54
+ );
55
+ }
56
+ return /* @__PURE__ */ jsx("span", { className: classes, ...extra, children: body });
57
+ };
58
+ if (!onRemove) {
59
+ return /* @__PURE__ */ jsx(Fragment, { children: main(outer, { ...props, "data-on": pressed || void 0 }) });
60
+ }
61
+ const removeName = fillLabel(removeLabel, { label: typeof children === "string" || typeof children === "number" ? String(children) : "" }).trim();
62
+ return /* @__PURE__ */ jsxs("span", { className: outer, "data-on": pressed || void 0, ...props, children: [
63
+ main("uix-chip__main"),
64
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-chip__remove", "aria-label": removeName, disabled, onClick: onRemove, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4.5 4.5l7 7M11.5 4.5l-7 7" }) }) })
65
+ ] });
66
+ }
67
+ function ChipGroup({ label, children, className, ...props }) {
68
+ return /* @__PURE__ */ jsx("div", { role: "group", "aria-label": label, className: cx("uix-chip-group", className), ...props, children });
69
+ }
70
+ export {
71
+ Chip,
72
+ ChipGroup
73
+ };
@@ -5,7 +5,25 @@ interface CollapsibleSectionProps extends Omit<DetailsHTMLAttributes<HTMLDetails
5
5
  title: ReactNode;
6
6
  summary?: ReactNode;
7
7
  children?: ReactNode;
8
+ /**
9
+ * Mount the body only while it is needed (HAR-1352; TENSOR B32): `true` mounts it while
10
+ * open and unmounts it on close; `'keep'` mounts it on first open and keeps it. For
11
+ * bodies that fetch or chart. Default: always mounted (and server-renderable).
12
+ */
13
+ lazy?: boolean | 'keep';
14
+ /**
15
+ * Remember the open state for the session under this key (`sessionStorage`,
16
+ * `uix:collapsible:<key>`). Applied after mount, so the server HTML uses `open`.
17
+ */
18
+ persistKey?: string;
19
+ /** Each new value opens the section and scrolls it into view ("jump to SLA"). */
20
+ openRequest?: number;
8
21
  }
9
- declare function CollapsibleSection({ title, summary, children, className, ...props }: CollapsibleSectionProps): react.JSX.Element;
22
+ /**
23
+ * A `<details>` disclosure. Without `lazy`, `persistKey` or `openRequest` it is a plain,
24
+ * server-renderable element that toggles without JavaScript; with any of them it becomes
25
+ * a client component that tracks its open state.
26
+ */
27
+ declare function CollapsibleSection(props: CollapsibleSectionProps): react.JSX.Element;
10
28
 
11
29
  export { CollapsibleSection, type CollapsibleSectionProps };
@@ -1,7 +1,10 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cx } from "../cx.js";
3
- function CollapsibleSection({ title, summary, children, className, ...props }) {
4
- return /* @__PURE__ */ jsxs("details", { className: cx("uix-collapsible", className), ...props, children: [
3
+ import { CollapsibleSectionState } from "./CollapsibleSectionState.js";
4
+ function CollapsibleSection(props) {
5
+ const { title, summary, children, className, lazy, persistKey, openRequest, ...rest } = props;
6
+ if (lazy || persistKey !== void 0 || openRequest !== void 0) return /* @__PURE__ */ jsx(CollapsibleSectionState, { ...props });
7
+ return /* @__PURE__ */ jsxs("details", { className: cx("uix-collapsible", className), ...rest, children: [
5
8
  /* @__PURE__ */ jsxs("summary", { className: "uix-collapsible__summary", children: [
6
9
  /* @__PURE__ */ jsxs("span", { children: [
7
10
  /* @__PURE__ */ jsx("span", { className: "uix-collapsible__title", children: title }),
@@ -0,0 +1,11 @@
1
+ import * as react from 'react';
2
+ import { CollapsibleSectionProps } from './CollapsibleSection.js';
3
+
4
+ /**
5
+ * The stateful half of `CollapsibleSection` (HAR-1352): lazy bodies, a remembered open
6
+ * state and `openRequest`. Internal — `CollapsibleSection` delegates here only when one
7
+ * of those props is set, so the plain section stays server-renderable and JS-free.
8
+ */
9
+ declare function CollapsibleSectionState({ title, summary, children, className, lazy, persistKey, openRequest, open, onToggle, ...props }: CollapsibleSectionProps): react.JSX.Element;
10
+
11
+ export { CollapsibleSectionState };
@@ -0,0 +1,67 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { cx } from "../cx.js";
5
+ const STORAGE_PREFIX = "uix:collapsible:";
6
+ const readStored = (key) => {
7
+ try {
8
+ const value = window.sessionStorage.getItem(STORAGE_PREFIX + key);
9
+ return value === "1" ? true : value === "0" ? false : void 0;
10
+ } catch {
11
+ return void 0;
12
+ }
13
+ };
14
+ const writeStored = (key, open) => {
15
+ try {
16
+ window.sessionStorage.setItem(STORAGE_PREFIX + key, open ? "1" : "0");
17
+ } catch {
18
+ }
19
+ };
20
+ function CollapsibleSectionState({ title, summary, children, className, lazy, persistKey, openRequest, open, onToggle, ...props }) {
21
+ const ref = useRef(null);
22
+ const [isOpen, setIsOpen] = useState(open ?? false);
23
+ const [hasOpened, setHasOpened] = useState(open ?? false);
24
+ const lastRequest = useRef(openRequest);
25
+ useEffect(() => {
26
+ if (!persistKey) return;
27
+ const stored = readStored(persistKey);
28
+ if (stored !== void 0) {
29
+ setIsOpen(stored);
30
+ if (stored) setHasOpened(true);
31
+ }
32
+ }, [persistKey]);
33
+ useEffect(() => {
34
+ if (open === void 0) return;
35
+ setIsOpen(open);
36
+ if (open) setHasOpened(true);
37
+ }, [open]);
38
+ useEffect(() => {
39
+ if (openRequest === void 0 || openRequest === lastRequest.current) return;
40
+ lastRequest.current = openRequest;
41
+ setIsOpen(true);
42
+ setHasOpened(true);
43
+ if (persistKey) writeStored(persistKey, true);
44
+ requestAnimationFrame(() => ref.current?.scrollIntoView?.({ block: "nearest" }));
45
+ }, [openRequest, persistKey]);
46
+ const handleToggle = (event) => {
47
+ const next = event.currentTarget.open;
48
+ setIsOpen(next);
49
+ if (next) setHasOpened(true);
50
+ if (persistKey) writeStored(persistKey, next);
51
+ onToggle?.(event);
52
+ };
53
+ const mountBody = !lazy || (lazy === "keep" ? hasOpened : isOpen);
54
+ return /* @__PURE__ */ jsxs("details", { ref, className: cx("uix-collapsible", className), ...props, open: isOpen, onToggle: handleToggle, children: [
55
+ /* @__PURE__ */ jsxs("summary", { className: "uix-collapsible__summary", children: [
56
+ /* @__PURE__ */ jsxs("span", { children: [
57
+ /* @__PURE__ */ jsx("span", { className: "uix-collapsible__title", children: title }),
58
+ summary != null && /* @__PURE__ */ jsx("span", { className: "uix-collapsible__meta", children: summary })
59
+ ] }),
60
+ /* @__PURE__ */ jsx("span", { className: "uix-collapsible__chevron", "aria-hidden": "true", children: "\u2304" })
61
+ ] }),
62
+ /* @__PURE__ */ jsx("div", { className: "uix-collapsible__body", children: mountBody ? children : null })
63
+ ] });
64
+ }
65
+ export {
66
+ CollapsibleSectionState
67
+ };
@@ -14,7 +14,7 @@ interface CommandPaletteProps extends HTMLAttributes<HTMLDivElement> {
14
14
  * a combobox over the listbox of `CommandItem`s; spreads override every default, so
15
15
  * consumers already passing their own roles/labels keep working.
16
16
  */
17
- declare function CommandPalette({ inputProps, children, inputLabel, className, ...props }: CommandPaletteProps): react.JSX.Element;
17
+ declare function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, className, ...props }: CommandPaletteProps): react.JSX.Element;
18
18
  interface CommandGroupProps {
19
19
  label?: ReactNode;
20
20
  children?: ReactNode;
@@ -2,8 +2,11 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useContext, useEffect, useId, useMemo, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
+ import { useUixLabels } from "../labels-context.js";
5
6
  const CmdkContext = createContext(null);
6
- function CommandPalette({ inputProps, children, inputLabel = "Command palette", className, ...props }) {
7
+ function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, className, ...props }) {
8
+ const uixLabels = useUixLabels();
9
+ const inputLabel = inputLabelProp ?? uixLabels.commandPalette?.input ?? "Command palette";
7
10
  const listId = useId();
8
11
  const [activeId, setActiveId] = useState(null);
9
12
  const [count, setCount] = useState(0);
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useMemo, useRef, useState } from "react";
4
4
  import { addCalendarDays, addCalendarMonths, buildMonthGrid, isDateInRange, isDateUnavailable, selectRangeDate, startOfMonth } from "../calendar-model.js";
5
5
  import { cx } from "../cx.js";
6
+ import { useUixLabels } from "../labels-context.js";
6
7
  const DEFAULT_DATE_RANGE_PICKER_LABELS = {
7
8
  previous: "Previous",
8
9
  previousMonth: "Show previous month",
@@ -39,7 +40,8 @@ function DateRangePicker({
39
40
  labels: labelOverrides
40
41
  }) {
41
42
  const id = useId();
42
- const labels = { ...DEFAULT_DATE_RANGE_PICKER_LABELS, ...labelOverrides };
43
+ const uixLabels = useUixLabels();
44
+ const labels = { ...DEFAULT_DATE_RANGE_PICKER_LABELS, ...uixLabels.dateRangePicker, ...labelOverrides };
43
45
  const weekdays = useMemo(() => weekdayNames(locale), [locale]);
44
46
  const [internalMonth, setInternalMonth] = useState(() => startOfMonth(controlledMonth ?? value.start ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)));
45
47
  const visibleMonth = startOfMonth(controlledMonth ?? internalMonth);
@@ -1,5 +1,6 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, RefObject } from 'react';
3
+ import { Placement } from '../overlay-position.js';
3
4
 
4
5
  interface ConfirmDialogProps {
5
6
  open: boolean;
@@ -12,9 +13,59 @@ interface ConfirmDialogProps {
12
13
  onCancel: () => void;
13
14
  pending?: boolean;
14
15
  destructive?: boolean;
16
+ /**
17
+ * Body below the description: consequences, a `<dl>` of what changes (HAR-1356; MOTUS B-A4).
18
+ * The description, body and compensation are the dialog's `aria-describedby`.
19
+ */
20
+ children?: ReactNode;
21
+ /** Why the last confirm failed. Shown in the dialog (`role="alert"`); the dialog stays open. */
22
+ error?: ReactNode;
23
+ /**
24
+ * Which button has focus when the dialog opens. Default: the type-to-confirm field when
25
+ * there is one, else Cancel for a `destructive` dialog, else the browser's first control.
26
+ */
27
+ initialFocus?: 'cancel' | 'confirm';
28
+ /** A third action at the start of the footer, e.g. "Save and leave" (MOTUS C-5). */
29
+ tertiaryLabel?: ReactNode;
30
+ onTertiary?: () => void | Promise<void>;
31
+ /**
32
+ * High-risk confirmation (TENSOR C2): Confirm stays disabled until the user types exactly
33
+ * this text, e.g. the record's name.
34
+ */
35
+ typeToConfirm?: string;
36
+ /** Label of the type-to-confirm field. `{value}` is replaced by `typeToConfirm`. */
37
+ typeToConfirmLabel?: string;
38
+ /** How the action is undone or compensated, shown as its own labelled block (TENSOR C2). */
39
+ compensation?: ReactNode;
40
+ /** Heading of the compensation block. Default "How to undo this". */
41
+ compensationLabel?: string;
15
42
  }
16
43
  /** Generic confirmation shell. Audit, authorization and compensation policy remain application-owned. */
17
- declare function ConfirmDialog({ open, title, description, confirmLabel, cancelLabel, closeLabel, onConfirm, onCancel, pending, destructive }: ConfirmDialogProps): react.JSX.Element;
44
+ declare function ConfirmDialog({ open, title, description, confirmLabel, cancelLabel, closeLabel, onConfirm, onCancel, pending, destructive, children, error, initialFocus, tertiaryLabel, onTertiary, typeToConfirm, typeToConfirmLabel: typeToConfirmLabelProp, compensation, compensationLabel: compensationLabelProp, }: ConfirmDialogProps): react.JSX.Element;
45
+ interface PopconfirmProps {
46
+ open: boolean;
47
+ /** The control that asked for confirmation; the step is placed against it and focus returns to it. */
48
+ anchor: RefObject<HTMLElement | null> | HTMLElement | null;
49
+ title: ReactNode;
50
+ description?: ReactNode;
51
+ confirmLabel: ReactNode;
52
+ cancelLabel: ReactNode;
53
+ onConfirm: () => void | Promise<void>;
54
+ /** Cancel, Escape and a press outside all call this. */
55
+ onCancel: () => void;
56
+ pending?: boolean;
57
+ destructive?: boolean;
58
+ /** How the action is undone, under its own heading (TENSOR C2 "single_click" tier). */
59
+ compensation?: ReactNode;
60
+ compensationLabel?: string;
61
+ placement?: Placement;
62
+ }
63
+ /**
64
+ * An anchored, single-step confirmation for low-risk actions (HAR-1356; TENSOR C2): a small
65
+ * `role="dialog"` on the kit `Popover`, focus on Cancel, Escape or a press outside cancels,
66
+ * and focus returns to the anchor. Use `ConfirmDialog` when the action is hard to undo.
67
+ */
68
+ declare function Popconfirm({ open, anchor, title, description, confirmLabel, cancelLabel, onConfirm, onCancel, pending, destructive, compensation, compensationLabel: compensationLabelProp, placement, }: PopconfirmProps): react.JSX.Element;
18
69
  interface PromptDialogProps {
19
70
  open: boolean;
20
71
  title: ReactNode;
@@ -32,4 +83,4 @@ interface PromptDialogProps {
32
83
  }
33
84
  declare function PromptDialog({ open, title, description, inputLabel, defaultValue, placeholder, submitLabel, cancelLabel, closeLabel, onSubmit, onCancel, validate, pending }: PromptDialogProps): react.JSX.Element;
34
85
 
35
- export { ConfirmDialog, type ConfirmDialogProps, PromptDialog, type PromptDialogProps };
86
+ export { ConfirmDialog, type ConfirmDialogProps, Popconfirm, type PopconfirmProps, PromptDialog, type PromptDialogProps };