@actioneer/ads 0.2.3 → 0.2.4

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.
@@ -0,0 +1,43 @@
1
+ import * as React from "react";
2
+ /**
3
+ * CommandPalette — the global-search / command surface (⌘K pattern).
4
+ *
5
+ * A portaled, top-anchored dialog with a search input and a grouped,
6
+ * keyboard-navigable result list. Deliberately NOT animated: the motion
7
+ * principles class a command palette as a frequent, keyboard-initiated
8
+ * action ("frequent = fast or none"), so it appears and dismisses
9
+ * instantly.
10
+ *
11
+ * Fully props-driven: the consumer supplies flat `items` (each optionally
12
+ * tagged with a `group`) and receives `onSelect`; filtering is a simple
13
+ * case-insensitive substring match over label + keywords, overridable via
14
+ * `filter`.
15
+ */
16
+ export interface CommandPaletteItem {
17
+ id: string;
18
+ /** Primary label — also the default match text. */
19
+ label: string;
20
+ /** Section heading the item is listed under. Ungrouped items come first. */
21
+ group?: string;
22
+ /** Leading icon slot (sized to the row's text). */
23
+ icon?: React.ReactNode;
24
+ /** Muted right-aligned hint (a path, type, or shortcut). */
25
+ hint?: string;
26
+ /** Extra text the filter matches against (synonyms, paths). */
27
+ keywords?: string;
28
+ }
29
+ export interface CommandPaletteProps {
30
+ open: boolean;
31
+ onOpenChange: (open: boolean) => void;
32
+ items: readonly CommandPaletteItem[];
33
+ /** Called with the chosen item; the palette closes itself afterwards. */
34
+ onSelect: (item: CommandPaletteItem) => void;
35
+ placeholder?: string;
36
+ emptyLabel?: string;
37
+ /** Accessible name for the dialog. */
38
+ "aria-label"?: string;
39
+ /** Override the match predicate (query is already lowercased). */
40
+ filter?: (item: CommandPaletteItem, query: string) => boolean;
41
+ className?: string;
42
+ }
43
+ export declare function CommandPalette({ open, onOpenChange, items, onSelect, placeholder, emptyLabel, "aria-label": ariaLabel, filter, className, }: CommandPaletteProps): React.ReactPortal | null;
@@ -0,0 +1,146 @@
1
+ "use client";
2
+ import { jsxs as d, jsx as s } from "react/jsx-runtime";
3
+ import * as o from "react";
4
+ import { createPortal as S } from "react-dom";
5
+ import { Search as q } from "../lib/icons.js";
6
+ import { cn as k } from "../lib/cn.js";
7
+ const C = (a, i) => `${a.label} ${a.keywords ?? ""}`.toLowerCase().includes(i);
8
+ function _({
9
+ open: a,
10
+ onOpenChange: i,
11
+ items: p,
12
+ onSelect: R,
13
+ placeholder: $ = "Search…",
14
+ emptyLabel: A = "No results",
15
+ "aria-label": D = "Global search",
16
+ filter: h = C,
17
+ className: E
18
+ }) {
19
+ const f = o.useId(), v = o.useRef(null), y = o.useRef(null), g = o.useRef(null), [m, b] = o.useState(""), [c, x] = o.useState(0), n = o.useMemo(() => {
20
+ const e = m.trim().toLowerCase();
21
+ return e ? p.filter((r) => h(r, e)) : [...p];
22
+ }, [p, m, h]), M = o.useMemo(() => {
23
+ const e = [], r = /* @__PURE__ */ new Map();
24
+ for (const t of n)
25
+ r.has(t.group) || (r.set(t.group, []), e.push(t.group)), r.get(t.group).push(t);
26
+ return e.map((t) => ({ group: t, items: r.get(t) }));
27
+ }, [n]), u = n[c];
28
+ o.useEffect(() => {
29
+ x(0);
30
+ }, [m, a]), o.useEffect(() => {
31
+ if (!a) return;
32
+ g.current = document.activeElement, b("");
33
+ const e = document.body.style.overflow;
34
+ document.body.style.overflow = "hidden";
35
+ const r = requestAnimationFrame(() => {
36
+ var t;
37
+ return (t = v.current) == null ? void 0 : t.focus();
38
+ });
39
+ return () => {
40
+ var t, l;
41
+ document.body.style.overflow = e, cancelAnimationFrame(r), (l = (t = g.current) == null ? void 0 : t.focus) == null || l.call(t);
42
+ };
43
+ }, [a]), o.useEffect(() => {
44
+ var e, r;
45
+ !a || !u || (r = (e = y.current) == null ? void 0 : e.querySelector(`[data-index="${c}"]`)) == null || r.scrollIntoView({ block: "nearest" });
46
+ }, [a, u, c]);
47
+ const w = (e) => {
48
+ i(!1), R(e);
49
+ }, I = (e) => {
50
+ e.key === "Escape" ? (e.preventDefault(), i(!1)) : e.key === "ArrowDown" ? (e.preventDefault(), x((r) => Math.min(r + 1, n.length - 1))) : e.key === "ArrowUp" ? (e.preventDefault(), x((r) => Math.max(r - 1, 0))) : e.key === "Enter" && u && (e.preventDefault(), w(u));
51
+ };
52
+ return a ? S(
53
+ /* @__PURE__ */ d("div", { className: "fixed inset-0 z-50 flex items-start justify-center px-4 pt-[15vh]", children: [
54
+ /* @__PURE__ */ s(
55
+ "div",
56
+ {
57
+ "aria-hidden": !0,
58
+ className: "absolute inset-0 bg-overlay",
59
+ onClick: () => i(!1)
60
+ }
61
+ ),
62
+ /* @__PURE__ */ d(
63
+ "div",
64
+ {
65
+ role: "dialog",
66
+ "aria-modal": "true",
67
+ "aria-label": D,
68
+ className: k(
69
+ "relative flex w-full max-w-lg flex-col overflow-hidden",
70
+ "rounded-xl bg-elevated text-foreground shadow-elevation",
71
+ E
72
+ ),
73
+ onKeyDown: I,
74
+ children: [
75
+ /* @__PURE__ */ d("div", { className: "flex items-center gap-2.5 px-4 py-3", children: [
76
+ /* @__PURE__ */ s(q, { "aria-hidden": !0, className: "size-4 shrink-0 text-muted-foreground" }),
77
+ /* @__PURE__ */ s(
78
+ "input",
79
+ {
80
+ ref: v,
81
+ role: "combobox",
82
+ "aria-expanded": "true",
83
+ "aria-controls": f,
84
+ "aria-activedescendant": u ? `${f}-${c}` : void 0,
85
+ "aria-autocomplete": "list",
86
+ value: m,
87
+ onChange: (e) => b(e.target.value),
88
+ placeholder: $,
89
+ className: "w-full bg-transparent text-base text-foreground outline-none placeholder:text-muted-foreground"
90
+ }
91
+ )
92
+ ] }),
93
+ /* @__PURE__ */ s(
94
+ "div",
95
+ {
96
+ ref: y,
97
+ id: f,
98
+ role: "listbox",
99
+ "aria-label": "Results",
100
+ className: "max-h-80 overflow-y-auto p-1.5 pt-0",
101
+ children: n.length === 0 ? /* @__PURE__ */ s("p", { className: "px-2.5 py-6 text-center text-xs text-muted-foreground", children: A }) : M.map(({ group: e, items: r }) => /* @__PURE__ */ d("div", { role: "group", "aria-label": e, children: [
102
+ e && /* @__PURE__ */ s("div", { className: "px-2.5 pb-1 pt-2.5 text-xs font-medium text-muted-foreground select-none", children: e }),
103
+ r.map((t) => {
104
+ const l = n.indexOf(t), N = l === c;
105
+ return /* @__PURE__ */ d(
106
+ "div",
107
+ {
108
+ id: `${f}-${l}`,
109
+ "data-index": l,
110
+ role: "option",
111
+ "aria-selected": N,
112
+ onPointerMove: () => x(l),
113
+ onClick: () => w(t),
114
+ className: k(
115
+ "flex cursor-default items-center gap-2 rounded-md px-2.5 py-1.5 text-xs",
116
+ N ? "bg-fill text-foreground" : "text-secondary"
117
+ ),
118
+ children: [
119
+ t.icon && /* @__PURE__ */ s(
120
+ "span",
121
+ {
122
+ "aria-hidden": !0,
123
+ className: "flex shrink-0 items-center text-muted-foreground [&_svg]:size-4",
124
+ children: t.icon
125
+ }
126
+ ),
127
+ /* @__PURE__ */ s("span", { className: "min-w-0 flex-1 truncate", children: t.label }),
128
+ t.hint && /* @__PURE__ */ s("span", { className: "shrink-0 text-xs text-tertiary", children: t.hint })
129
+ ]
130
+ },
131
+ t.id
132
+ );
133
+ })
134
+ ] }, e ?? "__ungrouped"))
135
+ }
136
+ )
137
+ ]
138
+ }
139
+ )
140
+ ] }),
141
+ document.body
142
+ ) : null;
143
+ }
144
+ export {
145
+ _ as CommandPalette
146
+ };
@@ -20,6 +20,7 @@ export * from './autocomplete';
20
20
  export * from './slider';
21
21
  export * from './segmented';
22
22
  export * from './modal';
23
+ export * from './command-palette';
23
24
  export * from './drawer';
24
25
  export * from './popover';
25
26
  export * from './tooltip';