@nadicodeai/ui 12.1.141 → 12.1.143

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.
@@ -112,6 +112,6 @@ function AvatarWithIcon({ icon, name, src, size = "default", iconBackground = fa
112
112
  return (_jsxs(Avatar, { ...props, size: size, children: [src ? _jsx(AvatarImage, { alt: "", src: src }) : null, _jsx(AvatarFallback, { children: avatarInitials(name, size === "xs" ? 1 : 2) }), _jsx("span", { "aria-hidden": "true", className: cn("absolute z-10 inline-flex items-center justify-center rounded-full text-muted-foreground", iconBackground && "border border-background bg-background", AVATAR_CORNER_ICON_CLASS[size]), "data-background": iconBackground || undefined, "data-slot": "avatar-icon", children: icon })] }));
113
113
  }
114
114
  function AvatarUploadButton({ label, pendingLabel, src, fallback, busy = false, onClick, }) {
115
- return (_jsxs("div", { className: "relative inline-flex w-20 shrink-0 flex-col items-center", children: [_jsxs(Button, { "aria-label": busy ? pendingLabel : label, loading: busy, size: "icon", type: "button", variant: "ghost", className: "group/avatar-upload relative size-20 shrink-0 rounded-full p-0", onClick: onClick, children: [_jsxs(Avatar, { className: "size-full", decorative: true, children: [_jsx(AvatarImage, { src: src, alt: "" }), _jsx(AvatarFallback, { className: "text-xl", children: fallback })] }), _jsx("span", { "aria-hidden": "true", className: "absolute inset-0 flex items-center justify-center rounded-full bg-background/80 opacity-0 group-hover/avatar-upload:opacity-100 group-focus-visible/avatar-upload:opacity-100 group-aria-busy/avatar-upload:opacity-100", children: _jsx(CameraIcon, { className: "size-5" }) })] }), busy ? _jsx(WaitingLine, { className: "absolute inset-x-0 -bottom-3", decorative: true }) : null] }));
115
+ return (_jsxs("div", { className: "relative inline-flex w-20 shrink-0 flex-col items-center", children: [_jsxs(Button, { "aria-label": busy ? pendingLabel : label, loading: busy, size: "icon", type: "button", variant: "ghost", className: "group/avatar-upload relative size-20 shrink-0 rounded-full p-0", onClick: onClick, children: [_jsxs(Avatar, { className: "size-full", decorative: true, children: [_jsx(AvatarImage, { src: src, alt: "" }), _jsx(AvatarFallback, { className: "text-xl", children: fallback })] }), _jsx("span", { "aria-hidden": "true", className: "absolute inset-0 flex items-center justify-center rounded-full bg-background/80 opacity-0 group-hover/avatar-upload:opacity-100 group-focus-visible/avatar-upload:opacity-100 group-aria-busy/avatar-upload:opacity-100", children: _jsx(CameraIcon, { className: "size-5" }) })] }), _jsx(WaitingLine, { className: "absolute inset-x-0 -bottom-3", decorative: true, waiting: busy })] }));
116
116
  }
117
117
  export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, AvatarUploadButton, AvatarWithIcon, avatarInitials, };
@@ -1 +1 @@
1
- {"version":3,"file":"clearable-input.d.ts","sourceRoot":"","sources":["../../src/components/clearable-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAS/B,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGtD,sGAAsG;AACtG,MAAM,MAAM,sBAAsB,GAAG,QAAQ,SAAS,MAAM,IAAI,GAC5D,IAAI,SAAS,OAAO,GAClB,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,GAClB,KAAK,GACP,KAAK,CAAC;AAEV,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,EAC7B,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAClE,GAAG;IACF,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,gHAAgH;IAChH,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,SAAS,CAAC;AAEd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,iBAAS,cAAc,CAAC,EACtB,SAAS,EACT,KAAK,EACL,aAAa,EACb,OAAO,EACP,UAAU,EACV,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,OAAe,EACf,KAAK,EACL,KAAK,EACL,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,mBAAmB,qBAyErB;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"clearable-input.d.ts","sourceRoot":"","sources":["../../src/components/clearable-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAS/B,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGtD,sGAAsG;AACtG,MAAM,MAAM,sBAAsB,GAAG,QAAQ,SAAS,MAAM,IAAI,GAC5D,IAAI,SAAS,OAAO,GAClB,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,GAClB,KAAK,GACP,KAAK,CAAC;AAEV,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,EAC7B,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAClE,GAAG;IACF,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,gHAAgH;IAChH,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,SAAS,CAAC;AAEd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,iBAAS,cAAc,CAAC,EACtB,SAAS,EACT,KAAK,EACL,aAAa,EACb,OAAO,EACP,UAAU,EACV,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,OAAe,EACf,KAAK,EACL,KAAK,EACL,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,mBAAmB,qBA2ErB;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -54,6 +54,6 @@ function ClearableInput({ className, value, onValueChange, onClear, clearLabel,
54
54
  event.preventDefault();
55
55
  event.stopPropagation();
56
56
  clear();
57
- }, value: value, ...props, ...aria }), clearable ? (_jsx(InputGroupAddon, { align: "inline-end", className: "has-[>button]:pl-3", children: _jsxs(InputGroupButton, { "aria-keyshortcuts": "Escape", "aria-label": clearLabel, onClick: clear, size: "icon", children: [_jsx(Kbd, { "aria-hidden": "true", className: "pointer-coarse:hidden group-hover/button:text-foreground", size: "sm", children: "Esc" }), _jsx(XIcon, { "aria-hidden": "true", className: "hidden pointer-coarse:block" })] }) })) : (shortcutHint), loading ? (_jsx(WaitingLine, { className: "absolute inset-x-1.5 bottom-px w-auto", decorative: true })) : null] })) }));
57
+ }, value: value, ...props, ...aria }), clearable ? (_jsx(InputGroupAddon, { align: "inline-end", className: "has-[>button]:pl-3", children: _jsxs(InputGroupButton, { "aria-keyshortcuts": "Escape", "aria-label": clearLabel, onClick: clear, size: "icon", children: [_jsx(Kbd, { "aria-hidden": "true", className: "pointer-coarse:hidden group-hover/button:text-foreground", size: "sm", children: "Esc" }), _jsx(XIcon, { "aria-hidden": "true", className: "hidden pointer-coarse:block" })] }) })) : (shortcutHint), _jsx(WaitingLine, { className: "absolute inset-x-1.5 bottom-px w-auto", decorative: true, waiting: loading })] })) }));
58
58
  }
59
59
  export { ClearableInput };
@@ -30,6 +30,6 @@ export function CollectionPage(props) {
30
30
  const { title, description, actions, tabs, attention, search, filters, sort, toolbarSurface, noMatch, pending, rail, ...list } = props;
31
31
  const hasRecords = list.records !== undefined && list.records.length > 0;
32
32
  const narrowed = Boolean(search?.value) || (filters?.facets.some((facet) => facet.value) ?? false);
33
- const page = (_jsxs("div", { className: "grid min-w-0 gap-4", "data-slot": "collection-page", children: [title ? _jsx(PageHeader, { description: description, title: title }) : null, actions ? _jsx(PageActions, { ...actions }) : null, tabs, attention, (search || filters || sort) && (hasRecords || narrowed) ? (_jsx(CollectionToolbar, { actions: filters || sort ? (_jsxs(_Fragment, { children: [filters ? _jsx(CollectionFilters, { ...filters }) : null, sort ? _jsx(CollectionSort, { ...sort }) : null] })) : undefined, "aria-busy": pending || undefined, label: list.label, search: search ? _jsx(CollectionSearch, { ...search }) : undefined, status: pending ? _jsx(WaitingLine, { className: "w-24", decorative: true }) : null, surface: toolbarSurface ?? "plain" })) : null, rail ? (_jsxs("div", { className: "grid min-w-0 gap-6 xl:grid-cols-[18.75rem_minmax(0,1fr)] xl:items-start", "data-slot": "collection-columns", children: [_jsx("div", { className: "min-w-0 xl:col-start-2 xl:row-start-1", children: _jsx(RecordList, { ...list, layout: "line", noMatch: narrowed ? noMatch : undefined }) }), _jsx(CollectionRailColumn, { className: "xl:col-start-1 xl:row-start-1", rail: rail })] })) : (_jsx(RecordList, { ...list, noMatch: narrowed ? noMatch : undefined }))] }));
33
+ const page = (_jsxs("div", { className: "grid min-w-0 gap-4", "data-slot": "collection-page", children: [title ? _jsx(PageHeader, { description: description, title: title }) : null, actions ? _jsx(PageActions, { ...actions }) : null, tabs, attention, (search || filters || sort) && (hasRecords || narrowed) ? (_jsx(CollectionToolbar, { actions: filters || sort ? (_jsxs(_Fragment, { children: [filters ? _jsx(CollectionFilters, { ...filters }) : null, sort ? _jsx(CollectionSort, { ...sort }) : null] })) : undefined, "aria-busy": pending || undefined, label: list.label, search: search ? _jsx(CollectionSearch, { ...search }) : undefined, status: _jsx(WaitingLine, { className: "w-24", decorative: true, waiting: pending === true }), surface: toolbarSurface ?? "plain" })) : null, rail ? (_jsxs("div", { className: "grid min-w-0 gap-6 xl:grid-cols-[18.75rem_minmax(0,1fr)] xl:items-start", "data-slot": "collection-columns", children: [_jsx("div", { className: "min-w-0 xl:col-start-2 xl:row-start-1", children: _jsx(RecordList, { ...list, layout: "line", noMatch: narrowed ? noMatch : undefined }) }), _jsx(CollectionRailColumn, { className: "xl:col-start-1 xl:row-start-1", rail: rail })] })) : (_jsx(RecordList, { ...list, noMatch: narrowed ? noMatch : undefined }))] }));
34
34
  return title ? _jsx(PageActionsHostProvider, { children: page }) : page;
35
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"form-dialog.d.ts","sourceRoot":"","sources":["../../src/components/form-dialog.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,KAAK,SAAS,EAAoB,MAAM,wBAAwB,CAAC;AAG1E,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG;IAC/C,0EAA0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG;IAC5C,gGAAgG;IAChG,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,oBAAoB,EACpB,KAAK,EACL,WAAW,EACX,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,UAAiB,EACjB,OAAO,EACP,OAAe,GAChB,EAAE,eAAe,+BAmBjB;AAED,wBAAgB,cAAc,CAAC,EAC7B,WAAW,EACX,MAAM,EACN,MAAM,EACN,WAAmB,EACnB,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,mBAAmB,+BAsBrB"}
1
+ {"version":3,"file":"form-dialog.d.ts","sourceRoot":"","sources":["../../src/components/form-dialog.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,KAAK,SAAS,EAAoB,MAAM,wBAAwB,CAAC;AAG1E,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG;IAC/C,0EAA0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG;IAC5C,gGAAgG;IAChG,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,oBAAoB,EACpB,KAAK,EACL,WAAW,EACX,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,UAAiB,EACjB,OAAO,EACP,OAAe,GAChB,EAAE,eAAe,+BAmBjB;AAED,wBAAgB,cAAc,CAAC,EAC7B,WAAW,EACX,MAAM,EACN,MAAM,EACN,WAAmB,EACnB,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,mBAAmB,+BAwBrB"}
@@ -24,5 +24,5 @@ export function FormDialog({ open, onOpenChange, onOpenChangeComplete, title, de
24
24
  }
25
25
  export function FormDialogForm({ cancelLabel, submit, notice, destructive = false, children, ...form }) {
26
26
  const pending = submit.pending === true;
27
- return (_jsxs("form", { className: "flex min-h-0 flex-col gap-(--dialog-gap)", "aria-busy": pending || undefined, "data-slot": "form-dialog-form", ...form, children: [_jsx(DialogBody, { children: children }), notice, _jsxs(DialogFooter, { className: "relative", children: [_jsx(DialogClose, { disabled: pending, render: _jsx(Button, { type: "button", variant: "outline" }), children: cancelLabel }), _jsx(FormSubmitButton, { destructive: destructive, submit: submit }), pending ? (_jsx(WaitingLine, { className: "absolute inset-x-nadicode-sm bottom-1 w-auto", decorative: true })) : null] })] }));
27
+ return (_jsxs("form", { className: "flex min-h-0 flex-col gap-(--dialog-gap)", "aria-busy": pending || undefined, "data-slot": "form-dialog-form", ...form, children: [_jsx(DialogBody, { children: children }), notice, _jsxs(DialogFooter, { className: "relative", children: [_jsx(DialogClose, { disabled: pending, render: _jsx(Button, { type: "button", variant: "outline" }), children: cancelLabel }), _jsx(FormSubmitButton, { destructive: destructive, submit: submit }), _jsx(WaitingLine, { className: "absolute inset-x-nadicode-sm bottom-1 w-auto", decorative: true, waiting: pending })] })] }));
28
28
  }
@@ -26,7 +26,7 @@ export function FormDrawerBody({ className, ...props }) {
26
26
  }
27
27
  /** The footer that stays in place under the body, on the sheet's own surface: Cancel, then the actions, at the trailing edge. */
28
28
  export function FormDrawerFooter({ cancel, children }) {
29
- return (_jsxs(SheetFooter, { className: "flex-wrap items-center", "data-slot": "form-drawer-footer", children: [cancel ? (_jsx(SheetClose, { disabled: cancel.disabled, render: _jsx(Button, { type: "button", variant: "outline" }), children: cancel.label })) : null, children] }));
29
+ return (_jsxs(SheetFooter, { className: "relative flex-wrap items-center", "data-slot": "form-drawer-footer", children: [cancel ? (_jsx(SheetClose, { disabled: cancel.disabled, render: _jsx(Button, { type: "button", variant: "outline" }), children: cancel.label })) : null, children] }));
30
30
  }
31
31
  /**
32
32
  * The drawer's form: the fields in the body, a notice where the save button
@@ -35,5 +35,5 @@ export function FormDrawerFooter({ cancel, children }) {
35
35
  */
36
36
  export function FormDrawerForm({ cancelLabel, submit, notice, children, ...form }) {
37
37
  const pending = submit.pending === true;
38
- return (_jsxs("form", { "aria-busy": pending || undefined, className: "flex min-h-0 flex-1 flex-col", "data-slot": "form-drawer-form", ...form, children: [_jsx(FormDrawerBody, { children: children }), notice ? (_jsx("div", { className: "px-nadicode-lg pt-nadicode-md", "data-slot": "form-drawer-notice", children: notice })) : null, _jsxs(FormDrawerFooter, { cancel: { label: cancelLabel, disabled: pending }, children: [_jsx(FormSubmitButton, { submit: submit }), pending ? _jsx(WaitingLine, { className: "basis-full", decorative: true }) : null] })] }));
38
+ return (_jsxs("form", { "aria-busy": pending || undefined, className: "flex min-h-0 flex-1 flex-col", "data-slot": "form-drawer-form", ...form, children: [_jsx(FormDrawerBody, { children: children }), notice ? (_jsx("div", { className: "px-nadicode-lg pt-nadicode-md", "data-slot": "form-drawer-notice", children: notice })) : null, _jsxs(FormDrawerFooter, { cancel: { label: cancelLabel, disabled: pending }, children: [_jsx(FormSubmitButton, { submit: submit }), _jsx(WaitingLine, { className: "absolute inset-x-0 top-0", decorative: true, waiting: pending })] })] }));
39
39
  }
@@ -15,5 +15,5 @@ import { FormSubmitButton } from "../internal/form-submit.js";
15
15
  */
16
16
  export function RowEditForm({ cancelLabel, onCancel, submit, notice, hint, children, ...form }) {
17
17
  const pending = submit.pending === true;
18
- return (_jsxs("form", { "aria-busy": pending || undefined, "data-slot": "row-edit-form", ...form, children: [_jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)] gap-4 px-nadicode-md pt-nadicode-sm pb-nadicode-md", children: [children, notice ? _jsx("div", { "data-slot": "row-edit-form-notice", children: notice }) : null] }), _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-3 border-t border-border bg-recessed px-nadicode-md py-3", "data-slot": "row-edit-form-footer", children: [hint ? _jsx("div", { className: "mr-auto min-w-0", children: hint }) : null, _jsx(Button, { disabled: pending, onClick: onCancel, type: "button", variant: "outline", children: cancelLabel }), _jsx(FormSubmitButton, { submit: submit }), pending ? _jsx(WaitingLine, { className: "basis-full", decorative: true }) : null] })] }));
18
+ return (_jsxs("form", { "aria-busy": pending || undefined, "data-slot": "row-edit-form", ...form, children: [_jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)] gap-4 px-nadicode-md pt-nadicode-sm pb-nadicode-md", children: [children, notice ? _jsx("div", { "data-slot": "row-edit-form-notice", children: notice }) : null] }), _jsxs("div", { className: "relative flex flex-wrap items-center justify-end gap-3 border-t border-border bg-recessed px-nadicode-md py-3", "data-slot": "row-edit-form-footer", children: [hint ? _jsx("div", { className: "mr-auto min-w-0", children: hint }) : null, _jsx(Button, { disabled: pending, onClick: onCancel, type: "button", variant: "outline", children: cancelLabel }), _jsx(FormSubmitButton, { submit: submit }), _jsx(WaitingLine, { className: "absolute inset-x-0 top-0", decorative: true, waiting: pending })] })] }));
19
19
  }
@@ -26,7 +26,7 @@ const Toaster = ({ closeLabel, regionLabel, ...props }) => {
26
26
  close: _jsx(XIcon, { className: "size-4" }),
27
27
  // A toast that stays up is already past a second, so its mark is the
28
28
  // one waiting line rather than a spinning glyph.
29
- loading: _jsx(WaitingLine, { className: "w-8", decorative: true }),
29
+ loading: _jsx(WaitingLine, { className: "w-8", decorative: true, waiting: true }),
30
30
  },
31
31
  // SAFETY: CSSProperties lists only standard properties; React sets each `--` custom property key here through style.setProperty.
32
32
  style: {
@@ -5,7 +5,10 @@ import * as React from "react";
5
5
  * every surface waits the same amount of time before anything moves.
6
6
  */
7
7
  declare const WAITING_LINE_DELAY_MS = 1000;
8
- type WaitingLineProps = Omit<React.ComponentProps<"div">, "children"> & ({
8
+ type WaitingLineProps = Omit<React.ComponentProps<"div">, "children"> & {
9
+ /** Whether the work is running now. The line stays mounted while it is not, so a wait never changes the layout. */
10
+ waiting: boolean;
11
+ } & ({
9
12
  decorative: true;
10
13
  label?: never;
11
14
  } | {
@@ -21,7 +24,10 @@ type WaitingLineProps = Omit<React.ComponentProps<"div">, "children"> & ({
21
24
  * In a record's row, `WaitingDetail` puts it under the row's detail line, which names the work.
22
25
  * @instead Under a second the `PendingLabel` alone. A known amount is a progress line with a count.
23
26
  * A page load is `Skeleton`. An Agent working is her orb and a sentence.
24
- * @behaviour Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing.
27
+ * @behaviour Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing. It is
28
+ * mounted with the control or region it waits for and told `waiting`, never mounted on a condition: a line
29
+ * that mounts with the wait adds its height and its gap to the layout (lint: nadicodeai/waiting-line-stays-mounted).
30
+ * In a footer it sits on the footer's edge, out of the flow (`absolute inset-x-0 top-0` in a `relative` footer).
25
31
  * No track colour; the segment is ink, never info blue, crossing at 32% width. Under reduced motion
26
32
  * it breathes in opacity, across the whole track, instead of travelling.
27
33
  * @words None of its own. The control beside it carries the pending word; a standalone line takes a
@@ -29,8 +35,8 @@ type WaitingLineProps = Omit<React.ComponentProps<"div">, "children"> & ({
29
35
  * @access With a label: `role="status"`, announced once after the second. `decorative` when a control or
30
36
  * sentence beside it already names the work (DESIGN.md "System activity"). The segment is hidden.
31
37
  */
32
- declare function WaitingLine({ className, decorative, label, ...props }: WaitingLineProps): React.JSX.Element;
33
- /** A record's wait in its row: the detail line naming the work, with the waiting line under it. */
38
+ declare function WaitingLine({ className, decorative, label, waiting, ...props }: WaitingLineProps): React.JSX.Element;
39
+ /** A record's wait in its row: the detail line naming the work, with the waiting line along the bottom of the detail's own box, out of the flow but inside a row that clips its detail, so the row keeps its height when its detail turns into the wait. */
34
40
  declare function WaitingDetail({ work }: {
35
41
  work: string;
36
42
  }): React.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"waiting-line.d.ts","sourceRoot":"","sources":["../../src/components/waiting-line.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B;;;;GAIG;AACH,QAAA,MAAM,qBAAqB,OAAO,CAAC;AAEnC,KAAK,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GACnE,CAAC;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEhF;;;;;;;;;;;;;;;;GAgBG;AACH,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,UAAkB,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,EAAE,gBAAgB,qBA0BxF;AAED,mGAAmG;AACnG,iBAAS,aAAa,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,qBAOhD;AAED,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,qBAAqB,EAAE,CAAC"}
1
+ {"version":3,"file":"waiting-line.d.ts","sourceRoot":"","sources":["../../src/components/waiting-line.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B;;;;GAIG;AACH,QAAA,MAAM,qBAAqB,OAAO,CAAC;AAEnC,KAAK,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG;IACtE,mHAAmH;IACnH,OAAO,EAAE,OAAO,CAAC;CAClB,GAAG,CAAC;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAElF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,UAAkB,EAClB,KAAK,EACL,OAAO,EACP,GAAG,KAAK,EACT,EAAE,gBAAgB,qBA+BlB;AAED,4PAA4P;AAC5P,iBAAS,aAAa,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,qBAOhD;AAED,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,qBAAqB,EAAE,CAAC"}
@@ -17,7 +17,10 @@ const WAITING_LINE_DELAY_MS = 1000;
17
17
  * In a record's row, `WaitingDetail` puts it under the row's detail line, which names the work.
18
18
  * @instead Under a second the `PendingLabel` alone. A known amount is a progress line with a count.
19
19
  * A page load is `Skeleton`. An Agent working is her orb and a sentence.
20
- * @behaviour Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing.
20
+ * @behaviour Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing. It is
21
+ * mounted with the control or region it waits for and told `waiting`, never mounted on a condition: a line
22
+ * that mounts with the wait adds its height and its gap to the layout (lint: nadicodeai/waiting-line-stays-mounted).
23
+ * In a footer it sits on the footer's edge, out of the flow (`absolute inset-x-0 top-0` in a `relative` footer).
21
24
  * No track colour; the segment is ink, never info blue, crossing at 32% width. Under reduced motion
22
25
  * it breathes in opacity, across the whole track, instead of travelling.
23
26
  * @words None of its own. The control beside it carries the pending word; a standalone line takes a
@@ -25,17 +28,23 @@ const WAITING_LINE_DELAY_MS = 1000;
25
28
  * @access With a label: `role="status"`, announced once after the second. `decorative` when a control or
26
29
  * sentence beside it already names the work (DESIGN.md "System activity"). The segment is hidden.
27
30
  */
28
- function WaitingLine({ className, decorative = false, label, ...props }) {
29
- const [elapsed, setElapsed] = React.useState(false);
31
+ function WaitingLine({ className, decorative = false, label, waiting, ...props }) {
32
+ const [elapsedWait, setElapsedWait] = React.useState(false);
30
33
  React.useEffect(() => {
31
- const timer = window.setTimeout(() => setElapsed(true), WAITING_LINE_DELAY_MS);
32
- return () => window.clearTimeout(timer);
33
- }, []);
34
+ if (!waiting)
35
+ return;
36
+ const timer = window.setTimeout(() => setElapsedWait(true), WAITING_LINE_DELAY_MS);
37
+ return () => {
38
+ window.clearTimeout(timer);
39
+ setElapsedWait(false);
40
+ };
41
+ }, [waiting]);
42
+ const elapsed = waiting && elapsedWait;
34
43
  const announced = elapsed && !decorative;
35
44
  return (_jsx("div", { ...props, "aria-label": announced ? label : undefined, className: cn("relative h-0.5 w-full overflow-hidden", className), "data-slot": "waiting-line", "data-waiting": elapsed || undefined, role: announced ? "status" : undefined, children: elapsed ? (_jsx("span", { "aria-hidden": "true", className: "nc-anim-wait absolute inset-y-0 start-0 block bg-current" })) : null }));
36
45
  }
37
- /** A record's wait in its row: the detail line naming the work, with the waiting line under it. */
46
+ /** A record's wait in its row: the detail line naming the work, with the waiting line along the bottom of the detail's own box, out of the flow but inside a row that clips its detail, so the row keeps its height when its detail turns into the wait. */
38
47
  function WaitingDetail({ work }) {
39
- return (_jsxs("span", { className: "grid gap-1.5", "data-slot": "waiting-detail", children: [_jsx("span", { children: work }), _jsx(WaitingLine, { decorative: true })] }));
48
+ return (_jsxs("span", { className: "relative block", "data-slot": "waiting-detail", children: [_jsx("span", { children: work }), _jsx(WaitingLine, { className: "absolute inset-x-0 bottom-0", decorative: true, waiting: true })] }));
40
49
  }
41
50
  export { WaitingDetail, WaitingLine, WAITING_LINE_DELAY_MS };
@@ -27,8 +27,6 @@ const AUTH_CALLS = new Set([
27
27
  "getPortalSession",
28
28
  "installationActionContext",
29
29
  "pageActor",
30
- "requirePlatformAdminViewer",
31
- "requirePlatformOrPartnerViewer",
32
30
  "requirePortalAdmin",
33
31
  "requirePortalViewer",
34
32
  "requireViewerForPage",
@@ -218,6 +218,18 @@ export const strictDesignSystemSelectors = {
218
218
  "This overlay takes a `key` that remounts it, so an opening that changes the key mounts it already open and its fade in never runs. Keep one mounted overlay and reset its content another way, such as a key on the content inside it.",
219
219
  },
220
220
  ],
221
+ "waiting-line-stays-mounted": [
222
+ ...keepName(["WaitingLine"]),
223
+ ...[
224
+ "LogicalExpression > JSXElement[openingElement.name.name='WaitingLine']",
225
+ "ConditionalExpression:not([alternate.type='JSXElement']) > JSXElement.consequent[openingElement.name.name='WaitingLine']",
226
+ "ConditionalExpression:not([consequent.type='JSXElement']) > JSXElement.alternate[openingElement.name.name='WaitingLine']",
227
+ ].map((selector) => ({
228
+ selector,
229
+ message:
230
+ "This waiting line mounts with the wait, so its 2 px and its gap arrive with it and the layout moves. Keep it mounted with the control it waits for and pass `waiting`, or place it on its container's edge, out of the flow (rule/waiting-line-stays-mounted).",
231
+ })),
232
+ ],
221
233
  "prefer-radio-for-few-static-options": [
222
234
  ...keepName([
223
235
  "SelectContent",
@@ -78,7 +78,7 @@ const AttachmentUI = () => {
78
78
  const errorMessage = useAuiState((s) => s.attachment.status.type === "incomplete" && s.attachment.status.reason === "error"
79
79
  ? (s.attachment.status.message ?? "Upload failed")
80
80
  : undefined);
81
- return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsxs(AttachmentPrimitive.Root, { className: cn("aui-attachment-root relative", isComposer && "nc-transition-opacity", isImage && !isComposer && "aui-attachment-root-message only:*:first:size-24"), children: [_jsx(AttachmentPreviewDialog, { children: _jsxs(TooltipTrigger, { render: _jsx("div", { className: cn("aui-attachment-tile nc-transition-transform relative size-14 cursor-pointer overflow-hidden rounded-[calc(var(--composer-radius)-var(--composer-padding))] bg-muted after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:ring-1 after:ring-border after:ring-inset hover:after:bg-foreground/10", isError && "after:ring-destructive/60"), role: "button", tabIndex: 0, "aria-label": `${typeLabel} attachment${isError ? ", upload failed" : isUploading ? ", uploading" : ""}` }), children: [_jsx(AttachmentThumb, {}), isUploading && (_jsx("div", { "aria-hidden": "true", className: "aui-attachment-tile-uploading nc-transition-opacity absolute inset-0 flex items-center justify-center bg-muted", children: _jsx(WaitingLine, { className: "w-8", decorative: true }) })), isError && (_jsx("div", { "aria-hidden": "true", className: "aui-attachment-tile-error nc-transition-opacity absolute inset-0 flex items-center justify-center bg-muted", children: _jsx(AlertCircleIcon, { className: "text-destructive size-4" }) }))] }) }), isComposer && _jsx(AttachmentRemove, {})] }), _jsxs(TooltipContent, { side: "top", children: [_jsx(AttachmentPrimitive.Name, {}), errorMessage && _jsx("p", { className: "aui-attachment-error-message", children: errorMessage })] })] }) }));
81
+ return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsxs(AttachmentPrimitive.Root, { className: cn("aui-attachment-root relative", isComposer && "nc-transition-opacity", isImage && !isComposer && "aui-attachment-root-message only:*:first:size-24"), children: [_jsx(AttachmentPreviewDialog, { children: _jsxs(TooltipTrigger, { render: _jsx("div", { className: cn("aui-attachment-tile nc-transition-transform relative size-14 cursor-pointer overflow-hidden rounded-[calc(var(--composer-radius)-var(--composer-padding))] bg-muted after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:ring-1 after:ring-border after:ring-inset hover:after:bg-foreground/10", isError && "after:ring-destructive/60"), role: "button", tabIndex: 0, "aria-label": `${typeLabel} attachment${isError ? ", upload failed" : isUploading ? ", uploading" : ""}` }), children: [_jsx(AttachmentThumb, {}), isUploading && (_jsx("div", { "aria-hidden": "true", className: "aui-attachment-tile-uploading nc-transition-opacity absolute inset-0 flex items-center justify-center bg-muted", children: _jsx(WaitingLine, { className: "w-8", decorative: true, waiting: true }) })), isError && (_jsx("div", { "aria-hidden": "true", className: "aui-attachment-tile-error nc-transition-opacity absolute inset-0 flex items-center justify-center bg-muted", children: _jsx(AlertCircleIcon, { className: "text-destructive size-4" }) }))] }) }), isComposer && _jsx(AttachmentRemove, {})] }), _jsxs(TooltipContent, { side: "top", children: [_jsx(AttachmentPrimitive.Name, {}), errorMessage && _jsx("p", { className: "aui-attachment-error-message", children: errorMessage })] })] }) }));
82
82
  };
83
83
  const AttachmentRemove = () => {
84
84
  return (_jsx(AttachmentPrimitive.Remove, { render: _jsx(TooltipIconButton, { tooltip: "Remove file", className: "aui-attachment-tile-remove absolute end-1 top-1 rounded-md bg-background nc-elevation-floating after:absolute after:-inset-1.5", side: "top" }), children: _jsx(XIcon, { className: "aui-attachment-remove-icon size-3 stroke-[2.5]" }) }));
@@ -59,7 +59,7 @@ function SaveFooter({ save, changed }) {
59
59
  pendingLabel: save.pendingLabel,
60
60
  pending,
61
61
  disabled: !changed,
62
- } }), pending ? _jsx(WaitingLine, { className: "absolute inset-x-0 top-0", decorative: true }) : null] }));
62
+ } }), _jsx(WaitingLine, { className: "absolute inset-x-0 top-0", decorative: true, waiting: pending })] }));
63
63
  }
64
64
  export function SettingsSaveForm({ save, titleId, children, }) {
65
65
  const [fieldsChanged, setFieldsChanged] = useState(false);
@@ -78,5 +78,5 @@ export const FEEDBACK = {
78
78
  Toaster: ToasterSpecimen,
79
79
  Tooltip: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx(Button, { "aria-label": "Remove attachment", size: "icon", variant: "ghost" }), children: _jsx(X, { "aria-hidden": "true" }) }), _jsx(TooltipContent, { children: "Remove attachment" })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx(Button, { "aria-label": "Search", size: "icon", variant: "outline" }), children: _jsx(Search, { "aria-hidden": "true" }) }), _jsxs(TooltipContent, { children: ["Search ", _jsx(Kbd, { children: "/" })] })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx(Button, { "aria-label": "Find a mailbox", size: "icon", variant: "outline" }), children: _jsx(Mail, { "aria-hidden": "true" }) }), _jsxs(TooltipContent, { children: ["Find a mailbox ", _jsx(Kbd, { meta: true, children: "K" })] })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx(Button, { variant: "outline" }), children: "Without a stem" }), _jsx(TooltipContent, { arrow: false, children: "Pinned to the button" })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx(Button, { variant: "outline" }), children: "Monthly limit" }), _jsx(TooltipContent, { centerText: false, children: "Limit: 400 runs a month. It resets on 1 November and unused runs do not carry over." })] })] })),
80
80
  UpdateCard: UpdateCardSpecimen,
81
- WaitingLine: () => (_jsxs("div", { className: "grid max-w-sm gap-2 text-sm", children: ["Connecting Telegram", _jsx(WaitingLine, { label: "Connecting Telegram" })] })),
81
+ WaitingLine: () => (_jsxs("div", { className: "grid max-w-sm gap-2 text-sm", children: ["Connecting Telegram", _jsx(WaitingLine, { label: "Connecting Telegram", waiting: true })] })),
82
82
  };
@@ -1396,7 +1396,7 @@ export const USAGE = [
1396
1396
  summary: "The one moving thing a system wait may show: a 2 px ink line, after a second.",
1397
1397
  when: "A system wait the person asked for that runs past a second: a save, a connection, a download without a count. In a record's row, `WaitingDetail` puts it under the row's detail line, which names the work.",
1398
1398
  instead: "Under a second the `PendingLabel` alone. A known amount is a progress line with a count. A page load is `Skeleton`. An Agent working is her orb and a sentence.",
1399
- behaviour: "Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing. No track colour; the segment is ink, never info blue, crossing at 32% width. Under reduced motion it breathes in opacity, across the whole track, instead of travelling.",
1399
+ behaviour: "Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing. It is mounted with the control or region it waits for and told `waiting`, never mounted on a condition: a line that mounts with the wait adds its height and its gap to the layout (lint: nadicodeai/waiting-line-stays-mounted). In a footer it sits on the footer's edge, out of the flow (`absolute inset-x-0 top-0` in a `relative` footer). No track colour; the segment is ink, never info blue, crossing at 32% width. Under reduced motion it breathes in opacity, across the whole track, instead of travelling.",
1400
1400
  words: "None of its own. The control beside it carries the pending word; a standalone line takes a `label` naming the work: *Connecting Telegram*.",
1401
1401
  access: 'With a label: `role="status"`, announced once after the second. `decorative` when a control or sentence beside it already names the work (DESIGN.md "System activity"). The segment is hidden.',
1402
1402
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nadicodeai/ui",
3
- "version": "12.1.141",
3
+ "version": "12.1.143",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -83,7 +83,7 @@
83
83
  "@base-ui/react": "^1.9.0",
84
84
  "@iconify-icons/logos": "^2.0.3",
85
85
  "@iconify/react": "^6.0.2",
86
- "@nadicodeai/design-system": "12.1.141",
86
+ "@nadicodeai/design-system": "12.1.143",
87
87
  "@tanstack/react-table": "^9.3.1",
88
88
  "@vgpu/wgsl": "0.6.0",
89
89
  "class-variance-authority": "^0.7.1",