@nadicodeai/ui 12.1.130 → 12.1.132
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/dist/components/agent-list-row.d.ts +4 -7
- package/dist/components/agent-list-row.d.ts.map +1 -1
- package/dist/components/agent-list-row.js +5 -6
- package/dist/components/combobox.d.ts +9 -2
- package/dist/components/combobox.d.ts.map +1 -1
- package/dist/components/combobox.js +14 -5
- package/dist/components/confirm-dialog.d.ts +1 -1
- package/dist/components/confirm-dialog.js +1 -1
- package/dist/components/form-dialog.d.ts +1 -1
- package/dist/components/form-dialog.js +1 -1
- package/dist/components/form-drawer.d.ts +1 -1
- package/dist/components/form-drawer.js +1 -1
- package/dist/components/select.d.ts +8 -2
- package/dist/components/select.d.ts.map +1 -1
- package/dist/components/select.js +15 -7
- package/dist/components/waiting-line.d.ts +8 -3
- package/dist/components/waiting-line.d.ts.map +1 -1
- package/dist/components/waiting-line.js +9 -4
- package/dist/eslint/rules/no-shadcn-appearance-override.js +1 -1
- package/dist/showcase/specimens/navigation.d.ts.map +1 -1
- package/dist/showcase/specimens/navigation.js +1 -1
- package/dist/showcase/usage.generated.js +10 -10
- package/dist/styles/components.css +37 -0
- package/package.json +2 -2
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { CSSProperties } from "react";
|
|
2
2
|
import { type AgentOrbFigure, type AgentOrbState } from "./agent-orb.js";
|
|
3
|
-
import { type StatusBadgeTone } from "./status-badge.js";
|
|
4
3
|
/**
|
|
5
4
|
* One Agent in a list of them: the orb, the name, and the line that says what
|
|
6
5
|
* the Agent is doing now or what it is for.
|
|
@@ -8,11 +7,11 @@ import { type StatusBadgeTone } from "./status-badge.js";
|
|
|
8
7
|
* @family Navigation
|
|
9
8
|
* @when Nadia's Agents in her window's sidebar, and a website scene that shows a list of Agents.
|
|
10
9
|
* @instead An Agent as a record among others in a page's list is a RecordList row with an agent IdentityMark; an Agent's own page heads with ObjectPage.
|
|
11
|
-
* @behaviour It is the row's presentation alone: the consumer owns the control around it (Nadia's button that opens the Agent, a scene's list item that opens nothing). The current row's name is set in 500.
|
|
12
|
-
* @words The name, then one state line: what she is doing now ("Answering on Telegram", "Working") or what she is for ("Sorts requests").
|
|
13
|
-
* @access The orb is decorative; the name and the state line are the row's text, read by its control.
|
|
10
|
+
* @behaviour It is the row's presentation alone: the consumer owns the control around it (Nadia's button that opens the Agent, a scene's list item that opens nothing). The current row's name is set in 500. The orb carries the Agent's state, working, waiting on the person or blocked, so the state line is plain muted text and never a badge. Narrow, the orb stands alone and the words stay for a screen reader; the consumer names it with its state line in a tooltip, as Nadia's list does. Only the orb moves in the list. The name is 14 px and the state line 12 px at every size. The drawn orb leaves about 10 px of its 64 px box empty, so at 64 px that empty edge overlaps the row's padding (6 px above and below, 4 px at the start) and the words stand 4 px from the box: the row hugs the visible orb and the name sits beside it, as beside a solid picture. In Nadia's list her orb is 64 px in her real state, and 40 px in the narrow column from 640 to 899 px.
|
|
11
|
+
* @words The name, then one state line: what she is doing now ("Answering on Telegram", "Working") or what she is for ("Sorts requests"). Nadia's list says "Working", "Idle" or "Needs you".
|
|
12
|
+
* @access The orb is decorative; the name and the state line are the row's text, read by its control.
|
|
14
13
|
*/
|
|
15
|
-
export declare function AgentListRow({ className, color, current, form, motionKey, name, narrow, size, state, stateLine,
|
|
14
|
+
export declare function AgentListRow({ className, color, current, form, motionKey, name, narrow, size, state, stateLine, ...props }: Omit<React.ComponentProps<"span">, "color"> & {
|
|
16
15
|
/** The pigment of the Agent's chosen form; Nadia's striped ribbon keeps her own. */
|
|
17
16
|
color?: CSSProperties["color"];
|
|
18
17
|
/** The row the list is on now. */
|
|
@@ -26,7 +25,5 @@ export declare function AgentListRow({ className, color, current, form, motionKe
|
|
|
26
25
|
state?: AgentOrbState;
|
|
27
26
|
/** What the Agent is doing, or what it is for. Absent, the name stands alone. */
|
|
28
27
|
stateLine?: string;
|
|
29
|
-
/** How the state line stands: a failure is `danger`. Absent, the line stays plain text. */
|
|
30
|
-
stateTone?: StatusBadgeTone;
|
|
31
28
|
}): import("react").JSX.Element;
|
|
32
29
|
//# sourceMappingURL=agent-list-row.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-list-row.d.ts","sourceRoot":"","sources":["../../src/components/agent-list-row.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,OAAO,EAAY,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-list-row.d.ts","sourceRoot":"","sources":["../../src/components/agent-list-row.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,OAAO,EAAY,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG3F;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,KAAK,EACL,OAAe,EACf,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,MAAc,EACd,IAAS,EACT,KAAc,EACd,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,GAAG;IAC/C,oFAAoF;IACpF,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAgCA"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { AgentOrb } from "./agent-orb.js";
|
|
4
|
-
import { StatusBadge } from "./status-badge.js";
|
|
5
4
|
import { cn } from "../lib/utils.js";
|
|
6
5
|
/**
|
|
7
6
|
* One Agent in a list of them: the orb, the name, and the line that says what
|
|
@@ -10,10 +9,10 @@ import { cn } from "../lib/utils.js";
|
|
|
10
9
|
* @family Navigation
|
|
11
10
|
* @when Nadia's Agents in her window's sidebar, and a website scene that shows a list of Agents.
|
|
12
11
|
* @instead An Agent as a record among others in a page's list is a RecordList row with an agent IdentityMark; an Agent's own page heads with ObjectPage.
|
|
13
|
-
* @behaviour It is the row's presentation alone: the consumer owns the control around it (Nadia's button that opens the Agent, a scene's list item that opens nothing). The current row's name is set in 500.
|
|
14
|
-
* @words The name, then one state line: what she is doing now ("Answering on Telegram", "Working") or what she is for ("Sorts requests").
|
|
15
|
-
* @access The orb is decorative; the name and the state line are the row's text, read by its control.
|
|
12
|
+
* @behaviour It is the row's presentation alone: the consumer owns the control around it (Nadia's button that opens the Agent, a scene's list item that opens nothing). The current row's name is set in 500. The orb carries the Agent's state, working, waiting on the person or blocked, so the state line is plain muted text and never a badge. Narrow, the orb stands alone and the words stay for a screen reader; the consumer names it with its state line in a tooltip, as Nadia's list does. Only the orb moves in the list. The name is 14 px and the state line 12 px at every size. The drawn orb leaves about 10 px of its 64 px box empty, so at 64 px that empty edge overlaps the row's padding (6 px above and below, 4 px at the start) and the words stand 4 px from the box: the row hugs the visible orb and the name sits beside it, as beside a solid picture. In Nadia's list her orb is 64 px in her real state, and 40 px in the narrow column from 640 to 899 px.
|
|
13
|
+
* @words The name, then one state line: what she is doing now ("Answering on Telegram", "Working") or what she is for ("Sorts requests"). Nadia's list says "Working", "Idle" or "Needs you".
|
|
14
|
+
* @access The orb is decorative; the name and the state line are the row's text, read by its control.
|
|
16
15
|
*/
|
|
17
|
-
export function AgentListRow({ className, color, current = false, form, motionKey, name, narrow = false, size = 40, state = "idle", stateLine,
|
|
18
|
-
return (_jsxs("span", { className: cn("flex w-full min-w-0 items-center gap-3 text-left text-sm", size === 64 && "gap-1", narrow && "justify-center", className), "data-slot": "agent-list-row", "data-current": current || undefined, ...props, children: [_jsx(AgentOrb, { className: cn(size === 64 && "-my-1.5 -ml-1"), color: color, decorative: true, form: form, motionKey: motionKey, size: size, state: state }), _jsxs("span", { className: cn("grid min-w-0", narrow && "sr-only"), children: [_jsx("span", { className: cn("truncate text-foreground", current && "font-medium"), children: name }), stateLine ? (_jsx("span", { className: "flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground", children:
|
|
16
|
+
export function AgentListRow({ className, color, current = false, form, motionKey, name, narrow = false, size = 40, state = "idle", stateLine, ...props }) {
|
|
17
|
+
return (_jsxs("span", { className: cn("flex w-full min-w-0 items-center gap-3 text-left text-sm", size === 64 && "gap-1", narrow && "justify-center", className), "data-slot": "agent-list-row", "data-current": current || undefined, ...props, children: [_jsx(AgentOrb, { className: cn(size === 64 && "-my-1.5 -ml-1"), color: color, decorative: true, form: form, motionKey: motionKey, size: size, state: state }), _jsxs("span", { className: cn("grid min-w-0", narrow && "sr-only"), children: [_jsx("span", { className: cn("truncate text-foreground", current && "font-medium"), children: name }), stateLine ? (_jsx("span", { className: "flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground", children: _jsx("span", { className: "truncate", children: stateLine }) })) : null] })] }));
|
|
19
18
|
}
|
|
@@ -29,7 +29,11 @@ import { type FieldSize } from "../internal/input-box.js";
|
|
|
29
29
|
* the placeholder, marks and list stay neutral. `label` and `error` go on `ComboboxInput`, and a
|
|
30
30
|
* string `label` requires `id`; a box with no name does not compile. The list, a solid floating surface, opens 8 px under it
|
|
31
31
|
* at its width (a narrower `className` width centres under the box), in a 6 px inset, up to five
|
|
32
|
-
* and a half rows tall (210 px) so a cut row shows there is more, with rows 36 px tall and no gap
|
|
32
|
+
* and a half rows tall (210 px) so a cut row shows there is more, with rows 36 px tall and no gap;
|
|
33
|
+
* a `long` list, one people scan by section, shows twelve and a half rows (462 px).
|
|
34
|
+
* A trigger that opens a list with its own search puts `ComboboxInput` first in `ComboboxContent`:
|
|
35
|
+
* there the box draws no ring or halo, spans the list on its surface, and a hairline divides it
|
|
36
|
+
* from the rows, wherever it sits in the content.
|
|
33
37
|
* A row is one line that ends in an ellipsis, with a 16 px mark 12 px before the name and a 14 px
|
|
34
38
|
* check at the chosen row's end, after the name. A row that cannot be chosen turns its text muted,
|
|
35
39
|
* never dimmed. Typing filters the list. "Options can opt out of the fixed row height with `ignoreDefaultHeight`
|
|
@@ -98,7 +102,10 @@ declare function ComboboxContent({ className, side, sideOffset, align, alignOffs
|
|
|
98
102
|
/** What the list says when nothing matches, quoting the query; `null` while a search runs. */
|
|
99
103
|
empty: string | null;
|
|
100
104
|
}): React.JSX.Element;
|
|
101
|
-
declare function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props
|
|
105
|
+
declare function ComboboxList({ className, long, ...props }: ComboboxPrimitive.List.Props & {
|
|
106
|
+
/** A list people scan by section, such as the models of every lab: twelve and a half rows. */
|
|
107
|
+
long?: boolean;
|
|
108
|
+
}): React.JSX.Element;
|
|
102
109
|
declare function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props): React.JSX.Element;
|
|
103
110
|
declare function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props): React.JSX.Element;
|
|
104
111
|
declare function ComboboxLabel({ className, ...props }: ComboboxPrimitive.GroupLabel.Props): React.JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../../src/components/combobox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAIxE,OAAO,EAAU,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AASpF
|
|
1
|
+
{"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../../src/components/combobox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAIxE,OAAO,EAAU,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AASpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,QAAA,MAAM,QAAQ,+BAAyB,CAAC;AAExC,iBAAS,aAAa,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,CAAC,KAAK,CAAC,KAAK,qBAEjE;AAED,iBAAS,eAAe,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,CAAC,OAAO,CAAC,KAAK,qBAW1F;AAkBD,KAAK,qBAAqB,GACtB;IAAE,WAAW,EAAE,KAAK,CAAC;IAAC,YAAY,CAAC,EAAE,SAAS,CAAA;CAAE,GAChD;IAAE,WAAW,CAAC,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,KAAK,mBAAmB,GACpB;IAAE,SAAS,CAAC,EAAE,KAAK,CAAC;IAAC,UAAU,CAAC,EAAE,SAAS,CAAA;CAAE,GAC7C;IAAE,SAAS,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAK5C,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,QAAgB,EAChB,WAAkB,EAClB,YAAY,EACZ,SAAiB,EACjB,UAAU,EACV,IAAI,EACJ,KAAK,EACL,KAAK,EACL,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAAC,GAAG;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wFAAwF;IACxF,WAAW,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,SAAS,GACX,qBAAqB,GACrB,mBAAmB,qBAiDpB;AAED,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,IAAe,EACf,UAAc,EACd,KAAgB,EAChB,WAAe,EACf,MAAM,EACN,KAAK,EACL,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,iBAAiB,CAAC,KAAK,CAAC,KAAK,GAC9B,IAAI,CACF,iBAAiB,CAAC,UAAU,CAAC,KAAK,EAClC,MAAM,GAAG,OAAO,GAAG,YAAY,GAAG,aAAa,GAAG,QAAQ,CAC3D,GAAG;IACF,8FAA8F;IAC9F,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,qBA6BF;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,IAAY,EACZ,GAAG,KAAK,EACT,EAAE,iBAAiB,CAAC,IAAI,CAAC,KAAK,GAAG;IAChC,8FAA8F;IAC9F,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,qBAcA;AAED,iBAAS,YAAY,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,CAAC,IAAI,CAAC,KAAK,qBAwBpF;AAED,iBAAS,aAAa,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,CAAC,KAAK,CAAC,KAAK,qBAI5E;AAED,iBAAS,aAAa,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,CAAC,UAAU,CAAC,KAAK,qBAQjF;AAED,iBAAS,kBAAkB,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,CAAC,UAAU,CAAC,KAAK,qBAE3E;AAeD,iBAAS,eAAe,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,CAAC,MAAM,CAAC,KAAK,qBAQ/E;AAED,iBAAS,iBAAiB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,CAAC,SAAS,CAAC,KAAK,qBAQpF;AAED,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,qBAAqB,CAAC,OAAO,iBAAiB,CAAC,KAAK,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,qBAa7F;AAED,KAAK,kBAAkB,GACnB;IAAE,UAAU,EAAE,KAAK,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,CAAA;CAAE,GAC9C;IAAE,UAAU,CAAC,EAAE,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,QAAQ,EACR,UAAiB,EACjB,WAAW,EACX,GAAG,KAAK,EACT,EAAE,iBAAiB,CAAC,IAAI,CAAC,KAAK,GAAG,kBAAkB,qBAsBnD;AAED,iBAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,iBAAiB,CAAC,KAAK,CAAC,KAAK,GAAG,cAAc,qBAShD;AAED,iBAAS,iBAAiB,2CAEzB;AAED,OAAO,EACL,QAAQ,EACR,aAAa,EACb,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,iBAAiB,GAClB,CAAC"}
|
|
@@ -34,7 +34,11 @@ import { ChevronDownIcon, XIcon, CheckIcon, SearchIcon } from "lucide-react";
|
|
|
34
34
|
* the placeholder, marks and list stay neutral. `label` and `error` go on `ComboboxInput`, and a
|
|
35
35
|
* string `label` requires `id`; a box with no name does not compile. The list, a solid floating surface, opens 8 px under it
|
|
36
36
|
* at its width (a narrower `className` width centres under the box), in a 6 px inset, up to five
|
|
37
|
-
* and a half rows tall (210 px) so a cut row shows there is more, with rows 36 px tall and no gap
|
|
37
|
+
* and a half rows tall (210 px) so a cut row shows there is more, with rows 36 px tall and no gap;
|
|
38
|
+
* a `long` list, one people scan by section, shows twelve and a half rows (462 px).
|
|
39
|
+
* A trigger that opens a list with its own search puts `ComboboxInput` first in `ComboboxContent`:
|
|
40
|
+
* there the box draws no ring or halo, spans the list on its surface, and a hairline divides it
|
|
41
|
+
* from the rows, wherever it sits in the content.
|
|
38
42
|
* A row is one line that ends in an ellipsis, with a 16 px mark 12 px before the name and a 14 px
|
|
39
43
|
* check at the chosen row's end, after the name. A row that cannot be chosen turns its text muted,
|
|
40
44
|
* never dimmed. Typing filters the list. "Options can opt out of the fixed row height with `ignoreDefaultHeight`
|
|
@@ -84,15 +88,20 @@ function ComboboxTrigger({ className, children, ...props }) {
|
|
|
84
88
|
function ComboboxClear({ className, ...props }) {
|
|
85
89
|
return (_jsx(ComboboxPrimitive.Clear, { "data-slot": "combobox-clear", render: _jsx(InputGroupButton, { variant: "ghost", size: "icon", "aria-label": props["aria-label"] }), className: cn(className), ...props, children: _jsx(XIcon, { className: "pointer-events-none size-3.5" }) }));
|
|
86
90
|
}
|
|
91
|
+
/** True inside `ComboboxContent`, where `ComboboxInput` is the list's own search. */
|
|
92
|
+
const InComboboxList = React.createContext(false);
|
|
87
93
|
function ComboboxInput({ className, children, disabled = false, showTrigger = true, triggerLabel, showClear = false, clearLabel, size, label, error, ...props }) {
|
|
88
94
|
const hasButton = showTrigger || showClear;
|
|
89
|
-
|
|
95
|
+
const inList = React.useContext(InComboboxList);
|
|
96
|
+
return (_jsx(FieldFrame, { label: label, error: error, size: size, control: props, children: (aria) => (_jsxs(InputGroup, { size: size, className: cn(inList && "rounded-none border-b border-border bg-transparent shadow-none", className), children: [_jsx(InputGroupAddon, { children: _jsx(SearchIcon, { "aria-hidden": "true" }) }), _jsx(ComboboxPrimitive.Input, { render: _jsx(InputGroupControl, { disabled: disabled, className: cn("aria-invalid:text-destructive", hasButton && "pr-0") }), ...props, ...aria }), hasButton ? (_jsxs(InputGroupAddon, { align: "inline-end", children: [showTrigger && triggerLabel !== undefined ? (_jsx(InputGroupButton, { size: "icon", variant: "ghost", "aria-label": triggerLabel, render: _jsx(ComboboxTrigger, {}), "data-slot": "input-group-button", className: "group-data-[size=default]/input-group:h-7.5 group-data-[size=default]/input-group:self-center group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent", disabled: disabled })) : null, showClear && clearLabel !== undefined ? (_jsx(ComboboxClear, { "aria-label": clearLabel, disabled: disabled })) : null] })) : null, children] })) }));
|
|
90
97
|
}
|
|
91
98
|
function ComboboxContent({ className, side = "bottom", sideOffset = 8, align = "center", alignOffset = 0, anchor, empty, children, ...props }) {
|
|
92
|
-
return (_jsx(ComboboxPrimitive.Portal, { children: _jsx(ComboboxPrimitive.Positioner, { side: side, sideOffset: sideOffset, align: align, alignOffset: alignOffset, anchor: anchor, className: "isolate z-50", children:
|
|
99
|
+
return (_jsx(ComboboxPrimitive.Portal, { children: _jsx(ComboboxPrimitive.Positioner, { side: side, sideOffset: sideOffset, align: align, alignOffset: alignOffset, anchor: anchor, className: "isolate z-50", children: _jsx(ComboboxPrimitive.Popup, { "data-slot": "combobox-content", className: cn("pointer-events-auto", "group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) overflow-hidden rounded-lg border-0 bg-popover text-popover-foreground nc-elevation-floating", anchoredOverlayMotion, className), ...props, children: _jsxs(InComboboxList.Provider, { value: true, children: [empty === null ? null : _jsx(ComboboxEmpty, { children: empty }), children] }) }) }) }));
|
|
93
100
|
}
|
|
94
|
-
function ComboboxList({ className, ...props }) {
|
|
95
|
-
return (_jsx(ComboboxPrimitive.List, { "data-slot": "combobox-list", className: cn("no-scrollbar
|
|
101
|
+
function ComboboxList({ className, long = false, ...props }) {
|
|
102
|
+
return (_jsx(ComboboxPrimitive.List, { "data-slot": "combobox-list", className: cn("no-scrollbar scroll-py-1 overflow-y-auto overscroll-contain p-1.5 data-empty:p-0", long
|
|
103
|
+
? "max-h-[min(--spacing(115.5),calc(var(--available-height)---spacing(9)))]"
|
|
104
|
+
: "max-h-[min(--spacing(52.5),calc(var(--available-height)---spacing(9)))]", className), ...props }));
|
|
96
105
|
}
|
|
97
106
|
function ComboboxItem({ className, children, ...props }) {
|
|
98
107
|
return (_jsxs(ComboboxPrimitive.Item, { "data-slot": "combobox-item", className: cn("relative flex min-h-touch-target w-full cursor-default items-center gap-3 rounded-sm px-2 py-2 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:text-muted-foreground [@media(hover:hover)_and_(pointer:fine)]:min-h-0 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: [React.Children.map(children, (child) => React.isValidElement(child) ? (child) : (_jsx("span", { className: "min-w-0 truncate text-left", children: child }))), _jsx(ComboboxPrimitive.ItemIndicator, { render: _jsx("span", { className: "pointer-events-none ml-auto flex shrink-0 items-center" }), children: _jsx(CheckIcon, { className: "pointer-events-none size-3.5" }) })] }));
|
|
@@ -44,7 +44,7 @@ export type ConfirmDialogProps = {
|
|
|
44
44
|
* @family Compositions
|
|
45
45
|
* @when "Reach for this over `Modal` when the action is destructive and warrants friction: delete, rotate, revoke, disconnect, downgrade, disable a security setting. The typed gate forces deliberate intent." (https://vercel.com/geist/destructive-action-modal) "Use it for reversible destructive actions too, when the consequence is serious enough to warrant a pause — disabling deployment protection, revoking a shared token. Keep the typed gate; drop `irreversibleDescription`." (https://vercel.com/geist/destructive-action-modal) "For high-stakes destructive actions (delete production resource, rotate signing key, downgrade plan), gate the primary button on a typed match of the resource name." (https://vercel.com/geist/modal) Ours: `ConfirmDialog` is Geist's DestructiveActionModal: remove a mailbox, delete an Agent, with `verify` as the typed gate. It asks for the record's name before the confirm enables on a high-stakes action (delete, rotate, revoke, disconnect, downgrade, a protection switched off), even a reversible one; `irreversible` names what cannot be undone and is left out when the action can be reversed.
|
|
46
46
|
* @instead "Don't use for routine confirmations (save draft, discard changes, close without saving). The typed gate reads as melodrama when the stakes are low — use a plain `Modal` instead." (https://vercel.com/geist/destructive-action-modal) "Confirm destructive actions in a Modal. `Drawer` and `Sheet` don’t fully dim the page, so they read as too soft for a delete or revoke." (https://vercel.com/geist/modal) Ours: a routine confirmation (discard a draft) takes neither `verify` nor `irreversible`; an action that can be undone runs at once and offers its undo in a toast; a form of a few fields is a `FormDialog`; a choice from a catalogue is a `CatalogDrawer`.
|
|
47
|
-
* @behaviour "The verification input gets focus on open so the user can start typing immediately. Submit stays disabled until the value matches `verificationPhrase` exactly." (https://vercel.com/geist/destructive-action-modal) "Enter submits only when the gate is open; it's a no-op otherwise. Cancel, outside-click, and Escape all dismiss." (https://vercel.com/geist/destructive-action-modal) "`loading` disables both buttons. The caller owns `open` — do not self-dismiss from `onConfirm`. Close the modal after the API settles (success or error), or keep it open on error so the user can retry." (https://vercel.com/geist/destructive-action-modal) "Default focus to `Cancel` on any destructive Modal. Enter must never trigger the destructive action without a typed confirmation." (https://vercel.com/geist/modal) "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: `title`, `description`, `confirmLabel` and `cancelLabel` are required; `irreversible` and `verify` are optional, and the absence of `irreversible` is what marks the action reversible. Geist's `irreversibleDescription` is `irreversible`, `verificationPhrase` and `verificationLabel` are `verify.phrase` and `verify.prompt`, and `error` is the catalogue's message. 480 px wide over a scrim. The title and the description, then the body in a 24 px column: the `irreversible` note, the `verify` field, the failure under it. `irreversible` is a flat red note with a red mark and red words, not a band over the footer; besides it the only red is the failure and the enabled destructive confirm. While `loading` the field turns read-only and keeps focus, Escape does not close it, and the confirm says `confirmPendingLabel`, which falls back to the confirm's label. A press outside does not close it, so a stray click never discards a typed phrase or an action in flight; Escape and Cancel do. The footer is a bar that stays at the bottom while a tall confirmation scrolls, and a row on a phone too.
|
|
47
|
+
* @behaviour "The verification input gets focus on open so the user can start typing immediately. Submit stays disabled until the value matches `verificationPhrase` exactly." (https://vercel.com/geist/destructive-action-modal) "Enter submits only when the gate is open; it's a no-op otherwise. Cancel, outside-click, and Escape all dismiss." (https://vercel.com/geist/destructive-action-modal) "`loading` disables both buttons. The caller owns `open` — do not self-dismiss from `onConfirm`. Close the modal after the API settles (success or error), or keep it open on error so the user can retry." (https://vercel.com/geist/destructive-action-modal) "Default focus to `Cancel` on any destructive Modal. Enter must never trigger the destructive action without a typed confirmation." (https://vercel.com/geist/modal) "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: `title`, `description`, `confirmLabel` and `cancelLabel` are required; `irreversible` and `verify` are optional, and the absence of `irreversible` is what marks the action reversible. Geist's `irreversibleDescription` is `irreversible`, `verificationPhrase` and `verificationLabel` are `verify.phrase` and `verify.prompt`, and `error` is the catalogue's message. 480 px wide over a scrim. The title and the description, then the body in a 24 px column: the `irreversible` note, the `verify` field, the failure under it. `irreversible` is a flat red note with a red mark and red words, not a band over the footer; besides it the only red is the failure and the enabled destructive confirm. While `loading` the field turns read-only and keeps focus, Escape does not close it, and the confirm says `confirmPendingLabel`, which falls back to the confirm's label. A press outside does not close it, so a stray click never discards a typed phrase or an action in flight; Escape and Cancel do. The footer is a bar that stays at the bottom while a tall confirmation scrolls, and a row on a phone too. How long it stays open while work goes on follows DESIGN.md "System activity".
|
|
48
48
|
* @words "`title` is Title Case, `Verb + Noun`, a statement — never a question. `Delete Project`, not `Delete this project?`." (https://vercel.com/geist/destructive-action-modal) "`description` is sentence case, names the consequence, and interpolates the specific resource when relevant. `<b>my-project</b> and all its deployments will be permanently deleted.` reads stronger than `This project and all its deployments will be deleted.`." (https://vercel.com/geist/destructive-action-modal) "`confirmLabel` matches the title 1:1. Never generic (`Confirm`, `OK`, `Continue`), never a bare verb (`Delete`)." (https://vercel.com/geist/destructive-action-modal) "`cancelLabel` defaults to `Cancel`. Override when Cancel is ambiguous, such as `Keep Turn Running` when the primary action is itself a cancellation." (https://vercel.com/geist/destructive-action-modal) "`verificationPhrase`: for entity deletes, type the **resource name itself** (`my-project`) and pair with `verificationLabel="project name"` so the prompt reads `To confirm, type the project name "my-project"`. That's the canonical signal that the user knows which thing they're acting on. Fall back to a lowercase verb phrase (`disable vercel authentication`) only when there's no entity to name." (https://vercel.com/geist/destructive-action-modal) "`irreversibleDescription` names the specific action and resource and ends with `cannot be undone.` — `Deleting my-project cannot be undone.` rather than the generic `This cannot be undone.`. Omit the prop entirely for reversible actions; the prop's absence is the signal, not a falsy value." (https://vercel.com/geist/destructive-action-modal) "`error` surfaces the API failure verbatim in Vercel voice: `Couldn't save settings. Try again.`. Never dump a raw error object." (https://vercel.com/geist/destructive-action-modal) "Pair the post-confirm success toast verb 1:1 with the primary button: `Delete Project` button, `Project deleted` toast. Never `Project removed`." (https://vercel.com/geist/destructive-action-modal) "Close irreversible bodies with `This cannot be undone.`; close cascade-only bodies with `Some effects cannot be undone.`. Don’t claim full irreversibility for a partial cascade." (https://vercel.com/geist/modal) Ours: the title is sentence case and names the record, "Remove pec@acme.it", and the confirm repeats the verb and the noun, "Remove mailbox", so the two share a verb and not every word; its success toast uses the same verb ("Mailbox removed"). The description names the record in medium weight.
|
|
49
49
|
* @access "The verification input is associated with its prompt via `aria-labelledby` + `htmlFor`. Screen readers announce the full prompt (`To confirm, type "delete my-project"`) on focus." (https://vercel.com/geist/destructive-action-modal) "The Warning icon in the irreversibility band is `aria-hidden`; the sentence carries the meaning so nothing is announced twice." (https://vercel.com/geist/destructive-action-modal) "Focus stays inside the modal across an `error` transition so the user can retry without losing context. After success, return focus to the trigger." (https://vercel.com/geist/destructive-action-modal) "Set `aria-labelledby` to the `Modal.Title` id so screen readers announce the title on open." (https://vercel.com/geist/modal) "Keep the Cancel button literally `Cancel` so screen-reader users hear a stable dismissal label across destructive flows." (https://vercel.com/geist/modal) Ours: `role="alertdialog"` named by its title and described by its consequence; the note is `role="note"` with its icon hidden. Focus starts on Cancel, or in the field with `verify`.
|
|
50
50
|
*/
|
|
@@ -14,7 +14,7 @@ import { bodyBeyondTitle } from "./product-message.js";
|
|
|
14
14
|
* @family Compositions
|
|
15
15
|
* @when "Reach for this over `Modal` when the action is destructive and warrants friction: delete, rotate, revoke, disconnect, downgrade, disable a security setting. The typed gate forces deliberate intent." (https://vercel.com/geist/destructive-action-modal) "Use it for reversible destructive actions too, when the consequence is serious enough to warrant a pause — disabling deployment protection, revoking a shared token. Keep the typed gate; drop `irreversibleDescription`." (https://vercel.com/geist/destructive-action-modal) "For high-stakes destructive actions (delete production resource, rotate signing key, downgrade plan), gate the primary button on a typed match of the resource name." (https://vercel.com/geist/modal) Ours: `ConfirmDialog` is Geist's DestructiveActionModal: remove a mailbox, delete an Agent, with `verify` as the typed gate. It asks for the record's name before the confirm enables on a high-stakes action (delete, rotate, revoke, disconnect, downgrade, a protection switched off), even a reversible one; `irreversible` names what cannot be undone and is left out when the action can be reversed.
|
|
16
16
|
* @instead "Don't use for routine confirmations (save draft, discard changes, close without saving). The typed gate reads as melodrama when the stakes are low — use a plain `Modal` instead." (https://vercel.com/geist/destructive-action-modal) "Confirm destructive actions in a Modal. `Drawer` and `Sheet` don’t fully dim the page, so they read as too soft for a delete or revoke." (https://vercel.com/geist/modal) Ours: a routine confirmation (discard a draft) takes neither `verify` nor `irreversible`; an action that can be undone runs at once and offers its undo in a toast; a form of a few fields is a `FormDialog`; a choice from a catalogue is a `CatalogDrawer`.
|
|
17
|
-
* @behaviour "The verification input gets focus on open so the user can start typing immediately. Submit stays disabled until the value matches `verificationPhrase` exactly." (https://vercel.com/geist/destructive-action-modal) "Enter submits only when the gate is open; it's a no-op otherwise. Cancel, outside-click, and Escape all dismiss." (https://vercel.com/geist/destructive-action-modal) "`loading` disables both buttons. The caller owns `open` — do not self-dismiss from `onConfirm`. Close the modal after the API settles (success or error), or keep it open on error so the user can retry." (https://vercel.com/geist/destructive-action-modal) "Default focus to `Cancel` on any destructive Modal. Enter must never trigger the destructive action without a typed confirmation." (https://vercel.com/geist/modal) "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: `title`, `description`, `confirmLabel` and `cancelLabel` are required; `irreversible` and `verify` are optional, and the absence of `irreversible` is what marks the action reversible. Geist's `irreversibleDescription` is `irreversible`, `verificationPhrase` and `verificationLabel` are `verify.phrase` and `verify.prompt`, and `error` is the catalogue's message. 480 px wide over a scrim. The title and the description, then the body in a 24 px column: the `irreversible` note, the `verify` field, the failure under it. `irreversible` is a flat red note with a red mark and red words, not a band over the footer; besides it the only red is the failure and the enabled destructive confirm. While `loading` the field turns read-only and keeps focus, Escape does not close it, and the confirm says `confirmPendingLabel`, which falls back to the confirm's label. A press outside does not close it, so a stray click never discards a typed phrase or an action in flight; Escape and Cancel do. The footer is a bar that stays at the bottom while a tall confirmation scrolls, and a row on a phone too.
|
|
17
|
+
* @behaviour "The verification input gets focus on open so the user can start typing immediately. Submit stays disabled until the value matches `verificationPhrase` exactly." (https://vercel.com/geist/destructive-action-modal) "Enter submits only when the gate is open; it's a no-op otherwise. Cancel, outside-click, and Escape all dismiss." (https://vercel.com/geist/destructive-action-modal) "`loading` disables both buttons. The caller owns `open` — do not self-dismiss from `onConfirm`. Close the modal after the API settles (success or error), or keep it open on error so the user can retry." (https://vercel.com/geist/destructive-action-modal) "Default focus to `Cancel` on any destructive Modal. Enter must never trigger the destructive action without a typed confirmation." (https://vercel.com/geist/modal) "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: `title`, `description`, `confirmLabel` and `cancelLabel` are required; `irreversible` and `verify` are optional, and the absence of `irreversible` is what marks the action reversible. Geist's `irreversibleDescription` is `irreversible`, `verificationPhrase` and `verificationLabel` are `verify.phrase` and `verify.prompt`, and `error` is the catalogue's message. 480 px wide over a scrim. The title and the description, then the body in a 24 px column: the `irreversible` note, the `verify` field, the failure under it. `irreversible` is a flat red note with a red mark and red words, not a band over the footer; besides it the only red is the failure and the enabled destructive confirm. While `loading` the field turns read-only and keeps focus, Escape does not close it, and the confirm says `confirmPendingLabel`, which falls back to the confirm's label. A press outside does not close it, so a stray click never discards a typed phrase or an action in flight; Escape and Cancel do. The footer is a bar that stays at the bottom while a tall confirmation scrolls, and a row on a phone too. How long it stays open while work goes on follows DESIGN.md "System activity".
|
|
18
18
|
* @words "`title` is Title Case, `Verb + Noun`, a statement — never a question. `Delete Project`, not `Delete this project?`." (https://vercel.com/geist/destructive-action-modal) "`description` is sentence case, names the consequence, and interpolates the specific resource when relevant. `<b>my-project</b> and all its deployments will be permanently deleted.` reads stronger than `This project and all its deployments will be deleted.`." (https://vercel.com/geist/destructive-action-modal) "`confirmLabel` matches the title 1:1. Never generic (`Confirm`, `OK`, `Continue`), never a bare verb (`Delete`)." (https://vercel.com/geist/destructive-action-modal) "`cancelLabel` defaults to `Cancel`. Override when Cancel is ambiguous, such as `Keep Turn Running` when the primary action is itself a cancellation." (https://vercel.com/geist/destructive-action-modal) "`verificationPhrase`: for entity deletes, type the **resource name itself** (`my-project`) and pair with `verificationLabel="project name"` so the prompt reads `To confirm, type the project name "my-project"`. That's the canonical signal that the user knows which thing they're acting on. Fall back to a lowercase verb phrase (`disable vercel authentication`) only when there's no entity to name." (https://vercel.com/geist/destructive-action-modal) "`irreversibleDescription` names the specific action and resource and ends with `cannot be undone.` — `Deleting my-project cannot be undone.` rather than the generic `This cannot be undone.`. Omit the prop entirely for reversible actions; the prop's absence is the signal, not a falsy value." (https://vercel.com/geist/destructive-action-modal) "`error` surfaces the API failure verbatim in Vercel voice: `Couldn't save settings. Try again.`. Never dump a raw error object." (https://vercel.com/geist/destructive-action-modal) "Pair the post-confirm success toast verb 1:1 with the primary button: `Delete Project` button, `Project deleted` toast. Never `Project removed`." (https://vercel.com/geist/destructive-action-modal) "Close irreversible bodies with `This cannot be undone.`; close cascade-only bodies with `Some effects cannot be undone.`. Don’t claim full irreversibility for a partial cascade." (https://vercel.com/geist/modal) Ours: the title is sentence case and names the record, "Remove pec@acme.it", and the confirm repeats the verb and the noun, "Remove mailbox", so the two share a verb and not every word; its success toast uses the same verb ("Mailbox removed"). The description names the record in medium weight.
|
|
19
19
|
* @access "The verification input is associated with its prompt via `aria-labelledby` + `htmlFor`. Screen readers announce the full prompt (`To confirm, type "delete my-project"`) on focus." (https://vercel.com/geist/destructive-action-modal) "The Warning icon in the irreversibility band is `aria-hidden`; the sentence carries the meaning so nothing is announced twice." (https://vercel.com/geist/destructive-action-modal) "Focus stays inside the modal across an `error` transition so the user can retry without losing context. After success, return focus to the trigger." (https://vercel.com/geist/destructive-action-modal) "Set `aria-labelledby` to the `Modal.Title` id so screen readers announce the title on open." (https://vercel.com/geist/modal) "Keep the Cancel button literally `Cancel` so screen-reader users hear a stable dismissal label across destructive flows." (https://vercel.com/geist/modal) Ours: `role="alertdialog"` named by its title and described by its consequence; the note is `role="note"` with its icon hidden. Focus starts on Cancel, or in the field with `verify`.
|
|
20
20
|
*/
|
|
@@ -10,7 +10,7 @@ export type FormDialogProps = FormSurfaceProps & {
|
|
|
10
10
|
* @family Compositions
|
|
11
11
|
* @when "Use Modal when a decision must block the rest of the page. For persistent associated context where the underlying page stays readable, use `Sheet` on desktop or `Drawer` on mobile." (https://vercel.com/geist/modal) Ours: a person adds a record through a few fields, or changes one or two values a command asks for: new customer, add a mailbox, add an MCP server.
|
|
12
12
|
* @instead "Skip Modal for routine create flows that have a dedicated page; route to the page instead." (https://vercel.com/geist/modal) "Confirm destructive actions in a Modal. `Drawer` and `Sheet` don’t fully dim the page, so they read as too soft for a delete or revoke." (https://vercel.com/geist/modal) Ours: a record already in a list edits in its row with `RowEditForm`; a choice from a catalogue is a `CatalogDrawer`; a setup in steps with a wait is a `FormDrawer` with `job="steps"`; a consequence is confirmed in a `ConfirmDialog`.
|
|
13
|
-
* @behaviour "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: centred over a scrim without blur, 540 px wide, a bottom sheet on a phone: the title and its help, the fields in a 12 px column, then the footer, which stays at the bottom while a tall form scrolls, with Cancel at the leading edge and the primary at the trailing edge, in one row also on a phone. `FormDialogForm` puts the fields in the dialog's own scroll body and the actions in its fixed footer; no other overlay is nested inside. A refusal or a failed save sits above the footer and the fields keep what was typed. While its save runs it holds open, Cancel and the primary hold, and the waiting line runs under them. With `unsaved.dirty`, Escape and a press outside are held back and `unsaved.onDismissBlocked` runs so the caller can ask; Close and Cancel always close.
|
|
13
|
+
* @behaviour "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: centred over a scrim without blur, 540 px wide, a bottom sheet on a phone: the title and its help, the fields in a 12 px column, then the footer, which stays at the bottom while a tall form scrolls, with Cancel at the leading edge and the primary at the trailing edge, in one row also on a phone. `FormDialogForm` puts the fields in the dialog's own scroll body and the actions in its fixed footer; no other overlay is nested inside. A refusal or a failed save sits above the footer and the fields keep what was typed. While its save runs it holds open, Cancel and the primary hold, and the waiting line runs under them. With `unsaved.dirty`, Escape and a press outside are held back and `unsaved.onDismissBlocked` runs so the caller can ask; Close and Cancel always close. How long it stays open while work goes on follows DESIGN.md "System activity".
|
|
14
14
|
* @words "`Modal.Title` is a Title Case statement, never a question. `Transfer Project` is correct; `Transfer Project?` is wrong." (https://vercel.com/geist/modal) "Primary button is `Verb + Noun` and matches the title verb (`Transfer Project` title pairs with `Transfer Project` button). Never `Confirm`, `OK`, or a bare verb on a destructive primary." (https://vercel.com/geist/modal) "Cancel literal stays `Cancel`. Acknowledgment-only Modals (after a key reveal, after a one-time-show) use `Done`, never `OK` or `Close`." (https://vercel.com/geist/modal) Ours: the title and the primary are sentence case, where Geist writes Title Case: the title names the job and its object, "New customer", "Add a mailbox"; the primary repeats it, "Create customer", "Add mailbox".
|
|
15
15
|
* @access "Set `aria-labelledby` to the `Modal.Title` id so screen readers announce the title on open." (https://vercel.com/geist/modal) "Trap focus inside the Modal while it’s open and return focus to the trigger after close. Restore body scroll on the same tick the Modal unmounts." (https://vercel.com/geist/modal) "After an error inside the Modal, keep focus inside so the user can retry; after success, return focus to the trigger." (https://vercel.com/geist/modal) Ours: a modal dialog with a Close named by `closeLabel`; focus starts in its first field or `initialFocus` and returns to the opener or `finalFocus`. Escape and Close close it unless the save runs.
|
|
16
16
|
*/
|
|
@@ -11,7 +11,7 @@ import { guardUnsaved } from "../internal/unsaved-guard.js";
|
|
|
11
11
|
* @family Compositions
|
|
12
12
|
* @when "Use Modal when a decision must block the rest of the page. For persistent associated context where the underlying page stays readable, use `Sheet` on desktop or `Drawer` on mobile." (https://vercel.com/geist/modal) Ours: a person adds a record through a few fields, or changes one or two values a command asks for: new customer, add a mailbox, add an MCP server.
|
|
13
13
|
* @instead "Skip Modal for routine create flows that have a dedicated page; route to the page instead." (https://vercel.com/geist/modal) "Confirm destructive actions in a Modal. `Drawer` and `Sheet` don’t fully dim the page, so they read as too soft for a delete or revoke." (https://vercel.com/geist/modal) Ours: a record already in a list edits in its row with `RowEditForm`; a choice from a catalogue is a `CatalogDrawer`; a setup in steps with a wait is a `FormDrawer` with `job="steps"`; a consequence is confirmed in a `ConfirmDialog`.
|
|
14
|
-
* @behaviour "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: centred over a scrim without blur, 540 px wide, a bottom sheet on a phone: the title and its help, the fields in a 12 px column, then the footer, which stays at the bottom while a tall form scrolls, with Cancel at the leading edge and the primary at the trailing edge, in one row also on a phone. `FormDialogForm` puts the fields in the dialog's own scroll body and the actions in its fixed footer; no other overlay is nested inside. A refusal or a failed save sits above the footer and the fields keep what was typed. While its save runs it holds open, Cancel and the primary hold, and the waiting line runs under them. With `unsaved.dirty`, Escape and a press outside are held back and `unsaved.onDismissBlocked` runs so the caller can ask; Close and Cancel always close.
|
|
14
|
+
* @behaviour "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: centred over a scrim without blur, 540 px wide, a bottom sheet on a phone: the title and its help, the fields in a 12 px column, then the footer, which stays at the bottom while a tall form scrolls, with Cancel at the leading edge and the primary at the trailing edge, in one row also on a phone. `FormDialogForm` puts the fields in the dialog's own scroll body and the actions in its fixed footer; no other overlay is nested inside. A refusal or a failed save sits above the footer and the fields keep what was typed. While its save runs it holds open, Cancel and the primary hold, and the waiting line runs under them. With `unsaved.dirty`, Escape and a press outside are held back and `unsaved.onDismissBlocked` runs so the caller can ask; Close and Cancel always close. How long it stays open while work goes on follows DESIGN.md "System activity".
|
|
15
15
|
* @words "`Modal.Title` is a Title Case statement, never a question. `Transfer Project` is correct; `Transfer Project?` is wrong." (https://vercel.com/geist/modal) "Primary button is `Verb + Noun` and matches the title verb (`Transfer Project` title pairs with `Transfer Project` button). Never `Confirm`, `OK`, or a bare verb on a destructive primary." (https://vercel.com/geist/modal) "Cancel literal stays `Cancel`. Acknowledgment-only Modals (after a key reveal, after a one-time-show) use `Done`, never `OK` or `Close`." (https://vercel.com/geist/modal) Ours: the title and the primary are sentence case, where Geist writes Title Case: the title names the job and its object, "New customer", "Add a mailbox"; the primary repeats it, "Create customer", "Add mailbox".
|
|
16
16
|
* @access "Set `aria-labelledby` to the `Modal.Title` id so screen readers announce the title on open." (https://vercel.com/geist/modal) "Trap focus inside the Modal while it’s open and return focus to the trigger after close. Restore body scroll on the same tick the Modal unmounts." (https://vercel.com/geist/modal) "After an error inside the Modal, keep focus inside so the user can retry; after success, return focus to the trigger." (https://vercel.com/geist/modal) Ours: a modal dialog with a Close named by `closeLabel`; focus starts in its first field or `initialFocus` and returns to the opener or `finalFocus`. Escape and Close close it unless the save runs.
|
|
17
17
|
*/
|
|
@@ -42,7 +42,7 @@ export type FormDrawerProps = FormSurfaceProps & ({
|
|
|
42
42
|
* @family Compositions
|
|
43
43
|
* @when Ours: choosing from a catalogue too long for a dialog (through `CatalogDrawer`), or setting something up in several steps with a wait: connect a messaging channel, Telegram's code.
|
|
44
44
|
* @instead "Don’t use Sheet to confirm destructive actions. The default `modal=false` keeps the page interactive, which weakens severity for a delete or revoke." (https://vercel.com/geist/sheet) Ours: a new record from a few fields is a `FormDialog`; a record already in a list edits in its row with `RowEditForm`; a fact on the record's own page changes in place in its section; a consequence is confirmed in a `ConfirmDialog`; a phone's short job is a `Drawer`.
|
|
45
|
-
* @behaviour "Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal `Sheet` from the right edge over the scrim, because the drawer owns the screen, at the Sheet's one width that no caller sets. The header holds the step ("Step 2 of 3", in mono) over the title, which stays the job's name, then the description and Close; the body scrolls between the header and a footer that stays, with no fill or rule of its own, Cancel then the primary at its trailing edge. A press outside does not close it; Escape does, unless `unsaved.dirty` holds it back. Cancel holds while a save runs.
|
|
45
|
+
* @behaviour "Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal `Sheet` from the right edge over the scrim, because the drawer owns the screen, at the Sheet's one width that no caller sets. The header holds the step ("Step 2 of 3", in mono) over the title, which stays the job's name, then the description and Close; the body scrolls between the header and a footer that stays, with no fill or rule of its own, Cancel then the primary at its trailing edge. A press outside does not close it; Escape does, unless `unsaved.dirty` holds it back. Cancel holds while a save runs. How long it stays open while work goes on follows DESIGN.md "System activity".
|
|
46
46
|
* @words "Title is Title Case and names the entity (`Deployment Details`, `Member Profile`), not the page action." (https://vercel.com/geist/sheet) "Body is read-mostly: sentence case prose with Title Case sub-headings. Action buttons are optional; when present, follow `Verb + Noun`." (https://vercel.com/geist/sheet) Ours: the title is sentence case and names the job and its object: "Connect Telegram", "Add a skill". The step reads "Step N of M". The primary says what it does: "Connect", "Continue", never "OK".
|
|
47
47
|
* @access "Trap focus inside the sheet while it’s open and return focus to the trigger row on close so keyboard users keep their place in the list." (https://vercel.com/geist/sheet) "Provide a visible close button labeled `Close` (or an icon button with `aria-label="Close"`) since clicking outside doesn’t dismiss." (https://vercel.com/geist/sheet) "Announce the sheet with `aria-labelledby` pointing at the title; pair it with `aria-describedby` only when the body is short and load-bearing." (https://vercel.com/geist/sheet) Ours: a modal dialog named by its title; focus returns to the opener on close, or to `finalFocus` when the opener is gone. The step is read before the title.
|
|
48
48
|
*/
|
|
@@ -12,7 +12,7 @@ import { cn } from "../lib/utils.js";
|
|
|
12
12
|
* @family Compositions
|
|
13
13
|
* @when Ours: choosing from a catalogue too long for a dialog (through `CatalogDrawer`), or setting something up in several steps with a wait: connect a messaging channel, Telegram's code.
|
|
14
14
|
* @instead "Don’t use Sheet to confirm destructive actions. The default `modal=false` keeps the page interactive, which weakens severity for a delete or revoke." (https://vercel.com/geist/sheet) Ours: a new record from a few fields is a `FormDialog`; a record already in a list edits in its row with `RowEditForm`; a fact on the record's own page changes in place in its section; a consequence is confirmed in a `ConfirmDialog`; a phone's short job is a `Drawer`.
|
|
15
|
-
* @behaviour "Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal `Sheet` from the right edge over the scrim, because the drawer owns the screen, at the Sheet's one width that no caller sets. The header holds the step ("Step 2 of 3", in mono) over the title, which stays the job's name, then the description and Close; the body scrolls between the header and a footer that stays, with no fill or rule of its own, Cancel then the primary at its trailing edge. A press outside does not close it; Escape does, unless `unsaved.dirty` holds it back. Cancel holds while a save runs.
|
|
15
|
+
* @behaviour "Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal `Sheet` from the right edge over the scrim, because the drawer owns the screen, at the Sheet's one width that no caller sets. The header holds the step ("Step 2 of 3", in mono) over the title, which stays the job's name, then the description and Close; the body scrolls between the header and a footer that stays, with no fill or rule of its own, Cancel then the primary at its trailing edge. A press outside does not close it; Escape does, unless `unsaved.dirty` holds it back. Cancel holds while a save runs. How long it stays open while work goes on follows DESIGN.md "System activity".
|
|
16
16
|
* @words "Title is Title Case and names the entity (`Deployment Details`, `Member Profile`), not the page action." (https://vercel.com/geist/sheet) "Body is read-mostly: sentence case prose with Title Case sub-headings. Action buttons are optional; when present, follow `Verb + Noun`." (https://vercel.com/geist/sheet) Ours: the title is sentence case and names the job and its object: "Connect Telegram", "Add a skill". The step reads "Step N of M". The primary says what it does: "Connect", "Continue", never "OK".
|
|
17
17
|
* @access "Trap focus inside the sheet while it’s open and return focus to the trigger row on close so keyboard users keep their place in the list." (https://vercel.com/geist/sheet) "Provide a visible close button labeled `Close` (or an icon button with `aria-label="Close"`) since clicking outside doesn’t dismiss." (https://vercel.com/geist/sheet) "Announce the sheet with `aria-labelledby` pointing at the title; pair it with `aria-describedby` only when the body is short and load-bearing." (https://vercel.com/geist/sheet) Ours: a modal dialog named by its title; focus returns to the opener on close, or to `finalFocus` when the opener is gone. The step is read before the title.
|
|
18
18
|
*/
|
|
@@ -26,7 +26,10 @@ import { type FieldSize } from "../internal/input-box.js";
|
|
|
26
26
|
* never dimmed. Arrows, typing, Enter, Escape. A placeholder reads in the muted grey and is never
|
|
27
27
|
* an option. `label` and `error` go on `SelectTrigger`, and a string `label` requires `id`; a
|
|
28
28
|
* trigger with no name does not compile. "Group options past ~10 items with native `<optgroup>`;
|
|
29
|
-
* Geist `<Select>` has no `.Group` static." (https://vercel.com/geist/select) Ours:
|
|
29
|
+
* Geist `<Select>` has no `.Group` static." (https://vercel.com/geist/select) Ours: `look="inline"`
|
|
30
|
+
* draws the trigger as a ghost `Button` of its size, for a toolbar such as a chat bar's reasoning
|
|
31
|
+
* level: no box and no field ring, the fill only when hovered or open, the Button's focus outline
|
|
32
|
+
* and its disabled dimming; the list is the same. Group the
|
|
30
33
|
* options with `SelectGroup`. "Validate on blur and pass a string to `error`; messages name the
|
|
31
34
|
* field and end in a period (`Select a framework.`)." (https://vercel.com/geist/select) "Don’t wrap
|
|
32
35
|
* a labelled `<Select>` in a `Tooltip`; put the hint on a sibling icon button so the label stays
|
|
@@ -50,10 +53,13 @@ declare function SelectValue({ className, ...props }: SelectPrimitive.Value.Prop
|
|
|
50
53
|
* `SelectTrigger`, such as `MultiSelectTrigger`, wears the same box.
|
|
51
54
|
*/
|
|
52
55
|
declare const selectTriggerVariants: (props?: ({
|
|
56
|
+
look?: "inline" | "field" | null | undefined;
|
|
53
57
|
size?: "default" | "sm" | "lg" | null | undefined;
|
|
54
58
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
55
59
|
type SelectTriggerProps = Omit<SelectPrimitive.Trigger.Props, "prefix"> & {
|
|
56
60
|
size?: FieldSize;
|
|
61
|
+
/** `inline` sits among a toolbar's ghost buttons, such as a chat bar's reasoning level, and is drawn as one. */
|
|
62
|
+
look?: "field" | "inline";
|
|
57
63
|
/** A mark before the value, such as an icon. */
|
|
58
64
|
prefix?: React.ReactNode;
|
|
59
65
|
/** A mark at the end, in place of the chevron. */
|
|
@@ -63,7 +69,7 @@ type SelectTriggerProps = Omit<SelectPrimitive.Trigger.Props, "prefix"> & {
|
|
|
63
69
|
/** The message under the box, in the product's words; it also marks the box invalid. */
|
|
64
70
|
error?: string;
|
|
65
71
|
} & FieldName;
|
|
66
|
-
declare function SelectTrigger({ className, size, prefix, suffix, label, description, error, children, ...props }: SelectTriggerProps): React.JSX.Element;
|
|
72
|
+
declare function SelectTrigger({ className, size, look, prefix, suffix, label, description, error, children, ...props }: SelectTriggerProps): React.JSX.Element;
|
|
67
73
|
declare function SelectContent({ className, children, side, sideOffset, align, alignOffset, alignItemWithTrigger, ...props }: SelectPrimitive.Popup.Props & Pick<SelectPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger">): React.JSX.Element;
|
|
68
74
|
declare function SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props): React.JSX.Element;
|
|
69
75
|
declare function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props): React.JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/components/select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/components/select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAMlE,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,sBAAsB,CAAC;AAG9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,QAAA,MAAM,MAAM,6BAAuB,CAAC;AAEpC,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,qBAQxE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,qBAQxE;AAED;;;GAGG;AACH,QAAA,MAAM,qBAAqB;;;8EAkB1B,CAAC;AAEF,KAAK,kBAAkB,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG;IACxE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,gHAAgH;IAChH,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,gDAAgD;IAChD,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,kDAAkD;IAClD,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,SAAS,CAAC;AAEd,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,IAAgB,EAChB,IAAc,EACd,MAAM,EACN,MAAM,EACN,KAAK,EACL,WAAW,EACX,KAAK,EACL,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,kBAAkB,qBA+CpB;AAED,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,IAAe,EACf,UAAc,EACd,KAAgB,EAChB,WAAe,EACf,oBAA4B,EAC5B,GAAG,KAAK,EACT,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,GAC5B,IAAI,CACF,eAAe,CAAC,UAAU,CAAC,KAAK,EAChC,OAAO,GAAG,aAAa,GAAG,MAAM,GAAG,YAAY,GAAG,sBAAsB,CACzE,qBA6BF;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,UAAU,CAAC,KAAK,qBAQ7E;AAED,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK,qBAsBhF;AAED,iBAAS,eAAe,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,SAAS,CAAC,KAAK,qBAQhF;AAED,iBAAS,oBAAoB,CAAC,EAC5B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,aAAa,CAAC,qBAa5D;AAED,iBAAS,sBAAsB,CAAC,EAC9B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,eAAe,CAAC,qBAa9D;AAED,OAAO,EACL,MAAM,EACN,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,sBAAsB,EACtB,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,qBAAqB,EACrB,WAAW,GACZ,CAAC"}
|
|
@@ -4,6 +4,7 @@ import { Select as SelectPrimitive } from "@base-ui/react/select";
|
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
6
|
import { anchoredOverlayMotion } from "../lib/motion.js";
|
|
7
|
+
import { buttonVariants } from "./button.js";
|
|
7
8
|
import { FieldFrame } from "../internal/field-frame.js";
|
|
8
9
|
import { fieldDisabled, fieldOpen, fieldRing, fieldStates, } from "../internal/input-box.js";
|
|
9
10
|
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
|
|
@@ -31,7 +32,10 @@ import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
|
|
|
31
32
|
* never dimmed. Arrows, typing, Enter, Escape. A placeholder reads in the muted grey and is never
|
|
32
33
|
* an option. `label` and `error` go on `SelectTrigger`, and a string `label` requires `id`; a
|
|
33
34
|
* trigger with no name does not compile. "Group options past ~10 items with native `<optgroup>`;
|
|
34
|
-
* Geist `<Select>` has no `.Group` static." (https://vercel.com/geist/select) Ours:
|
|
35
|
+
* Geist `<Select>` has no `.Group` static." (https://vercel.com/geist/select) Ours: `look="inline"`
|
|
36
|
+
* draws the trigger as a ghost `Button` of its size, for a toolbar such as a chat bar's reasoning
|
|
37
|
+
* level: no box and no field ring, the fill only when hovered or open, the Button's focus outline
|
|
38
|
+
* and its disabled dimming; the list is the same. Group the
|
|
35
39
|
* options with `SelectGroup`. "Validate on blur and pass a string to `error`; messages name the
|
|
36
40
|
* field and end in a period (`Select a framework.`)." (https://vercel.com/geist/select) "Don’t wrap
|
|
37
41
|
* a labelled `<Select>` in a `Tooltip`; put the hint on a sibling icon button so the label stays
|
|
@@ -59,21 +63,25 @@ function SelectValue({ className, ...props }) {
|
|
|
59
63
|
* `SelectTrigger`, such as `MultiSelectTrigger`, wears the same box.
|
|
60
64
|
*/
|
|
61
65
|
const selectTriggerVariants = cva([
|
|
62
|
-
"flex min-h-touch-target w-full items-center justify-between gap-1.5
|
|
63
|
-
fieldRing,
|
|
64
|
-
fieldDisabled,
|
|
66
|
+
"flex min-h-touch-target w-full items-center justify-between gap-1.5 py-0 pr-3 pl-control-padding-inline text-sm whitespace-nowrap select-none data-placeholder:text-placeholder [@media(hover:hover)_and_(pointer:fine)]:min-h-0 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
65
67
|
], {
|
|
66
68
|
variants: {
|
|
69
|
+
look: {
|
|
70
|
+
field: ["bg-(--nc-field,var(--background))", fieldRing, fieldDisabled],
|
|
71
|
+
inline: "",
|
|
72
|
+
},
|
|
67
73
|
size: {
|
|
68
74
|
sm: "h-nadicode-control-height-sm rounded-sm",
|
|
69
75
|
default: "h-control-height rounded-sm",
|
|
70
76
|
lg: "h-nadicode-control-height-lg rounded-md text-base",
|
|
71
77
|
},
|
|
72
78
|
},
|
|
73
|
-
defaultVariants: { size: "default" },
|
|
79
|
+
defaultVariants: { look: "field", size: "default" },
|
|
74
80
|
});
|
|
75
|
-
function SelectTrigger({ className, size = "default", prefix, suffix, label, description, error, children, ...props }) {
|
|
76
|
-
return (_jsx(FieldFrame, { label: label, description: description, error: error, size: size, control: props, children: (aria) => (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, "data-field-frame":
|
|
81
|
+
function SelectTrigger({ className, size = "default", look = "field", prefix, suffix, label, description, error, children, ...props }) {
|
|
82
|
+
return (_jsx(FieldFrame, { label: label, description: description, error: error, size: size, control: props, children: (aria) => (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, "data-field-frame": look === "field" ? "" : undefined, className: cn(selectTriggerVariants({ look, size }), look === "field"
|
|
83
|
+
? [fieldStates, fieldOpen]
|
|
84
|
+
: [buttonVariants({ variant: "ghost", size }), "w-fit min-w-0 justify-between"], className), ...props, ...aria, children: [prefix ? (_jsx("span", { "data-slot": "select-prefix", "aria-hidden": "true", className: "mr-1.5 flex shrink-0 items-center text-muted-foreground", children: prefix })) : null, children, suffix ? (_jsx("span", { "data-slot": "select-suffix", "aria-hidden": "true", className: "flex shrink-0 items-center text-muted-foreground", children: suffix })) : (_jsx(SelectPrimitive.Icon, { render: _jsx(ChevronDownIcon, { className: "pointer-events-none size-3.5 text-muted-foreground" }) }))] })) }));
|
|
77
85
|
}
|
|
78
86
|
function SelectContent({ className, children, side = "bottom", sideOffset = 8, align = "center", alignOffset = 0, alignItemWithTrigger = false, ...props }) {
|
|
79
87
|
return (_jsx(SelectPrimitive.Portal, { children: _jsx(SelectPrimitive.Positioner, { side: side, sideOffset: sideOffset, align: align, alignOffset: alignOffset, alignItemWithTrigger: alignItemWithTrigger, className: "isolate z-50", children: _jsxs(SelectPrimitive.Popup, { "data-slot": "select-content", "data-align-trigger": alignItemWithTrigger, className: cn("pointer-events-auto", "relative isolate z-50 max-h-(--available-height) w-auto max-w-(--available-width) min-w-(--anchor-width) overflow-x-hidden overflow-y-auto rounded-lg border-0 bg-popover text-popover-foreground nc-elevation-floating data-[align-trigger=true]:transition-none", anchoredOverlayMotion, className), ...props, children: [_jsx(SelectScrollUpButton, {}), _jsx(SelectPrimitive.List, { className: "p-1.5", children: children }), _jsx(SelectScrollDownButton, {})] }) }) }));
|
|
@@ -18,6 +18,7 @@ type WaitingLineProps = Omit<React.ComponentProps<"div">, "children"> & ({
|
|
|
18
18
|
* @family Feedback
|
|
19
19
|
* @when A system wait the person asked for that runs past a second: a save, a connection, a
|
|
20
20
|
* download without a count.
|
|
21
|
+
* In a record's row, `WaitingDetail` puts it under the row's detail line, which names the work.
|
|
21
22
|
* @instead Under a second the `PendingLabel` alone. A known amount is a progress line with a count.
|
|
22
23
|
* A page load is `Skeleton`. An Agent working is her orb and a sentence.
|
|
23
24
|
* @behaviour Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing.
|
|
@@ -25,9 +26,13 @@ type WaitingLineProps = Omit<React.ComponentProps<"div">, "children"> & ({
|
|
|
25
26
|
* it breathes in opacity, across the whole track, instead of travelling.
|
|
26
27
|
* @words None of its own. The control beside it carries the pending word; a standalone line takes a
|
|
27
28
|
* `label` naming the work: *Connecting Telegram*.
|
|
28
|
-
* @access With a label: `role="status"`, announced once after the second. `decorative` when
|
|
29
|
-
*
|
|
29
|
+
* @access With a label: `role="status"`, announced once after the second. `decorative` when a control or
|
|
30
|
+
* sentence beside it already names the work (DESIGN.md "System activity"). The segment is hidden.
|
|
30
31
|
*/
|
|
31
32
|
declare function WaitingLine({ className, decorative, label, ...props }: WaitingLineProps): React.JSX.Element;
|
|
32
|
-
|
|
33
|
+
/** A record's wait in its row: the detail line naming the work, with the waiting line under it. */
|
|
34
|
+
declare function WaitingDetail({ work }: {
|
|
35
|
+
work: string;
|
|
36
|
+
}): React.JSX.Element;
|
|
37
|
+
export { WaitingDetail, WaitingLine, WAITING_LINE_DELAY_MS };
|
|
33
38
|
//# sourceMappingURL=waiting-line.d.ts.map
|
|
@@ -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
|
|
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,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
/**
|
|
@@ -14,6 +14,7 @@ const WAITING_LINE_DELAY_MS = 1000;
|
|
|
14
14
|
* @family Feedback
|
|
15
15
|
* @when A system wait the person asked for that runs past a second: a save, a connection, a
|
|
16
16
|
* download without a count.
|
|
17
|
+
* In a record's row, `WaitingDetail` puts it under the row's detail line, which names the work.
|
|
17
18
|
* @instead Under a second the `PendingLabel` alone. A known amount is a progress line with a count.
|
|
18
19
|
* A page load is `Skeleton`. An Agent working is her orb and a sentence.
|
|
19
20
|
* @behaviour Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing.
|
|
@@ -21,8 +22,8 @@ const WAITING_LINE_DELAY_MS = 1000;
|
|
|
21
22
|
* it breathes in opacity, across the whole track, instead of travelling.
|
|
22
23
|
* @words None of its own. The control beside it carries the pending word; a standalone line takes a
|
|
23
24
|
* `label` naming the work: *Connecting Telegram*.
|
|
24
|
-
* @access With a label: `role="status"`, announced once after the second. `decorative` when
|
|
25
|
-
*
|
|
25
|
+
* @access With a label: `role="status"`, announced once after the second. `decorative` when a control or
|
|
26
|
+
* sentence beside it already names the work (DESIGN.md "System activity"). The segment is hidden.
|
|
26
27
|
*/
|
|
27
28
|
function WaitingLine({ className, decorative = false, label, ...props }) {
|
|
28
29
|
const [elapsed, setElapsed] = React.useState(false);
|
|
@@ -33,4 +34,8 @@ function WaitingLine({ className, decorative = false, label, ...props }) {
|
|
|
33
34
|
const announced = elapsed && !decorative;
|
|
34
35
|
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 }));
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
/** A record's wait in its row: the detail line naming the work, with the waiting line under it. */
|
|
38
|
+
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 })] }));
|
|
40
|
+
}
|
|
41
|
+
export { WaitingDetail, WaitingLine, WAITING_LINE_DELAY_MS };
|
|
@@ -167,7 +167,7 @@ function isRingOrOutlineMechanic(utility) {
|
|
|
167
167
|
* `better-tailwindcss/no-unknown-classes` reports it as one. That is what
|
|
168
168
|
* retired the hand-written colour-name list this rule used to carry.
|
|
169
169
|
*/
|
|
170
|
-
function isAppearanceUtility(utility) {
|
|
170
|
+
export function isAppearanceUtility(utility) {
|
|
171
171
|
const base = withoutVariants(utility);
|
|
172
172
|
return (
|
|
173
173
|
/^appearance-/.test(base) ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../src/showcase/specimens/navigation.tsx"],"names":[],"mappings":"AAoEA,iBAAS,qBAAqB,gCAoC7B;AAED,iBAAS,qBAAqB,gCAmB7B;AAED,iBAAS,uBAAuB,gCAiE/B;AAED,iBAAS,YAAY,gCAqCpB;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../src/showcase/specimens/navigation.tsx"],"names":[],"mappings":"AAoEA,iBAAS,qBAAqB,gCAoC7B;AAED,iBAAS,qBAAqB,gCAmB7B;AAED,iBAAS,uBAAuB,gCAiE/B;AAED,iBAAS,YAAY,gCAqCpB;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;CAkRI,CAAC"}
|
|
@@ -86,7 +86,7 @@ function TabsSpecimen() {
|
|
|
86
86
|
return (_jsxs("div", { className: "grid gap-8", children: [_jsxs(Tabs, { onValueChange: setPlain, value: plain, children: [_jsxs(TabsList, { "aria-label": "Rossi Ottica", children: [_jsx(TabsTrigger, { value: "overview", children: "Overview" }), _jsx(TabsTrigger, { value: "installations", children: "Installations" }), _jsx(TabsTrigger, { disabled: true, disabledReason: "Only owners see billing", value: "billing", children: "Billing" })] }), _jsx(TabsContent, { className: "pt-3 text-sm text-muted-foreground", value: "overview", children: "What the customer runs, at a glance." }), _jsx(TabsContent, { className: "pt-3 text-sm text-muted-foreground", value: "installations", children: "Every machine Nadia is installed on." })] }), _jsxs(Tabs, { onValueChange: setLined, value: lined, children: [_jsxs(TabsList, { "aria-label": "Rossi Ottica, with a rule", variant: "line", children: [_jsx(TabsTrigger, { value: "overview", children: "Overview" }), _jsx(TabsTrigger, { value: "installations", children: "Installations" }), _jsx(TabsTrigger, { disabled: true, disabledReason: "Only owners see billing", value: "billing", children: "Billing" })] }), _jsx(TabsContent, { className: "pt-3 text-sm text-muted-foreground", value: "overview", children: "What the customer runs, at a glance." }), _jsx(TabsContent, { className: "pt-3 text-sm text-muted-foreground", value: "installations", children: "Every machine Nadia is installed on." })] })] }));
|
|
87
87
|
}
|
|
88
88
|
export const NAVIGATION = {
|
|
89
|
-
AgentListRow: () => (_jsxs("ul", { className: "grid max-w-xs gap-1", children: [_jsx("li", { children: _jsx(AgentListRow, { color: COBALTO, current: true, form: "orbit", name: "Invoices", state: "working", stateLine: "Drafting October's invoices" }) }), _jsx("li", { children: _jsx(AgentListRow, { color: VERDE, form: "lattice", name: "Orders", state: "waiting", stateLine: "Waiting for you"
|
|
89
|
+
AgentListRow: () => (_jsxs("ul", { className: "grid max-w-xs gap-1", children: [_jsx("li", { children: _jsx(AgentListRow, { color: COBALTO, current: true, form: "orbit", name: "Invoices", state: "working", stateLine: "Drafting October's invoices" }) }), _jsx("li", { children: _jsx(AgentListRow, { color: VERDE, form: "lattice", name: "Orders", state: "waiting", stateLine: "Waiting for you" }) })] })),
|
|
90
90
|
Breadcrumb: () => (_jsxs("div", { className: "grid gap-4", children: [_jsx(Breadcrumb, { "aria-label": "Path", children: _jsxs(BreadcrumbList, { children: [_jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { href: "#customers", children: "Customers" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbPage, { children: "Rossi Ottica" }) })] }) }), _jsx(Breadcrumb, { "aria-label": "Path with a closed ancestor", children: _jsxs(BreadcrumbList, { children: [_jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { disabled: true, children: "Partners" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { href: "#customers", children: "Customers" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbPage, { children: "Rossi Ottica" }) })] }) }), _jsx(Breadcrumb, { "aria-label": "Long path", children: _jsxs(BreadcrumbList, { children: [_jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { href: "#customers", children: "Customers" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbEllipsis, { label: "More" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbLink, { href: "#installations", children: "Installations" }) }), _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { children: _jsx(BreadcrumbPage, { children: "Rossi Ottica, Milano" }) })] }) })] })),
|
|
91
91
|
CommandDialog: CommandDialogSpecimen,
|
|
92
92
|
Footer: () => (_jsx(Footer, { children: _jsxs(FooterInner, { children: [_jsx(FooterBrand, { "aria-label": "nadicode", href: "#top", children: _jsx(BrandWordmark, { alt: "" }) }), _jsx(FooterIdentity, { children: "Agents that do the work, for Italian companies." }), _jsxs(FooterNav, { "aria-label": "Company", children: [_jsx("a", { href: "#privacy", children: "Privacy" }), _jsx("a", { href: "#terms", children: "Terms" })] }), _jsx(FooterMeta, { children: "\u00A9 2026 nadicode" })] }) })),
|
|
@@ -87,7 +87,7 @@ export const USAGE = [
|
|
|
87
87
|
summary: "A consequence confirmed before it happens, in a blocking alert dialog.",
|
|
88
88
|
when: '"Reach for this over `Modal` when the action is destructive and warrants friction: delete, rotate, revoke, disconnect, downgrade, disable a security setting. The typed gate forces deliberate intent." (https://vercel.com/geist/destructive-action-modal) "Use it for reversible destructive actions too, when the consequence is serious enough to warrant a pause — disabling deployment protection, revoking a shared token. Keep the typed gate; drop `irreversibleDescription`." (https://vercel.com/geist/destructive-action-modal) "For high-stakes destructive actions (delete production resource, rotate signing key, downgrade plan), gate the primary button on a typed match of the resource name." (https://vercel.com/geist/modal) Ours: `ConfirmDialog` is Geist\'s DestructiveActionModal: remove a mailbox, delete an Agent, with `verify` as the typed gate. It asks for the record\'s name before the confirm enables on a high-stakes action (delete, rotate, revoke, disconnect, downgrade, a protection switched off), even a reversible one; `irreversible` names what cannot be undone and is left out when the action can be reversed.',
|
|
89
89
|
instead: '"Don\'t use for routine confirmations (save draft, discard changes, close without saving). The typed gate reads as melodrama when the stakes are low — use a plain `Modal` instead." (https://vercel.com/geist/destructive-action-modal) "Confirm destructive actions in a Modal. `Drawer` and `Sheet` don’t fully dim the page, so they read as too soft for a delete or revoke." (https://vercel.com/geist/modal) Ours: a routine confirmation (discard a draft) takes neither `verify` nor `irreversible`; an action that can be undone runs at once and offers its undo in a toast; a form of a few fields is a `FormDialog`; a choice from a catalogue is a `CatalogDrawer`.',
|
|
90
|
-
behaviour: '"The verification input gets focus on open so the user can start typing immediately. Submit stays disabled until the value matches `verificationPhrase` exactly." (https://vercel.com/geist/destructive-action-modal) "Enter submits only when the gate is open; it\'s a no-op otherwise. Cancel, outside-click, and Escape all dismiss." (https://vercel.com/geist/destructive-action-modal) "`loading` disables both buttons. The caller owns `open` — do not self-dismiss from `onConfirm`. Close the modal after the API settles (success or error), or keep it open on error so the user can retry." (https://vercel.com/geist/destructive-action-modal) "Default focus to `Cancel` on any destructive Modal. Enter must never trigger the destructive action without a typed confirmation." (https://vercel.com/geist/modal) "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: `title`, `description`, `confirmLabel` and `cancelLabel` are required; `irreversible` and `verify` are optional, and the absence of `irreversible` is what marks the action reversible. Geist\'s `irreversibleDescription` is `irreversible`, `verificationPhrase` and `verificationLabel` are `verify.phrase` and `verify.prompt`, and `error` is the catalogue\'s message. 480 px wide over a scrim. The title and the description, then the body in a 24 px column: the `irreversible` note, the `verify` field, the failure under it. `irreversible` is a flat red note with a red mark and red words, not a band over the footer; besides it the only red is the failure and the enabled destructive confirm. While `loading` the field turns read-only and keeps focus, Escape does not close it, and the confirm says `confirmPendingLabel`, which falls back to the confirm\'s label. A press outside does not close it, so a stray click never discards a typed phrase or an action in flight; Escape and Cancel do. The footer is a bar that stays at the bottom while a tall confirmation scrolls, and a row on a phone too.',
|
|
90
|
+
behaviour: '"The verification input gets focus on open so the user can start typing immediately. Submit stays disabled until the value matches `verificationPhrase` exactly." (https://vercel.com/geist/destructive-action-modal) "Enter submits only when the gate is open; it\'s a no-op otherwise. Cancel, outside-click, and Escape all dismiss." (https://vercel.com/geist/destructive-action-modal) "`loading` disables both buttons. The caller owns `open` — do not self-dismiss from `onConfirm`. Close the modal after the API settles (success or error), or keep it open on error so the user can retry." (https://vercel.com/geist/destructive-action-modal) "Default focus to `Cancel` on any destructive Modal. Enter must never trigger the destructive action without a typed confirmation." (https://vercel.com/geist/modal) "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: `title`, `description`, `confirmLabel` and `cancelLabel` are required; `irreversible` and `verify` are optional, and the absence of `irreversible` is what marks the action reversible. Geist\'s `irreversibleDescription` is `irreversible`, `verificationPhrase` and `verificationLabel` are `verify.phrase` and `verify.prompt`, and `error` is the catalogue\'s message. 480 px wide over a scrim. The title and the description, then the body in a 24 px column: the `irreversible` note, the `verify` field, the failure under it. `irreversible` is a flat red note with a red mark and red words, not a band over the footer; besides it the only red is the failure and the enabled destructive confirm. While `loading` the field turns read-only and keeps focus, Escape does not close it, and the confirm says `confirmPendingLabel`, which falls back to the confirm\'s label. A press outside does not close it, so a stray click never discards a typed phrase or an action in flight; Escape and Cancel do. The footer is a bar that stays at the bottom while a tall confirmation scrolls, and a row on a phone too. How long it stays open while work goes on follows DESIGN.md "System activity".',
|
|
91
91
|
words: '"`title` is Title Case, `Verb + Noun`, a statement — never a question. `Delete Project`, not `Delete this project?`." (https://vercel.com/geist/destructive-action-modal) "`description` is sentence case, names the consequence, and interpolates the specific resource when relevant. `<b>my-project</b> and all its deployments will be permanently deleted.` reads stronger than `This project and all its deployments will be deleted.`." (https://vercel.com/geist/destructive-action-modal) "`confirmLabel` matches the title 1:1. Never generic (`Confirm`, `OK`, `Continue`), never a bare verb (`Delete`)." (https://vercel.com/geist/destructive-action-modal) "`cancelLabel` defaults to `Cancel`. Override when Cancel is ambiguous, such as `Keep Turn Running` when the primary action is itself a cancellation." (https://vercel.com/geist/destructive-action-modal) "`verificationPhrase`: for entity deletes, type the **resource name itself** (`my-project`) and pair with `verificationLabel="project name"` so the prompt reads `To confirm, type the project name "my-project"`. That\'s the canonical signal that the user knows which thing they\'re acting on. Fall back to a lowercase verb phrase (`disable vercel authentication`) only when there\'s no entity to name." (https://vercel.com/geist/destructive-action-modal) "`irreversibleDescription` names the specific action and resource and ends with `cannot be undone.` — `Deleting my-project cannot be undone.` rather than the generic `This cannot be undone.`. Omit the prop entirely for reversible actions; the prop\'s absence is the signal, not a falsy value." (https://vercel.com/geist/destructive-action-modal) "`error` surfaces the API failure verbatim in Vercel voice: `Couldn\'t save settings. Try again.`. Never dump a raw error object." (https://vercel.com/geist/destructive-action-modal) "Pair the post-confirm success toast verb 1:1 with the primary button: `Delete Project` button, `Project deleted` toast. Never `Project removed`." (https://vercel.com/geist/destructive-action-modal) "Close irreversible bodies with `This cannot be undone.`; close cascade-only bodies with `Some effects cannot be undone.`. Don’t claim full irreversibility for a partial cascade." (https://vercel.com/geist/modal) Ours: the title is sentence case and names the record, "Remove pec@acme.it", and the confirm repeats the verb and the noun, "Remove mailbox", so the two share a verb and not every word; its success toast uses the same verb ("Mailbox removed"). The description names the record in medium weight.',
|
|
92
92
|
access: '"The verification input is associated with its prompt via `aria-labelledby` + `htmlFor`. Screen readers announce the full prompt (`To confirm, type "delete my-project"`) on focus." (https://vercel.com/geist/destructive-action-modal) "The Warning icon in the irreversibility band is `aria-hidden`; the sentence carries the meaning so nothing is announced twice." (https://vercel.com/geist/destructive-action-modal) "Focus stays inside the modal across an `error` transition so the user can retry without losing context. After success, return focus to the trigger." (https://vercel.com/geist/destructive-action-modal) "Set `aria-labelledby` to the `Modal.Title` id so screen readers announce the title on open." (https://vercel.com/geist/modal) "Keep the Cancel button literally `Cancel` so screen-reader users hear a stable dismissal label across destructive flows." (https://vercel.com/geist/modal) Ours: `role="alertdialog"` named by its title and described by its consequence; the note is `role="note"` with its icon hidden. Focus starts on Cancel, or in the field with `verify`.',
|
|
93
93
|
},
|
|
@@ -109,7 +109,7 @@ export const USAGE = [
|
|
|
109
109
|
summary: "A new record, or one or two values from a command, in a modal dialog.",
|
|
110
110
|
when: '"Use Modal when a decision must block the rest of the page. For persistent associated context where the underlying page stays readable, use `Sheet` on desktop or `Drawer` on mobile." (https://vercel.com/geist/modal) Ours: a person adds a record through a few fields, or changes one or two values a command asks for: new customer, add a mailbox, add an MCP server.',
|
|
111
111
|
instead: '"Skip Modal for routine create flows that have a dedicated page; route to the page instead." (https://vercel.com/geist/modal) "Confirm destructive actions in a Modal. `Drawer` and `Sheet` don’t fully dim the page, so they read as too soft for a delete or revoke." (https://vercel.com/geist/modal) Ours: a record already in a list edits in its row with `RowEditForm`; a choice from a catalogue is a `CatalogDrawer`; a setup in steps with a wait is a `FormDrawer` with `job="steps"`; a consequence is confirmed in a `ConfirmDialog`.',
|
|
112
|
-
behaviour: '"Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: centred over a scrim without blur, 540 px wide, a bottom sheet on a phone: the title and its help, the fields in a 12 px column, then the footer, which stays at the bottom while a tall form scrolls, with Cancel at the leading edge and the primary at the trailing edge, in one row also on a phone. `FormDialogForm` puts the fields in the dialog\'s own scroll body and the actions in its fixed footer; no other overlay is nested inside. A refusal or a failed save sits above the footer and the fields keep what was typed. While its save runs it holds open, Cancel and the primary hold, and the waiting line runs under them. With `unsaved.dirty`, Escape and a press outside are held back and `unsaved.onDismissBlocked` runs so the caller can ask; Close and Cancel always close.',
|
|
112
|
+
behaviour: '"Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: centred over a scrim without blur, 540 px wide, a bottom sheet on a phone: the title and its help, the fields in a 12 px column, then the footer, which stays at the bottom while a tall form scrolls, with Cancel at the leading edge and the primary at the trailing edge, in one row also on a phone. `FormDialogForm` puts the fields in the dialog\'s own scroll body and the actions in its fixed footer; no other overlay is nested inside. A refusal or a failed save sits above the footer and the fields keep what was typed. While its save runs it holds open, Cancel and the primary hold, and the waiting line runs under them. With `unsaved.dirty`, Escape and a press outside are held back and `unsaved.onDismissBlocked` runs so the caller can ask; Close and Cancel always close. How long it stays open while work goes on follows DESIGN.md "System activity".',
|
|
113
113
|
words: '"`Modal.Title` is a Title Case statement, never a question. `Transfer Project` is correct; `Transfer Project?` is wrong." (https://vercel.com/geist/modal) "Primary button is `Verb + Noun` and matches the title verb (`Transfer Project` title pairs with `Transfer Project` button). Never `Confirm`, `OK`, or a bare verb on a destructive primary." (https://vercel.com/geist/modal) "Cancel literal stays `Cancel`. Acknowledgment-only Modals (after a key reveal, after a one-time-show) use `Done`, never `OK` or `Close`." (https://vercel.com/geist/modal) Ours: the title and the primary are sentence case, where Geist writes Title Case: the title names the job and its object, "New customer", "Add a mailbox"; the primary repeats it, "Create customer", "Add mailbox".',
|
|
114
114
|
access: '"Set `aria-labelledby` to the `Modal.Title` id so screen readers announce the title on open." (https://vercel.com/geist/modal) "Trap focus inside the Modal while it’s open and return focus to the trigger after close. Restore body scroll on the same tick the Modal unmounts." (https://vercel.com/geist/modal) "After an error inside the Modal, keep focus inside so the user can retry; after success, return focus to the trigger." (https://vercel.com/geist/modal) Ours: a modal dialog with a Close named by `closeLabel`; focus starts in its first field or `initialFocus` and returns to the opener or `finalFocus`. Escape and Close close it unless the save runs.',
|
|
115
115
|
},
|
|
@@ -120,7 +120,7 @@ export const USAGE = [
|
|
|
120
120
|
summary: "The right-side sheet for a catalogue or a setup in steps, at one width.",
|
|
121
121
|
when: "Ours: choosing from a catalogue too long for a dialog (through `CatalogDrawer`), or setting something up in several steps with a wait: connect a messaging channel, Telegram's code.",
|
|
122
122
|
instead: "\"Don’t use Sheet to confirm destructive actions. The default `modal=false` keeps the page interactive, which weakens severity for a delete or revoke.\" (https://vercel.com/geist/sheet) Ours: a new record from a few fields is a `FormDialog`; a record already in a list edits in its row with `RowEditForm`; a fact on the record's own page changes in place in its section; a consequence is confirmed in a `ConfirmDialog`; a phone's short job is a `Drawer`.",
|
|
123
|
-
behaviour: '"Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal `Sheet` from the right edge over the scrim, because the drawer owns the screen, at the Sheet\'s one width that no caller sets. The header holds the step ("Step 2 of 3", in mono) over the title, which stays the job\'s name, then the description and Close; the body scrolls between the header and a footer that stays, with no fill or rule of its own, Cancel then the primary at its trailing edge. A press outside does not close it; Escape does, unless `unsaved.dirty` holds it back. Cancel holds while a save runs.',
|
|
123
|
+
behaviour: '"Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal `Sheet` from the right edge over the scrim, because the drawer owns the screen, at the Sheet\'s one width that no caller sets. The header holds the step ("Step 2 of 3", in mono) over the title, which stays the job\'s name, then the description and Close; the body scrolls between the header and a footer that stays, with no fill or rule of its own, Cancel then the primary at its trailing edge. A press outside does not close it; Escape does, unless `unsaved.dirty` holds it back. Cancel holds while a save runs. How long it stays open while work goes on follows DESIGN.md "System activity".',
|
|
124
124
|
words: '"Title is Title Case and names the entity (`Deployment Details`, `Member Profile`), not the page action." (https://vercel.com/geist/sheet) "Body is read-mostly: sentence case prose with Title Case sub-headings. Action buttons are optional; when present, follow `Verb + Noun`." (https://vercel.com/geist/sheet) Ours: the title is sentence case and names the job and its object: "Connect Telegram", "Add a skill". The step reads "Step N of M". The primary says what it does: "Connect", "Continue", never "OK".',
|
|
125
125
|
access: '"Trap focus inside the sheet while it’s open and return focus to the trigger row on close so keyboard users keep their place in the list." (https://vercel.com/geist/sheet) "Provide a visible close button labeled `Close` (or an icon button with `aria-label="Close"`) since clicking outside doesn’t dismiss." (https://vercel.com/geist/sheet) "Announce the sheet with `aria-labelledby` pointing at the title; pair it with `aria-describedby` only when the body is short and load-bearing." (https://vercel.com/geist/sheet) Ours: a modal dialog named by its title; focus returns to the opener on close, or to `finalFocus` when the opener is gone. The step is read before the title.',
|
|
126
126
|
},
|
|
@@ -483,7 +483,7 @@ export const USAGE = [
|
|
|
483
483
|
summary: "Type to find one value, or several, in a known set.",
|
|
484
484
|
when: '"Filters large lists to selectable options based on the matching query." (https://vercel.com/geist/combobox) "Pick `<Combobox>` when users type to filter a known list of values (regions, frameworks, env-var names)." (https://vercel.com/geist/combobox) Ours: assign an Agent, choose a time zone, add several Agents at once with `multiple`.',
|
|
485
485
|
instead: '"For short, fixed lists where typing adds nothing, switch to `Select`." (https://vercel.com/geist/combobox) "When more than one value can be chosen at once, use `MultiSelect`." (https://vercel.com/geist/combobox) "For free-form filter strings that don’t resolve to a single option, drop in `Input` with the `search` variant." (https://vercel.com/geist/combobox) Ours: several values chosen from a list you scan rather than type into is a `MultiSelect`; `Combobox` takes several with `multiple`. `Input` has no `search` variant, so free text is an `Input` and narrowing a collection is `CollectionFilters`.',
|
|
486
|
-
behaviour: 'The parts are `ComboboxInput`, `ComboboxContent`, `ComboboxList`, `ComboboxItem`, `ComboboxGroup` with its `ComboboxLabel`, `ComboboxLoading`, and `ComboboxChips` with its `ComboboxChip` and `ComboboxChipsInput` for `multiple`; the line for no match is the `empty` of `ComboboxContent`. The box fills its container and is `Input`\'s: its three sizes (`size` on `ComboboxInput`), its ring, hover, focus halo and disabled, with the search mark at the start, a `placeholder` that names the scope, and the open-list or clear button flush at the end, in the muted grey until hovered (the open-list button is 30 px high and centred at the default size, full height at `sm` and `lg`). Colour sits in one place: an error turns the ring red, fills the halo and colours the typed text; the placeholder, marks and list stay neutral. `label` and `error` go on `ComboboxInput`, and a string `label` requires `id`; a box with no name does not compile. The list, a solid floating surface, opens 8 px under it at its width (a narrower `className` width centres under the box), in a 6 px inset, up to five and a half rows tall (210 px) so a cut row shows there is more, with rows 36 px tall and no gap. A row is one line that ends in an ellipsis, with a 16 px mark 12 px before the name and a 14 px check at the chosen row\'s end, after the name. A row that cannot be chosen turns its text muted, never dimmed. Typing filters the list. "Options can opt out of the fixed row height with `ignoreDefaultHeight` to render multi-line content (e.g. a shared environment variable name plus its target). The list sizes each row to its content and stays scrollable." (https://vercel.com/geist/combobox) Ours: there is no `ignoreDefaultHeight`; a two-line row is an explicit column child. "Show a loading state for async results; don’t collapse the list while the request is in flight." (https://vercel.com/geist/combobox) Ours: while results load the list stays and `ComboboxLoading` says so, and the chosen value stays; `ComboboxContent` requires `empty`, the sentence for no match, or `null` while a search is still running. "Render a custom empty state of the form `No {items} match "{query}"` rather than a bare `No results`." (https://vercel.com/geist/combobox) "Keep arrow-key navigation through `<Combobox.Option>` items; don’t hijack Enter to submit the surrounding form while the list is open." (https://vercel.com/geist/combobox) "Set `clearable` to show a clear button when a value is selected." (https://vercel.com/geist/combobox) Ours: `showClear` with a `clearLabel` does it; clear empties the box and keeps focus there. With `multiple`, each choice is a chip with its own remove; Backspace in an empty box removes the last. "It is common to use Combobox inside a Modal. On mobile, the Modal automatically renders a Dialog instead." (https://vercel.com/geist/combobox) "Inside a `Modal`, Geist switches to a Dialog on mobile automatically; don’t layer a second portal." (https://vercel.com/geist/combobox) Ours: our overlay is `Dialog`, and `ComboboxContent` is the list\'s one portal.',
|
|
486
|
+
behaviour: 'The parts are `ComboboxInput`, `ComboboxContent`, `ComboboxList`, `ComboboxItem`, `ComboboxGroup` with its `ComboboxLabel`, `ComboboxLoading`, and `ComboboxChips` with its `ComboboxChip` and `ComboboxChipsInput` for `multiple`; the line for no match is the `empty` of `ComboboxContent`. The box fills its container and is `Input`\'s: its three sizes (`size` on `ComboboxInput`), its ring, hover, focus halo and disabled, with the search mark at the start, a `placeholder` that names the scope, and the open-list or clear button flush at the end, in the muted grey until hovered (the open-list button is 30 px high and centred at the default size, full height at `sm` and `lg`). Colour sits in one place: an error turns the ring red, fills the halo and colours the typed text; the placeholder, marks and list stay neutral. `label` and `error` go on `ComboboxInput`, and a string `label` requires `id`; a box with no name does not compile. The list, a solid floating surface, opens 8 px under it at its width (a narrower `className` width centres under the box), in a 6 px inset, up to five and a half rows tall (210 px) so a cut row shows there is more, with rows 36 px tall and no gap; a `long` list, one people scan by section, shows twelve and a half rows (462 px). A trigger that opens a list with its own search puts `ComboboxInput` first in `ComboboxContent`: there the box draws no ring or halo, spans the list on its surface, and a hairline divides it from the rows, wherever it sits in the content. A row is one line that ends in an ellipsis, with a 16 px mark 12 px before the name and a 14 px check at the chosen row\'s end, after the name. A row that cannot be chosen turns its text muted, never dimmed. Typing filters the list. "Options can opt out of the fixed row height with `ignoreDefaultHeight` to render multi-line content (e.g. a shared environment variable name plus its target). The list sizes each row to its content and stays scrollable." (https://vercel.com/geist/combobox) Ours: there is no `ignoreDefaultHeight`; a two-line row is an explicit column child. "Show a loading state for async results; don’t collapse the list while the request is in flight." (https://vercel.com/geist/combobox) Ours: while results load the list stays and `ComboboxLoading` says so, and the chosen value stays; `ComboboxContent` requires `empty`, the sentence for no match, or `null` while a search is still running. "Render a custom empty state of the form `No {items} match "{query}"` rather than a bare `No results`." (https://vercel.com/geist/combobox) "Keep arrow-key navigation through `<Combobox.Option>` items; don’t hijack Enter to submit the surrounding form while the list is open." (https://vercel.com/geist/combobox) "Set `clearable` to show a clear button when a value is selected." (https://vercel.com/geist/combobox) Ours: `showClear` with a `clearLabel` does it; clear empties the box and keeps focus there. With `multiple`, each choice is a chip with its own remove; Backspace in an empty box removes the last. "It is common to use Combobox inside a Modal. On mobile, the Modal automatically renders a Dialog instead." (https://vercel.com/geist/combobox) "Inside a `Modal`, Geist switches to a Dialog on mobile automatically; don’t layer a second portal." (https://vercel.com/geist/combobox) Ours: our overlay is `Dialog`, and `ComboboxContent` is the list\'s one portal.',
|
|
487
487
|
words: '"Visible label is a short Title Case noun (`Region`, `Environment Variable Name`)." (https://vercel.com/geist/combobox) "Placeholder is the inline hint: `Search regions`, `DATABASE_URL`. Never bare `Search…` and never the label restated." (https://vercel.com/geist/combobox) "Option text is Title Case for short values and matches canonical branding (`Next.js`, not `NextJS`); same register across the list." (https://vercel.com/geist/combobox) "Validation names the field and constraint, sentence case with a period (`Select a region.`)." (https://vercel.com/geist/combobox) Ours: the label and the rows are sentence case. The hint is scoped: *Find an Agent*. No match quotes the query: *No Agent matches “Xy”.* A chip reads as the record\'s name.',
|
|
488
488
|
access: '"`<Combobox>` has no `label` prop; pair a sibling `<Label htmlFor>` with the root `id`, or set `aria-label` on the root for icon-only triggers." (https://vercel.com/geist/combobox) "The root accepts `aria-label` only; there is no `aria-labelledby` prop, so the sibling-label path is the way to reference visible text." (https://vercel.com/geist/combobox) "Trap focus inside the popover when nested in a `Modal` so Tab cycles options instead of the page behind it." (https://vercel.com/geist/combobox) Ours: `ComboboxInput` takes a visible `label` tied to the box by `id`, or `aria-label` or `aria-labelledby`; combobox and listbox roles; the open-list and clear buttons are named by `triggerLabel` and `clearLabel`; each chip\'s remove is named: *Remove Acme Quotes*. An Agent\'s orb in a row is a decorative mark beside her name.',
|
|
489
489
|
},
|
|
@@ -549,7 +549,7 @@ export const USAGE = [
|
|
|
549
549
|
summary: "A short fixed list in our style, opened from a trigger that shows the choice.",
|
|
550
550
|
when: '"Display a dropdown list of items." (https://vercel.com/geist/select) "Pick `<Select>` for short, fixed lists (under ~10 items) where typing adds nothing; switch to `Combobox` once filtering helps." (https://vercel.com/geist/select) Ours: four or more options, each short enough to need no sentence: how often a task runs. A list of two or three static options is a `RadioGroup`, as Geist says.',
|
|
551
551
|
instead: '"Use `MultiSelect` when more than one value can be chosen at once; use `Switch` for a 2–3 option segmented choice." (https://vercel.com/geist/select) Ours: two or three options are radios or a segmented control. Inside a native form a long list is `NativeSelect`.',
|
|
552
|
-
behaviour: 'The trigger is `Input`\'s box: its sizes, its ring, hover, focus halo and disabled, the label above it and the message under it, in the three sizes `sm`, default and `lg` that `Input` has. Colour sits in one place: an error turns the ring red and fills the halo, and colours the message; the fill, the value, the chevron and the label stay neutral. It shows the chosen value, after an optional `prefix` mark, with the chevron at the end in the muted grey (a `suffix` mark takes the chevron\'s place), and fills its container; it keeps the hover edge while its list is open. The list, a solid floating surface with a 12 px corner, opens under it, 8 px away, never covering the trigger (`alignItemWithTrigger` is off), in a 6 px inset, with a 14 px check at the chosen row\'s end, rows 36 px tall and no gap between them; a row that cannot be chosen turns its text muted, never dimmed. Arrows, typing, Enter, Escape. A placeholder reads in the muted grey and is never an option. `label` and `error` go on `SelectTrigger`, and a string `label` requires `id`; a trigger with no name does not compile. "Group options past ~10 items with native `<optgroup>`; Geist `<Select>` has no `.Group` static." (https://vercel.com/geist/select) Ours:
|
|
552
|
+
behaviour: 'The trigger is `Input`\'s box: its sizes, its ring, hover, focus halo and disabled, the label above it and the message under it, in the three sizes `sm`, default and `lg` that `Input` has. Colour sits in one place: an error turns the ring red and fills the halo, and colours the message; the fill, the value, the chevron and the label stay neutral. It shows the chosen value, after an optional `prefix` mark, with the chevron at the end in the muted grey (a `suffix` mark takes the chevron\'s place), and fills its container; it keeps the hover edge while its list is open. The list, a solid floating surface with a 12 px corner, opens under it, 8 px away, never covering the trigger (`alignItemWithTrigger` is off), in a 6 px inset, with a 14 px check at the chosen row\'s end, rows 36 px tall and no gap between them; a row that cannot be chosen turns its text muted, never dimmed. Arrows, typing, Enter, Escape. A placeholder reads in the muted grey and is never an option. `label` and `error` go on `SelectTrigger`, and a string `label` requires `id`; a trigger with no name does not compile. "Group options past ~10 items with native `<optgroup>`; Geist `<Select>` has no `.Group` static." (https://vercel.com/geist/select) Ours: `look="inline"` draws the trigger as a ghost `Button` of its size, for a toolbar such as a chat bar\'s reasoning level: no box and no field ring, the fill only when hovered or open, the Button\'s focus outline and its disabled dimming; the list is the same. Group the options with `SelectGroup`. "Validate on blur and pass a string to `error`; messages name the field and end in a period (`Select a framework.`)." (https://vercel.com/geist/select) "Don’t wrap a labelled `<Select>` in a `Tooltip`; put the hint on a sibling icon button so the label stays announced." (https://vercel.com/geist/select) Other form rules are `Field`\'s.',
|
|
553
553
|
words: '"Options are Title Case when short and match canonical branding (`Next.js`, not `NextJS`); keep the same register across the list." (https://vercel.com/geist/select) "Label is a short Title Case noun (`Framework`, `Region`); accept the prop directly on `<Select>`." (https://vercel.com/geist/select) "Placeholder is action-oriented (`Select a framework`), never `Choose one…`, `Pick`, or the label restated." (https://vercel.com/geist/select) Ours: options are parallel phrases in sentence case (*Every hour*, *Every weekday*), the label is a short noun in sentence case, and the trigger never says *Select…* or *Choose one*.',
|
|
554
554
|
access: "A visible label tied to the trigger by `id`, or `aria-label` or `aria-labelledby`. Listbox and option roles from Base UI; focus returns to the trigger on close. The prefix and suffix are decorative and not part of the trigger's name.",
|
|
555
555
|
},
|
|
@@ -813,9 +813,9 @@ export const USAGE = [
|
|
|
813
813
|
summary: "One Agent in a list of them: the orb, the name, and the line that says what the Agent is doing now or what it is for.",
|
|
814
814
|
when: "Nadia's Agents in her window's sidebar, and a website scene that shows a list of Agents.",
|
|
815
815
|
instead: "An Agent as a record among others in a page's list is a RecordList row with an agent IdentityMark; an Agent's own page heads with ObjectPage.",
|
|
816
|
-
behaviour: "It is the row's presentation alone: the consumer owns the control around it (Nadia's button that opens the Agent, a scene's list item that opens nothing). The current row's name is set in 500.
|
|
817
|
-
words: 'The name, then one state line: what she is doing now ("Answering on Telegram", "Working") or what she is for ("Sorts requests").
|
|
818
|
-
access: "The orb is decorative; the name and the state line are the row's text, read by its control.
|
|
816
|
+
behaviour: "It is the row's presentation alone: the consumer owns the control around it (Nadia's button that opens the Agent, a scene's list item that opens nothing). The current row's name is set in 500. The orb carries the Agent's state, working, waiting on the person or blocked, so the state line is plain muted text and never a badge. Narrow, the orb stands alone and the words stay for a screen reader; the consumer names it with its state line in a tooltip, as Nadia's list does. Only the orb moves in the list. The name is 14 px and the state line 12 px at every size. The drawn orb leaves about 10 px of its 64 px box empty, so at 64 px that empty edge overlaps the row's padding (6 px above and below, 4 px at the start) and the words stand 4 px from the box: the row hugs the visible orb and the name sits beside it, as beside a solid picture. In Nadia's list her orb is 64 px in her real state, and 40 px in the narrow column from 640 to 899 px.",
|
|
817
|
+
words: 'The name, then one state line: what she is doing now ("Answering on Telegram", "Working") or what she is for ("Sorts requests"). Nadia\'s list says "Working", "Idle" or "Needs you".',
|
|
818
|
+
access: "The orb is decorative; the name and the state line are the row's text, read by its control.",
|
|
819
819
|
},
|
|
820
820
|
{
|
|
821
821
|
name: "Breadcrumb",
|
|
@@ -1394,11 +1394,11 @@ export const USAGE = [
|
|
|
1394
1394
|
module: "waiting-line",
|
|
1395
1395
|
family: "Feedback",
|
|
1396
1396
|
summary: "The one moving thing a system wait may show: a 2 px ink line, after a second.",
|
|
1397
|
-
when: "A system wait the person asked for that runs past a second: a save, a connection, a download without a count.",
|
|
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
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.",
|
|
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
|
-
access: 'With a label: `role="status"`, announced once after the second. `decorative` when
|
|
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
|
},
|
|
1403
1403
|
{
|
|
1404
1404
|
name: "AccountMenu",
|
|
@@ -94,6 +94,43 @@
|
|
|
94
94
|
box-shadow: var(--nc-elevation-modal);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/*
|
|
98
|
+
* Small state changes share one cadence instead of component-local defaults
|
|
99
|
+
* (design-system DESIGN.md "Motion"). Declared as utilities, beside the
|
|
100
|
+
* elevation rungs, so a host that takes this file and not the design system's
|
|
101
|
+
* whole stylesheet still runs them, and variant prefixes can gate them.
|
|
102
|
+
*/
|
|
103
|
+
@utility nc-transition-paint {
|
|
104
|
+
transition-property: background-color, border-color, color, opacity;
|
|
105
|
+
transition-duration: var(--nc-duration-confirm);
|
|
106
|
+
transition-timing-function: var(--nc-ease-out-strong);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@utility nc-transition-opacity {
|
|
110
|
+
transition-property: opacity;
|
|
111
|
+
transition-duration: var(--nc-duration-confirm);
|
|
112
|
+
transition-timing-function: var(--nc-ease-out-strong);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@utility nc-transition-transform {
|
|
116
|
+
transition-property: transform, translate, scale;
|
|
117
|
+
transition-duration: var(--nc-duration-confirm);
|
|
118
|
+
transition-timing-function: var(--nc-ease-out-strong);
|
|
119
|
+
|
|
120
|
+
@media (prefers-reduced-motion: reduce) {
|
|
121
|
+
transition: none;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* A list that scrolls under its own fade or edge, such as Command's and Combobox's, draws no scrollbar. */
|
|
126
|
+
@utility no-scrollbar {
|
|
127
|
+
scrollbar-width: none;
|
|
128
|
+
|
|
129
|
+
&::-webkit-scrollbar {
|
|
130
|
+
display: none;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
97
134
|
/*
|
|
98
135
|
* The proxy focus outline: the one restatement of the canonical treatment
|
|
99
136
|
* above, for an element that carries the indicator on another element's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nadicodeai/ui",
|
|
3
|
-
"version": "12.1.
|
|
3
|
+
"version": "12.1.132",
|
|
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.
|
|
86
|
+
"@nadicodeai/design-system": "12.1.132",
|
|
87
87
|
"@tanstack/react-table": "^9.3.1",
|
|
88
88
|
"@vgpu/wgsl": "0.6.0",
|
|
89
89
|
"class-variance-authority": "^0.7.1",
|