@reopt-ai/opt-ui 1.5.0 → 1.7.0

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 (99) hide show
  1. package/COMPONENT_CATALOG.md +178 -50
  2. package/README.md +28 -7
  3. package/dist/app.css +170 -0
  4. package/dist/core/index.cjs +164 -324
  5. package/dist/core/index.d.cts +3 -6
  6. package/dist/core/index.d.ts +3 -6
  7. package/dist/core/index.js +3 -323
  8. package/dist/docs/01-getting-started.md +25 -5
  9. package/dist/docs/02-components/01-core.md +36 -28
  10. package/dist/docs/02-components/02-visuals.md +1 -1
  11. package/dist/docs/02-components/03-shells.md +144 -23
  12. package/dist/docs/02-components/04-surfaces.md +1 -1
  13. package/dist/docs/02-components/index.md +4 -4
  14. package/dist/docs/03-recipes/03-layouts.md +16 -12
  15. package/dist/docs/05-migration/01-breaking-changes.md +46 -1
  16. package/dist/field-sidebar-BTZCBviA.d.cts +1031 -0
  17. package/dist/field-sidebar-BTZCBviA.d.cts.map +1 -0
  18. package/dist/field-sidebar-CB8vjvUM.d.ts +1031 -0
  19. package/dist/field-sidebar-CB8vjvUM.d.ts.map +1 -0
  20. package/dist/field-sidebar-Cs-J-5VE.js +4233 -0
  21. package/dist/field-sidebar-Cs-J-5VE.js.map +1 -0
  22. package/dist/field-sidebar-CzIQX70-.cjs +4561 -0
  23. package/dist/id-registry.cjs +1939 -1872
  24. package/dist/id-registry.d.cts +14 -11
  25. package/dist/id-registry.d.cts.map +1 -0
  26. package/dist/id-registry.d.ts +14 -11
  27. package/dist/id-registry.d.ts.map +1 -0
  28. package/dist/id-registry.js +1937 -1871
  29. package/dist/id-registry.js.map +1 -0
  30. package/dist/id-registry.json +113 -28
  31. package/dist/index.cjs +8380 -9790
  32. package/dist/index.d.cts +1555 -1442
  33. package/dist/index.d.cts.map +1 -0
  34. package/dist/index.d.ts +1555 -1442
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +7996 -9778
  37. package/dist/index.js.map +1 -0
  38. package/dist/meta.cjs +7753 -6875
  39. package/dist/meta.d.cts.map +1 -0
  40. package/dist/meta.d.ts.map +1 -0
  41. package/dist/meta.js +7752 -6846
  42. package/dist/meta.js.map +1 -0
  43. package/dist/pagination-DWbvACuN.js +3850 -0
  44. package/dist/pagination-DWbvACuN.js.map +1 -0
  45. package/dist/pagination-DjfxBcjX.cjs +4739 -0
  46. package/dist/query.cjs +386 -0
  47. package/dist/query.d.cts +141 -0
  48. package/dist/query.d.cts.map +1 -0
  49. package/dist/query.d.ts +141 -0
  50. package/dist/query.d.ts.map +1 -0
  51. package/dist/query.js +371 -0
  52. package/dist/query.js.map +1 -0
  53. package/dist/shader-surface-BUPZ0P-n.js +3654 -0
  54. package/dist/shader-surface-BUPZ0P-n.js.map +1 -0
  55. package/dist/shader-surface-BfTP9c1Q.d.ts +1788 -0
  56. package/dist/shader-surface-BfTP9c1Q.d.ts.map +1 -0
  57. package/dist/shader-surface-DRrcLrWf.d.cts +1788 -0
  58. package/dist/shader-surface-DRrcLrWf.d.cts.map +1 -0
  59. package/dist/shader-surface-Fav5K9UC.cjs +4041 -0
  60. package/dist/shells/index.cjs +58 -65
  61. package/dist/shells/index.d.cts +4 -5
  62. package/dist/shells/index.d.ts +4 -5
  63. package/dist/shells/index.js +4 -64
  64. package/dist/tailwind.css +49 -0
  65. package/dist/theme/presets/default.css +2 -2
  66. package/dist/theme/presets/minimal.css +2 -2
  67. package/dist/theme/presets/mono-dark.css +2 -2
  68. package/dist/theme/presets/natural.css +1 -1
  69. package/dist/theme/presets/pro.css +2 -2
  70. package/dist/theme/server.cjs +257 -228
  71. package/dist/theme/server.d.cts +48 -60
  72. package/dist/theme/server.d.cts.map +1 -0
  73. package/dist/theme/server.d.ts +48 -60
  74. package/dist/theme/server.d.ts.map +1 -0
  75. package/dist/theme/server.js +234 -189
  76. package/dist/theme/server.js.map +1 -0
  77. package/dist/types-D0FlcYnM.d.cts +301 -0
  78. package/dist/types-D0FlcYnM.d.cts.map +1 -0
  79. package/dist/types-D0FlcYnM.d.ts +301 -0
  80. package/dist/types-D0FlcYnM.d.ts.map +1 -0
  81. package/dist/visuals/index.cjs +10 -4
  82. package/dist/visuals/index.d.cts +2 -1
  83. package/dist/visuals/index.d.ts +2 -1
  84. package/dist/visuals/index.js +1 -2
  85. package/package.json +27 -11
  86. package/dist/chunk-3GWWZKX7.js +0 -38
  87. package/dist/chunk-3QFYBBL6.cjs +0 -4759
  88. package/dist/chunk-3XD4HIFL.js +0 -3241
  89. package/dist/chunk-AFF2HPE5.cjs +0 -5008
  90. package/dist/chunk-NXZJIHEZ.cjs +0 -3241
  91. package/dist/chunk-ONE3C5RV.cjs +0 -38
  92. package/dist/chunk-RBM2RNC2.js +0 -5008
  93. package/dist/chunk-VBGPEW45.js +0 -4759
  94. package/dist/index-BZ_lBlO1.d.ts +0 -474
  95. package/dist/index-BuvxoWHf.d.cts +0 -474
  96. package/dist/index-DWyqDIkH.d.cts +0 -1689
  97. package/dist/index-Uyijm14M.d.ts +0 -1689
  98. package/dist/types-D4-0lwaE.d.cts +0 -298
  99. package/dist/types-D4-0lwaE.d.ts +0 -298
@@ -0,0 +1,4561 @@
1
+ "use client";
2
+ const require_pagination = require("./pagination-DjfxBcjX.cjs");
3
+ let react_jsx_runtime = require("react/jsx-runtime");
4
+ let react = require("react");
5
+ react = require_pagination.__toESM(react, 1);
6
+ let _reopt_ai_opt_ui_primitives = require("@reopt-ai/opt-ui-primitives");
7
+ let lucide_react = require("lucide-react");
8
+ let _reopt_ai_opt_charts = require("@reopt-ai/opt-charts");
9
+ let match_sorter = require("match-sorter");
10
+ let next_link = require("next/link");
11
+ next_link = require_pagination.__toESM(next_link, 1);
12
+ let next_navigation = require("next/navigation");
13
+ //#region src/core/command.tsx
14
+ const _excluded = ["className"];
15
+ const _excluded2 = [
16
+ "className",
17
+ "trailing",
18
+ "wrapperClassName",
19
+ "placeholder",
20
+ "aria-label"
21
+ ];
22
+ const _excluded3 = ["className", "aria-label"];
23
+ const _excluded4 = ["className"];
24
+ const _excluded5 = ["className", "heading"];
25
+ const _excluded6 = ["className"];
26
+ const _excluded7 = ["className"];
27
+ const _excluded8 = ["className"];
28
+ /** Renders the `Command` component. */
29
+ function Command(_ref) {
30
+ let { className } = _ref, props = require_pagination._objectWithoutProperties(_ref, _excluded);
31
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CommandRoot, require_pagination._objectSpread2(require_pagination._objectSpread2({ className: require_pagination.cn("bg-surface text-text-primary flex w-full flex-col overflow-hidden rounded-xl", className) }, props), {}, {
32
+ "data-opt-id": "4FYBW",
33
+ "data-opt-slug": "command.Command"
34
+ }));
35
+ }
36
+ /** Renders a Command in a dialog overlay, à la Cmd+K palette. */
37
+ function CommandDialog({ open, onOpenChange, title = "Command Palette", description = "검색 후 Enter 또는 클릭으로 명령을 실행하세요.", className, commandClassName, shouldFilter, filter, children }) {
38
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.DialogRoot, {
39
+ open,
40
+ onOpenChange,
41
+ "data-opt-id": "DVRSX",
42
+ "data-opt-slug": "command.CommandDialog",
43
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.DialogPanel, {
44
+ className: require_pagination.cn("top-[20%] left-1/2 -translate-x-1/2", "w-full max-w-lg overflow-hidden p-0 shadow-2xl", className),
45
+ children: [
46
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.DialogHeading, {
47
+ className: "sr-only",
48
+ children: title
49
+ }),
50
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.DialogDescription, {
51
+ className: "sr-only",
52
+ children: description
53
+ }),
54
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Command, {
55
+ shouldFilter,
56
+ filter,
57
+ className: commandClassName,
58
+ children
59
+ })
60
+ ]
61
+ })
62
+ });
63
+ }
64
+ /** Renders the `CommandInput` component with a leading search icon. */
65
+ function CommandInput(_ref2) {
66
+ let { className, trailing, wrapperClassName, placeholder, "aria-label": ariaLabel } = _ref2, props = require_pagination._objectWithoutProperties(_ref2, _excluded2);
67
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
68
+ "data-slot": "command-input-wrapper",
69
+ className: require_pagination.cn("border-border-subtle gap-element flex h-12 items-center border-b px-3", wrapperClassName),
70
+ "data-opt-id": "HB1K3",
71
+ "data-opt-slug": "command.CommandInput",
72
+ children: [
73
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.SearchIcon, {
74
+ "aria-hidden": true,
75
+ className: "text-text-tertiary size-4 shrink-0"
76
+ }),
77
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CommandInput, require_pagination._objectSpread2({
78
+ className: require_pagination.cn("text-text-primary placeholder:text-text-tertiary flex h-full w-full bg-transparent text-sm outline-none focus-visible:ring-0 focus-visible:outline-none", "disabled:cursor-not-allowed disabled:opacity-50", className),
79
+ placeholder,
80
+ "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : typeof placeholder === "string" ? placeholder : "Command search"
81
+ }, props)),
82
+ trailing
83
+ ]
84
+ });
85
+ }
86
+ /** Renders the `CommandList` component. */
87
+ function CommandList(_ref3) {
88
+ let { className, "aria-label": ariaLabel } = _ref3, props = require_pagination._objectWithoutProperties(_ref3, _excluded3);
89
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CommandList, require_pagination._objectSpread2(require_pagination._objectSpread2({
90
+ className: require_pagination.cn("max-h-80 scroll-py-1 overflow-x-hidden overflow-y-auto p-1", className),
91
+ "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : "Commands"
92
+ }, props), {}, {
93
+ "data-opt-id": "NPXC5",
94
+ "data-opt-slug": "command.CommandList"
95
+ }));
96
+ }
97
+ /** Renders the `CommandEmpty` component. */
98
+ function CommandEmpty(_ref4) {
99
+ let { className } = _ref4, props = require_pagination._objectWithoutProperties(_ref4, _excluded4);
100
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CommandEmpty, require_pagination._objectSpread2(require_pagination._objectSpread2({ className: require_pagination.cn("text-text-tertiary py-6 text-center text-sm", className) }, props), {}, {
101
+ "data-opt-id": "A8BZM",
102
+ "data-opt-slug": "command.CommandEmpty"
103
+ }));
104
+ }
105
+ /** Renders the `CommandGroup` component with an optional heading. */
106
+ function CommandGroup(_ref5) {
107
+ let { className, heading } = _ref5, props = require_pagination._objectWithoutProperties(_ref5, _excluded5);
108
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CommandGroup, require_pagination._objectSpread2(require_pagination._objectSpread2({
109
+ heading: heading != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
110
+ className: "text-text-tertiary px-2 py-1.5 text-xs font-medium",
111
+ children: heading
112
+ }) : void 0,
113
+ className: require_pagination.cn("py-1", className)
114
+ }, props), {}, {
115
+ "data-opt-id": "JPK1Y",
116
+ "data-opt-slug": "command.CommandGroup"
117
+ }));
118
+ }
119
+ /** Renders the `CommandSeparator` component. */
120
+ function CommandSeparator(_ref6) {
121
+ let { className } = _ref6, props = require_pagination._objectWithoutProperties(_ref6, _excluded6);
122
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CommandSeparator, require_pagination._objectSpread2(require_pagination._objectSpread2({ className: require_pagination.cn("bg-border-subtle -mx-1 my-1 h-px", className) }, props), {}, {
123
+ "data-opt-id": "10P0E",
124
+ "data-opt-slug": "command.CommandSeparator"
125
+ }));
126
+ }
127
+ /** Renders the `CommandItem` component. */
128
+ function CommandItem(_ref7) {
129
+ let { className } = _ref7, props = require_pagination._objectWithoutProperties(_ref7, _excluded7);
130
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CommandItem, require_pagination._objectSpread2(require_pagination._objectSpread2({ className: require_pagination.cn("text-text-primary flex cursor-pointer items-center gap-2 rounded-md px-2 py-2 text-sm select-none", "data-[active-item]:bg-accent data-[active-item]:text-accent-fg", "data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className) }, props), {}, {
131
+ "data-opt-id": "K674W",
132
+ "data-opt-slug": "command.CommandItem"
133
+ }));
134
+ }
135
+ /** Renders the `CommandShortcut` component (right-aligned hint). */
136
+ function CommandShortcut(_ref8) {
137
+ let { className } = _ref8, props = require_pagination._objectWithoutProperties(_ref8, _excluded8);
138
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", require_pagination._objectSpread2(require_pagination._objectSpread2({
139
+ "data-slot": "command-shortcut",
140
+ className: require_pagination.cn("text-text-tertiary ml-auto text-xs tracking-widest", "data-[active-item]:text-accent-fg/80", className)
141
+ }, props), {}, {
142
+ "data-opt-id": "75CQK",
143
+ "data-opt-slug": "command.CommandShortcut"
144
+ }));
145
+ }
146
+ //#endregion
147
+ //#region src/shells/command-palette.tsx
148
+ const DEFAULT_LABELS = {
149
+ title: "Command Palette",
150
+ description: "검색 후 Enter 또는 클릭으로 명령을 실행하세요.",
151
+ input: "명령어 검색",
152
+ placeholder: "명령어를 검색하세요...",
153
+ list: "명령어 목록",
154
+ empty: "결과가 없습니다.",
155
+ escape: "ESC"
156
+ };
157
+ /** Renders the command palette component. */
158
+ function CommandPalette({ commands, open, onOpenChange, onSelect, inline = false, labels, footer, empty, inputTrailing, className }) {
159
+ const resolvedLabels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, DEFAULT_LABELS), labels);
160
+ const hasCommands = commands.length > 0;
161
+ const grouped = (0, react.useMemo)(() => {
162
+ const map = /* @__PURE__ */ new Map();
163
+ for (const cmd of commands) {
164
+ var _map$get;
165
+ const list = (_map$get = map.get(cmd.group)) !== null && _map$get !== void 0 ? _map$get : [];
166
+ list.push(cmd);
167
+ map.set(cmd.group, list);
168
+ }
169
+ return map;
170
+ }, [commands]);
171
+ const handleSelect = (cmdId) => {
172
+ const cmd = commands.find((c) => c.id === cmdId);
173
+ if (!cmd) return;
174
+ onSelect === null || onSelect === void 0 || onSelect(cmd);
175
+ onOpenChange(false);
176
+ };
177
+ const handleInputKeyDown = (e) => {
178
+ if (e.key === "Escape" && !inline) {
179
+ e.preventDefault();
180
+ onOpenChange(false);
181
+ }
182
+ };
183
+ const body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
184
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandInput, {
185
+ "aria-label": resolvedLabels.input,
186
+ placeholder: resolvedLabels.placeholder,
187
+ onKeyDown: handleInputKeyDown,
188
+ trailing: inputTrailing !== null && inputTrailing !== void 0 ? inputTrailing : !inline ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Kbd, { children: resolvedLabels.escape }) : void 0
189
+ }),
190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CommandList, {
191
+ "aria-label": resolvedLabels.list,
192
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandEmpty, { children: empty !== null && empty !== void 0 ? empty : resolvedLabels.empty }), hasCommands ? Array.from(grouped.entries()).map(([group, items]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandGroup, {
193
+ heading: group,
194
+ children: items.map((cmd) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CommandItem, {
195
+ value: cmd.id,
196
+ keywords: [
197
+ cmd.label,
198
+ cmd.group,
199
+ ...cmd.shortcut ? [cmd.shortcut] : []
200
+ ],
201
+ onSelect: handleSelect,
202
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: cmd.label }), cmd.shortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandShortcut, { children: cmd.shortcut })]
203
+ }, cmd.id))
204
+ }, group)) : null]
205
+ }),
206
+ footer
207
+ ] });
208
+ if (inline) {
209
+ if (!open) return null;
210
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Command, {
211
+ className: require_pagination.cn("border-border bg-surface w-full max-w-lg border shadow-lg", className),
212
+ "data-opt-id": "ATTF5",
213
+ "data-opt-slug": "command-palette.CommandPalette",
214
+ children: body
215
+ });
216
+ }
217
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandDialog, {
218
+ open,
219
+ onOpenChange,
220
+ title: resolvedLabels.title,
221
+ description: resolvedLabels.description,
222
+ className,
223
+ "data-opt-id": "ATTF5",
224
+ "data-opt-slug": "command-palette.CommandPalette",
225
+ children: body
226
+ });
227
+ }
228
+ //#endregion
229
+ //#region src/shells/command-palette-trigger.tsx
230
+ /** Renders the command palette trigger component. */
231
+ function CommandPaletteTrigger({ commands, onSelect, labels }) {
232
+ const [open, setOpen] = (0, react.useState)(false);
233
+ const handleKeyDown = (0, react.useCallback)((e) => {
234
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
235
+ e.preventDefault();
236
+ setOpen(true);
237
+ }
238
+ }, []);
239
+ (0, react.useEffect)(() => {
240
+ if (commands.length === 0) return;
241
+ document.addEventListener("keydown", handleKeyDown);
242
+ return () => document.removeEventListener("keydown", handleKeyDown);
243
+ }, [commands.length, handleKeyDown]);
244
+ if (commands.length === 0) return null;
245
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandPalette, {
246
+ commands,
247
+ open,
248
+ onOpenChange: setOpen,
249
+ onSelect,
250
+ labels,
251
+ "data-opt-id": "4FCDB",
252
+ "data-opt-slug": "command-palette-trigger.CommandPaletteTrigger"
253
+ });
254
+ }
255
+ //#endregion
256
+ //#region src/shells/dashboard-grid.tsx
257
+ const defaultLabels$18 = { ariaLabel: "대시보드 통계" };
258
+ const MIN_DASHBOARD_COLUMNS = 1;
259
+ const MAX_DASHBOARD_COLUMNS = 12;
260
+ function normalizeDashboardColumns(columns) {
261
+ if (!Number.isFinite(columns)) return MIN_DASHBOARD_COLUMNS;
262
+ return Math.min(MAX_DASHBOARD_COLUMNS, Math.max(MIN_DASHBOARD_COLUMNS, Math.floor(columns)));
263
+ }
264
+ /** Renders the dashboard grid component. */
265
+ function DashboardGrid({ stats, columns = 3, labels: customLabels }) {
266
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$18), customLabels);
267
+ const columnCount = normalizeDashboardColumns(columns);
268
+ const rowStyle = { "--opt-dashboard-grid-columns": `repeat(${columnCount}, minmax(0, 1fr))` };
269
+ const rows = [];
270
+ for (let i = 0; i < stats.length; i += columnCount) rows.push(stats.slice(i, i + columnCount));
271
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeProvider, {
272
+ focusLoop: true,
273
+ focusWrap: true,
274
+ "data-opt-id": "BM7NA",
275
+ "data-opt-slug": "dashboard-grid.DashboardGrid",
276
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.Composite, {
277
+ role: "grid",
278
+ "aria-label": labels.ariaLabel,
279
+ className: "gap-group flex flex-col",
280
+ children: rows.map((row, ri) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeRow, {
281
+ className: "gap-group grid grid-cols-1 sm:[grid-template-columns:var(--opt-dashboard-grid-columns)]",
282
+ style: rowStyle,
283
+ children: row.map((stat) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
284
+ className: "data-[active-item]:ring-ring data-[active-item]:ring-offset-ring-offset data-[focus-visible]:ring-ring data-[focus-visible]:ring-offset-ring-offset rounded-xl outline-none data-[active-item]:ring-2 data-[active-item]:ring-offset-2 data-[focus-visible]:ring-2 data-[focus-visible]:ring-offset-2",
285
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { role: "gridcell" }),
286
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.StatCard, require_pagination._objectSpread2({}, stat))
287
+ }, stat.id))
288
+ }, ri))
289
+ })
290
+ });
291
+ }
292
+ //#endregion
293
+ //#region src/shells/search-combobox.tsx
294
+ const defaultLabels$17 = {
295
+ noResults: "결과가 없습니다.",
296
+ loading: "Loading…"
297
+ };
298
+ /** Renders the search combobox component. */
299
+ function SearchCombobox({ items, placeholder = "검색...", label = "검색", onSelect, labels: customLabels, async: isAsync = false, onSearch, debounceMs = 300 }) {
300
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$17), customLabels);
301
+ const inputId = (0, react.useId)();
302
+ const [query, setQuery] = (0, react.useState)("");
303
+ const [asyncResults, setAsyncResults] = (0, react.useState)([]);
304
+ const [loading, setLoading] = (0, react.useState)(false);
305
+ const timerRef = (0, react.useRef)(void 0);
306
+ const searchGenerationRef = (0, react.useRef)(0);
307
+ const syncMatches = (0, react.useMemo)(() => {
308
+ if (isAsync) return [];
309
+ if (!query) return items;
310
+ return (0, match_sorter.matchSorter)(items, query);
311
+ }, [
312
+ query,
313
+ items,
314
+ isAsync
315
+ ]);
316
+ const handleInput = (event) => {
317
+ searchGenerationRef.current += 1;
318
+ setQuery(event.currentTarget.value);
319
+ };
320
+ const handleValueChange = (item) => {
321
+ searchGenerationRef.current += 1;
322
+ setQuery(item);
323
+ onSelect === null || onSelect === void 0 || onSelect(item);
324
+ };
325
+ (0, react.useEffect)(() => {
326
+ const generation = ++searchGenerationRef.current;
327
+ if (timerRef.current) clearTimeout(timerRef.current);
328
+ if (!isAsync || !onSearch) {
329
+ setLoading(false);
330
+ return;
331
+ }
332
+ if (!query) {
333
+ setAsyncResults([]);
334
+ setLoading(false);
335
+ return;
336
+ }
337
+ setLoading(true);
338
+ timerRef.current = setTimeout(async () => {
339
+ try {
340
+ const results = await onSearch(query);
341
+ if (searchGenerationRef.current !== generation) return;
342
+ setAsyncResults(results);
343
+ } finally {
344
+ if (searchGenerationRef.current === generation) setLoading(false);
345
+ }
346
+ }, debounceMs);
347
+ return () => {
348
+ if (timerRef.current) clearTimeout(timerRef.current);
349
+ };
350
+ }, [
351
+ query,
352
+ isAsync,
353
+ onSearch,
354
+ debounceMs
355
+ ]);
356
+ const matches = isAsync ? asyncResults : syncMatches;
357
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
358
+ className: "gap-element flex flex-col",
359
+ "data-opt-id": "KH4TQ",
360
+ "data-opt-slug": "search-combobox.SearchCombobox",
361
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.ComboboxRoot, {
362
+ onValueChange: handleValueChange,
363
+ children: [
364
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
365
+ htmlFor: inputId,
366
+ className: require_pagination.OPT_TEXT_LABEL,
367
+ children: label
368
+ }),
369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ComboboxInput, {
370
+ id: inputId,
371
+ placeholder,
372
+ className: "max-w-xs",
373
+ "aria-busy": loading || void 0,
374
+ onInput: handleInput
375
+ }),
376
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ComboboxPopover, {
377
+ className: "max-w-xs",
378
+ children: isAsync && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
379
+ className: "text-text-secondary gap-element flex items-center px-3 py-2 text-sm",
380
+ role: "status",
381
+ "aria-live": "polite",
382
+ "aria-busy": "true",
383
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Spinner, {
384
+ size: "sm",
385
+ "aria-hidden": "true"
386
+ }), labels.loading]
387
+ }) : matches.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ComboboxEmpty, { children: labels.noResults }) : matches.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ComboboxItem, {
388
+ value: item,
389
+ children: item
390
+ }, item))
391
+ })
392
+ ]
393
+ })
394
+ });
395
+ }
396
+ //#endregion
397
+ //#region src/shells/status-select.tsx
398
+ const defaultLabels$16 = {
399
+ selectLabel: "상태",
400
+ emptyMessage: "사용 가능한 상태 옵션이 없습니다."
401
+ };
402
+ /** Renders the status select component. */
403
+ function StatusSelect({ options, value: controlledValue, onChange, label, labels: customLabels }) {
404
+ var _options$0$value, _options$;
405
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$16), customLabels);
406
+ const resolvedLabel = label !== null && label !== void 0 ? label : labels.selectLabel;
407
+ const [internalValue, setInternalValue] = (0, react.useState)((_options$0$value = (_options$ = options[0]) === null || _options$ === void 0 ? void 0 : _options$.value) !== null && _options$0$value !== void 0 ? _options$0$value : "");
408
+ const value = controlledValue !== null && controlledValue !== void 0 ? controlledValue : internalValue;
409
+ const selected = options.find((s) => s.value === value);
410
+ if (options.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
411
+ className: "gap-element flex flex-col",
412
+ "data-opt-id": "R46E6",
413
+ "data-opt-slug": "status-select.StatusSelect",
414
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
415
+ className: require_pagination.OPT_TEXT_LABEL,
416
+ children: resolvedLabel
417
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
418
+ className: "text-text-tertiary text-sm",
419
+ children: labels.emptyMessage
420
+ })]
421
+ });
422
+ const handleChange = (v) => {
423
+ var _v$;
424
+ const next = Array.isArray(v) ? (_v$ = v[0]) !== null && _v$ !== void 0 ? _v$ : "" : v;
425
+ if (onChange) onChange(next);
426
+ else setInternalValue(next);
427
+ };
428
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
429
+ className: "gap-element flex flex-col",
430
+ "data-opt-id": "R46E6",
431
+ "data-opt-slug": "status-select.StatusSelect",
432
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectRoot, {
433
+ value,
434
+ onValueChange: handleChange,
435
+ children: [
436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectLabel, {
437
+ className: require_pagination.OPT_TEXT_LABEL,
438
+ children: resolvedLabel
439
+ }),
440
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectTrigger, {
441
+ className: `${require_pagination.OPT_INPUT} max-w-xs items-center justify-between ${require_pagination.OPT_FOCUS}`,
442
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
443
+ className: "gap-element flex items-center",
444
+ children: [(selected === null || selected === void 0 ? void 0 : selected.color) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `h-2 w-2 rounded-full ${selected.color}` }), selected === null || selected === void 0 ? void 0 : selected.label]
445
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
446
+ className: "text-text-tertiary h-4 w-4",
447
+ fill: "none",
448
+ viewBox: "0 0 24 24",
449
+ stroke: "currentColor",
450
+ strokeWidth: 2,
451
+ "aria-hidden": "true",
452
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
453
+ strokeLinecap: "round",
454
+ strokeLinejoin: "round",
455
+ d: "m19.5 8.25-7.5 7.5-7.5-7.5"
456
+ })
457
+ })]
458
+ }),
459
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectPopover, {
460
+ className: `${require_pagination.OPT_SURFACE_POPOVER} ${require_pagination.OPT_ANIMATE_ENTER_EXIT} max-w-xs`,
461
+ children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectItem, {
462
+ value: option.value,
463
+ children: [option.color && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `h-2 w-2 rounded-full ${option.color}` }), option.label]
464
+ }, option.value))
465
+ })
466
+ ]
467
+ })
468
+ });
469
+ }
470
+ //#endregion
471
+ //#region src/shells/app-menubar.tsx
472
+ const defaultLabels$15 = { ariaLabel: "Application menu" };
473
+ function MenuSection({ menu }) {
474
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.MenuRoot, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.MenuTrigger, { children: [menu.label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenuButtonArrow, {})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenuPopover, { children: menu.items.map((item, i) => {
475
+ if (item.type === "separator") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenuSeparator, {}, `sep-${i}`);
476
+ if (item.type === "checkbox") {
477
+ var _item$name, _item$value;
478
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenuItemCheckbox, {
479
+ name: (_item$name = item.name) !== null && _item$name !== void 0 ? _item$name : item.id,
480
+ value: (_item$value = item.value) !== null && _item$value !== void 0 ? _item$value : item.id,
481
+ checked: item.checked,
482
+ onChange: item.onChange,
483
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
484
+ className: "gap-element flex items-center",
485
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
486
+ className: "w-4 text-center",
487
+ children: item.checked ? "✓" : ""
488
+ }), item.label]
489
+ })
490
+ }, item.id);
491
+ }
492
+ if (item.type === "radio") {
493
+ var _item$name2, _item$value2;
494
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenuItemRadio, {
495
+ name: (_item$name2 = item.name) !== null && _item$name2 !== void 0 ? _item$name2 : "",
496
+ value: (_item$value2 = item.value) !== null && _item$value2 !== void 0 ? _item$value2 : "",
497
+ checked: item.checked,
498
+ onChange: item.onChange,
499
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
500
+ className: "gap-element flex items-center",
501
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
502
+ className: "w-4 text-center",
503
+ children: item.checked ? "●" : ""
504
+ }), item.label]
505
+ })
506
+ }, item.id);
507
+ }
508
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.MenuItem, {
509
+ onClick: item.onClick,
510
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.label }), item.shortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Kbd, { children: item.shortcut })]
511
+ }, item.id);
512
+ }) })] });
513
+ }
514
+ /** Renders the app menubar component. */
515
+ function AppMenubar({ menus, labels: customLabels }) {
516
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$15), customLabels);
517
+ if (menus.length === 0) return null;
518
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenubarRoot, {
519
+ "data-opt-id": "BGKHW",
520
+ "data-opt-slug": "app-menubar.AppMenubar",
521
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenubarContainer, {
522
+ "aria-label": labels.ariaLabel,
523
+ children: menus.map((menu) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuSection, { menu }, menu.id))
524
+ })
525
+ });
526
+ }
527
+ AppMenubar.displayName = "AppMenubar";
528
+ //#endregion
529
+ //#region src/shells/sidebar-nav.tsx
530
+ const NAV_ID_PREFIX = "sidebar-nav-";
531
+ function domId(id) {
532
+ return `${NAV_ID_PREFIX}${id}`;
533
+ }
534
+ const sectionStyles = {
535
+ 시작하기: "text-accent",
536
+ "핵심 개념": "text-info-fg",
537
+ 구축·운영: "text-success-fg",
538
+ 도구: "text-warning-fg",
539
+ DataGrid: "text-accent",
540
+ "컴포넌트 · Core": "text-accent",
541
+ "컴포넌트 · Visuals": "text-success-fg",
542
+ "컴포넌트 · Shells": "text-warning-fg",
543
+ "컴포넌트 · Surfaces": "text-info-fg",
544
+ "기본 컴포넌트": "text-accent",
545
+ "블록 컴포넌트": "text-info-fg",
546
+ "조합 컴포넌트": "text-warning-fg",
547
+ 템플릿: "text-text-tertiary",
548
+ 릴리즈: "text-text-tertiary",
549
+ Primitives: "text-accent",
550
+ Blocks: "text-info-fg",
551
+ Compositions: "text-warning-fg",
552
+ Templates: "text-text-tertiary"
553
+ };
554
+ const defaultSectionStyle = "text-text-secondary";
555
+ const defaultLabels$14 = { ariaLabel: "문서 네비게이션" };
556
+ /** Renders the sidebar nav component. */
557
+ function SidebarNav({ items, labels: customLabels }) {
558
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$14), customLabels);
559
+ const pathname = (0, next_navigation.usePathname)();
560
+ const activeItem = items.find((item) => item.href === pathname);
561
+ const [activeId, setActiveId] = (0, react.useState)(activeItem ? domId(activeItem.id) : void 0);
562
+ (0, react.useEffect)(() => {
563
+ const match = items.find((item) => item.href === pathname);
564
+ if (match) setActiveId(domId(match.id));
565
+ }, [pathname, items]);
566
+ if (items.length === 0) return null;
567
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeProvider, {
568
+ focusLoop: true,
569
+ orientation: "vertical",
570
+ activeId,
571
+ onActiveIdChange: setActiveId,
572
+ "data-opt-id": "1FZNH",
573
+ "data-opt-slug": "sidebar-nav.SidebarNav",
574
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.Composite, {
575
+ role: "navigation",
576
+ "aria-label": labels.ariaLabel,
577
+ className: "flex flex-col",
578
+ children: items.map((item, index) => {
579
+ var _sectionStyles$item$s, _item$sectionLabel;
580
+ const isSelected = pathname === item.href;
581
+ const isFirstItem = index === 0;
582
+ const sectionTextStyle = item.sectionLabel ? (_sectionStyles$item$s = sectionStyles[item.sectionLabel]) !== null && _sectionStyles$item$s !== void 0 ? _sectionStyles$item$s : defaultSectionStyle : defaultSectionStyle;
583
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [item.separator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
584
+ className: require_pagination.cn("mb-2 pt-2", !isFirstItem && "mt-7"),
585
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
586
+ className: require_pagination.cn("px-2 text-[11px] font-semibold tracking-wider uppercase", sectionTextStyle),
587
+ children: (_item$sectionLabel = item.sectionLabel) !== null && _item$sectionLabel !== void 0 ? _item$sectionLabel : ""
588
+ })
589
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.CompositeItem, {
590
+ id: domId(item.id),
591
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(next_link.default, {
592
+ href: item.href,
593
+ className: require_pagination.cn("group relative block rounded-md px-2 py-1.5 text-[13px] leading-snug outline-none", "transition-all duration-150", !isSelected && ["text-text-secondary", "hover:bg-bg-subtle hover:text-text-primary"], isSelected && ["bg-bg-subtle text-text-primary font-medium"], "data-[active-item]:bg-accent-subtle data-[active-item]:text-accent", "data-[focus-visible]:ring-ring data-[focus-visible]:ring-2", "data-[focus-visible]:ring-offset-ring-offset data-[focus-visible]:ring-offset-2", isSelected && ["data-[active-item]:bg-accent-subtle data-[active-item]:text-accent"])
594
+ }),
595
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: require_pagination.cn("absolute top-1/2 left-0 h-4 w-0.5 -translate-y-1/2 rounded-full transition-all duration-150", isSelected && "bg-accent", !isSelected && "group-data-[active-item]:bg-accent bg-transparent", "group-data-[focus-visible]:bg-accent") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
596
+ className: "pl-2",
597
+ children: item.label
598
+ })]
599
+ })] }, item.id);
600
+ })
601
+ })
602
+ });
603
+ }
604
+ //#endregion
605
+ //#region src/shells/editor-toolbar.tsx
606
+ const defaultLabels$13 = { ariaLabel: "텍스트 편집기 툴바" };
607
+ function ToolbarButtonWithTooltip({ button, active, onAction }) {
608
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.TooltipProvider, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.TooltipAnchor, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ToolbarButton, {
609
+ className: `text-text-primary data-[active-item]:bg-bg-subtle data-[focus-visible]:ring-ring rounded-md p-2 transition-colors outline-none data-[focus-visible]:ring-2 ${active ? "bg-bg-muted" : "hover:bg-bg-subtle"}`,
610
+ onClick: () => onAction === null || onAction === void 0 ? void 0 : onAction(button.id),
611
+ children: button.icon
612
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Tooltip, { children: button.label })] });
613
+ }
614
+ /** Renders the editor toolbar component. */
615
+ function EditorToolbar({ buttons, onAction, activeButtons = [], labels: customLabels }) {
616
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$13), customLabels);
617
+ const groups = [];
618
+ let current = [];
619
+ for (const btn of buttons) {
620
+ if (btn.separator) {
621
+ groups.push(current);
622
+ current = [];
623
+ }
624
+ current.push(btn);
625
+ }
626
+ if (current.length) groups.push(current);
627
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ToolbarRoot, {
628
+ "data-opt-id": "Z73K6",
629
+ "data-opt-slug": "editor-toolbar.EditorToolbar",
630
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ToolbarContainer, {
631
+ "aria-label": labels.ariaLabel,
632
+ children: groups.map((group, gi) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
633
+ className: "contents",
634
+ children: [gi > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ToolbarSeparator, {}), group.map((item) => {
635
+ if (item === "separator") return null;
636
+ const btn = item;
637
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButtonWithTooltip, {
638
+ button: btn,
639
+ active: activeButtons.includes(btn.id),
640
+ onAction
641
+ }, btn.id);
642
+ })]
643
+ }, gi))
644
+ })
645
+ });
646
+ }
647
+ //#endregion
648
+ //#region src/shells/content-tabs.tsx
649
+ const defaultLabels$12 = {
650
+ ariaLabel: "콘텐츠 탭",
651
+ emptyMessage: "표시할 탭이 없습니다."
652
+ };
653
+ /** Renders the content tabs component. */
654
+ function ContentTabs({ tabs, defaultTabId, labels: customLabels }) {
655
+ var _tabs$, _tabs$find;
656
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$12), customLabels);
657
+ const [selectedTabId, setSelectedTabId] = (0, react.useState)(defaultTabId !== null && defaultTabId !== void 0 ? defaultTabId : (_tabs$ = tabs[0]) === null || _tabs$ === void 0 ? void 0 : _tabs$.id);
658
+ if (tabs.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
659
+ className: "text-text-secondary text-sm",
660
+ "data-opt-id": "9W97K",
661
+ "data-opt-slug": "content-tabs.ContentTabs",
662
+ children: labels.emptyMessage
663
+ });
664
+ const handleSelectedTabId = (nextId) => {
665
+ var _tabs$2;
666
+ setSelectedTabId(nextId !== null && nextId !== void 0 ? nextId : (_tabs$2 = tabs[0]) === null || _tabs$2 === void 0 ? void 0 : _tabs$2.id);
667
+ };
668
+ const activeTab = (_tabs$find = tabs.find((tab) => tab.id === selectedTabId)) !== null && _tabs$find !== void 0 ? _tabs$find : tabs[0];
669
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.TabsRoot, {
670
+ selectedId: selectedTabId,
671
+ onSelectedIdChange: handleSelectedTabId,
672
+ "data-opt-id": "9W97K",
673
+ "data-opt-slug": "content-tabs.ContentTabs",
674
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.TabList, {
675
+ "aria-label": labels.ariaLabel,
676
+ children: tabs.map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Tab, {
677
+ id: tab.id,
678
+ children: tab.label
679
+ }, tab.id))
680
+ }), activeTab && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.TabPanel, {
681
+ tabId: activeTab.id,
682
+ children: activeTab.content
683
+ })]
684
+ });
685
+ }
686
+ //#endregion
687
+ //#region src/shells/faq-accordion.tsx
688
+ const defaultLabels$11 = {
689
+ ariaLabel: "자주 묻는 질문",
690
+ emptyMessage: "등록된 FAQ가 없습니다."
691
+ };
692
+ /** Renders the faq accordion component. */
693
+ function FaqAccordion({ items, labels: customLabels }) {
694
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$11), customLabels);
695
+ if (items.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
696
+ className: "text-text-secondary text-sm",
697
+ "data-opt-id": "QRBHV",
698
+ "data-opt-slug": "faq-accordion.FaqAccordion",
699
+ children: labels.emptyMessage
700
+ });
701
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeProvider, {
702
+ focusLoop: true,
703
+ orientation: "vertical",
704
+ "data-opt-id": "QRBHV",
705
+ "data-opt-slug": "faq-accordion.FaqAccordion",
706
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.Composite, {
707
+ role: "region",
708
+ "aria-label": labels.ariaLabel,
709
+ className: "divide-border-subtle border-border divide-y rounded-lg border",
710
+ children: items.map((faq) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.DisclosureRoot, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.CompositeItem, {
711
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.DisclosureTrigger, {}),
712
+ children: [faq.question, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
713
+ className: "text-text-tertiary h-4 w-4 shrink-0 transition-transform [[data-open]>&]:rotate-180",
714
+ fill: "none",
715
+ viewBox: "0 0 24 24",
716
+ stroke: "currentColor",
717
+ strokeWidth: 2,
718
+ "aria-hidden": "true",
719
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
720
+ strokeLinecap: "round",
721
+ strokeLinejoin: "round",
722
+ d: "m19.5 8.25-7.5 7.5-7.5-7.5"
723
+ })
724
+ })]
725
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.DisclosureContent, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
726
+ className: "text-text-secondary px-4 pt-2 pb-4 text-sm",
727
+ children: faq.answer
728
+ }) })] }) }, faq.id))
729
+ })
730
+ });
731
+ }
732
+ //#endregion
733
+ //#region src/shells/settings-form.tsx
734
+ const defaultLabels$10 = {
735
+ formAriaLabel: "설정 폼",
736
+ submitButton: "저장",
737
+ successMessage: "설정이 저장되었습니다."
738
+ };
739
+ /** Renders the settings form component. */
740
+ function SettingsForm({ fields, onSubmit, labels: customLabels }) {
741
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$10), customLabels);
742
+ const [submitted, setSubmitted] = (0, react.useState)(false);
743
+ const defaultValues = {};
744
+ for (const field of fields) {
745
+ var _field$defaultValue;
746
+ defaultValues[field.name] = (_field$defaultValue = field.defaultValue) !== null && _field$defaultValue !== void 0 ? _field$defaultValue : field.type === "switch" || field.type === "checkbox" ? false : field.type === "tags" ? [] : "";
747
+ }
748
+ const form = (0, _reopt_ai_opt_ui_primitives.useFormStore)({
749
+ defaultValues,
750
+ validate: (values) => {
751
+ const errors = {};
752
+ for (const field of fields) {
753
+ if (field.required) {
754
+ const value = values[field.name];
755
+ if (!value || typeof value === "string" && !value.trim()) {
756
+ var _field$requiredMessag;
757
+ errors[field.name] = (_field$requiredMessag = field.requiredMessage) !== null && _field$requiredMessag !== void 0 ? _field$requiredMessag : `${field.label}을(를) 입력해주세요.`;
758
+ }
759
+ }
760
+ if (field.validate) {
761
+ const value = values[field.name];
762
+ const error = field.validate(value);
763
+ if (error) errors[field.name] = error;
764
+ }
765
+ }
766
+ return errors;
767
+ },
768
+ onSubmit: (values) => {
769
+ setSubmitted(true);
770
+ onSubmit === null || onSubmit === void 0 || onSubmit(values);
771
+ }
772
+ });
773
+ if (fields.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.EmptyState, {
774
+ title: "설정 항목이 없습니다.",
775
+ description: "표시할 설정 필드를 추가하면 이 폼이 렌더링됩니다.",
776
+ "data-opt-id": "9TEEJ",
777
+ "data-opt-slug": "settings-form.SettingsForm"
778
+ });
779
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
780
+ className: "gap-group flex flex-col",
781
+ "data-opt-id": "9TEEJ",
782
+ "data-opt-slug": "settings-form.SettingsForm",
783
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.FormRoot, {
784
+ store: form,
785
+ "aria-label": labels.formAriaLabel,
786
+ children: [fields.map((field) => {
787
+ var _field$type;
788
+ if (field.visible !== void 0) {
789
+ const currentValues = form.getValues();
790
+ if (!(typeof field.visible === "function" ? field.visible(currentValues) : field.visible)) return null;
791
+ }
792
+ if (field.type === "tags") {
793
+ const rawValue = form.getValue(field.name);
794
+ const tags = Array.isArray(rawValue) ? rawValue : [];
795
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
796
+ className: "gap-element flex flex-col",
797
+ children: [
798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormLabel, {
799
+ name: field.name,
800
+ children: field.label
801
+ }),
802
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.TagInput, {
803
+ value: tags,
804
+ onChange: (next) => form.setValue(field.name, next),
805
+ placeholder: field.placeholder
806
+ }),
807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormError, { name: field.name })
808
+ ]
809
+ }, field.name);
810
+ }
811
+ if (field.type === "radio") {
812
+ var _field$options;
813
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
814
+ className: "gap-element flex flex-col",
815
+ children: [
816
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", {
817
+ className: "text-text-primary text-sm font-medium",
818
+ children: field.label
819
+ }),
820
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormRadioGroup, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
821
+ className: "gap-group flex",
822
+ children: (_field$options = field.options) === null || _field$options === void 0 ? void 0 : _field$options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
823
+ className: "text-text-primary gap-element flex items-center text-sm",
824
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormRadio, {
825
+ name: field.name,
826
+ value: option.value
827
+ }), option.label]
828
+ }, option.value))
829
+ }) }),
830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormError, { name: field.name })
831
+ ]
832
+ }, field.name);
833
+ }
834
+ if (field.type === "checkbox") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
835
+ className: "gap-element flex flex-col",
836
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
837
+ className: "text-text-primary gap-element flex items-center text-sm",
838
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormCheckbox, { name: field.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: field.label })]
839
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormError, { name: field.name })]
840
+ }, field.name);
841
+ if (field.type === "textarea") {
842
+ var _field$rows, _field$resize;
843
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
844
+ className: "gap-element flex flex-col",
845
+ children: [
846
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormLabel, {
847
+ name: field.name,
848
+ children: field.label
849
+ }),
850
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormTextarea, {
851
+ name: field.name,
852
+ placeholder: field.placeholder,
853
+ rows: (_field$rows = field.rows) !== null && _field$rows !== void 0 ? _field$rows : 4,
854
+ resize: (_field$resize = field.resize) !== null && _field$resize !== void 0 ? _field$resize : "vertical",
855
+ className: field.font === "mono" ? "font-mono text-sm" : void 0
856
+ }),
857
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormError, { name: field.name })
858
+ ]
859
+ }, field.name);
860
+ }
861
+ if (field.type === "select") {
862
+ var _field$options2;
863
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
864
+ className: "gap-element flex flex-col",
865
+ children: [
866
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormLabel, {
867
+ name: field.name,
868
+ children: field.label
869
+ }),
870
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormSelect, {
871
+ name: field.name,
872
+ placeholder: field.placeholder,
873
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormSelectPopover, { children: (_field$options2 = field.options) === null || _field$options2 === void 0 ? void 0 : _field$options2.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormSelectItem, {
874
+ value: option.value,
875
+ children: option.label
876
+ }, option.value)) })
877
+ }),
878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormError, { name: field.name })
879
+ ]
880
+ }, field.name);
881
+ }
882
+ if (field.type === "switch") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
883
+ className: "gap-group flex items-center",
884
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormSwitch, { name: field.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
885
+ className: "flex flex-col",
886
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
887
+ className: "text-text-primary text-sm font-medium",
888
+ children: field.label
889
+ }), field.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
890
+ className: "text-text-secondary text-sm",
891
+ children: field.description
892
+ })]
893
+ })]
894
+ }, field.name);
895
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
896
+ className: "gap-element flex flex-col",
897
+ children: [
898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormLabel, {
899
+ name: field.name,
900
+ children: field.label
901
+ }),
902
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormInput, {
903
+ name: field.name,
904
+ type: (_field$type = field.type) !== null && _field$type !== void 0 ? _field$type : "text",
905
+ placeholder: field.placeholder
906
+ }),
907
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormError, { name: field.name })
908
+ ]
909
+ }, field.name);
910
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.FormSubmit, { children: labels.submitButton })]
911
+ }), submitted && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
912
+ className: "border-success bg-success-subtle text-success-fg rounded-lg border p-3 text-sm",
913
+ children: labels.successMessage
914
+ })]
915
+ });
916
+ }
917
+ //#endregion
918
+ //#region src/shells/deployment-timeline.tsx
919
+ const statusIcon = {
920
+ success: "●",
921
+ pending: "◌",
922
+ running: "⟳",
923
+ error: "✗",
924
+ skipped: "○"
925
+ };
926
+ const statusColor = {
927
+ success: "text-success-fg",
928
+ pending: require_pagination.OPT_TEXT_TERTIARY,
929
+ running: "text-accent",
930
+ error: "text-danger-fg",
931
+ skipped: "text-text-tertiary"
932
+ };
933
+ const statusBadge$1 = {
934
+ success: require_pagination.OPT_BADGE_GREEN,
935
+ pending: require_pagination.OPT_BADGE_NEUTRAL,
936
+ running: "inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium bg-info-subtle text-info-fg",
937
+ error: require_pagination.OPT_BADGE_RED,
938
+ skipped: require_pagination.OPT_BADGE_NEUTRAL
939
+ };
940
+ const statusLabel$1 = {
941
+ success: "성공",
942
+ pending: "대기",
943
+ running: "실행 중",
944
+ error: "에러",
945
+ skipped: "건너뜀"
946
+ };
947
+ /** Renders the deployment timeline component. */
948
+ function DeploymentTimeline({ steps, title = "배포 타임라인" }) {
949
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
950
+ className: "gap-group flex flex-col",
951
+ "data-opt-id": "XXVQ2",
952
+ "data-opt-slug": "deployment-timeline.DeploymentTimeline",
953
+ children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
954
+ className: `text-sm font-semibold text-text-primary`,
955
+ children: title
956
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeProvider, {
957
+ focusLoop: true,
958
+ orientation: "vertical",
959
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.Composite, {
960
+ role: "list",
961
+ "aria-label": title,
962
+ className: "relative",
963
+ children: steps.map((step, i) => {
964
+ const isLast = i === steps.length - 1;
965
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.CompositeItem, {
966
+ className: `group gap-group relative flex w-full scroll-m-2 rounded-lg px-3 py-2 ${require_pagination.OPT_FOCUS} data-[active-item]:bg-accent-subtle`,
967
+ role: "listitem",
968
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
969
+ className: "flex flex-col items-center",
970
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
971
+ className: `text-lg leading-none ${statusColor[step.status]}`,
972
+ "aria-label": statusLabel$1[step.status],
973
+ children: statusIcon[step.status]
974
+ }), !isLast && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "bg-border-subtle mt-1 w-px flex-1" })]
975
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
976
+ className: "flex-1 pb-3",
977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
978
+ className: "gap-element flex items-center",
979
+ children: [
980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
981
+ className: `text-sm font-medium ${require_pagination.OPT_TEXT_PRIMARY}`,
982
+ children: step.name
983
+ }),
984
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
985
+ className: statusBadge$1[step.status],
986
+ children: statusLabel$1[step.status]
987
+ }),
988
+ step.duration && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
989
+ className: `text-xs text-text-tertiary`,
990
+ children: step.duration
991
+ })
992
+ ]
993
+ }), step.detail && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
994
+ className: `mt-0.5 text-xs text-text-secondary`,
995
+ children: step.detail
996
+ })]
997
+ })]
998
+ }, step.id);
999
+ })
1000
+ })
1001
+ })]
1002
+ });
1003
+ }
1004
+ //#endregion
1005
+ //#region src/shells/project-switcher.tsx
1006
+ const statusDot = {
1007
+ active: require_pagination.OPT_DOT_GREEN,
1008
+ paused: require_pagination.OPT_DOT_YELLOW,
1009
+ error: require_pagination.OPT_DOT_RED
1010
+ };
1011
+ /** Renders the project switcher component. */
1012
+ function ProjectSwitcher({ projects, value, onChange, placeholder = "프로젝트 검색..." }) {
1013
+ const [search, setSearch] = (0, react.useState)("");
1014
+ const matches = (0, react.useMemo)(() => {
1015
+ if (!search) return projects;
1016
+ return (0, match_sorter.matchSorter)(projects, search, { keys: ["name", "framework"] });
1017
+ }, [search, projects]);
1018
+ const selected = projects.find((p) => p.id === value);
1019
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1020
+ className: "gap-element flex flex-col",
1021
+ "data-opt-id": "5R3AG",
1022
+ "data-opt-slug": "project-switcher.ProjectSwitcher",
1023
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.ComboboxRoot, {
1024
+ onValueChange: setSearch,
1025
+ children: [
1026
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1027
+ className: require_pagination.OPT_TEXT_LABEL,
1028
+ children: "프로젝트"
1029
+ }),
1030
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ComboboxInput, {
1031
+ placeholder: selected ? selected.name : placeholder,
1032
+ className: require_pagination.OPT_INPUT
1033
+ }),
1034
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.ComboboxPopover, {
1035
+ className: "max-h-72 overflow-y-auto",
1036
+ children: [matches.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1037
+ className: `py-6 text-center text-sm text-text-secondary`,
1038
+ children: "결과가 없습니다."
1039
+ }), matches.map((project) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.ComboboxItem, {
1040
+ value: project.name,
1041
+ onClick: () => onChange === null || onChange === void 0 ? void 0 : onChange(project.id),
1042
+ className: `gap-element flex scroll-m-2 items-center ${require_pagination.OPT_ITEM_BASE} ${require_pagination.OPT_ACTIVE_ITEM}`,
1043
+ children: [
1044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `shrink-0 ${statusDot[project.status]}` }),
1045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1046
+ className: "flex-1 truncate font-medium",
1047
+ children: project.name
1048
+ }),
1049
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1050
+ className: require_pagination.OPT_BADGE_NEUTRAL,
1051
+ children: project.framework
1052
+ }),
1053
+ project.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1054
+ className: `truncate text-xs text-text-secondary data-[active-item]:text-accent-fg/70`,
1055
+ children: project.domain
1056
+ })
1057
+ ]
1058
+ }, project.id))]
1059
+ })
1060
+ ]
1061
+ })
1062
+ });
1063
+ }
1064
+ ProjectSwitcher.displayName = "ProjectSwitcher";
1065
+ //#endregion
1066
+ //#region src/shells/env-panel.tsx
1067
+ const defaultLabels$9 = {
1068
+ varCount: (count) => `${count}개 변수`,
1069
+ envVarsLabel: (env) => `${env} 환경 변수`,
1070
+ secretBadge: "secret"
1071
+ };
1072
+ const envDot = {
1073
+ Production: require_pagination.OPT_DOT_RED,
1074
+ Preview: require_pagination.OPT_DOT_YELLOW,
1075
+ Development: require_pagination.OPT_DOT_GREEN
1076
+ };
1077
+ /** Renders the env panel component. */
1078
+ function EnvPanel({ groups, labels: customLabels }) {
1079
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$9), customLabels);
1080
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1081
+ className: `rounded-lg ${require_pagination.OPT_BORDER} divide-y ${require_pagination.OPT_DIVIDER} bg-surface`,
1082
+ "data-opt-id": "ZBDWK",
1083
+ "data-opt-slug": "env-panel.EnvPanel",
1084
+ children: groups.map((group) => {
1085
+ var _envDot$group$env;
1086
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.DisclosureRoot, {
1087
+ defaultOpen: group.env === "Production",
1088
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.DisclosureTrigger, {
1089
+ className: require_pagination.OPT_DISCLOSURE_TRIGGER,
1090
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1091
+ className: "gap-element flex items-center",
1092
+ children: [
1093
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: (_envDot$group$env = envDot[group.env]) !== null && _envDot$group$env !== void 0 ? _envDot$group$env : "bg-bg-muted h-2 w-2 rounded-full" }),
1094
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1095
+ className: "font-medium",
1096
+ children: group.env
1097
+ }),
1098
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1099
+ className: `text-xs ${require_pagination.OPT_TEXT_TERTIARY}`,
1100
+ children: labels.varCount(group.vars.length)
1101
+ })
1102
+ ]
1103
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1104
+ className: "text-text-tertiary h-4 w-4 shrink-0 transition-transform [[data-open]>&]:rotate-180",
1105
+ fill: "none",
1106
+ viewBox: "0 0 24 24",
1107
+ stroke: "currentColor",
1108
+ strokeWidth: 2,
1109
+ "aria-hidden": "true",
1110
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1111
+ strokeLinecap: "round",
1112
+ strokeLinejoin: "round",
1113
+ d: "m19.5 8.25-7.5 7.5-7.5-7.5"
1114
+ })
1115
+ })]
1116
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.DisclosureContent, {
1117
+ className: require_pagination.OPT_DISCLOSURE_CONTENT,
1118
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeProvider, {
1119
+ focusLoop: true,
1120
+ orientation: "vertical",
1121
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.Composite, {
1122
+ role: "list",
1123
+ "aria-label": labels.envVarsLabel(group.env),
1124
+ className: "px-4 pb-3",
1125
+ children: group.vars.map((v) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1126
+ role: "listitem",
1127
+ className: `gap-group flex scroll-m-2 items-center rounded px-2 py-1.5 text-sm ${require_pagination.OPT_FOCUS} data-[active-item]:bg-accent-subtle`,
1128
+ children: [
1129
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1130
+ className: `w-44 shrink-0 truncate font-mono text-xs font-medium ${require_pagination.OPT_TEXT_PRIMARY}`,
1131
+ children: v.key
1132
+ }),
1133
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1134
+ className: `flex-1 truncate font-mono text-xs ${require_pagination.OPT_TEXT_SECONDARY}`,
1135
+ children: v.isSecret ? "••••••••" : v.value
1136
+ }),
1137
+ v.isSecret && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1138
+ className: require_pagination.OPT_BADGE_NEUTRAL,
1139
+ children: labels.secretBadge
1140
+ })
1141
+ ]
1142
+ }, v.key))
1143
+ })
1144
+ })
1145
+ })] })
1146
+ }, group.env);
1147
+ })
1148
+ });
1149
+ }
1150
+ //#endregion
1151
+ //#region src/shells/branch-select.tsx
1152
+ const defaultLabels$8 = {
1153
+ selectLabel: "브랜치",
1154
+ emptySelectionLabel: "선택...",
1155
+ emptyMessage: "사용 가능한 브랜치가 없습니다.",
1156
+ defaultBadge: "default"
1157
+ };
1158
+ /** Renders the branch select component. */
1159
+ function BranchSelect({ branches, value: controlledValue, onChange, label, labels: customLabels }) {
1160
+ var _ref, _defaultBranch$name, _branches$, _selected$name;
1161
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$8), customLabels);
1162
+ const resolvedLabel = label !== null && label !== void 0 ? label : labels.selectLabel;
1163
+ const defaultBranch = branches.find((b) => b.isDefault);
1164
+ const [internalValue, setInternalValue] = (0, react.useState)((_ref = (_defaultBranch$name = defaultBranch === null || defaultBranch === void 0 ? void 0 : defaultBranch.name) !== null && _defaultBranch$name !== void 0 ? _defaultBranch$name : (_branches$ = branches[0]) === null || _branches$ === void 0 ? void 0 : _branches$.name) !== null && _ref !== void 0 ? _ref : "");
1165
+ const value = controlledValue !== null && controlledValue !== void 0 ? controlledValue : internalValue;
1166
+ const selected = branches.find((b) => b.name === value);
1167
+ if (branches.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1168
+ className: "gap-element flex flex-col",
1169
+ "data-opt-id": "7EDEP",
1170
+ "data-opt-slug": "branch-select.BranchSelect",
1171
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1172
+ className: require_pagination.OPT_TEXT_LABEL,
1173
+ children: resolvedLabel
1174
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1175
+ className: require_pagination.OPT_TEXT_TERTIARY,
1176
+ children: labels.emptyMessage
1177
+ })]
1178
+ });
1179
+ const handleChange = (v) => {
1180
+ var _v$;
1181
+ const next = Array.isArray(v) ? (_v$ = v[0]) !== null && _v$ !== void 0 ? _v$ : "" : v;
1182
+ if (onChange) onChange(next);
1183
+ else setInternalValue(next);
1184
+ };
1185
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1186
+ className: "gap-element flex flex-col",
1187
+ "data-opt-id": "7EDEP",
1188
+ "data-opt-slug": "branch-select.BranchSelect",
1189
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectRoot, {
1190
+ value,
1191
+ onValueChange: handleChange,
1192
+ children: [
1193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectLabel, {
1194
+ className: require_pagination.OPT_TEXT_LABEL,
1195
+ children: resolvedLabel
1196
+ }),
1197
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectTrigger, {
1198
+ className: `${require_pagination.OPT_INPUT} max-w-xs items-center justify-between gap-1`,
1199
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1200
+ className: "gap-element flex items-center truncate",
1201
+ children: [
1202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1203
+ className: "text-text-tertiary h-4 w-4 shrink-0",
1204
+ fill: "none",
1205
+ viewBox: "0 0 24 24",
1206
+ stroke: "currentColor",
1207
+ strokeWidth: 2,
1208
+ "aria-hidden": "true",
1209
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1210
+ strokeLinecap: "round",
1211
+ strokeLinejoin: "round",
1212
+ d: "M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"
1213
+ })
1214
+ }),
1215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1216
+ className: "truncate",
1217
+ children: (_selected$name = selected === null || selected === void 0 ? void 0 : selected.name) !== null && _selected$name !== void 0 ? _selected$name : labels.emptySelectionLabel
1218
+ }),
1219
+ (selected === null || selected === void 0 ? void 0 : selected.isDefault) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1220
+ className: require_pagination.OPT_BADGE_BLUE,
1221
+ children: labels.defaultBadge
1222
+ })
1223
+ ]
1224
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1225
+ className: "text-text-tertiary h-4 w-4 shrink-0",
1226
+ fill: "none",
1227
+ viewBox: "0 0 24 24",
1228
+ stroke: "currentColor",
1229
+ strokeWidth: 2,
1230
+ "aria-hidden": "true",
1231
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1232
+ strokeLinecap: "round",
1233
+ strokeLinejoin: "round",
1234
+ d: "m19.5 8.25-7.5 7.5-7.5-7.5"
1235
+ })
1236
+ })]
1237
+ }),
1238
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectPopover, {
1239
+ className: "max-w-xs",
1240
+ children: branches.map((branch) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectItem, {
1241
+ value: branch.name,
1242
+ className: `gap-element flex scroll-m-2 items-center ${require_pagination.OPT_ITEM_BASE} ${require_pagination.OPT_ACTIVE_ITEM}`,
1243
+ children: [
1244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1245
+ className: "flex-1 truncate",
1246
+ children: branch.name
1247
+ }),
1248
+ branch.isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1249
+ className: "bg-accent-subtle text-accent data-[active-item]:bg-accent-fg/20 data-[active-item]:text-accent-fg rounded-full px-2 py-0.5 text-[11px] font-medium",
1250
+ children: labels.defaultBadge
1251
+ }),
1252
+ branch.lastCommit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1253
+ className: `font-mono text-xs text-text-tertiary data-[active-item]:text-accent-fg/60`,
1254
+ children: branch.lastCommit
1255
+ }),
1256
+ branch.author && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1257
+ className: `text-xs text-text-tertiary data-[active-item]:text-accent-fg/60`,
1258
+ children: branch.author
1259
+ })
1260
+ ]
1261
+ }, branch.name))
1262
+ })
1263
+ ]
1264
+ })
1265
+ });
1266
+ }
1267
+ BranchSelect.displayName = "BranchSelect";
1268
+ //#endregion
1269
+ //#region src/shells/domain-table.tsx
1270
+ const defaultLabels$7 = {
1271
+ domain: "Domain",
1272
+ status: "Status",
1273
+ ssl: "SSL",
1274
+ redirect: "Redirect",
1275
+ added: "Added",
1276
+ emptyMessage: "No domains configured"
1277
+ };
1278
+ const statusBadge = {
1279
+ active: require_pagination.OPT_BADGE_GREEN,
1280
+ pending: require_pagination.OPT_BADGE_YELLOW,
1281
+ error: require_pagination.OPT_BADGE_RED
1282
+ };
1283
+ const sslBadge = {
1284
+ valid: require_pagination.OPT_BADGE_GREEN,
1285
+ pending: require_pagination.OPT_BADGE_YELLOW,
1286
+ expired: require_pagination.OPT_BADGE_RED
1287
+ };
1288
+ const statusLabel = {
1289
+ active: "Active",
1290
+ pending: "Pending",
1291
+ error: "Error"
1292
+ };
1293
+ const sslLabel = {
1294
+ valid: "Valid",
1295
+ pending: "Pending",
1296
+ expired: "Expired"
1297
+ };
1298
+ /** Renders the domain table component. */
1299
+ function DomainTable({ domains, title = "도메인", labels: customLabels }) {
1300
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$7), customLabels);
1301
+ if (domains.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1302
+ className: "gap-group flex flex-col",
1303
+ "data-opt-id": "A7SRM",
1304
+ "data-opt-slug": "domain-table.DomainTable",
1305
+ children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1306
+ className: `text-sm font-semibold text-text-primary`,
1307
+ children: title
1308
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1309
+ className: `rounded-lg px-4 py-6 text-sm ${require_pagination.OPT_BORDER} ${require_pagination.OPT_TEXT_SECONDARY} bg-surface`,
1310
+ children: labels.emptyMessage
1311
+ })]
1312
+ });
1313
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1314
+ className: "gap-group flex flex-col",
1315
+ "data-opt-id": "A7SRM",
1316
+ "data-opt-slug": "domain-table.DomainTable",
1317
+ children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1318
+ className: `text-sm font-semibold text-text-primary`,
1319
+ children: title
1320
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1321
+ className: `overflow-x-auto rounded-lg ${require_pagination.OPT_BORDER} bg-surface`,
1322
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeProvider, {
1323
+ focusLoop: true,
1324
+ focusWrap: true,
1325
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.Composite, {
1326
+ role: "grid",
1327
+ "aria-label": title,
1328
+ className: "w-full",
1329
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("table", {}),
1330
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", {
1331
+ className: "border-border-subtle bg-surface-inset border-b",
1332
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1334
+ className: require_pagination.OPT_TH,
1335
+ children: labels.domain
1336
+ }),
1337
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1338
+ className: require_pagination.OPT_TH,
1339
+ children: labels.status
1340
+ }),
1341
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1342
+ className: require_pagination.OPT_TH,
1343
+ children: labels.ssl
1344
+ }),
1345
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1346
+ className: require_pagination.OPT_TH,
1347
+ children: labels.redirect
1348
+ }),
1349
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1350
+ className: require_pagination.OPT_TH,
1351
+ children: labels.added
1352
+ })
1353
+ ] })
1354
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", {
1355
+ className: `divide-y ${require_pagination.OPT_DIVIDER}`,
1356
+ children: domains.map((d) => {
1357
+ var _d$redirect, _d$addedAt;
1358
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.CompositeRow, {
1359
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {}),
1360
+ children: [
1361
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1362
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1363
+ className: `${require_pagination.OPT_TD} font-mono font-medium`,
1364
+ role: "gridcell"
1365
+ }),
1366
+ className: require_pagination.OPT_TABLE_ROW_ACTIVE,
1367
+ children: d.domain
1368
+ }),
1369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1370
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1371
+ className: require_pagination.OPT_TD,
1372
+ role: "gridcell"
1373
+ }),
1374
+ className: require_pagination.OPT_TABLE_ROW_ACTIVE,
1375
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1376
+ className: statusBadge[d.status],
1377
+ children: statusLabel[d.status]
1378
+ })
1379
+ }),
1380
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1381
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1382
+ className: require_pagination.OPT_TD,
1383
+ role: "gridcell"
1384
+ }),
1385
+ className: require_pagination.OPT_TABLE_ROW_ACTIVE,
1386
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1387
+ className: sslBadge[d.ssl],
1388
+ children: sslLabel[d.ssl]
1389
+ })
1390
+ }),
1391
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1392
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1393
+ className: require_pagination.OPT_TD,
1394
+ role: "gridcell"
1395
+ }),
1396
+ className: require_pagination.OPT_TABLE_ROW_ACTIVE,
1397
+ children: (_d$redirect = d.redirect) !== null && _d$redirect !== void 0 ? _d$redirect : "—"
1398
+ }),
1399
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1400
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1401
+ className: require_pagination.OPT_TD_MUTED,
1402
+ role: "gridcell"
1403
+ }),
1404
+ className: require_pagination.OPT_TABLE_ROW_ACTIVE,
1405
+ children: (_d$addedAt = d.addedAt) !== null && _d$addedAt !== void 0 ? _d$addedAt : "—"
1406
+ })
1407
+ ]
1408
+ }, d.domain);
1409
+ })
1410
+ })]
1411
+ })
1412
+ })
1413
+ })]
1414
+ });
1415
+ }
1416
+ //#endregion
1417
+ //#region src/shells/data-table.tsx
1418
+ const MIN_COLUMN_WIDTH = 40;
1419
+ const DEFAULT_COLUMN_RESIZE_MAX = 1200;
1420
+ const alignmentClass = {
1421
+ left: "text-left",
1422
+ center: "text-center",
1423
+ right: "text-right"
1424
+ };
1425
+ function getCellValue(row, accessor) {
1426
+ if (typeof accessor === "function") try {
1427
+ return accessor(row);
1428
+ } catch (_unused) {
1429
+ return "—";
1430
+ }
1431
+ const value = row[accessor];
1432
+ if (value === null || value === void 0 || typeof value === "number" && !Number.isFinite(value)) return "—";
1433
+ return String(value);
1434
+ }
1435
+ function getRawValue(row, accessor) {
1436
+ if (typeof accessor === "function") return accessor(row);
1437
+ return row[accessor];
1438
+ }
1439
+ function isMissingSortValue(value) {
1440
+ return value == null || typeof value === "number" && !Number.isFinite(value);
1441
+ }
1442
+ function compareSortValues(aVal, bVal, dir) {
1443
+ const aMissing = isMissingSortValue(aVal);
1444
+ const bMissing = isMissingSortValue(bVal);
1445
+ if (aMissing && bMissing) return 0;
1446
+ if (aMissing) return 1;
1447
+ if (bMissing) return -1;
1448
+ const cmp = typeof aVal === "number" && typeof bVal === "number" ? aVal - bVal : String(aVal).localeCompare(String(bVal));
1449
+ return dir === "desc" ? -cmp : cmp;
1450
+ }
1451
+ function clampPercent(ratio) {
1452
+ if (!Number.isFinite(ratio)) return 0;
1453
+ return Math.min(100, Math.max(0, Math.round(ratio * 100)));
1454
+ }
1455
+ function getResizeHandleValue(currentWidth, configuredWidth) {
1456
+ if (typeof currentWidth === "number" && Number.isFinite(currentWidth)) return Math.max(MIN_COLUMN_WIDTH, Math.round(currentWidth));
1457
+ const pxWidth = configuredWidth === null || configuredWidth === void 0 ? void 0 : configuredWidth.trim().match(/^(\d+(?:\.\d+)?)px$/);
1458
+ if (pxWidth) return Math.max(MIN_COLUMN_WIDTH, Math.round(Number(pxWidth[1])));
1459
+ return MIN_COLUMN_WIDTH;
1460
+ }
1461
+ function DataTableProgressBar({ value, barColor, label }) {
1462
+ const percent = clampPercent(value);
1463
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1464
+ className: "bg-bg-subtle mt-1 h-1.5 w-full overflow-hidden rounded-full",
1465
+ role: "progressbar",
1466
+ "aria-valuenow": percent,
1467
+ "aria-valuemin": 0,
1468
+ "aria-valuemax": 100,
1469
+ "aria-valuetext": `${percent}%`,
1470
+ "aria-label": label,
1471
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1472
+ className: require_pagination.cn("h-full rounded-full transition-all", barColor),
1473
+ style: { width: `${percent}%` }
1474
+ })
1475
+ });
1476
+ }
1477
+ /** Renders the data table component. */
1478
+ function DataTable({ data, columns, keyExtractor, title = "Data Table", onRowClick, emptyMessage = "No data available", activeRowId, sortable = false, selectable = false, selectedKeys, onSelectionChange, expandable = false, renderExpanded, pageSize, stickyHeader = false, resizableColumns = false, onSort: onSortExternal, className }) {
1479
+ const [sortCol, setSortCol] = (0, react.useState)(null);
1480
+ const [sortDir, setSortDir] = (0, react.useState)(null);
1481
+ const [expandedKeys, setExpandedKeys] = (0, react.useState)(/* @__PURE__ */ new Set());
1482
+ const [page, setPage] = (0, react.useState)(0);
1483
+ const [colWidths, setColWidths] = (0, react.useState)({});
1484
+ const sortedData = (0, react.useMemo)(() => {
1485
+ if (!sortable || !sortCol || !sortDir) return data;
1486
+ const col = columns.find((c) => c.id === sortCol);
1487
+ if (!col) return data;
1488
+ return [...data].sort((a, b) => {
1489
+ return compareSortValues(getRawValue(a, col.accessor), getRawValue(b, col.accessor), sortDir);
1490
+ });
1491
+ }, [
1492
+ data,
1493
+ sortable,
1494
+ sortCol,
1495
+ sortDir,
1496
+ columns
1497
+ ]);
1498
+ const totalPages = pageSize ? Math.ceil(sortedData.length / pageSize) : 1;
1499
+ const pagedData = pageSize ? sortedData.slice(page * pageSize, (page + 1) * pageSize) : sortedData;
1500
+ const handleSort = (0, react.useCallback)((colId) => {
1501
+ if (!sortable) return;
1502
+ let nextDir;
1503
+ if (sortCol === colId) {
1504
+ nextDir = sortDir === "asc" ? "desc" : sortDir === "desc" ? null : "asc";
1505
+ setSortDir(nextDir);
1506
+ if (nextDir === null) setSortCol(null);
1507
+ } else {
1508
+ setSortCol(colId);
1509
+ setSortDir("asc");
1510
+ nextDir = "asc";
1511
+ }
1512
+ setPage(0);
1513
+ if (onSortExternal) onSortExternal(colId, nextDir);
1514
+ }, [
1515
+ sortable,
1516
+ sortCol,
1517
+ sortDir,
1518
+ onSortExternal
1519
+ ]);
1520
+ const toggleExpand = (0, react.useCallback)((key) => {
1521
+ setExpandedKeys((prev) => {
1522
+ const next = new Set(prev);
1523
+ if (next.has(key)) next.delete(key);
1524
+ else next.add(key);
1525
+ return next;
1526
+ });
1527
+ }, []);
1528
+ const selected = selectedKeys !== null && selectedKeys !== void 0 ? selectedKeys : [];
1529
+ const selectedSet = (0, react.useMemo)(() => new Set(selectedKeys !== null && selectedKeys !== void 0 ? selectedKeys : []), [selectedKeys]);
1530
+ const allKeys = data.map(keyExtractor);
1531
+ const allSelected = allKeys.length > 0 && allKeys.every((k) => selectedSet.has(k));
1532
+ const toggleSelectAll = (0, react.useCallback)(() => {
1533
+ if (!onSelectionChange) return;
1534
+ onSelectionChange(allSelected ? [] : allKeys);
1535
+ }, [
1536
+ allSelected,
1537
+ allKeys,
1538
+ onSelectionChange
1539
+ ]);
1540
+ const toggleSelectRow = (0, react.useCallback)((key) => {
1541
+ if (!onSelectionChange) return;
1542
+ onSelectionChange(selectedSet.has(key) ? selected.filter((k) => k !== key) : [...selected, key]);
1543
+ }, [
1544
+ selected,
1545
+ selectedSet,
1546
+ onSelectionChange
1547
+ ]);
1548
+ const sortIndicator = (colId) => {
1549
+ if (sortCol !== colId) return null;
1550
+ return sortDir === "asc" ? " ▲" : sortDir === "desc" ? " ▼" : null;
1551
+ };
1552
+ const colCount = columns.length + (selectable ? 1 : 0) + (expandable ? 1 : 0);
1553
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1554
+ className: require_pagination.cn("gap-group flex flex-col", className),
1555
+ "data-opt-id": "J1KN4",
1556
+ "data-opt-slug": "data-table.DataTable",
1557
+ children: [
1558
+ title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1559
+ className: require_pagination.cn("text-sm font-semibold", "text-text-primary"),
1560
+ children: title
1561
+ }),
1562
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1563
+ className: require_pagination.cn("overflow-x-auto rounded-lg", require_pagination.OPT_BORDER, "bg-surface"),
1564
+ children: data.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1565
+ className: require_pagination.cn("px-4 py-8 text-center text-sm", require_pagination.OPT_TEXT_SECONDARY),
1566
+ children: emptyMessage
1567
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeProvider, {
1568
+ focusLoop: true,
1569
+ focusWrap: true,
1570
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.Composite, {
1571
+ role: "grid",
1572
+ "aria-label": title,
1573
+ className: "w-full",
1574
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("table", {}),
1575
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", {
1576
+ className: require_pagination.cn("border-border-subtle bg-surface-inset border-b", stickyHeader && "sticky top-0 z-10"),
1577
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1578
+ selectable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1579
+ className: require_pagination.cn("px-3 py-2.5 text-left text-xs font-semibold uppercase tracking-wider text-text-tertiary", "w-10"),
1580
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Checkbox, {
1581
+ checked: allSelected,
1582
+ onChange: toggleSelectAll,
1583
+ "aria-label": "Select all"
1584
+ })
1585
+ }),
1586
+ expandable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { className: require_pagination.cn("px-3 py-2.5 text-left text-xs font-semibold uppercase tracking-wider text-text-tertiary", "w-10") }),
1587
+ columns.map((col) => {
1588
+ var _col$align;
1589
+ const isSortable = sortable && col.sortable !== false;
1590
+ const resizeValue = getResizeHandleValue(colWidths[col.id], col.width);
1591
+ const resizeMax = Math.max(DEFAULT_COLUMN_RESIZE_MAX, resizeValue);
1592
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("th", {
1593
+ className: require_pagination.cn(require_pagination.OPT_TH, "relative", alignmentClass[(_col$align = col.align) !== null && _col$align !== void 0 ? _col$align : "left"], isSortable && "hover:text-text-primary cursor-pointer select-none", isSortable && "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset"),
1594
+ style: { width: colWidths[col.id] || col.width || void 0 },
1595
+ onClick: isSortable ? () => handleSort(col.id) : void 0,
1596
+ onKeyDown: (event) => {
1597
+ if (!isSortable || event.key !== "Enter" && event.key !== " ") return;
1598
+ event.preventDefault();
1599
+ handleSort(col.id);
1600
+ },
1601
+ tabIndex: isSortable ? 0 : void 0,
1602
+ "aria-sort": sortCol === col.id && sortDir ? sortDir === "asc" ? "ascending" : "descending" : void 0,
1603
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1604
+ className: "flex items-center",
1605
+ children: [col.header, sortIndicator(col.id)]
1606
+ }), resizableColumns && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1607
+ role: "separator",
1608
+ "aria-orientation": "vertical",
1609
+ "aria-label": `Resize ${col.header}`,
1610
+ "aria-valuemin": MIN_COLUMN_WIDTH,
1611
+ "aria-valuemax": resizeMax,
1612
+ "aria-valuenow": resizeValue,
1613
+ "aria-valuetext": `${resizeValue} pixels`,
1614
+ tabIndex: 0,
1615
+ className: "bg-border absolute top-0 right-0 bottom-0 w-1 cursor-col-resize opacity-0 hover:opacity-100",
1616
+ onKeyDown: (e) => {
1617
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight" && e.key !== "Home" && e.key !== "End") return;
1618
+ e.preventDefault();
1619
+ e.stopPropagation();
1620
+ if (e.key === "Home" || e.key === "End") {
1621
+ setColWidths((prev) => require_pagination._objectSpread2(require_pagination._objectSpread2({}, prev), {}, { [col.id]: e.key === "Home" ? MIN_COLUMN_WIDTH : resizeMax }));
1622
+ return;
1623
+ }
1624
+ const delta = e.shiftKey ? 40 : 10;
1625
+ const direction = e.key === "ArrowRight" ? 1 : -1;
1626
+ setColWidths((prev) => {
1627
+ const current = getResizeHandleValue(prev[col.id], col.width);
1628
+ return require_pagination._objectSpread2(require_pagination._objectSpread2({}, prev), {}, { [col.id]: Math.max(MIN_COLUMN_WIDTH, current + direction * delta) });
1629
+ });
1630
+ },
1631
+ onMouseDown: (e) => {
1632
+ e.preventDefault();
1633
+ e.stopPropagation();
1634
+ const startX = e.clientX;
1635
+ const startW = e.target.parentElement.offsetWidth;
1636
+ const onMove = (ev) => {
1637
+ const newW = Math.max(MIN_COLUMN_WIDTH, startW + ev.clientX - startX);
1638
+ setColWidths((prev) => require_pagination._objectSpread2(require_pagination._objectSpread2({}, prev), {}, { [col.id]: newW }));
1639
+ };
1640
+ const onUp = () => {
1641
+ document.removeEventListener("mousemove", onMove);
1642
+ document.removeEventListener("mouseup", onUp);
1643
+ };
1644
+ document.addEventListener("mousemove", onMove);
1645
+ document.addEventListener("mouseup", onUp);
1646
+ }
1647
+ })]
1648
+ }, col.id);
1649
+ })
1650
+ ] })
1651
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", {
1652
+ className: require_pagination.cn("divide-y", require_pagination.OPT_DIVIDER),
1653
+ children: pagedData.map((row) => {
1654
+ const key = keyExtractor(row);
1655
+ const isExpanded = expandedKeys.has(key);
1656
+ const isSelected = selectedSet.has(key);
1657
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.CompositeRow, {
1658
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
1659
+ className: require_pagination.cn(onRowClick ? "cursor-pointer" : void 0, (isSelected || activeRowId === key) && "bg-accent-subtle"),
1660
+ onClick: onRowClick ? () => onRowClick(row) : void 0
1661
+ }),
1662
+ children: [
1663
+ selectable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1664
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1665
+ className: require_pagination.cn("px-3 py-2.5 text-sm text-text-primary whitespace-nowrap", "w-10"),
1666
+ role: "gridcell"
1667
+ }),
1668
+ className: "outline-none data-[active-item]:bg-accent-subtle data-[focus-visible]:outline-2 data-[focus-visible]:outline-ring data-[focus-visible]:-outline-offset-1",
1669
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Checkbox, {
1670
+ checked: isSelected,
1671
+ onChange: () => toggleSelectRow(key),
1672
+ "aria-label": `Select row ${key}`
1673
+ })
1674
+ }),
1675
+ expandable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1676
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1677
+ className: require_pagination.cn("px-3 py-2.5 text-sm text-text-primary whitespace-nowrap", "w-10"),
1678
+ role: "gridcell"
1679
+ }),
1680
+ className: "outline-none data-[active-item]:bg-accent-subtle data-[focus-visible]:outline-2 data-[focus-visible]:outline-ring data-[focus-visible]:-outline-offset-1",
1681
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1682
+ type: "button",
1683
+ onClick: (e) => {
1684
+ e.stopPropagation();
1685
+ toggleExpand(key);
1686
+ },
1687
+ className: require_pagination.cn("rounded p-0.5 text-sm transition-transform", "text-text-secondary", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset", isExpanded && "rotate-90"),
1688
+ "aria-expanded": isExpanded,
1689
+ "aria-label": `${isExpanded ? "Collapse" : "Expand"} row ${key}`,
1690
+ children: "▶"
1691
+ })
1692
+ }),
1693
+ columns.map((col, colIndex) => {
1694
+ var _col$align2, _col$barColor;
1695
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1696
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1697
+ className: require_pagination.cn(require_pagination.OPT_TD, alignmentClass[(_col$align2 = col.align) !== null && _col$align2 !== void 0 ? _col$align2 : "left"], colIndex === 0 ? "font-medium" : ""),
1698
+ role: "gridcell"
1699
+ }),
1700
+ className: require_pagination.OPT_TABLE_ROW_ACTIVE,
1701
+ onKeyDown: (event) => {
1702
+ if (!onRowClick || event.target !== event.currentTarget || event.key !== "Enter" && event.key !== " ") return;
1703
+ event.preventDefault();
1704
+ onRowClick(row);
1705
+ },
1706
+ children: [getCellValue(row, col.accessor), col.bar && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableProgressBar, {
1707
+ value: col.bar(row),
1708
+ barColor: (_col$barColor = col.barColor) !== null && _col$barColor !== void 0 ? _col$barColor : "bg-accent",
1709
+ label: col.header
1710
+ })]
1711
+ }, col.id);
1712
+ })
1713
+ ]
1714
+ }), expandable && isExpanded && renderExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1715
+ colSpan: colCount,
1716
+ className: "border-border-subtle bg-surface-inset border-t px-4 py-3",
1717
+ children: renderExpanded(row)
1718
+ }) })] }, key);
1719
+ })
1720
+ })]
1721
+ })
1722
+ })
1723
+ }),
1724
+ pageSize && totalPages > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Pagination, {
1725
+ totalItems: sortedData.length,
1726
+ pageSize,
1727
+ currentPage: page + 1,
1728
+ onPageChange: (p) => setPage(p - 1)
1729
+ })
1730
+ ]
1731
+ });
1732
+ }
1733
+ //#endregion
1734
+ //#region src/shells/notification-toast.tsx
1735
+ let toastCounter = 0;
1736
+ let toasts = [];
1737
+ const listeners = /* @__PURE__ */ new Set();
1738
+ function emitChange() {
1739
+ for (const l of listeners) l();
1740
+ }
1741
+ const toastStore = {
1742
+ subscribe(listener) {
1743
+ listeners.add(listener);
1744
+ return () => {
1745
+ listeners.delete(listener);
1746
+ };
1747
+ },
1748
+ getSnapshot() {
1749
+ return toasts;
1750
+ },
1751
+ add(input) {
1752
+ var _input$variant;
1753
+ const id = `toast-${++toastCounter}`;
1754
+ toasts = [...toasts, require_pagination._objectSpread2({
1755
+ id,
1756
+ variant: (_input$variant = input.variant) !== null && _input$variant !== void 0 ? _input$variant : "info"
1757
+ }, input)];
1758
+ emitChange();
1759
+ return id;
1760
+ },
1761
+ dismiss(id) {
1762
+ const next = toasts.filter((t) => t.id !== id);
1763
+ if (next.length !== toasts.length) {
1764
+ toasts = next;
1765
+ emitChange();
1766
+ }
1767
+ },
1768
+ clear() {
1769
+ if (toasts.length) {
1770
+ toasts = [];
1771
+ emitChange();
1772
+ }
1773
+ }
1774
+ };
1775
+ function makeVariant(variant) {
1776
+ return (message, options) => toastStore.add(require_pagination._objectSpread2(require_pagination._objectSpread2({}, options), {}, {
1777
+ message,
1778
+ variant
1779
+ }));
1780
+ }
1781
+ /**
1782
+ * Imperative toast API. Call from anywhere (no hook required):
1783
+ * `toast.success("Saved")`, `toast.error("Failed", { title: "Oops" })`,
1784
+ * `toast.dismiss(id)`, `toast.clear()`. Requires a mounted `ToastProvider`.
1785
+ */
1786
+ const toast = Object.assign((options) => toastStore.add(options), {
1787
+ info: makeVariant("info"),
1788
+ success: makeVariant("success"),
1789
+ warning: makeVariant("warning"),
1790
+ error: makeVariant("error"),
1791
+ dismiss: (id) => toastStore.dismiss(id),
1792
+ clear: () => toastStore.clear()
1793
+ });
1794
+ const ToastContext = (0, react.createContext)(null);
1795
+ const positionStyles = {
1796
+ "top-right": "top-4 right-4",
1797
+ "top-left": "top-4 left-4",
1798
+ "bottom-right": "bottom-4 right-4",
1799
+ "bottom-left": "bottom-4 left-4"
1800
+ };
1801
+ /** Renders the toast provider component. */
1802
+ function ToastProvider({ children, position = "top-right", defaultDuration = 5e3, stackLimit = 5 }) {
1803
+ const allToasts = (0, react.useSyncExternalStore)(toastStore.subscribe, toastStore.getSnapshot, toastStore.getSnapshot);
1804
+ const visibleToasts = allToasts.length > stackLimit ? allToasts.slice(-stackLimit) : allToasts;
1805
+ const addToast = (0, react.useCallback)((t) => toastStore.add(t), []);
1806
+ const removeToast = (0, react.useCallback)((id) => toastStore.dismiss(id), []);
1807
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ToastContext.Provider, {
1808
+ value: {
1809
+ toasts: visibleToasts,
1810
+ addToast,
1811
+ removeToast
1812
+ },
1813
+ children: [children, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1814
+ className: require_pagination.cn("gap-element fixed z-50 flex flex-col", positionStyles[position]),
1815
+ role: "region",
1816
+ "aria-label": "Notifications",
1817
+ "data-opt-id": "M0KTS",
1818
+ "data-opt-slug": "notification-toast.ToastProvider",
1819
+ children: visibleToasts.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationToast, {
1820
+ toast: t,
1821
+ defaultDuration,
1822
+ onDismiss: () => toastStore.dismiss(t.id)
1823
+ }, t.id))
1824
+ })]
1825
+ });
1826
+ }
1827
+ /** Provides toast. */
1828
+ function useToast() {
1829
+ const context = (0, react.useContext)(ToastContext);
1830
+ if (!context) throw new Error("useToast must be used within a ToastProvider");
1831
+ return context;
1832
+ }
1833
+ /** Renders the notification toast component. */
1834
+ function NotificationToast({ toast, onDismiss, defaultDuration = 5e3 }) {
1835
+ var _toast$duration;
1836
+ const duration = (_toast$duration = toast.duration) !== null && _toast$duration !== void 0 ? _toast$duration : defaultDuration;
1837
+ const isUrgent = toast.variant === "error" || toast.variant === "warning";
1838
+ const onDismissRef = (0, react.useRef)(onDismiss);
1839
+ const dismissedRef = (0, react.useRef)(false);
1840
+ const timerStartedAtRef = (0, react.useRef)(null);
1841
+ const remainingDurationRef = (0, react.useRef)(duration);
1842
+ const [paused, setPaused] = (0, react.useState)(false);
1843
+ (0, react.useEffect)(() => {
1844
+ onDismissRef.current = onDismiss;
1845
+ }, [onDismiss]);
1846
+ const dismissOnce = (0, react.useCallback)(() => {
1847
+ if (dismissedRef.current) return;
1848
+ dismissedRef.current = true;
1849
+ onDismissRef.current();
1850
+ }, []);
1851
+ (0, react.useEffect)(() => {
1852
+ dismissedRef.current = false;
1853
+ remainingDurationRef.current = duration;
1854
+ }, [toast.id, duration]);
1855
+ (0, react.useEffect)(() => {
1856
+ if (!duration || duration <= 0 || paused || dismissedRef.current) return;
1857
+ timerStartedAtRef.current = Date.now();
1858
+ const timer = setTimeout(() => {
1859
+ dismissOnce();
1860
+ }, remainingDurationRef.current);
1861
+ return () => {
1862
+ clearTimeout(timer);
1863
+ if (timerStartedAtRef.current !== null) {
1864
+ const elapsed = Date.now() - timerStartedAtRef.current;
1865
+ remainingDurationRef.current = Math.max(0, remainingDurationRef.current - elapsed);
1866
+ timerStartedAtRef.current = null;
1867
+ }
1868
+ };
1869
+ }, [
1870
+ dismissOnce,
1871
+ duration,
1872
+ paused
1873
+ ]);
1874
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1875
+ className: "animate-in slide-in-from-right-full fade-in w-80 duration-200",
1876
+ role: isUrgent ? "alert" : "status",
1877
+ "aria-live": isUrgent ? "assertive" : "polite",
1878
+ "aria-atomic": "true",
1879
+ onMouseEnter: () => setPaused(true),
1880
+ onMouseLeave: () => setPaused(false),
1881
+ onFocus: () => setPaused(true),
1882
+ onBlur: (event) => {
1883
+ if (!event.currentTarget.contains(event.relatedTarget)) setPaused(false);
1884
+ },
1885
+ "data-opt-id": "26YVM",
1886
+ "data-opt-slug": "notification-toast.NotificationToast",
1887
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.Alert, {
1888
+ variant: toast.variant,
1889
+ title: toast.title,
1890
+ dismissible: true,
1891
+ onDismiss: dismissOnce,
1892
+ children: [toast.message, toast.actions && toast.actions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1893
+ className: "gap-element mt-2 flex",
1894
+ children: toast.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1895
+ type: "button",
1896
+ onClick: action.onClick,
1897
+ className: "text-xs font-medium underline hover:no-underline",
1898
+ children: action.label
1899
+ }, action.label))
1900
+ })]
1901
+ })
1902
+ });
1903
+ }
1904
+ //#endregion
1905
+ //#region src/shells/failure-list.tsx
1906
+ const defaultLabels$6 = {
1907
+ title: "최근 실패",
1908
+ emptyMessage: "실패 항목이 없습니다",
1909
+ moreItems: "{count}개 더 보기",
1910
+ severityLabels: {
1911
+ critical: "심각",
1912
+ error: "오류",
1913
+ warning: "경고"
1914
+ },
1915
+ timeAgo: {
1916
+ justNow: "방금 전",
1917
+ minutesAgo: "{minutes}분 전",
1918
+ hoursAgo: "{hours}시간 전",
1919
+ daysAgo: "{days}일 전"
1920
+ }
1921
+ };
1922
+ const severityVariant = {
1923
+ critical: "error",
1924
+ error: "error",
1925
+ warning: "warning"
1926
+ };
1927
+ const severityBorder = {
1928
+ critical: "border-l-2 border-l-danger",
1929
+ error: "border-l-2 border-l-danger",
1930
+ warning: "border-l-2 border-l-warning"
1931
+ };
1932
+ function formatRelativeTime(timestamp, timeLabels, locale) {
1933
+ const now = /* @__PURE__ */ new Date();
1934
+ const date = new Date(timestamp);
1935
+ const diffMs = now.getTime() - date.getTime();
1936
+ const diffMins = Math.floor(diffMs / 6e4);
1937
+ const diffHours = Math.floor(diffMs / 36e5);
1938
+ const diffDays = Math.floor(diffMs / 864e5);
1939
+ if (diffMins < 1) return timeLabels.justNow;
1940
+ if (diffMins < 60) return timeLabels.minutesAgo.replace("{minutes}", String(diffMins));
1941
+ if (diffHours < 24) return timeLabels.hoursAgo.replace("{hours}", String(diffHours));
1942
+ if (diffDays < 7) return timeLabels.daysAgo.replace("{days}", String(diffDays));
1943
+ return date.toLocaleDateString(locale);
1944
+ }
1945
+ function groupBy(items, key) {
1946
+ const map = /* @__PURE__ */ new Map();
1947
+ for (const item of items) {
1948
+ const k = key(item);
1949
+ const existing = map.get(k);
1950
+ if (existing) existing.push(item);
1951
+ else map.set(k, [item]);
1952
+ }
1953
+ return map;
1954
+ }
1955
+ function FailureItemRow({ failure, isLast, onFailureClick, renderActions, mergedSeverityLabels, mergedTimeAgo, locale }) {
1956
+ const itemId = `failure-${failure.id}`;
1957
+ const handleKeyDown = (event) => {
1958
+ if (!onFailureClick) return;
1959
+ if (event.key !== "Enter" && event.key !== " ") return;
1960
+ event.preventDefault();
1961
+ onFailureClick(failure);
1962
+ };
1963
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_reopt_ai_opt_ui_primitives.CompositeItem, {
1964
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("article", {}),
1965
+ role: "article",
1966
+ "aria-labelledby": itemId,
1967
+ className: require_pagination.cn("group gap-group relative flex w-full items-start p-3", require_pagination.OPT_FOCUS_VISIBLE, "data-[active-item]:bg-accent-subtle", onFailureClick && "cursor-pointer", !isLast && "border-border-subtle border-b", severityBorder[failure.severity]),
1968
+ onClick: () => onFailureClick === null || onFailureClick === void 0 ? void 0 : onFailureClick(failure),
1969
+ onKeyDown: handleKeyDown,
1970
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1971
+ className: "min-w-0 flex-1",
1972
+ children: [
1973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1974
+ className: "gap-element flex items-center",
1975
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Badge, {
1976
+ variant: severityVariant[failure.severity],
1977
+ size: "sm",
1978
+ children: mergedSeverityLabels[failure.severity]
1979
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1980
+ id: itemId,
1981
+ className: `text-sm font-medium ${require_pagination.OPT_TEXT_PRIMARY} truncate`,
1982
+ children: failure.title
1983
+ })]
1984
+ }),
1985
+ failure.message && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1986
+ className: `mt-1 text-xs text-text-secondary line-clamp-2`,
1987
+ children: failure.message
1988
+ }),
1989
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
1990
+ dateTime: failure.timestamp,
1991
+ className: `mt-1 block text-xs ${require_pagination.OPT_TEXT_TERTIARY}`,
1992
+ children: formatRelativeTime(failure.timestamp, mergedTimeAgo, locale)
1993
+ })
1994
+ ]
1995
+ }), renderActions && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1996
+ className: "flex shrink-0 items-center gap-1",
1997
+ children: renderActions(failure)
1998
+ })]
1999
+ }, failure.id);
2000
+ }
2001
+ /** Renders the failure list component. */
2002
+ function FailureList({ failures, title, maxItems, grouped = false, onFailureClick, onShowMore, labels: customLabels, locale = "ko-KR", loading = false, renderActions, className }) {
2003
+ var _customLabels$timeAgo, _customLabels$timeAgo2, _customLabels$timeAgo3, _customLabels$timeAgo4, _customLabels$timeAgo5, _customLabels$timeAgo6, _customLabels$timeAgo7, _customLabels$timeAgo8, _customLabels$severit, _customLabels$severit2, _customLabels$severit3, _customLabels$severit4, _customLabels$severit5, _customLabels$severit6, _customLabels$title, _customLabels$emptyMe, _customLabels$moreIte;
2004
+ const mergedTimeAgo = {
2005
+ justNow: (_customLabels$timeAgo = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo2 = customLabels.timeAgo) === null || _customLabels$timeAgo2 === void 0 ? void 0 : _customLabels$timeAgo2.justNow) !== null && _customLabels$timeAgo !== void 0 ? _customLabels$timeAgo : defaultLabels$6.timeAgo.justNow,
2006
+ minutesAgo: (_customLabels$timeAgo3 = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo4 = customLabels.timeAgo) === null || _customLabels$timeAgo4 === void 0 ? void 0 : _customLabels$timeAgo4.minutesAgo) !== null && _customLabels$timeAgo3 !== void 0 ? _customLabels$timeAgo3 : defaultLabels$6.timeAgo.minutesAgo,
2007
+ hoursAgo: (_customLabels$timeAgo5 = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo6 = customLabels.timeAgo) === null || _customLabels$timeAgo6 === void 0 ? void 0 : _customLabels$timeAgo6.hoursAgo) !== null && _customLabels$timeAgo5 !== void 0 ? _customLabels$timeAgo5 : defaultLabels$6.timeAgo.hoursAgo,
2008
+ daysAgo: (_customLabels$timeAgo7 = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo8 = customLabels.timeAgo) === null || _customLabels$timeAgo8 === void 0 ? void 0 : _customLabels$timeAgo8.daysAgo) !== null && _customLabels$timeAgo7 !== void 0 ? _customLabels$timeAgo7 : defaultLabels$6.timeAgo.daysAgo
2009
+ };
2010
+ const mergedSeverityLabels = {
2011
+ critical: (_customLabels$severit = customLabels === null || customLabels === void 0 || (_customLabels$severit2 = customLabels.severityLabels) === null || _customLabels$severit2 === void 0 ? void 0 : _customLabels$severit2.critical) !== null && _customLabels$severit !== void 0 ? _customLabels$severit : defaultLabels$6.severityLabels.critical,
2012
+ error: (_customLabels$severit3 = customLabels === null || customLabels === void 0 || (_customLabels$severit4 = customLabels.severityLabels) === null || _customLabels$severit4 === void 0 ? void 0 : _customLabels$severit4.error) !== null && _customLabels$severit3 !== void 0 ? _customLabels$severit3 : defaultLabels$6.severityLabels.error,
2013
+ warning: (_customLabels$severit5 = customLabels === null || customLabels === void 0 || (_customLabels$severit6 = customLabels.severityLabels) === null || _customLabels$severit6 === void 0 ? void 0 : _customLabels$severit6.warning) !== null && _customLabels$severit5 !== void 0 ? _customLabels$severit5 : defaultLabels$6.severityLabels.warning
2014
+ };
2015
+ const labels = {
2016
+ title: (_customLabels$title = customLabels === null || customLabels === void 0 ? void 0 : customLabels.title) !== null && _customLabels$title !== void 0 ? _customLabels$title : defaultLabels$6.title,
2017
+ emptyMessage: (_customLabels$emptyMe = customLabels === null || customLabels === void 0 ? void 0 : customLabels.emptyMessage) !== null && _customLabels$emptyMe !== void 0 ? _customLabels$emptyMe : defaultLabels$6.emptyMessage,
2018
+ moreItems: (_customLabels$moreIte = customLabels === null || customLabels === void 0 ? void 0 : customLabels.moreItems) !== null && _customLabels$moreIte !== void 0 ? _customLabels$moreIte : defaultLabels$6.moreItems
2019
+ };
2020
+ const displayTitle = title !== null && title !== void 0 ? title : labels.title;
2021
+ const allItems = maxItems ? failures.slice(0, maxItems) : failures;
2022
+ const remainingCount = maxItems ? Math.max(0, failures.length - maxItems) : 0;
2023
+ const rowProps = {
2024
+ onFailureClick,
2025
+ renderActions,
2026
+ mergedSeverityLabels,
2027
+ mergedTimeAgo,
2028
+ locale
2029
+ };
2030
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2031
+ className,
2032
+ "data-opt-id": "ZAM3R",
2033
+ "data-opt-slug": "failure-list.FailureList",
2034
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2035
+ className: "gap-group flex flex-col",
2036
+ children: [displayTitle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2037
+ className: `text-sm font-semibold text-text-primary`,
2038
+ children: displayTitle
2039
+ }), failures.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2040
+ role: "status",
2041
+ className: `py-8 text-center text-sm ${require_pagination.OPT_TEXT_SECONDARY}`,
2042
+ children: labels.emptyMessage
2043
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeProvider, {
2044
+ focusLoop: true,
2045
+ orientation: "vertical",
2046
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.Composite, {
2047
+ role: "feed",
2048
+ "aria-label": displayTitle,
2049
+ "aria-busy": loading,
2050
+ className: `rounded-lg ${require_pagination.OPT_BORDER} bg-surface`,
2051
+ children: grouped ? renderGrouped(allItems, remainingCount, rowProps, labels, onShowMore) : renderFlat(allItems, remainingCount, rowProps, labels, onShowMore)
2052
+ })
2053
+ })]
2054
+ })
2055
+ });
2056
+ }
2057
+ function renderFlat(items, remainingCount, rowProps, labels, onShowMore) {
2058
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [items.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailureItemRow, require_pagination._objectSpread2({
2059
+ failure,
2060
+ isLast: index === items.length - 1 && remainingCount === 0
2061
+ }, rowProps), failure.id)), remainingCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
2062
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button" }),
2063
+ onClick: onShowMore,
2064
+ className: `border-border-subtle w-full border-t p-3 text-center text-xs font-medium text-text-secondary outline-none data-[focus-visible]:outline-2 data-[focus-visible]:outline-ring data-[focus-visible]:-outline-offset-1 hover:text-accent data-[active-item]:bg-accent-subtle transition-colors`,
2065
+ "aria-label": `${remainingCount}개의 실패 더 보기`,
2066
+ children: labels.moreItems.replace("{count}", String(remainingCount))
2067
+ })] });
2068
+ }
2069
+ function renderGrouped(items, remainingCount, rowProps, labels, onShowMore) {
2070
+ const groups = groupBy(items, (f) => {
2071
+ var _f$group;
2072
+ return (_f$group = f.group) !== null && _f$group !== void 0 ? _f$group : "";
2073
+ });
2074
+ const groupEntries = Array.from(groups.entries());
2075
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [groupEntries.map(([groupName, groupItems], groupIndex) => {
2076
+ const isLastGroup = groupIndex === groupEntries.length - 1 && remainingCount === 0;
2077
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [groupName && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2078
+ className: require_pagination.cn(`px-3 py-2 text-xs font-semibold text-text-tertiary tracking-wider uppercase`, groupIndex > 0 && "border-border-subtle border-t"),
2079
+ children: groupName
2080
+ }), groupItems.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailureItemRow, require_pagination._objectSpread2({
2081
+ failure,
2082
+ isLast: isLastGroup && index === groupItems.length - 1
2083
+ }, rowProps), failure.id))] }, groupName || "__ungrouped__");
2084
+ }), remainingCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeItem, {
2085
+ render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button" }),
2086
+ onClick: onShowMore,
2087
+ className: `border-border-subtle w-full border-t p-3 text-center text-xs font-medium text-text-secondary outline-none data-[focus-visible]:outline-2 data-[focus-visible]:outline-ring data-[focus-visible]:-outline-offset-1 hover:text-accent data-[active-item]:bg-accent-subtle transition-colors`,
2088
+ "aria-label": `${remainingCount}개의 실패 더 보기`,
2089
+ children: labels.moreItems.replace("{count}", String(remainingCount))
2090
+ })] });
2091
+ }
2092
+ //#endregion
2093
+ //#region src/shells/key-value-editor.tsx
2094
+ const defaultLabels$5 = {
2095
+ keyPlaceholder: "키",
2096
+ valuePlaceholder: "값",
2097
+ addButton: "추가",
2098
+ removeAriaLabel: "항목 삭제",
2099
+ toggleSecretAriaLabel: "비밀값 토글",
2100
+ emptyMessage: "항목이 없습니다. 추가 버튼을 눌러주세요."
2101
+ };
2102
+ let idCounter = 0;
2103
+ function generateId() {
2104
+ return `kv-${++idCounter}`;
2105
+ }
2106
+ /** Renders the key value editor component. */
2107
+ function KeyValueEditor({ value: controlledValue, defaultValue, onChange, showSecretToggle = false, maxEntries, readOnly = false, labels: customLabels, renderActions, validateKey, className }) {
2108
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$5), customLabels);
2109
+ const [internalEntries, setInternalEntries] = (0, react.useState)(() => defaultValue !== null && defaultValue !== void 0 ? defaultValue : []);
2110
+ const entries = controlledValue !== null && controlledValue !== void 0 ? controlledValue : internalEntries;
2111
+ const keyInputRef = (0, react.useRef)(null);
2112
+ const updateEntries = (0, react.useCallback)((next) => {
2113
+ if (!controlledValue) setInternalEntries(next);
2114
+ onChange === null || onChange === void 0 || onChange(next);
2115
+ }, [controlledValue, onChange]);
2116
+ const addEntry = (0, react.useCallback)(() => {
2117
+ if (maxEntries && entries.length >= maxEntries) return;
2118
+ const newEntry = {
2119
+ id: generateId(),
2120
+ key: "",
2121
+ value: "",
2122
+ isSecret: false
2123
+ };
2124
+ updateEntries([...entries, newEntry]);
2125
+ requestAnimationFrame(() => {
2126
+ var _keyInputRef$current;
2127
+ (_keyInputRef$current = keyInputRef.current) === null || _keyInputRef$current === void 0 || (_keyInputRef$current = _keyInputRef$current.closest("[data-kv-list]")) === null || _keyInputRef$current === void 0 || (_keyInputRef$current = _keyInputRef$current.querySelector(`[data-kv-id="${newEntry.id}"] [data-kv-key]`)) === null || _keyInputRef$current === void 0 || _keyInputRef$current.focus();
2128
+ });
2129
+ }, [
2130
+ entries,
2131
+ maxEntries,
2132
+ updateEntries
2133
+ ]);
2134
+ const removeEntry = (0, react.useCallback)((id) => {
2135
+ updateEntries(entries.filter((e) => e.id !== id));
2136
+ }, [entries, updateEntries]);
2137
+ const updateEntry = (0, react.useCallback)((id, field, val) => {
2138
+ updateEntries(entries.map((e) => e.id === id ? require_pagination._objectSpread2(require_pagination._objectSpread2({}, e), {}, { [field]: val }) : e));
2139
+ }, [entries, updateEntries]);
2140
+ const toggleSecret = (0, react.useCallback)((id) => {
2141
+ updateEntries(entries.map((e) => e.id === id ? require_pagination._objectSpread2(require_pagination._objectSpread2({}, e), {}, { isSecret: !e.isSecret }) : e));
2142
+ }, [entries, updateEntries]);
2143
+ const canAdd = !readOnly && (!maxEntries || entries.length < maxEntries);
2144
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2145
+ className: require_pagination.cn("gap-group flex flex-col", className),
2146
+ "data-opt-id": "NCBWA",
2147
+ "data-opt-slug": "key-value-editor.KeyValueEditor",
2148
+ children: [entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2149
+ className: "text-text-tertiary py-4 text-center text-sm",
2150
+ children: labels.emptyMessage
2151
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2152
+ className: "gap-element flex flex-col",
2153
+ "data-kv-list": true,
2154
+ children: entries.map((entry, index) => {
2155
+ const keyError = validateKey === null || validateKey === void 0 ? void 0 : validateKey(entry.key, entries);
2156
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2157
+ "data-kv-id": entry.id,
2158
+ className: require_pagination.cn("gap-element flex items-start", "border-border rounded-lg border p-3", "bg-surface"),
2159
+ children: [
2160
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2161
+ className: "flex min-w-0 flex-1 flex-col gap-1",
2162
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2163
+ "data-kv-key": true,
2164
+ ref: index === entries.length - 1 ? keyInputRef : void 0,
2165
+ type: "text",
2166
+ value: entry.key,
2167
+ onChange: (e) => updateEntry(entry.id, "key", e.target.value),
2168
+ placeholder: labels.keyPlaceholder,
2169
+ readOnly,
2170
+ "aria-label": `${labels.keyPlaceholder} ${index + 1}`,
2171
+ className: require_pagination.cn("border-border bg-bg-subtle text-text-primary placeholder:text-text-tertiary w-full rounded-md border px-3 py-1.5 font-mono text-sm", "focus:border-accent focus-visible:ring-ring focus-visible:ring-offset-ring-offset focus-visible:ring-2 focus-visible:ring-offset-1", readOnly && "cursor-default opacity-70", keyError && "border-error")
2172
+ }), keyError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2173
+ className: "text-error text-xs",
2174
+ children: keyError
2175
+ })]
2176
+ }),
2177
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2178
+ className: "text-text-tertiary px-1 py-1.5",
2179
+ children: "="
2180
+ }),
2181
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2182
+ className: "min-w-0 flex-1",
2183
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2184
+ type: entry.isSecret ? "password" : "text",
2185
+ value: entry.value,
2186
+ onChange: (e) => updateEntry(entry.id, "value", e.target.value),
2187
+ placeholder: labels.valuePlaceholder,
2188
+ readOnly,
2189
+ "aria-label": `${labels.valuePlaceholder} ${index + 1}`,
2190
+ className: require_pagination.cn("border-border bg-bg-subtle text-text-primary placeholder:text-text-tertiary w-full rounded-md border px-3 py-1.5 font-mono text-sm", "focus:border-accent focus-visible:ring-ring focus-visible:ring-offset-ring-offset focus-visible:ring-2 focus-visible:ring-offset-1", readOnly && "cursor-default opacity-70")
2191
+ })
2192
+ }),
2193
+ showSecretToggle && !readOnly && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2194
+ type: "button",
2195
+ onClick: () => toggleSecret(entry.id),
2196
+ "aria-label": labels.toggleSecretAriaLabel,
2197
+ "aria-pressed": entry.isSecret,
2198
+ className: "text-text-tertiary hover:text-text-primary shrink-0 rounded p-1.5 transition-colors",
2199
+ children: entry.isSecret ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2200
+ className: "size-4",
2201
+ fill: "none",
2202
+ viewBox: "0 0 24 24",
2203
+ stroke: "currentColor",
2204
+ strokeWidth: 2,
2205
+ "aria-hidden": "true",
2206
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2207
+ strokeLinecap: "round",
2208
+ strokeLinejoin: "round",
2209
+ d: "M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
2210
+ })
2211
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
2212
+ className: "size-4",
2213
+ fill: "none",
2214
+ viewBox: "0 0 24 24",
2215
+ stroke: "currentColor",
2216
+ strokeWidth: 2,
2217
+ "aria-hidden": "true",
2218
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2219
+ strokeLinecap: "round",
2220
+ strokeLinejoin: "round",
2221
+ d: "M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
2222
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2223
+ strokeLinecap: "round",
2224
+ strokeLinejoin: "round",
2225
+ d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z"
2226
+ })]
2227
+ })
2228
+ }),
2229
+ renderActions === null || renderActions === void 0 ? void 0 : renderActions(entry, index),
2230
+ !readOnly && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2231
+ type: "button",
2232
+ onClick: () => removeEntry(entry.id),
2233
+ "aria-label": labels.removeAriaLabel,
2234
+ className: "text-text-tertiary hover:text-error shrink-0 rounded p-1.5 transition-colors",
2235
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2236
+ className: "size-4",
2237
+ fill: "none",
2238
+ viewBox: "0 0 24 24",
2239
+ stroke: "currentColor",
2240
+ strokeWidth: 2,
2241
+ "aria-hidden": "true",
2242
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2243
+ strokeLinecap: "round",
2244
+ strokeLinejoin: "round",
2245
+ d: "M6 18L18 6M6 6l12 12"
2246
+ })
2247
+ })
2248
+ })
2249
+ ]
2250
+ }, entry.id);
2251
+ })
2252
+ }), canAdd && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2253
+ type: "button",
2254
+ onClick: addEntry,
2255
+ className: require_pagination.cn("border-border text-text-secondary rounded-lg border border-dashed px-4 py-2 text-sm transition-colors", "hover:border-accent hover:text-accent"),
2256
+ children: labels.addButton
2257
+ })]
2258
+ });
2259
+ }
2260
+ //#endregion
2261
+ //#region src/shells/workflow-canvas.tsx
2262
+ const statusVariant = {
2263
+ idle: "default",
2264
+ running: "info",
2265
+ success: "success",
2266
+ error: "error"
2267
+ };
2268
+ function clamp(value, min, max) {
2269
+ return Math.max(min, Math.min(max, value));
2270
+ }
2271
+ /** Renders the workflow canvas component. */
2272
+ function WorkflowCanvas({ steps, connections = [], rows = 9, cols = 9, cellSize = 72, gap = 12, title = "Workflow Canvas", emptyMessage = "No workflow steps", selectedStepId, onSelectStep, onStepMove, renderStep, className }) {
2273
+ const stepMap = react.useMemo(() => new Map(steps.map((step) => [step.id, step])), [steps]);
2274
+ const width = cols * cellSize + (cols - 1) * gap;
2275
+ const height = rows * cellSize + (rows - 1) * gap;
2276
+ const renderDefaultStep = react.useCallback((step, selected) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2277
+ type: "button",
2278
+ onClick: () => onSelectStep === null || onSelectStep === void 0 ? void 0 : onSelectStep(step),
2279
+ className: require_pagination.cn("group absolute flex h-full w-full flex-col justify-between rounded-lg border p-2 text-left transition-colors", selected ? "border-accent bg-accent-subtle" : "border-border bg-surface hover:bg-bg-subtle"),
2280
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2281
+ className: "text-text-primary truncate text-xs font-medium",
2282
+ children: step.title
2283
+ }), step.status && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Badge, {
2284
+ variant: statusVariant[step.status],
2285
+ size: "sm",
2286
+ className: "w-fit",
2287
+ children: step.status
2288
+ })]
2289
+ }), [onSelectStep]);
2290
+ const handleStepKeyDown = react.useCallback((event, step) => {
2291
+ if (!onStepMove) return;
2292
+ let nextRow = step.row;
2293
+ let nextCol = step.col;
2294
+ switch (event.key) {
2295
+ case "ArrowUp":
2296
+ nextRow -= 1;
2297
+ break;
2298
+ case "ArrowDown":
2299
+ nextRow += 1;
2300
+ break;
2301
+ case "ArrowLeft":
2302
+ nextCol -= 1;
2303
+ break;
2304
+ case "ArrowRight":
2305
+ nextCol += 1;
2306
+ break;
2307
+ default: return;
2308
+ }
2309
+ event.preventDefault();
2310
+ onStepMove(step.id, clamp(nextRow, 0, rows - 1), clamp(nextCol, 0, cols - 1));
2311
+ }, [
2312
+ onStepMove,
2313
+ rows,
2314
+ cols
2315
+ ]);
2316
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2317
+ className: require_pagination.cn("gap-group flex flex-col", className),
2318
+ "data-opt-id": "4TX11",
2319
+ "data-opt-slug": "workflow-canvas.WorkflowCanvas",
2320
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2321
+ className: "text-text-primary text-sm font-semibold",
2322
+ children: title
2323
+ }), steps.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2324
+ className: "text-text-secondary border-border rounded-lg border border-dashed p-8 text-center text-sm",
2325
+ children: emptyMessage
2326
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2327
+ className: "border-border bg-bg-subtle relative overflow-auto rounded-lg border p-3",
2328
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2329
+ role: "grid",
2330
+ "aria-label": title,
2331
+ className: "relative",
2332
+ style: {
2333
+ width,
2334
+ height
2335
+ },
2336
+ children: [
2337
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2338
+ className: "grid",
2339
+ style: {
2340
+ gridTemplateColumns: `repeat(${cols}, ${cellSize}px)`,
2341
+ gridTemplateRows: `repeat(${rows}, ${cellSize}px)`,
2342
+ gap
2343
+ },
2344
+ children: Array.from({ length: rows * cols }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2345
+ "aria-hidden": "true",
2346
+ className: "border-border-subtle bg-surface/30 rounded-md border border-dashed"
2347
+ }, i))
2348
+ }),
2349
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2350
+ className: "pointer-events-none absolute inset-0",
2351
+ width,
2352
+ height,
2353
+ "aria-hidden": "true",
2354
+ children: connections.map((connection) => {
2355
+ const from = stepMap.get(connection.from);
2356
+ const to = stepMap.get(connection.to);
2357
+ if (!from || !to) return null;
2358
+ const x1 = from.col * (cellSize + gap) + cellSize / 2;
2359
+ const y1 = from.row * (cellSize + gap) + cellSize / 2;
2360
+ const x2 = to.col * (cellSize + gap) + cellSize / 2;
2361
+ const y2 = to.row * (cellSize + gap) + cellSize / 2;
2362
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
2363
+ x1,
2364
+ y1,
2365
+ x2,
2366
+ y2,
2367
+ stroke: "currentColor",
2368
+ strokeOpacity: "0.35",
2369
+ strokeWidth: "2"
2370
+ }), connection.label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
2371
+ x: (x1 + x2) / 2,
2372
+ y: (y1 + y2) / 2 - 4,
2373
+ textAnchor: "middle",
2374
+ className: "fill-text-secondary text-[10px]",
2375
+ children: connection.label
2376
+ })] }, `${connection.from}-${connection.to}`);
2377
+ })
2378
+ }),
2379
+ steps.map((step) => {
2380
+ const selected = selectedStepId === step.id;
2381
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2382
+ role: "gridcell",
2383
+ tabIndex: 0,
2384
+ "aria-selected": selected,
2385
+ "aria-label": `${step.title} (${step.row + 1}, ${step.col + 1})`,
2386
+ onKeyDown: (event) => handleStepKeyDown(event, step),
2387
+ className: "focus-visible:ring-ring absolute focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none",
2388
+ style: {
2389
+ width: cellSize,
2390
+ height: cellSize,
2391
+ left: step.col * (cellSize + gap),
2392
+ top: step.row * (cellSize + gap)
2393
+ },
2394
+ children: renderStep ? renderStep(step, selected) : renderDefaultStep(step, selected)
2395
+ }, step.id);
2396
+ })
2397
+ ]
2398
+ })
2399
+ })]
2400
+ });
2401
+ }
2402
+ WorkflowCanvas.displayName = "WorkflowCanvas";
2403
+ //#endregion
2404
+ //#region src/lib/use-auto-refresh.ts
2405
+ /**
2406
+ * A refresh timer that waits for its own work.
2407
+ *
2408
+ * A plain `setInterval` fires on a schedule regardless of whether the previous
2409
+ * query came back, so a five-second interval over a seven-second query stacks
2410
+ * requests until something gives. Chaining the next wait onto the settled
2411
+ * promise keeps exactly one refresh in flight, and a rejection stops the loop
2412
+ * instead of hammering an endpoint that is already failing.
2413
+ */
2414
+ function useAutoRefresh({ interval, paused = false, onRefresh }) {
2415
+ const callbackRef = (0, react.useRef)(onRefresh);
2416
+ callbackRef.current = onRefresh;
2417
+ (0, react.useEffect)(() => {
2418
+ if (paused || interval <= 0 || !callbackRef.current) return;
2419
+ let cancelled = false;
2420
+ let timer;
2421
+ const schedule = () => {
2422
+ timer = setTimeout(async () => {
2423
+ if (cancelled) return;
2424
+ try {
2425
+ var _callbackRef$current;
2426
+ await ((_callbackRef$current = callbackRef.current) === null || _callbackRef$current === void 0 ? void 0 : _callbackRef$current.call(callbackRef));
2427
+ } catch (_unused) {
2428
+ return;
2429
+ }
2430
+ if (!cancelled) schedule();
2431
+ }, interval);
2432
+ };
2433
+ schedule();
2434
+ return () => {
2435
+ cancelled = true;
2436
+ if (timer) clearTimeout(timer);
2437
+ };
2438
+ }, [interval, paused]);
2439
+ }
2440
+ //#endregion
2441
+ //#region src/shells/time-range-control.tsx
2442
+ const defaultLabels$4 = {
2443
+ trigger: "Select time range",
2444
+ invalid: "Invalid time range",
2445
+ last: (duration) => `Last ${duration}`,
2446
+ next: (duration) => `Next ${duration}`,
2447
+ quickSelect: "Quick select",
2448
+ apply: "Apply",
2449
+ update: "Update",
2450
+ refresh: "Refresh",
2451
+ commonlyUsed: "Commonly used",
2452
+ recentlyUsed: "Recently used",
2453
+ start: "Start",
2454
+ end: "End",
2455
+ startHint: "Absolute time or date math, e.g. now-7d",
2456
+ autoRefresh: "Auto refresh",
2457
+ autoRefreshOff: "Off",
2458
+ every: (duration) => `Every ${duration}`,
2459
+ previous: "Previous window",
2460
+ nextWindow: "Next window",
2461
+ zoomIn: "Zoom in",
2462
+ zoomOut: "Zoom out",
2463
+ thisUnit: (unit) => `This ${unit}`,
2464
+ units: {
2465
+ s: "seconds",
2466
+ m: "minutes",
2467
+ h: "hours",
2468
+ d: "days",
2469
+ w: "weeks",
2470
+ M: "months",
2471
+ y: "years"
2472
+ }
2473
+ };
2474
+ const QUICK_UNITS = [
2475
+ "m",
2476
+ "h",
2477
+ "d",
2478
+ "w",
2479
+ "M",
2480
+ "y"
2481
+ ];
2482
+ const REFRESH_INTERVALS = [
2483
+ 0,
2484
+ 5e3,
2485
+ 1e4,
2486
+ 3e4,
2487
+ 6e4,
2488
+ 3e5
2489
+ ];
2490
+ function selectValue(value) {
2491
+ var _value$;
2492
+ return Array.isArray(value) ? (_value$ = value[0]) !== null && _value$ !== void 0 ? _value$ : "" : value;
2493
+ }
2494
+ /**
2495
+ * What the trigger says.
2496
+ *
2497
+ * A window anchored to `now` deserves its intent ("Last 7 days"), not two
2498
+ * timestamps the reader has to subtract. Everything else gets both ends
2499
+ * spelled out. A duration that does not divide into whole minutes is prefixed
2500
+ * with `~` rather than rounded silently.
2501
+ */
2502
+ function describeRange(range, labels, options) {
2503
+ const window = (0, _reopt_ai_opt_charts.measureTimeWindow)(range, {
2504
+ timeZone: options.timeZone,
2505
+ now: options.now
2506
+ });
2507
+ if (window.isInvalid) return {
2508
+ text: labels.invalid,
2509
+ isInvalid: true
2510
+ };
2511
+ const fromParts = (0, _reopt_ai_opt_charts.parseRelativeParts)(range.from);
2512
+ const toParts = (0, _reopt_ai_opt_charts.parseRelativeParts)(range.to);
2513
+ if ((fromParts === null || fromParts === void 0 ? void 0 : fromParts.roundUnit) && fromParts.count === 0 && (toParts === null || toParts === void 0 ? void 0 : toParts.roundUnit) === fromParts.roundUnit && toParts.count === 0) return {
2514
+ text: labels.thisUnit((0, _reopt_ai_opt_charts.formatCountUnit)(1, fromParts.roundUnit, options.locale).replace(/^1\s*/, "")),
2515
+ isInvalid: false
2516
+ };
2517
+ if ((0, _reopt_ai_opt_charts.isRelativeToNow)(range)) {
2518
+ var _toParts$count;
2519
+ const anchoredAtEnd = ((_toParts$count = toParts === null || toParts === void 0 ? void 0 : toParts.count) !== null && _toParts$count !== void 0 ? _toParts$count : 0) === 0;
2520
+ const parts = anchoredAtEnd ? fromParts : toParts;
2521
+ const bothSnapped = (parts === null || parts === void 0 ? void 0 : parts.roundUnit) === (parts === null || parts === void 0 ? void 0 : parts.unit) && (toParts === null || toParts === void 0 ? void 0 : toParts.roundUnit) === (parts === null || parts === void 0 ? void 0 : parts.unit);
2522
+ const counted = parts && parts.count > 0 ? (0, _reopt_ai_opt_charts.formatCountUnit)(parts.roundUnit && bothSnapped ? parts.count + 1 : parts.count, parts.unit, options.locale) : null;
2523
+ const exact = counted !== null && (!(parts === null || parts === void 0 ? void 0 : parts.roundUnit) || bothSnapped);
2524
+ const duration = exact ? counted : (0, _reopt_ai_opt_charts.formatDuration)(window.duration, options.locale);
2525
+ const prefix = exact || (0, _reopt_ai_opt_charts.isExactMinutes)(window.duration) ? "" : "~";
2526
+ return {
2527
+ text: anchoredAtEnd ? labels.last(`${prefix}${duration}`) : labels.next(`${prefix}${duration}`),
2528
+ isInvalid: false
2529
+ };
2530
+ }
2531
+ const dateOnly = (0, _reopt_ai_opt_charts.isDateOnly)(range.from) && (0, _reopt_ai_opt_charts.isDateOnly)(range.to);
2532
+ const from = (0, _reopt_ai_opt_charts.formatInstant)(window.from, require_pagination._objectSpread2(require_pagination._objectSpread2({}, options), {}, { dateOnly }));
2533
+ const to = (0, _reopt_ai_opt_charts.formatInstant)(window.to, require_pagination._objectSpread2(require_pagination._objectSpread2({}, options), {}, { dateOnly }));
2534
+ return {
2535
+ text: from === to ? from : `${from} – ${to}`,
2536
+ isInvalid: false
2537
+ };
2538
+ }
2539
+ /**
2540
+ * TimeRangeControl — the window as an expression, not two frozen instants.
2541
+ *
2542
+ * Holding `{ from: "now-7d", to: "now" }` instead of a resolved pair is what
2543
+ * makes a saved board, a shared link and an auto-refresh timer all mean the
2544
+ * same thing tomorrow that they mean today. Around that sit the moves people
2545
+ * actually make on a chart: jump to a named window, step the same width back
2546
+ * and forth to compare, widen when the interesting thing is just off screen,
2547
+ * and let it re-query on a timer while they watch.
2548
+ *
2549
+ * @example
2550
+ * ```tsx
2551
+ * const [range, setRange] = useState<TimeRange>({ from: "now-7d", to: "now" });
2552
+ *
2553
+ * <TimeRangeControl
2554
+ * value={range}
2555
+ * onChange={setRange}
2556
+ * timeZone="Asia/Seoul"
2557
+ * showWindowButtons
2558
+ * onRefresh={() => refetch()}
2559
+ * />
2560
+ * ```
2561
+ */
2562
+ function TimeRangeControl({ value, onChange, presets, recentRanges, timeZone, locale, showUpdateButton = true, showWindowButtons = true, zoomFactor = .5, refresh, onRefreshChange, onRefresh, isLoading = false, disabled = false, className, labels: customLabels, "aria-label": ariaLabel }) {
2563
+ var _refresh$interval, _refresh$paused, _refresh$interval2;
2564
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$4), customLabels), {}, { units: require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$4.units), customLabels === null || customLabels === void 0 ? void 0 : customLabels.units) });
2565
+ const [open, setOpen] = (0, react.useState)(false);
2566
+ const [draft, setDraft] = (0, react.useState)(null);
2567
+ const [quickCount, setQuickCount] = (0, react.useState)("15");
2568
+ const [quickUnit, setQuickUnit] = (0, react.useState)("m");
2569
+ const editing = draft !== null && draft !== void 0 ? draft : value;
2570
+ const hasChanges = draft !== null && (draft.from !== value.from || draft.to !== value.to);
2571
+ const description = (0, react.useMemo)(() => describeRange(value, labels, {
2572
+ locale,
2573
+ timeZone
2574
+ }), [
2575
+ value,
2576
+ locale,
2577
+ timeZone,
2578
+ customLabels
2579
+ ]);
2580
+ const boundState = (0, react.useCallback)((expression) => (0, _reopt_ai_opt_charts.parseTimeExpression)(expression, { timeZone }) === null ? labels.invalid : void 0, [timeZone, labels]);
2581
+ const commit = (0, react.useCallback)((range) => {
2582
+ setDraft(null);
2583
+ onChange(range);
2584
+ }, [onChange]);
2585
+ const apply = (0, react.useCallback)(() => {
2586
+ if (draft) commit(draft);
2587
+ else onRefresh === null || onRefresh === void 0 || onRefresh();
2588
+ }, [
2589
+ draft,
2590
+ commit,
2591
+ onRefresh
2592
+ ]);
2593
+ const commitOnEnter = (0, react.useCallback)((event) => {
2594
+ if (event.key !== "Enter") return;
2595
+ apply();
2596
+ setOpen(false);
2597
+ }, [apply]);
2598
+ const applyQuick = (0, react.useCallback)(() => {
2599
+ const count = Number(quickCount);
2600
+ if (!Number.isFinite(count) || count <= 0) return;
2601
+ commit({
2602
+ from: `now-${Math.round(count)}${quickUnit}`,
2603
+ to: "now"
2604
+ });
2605
+ setOpen(false);
2606
+ }, [
2607
+ quickCount,
2608
+ quickUnit,
2609
+ commit
2610
+ ]);
2611
+ const step = (0, react.useCallback)((direction) => {
2612
+ const next = (0, _reopt_ai_opt_charts.shiftTimeRange)(value, direction, { timeZone });
2613
+ if (next) commit(next);
2614
+ }, [
2615
+ value,
2616
+ timeZone,
2617
+ commit
2618
+ ]);
2619
+ const zoom = (0, react.useCallback)((factor) => {
2620
+ const next = (0, _reopt_ai_opt_charts.zoomTimeRange)(value, factor, { timeZone });
2621
+ if (next) commit(next);
2622
+ }, [
2623
+ value,
2624
+ timeZone,
2625
+ commit
2626
+ ]);
2627
+ useAutoRefresh({
2628
+ interval: (_refresh$interval = refresh === null || refresh === void 0 ? void 0 : refresh.interval) !== null && _refresh$interval !== void 0 ? _refresh$interval : 0,
2629
+ paused: (_refresh$paused = refresh === null || refresh === void 0 ? void 0 : refresh.paused) !== null && _refresh$paused !== void 0 ? _refresh$paused : true,
2630
+ onRefresh
2631
+ });
2632
+ const refreshLabel = !refresh || refresh.paused || refresh.interval === 0 ? labels.autoRefreshOff : labels.every((0, _reopt_ai_opt_charts.formatDuration)(refresh.interval, locale));
2633
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2634
+ className: require_pagination.cn("bg-surface inline-flex items-center rounded-lg border", require_pagination.OPT_BORDER, className),
2635
+ role: "group",
2636
+ "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : labels.trigger,
2637
+ "data-opt-id": "3WJ8H",
2638
+ "data-opt-slug": "time-range-control.TimeRangeControl",
2639
+ children: [
2640
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.PopoverRoot, {
2641
+ open,
2642
+ onOpenChange: setOpen,
2643
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.PopoverTrigger, {
2644
+ className: require_pagination.cn("text-text-primary h-10 gap-2", description.isInvalid && "text-danger-fg"),
2645
+ disabled,
2646
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Clock, {
2647
+ className: "text-text-tertiary h-4 w-4",
2648
+ "aria-hidden": "true"
2649
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2650
+ className: "truncate text-sm",
2651
+ children: description.text
2652
+ })]
2653
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.PopoverContent, {
2654
+ className: "gap-group flex w-80 flex-col",
2655
+ children: [
2656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2657
+ className: "gap-element flex flex-col",
2658
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2659
+ className: "text-text-primary text-xs font-medium",
2660
+ children: labels.quickSelect
2661
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2662
+ className: "flex items-end gap-2",
2663
+ children: [
2664
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Input, {
2665
+ "aria-label": labels.quickSelect,
2666
+ type: "number",
2667
+ min: 1,
2668
+ size: "sm",
2669
+ className: "w-20",
2670
+ value: quickCount,
2671
+ onChange: (event) => setQuickCount(event.target.value),
2672
+ onKeyDown: (event) => {
2673
+ if (event.key === "Enter") applyQuick();
2674
+ }
2675
+ }),
2676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectRoot, {
2677
+ value: quickUnit,
2678
+ setValue: (next) => setQuickUnit(selectValue(next)),
2679
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectTrigger, {
2680
+ minWidth: 110,
2681
+ "aria-label": labels.quickSelect
2682
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectPopover, { children: QUICK_UNITS.map((unit) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectItem, {
2683
+ value: unit,
2684
+ children: labels.units[unit]
2685
+ }, unit)) })]
2686
+ }),
2687
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Button, {
2688
+ variant: "secondary",
2689
+ size: "sm",
2690
+ onClick: applyQuick,
2691
+ children: labels.apply
2692
+ })
2693
+ ]
2694
+ })]
2695
+ }),
2696
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2697
+ className: "gap-element flex flex-col",
2698
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Input, {
2699
+ label: labels.start,
2700
+ size: "sm",
2701
+ value: editing.from,
2702
+ error: boundState(editing.from),
2703
+ hint: labels.startHint,
2704
+ onChange: (event) => setDraft(require_pagination._objectSpread2(require_pagination._objectSpread2({}, editing), {}, { from: event.target.value })),
2705
+ onKeyDown: commitOnEnter
2706
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Input, {
2707
+ label: labels.end,
2708
+ size: "sm",
2709
+ value: editing.to,
2710
+ error: boundState(editing.to),
2711
+ onChange: (event) => setDraft(require_pagination._objectSpread2(require_pagination._objectSpread2({}, editing), {}, { to: event.target.value })),
2712
+ onKeyDown: commitOnEnter
2713
+ })]
2714
+ }),
2715
+ presets && presets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeList, {
2716
+ title: labels.commonlyUsed,
2717
+ items: presets,
2718
+ onPick: (range) => {
2719
+ commit(range);
2720
+ setOpen(false);
2721
+ }
2722
+ }),
2723
+ recentRanges && recentRanges.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeList, {
2724
+ title: labels.recentlyUsed,
2725
+ items: recentRanges.slice(0, 5),
2726
+ onPick: (range) => {
2727
+ commit(range);
2728
+ setOpen(false);
2729
+ }
2730
+ }),
2731
+ onRefreshChange && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2732
+ className: "gap-element flex flex-col",
2733
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2734
+ className: "text-text-primary text-xs font-medium",
2735
+ children: labels.autoRefresh
2736
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectRoot, {
2737
+ value: String((refresh === null || refresh === void 0 ? void 0 : refresh.paused) ? 0 : (_refresh$interval2 = refresh === null || refresh === void 0 ? void 0 : refresh.interval) !== null && _refresh$interval2 !== void 0 ? _refresh$interval2 : 0),
2738
+ setValue: (next) => {
2739
+ const interval = Number(selectValue(next));
2740
+ onRefreshChange({
2741
+ interval,
2742
+ paused: interval === 0
2743
+ });
2744
+ },
2745
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectTrigger, {
2746
+ minWidth: 140,
2747
+ "aria-label": labels.autoRefresh
2748
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectPopover, { children: REFRESH_INTERVALS.map((interval) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectItem, {
2749
+ value: String(interval),
2750
+ children: interval === 0 ? labels.autoRefreshOff : labels.every((0, _reopt_ai_opt_charts.formatDuration)(interval, locale))
2751
+ }, interval)) })]
2752
+ })]
2753
+ })
2754
+ ]
2755
+ })]
2756
+ }),
2757
+ showWindowButtons && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2758
+ className: require_pagination.cn("flex items-center border-l", "border border-border"),
2759
+ role: "group",
2760
+ "aria-label": labels.previous,
2761
+ children: [
2762
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
2763
+ label: labels.previous,
2764
+ disabled: disabled || description.isInvalid,
2765
+ onClick: () => step(-1),
2766
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, {
2767
+ className: "h-4 w-4",
2768
+ "aria-hidden": "true"
2769
+ })
2770
+ }),
2771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
2772
+ label: labels.zoomIn,
2773
+ disabled: disabled || description.isInvalid,
2774
+ onClick: () => zoom(-zoomFactor),
2775
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ZoomIn, {
2776
+ className: "h-4 w-4",
2777
+ "aria-hidden": "true"
2778
+ })
2779
+ }),
2780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
2781
+ label: labels.zoomOut,
2782
+ disabled: disabled || description.isInvalid,
2783
+ onClick: () => zoom(zoomFactor),
2784
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ZoomOut, {
2785
+ className: "h-4 w-4",
2786
+ "aria-hidden": "true"
2787
+ })
2788
+ }),
2789
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
2790
+ label: labels.nextWindow,
2791
+ disabled: disabled || description.isInvalid,
2792
+ onClick: () => step(1),
2793
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
2794
+ className: "h-4 w-4",
2795
+ "aria-hidden": "true"
2796
+ })
2797
+ })
2798
+ ]
2799
+ }),
2800
+ (onRefresh || showUpdateButton) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2801
+ className: require_pagination.cn("border-l pl-2 pr-2", "border border-border"),
2802
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Button, {
2803
+ variant: hasChanges ? "primary" : "secondary",
2804
+ size: "sm",
2805
+ loading: isLoading,
2806
+ disabled: disabled || description.isInvalid,
2807
+ onClick: apply,
2808
+ leftIcon: hasChanges ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.RefreshCw, {
2809
+ className: "h-3.5 w-3.5",
2810
+ "aria-hidden": "true"
2811
+ }),
2812
+ title: refreshLabel,
2813
+ children: hasChanges ? labels.update : labels.refresh
2814
+ })
2815
+ })
2816
+ ]
2817
+ });
2818
+ }
2819
+ TimeRangeControl.displayName = "TimeRangeControl";
2820
+ function IconButton({ label, disabled, onClick, children }) {
2821
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Button, {
2822
+ variant: "ghost",
2823
+ size: "sm",
2824
+ "aria-label": label,
2825
+ title: label,
2826
+ disabled,
2827
+ onClick,
2828
+ children
2829
+ });
2830
+ }
2831
+ function RangeList({ title, items, onPick }) {
2832
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2833
+ className: "gap-element flex flex-col",
2834
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2835
+ className: "text-text-primary text-xs font-medium",
2836
+ children: title
2837
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2838
+ className: "grid grid-cols-2 gap-1",
2839
+ children: items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2840
+ type: "button",
2841
+ className: require_pagination.cn("hover:bg-bg-subtle w-full cursor-pointer rounded px-2 py-1 text-left text-xs", require_pagination.OPT_TEXT_SECONDARY),
2842
+ onClick: () => onPick(item.range),
2843
+ children: item.label
2844
+ }) }, `${item.label}-${index}`))
2845
+ })]
2846
+ });
2847
+ }
2848
+ /**
2849
+ * The windows most analytics tools open with. Exported so a caller gets a
2850
+ * sensible list without inventing date math of their own.
2851
+ */
2852
+ const DEFAULT_TIME_PRESETS = [
2853
+ {
2854
+ label: "Last 15 minutes",
2855
+ range: {
2856
+ from: "now-15m",
2857
+ to: "now"
2858
+ }
2859
+ },
2860
+ {
2861
+ label: "Last hour",
2862
+ range: {
2863
+ from: "now-1h",
2864
+ to: "now"
2865
+ }
2866
+ },
2867
+ {
2868
+ label: "Last 24 hours",
2869
+ range: {
2870
+ from: "now-24h",
2871
+ to: "now"
2872
+ }
2873
+ },
2874
+ {
2875
+ label: "Last 7 days",
2876
+ range: {
2877
+ from: "now-7d",
2878
+ to: "now"
2879
+ }
2880
+ },
2881
+ {
2882
+ label: "Last 30 days",
2883
+ range: {
2884
+ from: "now-30d",
2885
+ to: "now"
2886
+ }
2887
+ },
2888
+ {
2889
+ label: "Last 90 days",
2890
+ range: {
2891
+ from: "now-90d",
2892
+ to: "now"
2893
+ }
2894
+ },
2895
+ {
2896
+ label: "Today",
2897
+ range: {
2898
+ from: "now/d",
2899
+ to: "now/d"
2900
+ }
2901
+ },
2902
+ {
2903
+ label: "This week",
2904
+ range: {
2905
+ from: "now/w",
2906
+ to: "now/w"
2907
+ }
2908
+ }
2909
+ ];
2910
+ //#endregion
2911
+ //#region src/shells/time-series-panel.tsx
2912
+ const defaultLabels$3 = {
2913
+ back: "Back to previous range",
2914
+ legend: "Series"
2915
+ };
2916
+ /**
2917
+ * TimeSeriesPanel — a chart you can steer.
2918
+ *
2919
+ * The point of putting the control and the chart in one component is the
2920
+ * loop between them: drag across the plot and the window narrows to what you
2921
+ * dragged, which re-queries, which redraws the axis you just picked. That
2922
+ * only works if someone owns the step back out again — so zoom keeps a
2923
+ * history, and the header offers the way back rather than leaving the reader
2924
+ * to retype the range they came from.
2925
+ *
2926
+ * @example
2927
+ * ```tsx
2928
+ * const [range, setRange] = useState<TimeRange>({ from: "now-7d", to: "now" });
2929
+ *
2930
+ * <TimeSeriesPanel
2931
+ * title="Sessions"
2932
+ * data={points}
2933
+ * series={[{ dataKey: "sessions", name: "Sessions" }]}
2934
+ * range={range}
2935
+ * onRangeChange={setRange}
2936
+ * onRefresh={() => refetch()}
2937
+ * toggleableLegend
2938
+ * />
2939
+ * ```
2940
+ */
2941
+ function TimeSeriesPanel({ title, description, actions, data, series, range, onRangeChange, granularity = "auto", timeZone, locale, showRangeControl = true, presets, recentRanges, refresh, onRefreshChange, onRefresh, toggleableLegend = false, annotations, isLoading = false, height = 280, className, chartProps, labels: customLabels, "aria-label": ariaLabel }) {
2942
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$3), customLabels);
2943
+ const [history, setHistory] = (0, react.useState)([]);
2944
+ const [hidden, setHidden] = (0, react.useState)([]);
2945
+ const resolved = (0, react.useMemo)(() => (0, _reopt_ai_opt_charts.resolveTimeRange)(range, { timeZone }), [range, timeZone]);
2946
+ const visibleSeries = (0, react.useMemo)(() => series.filter((def) => !hidden.includes(def.dataKey)), [series, hidden]);
2947
+ const handleRangeSelect = (0, react.useCallback)((selection) => {
2948
+ setHistory((current) => [...current, range].slice(-10));
2949
+ onRangeChange((0, _reopt_ai_opt_charts.toAbsoluteRange)(selection));
2950
+ }, [range, onRangeChange]);
2951
+ const goBack = (0, react.useCallback)(() => {
2952
+ setHistory((current) => {
2953
+ const previous = current[current.length - 1];
2954
+ if (previous) onRangeChange(previous);
2955
+ return current.slice(0, -1);
2956
+ });
2957
+ }, [onRangeChange]);
2958
+ const setRange = (0, react.useCallback)((next) => {
2959
+ setHistory([]);
2960
+ onRangeChange(next);
2961
+ }, [onRangeChange]);
2962
+ const toggleSeries = (0, react.useCallback)((dataKey) => {
2963
+ setHidden((current) => current.includes(dataKey) ? current.filter((key) => key !== dataKey) : [...current, dataKey]);
2964
+ }, []);
2965
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2966
+ className: require_pagination.cn(require_pagination.OPT_SURFACE, require_pagination.OPT_BORDER, "flex flex-col", className),
2967
+ "aria-label": ariaLabel,
2968
+ "data-opt-id": "Y0XCY",
2969
+ "data-opt-slug": "time-series-panel.TimeSeriesPanel",
2970
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2971
+ className: "gap-group flex flex-wrap items-start justify-between px-5 pt-5 pb-3",
2972
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2973
+ className: "gap-element flex min-w-0 flex-col",
2974
+ children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2975
+ className: "text-text-primary truncate text-base font-semibold",
2976
+ children: title
2977
+ }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2978
+ className: require_pagination.cn("text-text-secondary", "truncate text-sm"),
2979
+ children: description
2980
+ })]
2981
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2982
+ className: "gap-element flex flex-wrap items-center",
2983
+ children: [
2984
+ history.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Button, {
2985
+ variant: "ghost",
2986
+ size: "sm",
2987
+ onClick: goBack,
2988
+ leftIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Undo2, {
2989
+ className: "h-3.5 w-3.5",
2990
+ "aria-hidden": "true"
2991
+ }),
2992
+ children: labels.back
2993
+ }),
2994
+ actions,
2995
+ showRangeControl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeRangeControl, {
2996
+ value: range,
2997
+ onChange: setRange,
2998
+ presets,
2999
+ recentRanges,
3000
+ timeZone,
3001
+ locale,
3002
+ refresh,
3003
+ onRefreshChange,
3004
+ onRefresh,
3005
+ isLoading,
3006
+ labels: customLabels === null || customLabels === void 0 ? void 0 : customLabels.control
3007
+ })
3008
+ ]
3009
+ })]
3010
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3011
+ className: "px-2 pb-4",
3012
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_charts.TimeSeriesChart, require_pagination._objectSpread2({
3013
+ data,
3014
+ series: visibleSeries,
3015
+ from: resolved === null || resolved === void 0 ? void 0 : resolved.from,
3016
+ to: resolved === null || resolved === void 0 ? void 0 : resolved.to,
3017
+ granularity,
3018
+ timeZone,
3019
+ locale,
3020
+ height,
3021
+ selectable: true,
3022
+ onRangeSelect: handleRangeSelect,
3023
+ annotations,
3024
+ showLegend: series.length > 1,
3025
+ legendOptions: toggleableLegend ? {
3026
+ onClick: toggleSeries,
3027
+ inactiveKeys: hidden
3028
+ } : void 0,
3029
+ "aria-label": typeof title === "string" ? `${title} time series` : ariaLabel
3030
+ }, chartProps))
3031
+ })]
3032
+ });
3033
+ }
3034
+ TimeSeriesPanel.displayName = "TimeSeriesPanel";
3035
+ //#endregion
3036
+ //#region src/shells/log-table.tsx
3037
+ const HIDE_BELOW = {
3038
+ always: "",
3039
+ sm: "hidden @[40rem]/log:flex",
3040
+ md: "hidden @[46rem]/log:flex",
3041
+ lg: "hidden @[58rem]/log:flex",
3042
+ xl: "hidden @[70rem]/log:flex"
3043
+ };
3044
+ const ROW_HEIGHT = {
3045
+ compact: 26,
3046
+ normal: 32,
3047
+ comfortable: 40
3048
+ };
3049
+ const DENSITY_TEXT = {
3050
+ compact: "text-[11px]",
3051
+ normal: "text-xs",
3052
+ comfortable: "text-sm"
3053
+ };
3054
+ const defaultLabels$2 = {
3055
+ listLabel: "Log",
3056
+ loadingMore: "Loading earlier entries…",
3057
+ endOfResults: "That is every entry in this range.",
3058
+ filterFor: (value) => `Filter for ${value}`,
3059
+ filterOut: (value) => `Filter out ${value}`,
3060
+ sortAscending: "Sort ascending",
3061
+ sortDescending: "Sort descending"
3062
+ };
3063
+ /** Rows rendered beyond the viewport on each side, so a fast scroll stays filled. */
3064
+ const OVERSCAN = 10;
3065
+ /**
3066
+ * A stable hue per value. Colour does real work in a log — the eye groups by it
3067
+ * long before it reads the text — so the same event name has to land on the
3068
+ * same hue on every render and in every session, which rules out an index into
3069
+ * a palette.
3070
+ */
3071
+ function hueOf(value) {
3072
+ let hash = 0;
3073
+ for (let index = 0; index < value.length; index += 1) hash = hash * 31 + value.charCodeAt(index) | 0;
3074
+ return Math.abs(hash) % 360;
3075
+ }
3076
+ const EM_DASH = "—";
3077
+ /**
3078
+ * A column with nothing in it is a fact about the entry, not a value. Dimming
3079
+ * every absence the same way lets the eye skip the gaps while scanning, instead
3080
+ * of reading a dash as though it were data.
3081
+ */
3082
+ const ABSENT = "text-text-tertiary/50";
3083
+ function columnStyle(column) {
3084
+ return column.width ? {
3085
+ width: column.width,
3086
+ flex: "0 0 auto"
3087
+ } : {
3088
+ flex: "1 1 0%",
3089
+ minWidth: 0
3090
+ };
3091
+ }
3092
+ /**
3093
+ * LogTable — a windowed table for entries that keep arriving.
3094
+ *
3095
+ * Written by hand rather than delegated to a data grid for the two things a log
3096
+ * needs that a grid does not: rows are inserted at the *top* while the reader
3097
+ * is partway down, which needs a scroll correction applied before paint, and
3098
+ * "am I at the newest row?" has to be answerable from the scroll container to
3099
+ * decide whether to follow the tail or hold position.
3100
+ *
3101
+ * What it takes from a data grid is the part readers actually reach for:
3102
+ * columns are data, so a caller can let people add, drop and reorder them; a
3103
+ * cell offers to filter for or against its own value; and density is a setting
3104
+ * rather than a constant.
3105
+ *
3106
+ * @example
3107
+ * ```tsx
3108
+ * <LogTable
3109
+ * rows={rows}
3110
+ * getRowId={(row) => row.id}
3111
+ * columns={[
3112
+ * { id: "time", header: "Time", width: "9rem", mono: true, value: (r) => r.time },
3113
+ * { id: "name", header: "Event", width: "14rem", field: "name", marker: true, value: (r) => r.name },
3114
+ * { id: "path", header: "Path", field: "path", value: (r) => r.path },
3115
+ * ]}
3116
+ * onCellAction={(action) => setQuery(applyCellAction(query, action))}
3117
+ * />
3118
+ * ```
3119
+ */
3120
+ function LogTableInner({ rows, columns, getRowId, selectedId = null, onSelect, onLoadMore, hasMore = false, loadingMore = false, prepending = false, onFollowChange, isNew, sort = null, onSortChange, onCellAction, density = "normal", showHeader = true, emptyState, labels: customLabels, className }, ref) {
3121
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$2), customLabels);
3122
+ const rowHeight = ROW_HEIGHT[density];
3123
+ const scrollRef = (0, react.useRef)(null);
3124
+ const [scrollTop, setScrollTop] = (0, react.useState)(0);
3125
+ const [viewportHeight, setViewportHeight] = (0, react.useState)(0);
3126
+ const followingRef = (0, react.useRef)(true);
3127
+ const previousTopIdRef = (0, react.useRef)(null);
3128
+ const previousLengthRef = (0, react.useRef)(0);
3129
+ (0, react.useImperativeHandle)(ref, () => ({
3130
+ scrollToTop: () => {
3131
+ const container = scrollRef.current;
3132
+ if (!container) return;
3133
+ container.scrollTop = 0;
3134
+ setScrollTop(0);
3135
+ if (!followingRef.current) {
3136
+ followingRef.current = true;
3137
+ onFollowChange === null || onFollowChange === void 0 || onFollowChange(true);
3138
+ }
3139
+ },
3140
+ revealRow: (id) => {
3141
+ const index = rows.findIndex((row) => getRowId(row) === id);
3142
+ const container = scrollRef.current;
3143
+ if (index === -1 || !container) return;
3144
+ const top = index * rowHeight;
3145
+ const bottom = top + rowHeight;
3146
+ if (top < container.scrollTop) container.scrollTop = top;
3147
+ else if (bottom > container.scrollTop + container.clientHeight) container.scrollTop = bottom - container.clientHeight;
3148
+ }
3149
+ }), [
3150
+ onFollowChange,
3151
+ rows,
3152
+ getRowId,
3153
+ rowHeight
3154
+ ]);
3155
+ (0, react.useEffect)(() => {
3156
+ const container = scrollRef.current;
3157
+ if (!container) return;
3158
+ const observer = new ResizeObserver(() => setViewportHeight(container.clientHeight));
3159
+ observer.observe(container);
3160
+ setViewportHeight(container.clientHeight);
3161
+ return () => observer.disconnect();
3162
+ }, []);
3163
+ /**
3164
+ * Report whether the list is pinned to the newest row.
3165
+ *
3166
+ * Called from more than the scroll handler on purpose: the list can arrive at
3167
+ * the top without a scroll event — a reset, a jump to newest, a shorter
3168
+ * result set — and a stale "not following" would silently divert every live
3169
+ * row into a held-back pile.
3170
+ */
3171
+ const syncFollowing = (0, react.useCallback)((container) => {
3172
+ const following = container.scrollTop <= rowHeight;
3173
+ if (following === followingRef.current) return;
3174
+ followingRef.current = following;
3175
+ onFollowChange === null || onFollowChange === void 0 || onFollowChange(following);
3176
+ }, [onFollowChange, rowHeight]);
3177
+ /**
3178
+ * Hold the reader's place when rows arrive above them.
3179
+ *
3180
+ * Runs before paint: by the time the browser draws, the scroll offset has
3181
+ * already absorbed the inserted height, so the content under the cursor never
3182
+ * moves. Skipped while following, where the whole point is to stay at the top.
3183
+ */
3184
+ (0, react.useLayoutEffect)(() => {
3185
+ const container = scrollRef.current;
3186
+ const previousTopId = previousTopIdRef.current;
3187
+ previousTopIdRef.current = rows.length ? getRowId(rows[0]) : null;
3188
+ const previousLength = previousLengthRef.current;
3189
+ previousLengthRef.current = rows.length;
3190
+ if (!container) return;
3191
+ syncFollowing(container);
3192
+ if (!prepending || followingRef.current || !previousTopId) return;
3193
+ if (rows.length <= previousLength) return;
3194
+ const prepended = rows.findIndex((row) => getRowId(row) === previousTopId);
3195
+ if (prepended <= 0) return;
3196
+ container.scrollTop += prepended * rowHeight;
3197
+ setScrollTop(container.scrollTop);
3198
+ }, [
3199
+ rows,
3200
+ getRowId,
3201
+ prepending,
3202
+ rowHeight,
3203
+ syncFollowing
3204
+ ]);
3205
+ /**
3206
+ * Page forward when the rows do not reach the bottom of the viewport.
3207
+ *
3208
+ * Without this the list only advances on a scroll event, so a tall screen —
3209
+ * or a filter matching few rows per page — stops at the first page with no
3210
+ * way to ask for more.
3211
+ */
3212
+ (0, react.useEffect)(() => {
3213
+ const container = scrollRef.current;
3214
+ if (!container || !hasMore || loadingMore || !onLoadMore) return;
3215
+ if (container.scrollHeight <= container.clientHeight) onLoadMore();
3216
+ }, [
3217
+ hasMore,
3218
+ loadingMore,
3219
+ onLoadMore,
3220
+ rows.length,
3221
+ viewportHeight
3222
+ ]);
3223
+ const handleScroll = (0, react.useCallback)((event) => {
3224
+ const container = event.currentTarget;
3225
+ setScrollTop(container.scrollTop);
3226
+ syncFollowing(container);
3227
+ const remaining = container.scrollHeight - container.scrollTop - container.clientHeight;
3228
+ if (hasMore && !loadingMore && remaining < container.clientHeight) onLoadMore === null || onLoadMore === void 0 || onLoadMore();
3229
+ }, [
3230
+ hasMore,
3231
+ loadingMore,
3232
+ onLoadMore,
3233
+ syncFollowing
3234
+ ]);
3235
+ const totalHeight = rows.length * rowHeight;
3236
+ const firstVisible = Math.max(0, Math.floor(scrollTop / rowHeight) - OVERSCAN);
3237
+ const visibleCount = Math.ceil((viewportHeight || 480) / rowHeight) + 20;
3238
+ const lastVisible = Math.min(rows.length, firstVisible + visibleCount);
3239
+ const visibleRows = (0, react.useMemo)(() => rows.slice(firstVisible, lastVisible), [
3240
+ rows,
3241
+ firstVisible,
3242
+ lastVisible
3243
+ ]);
3244
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3245
+ className: require_pagination.cn("@container/log flex min-h-0 flex-col", className),
3246
+ "data-opt-id": "NJ4N9",
3247
+ "data-opt-slug": "log-table.LogTable",
3248
+ children: [showHeader && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LogTableHeader, {
3249
+ columns,
3250
+ sort,
3251
+ onSortChange,
3252
+ density,
3253
+ labels
3254
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3255
+ ref: scrollRef,
3256
+ onScroll: handleScroll,
3257
+ className: "relative min-h-0 flex-1 overflow-x-hidden overflow-y-auto",
3258
+ role: "listbox",
3259
+ "aria-label": labels.listLabel,
3260
+ "aria-activedescendant": selectedId ? `log-row-${selectedId}` : void 0,
3261
+ tabIndex: 0,
3262
+ children: [
3263
+ rows.length === 0 ? emptyState : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3264
+ style: { height: totalHeight },
3265
+ className: "relative",
3266
+ children: visibleRows.map((row, index) => {
3267
+ var _isNew;
3268
+ const id = getRowId(row);
3269
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LogTableRow, {
3270
+ id,
3271
+ row,
3272
+ columns,
3273
+ top: (firstVisible + index) * rowHeight,
3274
+ height: rowHeight,
3275
+ density,
3276
+ selected: id === selectedId,
3277
+ isNew: (_isNew = isNew === null || isNew === void 0 ? void 0 : isNew(row)) !== null && _isNew !== void 0 ? _isNew : false,
3278
+ onSelect,
3279
+ onCellAction,
3280
+ labels
3281
+ }, id);
3282
+ })
3283
+ }),
3284
+ loadingMore && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3285
+ className: "gap-element text-text-tertiary flex items-center justify-center py-3 text-xs",
3286
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Spinner, {
3287
+ size: "sm",
3288
+ "aria-hidden": true
3289
+ }), labels.loadingMore]
3290
+ }),
3291
+ !hasMore && rows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3292
+ className: require_pagination.cn("text-text-tertiary", "py-3 text-center text-xs"),
3293
+ children: labels.endOfResults
3294
+ })
3295
+ ]
3296
+ })]
3297
+ });
3298
+ }
3299
+ /**
3300
+ * LogTable — see {@link LogTableInner}.
3301
+ *
3302
+ * The cast preserves the row generic through `forwardRef`, which otherwise
3303
+ * widens it to its constraint and takes the column callbacks' types with it.
3304
+ */
3305
+ const LogTable = (0, react.forwardRef)(LogTableInner);
3306
+ function LogTableHeader({ columns, sort, onSortChange, density, labels }) {
3307
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3308
+ role: "row",
3309
+ className: require_pagination.cn("border-border-subtle text-text-tertiary flex shrink-0 items-center gap-3 border-b px-3", DENSITY_TEXT[density]),
3310
+ style: { height: ROW_HEIGHT[density] },
3311
+ children: columns.map((column) => {
3312
+ var _column$priority;
3313
+ const active = (sort === null || sort === void 0 ? void 0 : sort.columnId) === column.id;
3314
+ const sortable = column.sortable && onSortChange;
3315
+ const content = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3316
+ column.marker && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3317
+ "aria-hidden": true,
3318
+ className: "size-1.5 shrink-0"
3319
+ }),
3320
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3321
+ className: "truncate",
3322
+ children: column.header
3323
+ }),
3324
+ active && (sort.direction === "asc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronUp, {
3325
+ className: "size-3 shrink-0",
3326
+ "aria-hidden": true
3327
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {
3328
+ className: "size-3 shrink-0",
3329
+ "aria-hidden": true
3330
+ }))
3331
+ ] });
3332
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3333
+ role: "columnheader",
3334
+ "aria-sort": active ? sort.direction === "asc" ? "ascending" : "descending" : void 0,
3335
+ className: require_pagination.cn("flex items-center gap-1.5", column.align === "right" && "justify-end", HIDE_BELOW[(_column$priority = column.priority) !== null && _column$priority !== void 0 ? _column$priority : "always"]),
3336
+ style: columnStyle(column),
3337
+ children: sortable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3338
+ type: "button",
3339
+ className: "hover:text-text-primary flex cursor-pointer items-center gap-1.5 truncate",
3340
+ title: active && sort.direction === "desc" ? labels.sortAscending : labels.sortDescending,
3341
+ onClick: () => onSortChange({
3342
+ columnId: column.id,
3343
+ direction: active && sort.direction === "desc" ? "asc" : "desc"
3344
+ }),
3345
+ children: content
3346
+ }) : content
3347
+ }, column.id);
3348
+ })
3349
+ });
3350
+ }
3351
+ function LogTableRowImpl({ id, row, columns, top, height, density, selected, isNew, onSelect, onCellAction, labels }) {
3352
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3353
+ role: "option",
3354
+ "aria-selected": selected,
3355
+ tabIndex: -1,
3356
+ id: `log-row-${id}`,
3357
+ "data-log-row-id": id,
3358
+ onClick: () => onSelect === null || onSelect === void 0 ? void 0 : onSelect(id),
3359
+ onKeyDown: (event) => {
3360
+ if (event.key !== "Enter" && event.key !== " ") return;
3361
+ event.preventDefault();
3362
+ onSelect === null || onSelect === void 0 || onSelect(id);
3363
+ },
3364
+ style: {
3365
+ top,
3366
+ height
3367
+ },
3368
+ className: require_pagination.cn("border-border-subtle/60 absolute inset-x-0 flex items-center gap-3 border-b px-3", DENSITY_TEXT[density], onSelect && "cursor-pointer", "transition-colors duration-100", "hover:bg-surface-hover", selected && "bg-accent-subtle hover:bg-accent-subtle", isNew && !selected && "opt-log-row-enter"),
3369
+ children: columns.map((column) => {
3370
+ var _column$priority2;
3371
+ const raw = column.value(row);
3372
+ const text = raw === null || raw === void 0 || raw === "" ? "" : String(raw);
3373
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3374
+ className: require_pagination.cn("group/cell relative flex min-w-0 items-center gap-1.5", column.align === "right" && "justify-end", HIDE_BELOW[(_column$priority2 = column.priority) !== null && _column$priority2 !== void 0 ? _column$priority2 : "always"]),
3375
+ style: columnStyle(column),
3376
+ children: [
3377
+ column.marker && text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3378
+ "aria-hidden": true,
3379
+ className: "size-1.5 shrink-0 rounded-full",
3380
+ style: { backgroundColor: `hsl(${hueOf(text)} 65% 55%)` }
3381
+ }),
3382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3383
+ className: require_pagination.cn("truncate", column.mono && "font-mono", text ? "text-text-secondary" : ABSENT),
3384
+ title: text || void 0,
3385
+ children: column.render ? column.render(row) : text || EM_DASH
3386
+ }),
3387
+ onCellAction && column.field && text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CellActions, {
3388
+ field: column.field,
3389
+ value: text,
3390
+ onAction: onCellAction,
3391
+ labels
3392
+ })
3393
+ ]
3394
+ }, column.id);
3395
+ })
3396
+ });
3397
+ }
3398
+ /**
3399
+ * Memoized because a live log re-renders several times a second while almost
3400
+ * every visible row is unchanged.
3401
+ */
3402
+ const LogTableRow = (0, react.memo)(LogTableRowImpl);
3403
+ /**
3404
+ * The two questions a reader has about a value they just spotted: show me only
3405
+ * these, and get these out of my way.
3406
+ *
3407
+ * Revealed on hover rather than always drawn — a log is scanned, and a pair of
3408
+ * buttons on every cell of every row is a wall of chrome. They stay in the tab
3409
+ * order regardless, since hover is not an interaction everyone has.
3410
+ */
3411
+ function CellActions({ field, value, onAction, labels }) {
3412
+ const button = "cursor-pointer rounded-sm p-0.5 text-text-tertiary hover:bg-bg-muted hover:text-text-primary";
3413
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3414
+ className: require_pagination.cn("bg-surface/90 ml-auto hidden shrink-0 items-center gap-0.5 rounded pl-1", "group-hover/cell:flex focus-within:flex"),
3415
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3416
+ type: "button",
3417
+ className: button,
3418
+ title: labels.filterFor(value),
3419
+ "aria-label": labels.filterFor(value),
3420
+ onClick: (event) => {
3421
+ event.stopPropagation();
3422
+ onAction({
3423
+ field,
3424
+ value,
3425
+ intent: "include"
3426
+ });
3427
+ },
3428
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, {
3429
+ className: "size-3",
3430
+ "aria-hidden": true
3431
+ })
3432
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3433
+ type: "button",
3434
+ className: button,
3435
+ title: labels.filterOut(value),
3436
+ "aria-label": labels.filterOut(value),
3437
+ onClick: (event) => {
3438
+ event.stopPropagation();
3439
+ onAction({
3440
+ field,
3441
+ value,
3442
+ intent: "exclude"
3443
+ });
3444
+ },
3445
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Minus, {
3446
+ className: "size-3",
3447
+ "aria-hidden": true
3448
+ })
3449
+ })]
3450
+ });
3451
+ }
3452
+ //#endregion
3453
+ //#region src/lib/query/query-ast.ts
3454
+ const EMPTY_QUERY = { clauses: [] };
3455
+ /** Whether a query constrains anything at all. */
3456
+ function isEmptyQuery(query) {
3457
+ return !query || query.clauses.length === 0;
3458
+ }
3459
+ function sameClause(a, b) {
3460
+ if (a.type !== b.type) return false;
3461
+ if (a.type === "term" && b.type === "term") return a.value === b.value && a.negated === b.negated;
3462
+ const fieldA = a;
3463
+ const fieldB = b;
3464
+ return fieldA.field === fieldB.field && fieldA.operator === fieldB.operator && fieldA.negated === fieldB.negated && fieldA.values.length === fieldB.values.length && fieldA.values.every((value, index) => value === fieldB.values[index]);
3465
+ }
3466
+ /** Add a clause unless an identical one is already there. */
3467
+ function addClause(query, clause) {
3468
+ if (query.clauses.some((existing) => sameClause(existing, clause))) return query;
3469
+ return { clauses: [...query.clauses, clause] };
3470
+ }
3471
+ /**
3472
+ * Constrain a field to a value — what a cell's "filter for this" action does.
3473
+ *
3474
+ * Applying the opposite of a constraint already present replaces it rather than
3475
+ * adding it: `status:200` then "filter out 200" should read as one changed
3476
+ * mind, not as a query that can never match.
3477
+ */
3478
+ function withFieldValue(query, field, value, options = {}) {
3479
+ var _options$negated, _options$operator;
3480
+ const negated = (_options$negated = options.negated) !== null && _options$negated !== void 0 ? _options$negated : false;
3481
+ const operator = (_options$operator = options.operator) !== null && _options$operator !== void 0 ? _options$operator : "eq";
3482
+ return { clauses: [...query.clauses.filter((clause) => !(clause.type === "field" && clause.field === field && clause.operator === operator && clause.values.length === 1 && clause.values[0] === value)), {
3483
+ type: "field",
3484
+ field,
3485
+ operator,
3486
+ values: [value],
3487
+ negated
3488
+ }] };
3489
+ }
3490
+ /** Drop every clause naming `field`. */
3491
+ function withoutField(query, field) {
3492
+ return { clauses: query.clauses.filter((clause) => clause.type !== "field" || clause.field !== field) };
3493
+ }
3494
+ /** Drop the clause at `index`, for a removable chip. */
3495
+ function removeClause(query, index) {
3496
+ return { clauses: query.clauses.filter((_, at) => at !== index) };
3497
+ }
3498
+ /** Every field the query names, in the order they first appear. */
3499
+ function queryFields(query) {
3500
+ const seen = [];
3501
+ for (const clause of query.clauses) if (clause.type === "field" && !seen.includes(clause.field)) seen.push(clause.field);
3502
+ return seen;
3503
+ }
3504
+ /** The values a field is constrained to, if any — for showing a filter as active. */
3505
+ function fieldValues(query, field) {
3506
+ return query.clauses.flatMap((clause) => clause.type === "field" && clause.field === field && !clause.negated ? clause.values : []);
3507
+ }
3508
+ /**
3509
+ * Fold a cell action into the query.
3510
+ *
3511
+ * The pair of buttons on a log cell is the fastest path from "I noticed this
3512
+ * value" to a filtered view, and the reason it works is that the result is a
3513
+ * query the reader can then see, edit and share — not a hidden filter stack
3514
+ * with its own rules.
3515
+ */
3516
+ function applyCellAction(query, action) {
3517
+ return withFieldValue(query, action.field, action.value, { negated: action.intent === "exclude" });
3518
+ }
3519
+ //#endregion
3520
+ //#region src/lib/query/query-parse.ts
3521
+ const FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
3522
+ /**
3523
+ * Split on whitespace, but keep quoted runs and parenthesised groups whole.
3524
+ *
3525
+ * An unterminated quote or group swallows the rest of the line rather than
3526
+ * erroring: the reader is mid-word, and the alternative is the query flickering
3527
+ * between two different meanings as they type the closing character.
3528
+ */
3529
+ function tokenize(input) {
3530
+ const tokens = [];
3531
+ let index = 0;
3532
+ while (index < input.length) {
3533
+ while (index < input.length && /\s/.test(input[index])) index += 1;
3534
+ if (index >= input.length) break;
3535
+ const start = index;
3536
+ let text = "";
3537
+ let quoted = false;
3538
+ let atTokenStart = true;
3539
+ let depth = 0;
3540
+ while (index < input.length) {
3541
+ const char = input[index];
3542
+ if (char === "\"") {
3543
+ if (atTokenStart) quoted = true;
3544
+ atTokenStart = false;
3545
+ index += 1;
3546
+ while (index < input.length && input[index] !== "\"") {
3547
+ text += input[index];
3548
+ index += 1;
3549
+ }
3550
+ index += 1;
3551
+ continue;
3552
+ }
3553
+ if (char === "(") depth += 1;
3554
+ if (char === ")") depth = Math.max(0, depth - 1);
3555
+ if (/\s/.test(char) && depth === 0) break;
3556
+ atTokenStart = false;
3557
+ text += char;
3558
+ index += 1;
3559
+ }
3560
+ if (text.length > 0 || quoted) tokens.push({
3561
+ text,
3562
+ at: start,
3563
+ quoted
3564
+ });
3565
+ }
3566
+ return tokens;
3567
+ }
3568
+ /** `(a or b)` → `["a", "b"]`; anything else is a single value. */
3569
+ function readValues(raw) {
3570
+ const trimmed = raw.trim();
3571
+ if (!trimmed.startsWith("(")) return [stripQuotes(trimmed)];
3572
+ return trimmed.replace(/^\(/, "").replace(/\)$/, "").split(/\s+or\s+/i).map((part) => stripQuotes(part.trim())).filter((part) => part.length > 0);
3573
+ }
3574
+ function stripQuotes(value) {
3575
+ if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1);
3576
+ return value.replace(/^"/, "");
3577
+ }
3578
+ const OPERATORS = [
3579
+ {
3580
+ symbol: ">=",
3581
+ operator: "gte"
3582
+ },
3583
+ {
3584
+ symbol: "<=",
3585
+ operator: "lte"
3586
+ },
3587
+ {
3588
+ symbol: ":",
3589
+ operator: "eq"
3590
+ },
3591
+ {
3592
+ symbol: ">",
3593
+ operator: "gt"
3594
+ },
3595
+ {
3596
+ symbol: "<",
3597
+ operator: "lt"
3598
+ }
3599
+ ];
3600
+ /** Where a field operator starts in the token, or `-1`. */
3601
+ function findOperator(text) {
3602
+ let best = null;
3603
+ for (const { symbol, operator } of OPERATORS) {
3604
+ const index = text.indexOf(symbol);
3605
+ if (index <= 0) continue;
3606
+ if (!best || index < best.index || index === best.index && symbol.length > best.symbol.length) best = {
3607
+ index,
3608
+ symbol,
3609
+ operator
3610
+ };
3611
+ }
3612
+ return best;
3613
+ }
3614
+ /** Parse, keeping whatever could not be understood as a plain term. */
3615
+ function parseQueryWithDiagnostics(input) {
3616
+ const diagnostics = [];
3617
+ const clauses = [];
3618
+ let trailing = null;
3619
+ for (const token of tokenize(input)) {
3620
+ let text = token.text;
3621
+ let negated = false;
3622
+ if (!token.quoted && text.startsWith("-") && text.length > 1) {
3623
+ negated = true;
3624
+ text = text.slice(1);
3625
+ }
3626
+ const operator = token.quoted ? null : findOperator(text);
3627
+ if (!operator) {
3628
+ if (text.length > 0) clauses.push({
3629
+ type: "term",
3630
+ value: text,
3631
+ negated
3632
+ });
3633
+ continue;
3634
+ }
3635
+ const field = text.slice(0, operator.index);
3636
+ const rawValue = text.slice(operator.index + operator.symbol.length);
3637
+ if (!FIELD_NAME.test(field)) {
3638
+ clauses.push({
3639
+ type: "term",
3640
+ value: text,
3641
+ negated
3642
+ });
3643
+ continue;
3644
+ }
3645
+ if (rawValue === "") {
3646
+ trailing = {
3647
+ field,
3648
+ operator: operator.operator,
3649
+ value: ""
3650
+ };
3651
+ continue;
3652
+ }
3653
+ if (rawValue === "*" && operator.operator === "eq") {
3654
+ clauses.push({
3655
+ type: "field",
3656
+ field,
3657
+ operator: "exists",
3658
+ values: [],
3659
+ negated
3660
+ });
3661
+ continue;
3662
+ }
3663
+ if (operator.operator === "eq" && rawValue.length > 2 && rawValue.startsWith("*") && rawValue.endsWith("*")) {
3664
+ clauses.push({
3665
+ type: "field",
3666
+ field,
3667
+ operator: "contains",
3668
+ values: [stripQuotes(rawValue.slice(1, -1))],
3669
+ negated
3670
+ });
3671
+ continue;
3672
+ }
3673
+ const values = readValues(rawValue);
3674
+ if (values.length === 0) {
3675
+ diagnostics.push({
3676
+ at: token.at,
3677
+ text: token.text,
3678
+ message: "Empty value group"
3679
+ });
3680
+ continue;
3681
+ }
3682
+ if (operator.operator !== "eq" && values.length > 1) diagnostics.push({
3683
+ at: token.at,
3684
+ text: token.text,
3685
+ message: "A comparison takes a single value"
3686
+ });
3687
+ const clause = {
3688
+ type: "field",
3689
+ field,
3690
+ operator: operator.operator,
3691
+ values: operator.operator === "eq" ? values : values.slice(0, 1),
3692
+ negated
3693
+ };
3694
+ clauses.push(clause);
3695
+ if (!rawValue.startsWith("(")) {
3696
+ var _values$;
3697
+ trailing = {
3698
+ field,
3699
+ operator: operator.operator,
3700
+ value: (_values$ = values[0]) !== null && _values$ !== void 0 ? _values$ : ""
3701
+ };
3702
+ }
3703
+ }
3704
+ return {
3705
+ query: { clauses },
3706
+ diagnostics,
3707
+ trailing
3708
+ };
3709
+ }
3710
+ /** Parse, discarding diagnostics. */
3711
+ function parseQuery(input) {
3712
+ return parseQueryWithDiagnostics(input).query;
3713
+ }
3714
+ //#endregion
3715
+ //#region src/lib/query/query-format.ts
3716
+ const OPERATOR_SYMBOL = {
3717
+ eq: ":",
3718
+ contains: ":",
3719
+ gt: ">",
3720
+ gte: ">=",
3721
+ lt: "<",
3722
+ lte: "<=",
3723
+ exists: ":"
3724
+ };
3725
+ /** Quote a value that would otherwise re-parse as something else. */
3726
+ function quoteValue(value) {
3727
+ return /[\s"()]/.test(value) || value === "" ? `"${value.replace(/"/g, "")}"` : value;
3728
+ }
3729
+ /** One clause as text. */
3730
+ function formatClause(clause) {
3731
+ var _clause$values$2;
3732
+ const prefix = clause.negated ? "-" : "";
3733
+ if (clause.type === "term") return `${prefix}${quoteValue(clause.value)}`;
3734
+ if (clause.operator === "exists") return `${prefix}${clause.field}:*`;
3735
+ if (clause.operator === "contains") {
3736
+ var _clause$values$;
3737
+ return `${prefix}${clause.field}:*${(_clause$values$ = clause.values[0]) !== null && _clause$values$ !== void 0 ? _clause$values$ : ""}*`;
3738
+ }
3739
+ const symbol = OPERATOR_SYMBOL[clause.operator];
3740
+ if (clause.values.length > 1) return `${prefix}${clause.field}${symbol}(${clause.values.map(quoteValue).join(" or ")})`;
3741
+ return `${prefix}${clause.field}${symbol}${quoteValue((_clause$values$2 = clause.values[0]) !== null && _clause$values$2 !== void 0 ? _clause$values$2 : "")}`;
3742
+ }
3743
+ /** The whole query as text. */
3744
+ function formatQuery(query) {
3745
+ return query.clauses.map(formatClause).join(" ");
3746
+ }
3747
+ /** A short human label for a clause chip. */
3748
+ function describeClause(clause) {
3749
+ if (clause.type === "term") return clause.negated ? `not "${clause.value}"` : `"${clause.value}"`;
3750
+ if (clause.operator === "exists") return clause.negated ? `${clause.field} missing` : `${clause.field} present`;
3751
+ if (clause.operator === "contains") {
3752
+ var _clause$values$3;
3753
+ return `${clause.negated ? "not " : ""}${clause.field} ⊃ ${(_clause$values$3 = clause.values[0]) !== null && _clause$values$3 !== void 0 ? _clause$values$3 : ""}`;
3754
+ }
3755
+ const values = clause.values.join(", ");
3756
+ const symbol = OPERATOR_SYMBOL[clause.operator];
3757
+ return `${clause.negated ? "not " : ""}${clause.field} ${symbol === ":" ? "=" : symbol} ${values}`;
3758
+ }
3759
+ //#endregion
3760
+ //#region src/shells/query-bar.tsx
3761
+ const defaultLabels$1 = {
3762
+ placeholder: "Search, or filter with field:value",
3763
+ ariaLabel: "Search query",
3764
+ clear: "Clear query",
3765
+ removeClause: (clause) => `Remove ${clause}`,
3766
+ fieldsHeading: "Fields",
3767
+ valuesHeading: "Values",
3768
+ syntaxHint: null
3769
+ };
3770
+ const MAX_SUGGESTIONS = 8;
3771
+ /**
3772
+ * QueryBar — one control for "find this" and "only these".
3773
+ *
3774
+ * A search box and a row of filter pickers answer halves of the same question
3775
+ * and neither can express the other, so people end up using both and holding
3776
+ * the combination in their head. Typing the filter is what closes that: the
3777
+ * whole question is one string, which means it can be read back, corrected in
3778
+ * place, pasted to a colleague, and stored in a URL.
3779
+ *
3780
+ * The text is the source of truth while the reader is typing; the parsed query
3781
+ * is committed on Enter or blur. Anything half-written parses to a plain term
3782
+ * rather than an error, so the bar never argues with someone mid-word.
3783
+ *
3784
+ * @example
3785
+ * ```tsx
3786
+ * <QueryBar
3787
+ * value={query}
3788
+ * onChange={setQuery}
3789
+ * fields={[{ name: "path", values: ["/checkout", "/cart"] }]}
3790
+ * showClauses
3791
+ * />
3792
+ * ```
3793
+ */
3794
+ function QueryBar({ value, onChange, fields = [], showClauses = false, live = false, disabled = false, className, labels: customLabels }) {
3795
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$1), customLabels);
3796
+ const inputRef = (0, react.useRef)(null);
3797
+ const [text, setText] = (0, react.useState)(() => formatQuery(value));
3798
+ const [focused, setFocused] = (0, react.useState)(false);
3799
+ const [highlighted, setHighlighted] = (0, react.useState)(0);
3800
+ const committedRef = (0, react.useRef)(formatQuery(value));
3801
+ (0, react.useEffect)(() => {
3802
+ const next = formatQuery(value);
3803
+ if (next === committedRef.current) return;
3804
+ committedRef.current = next;
3805
+ if (!focused) setText(next);
3806
+ }, [value, focused]);
3807
+ const parsed = (0, react.useMemo)(() => parseQueryWithDiagnostics(text), [text]);
3808
+ const commit = (0, react.useCallback)((nextText) => {
3809
+ const nextQuery = parseQueryWithDiagnostics(nextText).query;
3810
+ committedRef.current = formatQuery(nextQuery);
3811
+ onChange(nextQuery);
3812
+ }, [onChange]);
3813
+ const suggestions = useSuggestions(text, parsed.trailing, fields);
3814
+ const applySuggestion = (0, react.useCallback)((suggestion) => {
3815
+ var _inputRef$current;
3816
+ const nextText = suggestion.replace(text);
3817
+ setText(nextText);
3818
+ if (suggestion.kind === "value") commit(nextText);
3819
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
3820
+ setHighlighted(0);
3821
+ }, [text, commit]);
3822
+ const handleKeyDown = (event) => {
3823
+ if (suggestions.length > 0 && focused) {
3824
+ if (event.key === "ArrowDown") {
3825
+ event.preventDefault();
3826
+ setHighlighted((current) => (current + 1) % suggestions.length);
3827
+ return;
3828
+ }
3829
+ if (event.key === "ArrowUp") {
3830
+ event.preventDefault();
3831
+ setHighlighted((current) => (current - 1 + suggestions.length) % suggestions.length);
3832
+ return;
3833
+ }
3834
+ if (event.key === "Tab" || event.key === "Enter" && highlighted > 0) {
3835
+ event.preventDefault();
3836
+ applySuggestion(suggestions[highlighted]);
3837
+ return;
3838
+ }
3839
+ }
3840
+ if (event.key === "Enter") {
3841
+ event.preventDefault();
3842
+ commit(text);
3843
+ return;
3844
+ }
3845
+ if (event.key === "Escape") {
3846
+ var _inputRef$current2;
3847
+ setText(committedRef.current);
3848
+ (_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 || _inputRef$current2.blur();
3849
+ }
3850
+ };
3851
+ const showSuggestions = focused && suggestions.length > 0;
3852
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3853
+ className: require_pagination.cn("relative flex min-w-0 flex-col gap-1.5", className),
3854
+ "data-opt-id": "JSVZ1",
3855
+ "data-opt-slug": "query-bar.QueryBar",
3856
+ children: [
3857
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3858
+ className: require_pagination.cn("bg-surface flex h-9 items-center gap-2 rounded-md border px-2.5", require_pagination.OPT_BORDER, "focus-within:border-accent transition-colors", disabled && "opacity-60"),
3859
+ children: [
3860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Search, {
3861
+ className: "text-text-tertiary size-4 shrink-0",
3862
+ "aria-hidden": true
3863
+ }),
3864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3865
+ ref: inputRef,
3866
+ type: "text",
3867
+ role: "combobox",
3868
+ "aria-expanded": showSuggestions,
3869
+ "aria-controls": "query-bar-suggestions",
3870
+ "aria-autocomplete": "list",
3871
+ "aria-label": labels.ariaLabel,
3872
+ placeholder: labels.placeholder,
3873
+ disabled,
3874
+ className: require_pagination.cn("text-text-primary placeholder:text-text-tertiary min-w-0 flex-1", "bg-transparent font-mono text-sm outline-none focus-visible:outline-none"),
3875
+ value: text,
3876
+ onChange: (event) => {
3877
+ setText(event.target.value);
3878
+ setHighlighted(0);
3879
+ if (live) commit(event.target.value);
3880
+ },
3881
+ onFocus: () => setFocused(true),
3882
+ onBlur: () => {
3883
+ window.setTimeout(() => setFocused(false), 120);
3884
+ if (!live) commit(text);
3885
+ },
3886
+ onKeyDown: handleKeyDown
3887
+ }),
3888
+ text.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3889
+ type: "button",
3890
+ "aria-label": labels.clear,
3891
+ title: labels.clear,
3892
+ className: require_pagination.cn("text-text-tertiary hover:text-text-primary cursor-pointer rounded-sm p-0.5", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset"),
3893
+ onClick: () => {
3894
+ setText("");
3895
+ commit("");
3896
+ },
3897
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {
3898
+ className: "size-3.5",
3899
+ "aria-hidden": true
3900
+ })
3901
+ })
3902
+ ]
3903
+ }),
3904
+ showSuggestions && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
3905
+ id: "query-bar-suggestions",
3906
+ role: "listbox",
3907
+ className: require_pagination.cn("bg-surface-overlay absolute top-10 z-40 max-h-64 w-full overflow-y-auto rounded-md border p-1 shadow-lg", "border border-border"),
3908
+ children: suggestions.map((suggestion, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3909
+ type: "button",
3910
+ role: "option",
3911
+ "aria-selected": index === highlighted,
3912
+ className: require_pagination.cn("flex w-full cursor-pointer items-center justify-between gap-3 rounded px-2 py-1 text-left text-sm", index === highlighted ? "bg-bg-subtle" : "hover:bg-bg-subtle"),
3913
+ onMouseEnter: () => setHighlighted(index),
3914
+ onMouseDown: (event) => event.preventDefault(),
3915
+ onClick: () => applySuggestion(suggestion),
3916
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3917
+ className: "text-text-primary truncate font-mono",
3918
+ children: suggestion.label
3919
+ }), suggestion.hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3920
+ className: "text-text-tertiary shrink-0 text-xs",
3921
+ children: suggestion.hint
3922
+ })]
3923
+ }) }, `${suggestion.kind}-${suggestion.label}`))
3924
+ }),
3925
+ showClauses && value.clauses.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
3926
+ className: "flex flex-wrap items-center gap-1.5",
3927
+ children: value.clauses.map((clause, index) => {
3928
+ const label = describeClause(clause);
3929
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
3930
+ className: require_pagination.cn("bg-bg-subtle text-text-secondary flex items-center gap-1 rounded px-1.5 py-0.5 text-xs", clause.negated && "text-danger-fg"),
3931
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3932
+ className: "truncate",
3933
+ children: label
3934
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3935
+ type: "button",
3936
+ "aria-label": labels.removeClause(label),
3937
+ className: "hover:text-text-primary cursor-pointer",
3938
+ onClick: () => {
3939
+ const next = removeClause(value, index);
3940
+ setText(formatQuery(next));
3941
+ committedRef.current = formatQuery(next);
3942
+ onChange(next);
3943
+ },
3944
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {
3945
+ className: "size-3",
3946
+ "aria-hidden": true
3947
+ })
3948
+ })]
3949
+ }, `${label}-${index}`);
3950
+ })
3951
+ }),
3952
+ parsed.diagnostics.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QueryDiagnostics, { diagnostics: parsed.diagnostics }),
3953
+ labels.syntaxHint && !showSuggestions && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3954
+ className: "text-text-tertiary text-xs",
3955
+ children: labels.syntaxHint
3956
+ })
3957
+ ]
3958
+ });
3959
+ }
3960
+ QueryBar.displayName = "QueryBar";
3961
+ function QueryDiagnostics({ diagnostics }) {
3962
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
3963
+ className: "text-warning-fg flex flex-col gap-0.5 text-xs",
3964
+ children: diagnostics.map((diagnostic, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
3965
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
3966
+ className: "font-mono",
3967
+ children: diagnostic.text
3968
+ }),
3969
+ " —",
3970
+ " ",
3971
+ diagnostic.message
3972
+ ] }, `${diagnostic.at}-${index}`))
3973
+ });
3974
+ }
3975
+ /**
3976
+ * What to offer, based on the token under the cursor.
3977
+ *
3978
+ * Completing fields is the part that makes the syntax discoverable at all —
3979
+ * nobody guesses that `utm_source` is a field they can filter on. Once the
3980
+ * field is chosen, its known values are the second half of the same guess.
3981
+ */
3982
+ function useSuggestions(text, trailing, fields) {
3983
+ return (0, react.useMemo)(() => {
3984
+ const lastSpace = text.lastIndexOf(" ");
3985
+ const head = lastSpace === -1 ? "" : text.slice(0, lastSpace + 1);
3986
+ const token = text.slice(lastSpace + 1);
3987
+ if (trailing) {
3988
+ var _field$values;
3989
+ const field = fields.find((candidate) => candidate.name === trailing.field);
3990
+ const values = (_field$values = field === null || field === void 0 ? void 0 : field.values) !== null && _field$values !== void 0 ? _field$values : [];
3991
+ const typed = trailing.value.toLowerCase();
3992
+ return values.filter((value) => value.toLowerCase().includes(typed)).slice(0, MAX_SUGGESTIONS).map((value) => ({
3993
+ kind: "value",
3994
+ label: `${trailing.field}:${value}`,
3995
+ replace: () => `${head}${trailing.field}:${/[\s"()]/.test(value) ? `"${value}"` : value} `
3996
+ }));
3997
+ }
3998
+ const bare = token.replace(/^-/, "").toLowerCase();
3999
+ if (bare.length === 0) return [];
4000
+ return fields.filter((field) => field.name.toLowerCase().includes(bare)).slice(0, MAX_SUGGESTIONS).map((field) => ({
4001
+ kind: "field",
4002
+ label: `${field.name}:`,
4003
+ hint: field.hint,
4004
+ replace: () => `${head}${token.startsWith("-") ? "-" : ""}${field.name}:`
4005
+ }));
4006
+ }, [
4007
+ text,
4008
+ trailing,
4009
+ fields
4010
+ ]);
4011
+ }
4012
+ //#endregion
4013
+ //#region src/shells/field-sidebar.tsx
4014
+ const defaultLabels = {
4015
+ heading: "Fields",
4016
+ searchPlaceholder: "Find a field",
4017
+ searchLabel: "Find a field",
4018
+ empty: "No fields match.",
4019
+ addColumn: (field) => `Show ${field} as a column`,
4020
+ removeColumn: (field) => `Remove the ${field} column`,
4021
+ filterFor: (value) => `Filter for ${value}`,
4022
+ filterOut: (value) => `Filter out ${value}`,
4023
+ topValues: (count) => `Top ${count} values`,
4024
+ noValues: "No values in this range.",
4025
+ loadingValues: "Counting…",
4026
+ otherGroup: "Other"
4027
+ };
4028
+ /**
4029
+ * FieldSidebar — what is in this data, and what does it contain.
4030
+ *
4031
+ * Without it, a log answers only the questions you already knew to ask: you can
4032
+ * filter on `utm_source` if you remember it exists, and you can read a value if
4033
+ * you open a row and scroll to it. Listing the fields turns that around — the
4034
+ * data tells the reader what is available — and the value counts answer the
4035
+ * question that actually follows ("mostly which?") before they have to run a
4036
+ * query to find out.
4037
+ *
4038
+ * Three actions live here because they are the three things people do next:
4039
+ * put the field in the table, keep only rows with a value, drop rows with it.
4040
+ *
4041
+ * @example
4042
+ * ```tsx
4043
+ * <FieldSidebar
4044
+ * fields={fields}
4045
+ * expandedField={openField}
4046
+ * onExpandedFieldChange={setOpenField}
4047
+ * values={valuesQuery.data}
4048
+ * valuesLoading={valuesQuery.isLoading}
4049
+ * onToggleColumn={(field, next) => setColumns(...)}
4050
+ * onFilter={(field, value, intent) => setQuery(applyCellAction(query, { field, value, intent }))}
4051
+ * />
4052
+ * ```
4053
+ */
4054
+ function FieldSidebar({ fields, expandedField = null, onExpandedFieldChange, values, valuesLoading = false, sampledRows, onToggleColumn, onFilter, header, className, labels: customLabels }) {
4055
+ const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels), customLabels);
4056
+ const [search, setSearch] = (0, react.useState)("");
4057
+ const groups = (0, react.useMemo)(() => {
4058
+ const needle = search.trim().toLowerCase();
4059
+ const matching = needle ? fields.filter((field) => field.name.toLowerCase().includes(needle)) : fields;
4060
+ const byGroup = /* @__PURE__ */ new Map();
4061
+ for (const field of matching) {
4062
+ var _field$group;
4063
+ const key = (_field$group = field.group) !== null && _field$group !== void 0 ? _field$group : "";
4064
+ const bucket = byGroup.get(key);
4065
+ if (bucket) bucket.push(field);
4066
+ else byGroup.set(key, [field]);
4067
+ }
4068
+ return [...byGroup.entries()];
4069
+ }, [fields, search]);
4070
+ const total = (0, react.useMemo)(() => (values !== null && values !== void 0 ? values : []).reduce((sum, entry) => sum + entry.count, 0), [values]);
4071
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
4072
+ className: require_pagination.cn("flex min-h-0 flex-col", className),
4073
+ "aria-label": labels.heading,
4074
+ "data-opt-id": "17D4Y",
4075
+ "data-opt-slug": "field-sidebar.FieldSidebar",
4076
+ children: [
4077
+ header,
4078
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4079
+ className: require_pagination.cn("flex items-center gap-2 border-b px-3 py-2", "focus-within:border-accent transition-colors", require_pagination.OPT_BORDER),
4080
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Search, {
4081
+ className: "text-text-tertiary size-3.5 shrink-0",
4082
+ "aria-hidden": true
4083
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4084
+ type: "search",
4085
+ "aria-label": labels.searchLabel,
4086
+ placeholder: labels.searchPlaceholder,
4087
+ className: require_pagination.cn("text-text-primary placeholder:text-text-tertiary min-w-0 flex-1", "bg-transparent text-sm outline-none focus-visible:outline-none"),
4088
+ value: search,
4089
+ onChange: (event) => setSearch(event.target.value)
4090
+ })]
4091
+ }),
4092
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4093
+ className: "min-h-0 flex-1 overflow-y-auto",
4094
+ children: groups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4095
+ className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "px-3 py-4 text-xs"),
4096
+ children: labels.empty
4097
+ }) : groups.map(([group, groupFields]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { children: [group && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
4098
+ className: require_pagination.cn("text-text-tertiary", "px-3 pt-3 pb-1 text-[11px] font-medium tracking-wide uppercase"),
4099
+ children: group
4100
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: groupFields.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldRow, {
4101
+ field,
4102
+ expanded: expandedField === field.name,
4103
+ onToggleExpanded: () => onExpandedFieldChange === null || onExpandedFieldChange === void 0 ? void 0 : onExpandedFieldChange(expandedField === field.name ? null : field.name),
4104
+ onToggleColumn,
4105
+ labels,
4106
+ children: expandedField === field.name && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldValues, {
4107
+ values,
4108
+ loading: valuesLoading,
4109
+ total,
4110
+ sampledRows,
4111
+ onFilter: onFilter ? (value, intent) => onFilter(field.name, value, intent) : void 0,
4112
+ labels
4113
+ })
4114
+ }, field.name)) })] }, group || "__ungrouped__"))
4115
+ })
4116
+ ]
4117
+ });
4118
+ }
4119
+ FieldSidebar.displayName = "FieldSidebar";
4120
+ function FieldRow({ field, expanded, onToggleExpanded, onToggleColumn, labels, children }) {
4121
+ var _field$columnable, _field$inTable;
4122
+ const columnable = (_field$columnable = field.columnable) !== null && _field$columnable !== void 0 ? _field$columnable : true;
4123
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4124
+ className: "hover:bg-bg-subtle group/field flex items-center gap-1.5 px-3 py-1",
4125
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4126
+ type: "button",
4127
+ className: "flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-left",
4128
+ "aria-expanded": expanded,
4129
+ onClick: onToggleExpanded,
4130
+ children: [field.type && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4131
+ className: require_pagination.cn("text-text-tertiary", "w-8 shrink-0 truncate font-mono text-[10px] uppercase"),
4132
+ children: field.type
4133
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4134
+ className: "text-text-primary truncate font-mono text-xs",
4135
+ children: field.name
4136
+ })]
4137
+ }), onToggleColumn && columnable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4138
+ type: "button",
4139
+ className: require_pagination.cn("cursor-pointer rounded-sm p-0.5", field.inTable ? "text-accent" : "text-text-tertiary hover:text-text-primary opacity-0 group-hover/field:opacity-100 focus-visible:opacity-100"),
4140
+ "aria-label": field.inTable ? labels.removeColumn(field.name) : labels.addColumn(field.name),
4141
+ title: field.inTable ? labels.removeColumn(field.name) : labels.addColumn(field.name),
4142
+ "aria-pressed": (_field$inTable = field.inTable) !== null && _field$inTable !== void 0 ? _field$inTable : false,
4143
+ onClick: () => onToggleColumn(field.name, !field.inTable),
4144
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Columns3, {
4145
+ className: "size-3.5",
4146
+ "aria-hidden": true
4147
+ })
4148
+ })]
4149
+ }), children] });
4150
+ }
4151
+ /**
4152
+ * The distribution, as bars.
4153
+ *
4154
+ * A count alone ("342") means nothing without the denominator, and a reader
4155
+ * scanning for "what is this field mostly" reads the bar before the number.
4156
+ * The sample size is stated rather than implied — these are the top values in
4157
+ * the rows that were counted, not in all of history, and a chart that hides
4158
+ * that invites the wrong conclusion.
4159
+ */
4160
+ function FieldValues({ values, loading, total, sampledRows, onFilter, labels }) {
4161
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4162
+ className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "flex items-center gap-2 px-3 pb-2 text-xs"),
4163
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Spinner, {
4164
+ size: "sm",
4165
+ "aria-hidden": true
4166
+ }), labels.loadingValues]
4167
+ });
4168
+ if (!values || values.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4169
+ className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "px-3 pb-2 text-xs"),
4170
+ children: labels.noValues
4171
+ });
4172
+ const denominator = sampledRows !== null && sampledRows !== void 0 ? sampledRows : total;
4173
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4174
+ className: "flex flex-col gap-1 px-3 pt-1 pb-2",
4175
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4176
+ className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "text-[11px]"),
4177
+ children: [labels.topValues(values.length), sampledRows ? ` · ${sampledRows.toLocaleString()}` : ""]
4178
+ }), values.map((entry) => {
4179
+ var _entry$share;
4180
+ const share = (_entry$share = entry.share) !== null && _entry$share !== void 0 ? _entry$share : denominator > 0 ? entry.count / denominator : 0;
4181
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4182
+ className: "group/value flex flex-col gap-0.5",
4183
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4184
+ className: "flex items-center justify-between gap-2",
4185
+ children: [
4186
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4187
+ className: "text-text-secondary min-w-0 flex-1 truncate font-mono text-[11px]",
4188
+ title: entry.value,
4189
+ children: entry.value || "—"
4190
+ }),
4191
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4192
+ className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "shrink-0 text-[11px] tabular-nums"),
4193
+ children: [Math.round(share * 100), "%"]
4194
+ }),
4195
+ onFilter && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4196
+ className: "hidden shrink-0 items-center gap-0.5 group-hover/value:flex focus-within:flex",
4197
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4198
+ type: "button",
4199
+ className: "text-text-tertiary hover:text-text-primary cursor-pointer",
4200
+ "aria-label": labels.filterFor(entry.value),
4201
+ title: labels.filterFor(entry.value),
4202
+ onClick: () => onFilter(entry.value, "include"),
4203
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, {
4204
+ className: "size-3",
4205
+ "aria-hidden": true
4206
+ })
4207
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4208
+ type: "button",
4209
+ className: "text-text-tertiary hover:text-text-primary cursor-pointer",
4210
+ "aria-label": labels.filterOut(entry.value),
4211
+ title: labels.filterOut(entry.value),
4212
+ onClick: () => onFilter(entry.value, "exclude"),
4213
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Minus, {
4214
+ className: "size-3",
4215
+ "aria-hidden": true
4216
+ })
4217
+ })]
4218
+ })
4219
+ ]
4220
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4221
+ className: "bg-bg-muted h-1 w-full overflow-hidden rounded-full",
4222
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4223
+ className: "bg-accent h-full rounded-full",
4224
+ style: { width: `${Math.max(2, Math.round(share * 100))}%` }
4225
+ })
4226
+ })]
4227
+ }, entry.value);
4228
+ })]
4229
+ });
4230
+ }
4231
+ //#endregion
4232
+ Object.defineProperty(exports, "AppMenubar", {
4233
+ enumerable: true,
4234
+ get: function() {
4235
+ return AppMenubar;
4236
+ }
4237
+ });
4238
+ Object.defineProperty(exports, "BranchSelect", {
4239
+ enumerable: true,
4240
+ get: function() {
4241
+ return BranchSelect;
4242
+ }
4243
+ });
4244
+ Object.defineProperty(exports, "Command", {
4245
+ enumerable: true,
4246
+ get: function() {
4247
+ return Command;
4248
+ }
4249
+ });
4250
+ Object.defineProperty(exports, "CommandDialog", {
4251
+ enumerable: true,
4252
+ get: function() {
4253
+ return CommandDialog;
4254
+ }
4255
+ });
4256
+ Object.defineProperty(exports, "CommandEmpty", {
4257
+ enumerable: true,
4258
+ get: function() {
4259
+ return CommandEmpty;
4260
+ }
4261
+ });
4262
+ Object.defineProperty(exports, "CommandGroup", {
4263
+ enumerable: true,
4264
+ get: function() {
4265
+ return CommandGroup;
4266
+ }
4267
+ });
4268
+ Object.defineProperty(exports, "CommandInput", {
4269
+ enumerable: true,
4270
+ get: function() {
4271
+ return CommandInput;
4272
+ }
4273
+ });
4274
+ Object.defineProperty(exports, "CommandItem", {
4275
+ enumerable: true,
4276
+ get: function() {
4277
+ return CommandItem;
4278
+ }
4279
+ });
4280
+ Object.defineProperty(exports, "CommandList", {
4281
+ enumerable: true,
4282
+ get: function() {
4283
+ return CommandList;
4284
+ }
4285
+ });
4286
+ Object.defineProperty(exports, "CommandPalette", {
4287
+ enumerable: true,
4288
+ get: function() {
4289
+ return CommandPalette;
4290
+ }
4291
+ });
4292
+ Object.defineProperty(exports, "CommandPaletteTrigger", {
4293
+ enumerable: true,
4294
+ get: function() {
4295
+ return CommandPaletteTrigger;
4296
+ }
4297
+ });
4298
+ Object.defineProperty(exports, "CommandSeparator", {
4299
+ enumerable: true,
4300
+ get: function() {
4301
+ return CommandSeparator;
4302
+ }
4303
+ });
4304
+ Object.defineProperty(exports, "CommandShortcut", {
4305
+ enumerable: true,
4306
+ get: function() {
4307
+ return CommandShortcut;
4308
+ }
4309
+ });
4310
+ Object.defineProperty(exports, "ContentTabs", {
4311
+ enumerable: true,
4312
+ get: function() {
4313
+ return ContentTabs;
4314
+ }
4315
+ });
4316
+ Object.defineProperty(exports, "DEFAULT_TIME_PRESETS", {
4317
+ enumerable: true,
4318
+ get: function() {
4319
+ return DEFAULT_TIME_PRESETS;
4320
+ }
4321
+ });
4322
+ Object.defineProperty(exports, "DashboardGrid", {
4323
+ enumerable: true,
4324
+ get: function() {
4325
+ return DashboardGrid;
4326
+ }
4327
+ });
4328
+ Object.defineProperty(exports, "DataTable", {
4329
+ enumerable: true,
4330
+ get: function() {
4331
+ return DataTable;
4332
+ }
4333
+ });
4334
+ Object.defineProperty(exports, "DeploymentTimeline", {
4335
+ enumerable: true,
4336
+ get: function() {
4337
+ return DeploymentTimeline;
4338
+ }
4339
+ });
4340
+ Object.defineProperty(exports, "DomainTable", {
4341
+ enumerable: true,
4342
+ get: function() {
4343
+ return DomainTable;
4344
+ }
4345
+ });
4346
+ Object.defineProperty(exports, "EMPTY_QUERY", {
4347
+ enumerable: true,
4348
+ get: function() {
4349
+ return EMPTY_QUERY;
4350
+ }
4351
+ });
4352
+ Object.defineProperty(exports, "EditorToolbar", {
4353
+ enumerable: true,
4354
+ get: function() {
4355
+ return EditorToolbar;
4356
+ }
4357
+ });
4358
+ Object.defineProperty(exports, "EnvPanel", {
4359
+ enumerable: true,
4360
+ get: function() {
4361
+ return EnvPanel;
4362
+ }
4363
+ });
4364
+ Object.defineProperty(exports, "FailureList", {
4365
+ enumerable: true,
4366
+ get: function() {
4367
+ return FailureList;
4368
+ }
4369
+ });
4370
+ Object.defineProperty(exports, "FaqAccordion", {
4371
+ enumerable: true,
4372
+ get: function() {
4373
+ return FaqAccordion;
4374
+ }
4375
+ });
4376
+ Object.defineProperty(exports, "FieldSidebar", {
4377
+ enumerable: true,
4378
+ get: function() {
4379
+ return FieldSidebar;
4380
+ }
4381
+ });
4382
+ Object.defineProperty(exports, "KeyValueEditor", {
4383
+ enumerable: true,
4384
+ get: function() {
4385
+ return KeyValueEditor;
4386
+ }
4387
+ });
4388
+ Object.defineProperty(exports, "LogTable", {
4389
+ enumerable: true,
4390
+ get: function() {
4391
+ return LogTable;
4392
+ }
4393
+ });
4394
+ Object.defineProperty(exports, "NotificationToast", {
4395
+ enumerable: true,
4396
+ get: function() {
4397
+ return NotificationToast;
4398
+ }
4399
+ });
4400
+ Object.defineProperty(exports, "ProjectSwitcher", {
4401
+ enumerable: true,
4402
+ get: function() {
4403
+ return ProjectSwitcher;
4404
+ }
4405
+ });
4406
+ Object.defineProperty(exports, "QueryBar", {
4407
+ enumerable: true,
4408
+ get: function() {
4409
+ return QueryBar;
4410
+ }
4411
+ });
4412
+ Object.defineProperty(exports, "SearchCombobox", {
4413
+ enumerable: true,
4414
+ get: function() {
4415
+ return SearchCombobox;
4416
+ }
4417
+ });
4418
+ Object.defineProperty(exports, "SettingsForm", {
4419
+ enumerable: true,
4420
+ get: function() {
4421
+ return SettingsForm;
4422
+ }
4423
+ });
4424
+ Object.defineProperty(exports, "SidebarNav", {
4425
+ enumerable: true,
4426
+ get: function() {
4427
+ return SidebarNav;
4428
+ }
4429
+ });
4430
+ Object.defineProperty(exports, "StatusSelect", {
4431
+ enumerable: true,
4432
+ get: function() {
4433
+ return StatusSelect;
4434
+ }
4435
+ });
4436
+ Object.defineProperty(exports, "TimeRangeControl", {
4437
+ enumerable: true,
4438
+ get: function() {
4439
+ return TimeRangeControl;
4440
+ }
4441
+ });
4442
+ Object.defineProperty(exports, "TimeSeriesPanel", {
4443
+ enumerable: true,
4444
+ get: function() {
4445
+ return TimeSeriesPanel;
4446
+ }
4447
+ });
4448
+ Object.defineProperty(exports, "ToastProvider", {
4449
+ enumerable: true,
4450
+ get: function() {
4451
+ return ToastProvider;
4452
+ }
4453
+ });
4454
+ Object.defineProperty(exports, "WorkflowCanvas", {
4455
+ enumerable: true,
4456
+ get: function() {
4457
+ return WorkflowCanvas;
4458
+ }
4459
+ });
4460
+ Object.defineProperty(exports, "addClause", {
4461
+ enumerable: true,
4462
+ get: function() {
4463
+ return addClause;
4464
+ }
4465
+ });
4466
+ Object.defineProperty(exports, "applyCellAction", {
4467
+ enumerable: true,
4468
+ get: function() {
4469
+ return applyCellAction;
4470
+ }
4471
+ });
4472
+ Object.defineProperty(exports, "describeClause", {
4473
+ enumerable: true,
4474
+ get: function() {
4475
+ return describeClause;
4476
+ }
4477
+ });
4478
+ Object.defineProperty(exports, "fieldValues", {
4479
+ enumerable: true,
4480
+ get: function() {
4481
+ return fieldValues;
4482
+ }
4483
+ });
4484
+ Object.defineProperty(exports, "formatClause", {
4485
+ enumerable: true,
4486
+ get: function() {
4487
+ return formatClause;
4488
+ }
4489
+ });
4490
+ Object.defineProperty(exports, "formatQuery", {
4491
+ enumerable: true,
4492
+ get: function() {
4493
+ return formatQuery;
4494
+ }
4495
+ });
4496
+ Object.defineProperty(exports, "isEmptyQuery", {
4497
+ enumerable: true,
4498
+ get: function() {
4499
+ return isEmptyQuery;
4500
+ }
4501
+ });
4502
+ Object.defineProperty(exports, "parseQuery", {
4503
+ enumerable: true,
4504
+ get: function() {
4505
+ return parseQuery;
4506
+ }
4507
+ });
4508
+ Object.defineProperty(exports, "parseQueryWithDiagnostics", {
4509
+ enumerable: true,
4510
+ get: function() {
4511
+ return parseQueryWithDiagnostics;
4512
+ }
4513
+ });
4514
+ Object.defineProperty(exports, "queryFields", {
4515
+ enumerable: true,
4516
+ get: function() {
4517
+ return queryFields;
4518
+ }
4519
+ });
4520
+ Object.defineProperty(exports, "quoteValue", {
4521
+ enumerable: true,
4522
+ get: function() {
4523
+ return quoteValue;
4524
+ }
4525
+ });
4526
+ Object.defineProperty(exports, "removeClause", {
4527
+ enumerable: true,
4528
+ get: function() {
4529
+ return removeClause;
4530
+ }
4531
+ });
4532
+ Object.defineProperty(exports, "toast", {
4533
+ enumerable: true,
4534
+ get: function() {
4535
+ return toast;
4536
+ }
4537
+ });
4538
+ Object.defineProperty(exports, "useAutoRefresh", {
4539
+ enumerable: true,
4540
+ get: function() {
4541
+ return useAutoRefresh;
4542
+ }
4543
+ });
4544
+ Object.defineProperty(exports, "useToast", {
4545
+ enumerable: true,
4546
+ get: function() {
4547
+ return useToast;
4548
+ }
4549
+ });
4550
+ Object.defineProperty(exports, "withFieldValue", {
4551
+ enumerable: true,
4552
+ get: function() {
4553
+ return withFieldValue;
4554
+ }
4555
+ });
4556
+ Object.defineProperty(exports, "withoutField", {
4557
+ enumerable: true,
4558
+ get: function() {
4559
+ return withoutField;
4560
+ }
4561
+ });