@nadicodeai/ui 12.1.117 → 12.1.119

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.
@@ -67,6 +67,10 @@ export declare function PageActionsRow({ period, primary, secondary, menu }: Pag
67
67
  * Holds the place on the title's row where the open page puts its actions. The
68
68
  * product's chrome draws the row (`PageHeader`) and the page below draws its
69
69
  * commands (`PageActions`), so the two meet here rather than through props.
70
+ *
71
+ * The context's value never changes: a changed context value above a page whose
72
+ * server HTML is still streaming makes React render that page again in the
73
+ * browser, beside the server's copy.
70
74
  */
71
75
  export declare function PageActionsHostProvider({ children }: {
72
76
  children: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"page-actions.d.ts","sourceRoot":"","sources":["../../src/components/page-actions.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"page-actions.d.ts","sourceRoot":"","sources":["../../src/components/page-actions.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AAIf,OAAO,EAAc,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAEpE,MAAM,MAAM,iBAAiB,GACzB;IAAE,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE;AACrF;;;GAGG;GACD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE;AACzD;;;GAGG;GACD;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,IAAI,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAE5D,uGAAuG;AACvG,MAAM,MAAM,WAAW,GACnB;IACE,kGAAkG;IAClG,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;CACtB,GACD;IAAE,OAAO,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEvE,4GAA4G;AAC5G,MAAM,MAAM,WAAW,GAAG,iBAAiB,GACzC,WAAW,GAAG;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,oGAAoG;IACpG,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC,CAAC;AAEJ,uEAAuE;AACvE,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,GAAG;IACpD,QAAQ,EAAE,SAAS,CAAC,WAAW,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAAC,CAAC;CACjE,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qGAAqG;IACrG,MAAM,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACpC,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAClC,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,KAAK,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/D,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,KAAK,EACL,IAAI,EACJ,OAAO,GACR,EAAE;IACD,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,cAAc,CAAC;CACzB,+BA+BA;AAED,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,gBAAgB,sCAUpF;AAwBD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAG5E;AAED,yFAAyF;AACzF,wBAAgB,eAAe,gCAS9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,sCAyBlD"}
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { ArrowUpRight } from "lucide-react";
4
- import { createContext, useCallback, useContext, useEffect, useId, useState, } from "react";
4
+ import { createContext, useContext, useEffect, useId, useState, } from "react";
5
5
  import { createPortal } from "react-dom";
6
+ import { createStore, useStore } from "zustand";
6
7
  import { ActionMenu } from "./action-menu.js";
7
8
  import { Button, ButtonLink } from "./button.js";
8
9
  export function PageCommandButton({ command, shape, size, variant, }) {
@@ -31,23 +32,34 @@ export function PageActionsRow({ period, primary, secondary, menu }) {
31
32
  return null;
32
33
  return (_jsxs("div", { "data-slot": "page-actions", className: "flex max-w-full min-w-0 flex-wrap items-center gap-2", children: [period, secondary ? _jsx(PageCommandButton, { command: secondary, variant: "outline" }) : null, primary ? _jsx(PageCommandButton, { command: primary, variant: "default" }) : null, menu ? _jsx(ActionMenu, { ...menu }) : null] }));
33
34
  }
35
+ function createHost() {
36
+ return createStore((set) => ({
37
+ element: null,
38
+ owner: null,
39
+ attach: (element) => set({ element }),
40
+ claim: (owner) => set({ owner }),
41
+ release: (owner) => set((state) => (state.owner === owner ? { owner: null } : state)),
42
+ }));
43
+ }
44
+ const unhosted = createHost();
34
45
  const PageActionsHostContext = createContext(null);
35
46
  /**
36
47
  * Holds the place on the title's row where the open page puts its actions. The
37
48
  * product's chrome draws the row (`PageHeader`) and the page below draws its
38
49
  * commands (`PageActions`), so the two meet here rather than through props.
50
+ *
51
+ * The context's value never changes: a changed context value above a page whose
52
+ * server HTML is still streaming makes React render that page again in the
53
+ * browser, beside the server's copy.
39
54
  */
40
55
  export function PageActionsHostProvider({ children }) {
41
- const [element, attach] = useState(null);
42
- const [owner, setOwner] = useState(null);
43
- const claim = useCallback((next) => setOwner(next), []);
44
- const release = useCallback((previous) => setOwner((current) => (current === previous ? null : current)), []);
45
- return (_jsx(PageActionsHostContext, { value: { element, attach, owner, claim, release }, children: children }));
56
+ const [host] = useState(createHost);
57
+ return _jsx(PageActionsHostContext, { value: host, children: children });
46
58
  }
47
59
  /** The element on `PageHeader`'s title row that the page's `PageActions` render into. */
48
60
  export function PageActionsSlot() {
49
61
  const host = useContext(PageActionsHostContext);
50
- return (_jsx("div", { "data-slot": "page-header-actions", className: "flex max-w-full min-w-0 items-center empty:hidden", ref: host?.attach }));
62
+ return (_jsx("div", { "data-slot": "page-header-actions", className: "flex max-w-full min-w-0 items-center empty:hidden", ref: host?.getState().attach }));
51
63
  }
52
64
  /**
53
65
  * A page's actions, drawn on its title's row. Only the page shown last owns
@@ -64,8 +76,10 @@ export function PageActionsSlot() {
64
76
  export function PageActions(props) {
65
77
  const host = useContext(PageActionsHostContext);
66
78
  const id = useId();
67
- const claim = host?.claim;
68
- const release = host?.release;
79
+ const element = useStore(host ?? unhosted, (state) => state.element);
80
+ const owner = useStore(host ?? unhosted, (state) => state.owner);
81
+ const claim = host?.getState().claim;
82
+ const release = host?.getState().release;
69
83
  const hasActions = Boolean(props.period || props.primary || props.secondary || props.menu);
70
84
  useEffect(() => {
71
85
  if (!hasActions)
@@ -76,7 +90,7 @@ export function PageActions(props) {
76
90
  if (!host) {
77
91
  return (_jsx("div", { className: "flex justify-end empty:hidden", children: _jsx(PageActionsRow, { ...props }) }));
78
92
  }
79
- if (!host.element || host.owner !== id)
93
+ if (!element || owner !== id)
80
94
  return null;
81
- return createPortal(_jsx(PageActionsRow, { ...props }), host.element);
95
+ return createPortal(_jsx(PageActionsRow, { ...props }), element);
82
96
  }
@@ -4,6 +4,7 @@ export type ScopeOption = {
4
4
  label: string;
5
5
  group?: string;
6
6
  keywords?: readonly string[];
7
+ mark?: React.ReactNode;
7
8
  };
8
9
  export type ScopeSwitcherCopy = {
9
10
  label: string;
@@ -22,16 +23,17 @@ export type ScopeSwitcherProps = {
22
23
  label: string;
23
24
  onSelect: () => void;
24
25
  };
26
+ variant?: "scope" | "navigation";
25
27
  };
26
28
  /**
27
29
  * Says which part of the account a page reads, and changes it.
28
30
  *
29
31
  * @family Navigation
30
- * @when Changing which part of the account a page reads, such as Analytics' scope: the whole fleet, one partner, one customer. Only when the viewer has more than one.
32
+ * @when Changing which part of the account a page reads, such as Analytics' scope, or switching between objects from the shell's top bar with `variant="navigation"`.
31
33
  * @instead A viewer with one scope and nothing to create reads its name with no control, which this draws by itself. Finding anything is `CommandDialog`; narrowing rows is `CollectionFilters`.
32
- * @behaviour A ghost trigger, at most 16rem, opens a 288 px popover with focus in its search. Type to filter, Enter chooses and closes; choosing the current one changes nothing. The caller writes the choice to the URL in `onValueChange`, so sibling pages keep it. An optional create command sits last.
34
+ * @behaviour A ghost trigger, at most 16rem, opens a 288 px popover with focus in its search. Navigation omits the visible prefix, carries each option's identity mark and opens a 384 px popover constrained to the viewport. Type to filter, Enter chooses and closes; choosing the current one changes nothing. The caller writes the choice to the URL in `onValueChange`, so sibling pages keep it. An optional create command sits last.
33
35
  * @words `copy.label` names what is chosen ("Scope") beside the current scope's name. Search and empty lines name the thing: "Find a scope", "No scope matches".
34
36
  * @access The trigger reads "Scope: All customers"; the popover has a hidden title; options are a listbox with the current one checked.
35
37
  */
36
- export declare function ScopeSwitcher({ value, options, onValueChange, copy, create }: ScopeSwitcherProps): React.JSX.Element;
38
+ export declare function ScopeSwitcher({ value, options, onValueChange, copy, create, variant, }: ScopeSwitcherProps): React.JSX.Element;
37
39
  //# sourceMappingURL=scope-switcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope-switcher.d.ts","sourceRoot":"","sources":["../../src/components/scope-switcher.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IAChC,8DAA8D;IAC9D,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,IAAI,EAAE,iBAAiB,CAAC;IACxB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC;CAClD,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,kBAAkB,qBA6FhG"}
1
+ {"version":3,"file":"scope-switcher.d.ts","sourceRoot":"","sources":["../../src/components/scope-switcher.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAgB/B,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IAChC,8DAA8D;IAC9D,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,IAAI,EAAE,iBAAiB,CAAC;IACxB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC;IACjD,OAAO,CAAC,EAAE,OAAO,GAAG,YAAY,CAAC;CAClC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,EACP,aAAa,EACb,IAAI,EACJ,MAAM,EACN,OAAiB,GAClB,EAAE,kBAAkB,qBA0GpB"}
@@ -5,20 +5,22 @@ import { ChevronsUpDown, Plus } from "lucide-react";
5
5
  import { Button } from "./button.js";
6
6
  import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "./command.js";
7
7
  import { Popover, PopoverContent, PopoverTitle, PopoverTrigger } from "./popover.js";
8
+ import { cn } from "../lib/utils.js";
8
9
  /**
9
10
  * Says which part of the account a page reads, and changes it.
10
11
  *
11
12
  * @family Navigation
12
- * @when Changing which part of the account a page reads, such as Analytics' scope: the whole fleet, one partner, one customer. Only when the viewer has more than one.
13
+ * @when Changing which part of the account a page reads, such as Analytics' scope, or switching between objects from the shell's top bar with `variant="navigation"`.
13
14
  * @instead A viewer with one scope and nothing to create reads its name with no control, which this draws by itself. Finding anything is `CommandDialog`; narrowing rows is `CollectionFilters`.
14
- * @behaviour A ghost trigger, at most 16rem, opens a 288 px popover with focus in its search. Type to filter, Enter chooses and closes; choosing the current one changes nothing. The caller writes the choice to the URL in `onValueChange`, so sibling pages keep it. An optional create command sits last.
15
+ * @behaviour A ghost trigger, at most 16rem, opens a 288 px popover with focus in its search. Navigation omits the visible prefix, carries each option's identity mark and opens a 384 px popover constrained to the viewport. Type to filter, Enter chooses and closes; choosing the current one changes nothing. The caller writes the choice to the URL in `onValueChange`, so sibling pages keep it. An optional create command sits last.
15
16
  * @words `copy.label` names what is chosen ("Scope") beside the current scope's name. Search and empty lines name the thing: "Find a scope", "No scope matches".
16
17
  * @access The trigger reads "Scope: All customers"; the popover has a hidden title; options are a listbox with the current one checked.
17
18
  */
18
- export function ScopeSwitcher({ value, options, onValueChange, copy, create }) {
19
+ export function ScopeSwitcher({ value, options, onValueChange, copy, create, variant = "scope", }) {
19
20
  const [open, setOpen] = React.useState(false);
20
21
  const search = React.useRef(null);
21
- const currentLabel = options.find((option) => option.value === value)?.label ?? value;
22
+ const current = options.find((option) => option.value === value);
23
+ const currentLabel = current?.label ?? value;
22
24
  const groups = React.useMemo(() => {
23
25
  const byGroup = new Map();
24
26
  for (const option of options) {
@@ -34,12 +36,12 @@ export function ScopeSwitcher({ value, options, onValueChange, copy, create }) {
34
36
  onValueChange(next);
35
37
  };
36
38
  const switchable = options.length > 1 || create !== undefined;
37
- return (_jsxs("span", { "data-slot": "scope-switcher", className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { "aria-hidden": switchable ? "true" : undefined, className: "text-xs font-medium text-muted-foreground", children: copy.label }), switchable ? (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { variant: "ghost", className: "max-w-64", suffix: _jsx(ChevronsUpDown, { "aria-hidden": "true" }) }), children: [_jsxs("span", { className: "sr-only", children: [copy.label, ":"] }), " ", _jsx("span", { className: "truncate", children: currentLabel })] }), _jsxs(PopoverContent, { align: "start", className: "w-72 p-0", initialFocus: search, children: [_jsx(PopoverTitle, { className: "sr-only", children: copy.label }), _jsxs(Command, { label: copy.search, resultCount: copy.results, children: [_jsx(CommandInput, { ref: search, "aria-label": copy.search, placeholder: copy.search }), _jsxs(CommandList, { label: copy.label, children: [_jsx(CommandEmpty, { children: copy.empty }), groups.map(([group, items]) => (_jsx(CommandGroup, { heading: group, children: items.map((option) => (_jsx(CommandItem, { value: option.value, keywords: [
39
+ return (_jsxs("span", { "data-slot": "scope-switcher", className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { "aria-hidden": switchable ? "true" : undefined, className: cn("text-xs font-medium text-muted-foreground", variant === "navigation" && "sr-only"), children: copy.label }), switchable ? (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { variant: "ghost", className: "min-w-0 max-w-64 shrink", prefix: current?.mark ? _jsx("span", { "aria-hidden": "true", children: current.mark }) : undefined, suffix: _jsx(ChevronsUpDown, { "aria-hidden": "true" }) }), children: [_jsxs("span", { className: "sr-only", children: [copy.label, ":"] }), " ", _jsx("span", { className: "truncate", children: currentLabel })] }), _jsxs(PopoverContent, { align: "start", className: cn("w-72 max-w-[calc(100vw-2rem)] p-0", variant === "navigation" && "w-96"), initialFocus: search, children: [_jsx(PopoverTitle, { className: "sr-only", children: copy.label }), _jsxs(Command, { label: copy.search, resultCount: copy.results, children: [_jsx(CommandInput, { ref: search, "aria-label": copy.search, placeholder: copy.search }), _jsxs(CommandList, { label: copy.label, children: [_jsx(CommandEmpty, { children: copy.empty }), groups.map(([group, items]) => (_jsx(CommandGroup, { heading: group, children: items.map((option) => (_jsxs(CommandItem, { value: option.value, keywords: [
38
40
  option.label,
39
41
  ...(option.group ? [option.group] : []),
40
42
  ...(option.keywords ?? []),
41
- ], "data-checked": option.value === value, onSelect: () => choose(option.value), children: _jsx("span", { className: "truncate", children: option.label }) }, option.value))) }, group ?? ""))), create ? (_jsxs(_Fragment, { children: [_jsx(CommandSeparator, { alwaysRender: true }), _jsx(CommandGroup, { forceMount: true, heading: undefined, children: _jsxs(CommandItem, { value: `create ${create.label}`, forceMount: true, onSelect: () => {
43
+ ], "data-checked": option.value === value, onSelect: () => choose(option.value), children: [option.mark ? _jsx("span", { "aria-hidden": "true", children: option.mark }) : null, _jsx("span", { className: "truncate", children: option.label })] }, option.value))) }, group ?? ""))), create ? (_jsxs(_Fragment, { children: [_jsx(CommandSeparator, { alwaysRender: true }), _jsx(CommandGroup, { forceMount: true, heading: undefined, children: _jsxs(CommandItem, { value: `create ${create.label}`, forceMount: true, onSelect: () => {
42
44
  setOpen(false);
43
45
  create.onSelect();
44
- }, children: [_jsx(Plus, { "aria-hidden": "true" }), create.label] }) })] })) : null] })] })] })] })) : (_jsx("span", { className: "truncate px-3 text-sm", children: currentLabel }))] }));
46
+ }, children: [_jsx(Plus, { "aria-hidden": "true" }), create.label] }) })] })) : null] })] })] })] })) : (_jsxs("span", { className: "flex min-w-0 items-center gap-2 px-3 text-sm", children: [current?.mark ? _jsx("span", { "aria-hidden": "true", children: current.mark }) : null, _jsx("span", { className: "truncate", children: currentLabel })] }))] }));
45
47
  }
@@ -866,9 +866,9 @@ export const USAGE = [
866
866
  module: "scope-switcher",
867
867
  family: "Navigation",
868
868
  summary: "Says which part of the account a page reads, and changes it.",
869
- when: "Changing which part of the account a page reads, such as Analytics' scope: the whole fleet, one partner, one customer. Only when the viewer has more than one.",
869
+ when: "Changing which part of the account a page reads, such as Analytics' scope, or switching between objects from the shell's top bar with `variant=\"navigation\"`.",
870
870
  instead: "A viewer with one scope and nothing to create reads its name with no control, which this draws by itself. Finding anything is `CommandDialog`; narrowing rows is `CollectionFilters`.",
871
- behaviour: "A ghost trigger, at most 16rem, opens a 288 px popover with focus in its search. Type to filter, Enter chooses and closes; choosing the current one changes nothing. The caller writes the choice to the URL in `onValueChange`, so sibling pages keep it. An optional create command sits last.",
871
+ behaviour: "A ghost trigger, at most 16rem, opens a 288 px popover with focus in its search. Navigation omits the visible prefix, carries each option's identity mark and opens a 384 px popover constrained to the viewport. Type to filter, Enter chooses and closes; choosing the current one changes nothing. The caller writes the choice to the URL in `onValueChange`, so sibling pages keep it. An optional create command sits last.",
872
872
  words: '`copy.label` names what is chosen ("Scope") beside the current scope\'s name. Search and empty lines name the thing: "Find a scope", "No scope matches".',
873
873
  access: 'The trigger reads "Scope: All customers"; the popover has a hidden title; options are a listbox with the current one checked.',
874
874
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nadicodeai/ui",
3
- "version": "12.1.117",
3
+ "version": "12.1.119",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -67,7 +67,8 @@
67
67
  "generate:merge-tokens": "node scripts/generate-merge-tokens.mjs",
68
68
  "generate:usage": "node scripts/generate-usage.mjs",
69
69
  "prepack": "npm run build",
70
- "lint": "node scripts/generate-merge-tokens.mjs --check && node scripts/generate-usage.mjs --check && node ../../tools/workspace/heavy-slots.mjs -- eslint --max-warnings=0",
70
+ "lint": "npm run lint:oxlint && node scripts/generate-merge-tokens.mjs --check && node scripts/generate-usage.mjs --check && node ../../tools/workspace/heavy-slots.mjs -- eslint --max-warnings=0",
71
+ "lint:oxlint": "node ../../tools/workspace/heavy-slots.mjs -- oxlint",
71
72
  "typecheck": "node ../../tools/workspace/heavy-slots.mjs -- node ../../node_modules/typescript-native/bin/tsc --checkers 2 --noEmit",
72
73
  "check": "npm run lint && npm run typecheck",
73
74
  "check:static": "npm run check",
@@ -77,12 +78,12 @@
77
78
  "test:distribution": "node ../../tools/release/package-smoke-test.mjs"
78
79
  },
79
80
  "dependencies": {
80
- "@assistant-ui/react": "0.15.28",
81
- "@assistant-ui/react-markdown": "0.14.22",
81
+ "@assistant-ui/react": "0.15.29",
82
+ "@assistant-ui/react-markdown": "0.14.23",
82
83
  "@base-ui/react": "^1.9.0",
83
84
  "@iconify-icons/logos": "^2.0.3",
84
85
  "@iconify/react": "^6.0.2",
85
- "@nadicodeai/design-system": "12.1.117",
86
+ "@nadicodeai/design-system": "12.1.119",
86
87
  "@tanstack/react-table": "^9.3.1",
87
88
  "@vgpu/wgsl": "0.6.0",
88
89
  "class-variance-authority": "^0.7.1",
@@ -92,7 +93,7 @@
92
93
  "embla-carousel-react": "^8.6.0",
93
94
  "input-otp": "^1.5.0",
94
95
  "lucide-react": "1.57.0",
95
- "motion": "^13.4.4",
96
+ "motion": "^14.3.0",
96
97
  "next-themes": "^0.4.6",
97
98
  "qrcode": "^1.5.4",
98
99
  "react-day-picker": "^10.0.2",
@@ -115,7 +116,10 @@
115
116
  "devDependencies": {
116
117
  "@arethetypeswrong/cli": "^0.18.5",
117
118
  "@lobehub/icons-static-svg": "^1.95.1",
119
+ "@nadicodeai/eslint-config": "*",
118
120
  "@nadicodeai/eslint-plugin": "*",
121
+ "@nadicodeai/oxlint-config": "*",
122
+ "@nadicodeai/test-support": "*",
119
123
  "@stryker-mutator/core": "10.0.0",
120
124
  "@stryker-mutator/vitest-runner": "10.0.0",
121
125
  "@tailwindcss/postcss": "^4.3.3",
@@ -127,6 +131,7 @@
127
131
  "esbuild": "^0.28.2",
128
132
  "eslint": "^9.39.4",
129
133
  "jsdom": "^30.1.3",
134
+ "oxlint": "1.88.0",
130
135
  "postcss": "^8.5.30",
131
136
  "publint": "^0.3.25",
132
137
  "react": "19.3.1",
@@ -135,6 +140,6 @@
135
140
  "tailwindcss": "^4.3.3",
136
141
  "typescript": "^6",
137
142
  "typescript-native": "npm:typescript@7.0.2",
138
- "vitest": "^5.0.0"
143
+ "vitest": "^5.0.5"
139
144
  }
140
145
  }