@flanksource/clicky-ui 0.2.3 → 0.2.5

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.
Files changed (178) hide show
  1. package/dist/components/Combobox.cjs +291 -0
  2. package/dist/components/Combobox.cjs.map +1 -0
  3. package/dist/components/Combobox.d.ts +59 -0
  4. package/dist/components/Combobox.d.ts.map +1 -0
  5. package/dist/components/Combobox.js +291 -0
  6. package/dist/components/Combobox.js.map +1 -0
  7. package/dist/components/FilterBar.cjs +153 -124
  8. package/dist/components/FilterBar.cjs.map +1 -1
  9. package/dist/components/FilterBar.d.ts +25 -1
  10. package/dist/components/FilterBar.d.ts.map +1 -1
  11. package/dist/components/FilterBar.js +153 -124
  12. package/dist/components/FilterBar.js.map +1 -1
  13. package/dist/components/FormatOptionsDropdown.cjs +62 -0
  14. package/dist/components/FormatOptionsDropdown.cjs.map +1 -0
  15. package/dist/components/FormatOptionsDropdown.d.ts +37 -0
  16. package/dist/components/FormatOptionsDropdown.d.ts.map +1 -0
  17. package/dist/components/FormatOptionsDropdown.js +62 -0
  18. package/dist/components/FormatOptionsDropdown.js.map +1 -0
  19. package/dist/components/SplitButton.cjs +87 -0
  20. package/dist/components/SplitButton.cjs.map +1 -0
  21. package/dist/components/SplitButton.d.ts +32 -0
  22. package/dist/components/SplitButton.d.ts.map +1 -0
  23. package/dist/components/SplitButton.js +87 -0
  24. package/dist/components/SplitButton.js.map +1 -0
  25. package/dist/components/icon-menu-picker.cjs +14 -3
  26. package/dist/components/icon-menu-picker.cjs.map +1 -1
  27. package/dist/components/icon-menu-picker.d.ts.map +1 -1
  28. package/dist/components/icon-menu-picker.js +15 -4
  29. package/dist/components/icon-menu-picker.js.map +1 -1
  30. package/dist/components.cjs +7 -0
  31. package/dist/components.cjs.map +1 -1
  32. package/dist/components.d.ts +3 -0
  33. package/dist/components.d.ts.map +1 -1
  34. package/dist/components.js +7 -0
  35. package/dist/components.js.map +1 -1
  36. package/dist/data/Badge.d.ts +1 -1
  37. package/dist/data/ProgressBar.cjs +4 -2
  38. package/dist/data/ProgressBar.cjs.map +1 -1
  39. package/dist/data/ProgressBar.d.ts +1 -1
  40. package/dist/data/ProgressBar.d.ts.map +1 -1
  41. package/dist/data/ProgressBar.js +4 -2
  42. package/dist/data/ProgressBar.js.map +1 -1
  43. package/dist/data/TaskManager.cjs +162 -0
  44. package/dist/data/TaskManager.cjs.map +1 -0
  45. package/dist/data/TaskManager.d.ts +21 -0
  46. package/dist/data/TaskManager.d.ts.map +1 -0
  47. package/dist/data/TaskManager.js +162 -0
  48. package/dist/data/TaskManager.js.map +1 -0
  49. package/dist/data/TaskProgress.cjs +125 -0
  50. package/dist/data/TaskProgress.cjs.map +1 -0
  51. package/dist/data/TaskProgress.d.ts +20 -0
  52. package/dist/data/TaskProgress.d.ts.map +1 -0
  53. package/dist/data/TaskProgress.js +125 -0
  54. package/dist/data/TaskProgress.js.map +1 -0
  55. package/dist/data/TaskSnapshot.cjs +23 -0
  56. package/dist/data/TaskSnapshot.cjs.map +1 -0
  57. package/dist/data/TaskSnapshot.d.ts +49 -0
  58. package/dist/data/TaskSnapshot.d.ts.map +1 -0
  59. package/dist/data/TaskSnapshot.js +23 -0
  60. package/dist/data/TaskSnapshot.js.map +1 -0
  61. package/dist/data/TimeseriesPanel.axes.cjs +39 -0
  62. package/dist/data/TimeseriesPanel.axes.cjs.map +1 -0
  63. package/dist/data/TimeseriesPanel.axes.d.ts +28 -0
  64. package/dist/data/TimeseriesPanel.axes.d.ts.map +1 -0
  65. package/dist/data/TimeseriesPanel.axes.js +39 -0
  66. package/dist/data/TimeseriesPanel.axes.js.map +1 -0
  67. package/dist/data/TimeseriesPanel.cjs +324 -0
  68. package/dist/data/TimeseriesPanel.cjs.map +1 -0
  69. package/dist/data/TimeseriesPanel.d.ts +141 -0
  70. package/dist/data/TimeseriesPanel.d.ts.map +1 -0
  71. package/dist/data/TimeseriesPanel.js +324 -0
  72. package/dist/data/TimeseriesPanel.js.map +1 -0
  73. package/dist/data/Tree.cjs.map +1 -1
  74. package/dist/data/Tree.d.ts.map +1 -1
  75. package/dist/data/Tree.js.map +1 -1
  76. package/dist/data/TreeNode.cjs +44 -7
  77. package/dist/data/TreeNode.cjs.map +1 -1
  78. package/dist/data/TreeNode.d.ts +19 -1
  79. package/dist/data/TreeNode.d.ts.map +1 -1
  80. package/dist/data/TreeNode.js +44 -7
  81. package/dist/data/TreeNode.js.map +1 -1
  82. package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs +66 -44
  83. package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs.map +1 -1
  84. package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts +21 -1
  85. package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts.map +1 -1
  86. package/dist/data/diagnostics/DiagnosticsDetailPanel.js +65 -43
  87. package/dist/data/diagnostics/DiagnosticsDetailPanel.js.map +1 -1
  88. package/dist/data/diagnostics/DiagnosticsTree.cjs +2 -1
  89. package/dist/data/diagnostics/DiagnosticsTree.cjs.map +1 -1
  90. package/dist/data/diagnostics/DiagnosticsTree.js +2 -1
  91. package/dist/data/diagnostics/DiagnosticsTree.js.map +1 -1
  92. package/dist/data/diagnostics/FrameSourceWindow.cjs +37 -0
  93. package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -0
  94. package/dist/data/diagnostics/FrameSourceWindow.d.ts +15 -0
  95. package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -0
  96. package/dist/data/diagnostics/FrameSourceWindow.js +37 -0
  97. package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -0
  98. package/dist/data/diagnostics/JvmStackTrace.cjs +25 -12
  99. package/dist/data/diagnostics/JvmStackTrace.cjs.map +1 -1
  100. package/dist/data/diagnostics/JvmStackTrace.d.ts +8 -1
  101. package/dist/data/diagnostics/JvmStackTrace.d.ts.map +1 -1
  102. package/dist/data/diagnostics/JvmStackTrace.js +26 -13
  103. package/dist/data/diagnostics/JvmStackTrace.js.map +1 -1
  104. package/dist/data/diagnostics/ThreadCard.cjs +9 -2
  105. package/dist/data/diagnostics/ThreadCard.cjs.map +1 -1
  106. package/dist/data/diagnostics/ThreadCard.d.ts +3 -1
  107. package/dist/data/diagnostics/ThreadCard.d.ts.map +1 -1
  108. package/dist/data/diagnostics/ThreadCard.js +9 -2
  109. package/dist/data/diagnostics/ThreadCard.js.map +1 -1
  110. package/dist/data/diagnostics/jvm-stacktrace.cjs +10 -1
  111. package/dist/data/diagnostics/jvm-stacktrace.cjs.map +1 -1
  112. package/dist/data/diagnostics/jvm-stacktrace.d.ts.map +1 -1
  113. package/dist/data/diagnostics/jvm-stacktrace.js +10 -1
  114. package/dist/data/diagnostics/jvm-stacktrace.js.map +1 -1
  115. package/dist/data/diagnostics/utils.cjs +0 -12
  116. package/dist/data/diagnostics/utils.cjs.map +1 -1
  117. package/dist/data/diagnostics/utils.d.ts +1 -1
  118. package/dist/data/diagnostics/utils.d.ts.map +1 -1
  119. package/dist/data/diagnostics/utils.js +0 -12
  120. package/dist/data/diagnostics/utils.js.map +1 -1
  121. package/dist/data/task-status.cjs +108 -0
  122. package/dist/data/task-status.cjs.map +1 -0
  123. package/dist/data/task-status.d.ts +21 -0
  124. package/dist/data/task-status.d.ts.map +1 -0
  125. package/dist/data/task-status.js +108 -0
  126. package/dist/data/task-status.js.map +1 -0
  127. package/dist/data.cjs +21 -0
  128. package/dist/data.cjs.map +1 -1
  129. package/dist/data.d.ts +7 -1
  130. package/dist/data.d.ts.map +1 -1
  131. package/dist/data.js +21 -0
  132. package/dist/data.js.map +1 -1
  133. package/dist/hooks/use-task-run.cjs +137 -0
  134. package/dist/hooks/use-task-run.cjs.map +1 -0
  135. package/dist/hooks/use-task-run.d.ts +35 -0
  136. package/dist/hooks/use-task-run.d.ts.map +1 -0
  137. package/dist/hooks/use-task-run.js +137 -0
  138. package/dist/hooks/use-task-run.js.map +1 -0
  139. package/dist/hooks.cjs +3 -0
  140. package/dist/hooks.cjs.map +1 -1
  141. package/dist/hooks.d.ts +1 -0
  142. package/dist/hooks.d.ts.map +1 -1
  143. package/dist/hooks.js +3 -0
  144. package/dist/hooks.js.map +1 -1
  145. package/dist/index.cjs +37 -0
  146. package/dist/index.cjs.map +1 -1
  147. package/dist/index.js +37 -0
  148. package/dist/index.js.map +1 -1
  149. package/dist/lib/color.cjs +33 -0
  150. package/dist/lib/color.cjs.map +1 -0
  151. package/dist/lib/color.d.ts +10 -0
  152. package/dist/lib/color.d.ts.map +1 -0
  153. package/dist/lib/color.js +33 -0
  154. package/dist/lib/color.js.map +1 -0
  155. package/dist/lib/format.cjs +77 -0
  156. package/dist/lib/format.cjs.map +1 -0
  157. package/dist/lib/format.d.ts +30 -0
  158. package/dist/lib/format.d.ts.map +1 -0
  159. package/dist/lib/format.js +77 -0
  160. package/dist/lib/format.js.map +1 -0
  161. package/dist/overlay/DropdownMenu.cjs +18 -2
  162. package/dist/overlay/DropdownMenu.cjs.map +1 -1
  163. package/dist/overlay/DropdownMenu.d.ts +3 -1
  164. package/dist/overlay/DropdownMenu.d.ts.map +1 -1
  165. package/dist/overlay/DropdownMenu.js +18 -2
  166. package/dist/overlay/DropdownMenu.js.map +1 -1
  167. package/dist/rpc/types.cjs.map +1 -1
  168. package/dist/rpc/types.d.ts +4 -0
  169. package/dist/rpc/types.d.ts.map +1 -1
  170. package/dist/rpc/types.js.map +1 -1
  171. package/dist/styles.css +1 -1
  172. package/dist/utils.cjs +6 -0
  173. package/dist/utils.cjs.map +1 -1
  174. package/dist/utils.d.ts +1 -0
  175. package/dist/utils.d.ts.map +1 -1
  176. package/dist/utils.js +6 -0
  177. package/dist/utils.js.map +1 -1
  178. package/package.json +4 -1
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const react = require("react");
5
+ const utils = require("../lib/utils.cjs");
6
+ const Icon = require("../data/Icon.cjs");
7
+ const UiClose = require("../icons/components/UiClose.cjs");
8
+ const UiChevronDown = require("../icons/components/UiChevronDown.cjs");
9
+ const UiCheck = require("../icons/components/UiCheck.cjs");
10
+ function Combobox(props) {
11
+ const {
12
+ options,
13
+ placeholder,
14
+ label,
15
+ disabled,
16
+ required,
17
+ allowCustomValue = true,
18
+ id,
19
+ className,
20
+ loading,
21
+ onSearch,
22
+ onKeyDown: onKeyDownProp
23
+ } = props;
24
+ const multiple = props.multiple === true;
25
+ const selectedValues = react.useMemo(
26
+ () => multiple ? props.value : props.value ? [props.value] : [],
27
+ [multiple, props.value]
28
+ );
29
+ const rootRef = react.useRef(null);
30
+ const inputRef = react.useRef(null);
31
+ const listRef = react.useRef(null);
32
+ const [open, setOpen] = react.useState(false);
33
+ const [query, setQuery] = react.useState("");
34
+ const [highlighted, setHighlighted] = react.useState(-1);
35
+ const isSelected = (optValue) => selectedValues.includes(optValue);
36
+ const closedLabel = react.useMemo(() => {
37
+ var _a;
38
+ if (multiple) {
39
+ const labels = options.filter((o) => selectedValues.includes(o.value)).map((o) => o.label);
40
+ if (labels.length === 0) return "";
41
+ if (labels.length <= 2) return labels.join(", ");
42
+ return `${labels.length} selected`;
43
+ }
44
+ const single = selectedValues[0] ?? "";
45
+ return ((_a = options.find((o) => o.value === single)) == null ? void 0 : _a.label) ?? single;
46
+ }, [multiple, options, selectedValues]);
47
+ const displayValue = open ? query : closedLabel;
48
+ const filtered = react.useMemo(() => {
49
+ if (onSearch) return withSelectedOptions(options, selectedValues);
50
+ const q = query.toLowerCase().trim();
51
+ if (!q) return options;
52
+ return options.filter(
53
+ (o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q)
54
+ );
55
+ }, [onSearch, options, query, selectedValues]);
56
+ react.useEffect(() => {
57
+ if (!onSearch || !open) return;
58
+ const handle = setTimeout(() => onSearch(query.trim()), 250);
59
+ return () => clearTimeout(handle);
60
+ }, [onSearch, open, query]);
61
+ react.useEffect(() => {
62
+ setHighlighted(-1);
63
+ }, [filtered.length]);
64
+ react.useEffect(() => {
65
+ if (!open) return;
66
+ const onPointerDown = (e) => {
67
+ var _a;
68
+ if (!((_a = rootRef.current) == null ? void 0 : _a.contains(e.target))) {
69
+ commitAndClose();
70
+ }
71
+ };
72
+ document.addEventListener("mousedown", onPointerDown);
73
+ return () => document.removeEventListener("mousedown", onPointerDown);
74
+ });
75
+ function emit(next) {
76
+ if (multiple) {
77
+ props.onChange(next);
78
+ } else {
79
+ props.onChange(next[0] ?? "");
80
+ }
81
+ }
82
+ function openMenu() {
83
+ setQuery("");
84
+ setHighlighted(-1);
85
+ setOpen(true);
86
+ }
87
+ function commitAndClose() {
88
+ const trimmed = query.trim();
89
+ if (!multiple && allowCustomValue && trimmed && trimmed !== selectedValues[0]) {
90
+ emit([trimmed]);
91
+ }
92
+ setQuery("");
93
+ setOpen(false);
94
+ setHighlighted(-1);
95
+ }
96
+ function selectOption(opt) {
97
+ var _a, _b;
98
+ if (multiple) {
99
+ const next = isSelected(opt.value) ? selectedValues.filter((v) => v !== opt.value) : [...selectedValues, opt.value];
100
+ emit(next);
101
+ setQuery("");
102
+ setHighlighted(-1);
103
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
104
+ return;
105
+ }
106
+ emit([opt.value]);
107
+ setQuery("");
108
+ setOpen(false);
109
+ setHighlighted(-1);
110
+ (_b = inputRef.current) == null ? void 0 : _b.focus();
111
+ }
112
+ function clear() {
113
+ var _a;
114
+ if (selectedValues.length > 0) emit([]);
115
+ setQuery("");
116
+ setHighlighted(-1);
117
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
118
+ }
119
+ function scrollToHighlighted(index) {
120
+ const list = listRef.current;
121
+ if (!list) return;
122
+ const item = list.children[index];
123
+ item == null ? void 0 : item.scrollIntoView({ block: "nearest" });
124
+ }
125
+ function onKeyDown(e) {
126
+ if (e.key === "ArrowDown") {
127
+ e.preventDefault();
128
+ if (!open) {
129
+ openMenu();
130
+ return;
131
+ }
132
+ const next = highlighted < filtered.length - 1 ? highlighted + 1 : 0;
133
+ setHighlighted(next);
134
+ scrollToHighlighted(next);
135
+ } else if (e.key === "ArrowUp") {
136
+ e.preventDefault();
137
+ if (!open) return;
138
+ const next = highlighted > 0 ? highlighted - 1 : filtered.length - 1;
139
+ setHighlighted(next);
140
+ scrollToHighlighted(next);
141
+ } else if (e.key === "Enter") {
142
+ if (open && highlighted >= 0 && filtered[highlighted] && !filtered[highlighted].disabled) {
143
+ e.preventDefault();
144
+ selectOption(filtered[highlighted]);
145
+ return;
146
+ }
147
+ commitAndClose();
148
+ onKeyDownProp == null ? void 0 : onKeyDownProp(e);
149
+ } else if (e.key === "Escape") {
150
+ if (open) {
151
+ e.preventDefault();
152
+ setQuery("");
153
+ setOpen(false);
154
+ setHighlighted(-1);
155
+ }
156
+ } else {
157
+ onKeyDownProp == null ? void 0 : onKeyDownProp(e);
158
+ }
159
+ }
160
+ const listId = id ? `${id}-listbox` : void 0;
161
+ const ariaLabel = typeof label === "string" ? label : void 0;
162
+ const showClear = !required && !loading && !disabled && selectedValues.length > 0;
163
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: utils.cn("relative", className), children: [
164
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
165
+ label != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none absolute left-2 z-10 whitespace-nowrap font-medium uppercase tracking-wide text-muted-foreground text-[10px]", children: label }),
166
+ /* @__PURE__ */ jsxRuntime.jsx(
167
+ "input",
168
+ {
169
+ ref: inputRef,
170
+ id,
171
+ type: "text",
172
+ role: "combobox",
173
+ "aria-expanded": open,
174
+ "aria-controls": listId,
175
+ "aria-activedescendant": highlighted >= 0 ? `${listId}-${highlighted}` : void 0,
176
+ "aria-label": ariaLabel,
177
+ autoComplete: "off",
178
+ disabled,
179
+ placeholder,
180
+ value: displayValue,
181
+ onChange: (e) => {
182
+ setQuery(e.target.value);
183
+ if (!open) setOpen(true);
184
+ },
185
+ onFocus: openMenu,
186
+ onKeyDown,
187
+ className: utils.cn(
188
+ "h-control-h w-full rounded-md border border-input bg-background px-control-px text-sm text-foreground",
189
+ showClear ? "pr-14" : "pr-8",
190
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
191
+ "disabled:cursor-not-allowed disabled:opacity-50"
192
+ ),
193
+ style: label != null ? labelPadding(label) : void 0
194
+ }
195
+ ),
196
+ showClear && /* @__PURE__ */ jsxRuntime.jsx(
197
+ "button",
198
+ {
199
+ type: "button",
200
+ tabIndex: -1,
201
+ "aria-label": "Clear",
202
+ onClick: clear,
203
+ className: "absolute right-7 flex h-full items-center px-1 text-muted-foreground hover:text-foreground",
204
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiClose.UiClose, className: "text-xs" })
205
+ }
206
+ ),
207
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "h-3.5 w-3.5 animate-spin text-muted-foreground", viewBox: "0 0 16 16", fill: "none", children: [
208
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeWidth: "2", opacity: "0.25" }),
209
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14.5 8a6.5 6.5 0 0 0-6.5-6.5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
210
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx(
211
+ "button",
212
+ {
213
+ type: "button",
214
+ tabIndex: -1,
215
+ disabled,
216
+ "aria-label": "Toggle options",
217
+ onClick: () => {
218
+ var _a;
219
+ if (open) {
220
+ commitAndClose();
221
+ } else {
222
+ openMenu();
223
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
224
+ }
225
+ },
226
+ className: "pointer-events-auto absolute right-0 flex h-full items-center px-2 text-muted-foreground",
227
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiChevronDown.UiChevronDown, className: "text-xs" })
228
+ }
229
+ )
230
+ ] }),
231
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
232
+ "div",
233
+ {
234
+ id: listId,
235
+ ref: listRef,
236
+ role: "listbox",
237
+ "aria-multiselectable": multiple || void 0,
238
+ className: "absolute left-0 top-[calc(100%+0.25rem)] z-50 w-full min-w-[10rem] max-h-64 overflow-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5",
239
+ children: [
240
+ loading && filtered.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-4 text-center text-sm text-muted-foreground", children: "Loading…" }),
241
+ !loading && filtered.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-4 text-center text-sm text-muted-foreground", children: "No results" }),
242
+ filtered.map((opt, i) => {
243
+ const selected = isSelected(opt.value);
244
+ return /* @__PURE__ */ jsxRuntime.jsxs(
245
+ "div",
246
+ {
247
+ id: listId ? `${listId}-${i}` : void 0,
248
+ role: "option",
249
+ "aria-selected": selected,
250
+ "aria-disabled": opt.disabled,
251
+ onMouseDown: (e) => {
252
+ e.preventDefault();
253
+ if (!opt.disabled) selectOption(opt);
254
+ },
255
+ onMouseEnter: () => setHighlighted(i),
256
+ className: utils.cn(
257
+ "flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm",
258
+ i === highlighted && "bg-accent",
259
+ selected && "font-medium",
260
+ opt.disabled && "cursor-not-allowed opacity-50"
261
+ ),
262
+ children: [
263
+ /* @__PURE__ */ jsxRuntime.jsx(
264
+ Icon.Icon,
265
+ {
266
+ icon: UiCheck.UiCheck,
267
+ className: utils.cn("text-xs shrink-0", selected ? "opacity-100" : "opacity-0")
268
+ }
269
+ ),
270
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: opt.label })
271
+ ]
272
+ },
273
+ opt.value
274
+ );
275
+ })
276
+ ]
277
+ }
278
+ )
279
+ ] });
280
+ }
281
+ function labelPadding(label) {
282
+ const length = typeof label === "string" ? label.length : 6;
283
+ return { paddingLeft: `${Math.min(length * 0.62 + 0.75, 9)}rem` };
284
+ }
285
+ function withSelectedOptions(options, selectedValues) {
286
+ const present = new Set(options.map((o) => o.value));
287
+ const pinned = selectedValues.filter((v) => !present.has(v)).map((v) => ({ value: v, label: v }));
288
+ return pinned.length === 0 ? options : [...pinned, ...options];
289
+ }
290
+ exports.Combobox = Combobox;
291
+ //# sourceMappingURL=Combobox.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Combobox.cjs","sources":["../../src/components/Combobox.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiChevronDown, UiClose, UiCheck } from \"../icons\";\n\nexport type ComboboxOption = {\n value: string;\n label: string;\n disabled?: boolean;\n};\n\ntype ComboboxBaseProps = {\n /** Available options, rendered in the order provided. */\n options: ComboboxOption[];\n /** Ghost text when no value is set. */\n placeholder?: string;\n /** Optional label rendered inline inside the control, before the input. */\n label?: ReactNode;\n /** Disables the input. */\n disabled?: boolean;\n /** When true, the value cannot be cleared (the clear button is hidden). */\n required?: boolean;\n /**\n * When false, the value is restricted to the provided options — typed text\n * that does not match an option is discarded instead of committed. Defaults\n * to true (freeform entry allowed).\n */\n allowCustomValue?: boolean;\n /** HTML id for the input element. */\n id?: string;\n /** Classes applied to the root wrapper. */\n className?: string;\n /** Shows a loading indicator when options are being fetched. */\n loading?: boolean;\n /**\n * Optional async search invoked (debounced ~250ms) as the user types. The\n * consumer fetches and feeds matching options back via the `options` prop\n * (setting `loading` while in flight). When set, the component renders the\n * provided `options` as-is instead of filtering them client-side — the\n * server has already filtered. When absent, typing filters `options`\n * client-side as before.\n */\n onSearch?: (query: string) => void;\n /** Called when a key is pressed in the input. */\n onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;\n};\n\nexport type ComboboxSingleProps = ComboboxBaseProps & {\n multiple?: false;\n /** Controlled selected value. */\n value: string;\n /** Called when the selected value changes (from list or freeform input). */\n onChange: (value: string) => void;\n};\n\nexport type ComboboxMultiProps = ComboboxBaseProps & {\n multiple: true;\n /** Controlled selected values. */\n value: string[];\n /** Called with the complete next value array after each toggle. */\n onChange: (value: string[]) => void;\n};\n\nexport type ComboboxProps = ComboboxSingleProps | ComboboxMultiProps;\n\nexport function Combobox(props: ComboboxProps) {\n const {\n options,\n placeholder,\n label,\n disabled,\n required,\n allowCustomValue = true,\n id,\n className,\n loading,\n onSearch,\n onKeyDown: onKeyDownProp,\n } = props;\n const multiple = props.multiple === true;\n const selectedValues = useMemo<string[]>(\n () => (multiple ? props.value : props.value ? [props.value] : []),\n [multiple, props.value],\n );\n\n const rootRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const [open, setOpen] = useState(false);\n // `query` is the type-ahead filter text, kept separate from the committed\n // value. It is empty unless the user is actively typing, so opening the\n // dropdown shows every option rather than only the selected one.\n const [query, setQuery] = useState(\"\");\n const [highlighted, setHighlighted] = useState(-1);\n\n const isSelected = (optValue: string) => selectedValues.includes(optValue);\n\n // Closed-state input text. Single: the selected option's label (or its raw\n // value as a fallback). Multi: a summary mirroring MultiSelect.\n const closedLabel = useMemo(() => {\n if (multiple) {\n const labels = options\n .filter((o) => selectedValues.includes(o.value))\n .map((o) => o.label);\n if (labels.length === 0) return \"\";\n if (labels.length <= 2) return labels.join(\", \");\n return `${labels.length} selected`;\n }\n const single = selectedValues[0] ?? \"\";\n return options.find((o) => o.value === single)?.label ?? single;\n }, [multiple, options, selectedValues]);\n\n // While open the input mirrors the query; while closed it shows the\n // committed selection's label/summary.\n const displayValue = open ? query : closedLabel;\n\n const filtered = useMemo(() => {\n // With onSearch the server already filtered. The matches replace the list,\n // but any already-selected value not in the matches is pinned so the user's\n // selection stays visible and toggleable. Selected values carry no label\n // object here, so synthesize {value, label: value} (the typeahead's value is\n // its own human label).\n if (onSearch) return withSelectedOptions(options, selectedValues);\n const q = query.toLowerCase().trim();\n if (!q) return options;\n return options.filter(\n (o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q),\n );\n }, [onSearch, options, query, selectedValues]);\n\n // Debounced server-side search: fires onSearch ~250ms after the query settles\n // while the menu is open. The empty-query call lets the consumer reset its\n // result set. Only active when onSearch is provided.\n useEffect(() => {\n if (!onSearch || !open) return;\n const handle = setTimeout(() => onSearch(query.trim()), 250);\n return () => clearTimeout(handle);\n }, [onSearch, open, query]);\n\n useEffect(() => {\n setHighlighted(-1);\n }, [filtered.length]);\n\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: MouseEvent) => {\n if (!rootRef.current?.contains(e.target as Node)) {\n commitAndClose();\n }\n };\n document.addEventListener(\"mousedown\", onPointerDown);\n return () => document.removeEventListener(\"mousedown\", onPointerDown);\n });\n\n function emit(next: string[]) {\n if (multiple) {\n props.onChange(next);\n } else {\n props.onChange(next[0] ?? \"\");\n }\n }\n\n function openMenu() {\n setQuery(\"\");\n setHighlighted(-1);\n setOpen(true);\n }\n\n function commitAndClose() {\n // Freeform commit (single, allowCustomValue only): a non-empty query that\n // doesn't match an option becomes the value. An empty query keeps the\n // existing selection. Multi and strict modes never commit freeform text.\n const trimmed = query.trim();\n if (!multiple && allowCustomValue && trimmed && trimmed !== selectedValues[0]) {\n emit([trimmed]);\n }\n setQuery(\"\");\n setOpen(false);\n setHighlighted(-1);\n }\n\n function selectOption(opt: ComboboxOption) {\n if (multiple) {\n const next = isSelected(opt.value)\n ? selectedValues.filter((v) => v !== opt.value)\n : [...selectedValues, opt.value];\n emit(next);\n setQuery(\"\");\n setHighlighted(-1);\n inputRef.current?.focus();\n return;\n }\n emit([opt.value]);\n setQuery(\"\");\n setOpen(false);\n setHighlighted(-1);\n inputRef.current?.focus();\n }\n\n function clear() {\n if (selectedValues.length > 0) emit([]);\n setQuery(\"\");\n setHighlighted(-1);\n inputRef.current?.focus();\n }\n\n function scrollToHighlighted(index: number) {\n const list = listRef.current;\n if (!list) return;\n const item = list.children[index] as HTMLElement | undefined;\n item?.scrollIntoView({ block: \"nearest\" });\n }\n\n function onKeyDown(e: KeyboardEvent<HTMLInputElement>) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n if (!open) {\n openMenu();\n return;\n }\n const next = highlighted < filtered.length - 1 ? highlighted + 1 : 0;\n setHighlighted(next);\n scrollToHighlighted(next);\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n if (!open) return;\n const next = highlighted > 0 ? highlighted - 1 : filtered.length - 1;\n setHighlighted(next);\n scrollToHighlighted(next);\n } else if (e.key === \"Enter\") {\n if (open && highlighted >= 0 && filtered[highlighted] && !filtered[highlighted].disabled) {\n e.preventDefault();\n selectOption(filtered[highlighted]);\n return;\n }\n commitAndClose();\n onKeyDownProp?.(e);\n } else if (e.key === \"Escape\") {\n if (open) {\n e.preventDefault();\n setQuery(\"\");\n setOpen(false);\n setHighlighted(-1);\n }\n } else {\n onKeyDownProp?.(e);\n }\n }\n\n const listId = id ? `${id}-listbox` : undefined;\n const ariaLabel = typeof label === \"string\" ? label : undefined;\n const showClear = !required && !loading && !disabled && selectedValues.length > 0;\n\n return (\n <div ref={rootRef} className={cn(\"relative\", className)}>\n <div className=\"relative flex items-center\">\n {label != null && (\n <span className=\"pointer-events-none absolute left-2 z-10 whitespace-nowrap font-medium uppercase tracking-wide text-muted-foreground text-[10px]\">\n {label}\n </span>\n )}\n <input\n ref={inputRef}\n id={id}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={listId}\n aria-activedescendant={highlighted >= 0 ? `${listId}-${highlighted}` : undefined}\n aria-label={ariaLabel}\n autoComplete=\"off\"\n disabled={disabled}\n placeholder={placeholder}\n value={displayValue}\n onChange={(e) => {\n setQuery(e.target.value);\n if (!open) setOpen(true);\n }}\n onFocus={openMenu}\n onKeyDown={onKeyDown}\n className={cn(\n \"h-control-h w-full rounded-md border border-input bg-background px-control-px text-sm text-foreground\",\n showClear ? \"pr-14\" : \"pr-8\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n )}\n style={label != null ? labelPadding(label) : undefined}\n />\n {showClear && (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label=\"Clear\"\n onClick={clear}\n className=\"absolute right-7 flex h-full items-center px-1 text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n {loading ? (\n <span className=\"pointer-events-none absolute right-2 top-1/2 -translate-y-1/2\">\n <svg className=\"h-3.5 w-3.5 animate-spin text-muted-foreground\" viewBox=\"0 0 16 16\" fill=\"none\">\n <circle cx=\"8\" cy=\"8\" r=\"6.5\" stroke=\"currentColor\" strokeWidth=\"2\" opacity=\"0.25\" />\n <path d=\"M14.5 8a6.5 6.5 0 0 0-6.5-6.5\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" />\n </svg>\n </span>\n ) : (\n <button\n type=\"button\"\n tabIndex={-1}\n disabled={disabled}\n aria-label=\"Toggle options\"\n onClick={() => {\n if (open) {\n commitAndClose();\n } else {\n openMenu();\n inputRef.current?.focus();\n }\n }}\n className=\"pointer-events-auto absolute right-0 flex h-full items-center px-2 text-muted-foreground\"\n >\n <Icon icon={UiChevronDown} className=\"text-xs\" />\n </button>\n )}\n </div>\n {open && (\n <div\n id={listId}\n ref={listRef}\n role=\"listbox\"\n aria-multiselectable={multiple || undefined}\n className=\"absolute left-0 top-[calc(100%+0.25rem)] z-50 w-full min-w-[10rem] max-h-64 overflow-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\"\n >\n {loading && filtered.length === 0 && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">Loading…</div>\n )}\n {!loading && filtered.length === 0 && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">No results</div>\n )}\n {filtered.map((opt, i) => {\n const selected = isSelected(opt.value);\n return (\n <div\n key={opt.value}\n id={listId ? `${listId}-${i}` : undefined}\n role=\"option\"\n aria-selected={selected}\n aria-disabled={opt.disabled}\n onMouseDown={(e) => {\n e.preventDefault();\n if (!opt.disabled) selectOption(opt);\n }}\n onMouseEnter={() => setHighlighted(i)}\n className={cn(\n \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm\",\n i === highlighted && \"bg-accent\",\n selected && \"font-medium\",\n opt.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <Icon\n icon={UiCheck}\n className={cn(\"text-xs shrink-0\", selected ? \"opacity-100\" : \"opacity-0\")}\n />\n <span className=\"min-w-0 truncate\">{opt.label}</span>\n </div>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\n// Reserve room on the left of the input for the inline label so typed text\n// does not overlap it. Roughly 0.6rem per character plus padding.\nfunction labelPadding(label: ReactNode) {\n const length = typeof label === \"string\" ? label.length : 6;\n return { paddingLeft: `${Math.min(length * 0.62 + 0.75, 9)}rem` };\n}\n\n// withSelectedOptions returns the option list plus any selected value that isn't\n// already present, synthesized as {value, label: value}. Used by the server\n// (onSearch) typeahead so an already-selected value missing from the current\n// matches still renders as a checked row. Selected-but-absent values come first\n// so they don't get scrolled past by a long match list.\nfunction withSelectedOptions(\n options: ComboboxOption[],\n selectedValues: string[],\n): ComboboxOption[] {\n const present = new Set(options.map((o) => o.value));\n const pinned = selectedValues\n .filter((v) => !present.has(v))\n .map((v) => ({ value: v, label: v }));\n return pinned.length === 0 ? options : [...pinned, ...options];\n}\n"],"names":["useMemo","useRef","useState","useEffect","jsxs","cn","jsx","Icon","UiClose","UiChevronDown","UiCheck"],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import { KeyboardEvent, ReactNode } from 'react';
2
+ export type ComboboxOption = {
3
+ value: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ };
7
+ type ComboboxBaseProps = {
8
+ /** Available options, rendered in the order provided. */
9
+ options: ComboboxOption[];
10
+ /** Ghost text when no value is set. */
11
+ placeholder?: string;
12
+ /** Optional label rendered inline inside the control, before the input. */
13
+ label?: ReactNode;
14
+ /** Disables the input. */
15
+ disabled?: boolean;
16
+ /** When true, the value cannot be cleared (the clear button is hidden). */
17
+ required?: boolean;
18
+ /**
19
+ * When false, the value is restricted to the provided options — typed text
20
+ * that does not match an option is discarded instead of committed. Defaults
21
+ * to true (freeform entry allowed).
22
+ */
23
+ allowCustomValue?: boolean;
24
+ /** HTML id for the input element. */
25
+ id?: string;
26
+ /** Classes applied to the root wrapper. */
27
+ className?: string;
28
+ /** Shows a loading indicator when options are being fetched. */
29
+ loading?: boolean;
30
+ /**
31
+ * Optional async search invoked (debounced ~250ms) as the user types. The
32
+ * consumer fetches and feeds matching options back via the `options` prop
33
+ * (setting `loading` while in flight). When set, the component renders the
34
+ * provided `options` as-is instead of filtering them client-side — the
35
+ * server has already filtered. When absent, typing filters `options`
36
+ * client-side as before.
37
+ */
38
+ onSearch?: (query: string) => void;
39
+ /** Called when a key is pressed in the input. */
40
+ onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;
41
+ };
42
+ export type ComboboxSingleProps = ComboboxBaseProps & {
43
+ multiple?: false;
44
+ /** Controlled selected value. */
45
+ value: string;
46
+ /** Called when the selected value changes (from list or freeform input). */
47
+ onChange: (value: string) => void;
48
+ };
49
+ export type ComboboxMultiProps = ComboboxBaseProps & {
50
+ multiple: true;
51
+ /** Controlled selected values. */
52
+ value: string[];
53
+ /** Called with the complete next value array after each toggle. */
54
+ onChange: (value: string[]) => void;
55
+ };
56
+ export type ComboboxProps = ComboboxSingleProps | ComboboxMultiProps;
57
+ export declare function Combobox(props: ComboboxProps): import("react/jsx-runtime").JSX.Element;
58
+ export {};
59
+ //# sourceMappingURL=Combobox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Combobox.d.ts","sourceRoot":"","sources":["../../src/components/Combobox.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKjG,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,yDAAyD;IACzD,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qCAAqC;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,iDAAiD;IACjD,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;CAC1D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,GAAG;IACpD,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG;IACnD,QAAQ,EAAE,IAAI,CAAC;IACf,kCAAkC;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mEAAmE;IACnE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAErE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,2CAoT5C"}