@orchestra-mcp/settings 1.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,903 @@
1
+ // src/SettingsForm/SettingField.tsx
2
+ import { useState, useCallback, useRef, useEffect } from "react";
3
+
4
+ // src/SettingsForm/timezones.ts
5
+ var TIMEZONE_OPTIONS = [
6
+ { label: "UTC", value: "UTC", description: "Coordinated Universal Time" },
7
+ { label: "US/Eastern (ET)", value: "America/New_York", description: "UTC-5 / UTC-4 DST" },
8
+ { label: "US/Central (CT)", value: "America/Chicago", description: "UTC-6 / UTC-5 DST" },
9
+ { label: "US/Mountain (MT)", value: "America/Denver", description: "UTC-7 / UTC-6 DST" },
10
+ { label: "US/Pacific (PT)", value: "America/Los_Angeles", description: "UTC-8 / UTC-7 DST" },
11
+ { label: "US/Alaska", value: "America/Anchorage", description: "UTC-9 / UTC-8 DST" },
12
+ { label: "US/Hawaii", value: "Pacific/Honolulu", description: "UTC-10" },
13
+ { label: "Canada/Atlantic", value: "America/Halifax", description: "UTC-4 / UTC-3 DST" },
14
+ { label: "Canada/Newfoundland", value: "America/St_Johns", description: "UTC-3:30" },
15
+ { label: "Mexico City", value: "America/Mexico_City", description: "UTC-6 / UTC-5 DST" },
16
+ { label: "Sao Paulo", value: "America/Sao_Paulo", description: "UTC-3" },
17
+ { label: "Buenos Aires", value: "America/Argentina/Buenos_Aires", description: "UTC-3" },
18
+ { label: "London (GMT/BST)", value: "Europe/London", description: "UTC+0 / UTC+1 DST" },
19
+ { label: "Paris (CET)", value: "Europe/Paris", description: "UTC+1 / UTC+2 DST" },
20
+ { label: "Berlin (CET)", value: "Europe/Berlin", description: "UTC+1 / UTC+2 DST" },
21
+ { label: "Amsterdam (CET)", value: "Europe/Amsterdam", description: "UTC+1 / UTC+2 DST" },
22
+ { label: "Rome (CET)", value: "Europe/Rome", description: "UTC+1 / UTC+2 DST" },
23
+ { label: "Madrid (CET)", value: "Europe/Madrid", description: "UTC+1 / UTC+2 DST" },
24
+ { label: "Athens (EET)", value: "Europe/Athens", description: "UTC+2 / UTC+3 DST" },
25
+ { label: "Istanbul", value: "Europe/Istanbul", description: "UTC+3" },
26
+ { label: "Moscow", value: "Europe/Moscow", description: "UTC+3" },
27
+ { label: "Dubai", value: "Asia/Dubai", description: "UTC+4" },
28
+ { label: "Kolkata (IST)", value: "Asia/Kolkata", description: "UTC+5:30" },
29
+ { label: "Bangkok (ICT)", value: "Asia/Bangkok", description: "UTC+7" },
30
+ { label: "Singapore (SGT)", value: "Asia/Singapore", description: "UTC+8" },
31
+ { label: "Hong Kong (HKT)", value: "Asia/Hong_Kong", description: "UTC+8" },
32
+ { label: "Shanghai (CST)", value: "Asia/Shanghai", description: "UTC+8" },
33
+ { label: "Taipei (CST)", value: "Asia/Taipei", description: "UTC+8" },
34
+ { label: "Seoul (KST)", value: "Asia/Seoul", description: "UTC+9" },
35
+ { label: "Tokyo (JST)", value: "Asia/Tokyo", description: "UTC+9" },
36
+ { label: "Sydney (AEST)", value: "Australia/Sydney", description: "UTC+10 / UTC+11 DST" },
37
+ { label: "Melbourne (AEST)", value: "Australia/Melbourne", description: "UTC+10 / UTC+11 DST" },
38
+ { label: "Auckland (NZST)", value: "Pacific/Auckland", description: "UTC+12 / UTC+13 DST" },
39
+ { label: "Cairo (EET)", value: "Africa/Cairo", description: "UTC+2" },
40
+ { label: "Lagos (WAT)", value: "Africa/Lagos", description: "UTC+1" },
41
+ { label: "Nairobi (EAT)", value: "Africa/Nairobi", description: "UTC+3" },
42
+ { label: "Johannesburg (SAST)", value: "Africa/Johannesburg", description: "UTC+2" }
43
+ ];
44
+
45
+ // src/SettingsForm/SettingField.tsx
46
+ import { jsx, jsxs } from "react/jsx-runtime";
47
+ var SettingField = ({
48
+ setting,
49
+ value,
50
+ onChange,
51
+ disabled
52
+ }) => {
53
+ const isDisabled = disabled || setting.disabled;
54
+ const handleChange = useCallback(
55
+ (newValue) => {
56
+ onChange(setting.key, newValue);
57
+ },
58
+ [onChange, setting.key]
59
+ );
60
+ if (setting.hidden) {
61
+ return null;
62
+ }
63
+ if (setting.type === "boolean") {
64
+ return /* @__PURE__ */ jsx(
65
+ BooleanField,
66
+ {
67
+ setting,
68
+ value,
69
+ onChange: handleChange,
70
+ disabled: isDisabled
71
+ }
72
+ );
73
+ }
74
+ return /* @__PURE__ */ jsxs("div", { className: "setting-field", children: [
75
+ /* @__PURE__ */ jsxs("label", { className: "setting-field__label", children: [
76
+ setting.label,
77
+ setting.validation?.required && /* @__PURE__ */ jsx("span", { className: "setting-field__required", children: "*" })
78
+ ] }),
79
+ /* @__PURE__ */ jsx(
80
+ FieldInput,
81
+ {
82
+ setting,
83
+ value,
84
+ onChange: handleChange,
85
+ disabled: isDisabled
86
+ }
87
+ ),
88
+ setting.description && /* @__PURE__ */ jsx("p", { className: "setting-field__description", children: setting.description })
89
+ ] });
90
+ };
91
+ var FieldInput = ({ setting, value, onChange, disabled }) => {
92
+ switch (setting.type) {
93
+ case "string":
94
+ return /* @__PURE__ */ jsx(
95
+ StringField,
96
+ {
97
+ setting,
98
+ value,
99
+ onChange,
100
+ disabled
101
+ }
102
+ );
103
+ case "number":
104
+ return /* @__PURE__ */ jsx(
105
+ NumberField,
106
+ {
107
+ setting,
108
+ value,
109
+ onChange,
110
+ disabled
111
+ }
112
+ );
113
+ case "select":
114
+ return /* @__PURE__ */ jsx(
115
+ SelectField,
116
+ {
117
+ setting,
118
+ value,
119
+ onChange,
120
+ disabled
121
+ }
122
+ );
123
+ case "multi-select":
124
+ return /* @__PURE__ */ jsx(
125
+ MultiSelectField,
126
+ {
127
+ setting,
128
+ value,
129
+ onChange,
130
+ disabled
131
+ }
132
+ );
133
+ case "color":
134
+ return /* @__PURE__ */ jsx(
135
+ ColorField,
136
+ {
137
+ setting,
138
+ value,
139
+ onChange,
140
+ disabled
141
+ }
142
+ );
143
+ case "range":
144
+ return /* @__PURE__ */ jsx(
145
+ RangeField,
146
+ {
147
+ setting,
148
+ value,
149
+ onChange,
150
+ disabled
151
+ }
152
+ );
153
+ case "date":
154
+ return /* @__PURE__ */ jsx(
155
+ DateField,
156
+ {
157
+ setting,
158
+ value,
159
+ onChange,
160
+ disabled
161
+ }
162
+ );
163
+ case "timezone":
164
+ return /* @__PURE__ */ jsx(
165
+ TimezoneField,
166
+ {
167
+ setting,
168
+ value,
169
+ onChange,
170
+ disabled
171
+ }
172
+ );
173
+ case "key-value":
174
+ return /* @__PURE__ */ jsx(
175
+ KeyValueField,
176
+ {
177
+ setting,
178
+ value,
179
+ onChange,
180
+ disabled
181
+ }
182
+ );
183
+ case "repeater":
184
+ return /* @__PURE__ */ jsx(
185
+ RepeaterField,
186
+ {
187
+ setting,
188
+ value,
189
+ onChange,
190
+ disabled
191
+ }
192
+ );
193
+ case "code":
194
+ return /* @__PURE__ */ jsx(
195
+ CodeField,
196
+ {
197
+ setting,
198
+ value,
199
+ onChange,
200
+ disabled
201
+ }
202
+ );
203
+ case "markdown":
204
+ return /* @__PURE__ */ jsx(
205
+ MarkdownSettingField,
206
+ {
207
+ setting,
208
+ value,
209
+ onChange,
210
+ disabled
211
+ }
212
+ );
213
+ default:
214
+ return null;
215
+ }
216
+ };
217
+ var StringField = ({ setting, value, onChange, disabled }) => /* @__PURE__ */ jsx(
218
+ "input",
219
+ {
220
+ type: "text",
221
+ className: "setting-field__input",
222
+ value: value ?? "",
223
+ placeholder: setting.placeholder,
224
+ disabled,
225
+ minLength: setting.validation?.minLength,
226
+ maxLength: setting.validation?.maxLength,
227
+ pattern: setting.validation?.pattern,
228
+ onChange: (e) => onChange(e.target.value)
229
+ }
230
+ );
231
+ var NumberField = ({ setting, value, onChange, disabled }) => /* @__PURE__ */ jsx(
232
+ "input",
233
+ {
234
+ type: "number",
235
+ className: "setting-field__input",
236
+ value: value ?? "",
237
+ placeholder: setting.placeholder,
238
+ disabled,
239
+ min: setting.validation?.min,
240
+ max: setting.validation?.max,
241
+ step: setting.validation?.step,
242
+ onChange: (e) => onChange(Number(e.target.value))
243
+ }
244
+ );
245
+ var BooleanField = ({ setting, value, onChange, disabled }) => {
246
+ const isOn = Boolean(value);
247
+ return /* @__PURE__ */ jsx("div", { className: "setting-field", children: /* @__PURE__ */ jsxs("div", { className: "setting-field__toggle-row", children: [
248
+ /* @__PURE__ */ jsxs("div", { className: "setting-field__toggle-text", children: [
249
+ /* @__PURE__ */ jsxs("span", { className: "setting-field__toggle-label", children: [
250
+ setting.label,
251
+ setting.validation?.required && /* @__PURE__ */ jsx("span", { className: "setting-field__required", children: "*" })
252
+ ] }),
253
+ setting.description && /* @__PURE__ */ jsx("p", { className: "setting-field__toggle-desc", children: setting.description })
254
+ ] }),
255
+ /* @__PURE__ */ jsx(
256
+ "button",
257
+ {
258
+ type: "button",
259
+ className: `setting-field__toggle ${isOn ? "setting-field__toggle--on" : "setting-field__toggle--off"}`,
260
+ disabled,
261
+ onClick: () => onChange(!isOn),
262
+ "aria-label": `Toggle ${setting.label}`,
263
+ children: /* @__PURE__ */ jsx(
264
+ "span",
265
+ {
266
+ className: `setting-field__toggle-knob ${isOn ? "setting-field__toggle-knob--on" : "setting-field__toggle-knob--off"}`
267
+ }
268
+ )
269
+ }
270
+ )
271
+ ] }) });
272
+ };
273
+ var SelectField = ({ setting, value, onChange, disabled }) => {
274
+ const [open, setOpen] = useState(false);
275
+ const [search, setSearch] = useState("");
276
+ const wrapperRef = useRef(null);
277
+ const inputRef = useRef(null);
278
+ const options = setting.options ?? [];
279
+ const selected = options.find((o) => String(o.value) === String(value));
280
+ const filtered = search ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : options;
281
+ useEffect(() => {
282
+ if (!open) return;
283
+ const handler = (e) => {
284
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
285
+ setOpen(false);
286
+ setSearch("");
287
+ }
288
+ };
289
+ document.addEventListener("mousedown", handler);
290
+ return () => document.removeEventListener("mousedown", handler);
291
+ }, [open]);
292
+ const handleSelect = (optValue) => {
293
+ onChange(optValue);
294
+ setOpen(false);
295
+ setSearch("");
296
+ };
297
+ const handleKeyDown = (e) => {
298
+ if (e.key === "Escape") {
299
+ setOpen(false);
300
+ setSearch("");
301
+ }
302
+ };
303
+ return /* @__PURE__ */ jsxs("div", { className: "setting-field__select-wrapper", ref: wrapperRef, children: [
304
+ /* @__PURE__ */ jsxs(
305
+ "button",
306
+ {
307
+ type: "button",
308
+ className: `setting-field__select-trigger ${open ? "setting-field__select-trigger--open" : ""}`,
309
+ disabled,
310
+ onClick: () => {
311
+ setOpen(!open);
312
+ if (!open) setTimeout(() => inputRef.current?.focus(), 0);
313
+ },
314
+ "aria-haspopup": "listbox",
315
+ "aria-expanded": open,
316
+ children: [
317
+ /* @__PURE__ */ jsx("span", { className: selected ? "" : "setting-field__select-placeholder", children: selected?.label || setting.placeholder || "Select..." }),
318
+ /* @__PURE__ */ jsx(SelectChevron, {})
319
+ ]
320
+ }
321
+ ),
322
+ open && /* @__PURE__ */ jsxs("div", { className: "setting-field__select-dropdown", role: "listbox", onKeyDown: handleKeyDown, children: [
323
+ /* @__PURE__ */ jsx("div", { className: "setting-field__select-search-box", children: /* @__PURE__ */ jsx(
324
+ "input",
325
+ {
326
+ ref: inputRef,
327
+ type: "text",
328
+ className: "setting-field__select-search",
329
+ placeholder: "Search...",
330
+ value: search,
331
+ onChange: (e) => setSearch(e.target.value)
332
+ }
333
+ ) }),
334
+ /* @__PURE__ */ jsxs("div", { className: "setting-field__select-options", children: [
335
+ filtered.length === 0 && /* @__PURE__ */ jsx("div", { className: "setting-field__select-empty", children: "No results" }),
336
+ filtered.map((opt) => /* @__PURE__ */ jsxs(
337
+ "button",
338
+ {
339
+ type: "button",
340
+ className: `setting-field__select-option ${String(opt.value) === String(value) ? "setting-field__select-option--active" : ""}`,
341
+ onClick: () => handleSelect(opt.value),
342
+ role: "option",
343
+ "aria-selected": String(opt.value) === String(value),
344
+ children: [
345
+ opt.label,
346
+ opt.description && /* @__PURE__ */ jsx("span", { className: "setting-field__select-option-desc", children: opt.description })
347
+ ]
348
+ },
349
+ String(opt.value)
350
+ ))
351
+ ] })
352
+ ] })
353
+ ] });
354
+ };
355
+ var SelectChevron = () => /* @__PURE__ */ jsx("svg", { className: "setting-field__select-chevron", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) });
356
+ var MultiSelectField = ({
357
+ setting,
358
+ value,
359
+ onChange,
360
+ disabled
361
+ }) => {
362
+ const selected = value ?? [];
363
+ const toggle = (optValue) => {
364
+ const next = selected.includes(optValue) ? selected.filter((v) => v !== optValue) : [...selected, optValue];
365
+ onChange(next);
366
+ };
367
+ return /* @__PURE__ */ jsx("div", { className: "setting-field__checkbox-group", children: setting.options?.map((opt) => {
368
+ const optStr = String(opt.value);
369
+ return /* @__PURE__ */ jsxs(
370
+ "label",
371
+ {
372
+ className: `setting-field__checkbox-card${disabled ? " setting-field__checkbox-card--disabled" : ""}${selected.includes(optStr) ? " setting-field__checkbox-card--checked" : ""}`,
373
+ children: [
374
+ /* @__PURE__ */ jsx(
375
+ "input",
376
+ {
377
+ type: "checkbox",
378
+ className: "setting-field__checkbox",
379
+ checked: selected.includes(optStr),
380
+ disabled,
381
+ onChange: () => toggle(optStr)
382
+ }
383
+ ),
384
+ opt.icon && /* @__PURE__ */ jsx("span", { className: "setting-field__checkbox-icon", children: opt.icon }),
385
+ /* @__PURE__ */ jsxs("span", { className: "setting-field__checkbox-text", children: [
386
+ /* @__PURE__ */ jsx("span", { className: "setting-field__checkbox-title", children: opt.label }),
387
+ opt.description && /* @__PURE__ */ jsx("span", { className: "setting-field__checkbox-desc", children: opt.description })
388
+ ] })
389
+ ]
390
+ },
391
+ optStr
392
+ );
393
+ }) });
394
+ };
395
+ var ColorField = ({ setting, value, onChange, disabled }) => {
396
+ const swatches = setting.options ?? [];
397
+ const isGradient = (v) => v.includes("gradient") || v.includes(",");
398
+ return /* @__PURE__ */ jsxs("div", { className: "setting-field__color-wrapper", children: [
399
+ /* @__PURE__ */ jsxs("div", { className: "setting-field__color-row", children: [
400
+ /* @__PURE__ */ jsx(
401
+ "input",
402
+ {
403
+ type: "color",
404
+ className: "setting-field__color-picker",
405
+ value: isGradient(value || "") ? "#000000" : value || "#000000",
406
+ disabled,
407
+ onChange: (e) => onChange(e.target.value)
408
+ }
409
+ ),
410
+ /* @__PURE__ */ jsx(
411
+ "input",
412
+ {
413
+ type: "text",
414
+ className: "setting-field__input setting-field__color-text",
415
+ value: value ?? "",
416
+ placeholder: setting.placeholder || "#000000",
417
+ disabled,
418
+ onChange: (e) => onChange(e.target.value)
419
+ }
420
+ )
421
+ ] }),
422
+ swatches.length > 0 && /* @__PURE__ */ jsx("div", { className: "setting-field__color-swatches", children: swatches.map((swatch) => {
423
+ const swatchVal = String(swatch.value);
424
+ const active = swatchVal === value;
425
+ const gradient = isGradient(swatchVal);
426
+ return /* @__PURE__ */ jsx(
427
+ "button",
428
+ {
429
+ type: "button",
430
+ className: `setting-field__color-swatch${active ? " setting-field__color-swatch--active" : ""}`,
431
+ style: {
432
+ background: gradient ? swatchVal : swatchVal
433
+ },
434
+ disabled,
435
+ onClick: () => onChange(swatchVal),
436
+ "aria-label": swatch.label,
437
+ title: swatch.label
438
+ },
439
+ swatchVal
440
+ );
441
+ }) })
442
+ ] });
443
+ };
444
+ var RangeField = ({ setting, value, onChange, disabled }) => /* @__PURE__ */ jsxs("div", { className: "setting-field__range-row", children: [
445
+ /* @__PURE__ */ jsx(
446
+ "input",
447
+ {
448
+ type: "range",
449
+ className: "setting-field__range",
450
+ value: value ?? setting.validation?.min ?? 0,
451
+ min: setting.validation?.min ?? 0,
452
+ max: setting.validation?.max ?? 100,
453
+ step: setting.validation?.step ?? 1,
454
+ disabled,
455
+ onChange: (e) => onChange(Number(e.target.value))
456
+ }
457
+ ),
458
+ /* @__PURE__ */ jsx("span", { className: "setting-field__range-value", children: value ?? setting.validation?.min ?? 0 })
459
+ ] });
460
+ var DateField = ({ setting, value, onChange, disabled }) => /* @__PURE__ */ jsx(
461
+ "input",
462
+ {
463
+ type: "date",
464
+ className: "setting-field__input setting-field__date",
465
+ value: value ?? "",
466
+ disabled,
467
+ onChange: (e) => onChange(e.target.value)
468
+ }
469
+ );
470
+ var TimezoneField = ({ setting, value, onChange, disabled }) => {
471
+ const tzSetting = {
472
+ ...setting,
473
+ type: "select",
474
+ options: TIMEZONE_OPTIONS,
475
+ placeholder: setting.placeholder || "Select timezone..."
476
+ };
477
+ return /* @__PURE__ */ jsx(
478
+ SelectField,
479
+ {
480
+ setting: tzSetting,
481
+ value,
482
+ onChange: (v) => onChange(String(v)),
483
+ disabled
484
+ }
485
+ );
486
+ };
487
+ var KeyValueField = ({ setting, value, onChange, disabled }) => {
488
+ const pairs = value ?? {};
489
+ const entries = Object.entries(pairs);
490
+ const updateKey = (oldKey, newKey) => {
491
+ const next = {};
492
+ for (const [k, v] of entries) {
493
+ next[k === oldKey ? newKey : k] = v;
494
+ }
495
+ onChange(next);
496
+ };
497
+ const updateValue = (key, newVal) => {
498
+ onChange({ ...pairs, [key]: newVal });
499
+ };
500
+ const addPair = () => {
501
+ onChange({ ...pairs, "": "" });
502
+ };
503
+ const removePair = (key) => {
504
+ const next = { ...pairs };
505
+ delete next[key];
506
+ onChange(next);
507
+ };
508
+ return /* @__PURE__ */ jsxs("div", { className: "setting-field__kv", children: [
509
+ entries.map(([k, v], i) => /* @__PURE__ */ jsxs("div", { className: "setting-field__kv-row", children: [
510
+ /* @__PURE__ */ jsx(
511
+ "input",
512
+ {
513
+ type: "text",
514
+ className: "setting-field__input setting-field__kv-key",
515
+ value: k,
516
+ placeholder: "Key",
517
+ disabled,
518
+ onChange: (e) => updateKey(k, e.target.value)
519
+ }
520
+ ),
521
+ /* @__PURE__ */ jsx(
522
+ "input",
523
+ {
524
+ type: "text",
525
+ className: "setting-field__input setting-field__kv-value",
526
+ value: v,
527
+ placeholder: "Value",
528
+ disabled,
529
+ onChange: (e) => updateValue(k, e.target.value)
530
+ }
531
+ ),
532
+ /* @__PURE__ */ jsx(
533
+ "button",
534
+ {
535
+ type: "button",
536
+ className: "setting-field__kv-remove",
537
+ disabled,
538
+ onClick: () => removePair(k),
539
+ "aria-label": "Remove pair",
540
+ children: /* @__PURE__ */ jsx(RemoveIcon, {})
541
+ }
542
+ )
543
+ ] }, i)),
544
+ /* @__PURE__ */ jsxs(
545
+ "button",
546
+ {
547
+ type: "button",
548
+ className: "setting-field__kv-add",
549
+ disabled,
550
+ onClick: addPair,
551
+ children: [
552
+ /* @__PURE__ */ jsx(AddIcon, {}),
553
+ " Add pair"
554
+ ]
555
+ }
556
+ )
557
+ ] });
558
+ };
559
+ var RepeaterField = ({ setting, value, onChange, disabled }) => {
560
+ const rows = value ?? [];
561
+ const columns = setting.repeaterColumns ?? [{ key: "value", label: "Value" }];
562
+ const addRow = () => {
563
+ const newRow = { id: crypto.randomUUID() };
564
+ for (const col of columns) newRow[col.key] = "";
565
+ onChange([...rows, newRow]);
566
+ };
567
+ const removeRow = (id) => {
568
+ onChange(rows.filter((r) => r.id !== id));
569
+ };
570
+ const updateCell = (id, colKey, val) => {
571
+ onChange(rows.map((r) => r.id === id ? { ...r, [colKey]: val } : r));
572
+ };
573
+ return /* @__PURE__ */ jsxs("div", { className: "setting-field__repeater", children: [
574
+ columns.length > 1 && /* @__PURE__ */ jsxs("div", { className: "setting-field__repeater-header", children: [
575
+ columns.map((col) => /* @__PURE__ */ jsx("span", { className: "setting-field__repeater-col-label", children: col.label }, col.key)),
576
+ /* @__PURE__ */ jsx("span", { className: "setting-field__repeater-col-action" })
577
+ ] }),
578
+ rows.map((row) => /* @__PURE__ */ jsxs("div", { className: "setting-field__repeater-row", children: [
579
+ columns.map((col) => /* @__PURE__ */ jsx(
580
+ "input",
581
+ {
582
+ type: "text",
583
+ className: "setting-field__input setting-field__repeater-cell",
584
+ value: row[col.key] ?? "",
585
+ placeholder: col.placeholder || col.label,
586
+ disabled,
587
+ onChange: (e) => updateCell(row.id, col.key, e.target.value)
588
+ },
589
+ col.key
590
+ )),
591
+ /* @__PURE__ */ jsx(
592
+ "button",
593
+ {
594
+ type: "button",
595
+ className: "setting-field__repeater-remove",
596
+ disabled,
597
+ onClick: () => removeRow(row.id),
598
+ "aria-label": "Remove row",
599
+ children: /* @__PURE__ */ jsx(RemoveIcon, {})
600
+ }
601
+ )
602
+ ] }, row.id)),
603
+ /* @__PURE__ */ jsxs(
604
+ "button",
605
+ {
606
+ type: "button",
607
+ className: "setting-field__repeater-add",
608
+ disabled,
609
+ onClick: addRow,
610
+ children: [
611
+ /* @__PURE__ */ jsx(AddIcon, {}),
612
+ " Add row"
613
+ ]
614
+ }
615
+ )
616
+ ] });
617
+ };
618
+ var CodeField = ({ setting, value, onChange, disabled }) => /* @__PURE__ */ jsx(
619
+ "textarea",
620
+ {
621
+ className: "setting-field__input setting-field__code",
622
+ value: value ?? "",
623
+ placeholder: setting.placeholder,
624
+ disabled,
625
+ rows: 8,
626
+ spellCheck: false,
627
+ onChange: (e) => onChange(e.target.value)
628
+ }
629
+ );
630
+ var MarkdownSettingField = ({ setting, value, onChange, disabled }) => {
631
+ return /* @__PURE__ */ jsx(
632
+ "textarea",
633
+ {
634
+ className: "setting-field__input setting-field__code",
635
+ value: value ?? "",
636
+ placeholder: setting.placeholder || "Write markdown...",
637
+ disabled,
638
+ rows: 8,
639
+ onChange: (e) => onChange(e.target.value)
640
+ }
641
+ );
642
+ };
643
+ var AddIcon = () => /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M12 5v14M5 12h14" }) });
644
+ var RemoveIcon = () => /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
645
+
646
+ // src/SettingsForm/SettingsGroup.tsx
647
+ import { useState as useState2, useMemo } from "react";
648
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
649
+ var SettingsGroup = ({
650
+ group,
651
+ settings,
652
+ values,
653
+ onChange,
654
+ disabled
655
+ }) => {
656
+ const [collapsed, setCollapsed] = useState2(group.collapsed ?? false);
657
+ const sortedSettings = useMemo(
658
+ () => [...settings].sort((a, b) => a.order - b.order),
659
+ [settings]
660
+ );
661
+ const groupClasses = [
662
+ "settings-group",
663
+ collapsed ? "settings-group--collapsed" : ""
664
+ ].filter(Boolean).join(" ");
665
+ const headerClasses = [
666
+ "settings-group__header",
667
+ group.collapsible ? "settings-group__header--collapsible" : ""
668
+ ].filter(Boolean).join(" ");
669
+ const handleHeaderClick = () => {
670
+ if (group.collapsible) {
671
+ setCollapsed((prev) => !prev);
672
+ }
673
+ };
674
+ return /* @__PURE__ */ jsxs2("div", { className: groupClasses, children: [
675
+ /* @__PURE__ */ jsxs2(
676
+ "div",
677
+ {
678
+ className: headerClasses,
679
+ onClick: handleHeaderClick,
680
+ role: group.collapsible ? "button" : void 0,
681
+ tabIndex: group.collapsible ? 0 : void 0,
682
+ onKeyDown: group.collapsible ? (e) => {
683
+ if (e.key === "Enter" || e.key === " ") {
684
+ e.preventDefault();
685
+ handleHeaderClick();
686
+ }
687
+ } : void 0,
688
+ children: [
689
+ group.icon && /* @__PURE__ */ jsx2("span", { className: "settings-group__icon", children: group.icon }),
690
+ /* @__PURE__ */ jsxs2("div", { className: "settings-group__header-text", children: [
691
+ /* @__PURE__ */ jsx2("h3", { className: "settings-group__title", children: group.label }),
692
+ group.description && /* @__PURE__ */ jsx2("p", { className: "settings-group__description", children: group.description })
693
+ ] }),
694
+ group.collapsible && /* @__PURE__ */ jsx2(ChevronIcon, { collapsed })
695
+ ]
696
+ }
697
+ ),
698
+ /* @__PURE__ */ jsx2("div", { className: "settings-group__body", children: sortedSettings.map((setting) => /* @__PURE__ */ jsx2(
699
+ SettingField,
700
+ {
701
+ setting,
702
+ value: values[setting.key] ?? setting.default,
703
+ onChange,
704
+ disabled
705
+ },
706
+ setting.key
707
+ )) })
708
+ ] });
709
+ };
710
+ function ChevronIcon({ collapsed }) {
711
+ return /* @__PURE__ */ jsx2(
712
+ "svg",
713
+ {
714
+ className: `settings-group__chevron ${collapsed ? "settings-group__chevron--collapsed" : ""}`,
715
+ width: "16",
716
+ height: "16",
717
+ viewBox: "0 0 24 24",
718
+ fill: "none",
719
+ stroke: "currentColor",
720
+ strokeWidth: 2,
721
+ strokeLinecap: "round",
722
+ strokeLinejoin: "round",
723
+ children: /* @__PURE__ */ jsx2("path", { d: "m6 9 6 6 6-6" })
724
+ }
725
+ );
726
+ }
727
+
728
+ // src/SettingsForm/SettingGroupShell.tsx
729
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
730
+ var SettingGroupShell = ({ group, children }) => {
731
+ return /* @__PURE__ */ jsxs3("div", { className: "settings-group", children: [
732
+ /* @__PURE__ */ jsxs3("div", { className: "settings-group__header", children: [
733
+ group.icon && /* @__PURE__ */ jsx3("span", { className: "settings-group__icon", children: group.icon }),
734
+ /* @__PURE__ */ jsxs3("div", { className: "settings-group__header-text", children: [
735
+ /* @__PURE__ */ jsx3("h3", { className: "settings-group__title", children: group.label }),
736
+ group.description && /* @__PURE__ */ jsx3("p", { className: "settings-group__description", children: group.description })
737
+ ] })
738
+ ] }),
739
+ /* @__PURE__ */ jsx3("div", { className: "settings-group__body", children })
740
+ ] });
741
+ };
742
+
743
+ // src/SettingsForm/SettingsForm.tsx
744
+ import { useMemo as useMemo2 } from "react";
745
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
746
+ var SettingsForm = ({
747
+ state,
748
+ values,
749
+ onChange,
750
+ activeGroup,
751
+ disabled,
752
+ className,
753
+ emptyState
754
+ }) => {
755
+ const visibleGroups = useMemo2(() => {
756
+ const sorted = [...state.groups].sort((a, b) => a.order - b.order);
757
+ if (activeGroup) {
758
+ return sorted.filter((g) => g.id === activeGroup);
759
+ }
760
+ return sorted;
761
+ }, [state.groups, activeGroup]);
762
+ const settingsByGroup = useMemo2(() => {
763
+ const map = {};
764
+ for (const setting of state.settings) {
765
+ if (setting.hidden) continue;
766
+ if (!map[setting.group]) {
767
+ map[setting.group] = [];
768
+ }
769
+ map[setting.group].push(setting);
770
+ }
771
+ return map;
772
+ }, [state.settings]);
773
+ const formClasses = ["settings-form", className].filter(Boolean).join(" ");
774
+ const hasVisibleSettings = visibleGroups.some(
775
+ (g) => settingsByGroup[g.id] && settingsByGroup[g.id].length > 0
776
+ );
777
+ if (!hasVisibleSettings) {
778
+ return /* @__PURE__ */ jsx4("div", { className: formClasses, children: emptyState ?? /* @__PURE__ */ jsxs4("div", { className: "settings-form__empty", children: [
779
+ /* @__PURE__ */ jsxs4("svg", { className: "settings-form__empty-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
780
+ /* @__PURE__ */ jsx4("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }),
781
+ /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "3" })
782
+ ] }),
783
+ /* @__PURE__ */ jsx4("p", { className: "settings-form__empty-title", children: "No settings available" }),
784
+ /* @__PURE__ */ jsx4("p", { className: "settings-form__empty-desc", children: "There are no configurable settings for this section." })
785
+ ] }) });
786
+ }
787
+ return /* @__PURE__ */ jsx4("div", { className: formClasses, children: visibleGroups.map((group) => {
788
+ const groupSettings = settingsByGroup[group.id];
789
+ if (!groupSettings || groupSettings.length === 0) return null;
790
+ return /* @__PURE__ */ jsx4(
791
+ SettingsGroup,
792
+ {
793
+ group,
794
+ settings: groupSettings,
795
+ values,
796
+ onChange,
797
+ disabled
798
+ },
799
+ group.id
800
+ );
801
+ }) });
802
+ };
803
+
804
+ // src/SettingsNav/SettingsNav.tsx
805
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
806
+ var SettingsNav = ({
807
+ title = "Settings",
808
+ subtitle = "Customize your workspace",
809
+ items,
810
+ activeId,
811
+ searchQuery = "",
812
+ onSearchChange,
813
+ onSelect,
814
+ showSearch = true
815
+ }) => {
816
+ return /* @__PURE__ */ jsxs5("div", { className: "settings-nav", children: [
817
+ /* @__PURE__ */ jsxs5("div", { className: "settings-nav__header", children: [
818
+ /* @__PURE__ */ jsx5("h2", { className: "settings-nav__title", children: title }),
819
+ subtitle && /* @__PURE__ */ jsx5("p", { className: "settings-nav__subtitle", children: subtitle })
820
+ ] }),
821
+ showSearch && /* @__PURE__ */ jsxs5("div", { className: "settings-nav__search", children: [
822
+ /* @__PURE__ */ jsx5(SearchIcon, {}),
823
+ /* @__PURE__ */ jsx5(
824
+ "input",
825
+ {
826
+ type: "text",
827
+ placeholder: "Search settings...",
828
+ value: searchQuery,
829
+ onChange: (e) => onSearchChange?.(e.target.value),
830
+ className: "settings-nav__search-input"
831
+ }
832
+ )
833
+ ] }),
834
+ /* @__PURE__ */ jsx5("nav", { className: "settings-nav__list", children: items.map((item) => /* @__PURE__ */ jsxs5(
835
+ "button",
836
+ {
837
+ onClick: () => onSelect(item.id),
838
+ className: `settings-nav__item ${activeId === item.id ? "settings-nav__item--active" : ""}`,
839
+ children: [
840
+ item.icon && /* @__PURE__ */ jsx5("span", { className: "settings-nav__icon", children: item.icon }),
841
+ /* @__PURE__ */ jsx5("span", { children: item.label })
842
+ ]
843
+ },
844
+ item.id
845
+ )) })
846
+ ] });
847
+ };
848
+ function SearchIcon() {
849
+ return /* @__PURE__ */ jsxs5(
850
+ "svg",
851
+ {
852
+ className: "settings-nav__search-icon",
853
+ width: "16",
854
+ height: "16",
855
+ viewBox: "0 0 24 24",
856
+ fill: "none",
857
+ stroke: "currentColor",
858
+ strokeWidth: 2,
859
+ strokeLinecap: "round",
860
+ strokeLinejoin: "round",
861
+ children: [
862
+ /* @__PURE__ */ jsx5("circle", { cx: "11", cy: "11", r: "8" }),
863
+ /* @__PURE__ */ jsx5("path", { d: "m21 21-4.3-4.3" })
864
+ ]
865
+ }
866
+ );
867
+ }
868
+
869
+ // src/validate.ts
870
+ function validateSetting(value, validation) {
871
+ if (!validation) return null;
872
+ if (validation.required && (value === void 0 || value === null || value === "")) {
873
+ return validation.message ?? "This field is required";
874
+ }
875
+ if (typeof value === "string") {
876
+ if (validation.min !== void 0 && value.length < validation.min) {
877
+ return validation.message ?? `Must be at least ${validation.min} characters`;
878
+ }
879
+ if (validation.max !== void 0 && value.length > validation.max) {
880
+ return validation.message ?? `Must be at most ${validation.max} characters`;
881
+ }
882
+ if (validation.pattern && !new RegExp(validation.pattern).test(value)) {
883
+ return validation.message ?? "Invalid format";
884
+ }
885
+ }
886
+ if (typeof value === "number") {
887
+ if (validation.min !== void 0 && value < validation.min) {
888
+ return validation.message ?? `Must be at least ${validation.min}`;
889
+ }
890
+ if (validation.max !== void 0 && value > validation.max) {
891
+ return validation.message ?? `Must be at most ${validation.max}`;
892
+ }
893
+ }
894
+ return null;
895
+ }
896
+ export {
897
+ SettingField,
898
+ SettingGroupShell,
899
+ SettingsForm,
900
+ SettingsGroup,
901
+ SettingsNav,
902
+ validateSetting
903
+ };