@reopt-ai/opt-ui 1.4.1 → 1.6.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 (89) hide show
  1. package/COMPONENT_CATALOG.md +374 -84
  2. package/README.md +28 -7
  3. package/dist/app.css +146 -0
  4. package/dist/core/index.cjs +164 -324
  5. package/dist/core/index.d.cts +3 -5
  6. package/dist/core/index.d.ts +3 -5
  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 +192 -46
  10. package/dist/docs/02-components/02-visuals.md +1 -1
  11. package/dist/docs/02-components/03-shells.md +177 -42
  12. package/dist/docs/02-components/04-surfaces.md +1 -1
  13. package/dist/docs/02-components/index.md +1 -1
  14. package/dist/docs/03-recipes/03-layouts.md +20 -12
  15. package/dist/docs/04-theming.md +3 -1
  16. package/dist/docs/05-migration/01-breaking-changes.md +28 -0
  17. package/dist/id-registry.cjs +1899 -1872
  18. package/dist/id-registry.d.cts +13 -10
  19. package/dist/id-registry.d.cts.map +1 -0
  20. package/dist/id-registry.d.ts +13 -10
  21. package/dist/id-registry.d.ts.map +1 -0
  22. package/dist/id-registry.js +1897 -1871
  23. package/dist/id-registry.js.map +1 -0
  24. package/dist/id-registry.json +44 -44
  25. package/dist/index.cjs +8294 -9613
  26. package/dist/index.d.cts +1486 -1438
  27. package/dist/index.d.cts.map +1 -0
  28. package/dist/index.d.ts +1486 -1438
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +7933 -9600
  31. package/dist/index.js.map +1 -0
  32. package/dist/meta.cjs +7461 -6765
  33. package/dist/meta.d.cts.map +1 -0
  34. package/dist/meta.d.ts.map +1 -0
  35. package/dist/meta.js +7460 -6736
  36. package/dist/meta.js.map +1 -0
  37. package/dist/pagination-By7Ozv3U.js +3788 -0
  38. package/dist/pagination-By7Ozv3U.js.map +1 -0
  39. package/dist/pagination-Uv3CnGJz.cjs +4677 -0
  40. package/dist/shader-surface-BK1GMt5_.d.cts +1788 -0
  41. package/dist/shader-surface-BK1GMt5_.d.cts.map +1 -0
  42. package/dist/shader-surface-BWhO2xkk.js +3715 -0
  43. package/dist/shader-surface-BWhO2xkk.js.map +1 -0
  44. package/dist/shader-surface-DIVWtpFl.d.ts +1788 -0
  45. package/dist/shader-surface-DIVWtpFl.d.ts.map +1 -0
  46. package/dist/shader-surface-DxLLfrE6.cjs +4108 -0
  47. package/dist/shells/index.cjs +51 -65
  48. package/dist/shells/index.d.cts +4 -5
  49. package/dist/shells/index.d.ts +4 -5
  50. package/dist/shells/index.js +3 -64
  51. package/dist/tailwind.css +54 -5
  52. package/dist/theme/presets/default.css +12 -12
  53. package/dist/theme/server.cjs +310 -0
  54. package/dist/theme/server.d.cts +117 -0
  55. package/dist/theme/server.d.cts.map +1 -0
  56. package/dist/theme/server.d.ts +117 -0
  57. package/dist/theme/server.d.ts.map +1 -0
  58. package/dist/theme/server.js +287 -0
  59. package/dist/theme/server.js.map +1 -0
  60. package/dist/types-D0FlcYnM.d.cts +301 -0
  61. package/dist/types-D0FlcYnM.d.cts.map +1 -0
  62. package/dist/types-D0FlcYnM.d.ts +301 -0
  63. package/dist/types-D0FlcYnM.d.ts.map +1 -0
  64. package/dist/visuals/index.cjs +10 -4
  65. package/dist/visuals/index.d.cts +2 -1
  66. package/dist/visuals/index.d.ts +2 -1
  67. package/dist/visuals/index.js +1 -2
  68. package/dist/workflow-canvas-3Jeevkgy.cjs +2599 -0
  69. package/dist/workflow-canvas-BGBAcjf-.js +2403 -0
  70. package/dist/workflow-canvas-BGBAcjf-.js.map +1 -0
  71. package/dist/workflow-canvas-Bar_O2SQ.d.ts +494 -0
  72. package/dist/workflow-canvas-Bar_O2SQ.d.ts.map +1 -0
  73. package/dist/workflow-canvas-DghDRtHn.d.cts +494 -0
  74. package/dist/workflow-canvas-DghDRtHn.d.cts.map +1 -0
  75. package/package.json +24 -8
  76. package/dist/chunk-3GWWZKX7.js +0 -38
  77. package/dist/chunk-AFF2HPE5.cjs +0 -5008
  78. package/dist/chunk-ELWICXYY.js +0 -4745
  79. package/dist/chunk-N4NDU5ET.cjs +0 -4745
  80. package/dist/chunk-ONE3C5RV.cjs +0 -38
  81. package/dist/chunk-QWBHD54V.js +0 -3218
  82. package/dist/chunk-RBM2RNC2.js +0 -5008
  83. package/dist/chunk-X5WCXJAF.cjs +0 -3218
  84. package/dist/index-BZ_lBlO1.d.ts +0 -474
  85. package/dist/index-BuvxoWHf.d.cts +0 -474
  86. package/dist/index-DlAcuvQp.d.cts +0 -1686
  87. package/dist/index-Slu5hOj1.d.ts +0 -1686
  88. package/dist/types-D4-0lwaE.d.cts +0 -298
  89. package/dist/types-D4-0lwaE.d.ts +0 -298
@@ -0,0 +1,2403 @@
1
+ "use client";
2
+ import { $ as TooltipAnchor$1, A as FormSwitch$1, An as OPT_SURFACE_POPOVER, At as SelectPopover$1, Bn as _objectSpread2, Bt as Composite, C as FormRoot$1, Cn as OPT_FOCUS_VISIBLE, Dt as DialogRoot$1, E as FormSelectPopover, Et as DialogPanel$1, F as useFormStore, Fn as OPT_TEXT_LABEL, Ft as TabPanel$1, Hn as Kbd, Ht as CompositeProvider, In as OPT_TEXT_PRIMARY, It as TabsRoot$1, J as ToolbarButton$1, Jt as Spinner, Ln as OPT_TEXT_SECONDARY, Lt as DisclosureContent$1, M as FormTextarea, Mn as OPT_TD, Mt as SelectTrigger$1, Nn as OPT_TD_MUTED, Nt as Tab$1, On as OPT_ITEM_BASE, Ot as SelectItem$1, Pt as TabList$1, Q as Tooltip$1, R as TagInput, Rn as OPT_TEXT_TERTIARY, Rt as DisclosureRoot$1, Sn as OPT_FOCUS, T as FormSelectItem, Tn as OPT_INPUT, Tt as DialogHeading$1, Un as cn, Ut as CompositeRow, Vn as _objectWithoutProperties, Vt as CompositeItem, X as ToolbarRoot$1, Xt as EmptyState, Y as ToolbarContainer$1, Yt as Badge, Z as ToolbarSeparator$1, _n as OPT_DIVIDER, _t as ComboboxRoot$1, at as MenuPopover$1, b as FormRadioGroup$1, bn as OPT_DOT_RED, bt as DialogDescription$1, cn as OPT_ANIMATE_ENTER_EXIT, ct as MenuTrigger$1, dn as OPT_BADGE_NEUTRAL, dt as ComboboxEmpty, et as TooltipProvider$1, fn as OPT_BADGE_RED, gn as OPT_DISCLOSURE_TRIGGER, gt as ComboboxPopover$1, h as FormLabel$1, hn as OPT_DISCLOSURE_CONTENT, ht as ComboboxItem$1, i as FormCheckbox$1, it as MenuItemRadio$1, jn as OPT_TABLE_ROW_ACTIVE, jt as SelectRoot$1, k as FormSubmit$1, kt as SelectLabel$1, l as FormError$1, ln as OPT_BADGE_BLUE, lt as MenubarContainer$1, m as FormInput$1, mn as OPT_BORDER, mt as ComboboxInput$1, n as Checkbox, nt as MenuItem$1, on as OPT_ACTIVE_ITEM, ot as MenuRoot$1, pn as OPT_BADGE_YELLOW, qt as Alert, rt as MenuItemCheckbox$1, st as MenuSeparator$1, t as Pagination, tn as StatCard, tt as MenuButtonArrow$1, un as OPT_BADGE_GREEN, ut as MenubarRoot$1, w as FormSelect, xn as OPT_DOT_YELLOW, y as FormRadio$1, yn as OPT_DOT_GREEN, zn as OPT_TH, zt as DisclosureTrigger$1 } from "./pagination-By7Ozv3U.js";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import * as React from "react";
5
+ import { Fragment as Fragment$1, createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
+ import { CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandRoot, CommandSeparator } from "@reopt-ai/opt-ui-primitives";
7
+ import { SearchIcon } from "lucide-react";
8
+ import { matchSorter } from "match-sorter";
9
+ import Link from "next/link";
10
+ import { usePathname } from "next/navigation";
11
+ //#region src/core/command.tsx
12
+ const _excluded = ["className"];
13
+ const _excluded2 = [
14
+ "className",
15
+ "trailing",
16
+ "wrapperClassName",
17
+ "placeholder",
18
+ "aria-label"
19
+ ];
20
+ const _excluded3 = ["className", "aria-label"];
21
+ const _excluded4 = ["className"];
22
+ const _excluded5 = ["className", "heading"];
23
+ const _excluded6 = ["className"];
24
+ const _excluded7 = ["className"];
25
+ const _excluded8 = ["className"];
26
+ /** Renders the `Command` component. */
27
+ function Command(_ref) {
28
+ let { className } = _ref, props = _objectWithoutProperties(_ref, _excluded);
29
+ return /* @__PURE__ */ jsx(CommandRoot, _objectSpread2(_objectSpread2({ className: cn("bg-surface text-text-primary flex w-full flex-col overflow-hidden rounded-xl", className) }, props), {}, {
30
+ "data-opt-id": "4FYBW",
31
+ "data-opt-slug": "command.Command"
32
+ }));
33
+ }
34
+ /** Renders a Command in a dialog overlay, à la Cmd+K palette. */
35
+ function CommandDialog({ open, onOpenChange, title = "Command Palette", description = "검색 후 Enter 또는 클릭으로 명령을 실행하세요.", className, commandClassName, shouldFilter, filter, children }) {
36
+ return /* @__PURE__ */ jsx(DialogRoot$1, {
37
+ open,
38
+ onOpenChange,
39
+ "data-opt-id": "DVRSX",
40
+ "data-opt-slug": "command.CommandDialog",
41
+ children: /* @__PURE__ */ jsxs(DialogPanel$1, {
42
+ className: cn("top-[20%] left-1/2 -translate-x-1/2", "w-full max-w-lg overflow-hidden p-0 shadow-2xl", className),
43
+ children: [
44
+ /* @__PURE__ */ jsx(DialogHeading$1, {
45
+ className: "sr-only",
46
+ children: title
47
+ }),
48
+ /* @__PURE__ */ jsx(DialogDescription$1, {
49
+ className: "sr-only",
50
+ children: description
51
+ }),
52
+ /* @__PURE__ */ jsx(Command, {
53
+ shouldFilter,
54
+ filter,
55
+ className: commandClassName,
56
+ children
57
+ })
58
+ ]
59
+ })
60
+ });
61
+ }
62
+ /** Renders the `CommandInput` component with a leading search icon. */
63
+ function CommandInput$1(_ref2) {
64
+ let { className, trailing, wrapperClassName, placeholder, "aria-label": ariaLabel } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2);
65
+ return /* @__PURE__ */ jsxs("div", {
66
+ "data-slot": "command-input-wrapper",
67
+ className: cn("border-border-subtle gap-element flex h-12 items-center border-b px-3", wrapperClassName),
68
+ "data-opt-id": "HB1K3",
69
+ "data-opt-slug": "command.CommandInput",
70
+ children: [
71
+ /* @__PURE__ */ jsx(SearchIcon, {
72
+ "aria-hidden": true,
73
+ className: "text-text-tertiary size-4 shrink-0"
74
+ }),
75
+ /* @__PURE__ */ jsx(CommandInput, _objectSpread2({
76
+ className: 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),
77
+ placeholder,
78
+ "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : typeof placeholder === "string" ? placeholder : "Command search"
79
+ }, props)),
80
+ trailing
81
+ ]
82
+ });
83
+ }
84
+ /** Renders the `CommandList` component. */
85
+ function CommandList$1(_ref3) {
86
+ let { className, "aria-label": ariaLabel } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3);
87
+ return /* @__PURE__ */ jsx(CommandList, _objectSpread2(_objectSpread2({
88
+ className: cn("max-h-80 scroll-py-1 overflow-x-hidden overflow-y-auto p-1", className),
89
+ "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : "Commands"
90
+ }, props), {}, {
91
+ "data-opt-id": "NPXC5",
92
+ "data-opt-slug": "command.CommandList"
93
+ }));
94
+ }
95
+ /** Renders the `CommandEmpty` component. */
96
+ function CommandEmpty$1(_ref4) {
97
+ let { className } = _ref4, props = _objectWithoutProperties(_ref4, _excluded4);
98
+ return /* @__PURE__ */ jsx(CommandEmpty, _objectSpread2(_objectSpread2({ className: cn("text-text-tertiary py-6 text-center text-sm", className) }, props), {}, {
99
+ "data-opt-id": "A8BZM",
100
+ "data-opt-slug": "command.CommandEmpty"
101
+ }));
102
+ }
103
+ /** Renders the `CommandGroup` component with an optional heading. */
104
+ function CommandGroup$1(_ref5) {
105
+ let { className, heading } = _ref5, props = _objectWithoutProperties(_ref5, _excluded5);
106
+ return /* @__PURE__ */ jsx(CommandGroup, _objectSpread2(_objectSpread2({
107
+ heading: heading != null ? /* @__PURE__ */ jsx("div", {
108
+ className: "text-text-tertiary px-2 py-1.5 text-xs font-medium",
109
+ children: heading
110
+ }) : void 0,
111
+ className: cn("py-1", className)
112
+ }, props), {}, {
113
+ "data-opt-id": "JPK1Y",
114
+ "data-opt-slug": "command.CommandGroup"
115
+ }));
116
+ }
117
+ /** Renders the `CommandSeparator` component. */
118
+ function CommandSeparator$1(_ref6) {
119
+ let { className } = _ref6, props = _objectWithoutProperties(_ref6, _excluded6);
120
+ return /* @__PURE__ */ jsx(CommandSeparator, _objectSpread2(_objectSpread2({ className: cn("bg-border-subtle -mx-1 my-1 h-px", className) }, props), {}, {
121
+ "data-opt-id": "10P0E",
122
+ "data-opt-slug": "command.CommandSeparator"
123
+ }));
124
+ }
125
+ /** Renders the `CommandItem` component. */
126
+ function CommandItem$1(_ref7) {
127
+ let { className } = _ref7, props = _objectWithoutProperties(_ref7, _excluded7);
128
+ return /* @__PURE__ */ jsx(CommandItem, _objectSpread2(_objectSpread2({ className: 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), {}, {
129
+ "data-opt-id": "K674W",
130
+ "data-opt-slug": "command.CommandItem"
131
+ }));
132
+ }
133
+ /** Renders the `CommandShortcut` component (right-aligned hint). */
134
+ function CommandShortcut(_ref8) {
135
+ let { className } = _ref8, props = _objectWithoutProperties(_ref8, _excluded8);
136
+ return /* @__PURE__ */ jsx("span", _objectSpread2(_objectSpread2({
137
+ "data-slot": "command-shortcut",
138
+ className: cn("text-text-tertiary ml-auto text-xs tracking-widest", "data-[active-item]:text-accent-fg/80", className)
139
+ }, props), {}, {
140
+ "data-opt-id": "75CQK",
141
+ "data-opt-slug": "command.CommandShortcut"
142
+ }));
143
+ }
144
+ //#endregion
145
+ //#region src/shells/command-palette.tsx
146
+ const DEFAULT_LABELS = {
147
+ title: "Command Palette",
148
+ description: "검색 후 Enter 또는 클릭으로 명령을 실행하세요.",
149
+ input: "명령어 검색",
150
+ placeholder: "명령어를 검색하세요...",
151
+ list: "명령어 목록",
152
+ empty: "결과가 없습니다.",
153
+ escape: "ESC"
154
+ };
155
+ /** Renders the command palette component. */
156
+ function CommandPalette({ commands, open, onOpenChange, onSelect, inline = false, labels, footer, empty, inputTrailing, className }) {
157
+ const resolvedLabels = _objectSpread2(_objectSpread2({}, DEFAULT_LABELS), labels);
158
+ const hasCommands = commands.length > 0;
159
+ const grouped = useMemo(() => {
160
+ const map = /* @__PURE__ */ new Map();
161
+ for (const cmd of commands) {
162
+ var _map$get;
163
+ const list = (_map$get = map.get(cmd.group)) !== null && _map$get !== void 0 ? _map$get : [];
164
+ list.push(cmd);
165
+ map.set(cmd.group, list);
166
+ }
167
+ return map;
168
+ }, [commands]);
169
+ const handleSelect = (cmdId) => {
170
+ const cmd = commands.find((c) => c.id === cmdId);
171
+ if (!cmd) return;
172
+ onSelect === null || onSelect === void 0 || onSelect(cmd);
173
+ onOpenChange(false);
174
+ };
175
+ const handleInputKeyDown = (e) => {
176
+ if (e.key === "Escape" && !inline) {
177
+ e.preventDefault();
178
+ onOpenChange(false);
179
+ }
180
+ };
181
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
182
+ /* @__PURE__ */ jsx(CommandInput$1, {
183
+ "aria-label": resolvedLabels.input,
184
+ placeholder: resolvedLabels.placeholder,
185
+ onKeyDown: handleInputKeyDown,
186
+ trailing: inputTrailing !== null && inputTrailing !== void 0 ? inputTrailing : !inline ? /* @__PURE__ */ jsx(Kbd, { children: resolvedLabels.escape }) : void 0
187
+ }),
188
+ /* @__PURE__ */ jsxs(CommandList$1, {
189
+ "aria-label": resolvedLabels.list,
190
+ children: [/* @__PURE__ */ jsx(CommandEmpty$1, { children: empty !== null && empty !== void 0 ? empty : resolvedLabels.empty }), hasCommands ? Array.from(grouped.entries()).map(([group, items]) => /* @__PURE__ */ jsx(CommandGroup$1, {
191
+ heading: group,
192
+ children: items.map((cmd) => /* @__PURE__ */ jsxs(CommandItem$1, {
193
+ value: cmd.id,
194
+ keywords: [
195
+ cmd.label,
196
+ cmd.group,
197
+ ...cmd.shortcut ? [cmd.shortcut] : []
198
+ ],
199
+ onSelect: handleSelect,
200
+ children: [/* @__PURE__ */ jsx("span", { children: cmd.label }), cmd.shortcut && /* @__PURE__ */ jsx(CommandShortcut, { children: cmd.shortcut })]
201
+ }, cmd.id))
202
+ }, group)) : null]
203
+ }),
204
+ footer
205
+ ] });
206
+ if (inline) {
207
+ if (!open) return null;
208
+ return /* @__PURE__ */ jsx(Command, {
209
+ className: cn("border-border bg-surface w-full max-w-lg border shadow-lg", className),
210
+ "data-opt-id": "ATTF5",
211
+ "data-opt-slug": "command-palette.CommandPalette",
212
+ children: body
213
+ });
214
+ }
215
+ return /* @__PURE__ */ jsx(CommandDialog, {
216
+ open,
217
+ onOpenChange,
218
+ title: resolvedLabels.title,
219
+ description: resolvedLabels.description,
220
+ className,
221
+ "data-opt-id": "ATTF5",
222
+ "data-opt-slug": "command-palette.CommandPalette",
223
+ children: body
224
+ });
225
+ }
226
+ //#endregion
227
+ //#region src/shells/command-palette-trigger.tsx
228
+ /** Renders the command palette trigger component. */
229
+ function CommandPaletteTrigger({ commands, onSelect, labels }) {
230
+ if (commands.length === 0) return null;
231
+ const [open, setOpen] = useState(false);
232
+ const handleKeyDown = useCallback((e) => {
233
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
234
+ e.preventDefault();
235
+ setOpen(true);
236
+ }
237
+ }, []);
238
+ useEffect(() => {
239
+ document.addEventListener("keydown", handleKeyDown);
240
+ return () => document.removeEventListener("keydown", handleKeyDown);
241
+ }, [handleKeyDown]);
242
+ return /* @__PURE__ */ jsx(CommandPalette, {
243
+ commands,
244
+ open,
245
+ onOpenChange: setOpen,
246
+ onSelect,
247
+ labels,
248
+ "data-opt-id": "4FCDB",
249
+ "data-opt-slug": "command-palette-trigger.CommandPaletteTrigger"
250
+ });
251
+ }
252
+ //#endregion
253
+ //#region src/shells/dashboard-grid.tsx
254
+ const defaultLabels$13 = { ariaLabel: "대시보드 통계" };
255
+ const MIN_DASHBOARD_COLUMNS = 1;
256
+ const MAX_DASHBOARD_COLUMNS = 12;
257
+ function normalizeDashboardColumns(columns) {
258
+ if (!Number.isFinite(columns)) return MIN_DASHBOARD_COLUMNS;
259
+ return Math.min(MAX_DASHBOARD_COLUMNS, Math.max(MIN_DASHBOARD_COLUMNS, Math.floor(columns)));
260
+ }
261
+ /** Renders the dashboard grid component. */
262
+ function DashboardGrid({ stats, columns = 3, labels: customLabels }) {
263
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$13), customLabels);
264
+ const columnCount = normalizeDashboardColumns(columns);
265
+ const rowStyle = { "--opt-dashboard-grid-columns": `repeat(${columnCount}, minmax(0, 1fr))` };
266
+ const rows = [];
267
+ for (let i = 0; i < stats.length; i += columnCount) rows.push(stats.slice(i, i + columnCount));
268
+ return /* @__PURE__ */ jsx(CompositeProvider, {
269
+ focusLoop: true,
270
+ focusWrap: true,
271
+ "data-opt-id": "BM7NA",
272
+ "data-opt-slug": "dashboard-grid.DashboardGrid",
273
+ children: /* @__PURE__ */ jsx(Composite, {
274
+ role: "grid",
275
+ "aria-label": labels.ariaLabel,
276
+ className: "gap-group flex flex-col",
277
+ children: rows.map((row, ri) => /* @__PURE__ */ jsx(CompositeRow, {
278
+ className: "gap-group grid grid-cols-1 sm:[grid-template-columns:var(--opt-dashboard-grid-columns)]",
279
+ style: rowStyle,
280
+ children: row.map((stat) => /* @__PURE__ */ jsx(CompositeItem, {
281
+ 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",
282
+ render: /* @__PURE__ */ jsx("div", { role: "gridcell" }),
283
+ children: /* @__PURE__ */ jsx(StatCard, _objectSpread2({}, stat))
284
+ }, stat.id))
285
+ }, ri))
286
+ })
287
+ });
288
+ }
289
+ //#endregion
290
+ //#region src/shells/search-combobox.tsx
291
+ const defaultLabels$12 = {
292
+ noResults: "결과가 없습니다.",
293
+ loading: "Loading…"
294
+ };
295
+ /** Renders the search combobox component. */
296
+ function SearchCombobox({ items, placeholder = "검색...", label = "검색", onSelect, labels: customLabels, async: isAsync = false, onSearch, debounceMs = 300 }) {
297
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$12), customLabels);
298
+ const inputId = useId();
299
+ const [query, setQuery] = useState("");
300
+ const [asyncResults, setAsyncResults] = useState([]);
301
+ const [loading, setLoading] = useState(false);
302
+ const timerRef = useRef(void 0);
303
+ const searchGenerationRef = useRef(0);
304
+ const syncMatches = useMemo(() => {
305
+ if (isAsync) return [];
306
+ if (!query) return items;
307
+ return matchSorter(items, query);
308
+ }, [
309
+ query,
310
+ items,
311
+ isAsync
312
+ ]);
313
+ const handleInput = (event) => {
314
+ searchGenerationRef.current += 1;
315
+ setQuery(event.currentTarget.value);
316
+ };
317
+ const handleValueChange = (item) => {
318
+ searchGenerationRef.current += 1;
319
+ setQuery(item);
320
+ onSelect === null || onSelect === void 0 || onSelect(item);
321
+ };
322
+ useEffect(() => {
323
+ const generation = ++searchGenerationRef.current;
324
+ if (timerRef.current) clearTimeout(timerRef.current);
325
+ if (!isAsync || !onSearch) {
326
+ setLoading(false);
327
+ return;
328
+ }
329
+ if (!query) {
330
+ setAsyncResults([]);
331
+ setLoading(false);
332
+ return;
333
+ }
334
+ setLoading(true);
335
+ timerRef.current = setTimeout(async () => {
336
+ try {
337
+ const results = await onSearch(query);
338
+ if (searchGenerationRef.current !== generation) return;
339
+ setAsyncResults(results);
340
+ } finally {
341
+ if (searchGenerationRef.current === generation) setLoading(false);
342
+ }
343
+ }, debounceMs);
344
+ return () => {
345
+ if (timerRef.current) clearTimeout(timerRef.current);
346
+ };
347
+ }, [
348
+ query,
349
+ isAsync,
350
+ onSearch,
351
+ debounceMs
352
+ ]);
353
+ const matches = isAsync ? asyncResults : syncMatches;
354
+ return /* @__PURE__ */ jsx("div", {
355
+ className: "gap-element flex flex-col",
356
+ "data-opt-id": "KH4TQ",
357
+ "data-opt-slug": "search-combobox.SearchCombobox",
358
+ children: /* @__PURE__ */ jsxs(ComboboxRoot$1, {
359
+ onValueChange: handleValueChange,
360
+ children: [
361
+ /* @__PURE__ */ jsx("label", {
362
+ htmlFor: inputId,
363
+ className: OPT_TEXT_LABEL,
364
+ children: label
365
+ }),
366
+ /* @__PURE__ */ jsx(ComboboxInput$1, {
367
+ id: inputId,
368
+ placeholder,
369
+ className: "max-w-xs",
370
+ "aria-busy": loading || void 0,
371
+ onInput: handleInput
372
+ }),
373
+ /* @__PURE__ */ jsx(ComboboxPopover$1, {
374
+ className: "max-w-xs",
375
+ children: isAsync && loading ? /* @__PURE__ */ jsxs("div", {
376
+ className: "text-text-secondary gap-element flex items-center px-3 py-2 text-sm",
377
+ role: "status",
378
+ "aria-live": "polite",
379
+ "aria-busy": "true",
380
+ children: [/* @__PURE__ */ jsx(Spinner, {
381
+ size: "sm",
382
+ "aria-hidden": "true"
383
+ }), labels.loading]
384
+ }) : matches.length === 0 ? /* @__PURE__ */ jsx(ComboboxEmpty, { children: labels.noResults }) : matches.map((item) => /* @__PURE__ */ jsx(ComboboxItem$1, {
385
+ value: item,
386
+ children: item
387
+ }, item))
388
+ })
389
+ ]
390
+ })
391
+ });
392
+ }
393
+ //#endregion
394
+ //#region src/shells/status-select.tsx
395
+ const defaultLabels$11 = {
396
+ selectLabel: "상태",
397
+ emptyMessage: "사용 가능한 상태 옵션이 없습니다."
398
+ };
399
+ /** Renders the status select component. */
400
+ function StatusSelect({ options, value: controlledValue, onChange, label, labels: customLabels }) {
401
+ var _options$0$value, _options$;
402
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$11), customLabels);
403
+ const resolvedLabel = label !== null && label !== void 0 ? label : labels.selectLabel;
404
+ const [internalValue, setInternalValue] = useState((_options$0$value = (_options$ = options[0]) === null || _options$ === void 0 ? void 0 : _options$.value) !== null && _options$0$value !== void 0 ? _options$0$value : "");
405
+ const value = controlledValue !== null && controlledValue !== void 0 ? controlledValue : internalValue;
406
+ const selected = options.find((s) => s.value === value);
407
+ if (options.length === 0) return /* @__PURE__ */ jsxs("div", {
408
+ className: "gap-element flex flex-col",
409
+ "data-opt-id": "R46E6",
410
+ "data-opt-slug": "status-select.StatusSelect",
411
+ children: [/* @__PURE__ */ jsx("span", {
412
+ className: OPT_TEXT_LABEL,
413
+ children: resolvedLabel
414
+ }), /* @__PURE__ */ jsx("span", {
415
+ className: "text-text-tertiary text-sm",
416
+ children: labels.emptyMessage
417
+ })]
418
+ });
419
+ const handleChange = (v) => {
420
+ var _v$;
421
+ const next = Array.isArray(v) ? (_v$ = v[0]) !== null && _v$ !== void 0 ? _v$ : "" : v;
422
+ if (onChange) onChange(next);
423
+ else setInternalValue(next);
424
+ };
425
+ return /* @__PURE__ */ jsx("div", {
426
+ className: "gap-element flex flex-col",
427
+ "data-opt-id": "R46E6",
428
+ "data-opt-slug": "status-select.StatusSelect",
429
+ children: /* @__PURE__ */ jsxs(SelectRoot$1, {
430
+ value,
431
+ onValueChange: handleChange,
432
+ children: [
433
+ /* @__PURE__ */ jsx(SelectLabel$1, {
434
+ className: OPT_TEXT_LABEL,
435
+ children: resolvedLabel
436
+ }),
437
+ /* @__PURE__ */ jsxs(SelectTrigger$1, {
438
+ className: `${OPT_INPUT} max-w-xs items-center justify-between ${OPT_FOCUS}`,
439
+ children: [/* @__PURE__ */ jsxs("span", {
440
+ className: "gap-element flex items-center",
441
+ children: [(selected === null || selected === void 0 ? void 0 : selected.color) && /* @__PURE__ */ jsx("span", { className: `h-2 w-2 rounded-full ${selected.color}` }), selected === null || selected === void 0 ? void 0 : selected.label]
442
+ }), /* @__PURE__ */ jsx("svg", {
443
+ className: "text-text-tertiary h-4 w-4",
444
+ fill: "none",
445
+ viewBox: "0 0 24 24",
446
+ stroke: "currentColor",
447
+ strokeWidth: 2,
448
+ "aria-hidden": "true",
449
+ children: /* @__PURE__ */ jsx("path", {
450
+ strokeLinecap: "round",
451
+ strokeLinejoin: "round",
452
+ d: "m19.5 8.25-7.5 7.5-7.5-7.5"
453
+ })
454
+ })]
455
+ }),
456
+ /* @__PURE__ */ jsx(SelectPopover$1, {
457
+ className: `${OPT_SURFACE_POPOVER} ${OPT_ANIMATE_ENTER_EXIT} max-w-xs`,
458
+ children: options.map((option) => /* @__PURE__ */ jsxs(SelectItem$1, {
459
+ value: option.value,
460
+ children: [option.color && /* @__PURE__ */ jsx("span", { className: `h-2 w-2 rounded-full ${option.color}` }), option.label]
461
+ }, option.value))
462
+ })
463
+ ]
464
+ })
465
+ });
466
+ }
467
+ //#endregion
468
+ //#region src/shells/app-menubar.tsx
469
+ const defaultLabels$10 = { ariaLabel: "Application menu" };
470
+ function MenuSection({ menu }) {
471
+ return /* @__PURE__ */ jsxs(MenuRoot$1, { children: [/* @__PURE__ */ jsxs(MenuTrigger$1, { children: [menu.label, /* @__PURE__ */ jsx(MenuButtonArrow$1, {})] }), /* @__PURE__ */ jsx(MenuPopover$1, { children: menu.items.map((item, i) => {
472
+ if (item.type === "separator") return /* @__PURE__ */ jsx(MenuSeparator$1, {}, `sep-${i}`);
473
+ if (item.type === "checkbox") {
474
+ var _item$name, _item$value;
475
+ return /* @__PURE__ */ jsx(MenuItemCheckbox$1, {
476
+ name: (_item$name = item.name) !== null && _item$name !== void 0 ? _item$name : item.id,
477
+ value: (_item$value = item.value) !== null && _item$value !== void 0 ? _item$value : item.id,
478
+ checked: item.checked,
479
+ onChange: item.onChange,
480
+ children: /* @__PURE__ */ jsxs("span", {
481
+ className: "gap-element flex items-center",
482
+ children: [/* @__PURE__ */ jsx("span", {
483
+ className: "w-4 text-center",
484
+ children: item.checked ? "✓" : ""
485
+ }), item.label]
486
+ })
487
+ }, item.id);
488
+ }
489
+ if (item.type === "radio") {
490
+ var _item$name2, _item$value2;
491
+ return /* @__PURE__ */ jsx(MenuItemRadio$1, {
492
+ name: (_item$name2 = item.name) !== null && _item$name2 !== void 0 ? _item$name2 : "",
493
+ value: (_item$value2 = item.value) !== null && _item$value2 !== void 0 ? _item$value2 : "",
494
+ checked: item.checked,
495
+ onChange: item.onChange,
496
+ children: /* @__PURE__ */ jsxs("span", {
497
+ className: "gap-element flex items-center",
498
+ children: [/* @__PURE__ */ jsx("span", {
499
+ className: "w-4 text-center",
500
+ children: item.checked ? "●" : ""
501
+ }), item.label]
502
+ })
503
+ }, item.id);
504
+ }
505
+ return /* @__PURE__ */ jsxs(MenuItem$1, {
506
+ onClick: item.onClick,
507
+ children: [/* @__PURE__ */ jsx("span", { children: item.label }), item.shortcut && /* @__PURE__ */ jsx(Kbd, { children: item.shortcut })]
508
+ }, item.id);
509
+ }) })] });
510
+ }
511
+ /** Renders the app menubar component. */
512
+ function AppMenubar({ menus, labels: customLabels }) {
513
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$10), customLabels);
514
+ if (menus.length === 0) return null;
515
+ return /* @__PURE__ */ jsx(MenubarRoot$1, {
516
+ "data-opt-id": "BGKHW",
517
+ "data-opt-slug": "app-menubar.AppMenubar",
518
+ children: /* @__PURE__ */ jsx(MenubarContainer$1, {
519
+ "aria-label": labels.ariaLabel,
520
+ children: menus.map((menu) => /* @__PURE__ */ jsx(MenuSection, { menu }, menu.id))
521
+ })
522
+ });
523
+ }
524
+ AppMenubar.displayName = "AppMenubar";
525
+ //#endregion
526
+ //#region src/shells/sidebar-nav.tsx
527
+ const NAV_ID_PREFIX = "sidebar-nav-";
528
+ function domId(id) {
529
+ return `${NAV_ID_PREFIX}${id}`;
530
+ }
531
+ const sectionStyles = {
532
+ 시작하기: "text-accent",
533
+ "핵심 개념": "text-info-fg",
534
+ 구축·운영: "text-success-fg",
535
+ 도구: "text-warning-fg",
536
+ DataGrid: "text-accent",
537
+ "컴포넌트 · Core": "text-accent",
538
+ "컴포넌트 · Visuals": "text-success-fg",
539
+ "컴포넌트 · Shells": "text-warning-fg",
540
+ "컴포넌트 · Surfaces": "text-info-fg",
541
+ "기본 컴포넌트": "text-accent",
542
+ "블록 컴포넌트": "text-info-fg",
543
+ "조합 컴포넌트": "text-warning-fg",
544
+ 템플릿: "text-text-tertiary",
545
+ 릴리즈: "text-text-tertiary",
546
+ Primitives: "text-accent",
547
+ Blocks: "text-info-fg",
548
+ Compositions: "text-warning-fg",
549
+ Templates: "text-text-tertiary"
550
+ };
551
+ const defaultSectionStyle = "text-text-secondary";
552
+ const defaultLabels$9 = { ariaLabel: "문서 네비게이션" };
553
+ /** Renders the sidebar nav component. */
554
+ function SidebarNav({ items, labels: customLabels }) {
555
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$9), customLabels);
556
+ if (items.length === 0) return null;
557
+ const pathname = usePathname();
558
+ const activeItem = items.find((item) => item.href === pathname);
559
+ const [activeId, setActiveId] = useState(activeItem ? domId(activeItem.id) : void 0);
560
+ useEffect(() => {
561
+ const match = items.find((item) => item.href === pathname);
562
+ if (match) setActiveId(domId(match.id));
563
+ }, [pathname, items]);
564
+ return /* @__PURE__ */ jsx(CompositeProvider, {
565
+ focusLoop: true,
566
+ orientation: "vertical",
567
+ activeId,
568
+ onActiveIdChange: setActiveId,
569
+ "data-opt-id": "1FZNH",
570
+ "data-opt-slug": "sidebar-nav.SidebarNav",
571
+ children: /* @__PURE__ */ jsx(Composite, {
572
+ role: "navigation",
573
+ "aria-label": labels.ariaLabel,
574
+ className: "flex flex-col",
575
+ children: items.map((item, index) => {
576
+ var _sectionStyles$item$s, _item$sectionLabel;
577
+ const isSelected = pathname === item.href;
578
+ const isFirstItem = index === 0;
579
+ const sectionTextStyle = item.sectionLabel ? (_sectionStyles$item$s = sectionStyles[item.sectionLabel]) !== null && _sectionStyles$item$s !== void 0 ? _sectionStyles$item$s : defaultSectionStyle : defaultSectionStyle;
580
+ return /* @__PURE__ */ jsxs("div", { children: [item.separator && /* @__PURE__ */ jsx("div", {
581
+ className: cn("mb-2 pt-2", !isFirstItem && "mt-7"),
582
+ children: /* @__PURE__ */ jsx("span", {
583
+ className: cn("px-2 text-[11px] font-semibold tracking-wider uppercase", sectionTextStyle),
584
+ children: (_item$sectionLabel = item.sectionLabel) !== null && _item$sectionLabel !== void 0 ? _item$sectionLabel : ""
585
+ })
586
+ }), /* @__PURE__ */ jsxs(CompositeItem, {
587
+ id: domId(item.id),
588
+ render: /* @__PURE__ */ jsx(Link, {
589
+ href: item.href,
590
+ className: 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"])
591
+ }),
592
+ children: [/* @__PURE__ */ jsx("span", { className: 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__ */ jsx("span", {
593
+ className: "pl-2",
594
+ children: item.label
595
+ })]
596
+ })] }, item.id);
597
+ })
598
+ })
599
+ });
600
+ }
601
+ //#endregion
602
+ //#region src/shells/editor-toolbar.tsx
603
+ const defaultLabels$8 = { ariaLabel: "텍스트 편집기 툴바" };
604
+ function ToolbarButtonWithTooltip({ button, active, onAction }) {
605
+ return /* @__PURE__ */ jsxs(TooltipProvider$1, { children: [/* @__PURE__ */ jsx(TooltipAnchor$1, { children: /* @__PURE__ */ jsx(ToolbarButton$1, {
606
+ 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"}`,
607
+ onClick: () => onAction === null || onAction === void 0 ? void 0 : onAction(button.id),
608
+ children: button.icon
609
+ }) }), /* @__PURE__ */ jsx(Tooltip$1, { children: button.label })] });
610
+ }
611
+ /** Renders the editor toolbar component. */
612
+ function EditorToolbar({ buttons, onAction, activeButtons = [], labels: customLabels }) {
613
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$8), customLabels);
614
+ const groups = [];
615
+ let current = [];
616
+ for (const btn of buttons) {
617
+ if (btn.separator) {
618
+ groups.push(current);
619
+ current = [];
620
+ }
621
+ current.push(btn);
622
+ }
623
+ if (current.length) groups.push(current);
624
+ return /* @__PURE__ */ jsx(ToolbarRoot$1, {
625
+ "data-opt-id": "Z73K6",
626
+ "data-opt-slug": "editor-toolbar.EditorToolbar",
627
+ children: /* @__PURE__ */ jsx(ToolbarContainer$1, {
628
+ "aria-label": labels.ariaLabel,
629
+ children: groups.map((group, gi) => /* @__PURE__ */ jsxs("span", {
630
+ className: "contents",
631
+ children: [gi > 0 && /* @__PURE__ */ jsx(ToolbarSeparator$1, {}), group.map((item) => {
632
+ if (item === "separator") return null;
633
+ const btn = item;
634
+ return /* @__PURE__ */ jsx(ToolbarButtonWithTooltip, {
635
+ button: btn,
636
+ active: activeButtons.includes(btn.id),
637
+ onAction
638
+ }, btn.id);
639
+ })]
640
+ }, gi))
641
+ })
642
+ });
643
+ }
644
+ //#endregion
645
+ //#region src/shells/content-tabs.tsx
646
+ const defaultLabels$7 = {
647
+ ariaLabel: "콘텐츠 탭",
648
+ emptyMessage: "표시할 탭이 없습니다."
649
+ };
650
+ /** Renders the content tabs component. */
651
+ function ContentTabs({ tabs, defaultTabId, labels: customLabels }) {
652
+ var _tabs$, _tabs$find;
653
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$7), customLabels);
654
+ if (tabs.length === 0) return /* @__PURE__ */ jsx("div", {
655
+ className: "text-text-secondary text-sm",
656
+ "data-opt-id": "9W97K",
657
+ "data-opt-slug": "content-tabs.ContentTabs",
658
+ children: labels.emptyMessage
659
+ });
660
+ const [selectedTabId, setSelectedTabId] = useState(defaultTabId !== null && defaultTabId !== void 0 ? defaultTabId : (_tabs$ = tabs[0]) === null || _tabs$ === void 0 ? void 0 : _tabs$.id);
661
+ const handleSelectedTabId = (nextId) => {
662
+ var _tabs$2;
663
+ setSelectedTabId(nextId !== null && nextId !== void 0 ? nextId : (_tabs$2 = tabs[0]) === null || _tabs$2 === void 0 ? void 0 : _tabs$2.id);
664
+ };
665
+ const activeTab = (_tabs$find = tabs.find((tab) => tab.id === selectedTabId)) !== null && _tabs$find !== void 0 ? _tabs$find : tabs[0];
666
+ return /* @__PURE__ */ jsxs(TabsRoot$1, {
667
+ selectedId: selectedTabId,
668
+ onSelectedIdChange: handleSelectedTabId,
669
+ "data-opt-id": "9W97K",
670
+ "data-opt-slug": "content-tabs.ContentTabs",
671
+ children: [/* @__PURE__ */ jsx(TabList$1, {
672
+ "aria-label": labels.ariaLabel,
673
+ children: tabs.map((tab) => /* @__PURE__ */ jsx(Tab$1, {
674
+ id: tab.id,
675
+ children: tab.label
676
+ }, tab.id))
677
+ }), activeTab && /* @__PURE__ */ jsx(TabPanel$1, {
678
+ tabId: activeTab.id,
679
+ children: activeTab.content
680
+ })]
681
+ });
682
+ }
683
+ //#endregion
684
+ //#region src/shells/faq-accordion.tsx
685
+ const defaultLabels$6 = {
686
+ ariaLabel: "자주 묻는 질문",
687
+ emptyMessage: "등록된 FAQ가 없습니다."
688
+ };
689
+ /** Renders the faq accordion component. */
690
+ function FaqAccordion({ items, labels: customLabels }) {
691
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$6), customLabels);
692
+ if (items.length === 0) return /* @__PURE__ */ jsx("div", {
693
+ className: "text-text-secondary text-sm",
694
+ "data-opt-id": "QRBHV",
695
+ "data-opt-slug": "faq-accordion.FaqAccordion",
696
+ children: labels.emptyMessage
697
+ });
698
+ return /* @__PURE__ */ jsx(CompositeProvider, {
699
+ focusLoop: true,
700
+ orientation: "vertical",
701
+ "data-opt-id": "QRBHV",
702
+ "data-opt-slug": "faq-accordion.FaqAccordion",
703
+ children: /* @__PURE__ */ jsx(Composite, {
704
+ role: "region",
705
+ "aria-label": labels.ariaLabel,
706
+ className: "divide-border-subtle border-border divide-y rounded-lg border",
707
+ children: items.map((faq) => /* @__PURE__ */ jsx(DisclosureRoot$1, { children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(CompositeItem, {
708
+ render: /* @__PURE__ */ jsx(DisclosureTrigger$1, {}),
709
+ children: [faq.question, /* @__PURE__ */ jsx("svg", {
710
+ className: "text-text-tertiary h-4 w-4 shrink-0 transition-transform [[data-open]>&]:rotate-180",
711
+ fill: "none",
712
+ viewBox: "0 0 24 24",
713
+ stroke: "currentColor",
714
+ strokeWidth: 2,
715
+ "aria-hidden": "true",
716
+ children: /* @__PURE__ */ jsx("path", {
717
+ strokeLinecap: "round",
718
+ strokeLinejoin: "round",
719
+ d: "m19.5 8.25-7.5 7.5-7.5-7.5"
720
+ })
721
+ })]
722
+ }), /* @__PURE__ */ jsx(DisclosureContent$1, { children: /* @__PURE__ */ jsx("p", {
723
+ className: "text-text-secondary px-4 pt-2 pb-4 text-sm",
724
+ children: faq.answer
725
+ }) })] }) }, faq.id))
726
+ })
727
+ });
728
+ }
729
+ //#endregion
730
+ //#region src/shells/settings-form.tsx
731
+ const defaultLabels$5 = {
732
+ formAriaLabel: "설정 폼",
733
+ submitButton: "저장",
734
+ successMessage: "설정이 저장되었습니다."
735
+ };
736
+ /** Renders the settings form component. */
737
+ function SettingsForm({ fields, onSubmit, labels: customLabels }) {
738
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$5), customLabels);
739
+ const [submitted, setSubmitted] = useState(false);
740
+ if (fields.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
741
+ title: "설정 항목이 없습니다.",
742
+ description: "표시할 설정 필드를 추가하면 이 폼이 렌더링됩니다.",
743
+ "data-opt-id": "9TEEJ",
744
+ "data-opt-slug": "settings-form.SettingsForm"
745
+ });
746
+ const defaultValues = {};
747
+ for (const field of fields) {
748
+ var _field$defaultValue;
749
+ defaultValues[field.name] = (_field$defaultValue = field.defaultValue) !== null && _field$defaultValue !== void 0 ? _field$defaultValue : field.type === "switch" || field.type === "checkbox" ? false : field.type === "tags" ? [] : "";
750
+ }
751
+ const form = useFormStore({
752
+ defaultValues,
753
+ validate: (values) => {
754
+ const errors = {};
755
+ for (const field of fields) {
756
+ if (field.required) {
757
+ const value = values[field.name];
758
+ if (!value || typeof value === "string" && !value.trim()) {
759
+ var _field$requiredMessag;
760
+ errors[field.name] = (_field$requiredMessag = field.requiredMessage) !== null && _field$requiredMessag !== void 0 ? _field$requiredMessag : `${field.label}을(를) 입력해주세요.`;
761
+ }
762
+ }
763
+ if (field.validate) {
764
+ const value = values[field.name];
765
+ const error = field.validate(value);
766
+ if (error) errors[field.name] = error;
767
+ }
768
+ }
769
+ return errors;
770
+ },
771
+ onSubmit: (values) => {
772
+ setSubmitted(true);
773
+ onSubmit === null || onSubmit === void 0 || onSubmit(values);
774
+ }
775
+ });
776
+ return /* @__PURE__ */ jsxs("div", {
777
+ className: "gap-group flex flex-col",
778
+ "data-opt-id": "9TEEJ",
779
+ "data-opt-slug": "settings-form.SettingsForm",
780
+ children: [/* @__PURE__ */ jsxs(FormRoot$1, {
781
+ store: form,
782
+ "aria-label": labels.formAriaLabel,
783
+ children: [fields.map((field) => {
784
+ var _field$type;
785
+ if (field.visible !== void 0) {
786
+ const currentValues = form.getValues();
787
+ if (!(typeof field.visible === "function" ? field.visible(currentValues) : field.visible)) return null;
788
+ }
789
+ if (field.type === "tags") {
790
+ const rawValue = form.getValue(field.name);
791
+ const tags = Array.isArray(rawValue) ? rawValue : [];
792
+ return /* @__PURE__ */ jsxs("div", {
793
+ className: "gap-element flex flex-col",
794
+ children: [
795
+ /* @__PURE__ */ jsx(FormLabel$1, {
796
+ name: field.name,
797
+ children: field.label
798
+ }),
799
+ /* @__PURE__ */ jsx(TagInput, {
800
+ value: tags,
801
+ onChange: (next) => form.setValue(field.name, next),
802
+ placeholder: field.placeholder
803
+ }),
804
+ /* @__PURE__ */ jsx(FormError$1, { name: field.name })
805
+ ]
806
+ }, field.name);
807
+ }
808
+ if (field.type === "radio") {
809
+ var _field$options;
810
+ return /* @__PURE__ */ jsxs("fieldset", {
811
+ className: "gap-element flex flex-col",
812
+ children: [
813
+ /* @__PURE__ */ jsx("legend", {
814
+ className: "text-text-primary text-sm font-medium",
815
+ children: field.label
816
+ }),
817
+ /* @__PURE__ */ jsx(FormRadioGroup$1, { children: /* @__PURE__ */ jsx("div", {
818
+ className: "gap-group flex",
819
+ children: (_field$options = field.options) === null || _field$options === void 0 ? void 0 : _field$options.map((option) => /* @__PURE__ */ jsxs("label", {
820
+ className: "text-text-primary gap-element flex items-center text-sm",
821
+ children: [/* @__PURE__ */ jsx(FormRadio$1, {
822
+ name: field.name,
823
+ value: option.value
824
+ }), option.label]
825
+ }, option.value))
826
+ }) }),
827
+ /* @__PURE__ */ jsx(FormError$1, { name: field.name })
828
+ ]
829
+ }, field.name);
830
+ }
831
+ if (field.type === "checkbox") return /* @__PURE__ */ jsxs("div", {
832
+ className: "gap-element flex flex-col",
833
+ children: [/* @__PURE__ */ jsxs("label", {
834
+ className: "text-text-primary gap-element flex items-center text-sm",
835
+ children: [/* @__PURE__ */ jsx(FormCheckbox$1, { name: field.name }), /* @__PURE__ */ jsx("span", { children: field.label })]
836
+ }), /* @__PURE__ */ jsx(FormError$1, { name: field.name })]
837
+ }, field.name);
838
+ if (field.type === "textarea") {
839
+ var _field$rows, _field$resize;
840
+ return /* @__PURE__ */ jsxs("div", {
841
+ className: "gap-element flex flex-col",
842
+ children: [
843
+ /* @__PURE__ */ jsx(FormLabel$1, {
844
+ name: field.name,
845
+ children: field.label
846
+ }),
847
+ /* @__PURE__ */ jsx(FormTextarea, {
848
+ name: field.name,
849
+ placeholder: field.placeholder,
850
+ rows: (_field$rows = field.rows) !== null && _field$rows !== void 0 ? _field$rows : 4,
851
+ resize: (_field$resize = field.resize) !== null && _field$resize !== void 0 ? _field$resize : "vertical",
852
+ className: field.font === "mono" ? "font-mono text-sm" : void 0
853
+ }),
854
+ /* @__PURE__ */ jsx(FormError$1, { name: field.name })
855
+ ]
856
+ }, field.name);
857
+ }
858
+ if (field.type === "select") {
859
+ var _field$options2;
860
+ return /* @__PURE__ */ jsxs("div", {
861
+ className: "gap-element flex flex-col",
862
+ children: [
863
+ /* @__PURE__ */ jsx(FormLabel$1, {
864
+ name: field.name,
865
+ children: field.label
866
+ }),
867
+ /* @__PURE__ */ jsx(FormSelect, {
868
+ name: field.name,
869
+ placeholder: field.placeholder,
870
+ children: /* @__PURE__ */ jsx(FormSelectPopover, { children: (_field$options2 = field.options) === null || _field$options2 === void 0 ? void 0 : _field$options2.map((option) => /* @__PURE__ */ jsx(FormSelectItem, {
871
+ value: option.value,
872
+ children: option.label
873
+ }, option.value)) })
874
+ }),
875
+ /* @__PURE__ */ jsx(FormError$1, { name: field.name })
876
+ ]
877
+ }, field.name);
878
+ }
879
+ if (field.type === "switch") return /* @__PURE__ */ jsxs("label", {
880
+ className: "gap-group flex items-center",
881
+ children: [/* @__PURE__ */ jsx(FormSwitch$1, { name: field.name }), /* @__PURE__ */ jsxs("div", {
882
+ className: "flex flex-col",
883
+ children: [/* @__PURE__ */ jsx("span", {
884
+ className: "text-text-primary text-sm font-medium",
885
+ children: field.label
886
+ }), field.description && /* @__PURE__ */ jsx("span", {
887
+ className: "text-text-secondary text-sm",
888
+ children: field.description
889
+ })]
890
+ })]
891
+ }, field.name);
892
+ return /* @__PURE__ */ jsxs("div", {
893
+ className: "gap-element flex flex-col",
894
+ children: [
895
+ /* @__PURE__ */ jsx(FormLabel$1, {
896
+ name: field.name,
897
+ children: field.label
898
+ }),
899
+ /* @__PURE__ */ jsx(FormInput$1, {
900
+ name: field.name,
901
+ type: (_field$type = field.type) !== null && _field$type !== void 0 ? _field$type : "text",
902
+ placeholder: field.placeholder
903
+ }),
904
+ /* @__PURE__ */ jsx(FormError$1, { name: field.name })
905
+ ]
906
+ }, field.name);
907
+ }), /* @__PURE__ */ jsx(FormSubmit$1, { children: labels.submitButton })]
908
+ }), submitted && /* @__PURE__ */ jsx("div", {
909
+ className: "border-success bg-success-subtle text-success-fg rounded-lg border p-3 text-sm",
910
+ children: labels.successMessage
911
+ })]
912
+ });
913
+ }
914
+ //#endregion
915
+ //#region src/shells/deployment-timeline.tsx
916
+ const statusIcon = {
917
+ success: "●",
918
+ pending: "◌",
919
+ running: "⟳",
920
+ error: "✗",
921
+ skipped: "○"
922
+ };
923
+ const statusColor = {
924
+ success: "text-success-fg",
925
+ pending: OPT_TEXT_TERTIARY,
926
+ running: "text-accent",
927
+ error: "text-danger-fg",
928
+ skipped: "text-text-tertiary"
929
+ };
930
+ const statusBadge$1 = {
931
+ success: OPT_BADGE_GREEN,
932
+ pending: OPT_BADGE_NEUTRAL,
933
+ running: "inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium bg-info-subtle text-info-fg",
934
+ error: OPT_BADGE_RED,
935
+ skipped: OPT_BADGE_NEUTRAL
936
+ };
937
+ const statusLabel$1 = {
938
+ success: "성공",
939
+ pending: "대기",
940
+ running: "실행 중",
941
+ error: "에러",
942
+ skipped: "건너뜀"
943
+ };
944
+ /** Renders the deployment timeline component. */
945
+ function DeploymentTimeline({ steps, title = "배포 타임라인" }) {
946
+ return /* @__PURE__ */ jsxs("div", {
947
+ className: "gap-group flex flex-col",
948
+ "data-opt-id": "XXVQ2",
949
+ "data-opt-slug": "deployment-timeline.DeploymentTimeline",
950
+ children: [title && /* @__PURE__ */ jsx("h3", {
951
+ className: `text-sm font-semibold text-text-primary`,
952
+ children: title
953
+ }), /* @__PURE__ */ jsx(CompositeProvider, {
954
+ focusLoop: true,
955
+ orientation: "vertical",
956
+ children: /* @__PURE__ */ jsx(Composite, {
957
+ role: "list",
958
+ "aria-label": title,
959
+ className: "relative",
960
+ children: steps.map((step, i) => {
961
+ const isLast = i === steps.length - 1;
962
+ return /* @__PURE__ */ jsxs(CompositeItem, {
963
+ className: `group gap-group relative flex w-full scroll-m-2 rounded-lg px-3 py-2 ${OPT_FOCUS} data-[active-item]:bg-accent-subtle`,
964
+ role: "listitem",
965
+ children: [/* @__PURE__ */ jsxs("div", {
966
+ className: "flex flex-col items-center",
967
+ children: [/* @__PURE__ */ jsx("span", {
968
+ className: `text-lg leading-none ${statusColor[step.status]}`,
969
+ "aria-label": statusLabel$1[step.status],
970
+ children: statusIcon[step.status]
971
+ }), !isLast && /* @__PURE__ */ jsx("div", { className: "bg-border-subtle mt-1 w-px flex-1" })]
972
+ }), /* @__PURE__ */ jsxs("div", {
973
+ className: "flex-1 pb-3",
974
+ children: [/* @__PURE__ */ jsxs("div", {
975
+ className: "gap-element flex items-center",
976
+ children: [
977
+ /* @__PURE__ */ jsx("span", {
978
+ className: `text-sm font-medium ${OPT_TEXT_PRIMARY}`,
979
+ children: step.name
980
+ }),
981
+ /* @__PURE__ */ jsx("span", {
982
+ className: statusBadge$1[step.status],
983
+ children: statusLabel$1[step.status]
984
+ }),
985
+ step.duration && /* @__PURE__ */ jsx("span", {
986
+ className: `text-xs text-text-tertiary`,
987
+ children: step.duration
988
+ })
989
+ ]
990
+ }), step.detail && /* @__PURE__ */ jsx("p", {
991
+ className: `mt-0.5 text-xs text-text-secondary`,
992
+ children: step.detail
993
+ })]
994
+ })]
995
+ }, step.id);
996
+ })
997
+ })
998
+ })]
999
+ });
1000
+ }
1001
+ //#endregion
1002
+ //#region src/shells/project-switcher.tsx
1003
+ const statusDot = {
1004
+ active: OPT_DOT_GREEN,
1005
+ paused: OPT_DOT_YELLOW,
1006
+ error: OPT_DOT_RED
1007
+ };
1008
+ /** Renders the project switcher component. */
1009
+ function ProjectSwitcher({ projects, value, onChange, placeholder = "프로젝트 검색..." }) {
1010
+ const [search, setSearch] = useState("");
1011
+ const matches = useMemo(() => {
1012
+ if (!search) return projects;
1013
+ return matchSorter(projects, search, { keys: ["name", "framework"] });
1014
+ }, [search, projects]);
1015
+ const selected = projects.find((p) => p.id === value);
1016
+ return /* @__PURE__ */ jsx("div", {
1017
+ className: "gap-element flex flex-col",
1018
+ "data-opt-id": "5R3AG",
1019
+ "data-opt-slug": "project-switcher.ProjectSwitcher",
1020
+ children: /* @__PURE__ */ jsxs(ComboboxRoot$1, {
1021
+ onValueChange: setSearch,
1022
+ children: [
1023
+ /* @__PURE__ */ jsx("label", {
1024
+ className: OPT_TEXT_LABEL,
1025
+ children: "프로젝트"
1026
+ }),
1027
+ /* @__PURE__ */ jsx(ComboboxInput$1, {
1028
+ placeholder: selected ? selected.name : placeholder,
1029
+ className: OPT_INPUT
1030
+ }),
1031
+ /* @__PURE__ */ jsxs(ComboboxPopover$1, {
1032
+ className: "max-h-72 overflow-y-auto",
1033
+ children: [matches.length === 0 && /* @__PURE__ */ jsx("p", {
1034
+ className: `py-6 text-center text-sm text-text-secondary`,
1035
+ children: "결과가 없습니다."
1036
+ }), matches.map((project) => /* @__PURE__ */ jsxs(ComboboxItem$1, {
1037
+ value: project.name,
1038
+ onClick: () => onChange === null || onChange === void 0 ? void 0 : onChange(project.id),
1039
+ className: `gap-element flex scroll-m-2 items-center ${OPT_ITEM_BASE} ${OPT_ACTIVE_ITEM}`,
1040
+ children: [
1041
+ /* @__PURE__ */ jsx("span", { className: `shrink-0 ${statusDot[project.status]}` }),
1042
+ /* @__PURE__ */ jsx("span", {
1043
+ className: "flex-1 truncate font-medium",
1044
+ children: project.name
1045
+ }),
1046
+ /* @__PURE__ */ jsx("span", {
1047
+ className: OPT_BADGE_NEUTRAL,
1048
+ children: project.framework
1049
+ }),
1050
+ project.domain && /* @__PURE__ */ jsx("span", {
1051
+ className: `truncate text-xs text-text-secondary data-[active-item]:text-accent-fg/70`,
1052
+ children: project.domain
1053
+ })
1054
+ ]
1055
+ }, project.id))]
1056
+ })
1057
+ ]
1058
+ })
1059
+ });
1060
+ }
1061
+ ProjectSwitcher.displayName = "ProjectSwitcher";
1062
+ //#endregion
1063
+ //#region src/shells/env-panel.tsx
1064
+ const defaultLabels$4 = {
1065
+ varCount: (count) => `${count}개 변수`,
1066
+ envVarsLabel: (env) => `${env} 환경 변수`,
1067
+ secretBadge: "secret"
1068
+ };
1069
+ const envDot = {
1070
+ Production: OPT_DOT_RED,
1071
+ Preview: OPT_DOT_YELLOW,
1072
+ Development: OPT_DOT_GREEN
1073
+ };
1074
+ /** Renders the env panel component. */
1075
+ function EnvPanel({ groups, labels: customLabels }) {
1076
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$4), customLabels);
1077
+ return /* @__PURE__ */ jsx("div", {
1078
+ className: `rounded-lg ${OPT_BORDER} divide-y ${OPT_DIVIDER} bg-surface`,
1079
+ "data-opt-id": "ZBDWK",
1080
+ "data-opt-slug": "env-panel.EnvPanel",
1081
+ children: groups.map((group) => {
1082
+ var _envDot$group$env;
1083
+ return /* @__PURE__ */ jsx(DisclosureRoot$1, {
1084
+ defaultOpen: group.env === "Production",
1085
+ children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(DisclosureTrigger$1, {
1086
+ className: OPT_DISCLOSURE_TRIGGER,
1087
+ children: [/* @__PURE__ */ jsxs("span", {
1088
+ className: "gap-element flex items-center",
1089
+ children: [
1090
+ /* @__PURE__ */ 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" }),
1091
+ /* @__PURE__ */ jsx("span", {
1092
+ className: "font-medium",
1093
+ children: group.env
1094
+ }),
1095
+ /* @__PURE__ */ jsx("span", {
1096
+ className: `text-xs ${OPT_TEXT_TERTIARY}`,
1097
+ children: labels.varCount(group.vars.length)
1098
+ })
1099
+ ]
1100
+ }), /* @__PURE__ */ jsx("svg", {
1101
+ className: "text-text-tertiary h-4 w-4 shrink-0 transition-transform [[data-open]>&]:rotate-180",
1102
+ fill: "none",
1103
+ viewBox: "0 0 24 24",
1104
+ stroke: "currentColor",
1105
+ strokeWidth: 2,
1106
+ "aria-hidden": "true",
1107
+ children: /* @__PURE__ */ jsx("path", {
1108
+ strokeLinecap: "round",
1109
+ strokeLinejoin: "round",
1110
+ d: "m19.5 8.25-7.5 7.5-7.5-7.5"
1111
+ })
1112
+ })]
1113
+ }), /* @__PURE__ */ jsx(DisclosureContent$1, {
1114
+ className: OPT_DISCLOSURE_CONTENT,
1115
+ children: /* @__PURE__ */ jsx(CompositeProvider, {
1116
+ focusLoop: true,
1117
+ orientation: "vertical",
1118
+ children: /* @__PURE__ */ jsx(Composite, {
1119
+ role: "list",
1120
+ "aria-label": labels.envVarsLabel(group.env),
1121
+ className: "px-4 pb-3",
1122
+ children: group.vars.map((v) => /* @__PURE__ */ jsxs(CompositeItem, {
1123
+ role: "listitem",
1124
+ className: `gap-group flex scroll-m-2 items-center rounded px-2 py-1.5 text-sm ${OPT_FOCUS} data-[active-item]:bg-accent-subtle`,
1125
+ children: [
1126
+ /* @__PURE__ */ jsx("span", {
1127
+ className: `w-44 shrink-0 truncate font-mono text-xs font-medium ${OPT_TEXT_PRIMARY}`,
1128
+ children: v.key
1129
+ }),
1130
+ /* @__PURE__ */ jsx("span", {
1131
+ className: `flex-1 truncate font-mono text-xs ${OPT_TEXT_SECONDARY}`,
1132
+ children: v.isSecret ? "••••••••" : v.value
1133
+ }),
1134
+ v.isSecret && /* @__PURE__ */ jsx("span", {
1135
+ className: OPT_BADGE_NEUTRAL,
1136
+ children: labels.secretBadge
1137
+ })
1138
+ ]
1139
+ }, v.key))
1140
+ })
1141
+ })
1142
+ })] })
1143
+ }, group.env);
1144
+ })
1145
+ });
1146
+ }
1147
+ //#endregion
1148
+ //#region src/shells/branch-select.tsx
1149
+ const defaultLabels$3 = {
1150
+ selectLabel: "브랜치",
1151
+ emptySelectionLabel: "선택...",
1152
+ emptyMessage: "사용 가능한 브랜치가 없습니다.",
1153
+ defaultBadge: "default"
1154
+ };
1155
+ /** Renders the branch select component. */
1156
+ function BranchSelect({ branches, value: controlledValue, onChange, label, labels: customLabels }) {
1157
+ var _ref, _defaultBranch$name, _branches$, _selected$name;
1158
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$3), customLabels);
1159
+ const resolvedLabel = label !== null && label !== void 0 ? label : labels.selectLabel;
1160
+ const defaultBranch = branches.find((b) => b.isDefault);
1161
+ const [internalValue, setInternalValue] = 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 : "");
1162
+ const value = controlledValue !== null && controlledValue !== void 0 ? controlledValue : internalValue;
1163
+ const selected = branches.find((b) => b.name === value);
1164
+ if (branches.length === 0) return /* @__PURE__ */ jsxs("div", {
1165
+ className: "gap-element flex flex-col",
1166
+ "data-opt-id": "7EDEP",
1167
+ "data-opt-slug": "branch-select.BranchSelect",
1168
+ children: [/* @__PURE__ */ jsx("span", {
1169
+ className: OPT_TEXT_LABEL,
1170
+ children: resolvedLabel
1171
+ }), /* @__PURE__ */ jsx("span", {
1172
+ className: OPT_TEXT_TERTIARY,
1173
+ children: labels.emptyMessage
1174
+ })]
1175
+ });
1176
+ const handleChange = (v) => {
1177
+ var _v$;
1178
+ const next = Array.isArray(v) ? (_v$ = v[0]) !== null && _v$ !== void 0 ? _v$ : "" : v;
1179
+ if (onChange) onChange(next);
1180
+ else setInternalValue(next);
1181
+ };
1182
+ return /* @__PURE__ */ jsx("div", {
1183
+ className: "gap-element flex flex-col",
1184
+ "data-opt-id": "7EDEP",
1185
+ "data-opt-slug": "branch-select.BranchSelect",
1186
+ children: /* @__PURE__ */ jsxs(SelectRoot$1, {
1187
+ value,
1188
+ onValueChange: handleChange,
1189
+ children: [
1190
+ /* @__PURE__ */ jsx(SelectLabel$1, {
1191
+ className: OPT_TEXT_LABEL,
1192
+ children: resolvedLabel
1193
+ }),
1194
+ /* @__PURE__ */ jsxs(SelectTrigger$1, {
1195
+ className: `${OPT_INPUT} max-w-xs items-center justify-between gap-1`,
1196
+ children: [/* @__PURE__ */ jsxs("span", {
1197
+ className: "gap-element flex items-center truncate",
1198
+ children: [
1199
+ /* @__PURE__ */ jsx("svg", {
1200
+ className: "text-text-tertiary h-4 w-4 shrink-0",
1201
+ fill: "none",
1202
+ viewBox: "0 0 24 24",
1203
+ stroke: "currentColor",
1204
+ strokeWidth: 2,
1205
+ "aria-hidden": "true",
1206
+ children: /* @__PURE__ */ jsx("path", {
1207
+ strokeLinecap: "round",
1208
+ strokeLinejoin: "round",
1209
+ 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"
1210
+ })
1211
+ }),
1212
+ /* @__PURE__ */ jsx("span", {
1213
+ className: "truncate",
1214
+ children: (_selected$name = selected === null || selected === void 0 ? void 0 : selected.name) !== null && _selected$name !== void 0 ? _selected$name : labels.emptySelectionLabel
1215
+ }),
1216
+ (selected === null || selected === void 0 ? void 0 : selected.isDefault) && /* @__PURE__ */ jsx("span", {
1217
+ className: OPT_BADGE_BLUE,
1218
+ children: labels.defaultBadge
1219
+ })
1220
+ ]
1221
+ }), /* @__PURE__ */ jsx("svg", {
1222
+ className: "text-text-tertiary h-4 w-4 shrink-0",
1223
+ fill: "none",
1224
+ viewBox: "0 0 24 24",
1225
+ stroke: "currentColor",
1226
+ strokeWidth: 2,
1227
+ "aria-hidden": "true",
1228
+ children: /* @__PURE__ */ jsx("path", {
1229
+ strokeLinecap: "round",
1230
+ strokeLinejoin: "round",
1231
+ d: "m19.5 8.25-7.5 7.5-7.5-7.5"
1232
+ })
1233
+ })]
1234
+ }),
1235
+ /* @__PURE__ */ jsx(SelectPopover$1, {
1236
+ className: "max-w-xs",
1237
+ children: branches.map((branch) => /* @__PURE__ */ jsxs(SelectItem$1, {
1238
+ value: branch.name,
1239
+ className: `gap-element flex scroll-m-2 items-center ${OPT_ITEM_BASE} ${OPT_ACTIVE_ITEM}`,
1240
+ children: [
1241
+ /* @__PURE__ */ jsx("span", {
1242
+ className: "flex-1 truncate",
1243
+ children: branch.name
1244
+ }),
1245
+ branch.isDefault && /* @__PURE__ */ jsx("span", {
1246
+ 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",
1247
+ children: labels.defaultBadge
1248
+ }),
1249
+ branch.lastCommit && /* @__PURE__ */ jsx("span", {
1250
+ className: `font-mono text-xs text-text-tertiary data-[active-item]:text-accent-fg/60`,
1251
+ children: branch.lastCommit
1252
+ }),
1253
+ branch.author && /* @__PURE__ */ jsx("span", {
1254
+ className: `text-xs text-text-tertiary data-[active-item]:text-accent-fg/60`,
1255
+ children: branch.author
1256
+ })
1257
+ ]
1258
+ }, branch.name))
1259
+ })
1260
+ ]
1261
+ })
1262
+ });
1263
+ }
1264
+ BranchSelect.displayName = "BranchSelect";
1265
+ //#endregion
1266
+ //#region src/shells/domain-table.tsx
1267
+ const defaultLabels$2 = {
1268
+ domain: "Domain",
1269
+ status: "Status",
1270
+ ssl: "SSL",
1271
+ redirect: "Redirect",
1272
+ added: "Added",
1273
+ emptyMessage: "No domains configured"
1274
+ };
1275
+ const statusBadge = {
1276
+ active: OPT_BADGE_GREEN,
1277
+ pending: OPT_BADGE_YELLOW,
1278
+ error: OPT_BADGE_RED
1279
+ };
1280
+ const sslBadge = {
1281
+ valid: OPT_BADGE_GREEN,
1282
+ pending: OPT_BADGE_YELLOW,
1283
+ expired: OPT_BADGE_RED
1284
+ };
1285
+ const statusLabel = {
1286
+ active: "Active",
1287
+ pending: "Pending",
1288
+ error: "Error"
1289
+ };
1290
+ const sslLabel = {
1291
+ valid: "Valid",
1292
+ pending: "Pending",
1293
+ expired: "Expired"
1294
+ };
1295
+ /** Renders the domain table component. */
1296
+ function DomainTable({ domains, title = "도메인", labels: customLabels }) {
1297
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels$2), customLabels);
1298
+ if (domains.length === 0) return /* @__PURE__ */ jsxs("div", {
1299
+ className: "gap-group flex flex-col",
1300
+ "data-opt-id": "A7SRM",
1301
+ "data-opt-slug": "domain-table.DomainTable",
1302
+ children: [title && /* @__PURE__ */ jsx("h3", {
1303
+ className: `text-sm font-semibold text-text-primary`,
1304
+ children: title
1305
+ }), /* @__PURE__ */ jsx("div", {
1306
+ className: `rounded-lg px-4 py-6 text-sm ${OPT_BORDER} ${OPT_TEXT_SECONDARY} bg-surface`,
1307
+ children: labels.emptyMessage
1308
+ })]
1309
+ });
1310
+ return /* @__PURE__ */ jsxs("div", {
1311
+ className: "gap-group flex flex-col",
1312
+ "data-opt-id": "A7SRM",
1313
+ "data-opt-slug": "domain-table.DomainTable",
1314
+ children: [title && /* @__PURE__ */ jsx("h3", {
1315
+ className: `text-sm font-semibold text-text-primary`,
1316
+ children: title
1317
+ }), /* @__PURE__ */ jsx("div", {
1318
+ className: `overflow-x-auto rounded-lg ${OPT_BORDER} bg-surface`,
1319
+ children: /* @__PURE__ */ jsx(CompositeProvider, {
1320
+ focusLoop: true,
1321
+ focusWrap: true,
1322
+ children: /* @__PURE__ */ jsxs(Composite, {
1323
+ role: "grid",
1324
+ "aria-label": title,
1325
+ className: "w-full",
1326
+ render: /* @__PURE__ */ jsx("table", {}),
1327
+ children: [/* @__PURE__ */ jsx("thead", {
1328
+ className: "border-border-subtle bg-surface-inset border-b",
1329
+ children: /* @__PURE__ */ jsxs("tr", { children: [
1330
+ /* @__PURE__ */ jsx("th", {
1331
+ className: OPT_TH,
1332
+ children: labels.domain
1333
+ }),
1334
+ /* @__PURE__ */ jsx("th", {
1335
+ className: OPT_TH,
1336
+ children: labels.status
1337
+ }),
1338
+ /* @__PURE__ */ jsx("th", {
1339
+ className: OPT_TH,
1340
+ children: labels.ssl
1341
+ }),
1342
+ /* @__PURE__ */ jsx("th", {
1343
+ className: OPT_TH,
1344
+ children: labels.redirect
1345
+ }),
1346
+ /* @__PURE__ */ jsx("th", {
1347
+ className: OPT_TH,
1348
+ children: labels.added
1349
+ })
1350
+ ] })
1351
+ }), /* @__PURE__ */ jsx("tbody", {
1352
+ className: `divide-y ${OPT_DIVIDER}`,
1353
+ children: domains.map((d) => {
1354
+ var _d$redirect, _d$addedAt;
1355
+ return /* @__PURE__ */ jsxs(CompositeRow, {
1356
+ render: /* @__PURE__ */ jsx("tr", {}),
1357
+ children: [
1358
+ /* @__PURE__ */ jsx(CompositeItem, {
1359
+ render: /* @__PURE__ */ jsx("td", {
1360
+ className: `${OPT_TD} font-mono font-medium`,
1361
+ role: "gridcell"
1362
+ }),
1363
+ className: OPT_TABLE_ROW_ACTIVE,
1364
+ children: d.domain
1365
+ }),
1366
+ /* @__PURE__ */ jsx(CompositeItem, {
1367
+ render: /* @__PURE__ */ jsx("td", {
1368
+ className: OPT_TD,
1369
+ role: "gridcell"
1370
+ }),
1371
+ className: OPT_TABLE_ROW_ACTIVE,
1372
+ children: /* @__PURE__ */ jsx("span", {
1373
+ className: statusBadge[d.status],
1374
+ children: statusLabel[d.status]
1375
+ })
1376
+ }),
1377
+ /* @__PURE__ */ jsx(CompositeItem, {
1378
+ render: /* @__PURE__ */ jsx("td", {
1379
+ className: OPT_TD,
1380
+ role: "gridcell"
1381
+ }),
1382
+ className: OPT_TABLE_ROW_ACTIVE,
1383
+ children: /* @__PURE__ */ jsx("span", {
1384
+ className: sslBadge[d.ssl],
1385
+ children: sslLabel[d.ssl]
1386
+ })
1387
+ }),
1388
+ /* @__PURE__ */ jsx(CompositeItem, {
1389
+ render: /* @__PURE__ */ jsx("td", {
1390
+ className: OPT_TD,
1391
+ role: "gridcell"
1392
+ }),
1393
+ className: OPT_TABLE_ROW_ACTIVE,
1394
+ children: (_d$redirect = d.redirect) !== null && _d$redirect !== void 0 ? _d$redirect : "—"
1395
+ }),
1396
+ /* @__PURE__ */ jsx(CompositeItem, {
1397
+ render: /* @__PURE__ */ jsx("td", {
1398
+ className: OPT_TD_MUTED,
1399
+ role: "gridcell"
1400
+ }),
1401
+ className: OPT_TABLE_ROW_ACTIVE,
1402
+ children: (_d$addedAt = d.addedAt) !== null && _d$addedAt !== void 0 ? _d$addedAt : "—"
1403
+ })
1404
+ ]
1405
+ }, d.domain);
1406
+ })
1407
+ })]
1408
+ })
1409
+ })
1410
+ })]
1411
+ });
1412
+ }
1413
+ //#endregion
1414
+ //#region src/shells/data-table.tsx
1415
+ const MIN_COLUMN_WIDTH = 40;
1416
+ const DEFAULT_COLUMN_RESIZE_MAX = 1200;
1417
+ const alignmentClass = {
1418
+ left: "text-left",
1419
+ center: "text-center",
1420
+ right: "text-right"
1421
+ };
1422
+ function getCellValue(row, accessor) {
1423
+ if (typeof accessor === "function") try {
1424
+ return accessor(row);
1425
+ } catch (_unused) {
1426
+ return "—";
1427
+ }
1428
+ const value = row[accessor];
1429
+ if (value === null || value === void 0 || typeof value === "number" && !Number.isFinite(value)) return "—";
1430
+ return String(value);
1431
+ }
1432
+ function getRawValue(row, accessor) {
1433
+ if (typeof accessor === "function") return accessor(row);
1434
+ return row[accessor];
1435
+ }
1436
+ function isMissingSortValue(value) {
1437
+ return value == null || typeof value === "number" && !Number.isFinite(value);
1438
+ }
1439
+ function compareSortValues(aVal, bVal, dir) {
1440
+ const aMissing = isMissingSortValue(aVal);
1441
+ const bMissing = isMissingSortValue(bVal);
1442
+ if (aMissing && bMissing) return 0;
1443
+ if (aMissing) return 1;
1444
+ if (bMissing) return -1;
1445
+ const cmp = typeof aVal === "number" && typeof bVal === "number" ? aVal - bVal : String(aVal).localeCompare(String(bVal));
1446
+ return dir === "desc" ? -cmp : cmp;
1447
+ }
1448
+ function clampPercent(ratio) {
1449
+ if (!Number.isFinite(ratio)) return 0;
1450
+ return Math.min(100, Math.max(0, Math.round(ratio * 100)));
1451
+ }
1452
+ function getResizeHandleValue(currentWidth, configuredWidth) {
1453
+ if (typeof currentWidth === "number" && Number.isFinite(currentWidth)) return Math.max(MIN_COLUMN_WIDTH, Math.round(currentWidth));
1454
+ const pxWidth = configuredWidth === null || configuredWidth === void 0 ? void 0 : configuredWidth.trim().match(/^(\d+(?:\.\d+)?)px$/);
1455
+ if (pxWidth) return Math.max(MIN_COLUMN_WIDTH, Math.round(Number(pxWidth[1])));
1456
+ return MIN_COLUMN_WIDTH;
1457
+ }
1458
+ function DataTableProgressBar({ value, barColor, label }) {
1459
+ const percent = clampPercent(value);
1460
+ return /* @__PURE__ */ jsx("div", {
1461
+ className: "bg-bg-subtle mt-1 h-1.5 w-full overflow-hidden rounded-full",
1462
+ role: "progressbar",
1463
+ "aria-valuenow": percent,
1464
+ "aria-valuemin": 0,
1465
+ "aria-valuemax": 100,
1466
+ "aria-valuetext": `${percent}%`,
1467
+ "aria-label": label,
1468
+ children: /* @__PURE__ */ jsx("div", {
1469
+ className: cn("h-full rounded-full transition-all", barColor),
1470
+ style: { width: `${percent}%` }
1471
+ })
1472
+ });
1473
+ }
1474
+ /** Renders the data table component. */
1475
+ 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 }) {
1476
+ const [sortCol, setSortCol] = useState(null);
1477
+ const [sortDir, setSortDir] = useState(null);
1478
+ const [expandedKeys, setExpandedKeys] = useState(/* @__PURE__ */ new Set());
1479
+ const [page, setPage] = useState(0);
1480
+ const [colWidths, setColWidths] = useState({});
1481
+ const sortedData = useMemo(() => {
1482
+ if (!sortable || !sortCol || !sortDir) return data;
1483
+ const col = columns.find((c) => c.id === sortCol);
1484
+ if (!col) return data;
1485
+ return [...data].sort((a, b) => {
1486
+ return compareSortValues(getRawValue(a, col.accessor), getRawValue(b, col.accessor), sortDir);
1487
+ });
1488
+ }, [
1489
+ data,
1490
+ sortable,
1491
+ sortCol,
1492
+ sortDir,
1493
+ columns
1494
+ ]);
1495
+ const totalPages = pageSize ? Math.ceil(sortedData.length / pageSize) : 1;
1496
+ const pagedData = pageSize ? sortedData.slice(page * pageSize, (page + 1) * pageSize) : sortedData;
1497
+ const handleSort = useCallback((colId) => {
1498
+ if (!sortable) return;
1499
+ let nextDir;
1500
+ if (sortCol === colId) {
1501
+ nextDir = sortDir === "asc" ? "desc" : sortDir === "desc" ? null : "asc";
1502
+ setSortDir(nextDir);
1503
+ if (nextDir === null) setSortCol(null);
1504
+ } else {
1505
+ setSortCol(colId);
1506
+ setSortDir("asc");
1507
+ nextDir = "asc";
1508
+ }
1509
+ setPage(0);
1510
+ if (onSortExternal) onSortExternal(colId, nextDir);
1511
+ }, [
1512
+ sortable,
1513
+ sortCol,
1514
+ sortDir,
1515
+ onSortExternal
1516
+ ]);
1517
+ const toggleExpand = useCallback((key) => {
1518
+ setExpandedKeys((prev) => {
1519
+ const next = new Set(prev);
1520
+ if (next.has(key)) next.delete(key);
1521
+ else next.add(key);
1522
+ return next;
1523
+ });
1524
+ }, []);
1525
+ const selected = selectedKeys !== null && selectedKeys !== void 0 ? selectedKeys : [];
1526
+ const selectedSet = useMemo(() => new Set(selectedKeys !== null && selectedKeys !== void 0 ? selectedKeys : []), [selectedKeys]);
1527
+ const allKeys = data.map(keyExtractor);
1528
+ const allSelected = allKeys.length > 0 && allKeys.every((k) => selectedSet.has(k));
1529
+ const toggleSelectAll = useCallback(() => {
1530
+ if (!onSelectionChange) return;
1531
+ onSelectionChange(allSelected ? [] : allKeys);
1532
+ }, [
1533
+ allSelected,
1534
+ allKeys,
1535
+ onSelectionChange
1536
+ ]);
1537
+ const toggleSelectRow = useCallback((key) => {
1538
+ if (!onSelectionChange) return;
1539
+ onSelectionChange(selectedSet.has(key) ? selected.filter((k) => k !== key) : [...selected, key]);
1540
+ }, [
1541
+ selected,
1542
+ selectedSet,
1543
+ onSelectionChange
1544
+ ]);
1545
+ const sortIndicator = (colId) => {
1546
+ if (sortCol !== colId) return null;
1547
+ return sortDir === "asc" ? " ▲" : sortDir === "desc" ? " ▼" : null;
1548
+ };
1549
+ const colCount = columns.length + (selectable ? 1 : 0) + (expandable ? 1 : 0);
1550
+ return /* @__PURE__ */ jsxs("div", {
1551
+ className: cn("gap-group flex flex-col", className),
1552
+ "data-opt-id": "J1KN4",
1553
+ "data-opt-slug": "data-table.DataTable",
1554
+ children: [
1555
+ title && /* @__PURE__ */ jsx("h3", {
1556
+ className: cn("text-sm font-semibold", "text-text-primary"),
1557
+ children: title
1558
+ }),
1559
+ /* @__PURE__ */ jsx("div", {
1560
+ className: cn("overflow-x-auto rounded-lg", OPT_BORDER, "bg-surface"),
1561
+ children: data.length === 0 ? /* @__PURE__ */ jsx("div", {
1562
+ className: cn("px-4 py-8 text-center text-sm", OPT_TEXT_SECONDARY),
1563
+ children: emptyMessage
1564
+ }) : /* @__PURE__ */ jsx(CompositeProvider, {
1565
+ focusLoop: true,
1566
+ focusWrap: true,
1567
+ children: /* @__PURE__ */ jsxs(Composite, {
1568
+ role: "grid",
1569
+ "aria-label": title,
1570
+ className: "w-full",
1571
+ render: /* @__PURE__ */ jsx("table", {}),
1572
+ children: [/* @__PURE__ */ jsx("thead", {
1573
+ className: cn("border-border-subtle bg-surface-inset border-b", stickyHeader && "sticky top-0 z-10"),
1574
+ children: /* @__PURE__ */ jsxs("tr", { children: [
1575
+ selectable && /* @__PURE__ */ jsx("th", {
1576
+ className: cn("px-3 py-2.5 text-left text-xs font-semibold uppercase tracking-wider text-text-tertiary", "w-10"),
1577
+ children: /* @__PURE__ */ jsx(Checkbox, {
1578
+ checked: allSelected,
1579
+ onChange: toggleSelectAll,
1580
+ "aria-label": "Select all"
1581
+ })
1582
+ }),
1583
+ expandable && /* @__PURE__ */ jsx("th", { className: cn("px-3 py-2.5 text-left text-xs font-semibold uppercase tracking-wider text-text-tertiary", "w-10") }),
1584
+ columns.map((col) => {
1585
+ var _col$align;
1586
+ const isSortable = sortable && col.sortable !== false;
1587
+ const resizeValue = getResizeHandleValue(colWidths[col.id], col.width);
1588
+ const resizeMax = Math.max(DEFAULT_COLUMN_RESIZE_MAX, resizeValue);
1589
+ return /* @__PURE__ */ jsxs("th", {
1590
+ className: cn(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"),
1591
+ style: { width: colWidths[col.id] || col.width || void 0 },
1592
+ onClick: isSortable ? () => handleSort(col.id) : void 0,
1593
+ onKeyDown: (event) => {
1594
+ if (!isSortable || event.key !== "Enter" && event.key !== " ") return;
1595
+ event.preventDefault();
1596
+ handleSort(col.id);
1597
+ },
1598
+ tabIndex: isSortable ? 0 : void 0,
1599
+ "aria-sort": sortCol === col.id && sortDir ? sortDir === "asc" ? "ascending" : "descending" : void 0,
1600
+ children: [/* @__PURE__ */ jsxs("span", {
1601
+ className: "flex items-center",
1602
+ children: [col.header, sortIndicator(col.id)]
1603
+ }), resizableColumns && /* @__PURE__ */ jsx("span", {
1604
+ role: "separator",
1605
+ "aria-orientation": "vertical",
1606
+ "aria-label": `Resize ${col.header}`,
1607
+ "aria-valuemin": MIN_COLUMN_WIDTH,
1608
+ "aria-valuemax": resizeMax,
1609
+ "aria-valuenow": resizeValue,
1610
+ "aria-valuetext": `${resizeValue} pixels`,
1611
+ tabIndex: 0,
1612
+ className: "bg-border absolute top-0 right-0 bottom-0 w-1 cursor-col-resize opacity-0 hover:opacity-100",
1613
+ onKeyDown: (e) => {
1614
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight" && e.key !== "Home" && e.key !== "End") return;
1615
+ e.preventDefault();
1616
+ e.stopPropagation();
1617
+ if (e.key === "Home" || e.key === "End") {
1618
+ setColWidths((prev) => _objectSpread2(_objectSpread2({}, prev), {}, { [col.id]: e.key === "Home" ? MIN_COLUMN_WIDTH : resizeMax }));
1619
+ return;
1620
+ }
1621
+ const delta = e.shiftKey ? 40 : 10;
1622
+ const direction = e.key === "ArrowRight" ? 1 : -1;
1623
+ setColWidths((prev) => {
1624
+ const current = getResizeHandleValue(prev[col.id], col.width);
1625
+ return _objectSpread2(_objectSpread2({}, prev), {}, { [col.id]: Math.max(MIN_COLUMN_WIDTH, current + direction * delta) });
1626
+ });
1627
+ },
1628
+ onMouseDown: (e) => {
1629
+ e.preventDefault();
1630
+ e.stopPropagation();
1631
+ const startX = e.clientX;
1632
+ const startW = e.target.parentElement.offsetWidth;
1633
+ const onMove = (ev) => {
1634
+ const newW = Math.max(MIN_COLUMN_WIDTH, startW + ev.clientX - startX);
1635
+ setColWidths((prev) => _objectSpread2(_objectSpread2({}, prev), {}, { [col.id]: newW }));
1636
+ };
1637
+ const onUp = () => {
1638
+ document.removeEventListener("mousemove", onMove);
1639
+ document.removeEventListener("mouseup", onUp);
1640
+ };
1641
+ document.addEventListener("mousemove", onMove);
1642
+ document.addEventListener("mouseup", onUp);
1643
+ }
1644
+ })]
1645
+ }, col.id);
1646
+ })
1647
+ ] })
1648
+ }), /* @__PURE__ */ jsx("tbody", {
1649
+ className: cn("divide-y", OPT_DIVIDER),
1650
+ children: pagedData.map((row) => {
1651
+ const key = keyExtractor(row);
1652
+ const isExpanded = expandedKeys.has(key);
1653
+ const isSelected = selectedSet.has(key);
1654
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(CompositeRow, {
1655
+ render: /* @__PURE__ */ jsx("tr", {
1656
+ className: cn(onRowClick ? "cursor-pointer" : void 0, (isSelected || activeRowId === key) && "bg-accent-subtle"),
1657
+ onClick: onRowClick ? () => onRowClick(row) : void 0
1658
+ }),
1659
+ children: [
1660
+ selectable && /* @__PURE__ */ jsx(CompositeItem, {
1661
+ render: /* @__PURE__ */ jsx("td", {
1662
+ className: cn("px-3 py-2.5 text-sm text-text-primary whitespace-nowrap", "w-10"),
1663
+ role: "gridcell"
1664
+ }),
1665
+ 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",
1666
+ children: /* @__PURE__ */ jsx(Checkbox, {
1667
+ checked: isSelected,
1668
+ onChange: () => toggleSelectRow(key),
1669
+ "aria-label": `Select row ${key}`
1670
+ })
1671
+ }),
1672
+ expandable && /* @__PURE__ */ jsx(CompositeItem, {
1673
+ render: /* @__PURE__ */ jsx("td", {
1674
+ className: cn("px-3 py-2.5 text-sm text-text-primary whitespace-nowrap", "w-10"),
1675
+ role: "gridcell"
1676
+ }),
1677
+ 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",
1678
+ children: /* @__PURE__ */ jsx("button", {
1679
+ type: "button",
1680
+ onClick: (e) => {
1681
+ e.stopPropagation();
1682
+ toggleExpand(key);
1683
+ },
1684
+ className: 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"),
1685
+ "aria-expanded": isExpanded,
1686
+ "aria-label": `${isExpanded ? "Collapse" : "Expand"} row ${key}`,
1687
+ children: "▶"
1688
+ })
1689
+ }),
1690
+ columns.map((col, colIndex) => {
1691
+ var _col$align2, _col$barColor;
1692
+ return /* @__PURE__ */ jsxs(CompositeItem, {
1693
+ render: /* @__PURE__ */ jsx("td", {
1694
+ className: cn(OPT_TD, alignmentClass[(_col$align2 = col.align) !== null && _col$align2 !== void 0 ? _col$align2 : "left"], colIndex === 0 ? "font-medium" : ""),
1695
+ role: "gridcell"
1696
+ }),
1697
+ className: OPT_TABLE_ROW_ACTIVE,
1698
+ onKeyDown: (event) => {
1699
+ if (!onRowClick || event.target !== event.currentTarget || event.key !== "Enter" && event.key !== " ") return;
1700
+ event.preventDefault();
1701
+ onRowClick(row);
1702
+ },
1703
+ children: [getCellValue(row, col.accessor), col.bar && /* @__PURE__ */ jsx(DataTableProgressBar, {
1704
+ value: col.bar(row),
1705
+ barColor: (_col$barColor = col.barColor) !== null && _col$barColor !== void 0 ? _col$barColor : "bg-accent",
1706
+ label: col.header
1707
+ })]
1708
+ }, col.id);
1709
+ })
1710
+ ]
1711
+ }), expandable && isExpanded && renderExpanded && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
1712
+ colSpan: colCount,
1713
+ className: "border-border-subtle bg-surface-inset border-t px-4 py-3",
1714
+ children: renderExpanded(row)
1715
+ }) })] }, key);
1716
+ })
1717
+ })]
1718
+ })
1719
+ })
1720
+ }),
1721
+ pageSize && totalPages > 1 && /* @__PURE__ */ jsx(Pagination, {
1722
+ totalItems: sortedData.length,
1723
+ pageSize,
1724
+ currentPage: page + 1,
1725
+ onPageChange: (p) => setPage(p - 1)
1726
+ })
1727
+ ]
1728
+ });
1729
+ }
1730
+ //#endregion
1731
+ //#region src/shells/notification-toast.tsx
1732
+ let toastCounter = 0;
1733
+ let toasts = [];
1734
+ const listeners = /* @__PURE__ */ new Set();
1735
+ function emitChange() {
1736
+ for (const l of listeners) l();
1737
+ }
1738
+ const toastStore = {
1739
+ subscribe(listener) {
1740
+ listeners.add(listener);
1741
+ return () => {
1742
+ listeners.delete(listener);
1743
+ };
1744
+ },
1745
+ getSnapshot() {
1746
+ return toasts;
1747
+ },
1748
+ add(input) {
1749
+ var _input$variant;
1750
+ const id = `toast-${++toastCounter}`;
1751
+ toasts = [...toasts, _objectSpread2({
1752
+ id,
1753
+ variant: (_input$variant = input.variant) !== null && _input$variant !== void 0 ? _input$variant : "info"
1754
+ }, input)];
1755
+ emitChange();
1756
+ return id;
1757
+ },
1758
+ dismiss(id) {
1759
+ const next = toasts.filter((t) => t.id !== id);
1760
+ if (next.length !== toasts.length) {
1761
+ toasts = next;
1762
+ emitChange();
1763
+ }
1764
+ },
1765
+ clear() {
1766
+ if (toasts.length) {
1767
+ toasts = [];
1768
+ emitChange();
1769
+ }
1770
+ }
1771
+ };
1772
+ function makeVariant(variant) {
1773
+ return (message, options) => toastStore.add(_objectSpread2(_objectSpread2({}, options), {}, {
1774
+ message,
1775
+ variant
1776
+ }));
1777
+ }
1778
+ /**
1779
+ * Imperative toast API. Call from anywhere (no hook required):
1780
+ * `toast.success("Saved")`, `toast.error("Failed", { title: "Oops" })`,
1781
+ * `toast.dismiss(id)`, `toast.clear()`. Requires a mounted `ToastProvider`.
1782
+ */
1783
+ const toast = Object.assign((options) => toastStore.add(options), {
1784
+ info: makeVariant("info"),
1785
+ success: makeVariant("success"),
1786
+ warning: makeVariant("warning"),
1787
+ error: makeVariant("error"),
1788
+ dismiss: (id) => toastStore.dismiss(id),
1789
+ clear: () => toastStore.clear()
1790
+ });
1791
+ const ToastContext = createContext(null);
1792
+ const positionStyles = {
1793
+ "top-right": "top-4 right-4",
1794
+ "top-left": "top-4 left-4",
1795
+ "bottom-right": "bottom-4 right-4",
1796
+ "bottom-left": "bottom-4 left-4"
1797
+ };
1798
+ /** Renders the toast provider component. */
1799
+ function ToastProvider({ children, position = "top-right", defaultDuration = 5e3, stackLimit = 5 }) {
1800
+ const allToasts = useSyncExternalStore(toastStore.subscribe, toastStore.getSnapshot, toastStore.getSnapshot);
1801
+ const visibleToasts = allToasts.length > stackLimit ? allToasts.slice(-stackLimit) : allToasts;
1802
+ const addToast = useCallback((t) => toastStore.add(t), []);
1803
+ const removeToast = useCallback((id) => toastStore.dismiss(id), []);
1804
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, {
1805
+ value: {
1806
+ toasts: visibleToasts,
1807
+ addToast,
1808
+ removeToast
1809
+ },
1810
+ children: [children, /* @__PURE__ */ jsx("div", {
1811
+ className: cn("gap-element fixed z-50 flex flex-col", positionStyles[position]),
1812
+ role: "region",
1813
+ "aria-label": "Notifications",
1814
+ "data-opt-id": "M0KTS",
1815
+ "data-opt-slug": "notification-toast.ToastProvider",
1816
+ children: visibleToasts.map((t) => /* @__PURE__ */ jsx(NotificationToast, {
1817
+ toast: t,
1818
+ defaultDuration,
1819
+ onDismiss: () => toastStore.dismiss(t.id)
1820
+ }, t.id))
1821
+ })]
1822
+ });
1823
+ }
1824
+ /** Provides toast. */
1825
+ function useToast() {
1826
+ const context = useContext(ToastContext);
1827
+ if (!context) throw new Error("useToast must be used within a ToastProvider");
1828
+ return context;
1829
+ }
1830
+ /** Renders the notification toast component. */
1831
+ function NotificationToast({ toast, onDismiss, defaultDuration = 5e3 }) {
1832
+ var _toast$duration;
1833
+ const duration = (_toast$duration = toast.duration) !== null && _toast$duration !== void 0 ? _toast$duration : defaultDuration;
1834
+ const isUrgent = toast.variant === "error" || toast.variant === "warning";
1835
+ const onDismissRef = useRef(onDismiss);
1836
+ const dismissedRef = useRef(false);
1837
+ const timerStartedAtRef = useRef(null);
1838
+ const remainingDurationRef = useRef(duration);
1839
+ const [paused, setPaused] = useState(false);
1840
+ useEffect(() => {
1841
+ onDismissRef.current = onDismiss;
1842
+ }, [onDismiss]);
1843
+ const dismissOnce = useCallback(() => {
1844
+ if (dismissedRef.current) return;
1845
+ dismissedRef.current = true;
1846
+ onDismissRef.current();
1847
+ }, []);
1848
+ useEffect(() => {
1849
+ dismissedRef.current = false;
1850
+ remainingDurationRef.current = duration;
1851
+ }, [toast.id, duration]);
1852
+ useEffect(() => {
1853
+ if (!duration || duration <= 0 || paused || dismissedRef.current) return;
1854
+ timerStartedAtRef.current = Date.now();
1855
+ const timer = setTimeout(() => {
1856
+ dismissOnce();
1857
+ }, remainingDurationRef.current);
1858
+ return () => {
1859
+ clearTimeout(timer);
1860
+ if (timerStartedAtRef.current !== null) {
1861
+ const elapsed = Date.now() - timerStartedAtRef.current;
1862
+ remainingDurationRef.current = Math.max(0, remainingDurationRef.current - elapsed);
1863
+ timerStartedAtRef.current = null;
1864
+ }
1865
+ };
1866
+ }, [
1867
+ dismissOnce,
1868
+ duration,
1869
+ paused
1870
+ ]);
1871
+ return /* @__PURE__ */ jsx("div", {
1872
+ className: "animate-in slide-in-from-right-full fade-in w-80 duration-200",
1873
+ role: isUrgent ? "alert" : "status",
1874
+ "aria-live": isUrgent ? "assertive" : "polite",
1875
+ "aria-atomic": "true",
1876
+ onMouseEnter: () => setPaused(true),
1877
+ onMouseLeave: () => setPaused(false),
1878
+ onFocus: () => setPaused(true),
1879
+ onBlur: (event) => {
1880
+ if (!event.currentTarget.contains(event.relatedTarget)) setPaused(false);
1881
+ },
1882
+ "data-opt-id": "26YVM",
1883
+ "data-opt-slug": "notification-toast.NotificationToast",
1884
+ children: /* @__PURE__ */ jsxs(Alert, {
1885
+ variant: toast.variant,
1886
+ title: toast.title,
1887
+ dismissible: true,
1888
+ onDismiss: dismissOnce,
1889
+ children: [toast.message, toast.actions && toast.actions.length > 0 && /* @__PURE__ */ jsx("div", {
1890
+ className: "gap-element mt-2 flex",
1891
+ children: toast.actions.map((action) => /* @__PURE__ */ jsx("button", {
1892
+ type: "button",
1893
+ onClick: action.onClick,
1894
+ className: "text-xs font-medium underline hover:no-underline",
1895
+ children: action.label
1896
+ }, action.label))
1897
+ })]
1898
+ })
1899
+ });
1900
+ }
1901
+ //#endregion
1902
+ //#region src/shells/failure-list.tsx
1903
+ const defaultLabels$1 = {
1904
+ title: "최근 실패",
1905
+ emptyMessage: "실패 항목이 없습니다",
1906
+ moreItems: "{count}개 더 보기",
1907
+ severityLabels: {
1908
+ critical: "심각",
1909
+ error: "오류",
1910
+ warning: "경고"
1911
+ },
1912
+ timeAgo: {
1913
+ justNow: "방금 전",
1914
+ minutesAgo: "{minutes}분 전",
1915
+ hoursAgo: "{hours}시간 전",
1916
+ daysAgo: "{days}일 전"
1917
+ }
1918
+ };
1919
+ const severityVariant = {
1920
+ critical: "error",
1921
+ error: "error",
1922
+ warning: "warning"
1923
+ };
1924
+ const severityBorder = {
1925
+ critical: "border-l-2 border-l-danger",
1926
+ error: "border-l-2 border-l-danger",
1927
+ warning: "border-l-2 border-l-warning"
1928
+ };
1929
+ function formatRelativeTime(timestamp, timeLabels, locale) {
1930
+ const now = /* @__PURE__ */ new Date();
1931
+ const date = new Date(timestamp);
1932
+ const diffMs = now.getTime() - date.getTime();
1933
+ const diffMins = Math.floor(diffMs / 6e4);
1934
+ const diffHours = Math.floor(diffMs / 36e5);
1935
+ const diffDays = Math.floor(diffMs / 864e5);
1936
+ if (diffMins < 1) return timeLabels.justNow;
1937
+ if (diffMins < 60) return timeLabels.minutesAgo.replace("{minutes}", String(diffMins));
1938
+ if (diffHours < 24) return timeLabels.hoursAgo.replace("{hours}", String(diffHours));
1939
+ if (diffDays < 7) return timeLabels.daysAgo.replace("{days}", String(diffDays));
1940
+ return date.toLocaleDateString(locale);
1941
+ }
1942
+ function groupBy(items, key) {
1943
+ const map = /* @__PURE__ */ new Map();
1944
+ for (const item of items) {
1945
+ const k = key(item);
1946
+ const existing = map.get(k);
1947
+ if (existing) existing.push(item);
1948
+ else map.set(k, [item]);
1949
+ }
1950
+ return map;
1951
+ }
1952
+ function FailureItemRow({ failure, isLast, onFailureClick, renderActions, mergedSeverityLabels, mergedTimeAgo, locale }) {
1953
+ const itemId = `failure-${failure.id}`;
1954
+ const handleKeyDown = (event) => {
1955
+ if (!onFailureClick) return;
1956
+ if (event.key !== "Enter" && event.key !== " ") return;
1957
+ event.preventDefault();
1958
+ onFailureClick(failure);
1959
+ };
1960
+ return /* @__PURE__ */ jsxs(CompositeItem, {
1961
+ render: /* @__PURE__ */ jsx("article", {}),
1962
+ role: "article",
1963
+ "aria-labelledby": itemId,
1964
+ className: cn("group gap-group relative flex w-full items-start p-3", OPT_FOCUS_VISIBLE, "data-[active-item]:bg-accent-subtle", onFailureClick && "cursor-pointer", !isLast && "border-border-subtle border-b", severityBorder[failure.severity]),
1965
+ onClick: () => onFailureClick === null || onFailureClick === void 0 ? void 0 : onFailureClick(failure),
1966
+ onKeyDown: handleKeyDown,
1967
+ children: [/* @__PURE__ */ jsxs("div", {
1968
+ className: "min-w-0 flex-1",
1969
+ children: [
1970
+ /* @__PURE__ */ jsxs("div", {
1971
+ className: "gap-element flex items-center",
1972
+ children: [/* @__PURE__ */ jsx(Badge, {
1973
+ variant: severityVariant[failure.severity],
1974
+ size: "sm",
1975
+ children: mergedSeverityLabels[failure.severity]
1976
+ }), /* @__PURE__ */ jsx("span", {
1977
+ id: itemId,
1978
+ className: `text-sm font-medium ${OPT_TEXT_PRIMARY} truncate`,
1979
+ children: failure.title
1980
+ })]
1981
+ }),
1982
+ failure.message && /* @__PURE__ */ jsx("p", {
1983
+ className: `mt-1 text-xs text-text-secondary line-clamp-2`,
1984
+ children: failure.message
1985
+ }),
1986
+ /* @__PURE__ */ jsx("time", {
1987
+ dateTime: failure.timestamp,
1988
+ className: `mt-1 block text-xs ${OPT_TEXT_TERTIARY}`,
1989
+ children: formatRelativeTime(failure.timestamp, mergedTimeAgo, locale)
1990
+ })
1991
+ ]
1992
+ }), renderActions && /* @__PURE__ */ jsx("div", {
1993
+ className: "flex shrink-0 items-center gap-1",
1994
+ children: renderActions(failure)
1995
+ })]
1996
+ }, failure.id);
1997
+ }
1998
+ /** Renders the failure list component. */
1999
+ function FailureList({ failures, title, maxItems, grouped = false, onFailureClick, onShowMore, labels: customLabels, locale = "ko-KR", loading = false, renderActions, className }) {
2000
+ 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;
2001
+ const mergedTimeAgo = {
2002
+ 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$1.timeAgo.justNow,
2003
+ 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$1.timeAgo.minutesAgo,
2004
+ 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$1.timeAgo.hoursAgo,
2005
+ 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$1.timeAgo.daysAgo
2006
+ };
2007
+ const mergedSeverityLabels = {
2008
+ 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$1.severityLabels.critical,
2009
+ 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$1.severityLabels.error,
2010
+ 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$1.severityLabels.warning
2011
+ };
2012
+ const labels = {
2013
+ title: (_customLabels$title = customLabels === null || customLabels === void 0 ? void 0 : customLabels.title) !== null && _customLabels$title !== void 0 ? _customLabels$title : defaultLabels$1.title,
2014
+ emptyMessage: (_customLabels$emptyMe = customLabels === null || customLabels === void 0 ? void 0 : customLabels.emptyMessage) !== null && _customLabels$emptyMe !== void 0 ? _customLabels$emptyMe : defaultLabels$1.emptyMessage,
2015
+ moreItems: (_customLabels$moreIte = customLabels === null || customLabels === void 0 ? void 0 : customLabels.moreItems) !== null && _customLabels$moreIte !== void 0 ? _customLabels$moreIte : defaultLabels$1.moreItems
2016
+ };
2017
+ const displayTitle = title !== null && title !== void 0 ? title : labels.title;
2018
+ const allItems = maxItems ? failures.slice(0, maxItems) : failures;
2019
+ const remainingCount = maxItems ? Math.max(0, failures.length - maxItems) : 0;
2020
+ const rowProps = {
2021
+ onFailureClick,
2022
+ renderActions,
2023
+ mergedSeverityLabels,
2024
+ mergedTimeAgo,
2025
+ locale
2026
+ };
2027
+ return /* @__PURE__ */ jsx("div", {
2028
+ className,
2029
+ "data-opt-id": "ZAM3R",
2030
+ "data-opt-slug": "failure-list.FailureList",
2031
+ children: /* @__PURE__ */ jsxs("div", {
2032
+ className: "gap-group flex flex-col",
2033
+ children: [displayTitle && /* @__PURE__ */ jsx("h3", {
2034
+ className: `text-sm font-semibold text-text-primary`,
2035
+ children: displayTitle
2036
+ }), failures.length === 0 ? /* @__PURE__ */ jsx("div", {
2037
+ role: "status",
2038
+ className: `py-8 text-center text-sm ${OPT_TEXT_SECONDARY}`,
2039
+ children: labels.emptyMessage
2040
+ }) : /* @__PURE__ */ jsx(CompositeProvider, {
2041
+ focusLoop: true,
2042
+ orientation: "vertical",
2043
+ children: /* @__PURE__ */ jsx(Composite, {
2044
+ role: "feed",
2045
+ "aria-label": displayTitle,
2046
+ "aria-busy": loading,
2047
+ className: `rounded-lg ${OPT_BORDER} bg-surface`,
2048
+ children: grouped ? renderGrouped(allItems, remainingCount, rowProps, labels, onShowMore) : renderFlat(allItems, remainingCount, rowProps, labels, onShowMore)
2049
+ })
2050
+ })]
2051
+ })
2052
+ });
2053
+ }
2054
+ function renderFlat(items, remainingCount, rowProps, labels, onShowMore) {
2055
+ return /* @__PURE__ */ jsxs(Fragment, { children: [items.map((failure, index) => /* @__PURE__ */ jsx(FailureItemRow, _objectSpread2({
2056
+ failure,
2057
+ isLast: index === items.length - 1 && remainingCount === 0
2058
+ }, rowProps), failure.id)), remainingCount > 0 && /* @__PURE__ */ jsx(CompositeItem, {
2059
+ render: /* @__PURE__ */ jsx("button", { type: "button" }),
2060
+ onClick: onShowMore,
2061
+ 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`,
2062
+ "aria-label": `${remainingCount}개의 실패 더 보기`,
2063
+ children: labels.moreItems.replace("{count}", String(remainingCount))
2064
+ })] });
2065
+ }
2066
+ function renderGrouped(items, remainingCount, rowProps, labels, onShowMore) {
2067
+ const groups = groupBy(items, (f) => {
2068
+ var _f$group;
2069
+ return (_f$group = f.group) !== null && _f$group !== void 0 ? _f$group : "";
2070
+ });
2071
+ const groupEntries = Array.from(groups.entries());
2072
+ return /* @__PURE__ */ jsxs(Fragment, { children: [groupEntries.map(([groupName, groupItems], groupIndex) => {
2073
+ const isLastGroup = groupIndex === groupEntries.length - 1 && remainingCount === 0;
2074
+ return /* @__PURE__ */ jsxs("div", { children: [groupName && /* @__PURE__ */ jsx("div", {
2075
+ className: cn(`px-3 py-2 text-xs font-semibold text-text-tertiary tracking-wider uppercase`, groupIndex > 0 && "border-border-subtle border-t"),
2076
+ children: groupName
2077
+ }), groupItems.map((failure, index) => /* @__PURE__ */ jsx(FailureItemRow, _objectSpread2({
2078
+ failure,
2079
+ isLast: isLastGroup && index === groupItems.length - 1
2080
+ }, rowProps), failure.id))] }, groupName || "__ungrouped__");
2081
+ }), remainingCount > 0 && /* @__PURE__ */ jsx(CompositeItem, {
2082
+ render: /* @__PURE__ */ jsx("button", { type: "button" }),
2083
+ onClick: onShowMore,
2084
+ 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`,
2085
+ "aria-label": `${remainingCount}개의 실패 더 보기`,
2086
+ children: labels.moreItems.replace("{count}", String(remainingCount))
2087
+ })] });
2088
+ }
2089
+ //#endregion
2090
+ //#region src/shells/key-value-editor.tsx
2091
+ const defaultLabels = {
2092
+ keyPlaceholder: "키",
2093
+ valuePlaceholder: "값",
2094
+ addButton: "추가",
2095
+ removeAriaLabel: "항목 삭제",
2096
+ toggleSecretAriaLabel: "비밀값 토글",
2097
+ emptyMessage: "항목이 없습니다. 추가 버튼을 눌러주세요."
2098
+ };
2099
+ let idCounter = 0;
2100
+ function generateId() {
2101
+ return `kv-${++idCounter}`;
2102
+ }
2103
+ /** Renders the key value editor component. */
2104
+ function KeyValueEditor({ value: controlledValue, defaultValue, onChange, showSecretToggle = false, maxEntries, readOnly = false, labels: customLabels, renderActions, validateKey, className }) {
2105
+ const labels = _objectSpread2(_objectSpread2({}, defaultLabels), customLabels);
2106
+ const [internalEntries, setInternalEntries] = useState(() => defaultValue !== null && defaultValue !== void 0 ? defaultValue : []);
2107
+ const entries = controlledValue !== null && controlledValue !== void 0 ? controlledValue : internalEntries;
2108
+ const keyInputRef = useRef(null);
2109
+ const updateEntries = useCallback((next) => {
2110
+ if (!controlledValue) setInternalEntries(next);
2111
+ onChange === null || onChange === void 0 || onChange(next);
2112
+ }, [controlledValue, onChange]);
2113
+ const addEntry = useCallback(() => {
2114
+ if (maxEntries && entries.length >= maxEntries) return;
2115
+ const newEntry = {
2116
+ id: generateId(),
2117
+ key: "",
2118
+ value: "",
2119
+ isSecret: false
2120
+ };
2121
+ updateEntries([...entries, newEntry]);
2122
+ requestAnimationFrame(() => {
2123
+ var _keyInputRef$current;
2124
+ (_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();
2125
+ });
2126
+ }, [
2127
+ entries,
2128
+ maxEntries,
2129
+ updateEntries
2130
+ ]);
2131
+ const removeEntry = useCallback((id) => {
2132
+ updateEntries(entries.filter((e) => e.id !== id));
2133
+ }, [entries, updateEntries]);
2134
+ const updateEntry = useCallback((id, field, val) => {
2135
+ updateEntries(entries.map((e) => e.id === id ? _objectSpread2(_objectSpread2({}, e), {}, { [field]: val }) : e));
2136
+ }, [entries, updateEntries]);
2137
+ const toggleSecret = useCallback((id) => {
2138
+ updateEntries(entries.map((e) => e.id === id ? _objectSpread2(_objectSpread2({}, e), {}, { isSecret: !e.isSecret }) : e));
2139
+ }, [entries, updateEntries]);
2140
+ const canAdd = !readOnly && (!maxEntries || entries.length < maxEntries);
2141
+ return /* @__PURE__ */ jsxs("div", {
2142
+ className: cn("gap-group flex flex-col", className),
2143
+ "data-opt-id": "NCBWA",
2144
+ "data-opt-slug": "key-value-editor.KeyValueEditor",
2145
+ children: [entries.length === 0 ? /* @__PURE__ */ jsx("p", {
2146
+ className: "text-text-tertiary py-4 text-center text-sm",
2147
+ children: labels.emptyMessage
2148
+ }) : /* @__PURE__ */ jsx("div", {
2149
+ className: "gap-element flex flex-col",
2150
+ "data-kv-list": true,
2151
+ children: entries.map((entry, index) => {
2152
+ const keyError = validateKey === null || validateKey === void 0 ? void 0 : validateKey(entry.key, entries);
2153
+ return /* @__PURE__ */ jsxs("div", {
2154
+ "data-kv-id": entry.id,
2155
+ className: cn("gap-element flex items-start", "border-border rounded-lg border p-3", "bg-surface"),
2156
+ children: [
2157
+ /* @__PURE__ */ jsxs("div", {
2158
+ className: "flex min-w-0 flex-1 flex-col gap-1",
2159
+ children: [/* @__PURE__ */ jsx("input", {
2160
+ "data-kv-key": true,
2161
+ ref: index === entries.length - 1 ? keyInputRef : void 0,
2162
+ type: "text",
2163
+ value: entry.key,
2164
+ onChange: (e) => updateEntry(entry.id, "key", e.target.value),
2165
+ placeholder: labels.keyPlaceholder,
2166
+ readOnly,
2167
+ "aria-label": `${labels.keyPlaceholder} ${index + 1}`,
2168
+ className: 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")
2169
+ }), keyError && /* @__PURE__ */ jsx("span", {
2170
+ className: "text-error text-xs",
2171
+ children: keyError
2172
+ })]
2173
+ }),
2174
+ /* @__PURE__ */ jsx("span", {
2175
+ className: "text-text-tertiary px-1 py-1.5",
2176
+ children: "="
2177
+ }),
2178
+ /* @__PURE__ */ jsx("div", {
2179
+ className: "min-w-0 flex-1",
2180
+ children: /* @__PURE__ */ jsx("input", {
2181
+ type: entry.isSecret ? "password" : "text",
2182
+ value: entry.value,
2183
+ onChange: (e) => updateEntry(entry.id, "value", e.target.value),
2184
+ placeholder: labels.valuePlaceholder,
2185
+ readOnly,
2186
+ "aria-label": `${labels.valuePlaceholder} ${index + 1}`,
2187
+ className: 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")
2188
+ })
2189
+ }),
2190
+ showSecretToggle && !readOnly && /* @__PURE__ */ jsx("button", {
2191
+ type: "button",
2192
+ onClick: () => toggleSecret(entry.id),
2193
+ "aria-label": labels.toggleSecretAriaLabel,
2194
+ "aria-pressed": entry.isSecret,
2195
+ className: "text-text-tertiary hover:text-text-primary shrink-0 rounded p-1.5 transition-colors",
2196
+ children: entry.isSecret ? /* @__PURE__ */ jsx("svg", {
2197
+ className: "size-4",
2198
+ fill: "none",
2199
+ viewBox: "0 0 24 24",
2200
+ stroke: "currentColor",
2201
+ strokeWidth: 2,
2202
+ "aria-hidden": "true",
2203
+ children: /* @__PURE__ */ jsx("path", {
2204
+ strokeLinecap: "round",
2205
+ strokeLinejoin: "round",
2206
+ 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"
2207
+ })
2208
+ }) : /* @__PURE__ */ jsxs("svg", {
2209
+ className: "size-4",
2210
+ fill: "none",
2211
+ viewBox: "0 0 24 24",
2212
+ stroke: "currentColor",
2213
+ strokeWidth: 2,
2214
+ "aria-hidden": "true",
2215
+ children: [/* @__PURE__ */ jsx("path", {
2216
+ strokeLinecap: "round",
2217
+ strokeLinejoin: "round",
2218
+ 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"
2219
+ }), /* @__PURE__ */ jsx("path", {
2220
+ strokeLinecap: "round",
2221
+ strokeLinejoin: "round",
2222
+ d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z"
2223
+ })]
2224
+ })
2225
+ }),
2226
+ renderActions === null || renderActions === void 0 ? void 0 : renderActions(entry, index),
2227
+ !readOnly && /* @__PURE__ */ jsx("button", {
2228
+ type: "button",
2229
+ onClick: () => removeEntry(entry.id),
2230
+ "aria-label": labels.removeAriaLabel,
2231
+ className: "text-text-tertiary hover:text-error shrink-0 rounded p-1.5 transition-colors",
2232
+ children: /* @__PURE__ */ jsx("svg", {
2233
+ className: "size-4",
2234
+ fill: "none",
2235
+ viewBox: "0 0 24 24",
2236
+ stroke: "currentColor",
2237
+ strokeWidth: 2,
2238
+ "aria-hidden": "true",
2239
+ children: /* @__PURE__ */ jsx("path", {
2240
+ strokeLinecap: "round",
2241
+ strokeLinejoin: "round",
2242
+ d: "M6 18L18 6M6 6l12 12"
2243
+ })
2244
+ })
2245
+ })
2246
+ ]
2247
+ }, entry.id);
2248
+ })
2249
+ }), canAdd && /* @__PURE__ */ jsx("button", {
2250
+ type: "button",
2251
+ onClick: addEntry,
2252
+ className: 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"),
2253
+ children: labels.addButton
2254
+ })]
2255
+ });
2256
+ }
2257
+ //#endregion
2258
+ //#region src/shells/workflow-canvas.tsx
2259
+ const statusVariant = {
2260
+ idle: "default",
2261
+ running: "info",
2262
+ success: "success",
2263
+ error: "error"
2264
+ };
2265
+ function clamp(value, min, max) {
2266
+ return Math.max(min, Math.min(max, value));
2267
+ }
2268
+ /** Renders the workflow canvas component. */
2269
+ function WorkflowCanvas({ steps, connections = [], rows = 9, cols = 9, cellSize = 72, gap = 12, title = "Workflow Canvas", emptyMessage = "No workflow steps", selectedStepId, onSelectStep, onStepMove, renderStep, className }) {
2270
+ const stepMap = React.useMemo(() => new Map(steps.map((step) => [step.id, step])), [steps]);
2271
+ const width = cols * cellSize + (cols - 1) * gap;
2272
+ const height = rows * cellSize + (rows - 1) * gap;
2273
+ const renderDefaultStep = React.useCallback((step, selected) => /* @__PURE__ */ jsxs("button", {
2274
+ type: "button",
2275
+ onClick: () => onSelectStep === null || onSelectStep === void 0 ? void 0 : onSelectStep(step),
2276
+ className: 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"),
2277
+ children: [/* @__PURE__ */ jsx("span", {
2278
+ className: "text-text-primary truncate text-xs font-medium",
2279
+ children: step.title
2280
+ }), step.status && /* @__PURE__ */ jsx(Badge, {
2281
+ variant: statusVariant[step.status],
2282
+ size: "sm",
2283
+ className: "w-fit",
2284
+ children: step.status
2285
+ })]
2286
+ }), [onSelectStep]);
2287
+ const handleStepKeyDown = React.useCallback((event, step) => {
2288
+ if (!onStepMove) return;
2289
+ let nextRow = step.row;
2290
+ let nextCol = step.col;
2291
+ switch (event.key) {
2292
+ case "ArrowUp":
2293
+ nextRow -= 1;
2294
+ break;
2295
+ case "ArrowDown":
2296
+ nextRow += 1;
2297
+ break;
2298
+ case "ArrowLeft":
2299
+ nextCol -= 1;
2300
+ break;
2301
+ case "ArrowRight":
2302
+ nextCol += 1;
2303
+ break;
2304
+ default: return;
2305
+ }
2306
+ event.preventDefault();
2307
+ onStepMove(step.id, clamp(nextRow, 0, rows - 1), clamp(nextCol, 0, cols - 1));
2308
+ }, [
2309
+ onStepMove,
2310
+ rows,
2311
+ cols
2312
+ ]);
2313
+ return /* @__PURE__ */ jsxs("div", {
2314
+ className: cn("gap-group flex flex-col", className),
2315
+ "data-opt-id": "4TX11",
2316
+ "data-opt-slug": "workflow-canvas.WorkflowCanvas",
2317
+ children: [/* @__PURE__ */ jsx("h3", {
2318
+ className: "text-text-primary text-sm font-semibold",
2319
+ children: title
2320
+ }), steps.length === 0 ? /* @__PURE__ */ jsx("div", {
2321
+ className: "text-text-secondary border-border rounded-lg border border-dashed p-8 text-center text-sm",
2322
+ children: emptyMessage
2323
+ }) : /* @__PURE__ */ jsx("div", {
2324
+ className: "border-border bg-bg-subtle relative overflow-auto rounded-lg border p-3",
2325
+ children: /* @__PURE__ */ jsxs("div", {
2326
+ role: "grid",
2327
+ "aria-label": title,
2328
+ className: "relative",
2329
+ style: {
2330
+ width,
2331
+ height
2332
+ },
2333
+ children: [
2334
+ /* @__PURE__ */ jsx("div", {
2335
+ className: "grid",
2336
+ style: {
2337
+ gridTemplateColumns: `repeat(${cols}, ${cellSize}px)`,
2338
+ gridTemplateRows: `repeat(${rows}, ${cellSize}px)`,
2339
+ gap
2340
+ },
2341
+ children: Array.from({ length: rows * cols }, (_, i) => /* @__PURE__ */ jsx("div", {
2342
+ "aria-hidden": "true",
2343
+ className: "border-border-subtle bg-surface/30 rounded-md border border-dashed"
2344
+ }, i))
2345
+ }),
2346
+ /* @__PURE__ */ jsx("svg", {
2347
+ className: "pointer-events-none absolute inset-0",
2348
+ width,
2349
+ height,
2350
+ "aria-hidden": "true",
2351
+ children: connections.map((connection) => {
2352
+ const from = stepMap.get(connection.from);
2353
+ const to = stepMap.get(connection.to);
2354
+ if (!from || !to) return null;
2355
+ const x1 = from.col * (cellSize + gap) + cellSize / 2;
2356
+ const y1 = from.row * (cellSize + gap) + cellSize / 2;
2357
+ const x2 = to.col * (cellSize + gap) + cellSize / 2;
2358
+ const y2 = to.row * (cellSize + gap) + cellSize / 2;
2359
+ return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
2360
+ x1,
2361
+ y1,
2362
+ x2,
2363
+ y2,
2364
+ stroke: "currentColor",
2365
+ strokeOpacity: "0.35",
2366
+ strokeWidth: "2"
2367
+ }), connection.label && /* @__PURE__ */ jsx("text", {
2368
+ x: (x1 + x2) / 2,
2369
+ y: (y1 + y2) / 2 - 4,
2370
+ textAnchor: "middle",
2371
+ className: "fill-text-secondary text-[10px]",
2372
+ children: connection.label
2373
+ })] }, `${connection.from}-${connection.to}`);
2374
+ })
2375
+ }),
2376
+ steps.map((step) => {
2377
+ const selected = selectedStepId === step.id;
2378
+ return /* @__PURE__ */ jsx("div", {
2379
+ role: "gridcell",
2380
+ tabIndex: 0,
2381
+ "aria-selected": selected,
2382
+ "aria-label": `${step.title} (${step.row + 1}, ${step.col + 1})`,
2383
+ onKeyDown: (event) => handleStepKeyDown(event, step),
2384
+ className: "focus-visible:ring-ring absolute focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none",
2385
+ style: {
2386
+ width: cellSize,
2387
+ height: cellSize,
2388
+ left: step.col * (cellSize + gap),
2389
+ top: step.row * (cellSize + gap)
2390
+ },
2391
+ children: renderStep ? renderStep(step, selected) : renderDefaultStep(step, selected)
2392
+ }, step.id);
2393
+ })
2394
+ ]
2395
+ })
2396
+ })]
2397
+ });
2398
+ }
2399
+ WorkflowCanvas.displayName = "WorkflowCanvas";
2400
+ //#endregion
2401
+ export { CommandItem$1 as A, CommandPaletteTrigger as C, CommandEmpty$1 as D, CommandDialog as E, CommandSeparator$1 as M, CommandShortcut as N, CommandGroup$1 as O, DashboardGrid as S, Command as T, EditorToolbar as _, ToastProvider as a, StatusSelect as b, DataTable as c, EnvPanel as d, ProjectSwitcher as f, ContentTabs as g, FaqAccordion as h, NotificationToast as i, CommandList$1 as j, CommandInput$1 as k, DomainTable as l, SettingsForm as m, KeyValueEditor as n, toast as o, DeploymentTimeline as p, FailureList as r, useToast as s, WorkflowCanvas as t, BranchSelect as u, SidebarNav as v, CommandPalette as w, SearchCombobox as x, AppMenubar as y };
2402
+
2403
+ //# sourceMappingURL=workflow-canvas-BGBAcjf-.js.map