@tensor_1/react 2.26.3 → 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.
- package/README.md +51 -0
- package/dist/calendar-model.d.ts +4 -2
- package/dist/components/AppShell.d.ts +1 -1
- package/dist/components/AppShell.js +6 -2
- package/dist/components/Button.d.ts +28 -4
- package/dist/components/Button.js +19 -9
- package/dist/components/Card.d.ts +7 -1
- package/dist/components/Card.js +3 -3
- package/dist/components/Chip.d.ts +43 -0
- package/dist/components/Chip.js +73 -0
- package/dist/components/CollapsibleSection.d.ts +19 -1
- package/dist/components/CollapsibleSection.js +5 -2
- package/dist/components/CollapsibleSectionState.d.ts +11 -0
- package/dist/components/CollapsibleSectionState.js +67 -0
- package/dist/components/CommandPalette.d.ts +1 -1
- package/dist/components/CommandPalette.js +4 -1
- package/dist/components/DateRangePicker.js +3 -1
- package/dist/components/Dialogs.d.ts +54 -3
- package/dist/components/Dialogs.js +167 -6
- package/dist/components/Drawer.d.ts +6 -1
- package/dist/components/Drawer.js +5 -2
- package/dist/components/Kbd.d.ts +27 -0
- package/dist/components/Kbd.js +45 -0
- package/dist/components/Menu.d.ts +51 -0
- package/dist/components/Menu.js +177 -0
- package/dist/components/Modal.d.ts +1 -1
- package/dist/components/Modal.js +4 -1
- package/dist/components/Pagination.d.ts +36 -5
- package/dist/components/Pagination.js +59 -37
- package/dist/components/Peek.d.ts +1 -1
- package/dist/components/Peek.js +6 -1
- package/dist/components/SchedulingCalendar.d.ts +47 -2
- package/dist/components/SchedulingCalendar.js +69 -22
- package/dist/components/SearchSuggest.d.ts +1 -1
- package/dist/components/SearchSuggest.js +4 -1
- package/dist/components/Sidebar.d.ts +7 -2
- package/dist/components/Sidebar.js +15 -4
- package/dist/components/Stat.d.ts +16 -0
- package/dist/components/Stat.js +12 -5
- package/dist/components/Steps.d.ts +48 -0
- package/dist/components/Steps.js +42 -0
- package/dist/components/Toast.d.ts +2 -2
- package/dist/components/Toast.js +7 -2
- package/dist/components/Tooltip.d.ts +10 -4
- package/dist/components/Tooltip.js +2 -2
- package/dist/emoji.cjs +2 -2
- package/dist/index.cjs +2061 -1350
- package/dist/index.d.cts +596 -165
- package/dist/index.d.ts +10 -4
- package/dist/index.js +24 -2
- package/dist/labels-context.d.ts +72 -0
- package/dist/labels-context.js +23 -0
- package/dist/link.d.ts +20 -0
- package/dist/link.js +9 -0
- 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.
|
package/dist/calendar-model.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
16
|
-
exitFocusLabel
|
|
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?:
|
|
6
|
-
size?:
|
|
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:
|
|
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;
|
package/dist/components/Card.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
|
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
|
|
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 };
|