@d34dman/flowdrop 0.0.22 → 0.0.24

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 (44) hide show
  1. package/dist/components/App.svelte +26 -25
  2. package/dist/components/ConfigForm.svelte +141 -520
  3. package/dist/components/ConfigForm.svelte.d.ts +5 -3
  4. package/dist/components/form/FormArray.svelte +1049 -0
  5. package/dist/components/form/FormArray.svelte.d.ts +22 -0
  6. package/dist/components/form/FormCheckboxGroup.svelte +152 -0
  7. package/dist/components/form/FormCheckboxGroup.svelte.d.ts +15 -0
  8. package/dist/components/form/FormField.svelte +297 -0
  9. package/dist/components/form/FormField.svelte.d.ts +18 -0
  10. package/dist/components/form/FormFieldWrapper.svelte +133 -0
  11. package/dist/components/form/FormFieldWrapper.svelte.d.ts +18 -0
  12. package/dist/components/form/FormNumberField.svelte +109 -0
  13. package/dist/components/form/FormNumberField.svelte.d.ts +23 -0
  14. package/dist/components/form/FormRangeField.svelte +252 -0
  15. package/dist/components/form/FormRangeField.svelte.d.ts +21 -0
  16. package/dist/components/form/FormSelect.svelte +126 -0
  17. package/dist/components/form/FormSelect.svelte.d.ts +18 -0
  18. package/dist/components/form/FormTextField.svelte +88 -0
  19. package/dist/components/form/FormTextField.svelte.d.ts +17 -0
  20. package/dist/components/form/FormTextarea.svelte +94 -0
  21. package/dist/components/form/FormTextarea.svelte.d.ts +19 -0
  22. package/dist/components/form/FormToggle.svelte +123 -0
  23. package/dist/components/form/FormToggle.svelte.d.ts +17 -0
  24. package/dist/components/form/index.d.ts +42 -0
  25. package/dist/components/form/index.js +46 -0
  26. package/dist/components/form/types.d.ts +224 -0
  27. package/dist/components/form/types.js +29 -0
  28. package/dist/components/nodes/GatewayNode.svelte +76 -16
  29. package/dist/components/nodes/SimpleNode.svelte +41 -5
  30. package/dist/components/nodes/SimpleNode.svelte.d.ts +2 -1
  31. package/dist/components/nodes/SquareNode.svelte +41 -5
  32. package/dist/components/nodes/SquareNode.svelte.d.ts +2 -1
  33. package/dist/components/nodes/WorkflowNode.svelte +88 -5
  34. package/dist/index.d.ts +2 -3
  35. package/dist/index.js +1 -3
  36. package/dist/stores/workflowStore.d.ts +15 -0
  37. package/dist/stores/workflowStore.js +28 -0
  38. package/dist/types/index.d.ts +176 -1
  39. package/dist/types/index.js +16 -0
  40. package/package.json +3 -3
  41. package/dist/config/demo.d.ts +0 -58
  42. package/dist/config/demo.js +0 -142
  43. package/dist/data/samples.d.ts +0 -51
  44. package/dist/data/samples.js +0 -3245
@@ -4,6 +4,11 @@
4
4
  Supports both node-based config and direct schema/values
5
5
  Uses reactive $state for proper Svelte 5 reactivity
6
6
 
7
+ Features:
8
+ - Dynamic form generation from JSON Schema using modular form components
9
+ - UI Extensions support for display settings (e.g., hide unconnected handles)
10
+ - Extensible architecture for complex schema types (array, object)
11
+
7
12
  Accessibility features:
8
13
  - Proper label associations with for/id attributes
9
14
  - ARIA describedby for field descriptions
@@ -13,7 +18,9 @@
13
18
 
14
19
  <script lang="ts">
15
20
  import Icon from '@iconify/svelte';
16
- import type { ConfigSchema, WorkflowNode } from '../types/index.js';
21
+ import type { ConfigSchema, WorkflowNode, NodeUIExtensions } from '../types/index.js';
22
+ import { FormField, FormFieldWrapper, FormToggle } from './form/index.js';
23
+ import type { FieldSchema } from './form/index.js';
17
24
 
18
25
  interface Props {
19
26
  /** Optional workflow node (if provided, schema and values are derived from it) */
@@ -22,13 +29,15 @@
22
29
  schema?: ConfigSchema;
23
30
  /** Direct config values (used when node is not provided) */
24
31
  values?: Record<string, unknown>;
25
- /** Callback when form is saved */
26
- onSave: (config: Record<string, unknown>) => void;
32
+ /** Whether to show UI extension settings section */
33
+ showUIExtensions?: boolean;
34
+ /** Callback when form is saved (includes both config and extensions if enabled) */
35
+ onSave: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
27
36
  /** Callback when form is cancelled */
28
37
  onCancel: () => void;
29
38
  }
30
39
 
31
- let { node, schema, values, onSave, onCancel }: Props = $props();
40
+ let { node, schema, values, showUIExtensions = true, onSave, onCancel }: Props = $props();
32
41
 
33
42
  /**
34
43
  * Get the configuration schema from node metadata or direct prop
@@ -48,6 +57,23 @@
48
57
  */
49
58
  let configValues = $state<Record<string, unknown>>({});
50
59
 
60
+ /**
61
+ * UI Extension values for display settings
62
+ * Merges node type defaults with instance overrides
63
+ */
64
+ let uiExtensionValues = $state<NodeUIExtensions>({});
65
+
66
+ /**
67
+ * Get initial UI extensions from node (instance level overrides type level)
68
+ */
69
+ const initialUIExtensions = $derived.by<NodeUIExtensions>(() => {
70
+ if (!node) return {};
71
+ // Merge type-level defaults with instance-level overrides
72
+ const typeDefaults = node.data.metadata?.extensions?.ui ?? {};
73
+ const instanceOverrides = node.data.extensions?.ui ?? {};
74
+ return { ...typeDefaults, ...instanceOverrides };
75
+ });
76
+
51
77
  /**
52
78
  * Initialize config values when node/schema changes
53
79
  */
@@ -64,6 +90,15 @@
64
90
  }
65
91
  });
66
92
 
93
+ /**
94
+ * Initialize UI extension values when node changes
95
+ */
96
+ $effect(() => {
97
+ uiExtensionValues = {
98
+ hideUnconnectedHandles: initialUIExtensions.hideUnconnectedHandles ?? false
99
+ };
100
+ });
101
+
67
102
  /**
68
103
  * Check if a field is required based on schema
69
104
  */
@@ -72,8 +107,16 @@
72
107
  return configSchema.required.includes(key);
73
108
  }
74
109
 
110
+ /**
111
+ * Handle field value changes from FormField components
112
+ */
113
+ function handleFieldChange(key: string, value: unknown): void {
114
+ configValues[key] = value;
115
+ }
116
+
75
117
  /**
76
118
  * Handle form submission
119
+ * Collects both config values and UI extension values
77
120
  */
78
121
  function handleSave(): void {
79
122
  // Collect all form values including hidden fields
@@ -84,10 +127,11 @@
84
127
  const inputs = form.querySelectorAll('input, select, textarea');
85
128
  inputs.forEach((input: Element) => {
86
129
  const inputEl = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
87
- if (inputEl.id) {
130
+ // Skip UI extension fields (prefixed with ext-)
131
+ if (inputEl.id && !inputEl.id.startsWith('ext-')) {
88
132
  if (inputEl instanceof HTMLInputElement && inputEl.type === 'checkbox') {
89
133
  updatedConfig[inputEl.id] = inputEl.checked;
90
- } else if (inputEl instanceof HTMLInputElement && inputEl.type === 'number') {
134
+ } else if (inputEl instanceof HTMLInputElement && (inputEl.type === 'number' || inputEl.type === 'range')) {
91
135
  updatedConfig[inputEl.id] = inputEl.value ? Number(inputEl.value) : inputEl.value;
92
136
  } else if (inputEl instanceof HTMLInputElement && inputEl.type === 'hidden') {
93
137
  // Parse hidden field values that might be JSON
@@ -116,7 +160,19 @@
116
160
  );
117
161
  }
118
162
 
119
- onSave(updatedConfig);
163
+ // Pass UI extensions only if enabled
164
+ if (showUIExtensions && node) {
165
+ onSave(updatedConfig, uiExtensionValues);
166
+ } else {
167
+ onSave(updatedConfig);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Convert ConfigProperty to FieldSchema for FormField component
173
+ */
174
+ function toFieldSchema(property: Record<string, unknown>): FieldSchema {
175
+ return property as FieldSchema;
120
176
  }
121
177
  </script>
122
178
 
@@ -131,179 +187,17 @@
131
187
  {#if configSchema.properties}
132
188
  <div class="config-form__fields">
133
189
  {#each Object.entries(configSchema.properties) as [key, field], index (key)}
134
- {@const fieldConfig = field as Record<string, unknown>}
190
+ {@const fieldSchema = toFieldSchema(field as Record<string, unknown>)}
135
191
  {@const required = isFieldRequired(key)}
136
- {@const hasDescription = Boolean(fieldConfig.description)}
137
- {@const descriptionId = hasDescription ? `${key}-description` : undefined}
138
-
139
- {#if fieldConfig.format !== 'hidden'}
140
- <div class="config-form__field" style="animation-delay: {index * 30}ms">
141
- <!-- Field Label -->
142
- <label class="config-form__label" for={key}>
143
- <span class="config-form__label-text">
144
- {String(fieldConfig.title || fieldConfig.description || key)}
145
- </span>
146
- {#if required}
147
- <span class="config-form__required" aria-label="required">*</span>
148
- {/if}
149
- </label>
150
-
151
- <!-- Field Input Container -->
152
- <div class="config-form__input-wrapper">
153
- {#if fieldConfig.enum && fieldConfig.multiple}
154
- <!-- Checkboxes for enum with multiple selection -->
155
- {@const enumOptions = fieldConfig.enum as string[]}
156
- <div
157
- class="config-form__checkbox-group"
158
- role="group"
159
- aria-labelledby="{key}-label"
160
- aria-describedby={descriptionId}
161
- >
162
- {#each enumOptions as option (String(option))}
163
- {@const currentValue = configValues[key]}
164
- {@const valueArray = Array.isArray(currentValue) ? currentValue : []}
165
- <label class="config-form__checkbox-item">
166
- <input
167
- type="checkbox"
168
- class="config-form__checkbox-input"
169
- value={String(option)}
170
- checked={valueArray.includes(String(option))}
171
- onchange={(e) => {
172
- const checked = e.currentTarget.checked;
173
- const existingValue = configValues[key];
174
- const currentValues: unknown[] = Array.isArray(existingValue)
175
- ? [...existingValue]
176
- : [];
177
- if (checked) {
178
- if (!currentValues.includes(String(option))) {
179
- configValues[key] = [...currentValues, String(option)];
180
- }
181
- } else {
182
- configValues[key] = currentValues.filter((v) => v !== String(option));
183
- }
184
- }}
185
- />
186
- <span class="config-form__checkbox-custom" aria-hidden="true">
187
- <Icon icon="heroicons:check" />
188
- </span>
189
- <span class="config-form__checkbox-label">
190
- {String(option)}
191
- </span>
192
- </label>
193
- {/each}
194
- </div>
195
- {:else if fieldConfig.enum}
196
- <!-- Select for enum with single selection -->
197
- {@const enumOptions = fieldConfig.enum as string[]}
198
- <div class="config-form__select-wrapper">
199
- <select
200
- id={key}
201
- class="config-form__select"
202
- bind:value={configValues[key]}
203
- aria-describedby={descriptionId}
204
- aria-required={required}
205
- >
206
- {#each enumOptions as option (String(option))}
207
- <option value={String(option)}>{String(option)}</option>
208
- {/each}
209
- </select>
210
- <span class="config-form__select-icon" aria-hidden="true">
211
- <Icon icon="heroicons:chevron-down" />
212
- </span>
213
- </div>
214
- {:else if fieldConfig.type === 'string' && fieldConfig.format === 'multiline'}
215
- <!-- Textarea for multiline strings -->
216
- <textarea
217
- id={key}
218
- class="config-form__textarea"
219
- bind:value={configValues[key]}
220
- placeholder={String(fieldConfig.placeholder || '')}
221
- rows="4"
222
- aria-describedby={descriptionId}
223
- aria-required={required}
224
- ></textarea>
225
- {:else if fieldConfig.type === 'string'}
226
- <input
227
- id={key}
228
- type="text"
229
- class="config-form__input"
230
- bind:value={configValues[key]}
231
- placeholder={String(fieldConfig.placeholder || '')}
232
- aria-describedby={descriptionId}
233
- aria-required={required}
234
- />
235
- {:else if fieldConfig.type === 'number'}
236
- <input
237
- id={key}
238
- type="number"
239
- class="config-form__input config-form__input--number"
240
- bind:value={configValues[key]}
241
- placeholder={String(fieldConfig.placeholder || '')}
242
- aria-describedby={descriptionId}
243
- aria-required={required}
244
- />
245
- {:else if fieldConfig.type === 'boolean'}
246
- <!-- Toggle Switch for boolean -->
247
- <label class="config-form__toggle">
248
- <input
249
- id={key}
250
- type="checkbox"
251
- class="config-form__toggle-input"
252
- checked={Boolean(configValues[key] || fieldConfig.default || false)}
253
- onchange={(e) => {
254
- configValues[key] = e.currentTarget.checked;
255
- }}
256
- aria-describedby={descriptionId}
257
- />
258
- <span class="config-form__toggle-track">
259
- <span class="config-form__toggle-thumb"></span>
260
- </span>
261
- <span class="config-form__toggle-label">
262
- {configValues[key] ? 'Enabled' : 'Disabled'}
263
- </span>
264
- </label>
265
- {:else if fieldConfig.type === 'select' || fieldConfig.options}
266
- {@const selectOptions = (fieldConfig.options ?? []) as Array<{
267
- value: unknown;
268
- label: unknown;
269
- }>}
270
- <div class="config-form__select-wrapper">
271
- <select
272
- id={key}
273
- class="config-form__select"
274
- bind:value={configValues[key]}
275
- aria-describedby={descriptionId}
276
- aria-required={required}
277
- >
278
- {#each selectOptions as option (String(option.value))}
279
- <option value={String(option.value)}>{String(option.label)}</option>
280
- {/each}
281
- </select>
282
- <span class="config-form__select-icon" aria-hidden="true">
283
- <Icon icon="heroicons:chevron-down" />
284
- </span>
285
- </div>
286
- {:else}
287
- <!-- Fallback for unknown field types -->
288
- <input
289
- id={key}
290
- type="text"
291
- class="config-form__input"
292
- bind:value={configValues[key]}
293
- placeholder={String(fieldConfig.placeholder || '')}
294
- aria-describedby={descriptionId}
295
- />
296
- {/if}
297
- </div>
298
-
299
- <!-- Field Description -->
300
- {#if hasDescription && fieldConfig.title}
301
- <p id={descriptionId} class="config-form__description">
302
- {String(fieldConfig.description)}
303
- </p>
304
- {/if}
305
- </div>
306
- {/if}
192
+
193
+ <FormField
194
+ fieldKey={key}
195
+ schema={fieldSchema}
196
+ value={configValues[key]}
197
+ {required}
198
+ animationIndex={index}
199
+ onChange={(val) => handleFieldChange(key, val)}
200
+ />
307
201
  {/each}
308
202
  </div>
309
203
  {:else}
@@ -317,6 +211,35 @@
317
211
  </div>
318
212
  {/if}
319
213
 
214
+ <!-- UI Extensions Section -->
215
+ {#if showUIExtensions && node}
216
+ <div class="config-form__extensions">
217
+ <div class="config-form__extensions-header">
218
+ <Icon icon="heroicons:adjustments-horizontal" class="config-form__extensions-icon" />
219
+ <span>Display Settings</span>
220
+ </div>
221
+ <div class="config-form__extensions-content">
222
+ <!-- Hide Unconnected Handles Toggle -->
223
+ <FormFieldWrapper
224
+ id="ext-hideUnconnectedHandles"
225
+ label="Hide Unconnected Ports"
226
+ description="Hide input and output ports that are not connected to reduce visual clutter"
227
+ >
228
+ <FormToggle
229
+ id="ext-hideUnconnectedHandles"
230
+ value={Boolean(uiExtensionValues.hideUnconnectedHandles)}
231
+ onLabel="Hidden"
232
+ offLabel="Visible"
233
+ ariaDescribedBy="ext-hideUnconnectedHandles-description"
234
+ onChange={(val) => {
235
+ uiExtensionValues.hideUnconnectedHandles = val;
236
+ }}
237
+ />
238
+ </FormFieldWrapper>
239
+ </div>
240
+ </div>
241
+ {/if}
242
+
320
243
  <!-- Footer Actions -->
321
244
  <div class="config-form__footer">
322
245
  <button
@@ -344,7 +267,8 @@
344
267
 
345
268
  <style>
346
269
  /* ============================================
347
- CONFIG FORM - Modern, Accessible Design
270
+ CONFIG FORM - Container Styles
271
+ Individual field styles are in form/ components
348
272
  ============================================ */
349
273
 
350
274
  .config-form {
@@ -359,346 +283,6 @@
359
283
  gap: 1.25rem;
360
284
  }
361
285
 
362
- /* ============================================
363
- FIELD CONTAINER
364
- ============================================ */
365
-
366
- .config-form__field {
367
- display: flex;
368
- flex-direction: column;
369
- gap: 0.5rem;
370
- animation: fieldFadeIn 0.3s ease-out forwards;
371
- opacity: 0;
372
- transform: translateY(4px);
373
- }
374
-
375
- @keyframes fieldFadeIn {
376
- to {
377
- opacity: 1;
378
- transform: translateY(0);
379
- }
380
- }
381
-
382
- /* ============================================
383
- LABELS
384
- ============================================ */
385
-
386
- .config-form__label {
387
- display: flex;
388
- align-items: center;
389
- gap: 0.25rem;
390
- font-size: 0.8125rem;
391
- font-weight: 600;
392
- color: var(--color-ref-gray-700, #374151);
393
- letter-spacing: -0.01em;
394
- }
395
-
396
- .config-form__label-text {
397
- line-height: 1.4;
398
- }
399
-
400
- .config-form__required {
401
- color: var(--color-ref-red-500, #ef4444);
402
- font-weight: 500;
403
- }
404
-
405
- /* ============================================
406
- INPUT WRAPPER
407
- ============================================ */
408
-
409
- .config-form__input-wrapper {
410
- position: relative;
411
- }
412
-
413
- /* ============================================
414
- TEXT INPUTS
415
- ============================================ */
416
-
417
- .config-form__input {
418
- width: 100%;
419
- padding: 0.625rem 0.875rem;
420
- border: 1px solid var(--color-ref-gray-200, #e5e7eb);
421
- border-radius: 0.5rem;
422
- font-size: 0.875rem;
423
- font-family: inherit;
424
- color: var(--color-ref-gray-900, #111827);
425
- background-color: var(--color-ref-gray-50, #f9fafb);
426
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
427
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
428
- }
429
-
430
- .config-form__input::placeholder {
431
- color: var(--color-ref-gray-400, #9ca3af);
432
- }
433
-
434
- .config-form__input:hover {
435
- border-color: var(--color-ref-gray-300, #d1d5db);
436
- background-color: #ffffff;
437
- }
438
-
439
- .config-form__input:focus {
440
- outline: none;
441
- border-color: var(--color-ref-blue-500, #3b82f6);
442
- background-color: #ffffff;
443
- box-shadow:
444
- 0 0 0 3px rgba(59, 130, 246, 0.12),
445
- 0 1px 2px rgba(0, 0, 0, 0.04);
446
- }
447
-
448
- .config-form__input--number {
449
- font-variant-numeric: tabular-nums;
450
- }
451
-
452
- /* ============================================
453
- TEXTAREA
454
- ============================================ */
455
-
456
- .config-form__textarea {
457
- width: 100%;
458
- padding: 0.625rem 0.875rem;
459
- border: 1px solid var(--color-ref-gray-200, #e5e7eb);
460
- border-radius: 0.5rem;
461
- font-size: 0.875rem;
462
- font-family: inherit;
463
- color: var(--color-ref-gray-900, #111827);
464
- background-color: var(--color-ref-gray-50, #f9fafb);
465
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
466
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
467
- resize: vertical;
468
- min-height: 5rem;
469
- line-height: 1.5;
470
- }
471
-
472
- .config-form__textarea::placeholder {
473
- color: var(--color-ref-gray-400, #9ca3af);
474
- }
475
-
476
- .config-form__textarea:hover {
477
- border-color: var(--color-ref-gray-300, #d1d5db);
478
- background-color: #ffffff;
479
- }
480
-
481
- .config-form__textarea:focus {
482
- outline: none;
483
- border-color: var(--color-ref-blue-500, #3b82f6);
484
- background-color: #ffffff;
485
- box-shadow:
486
- 0 0 0 3px rgba(59, 130, 246, 0.12),
487
- 0 1px 2px rgba(0, 0, 0, 0.04);
488
- }
489
-
490
- /* ============================================
491
- SELECT DROPDOWN
492
- ============================================ */
493
-
494
- .config-form__select-wrapper {
495
- position: relative;
496
- }
497
-
498
- .config-form__select {
499
- width: 100%;
500
- padding: 0.625rem 2.5rem 0.625rem 0.875rem;
501
- border: 1px solid var(--color-ref-gray-200, #e5e7eb);
502
- border-radius: 0.5rem;
503
- font-size: 0.875rem;
504
- font-family: inherit;
505
- color: var(--color-ref-gray-900, #111827);
506
- background-color: var(--color-ref-gray-50, #f9fafb);
507
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
508
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
509
- cursor: pointer;
510
- appearance: none;
511
- }
512
-
513
- .config-form__select:hover {
514
- border-color: var(--color-ref-gray-300, #d1d5db);
515
- background-color: #ffffff;
516
- }
517
-
518
- .config-form__select:focus {
519
- outline: none;
520
- border-color: var(--color-ref-blue-500, #3b82f6);
521
- background-color: #ffffff;
522
- box-shadow:
523
- 0 0 0 3px rgba(59, 130, 246, 0.12),
524
- 0 1px 2px rgba(0, 0, 0, 0.04);
525
- }
526
-
527
- .config-form__select-icon {
528
- position: absolute;
529
- right: 0.75rem;
530
- top: 50%;
531
- transform: translateY(-50%);
532
- pointer-events: none;
533
- color: var(--color-ref-gray-400, #9ca3af);
534
- display: flex;
535
- align-items: center;
536
- transition: color 0.2s;
537
- }
538
-
539
- .config-form__select-icon :global(svg) {
540
- width: 1rem;
541
- height: 1rem;
542
- }
543
-
544
- .config-form__select:focus + .config-form__select-icon {
545
- color: var(--color-ref-blue-500, #3b82f6);
546
- }
547
-
548
- /* ============================================
549
- CHECKBOX GROUP
550
- ============================================ */
551
-
552
- .config-form__checkbox-group {
553
- display: flex;
554
- flex-direction: column;
555
- gap: 0.625rem;
556
- padding: 0.75rem;
557
- background-color: var(--color-ref-gray-50, #f9fafb);
558
- border: 1px solid var(--color-ref-gray-200, #e5e7eb);
559
- border-radius: 0.5rem;
560
- }
561
-
562
- .config-form__checkbox-item {
563
- display: flex;
564
- align-items: center;
565
- gap: 0.625rem;
566
- cursor: pointer;
567
- padding: 0.375rem;
568
- margin: -0.375rem;
569
- border-radius: 0.375rem;
570
- transition: background-color 0.15s;
571
- }
572
-
573
- .config-form__checkbox-item:hover {
574
- background-color: var(--color-ref-gray-100, #f3f4f6);
575
- }
576
-
577
- .config-form__checkbox-input {
578
- position: absolute;
579
- opacity: 0;
580
- width: 0;
581
- height: 0;
582
- }
583
-
584
- .config-form__checkbox-custom {
585
- width: 1.125rem;
586
- height: 1.125rem;
587
- border: 1.5px solid var(--color-ref-gray-300, #d1d5db);
588
- border-radius: 0.25rem;
589
- background-color: #ffffff;
590
- display: flex;
591
- align-items: center;
592
- justify-content: center;
593
- transition: all 0.15s;
594
- flex-shrink: 0;
595
- }
596
-
597
- .config-form__checkbox-custom :global(svg) {
598
- width: 0.75rem;
599
- height: 0.75rem;
600
- color: #ffffff;
601
- opacity: 0;
602
- transform: scale(0.5);
603
- transition: all 0.15s;
604
- }
605
-
606
- .config-form__checkbox-input:checked + .config-form__checkbox-custom {
607
- background-color: var(--color-ref-blue-500, #3b82f6);
608
- border-color: var(--color-ref-blue-500, #3b82f6);
609
- }
610
-
611
- .config-form__checkbox-input:checked + .config-form__checkbox-custom :global(svg) {
612
- opacity: 1;
613
- transform: scale(1);
614
- }
615
-
616
- .config-form__checkbox-input:focus-visible + .config-form__checkbox-custom {
617
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
618
- }
619
-
620
- .config-form__checkbox-label {
621
- font-size: 0.875rem;
622
- color: var(--color-ref-gray-700, #374151);
623
- line-height: 1.4;
624
- }
625
-
626
- /* ============================================
627
- TOGGLE SWITCH (Boolean)
628
- ============================================ */
629
-
630
- .config-form__toggle {
631
- display: flex;
632
- align-items: center;
633
- gap: 0.75rem;
634
- cursor: pointer;
635
- padding: 0.5rem 0;
636
- }
637
-
638
- .config-form__toggle-input {
639
- position: absolute;
640
- opacity: 0;
641
- width: 0;
642
- height: 0;
643
- }
644
-
645
- .config-form__toggle-track {
646
- position: relative;
647
- width: 2.75rem;
648
- height: 1.5rem;
649
- background-color: var(--color-ref-gray-300, #d1d5db);
650
- border-radius: 0.75rem;
651
- transition: background-color 0.2s;
652
- flex-shrink: 0;
653
- }
654
-
655
- .config-form__toggle-thumb {
656
- position: absolute;
657
- top: 0.125rem;
658
- left: 0.125rem;
659
- width: 1.25rem;
660
- height: 1.25rem;
661
- background-color: #ffffff;
662
- border-radius: 50%;
663
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
664
- transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
665
- }
666
-
667
- .config-form__toggle-input:checked + .config-form__toggle-track {
668
- background-color: var(--color-ref-blue-500, #3b82f6);
669
- }
670
-
671
- .config-form__toggle-input:checked + .config-form__toggle-track .config-form__toggle-thumb {
672
- transform: translateX(1.25rem);
673
- }
674
-
675
- .config-form__toggle-input:focus-visible + .config-form__toggle-track {
676
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
677
- }
678
-
679
- .config-form__toggle-label {
680
- font-size: 0.875rem;
681
- color: var(--color-ref-gray-600, #4b5563);
682
- font-weight: 500;
683
- min-width: 4.5rem;
684
- }
685
-
686
- .config-form__toggle-input:checked ~ .config-form__toggle-label {
687
- color: var(--color-ref-blue-600, #2563eb);
688
- }
689
-
690
- /* ============================================
691
- FIELD DESCRIPTION
692
- ============================================ */
693
-
694
- .config-form__description {
695
- margin: 0;
696
- font-size: 0.75rem;
697
- color: var(--color-ref-gray-500, #6b7280);
698
- line-height: 1.5;
699
- padding-left: 0.125rem;
700
- }
701
-
702
286
  /* ============================================
703
287
  FOOTER ACTIONS
704
288
  ============================================ */
@@ -787,6 +371,43 @@
787
371
  0 4px 12px rgba(59, 130, 246, 0.35);
788
372
  }
789
373
 
374
+ /* ============================================
375
+ UI EXTENSIONS SECTION
376
+ ============================================ */
377
+
378
+ .config-form__extensions {
379
+ background-color: var(--color-ref-slate-50, #f8fafc);
380
+ border: 1px solid var(--color-ref-slate-200, #e2e8f0);
381
+ border-radius: 0.5rem;
382
+ overflow: hidden;
383
+ margin-top: 0.5rem;
384
+ }
385
+
386
+ .config-form__extensions-header {
387
+ display: flex;
388
+ align-items: center;
389
+ gap: 0.5rem;
390
+ padding: 0.75rem 1rem;
391
+ background-color: var(--color-ref-slate-100, #f1f5f9);
392
+ border-bottom: 1px solid var(--color-ref-slate-200, #e2e8f0);
393
+ font-size: 0.8125rem;
394
+ font-weight: 600;
395
+ color: var(--color-ref-slate-700, #334155);
396
+ }
397
+
398
+ .config-form__extensions-header :global(svg) {
399
+ width: 1rem;
400
+ height: 1rem;
401
+ color: var(--color-ref-slate-500, #64748b);
402
+ }
403
+
404
+ .config-form__extensions-content {
405
+ padding: 1rem;
406
+ display: flex;
407
+ flex-direction: column;
408
+ gap: 1rem;
409
+ }
410
+
790
411
  /* ============================================
791
412
  DEBUG SECTION
792
413
  ============================================ */