@d34dman/flowdrop 0.0.20 → 0.0.22

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 (37) hide show
  1. package/README.md +8 -0
  2. package/dist/components/App.svelte +172 -417
  3. package/dist/components/ConfigForm.svelte +707 -241
  4. package/dist/components/ConfigForm.svelte.d.ts +9 -2
  5. package/dist/components/ConfigPanel.svelte +160 -0
  6. package/dist/components/ConfigPanel.svelte.d.ts +32 -0
  7. package/dist/components/Navbar.svelte +2 -1
  8. package/dist/components/NodeStatusOverlay.svelte +1 -0
  9. package/dist/components/NodeStatusOverlay.svelte.d.ts +1 -0
  10. package/dist/components/ReadOnlyDetails.svelte +168 -0
  11. package/dist/components/ReadOnlyDetails.svelte.d.ts +25 -0
  12. package/dist/components/WorkflowEditor.svelte +34 -26
  13. package/dist/components/layouts/MainLayout.svelte +513 -0
  14. package/dist/components/layouts/MainLayout.svelte.d.ts +50 -0
  15. package/dist/components/{GatewayNode.svelte → nodes/GatewayNode.svelte} +3 -3
  16. package/dist/components/{GatewayNode.svelte.d.ts → nodes/GatewayNode.svelte.d.ts} +1 -1
  17. package/dist/components/nodes/NotesNode.svelte +348 -0
  18. package/dist/components/nodes/NotesNode.svelte.d.ts +24 -0
  19. package/dist/components/{SimpleNode.svelte → nodes/SimpleNode.svelte} +2 -2
  20. package/dist/components/{SimpleNode.svelte.d.ts → nodes/SimpleNode.svelte.d.ts} +1 -1
  21. package/dist/components/{SquareNode.svelte → nodes/SquareNode.svelte} +2 -2
  22. package/dist/components/{SquareNode.svelte.d.ts → nodes/SquareNode.svelte.d.ts} +1 -1
  23. package/dist/components/{TerminalNode.svelte → nodes/TerminalNode.svelte} +2 -2
  24. package/dist/components/{TerminalNode.svelte.d.ts → nodes/TerminalNode.svelte.d.ts} +1 -1
  25. package/dist/components/{ToolNode.svelte → nodes/ToolNode.svelte} +2 -2
  26. package/dist/components/{ToolNode.svelte.d.ts → nodes/ToolNode.svelte.d.ts} +1 -1
  27. package/dist/components/{WorkflowNode.svelte → nodes/WorkflowNode.svelte} +3 -3
  28. package/dist/components/{WorkflowNode.svelte.d.ts → nodes/WorkflowNode.svelte.d.ts} +1 -1
  29. package/dist/index.d.ts +8 -7
  30. package/dist/index.js +8 -7
  31. package/dist/registry/builtinNodes.js +7 -7
  32. package/dist/styles/base.css +15 -1
  33. package/package.json +1 -1
  34. package/dist/components/ConfigSidebar.svelte +0 -934
  35. package/dist/components/ConfigSidebar.svelte.d.ts +0 -51
  36. package/dist/components/NotesNode.svelte +0 -566
  37. package/dist/components/NotesNode.svelte.d.ts +0 -43
@@ -1,29 +1,46 @@
1
1
  <!--
2
2
  ConfigForm Component
3
- Handles dynamic form rendering for node configuration
3
+ Handles dynamic form rendering for node or entity configuration
4
+ Supports both node-based config and direct schema/values
4
5
  Uses reactive $state for proper Svelte 5 reactivity
6
+
7
+ Accessibility features:
8
+ - Proper label associations with for/id attributes
9
+ - ARIA describedby for field descriptions
10
+ - Focus-visible states for keyboard navigation
11
+ - Required field indicators
5
12
  -->
6
13
 
7
14
  <script lang="ts">
15
+ import Icon from '@iconify/svelte';
8
16
  import type { ConfigSchema, WorkflowNode } from '../types/index.js';
9
17
 
10
18
  interface Props {
11
- node: WorkflowNode;
19
+ /** Optional workflow node (if provided, schema and values are derived from it) */
20
+ node?: WorkflowNode;
21
+ /** Direct config schema (used when node is not provided) */
22
+ schema?: ConfigSchema;
23
+ /** Direct config values (used when node is not provided) */
24
+ values?: Record<string, unknown>;
25
+ /** Callback when form is saved */
12
26
  onSave: (config: Record<string, unknown>) => void;
27
+ /** Callback when form is cancelled */
13
28
  onCancel: () => void;
14
29
  }
15
30
 
16
- let { node, onSave, onCancel }: Props = $props();
31
+ let { node, schema, values, onSave, onCancel }: Props = $props();
17
32
 
18
33
  /**
19
- * Get the configuration schema from node metadata
34
+ * Get the configuration schema from node metadata or direct prop
20
35
  */
21
- const configSchema = $derived(node.data.metadata?.configSchema as ConfigSchema | undefined);
36
+ const configSchema = $derived(
37
+ schema ?? (node?.data.metadata?.configSchema as ConfigSchema | undefined)
38
+ );
22
39
 
23
40
  /**
24
- * Get the current node configuration
41
+ * Get the current configuration from node or direct prop
25
42
  */
26
- const nodeConfig = $derived(node.data.config || {});
43
+ const initialConfig = $derived(values ?? node?.data.config ?? {});
27
44
 
28
45
  /**
29
46
  * Create reactive configuration values using $state
@@ -32,59 +49,71 @@
32
49
  let configValues = $state<Record<string, unknown>>({});
33
50
 
34
51
  /**
35
- * Initialize config values when node or schema changes
52
+ * Initialize config values when node/schema changes
36
53
  */
37
54
  $effect(() => {
38
55
  if (configSchema?.properties) {
39
56
  const mergedConfig: Record<string, unknown> = {};
40
57
  Object.entries(configSchema.properties).forEach(([key, field]) => {
41
- const fieldConfig = field as any;
58
+ const fieldConfig = field as Record<string, unknown>;
42
59
  // Use existing value if available, otherwise use default
43
- mergedConfig[key] = nodeConfig[key] !== undefined ? nodeConfig[key] : fieldConfig.default;
60
+ mergedConfig[key] =
61
+ initialConfig[key] !== undefined ? initialConfig[key] : fieldConfig.default;
44
62
  });
45
63
  configValues = mergedConfig;
46
64
  }
47
65
  });
48
66
 
67
+ /**
68
+ * Check if a field is required based on schema
69
+ */
70
+ function isFieldRequired(key: string): boolean {
71
+ if (!configSchema?.required) return false;
72
+ return configSchema.required.includes(key);
73
+ }
74
+
49
75
  /**
50
76
  * Handle form submission
51
77
  */
52
78
  function handleSave(): void {
53
79
  // Collect all form values including hidden fields
54
- const form = document.querySelector('.flowdrop-config-sidebar__form');
80
+ const form = document.querySelector('.config-form');
55
81
  const updatedConfig: Record<string, unknown> = { ...configValues };
56
82
 
57
83
  if (form) {
58
84
  const inputs = form.querySelectorAll('input, select, textarea');
59
- inputs.forEach((input: any) => {
60
- if (input.id) {
61
- if (input.type === 'checkbox') {
62
- updatedConfig[input.id] = input.checked;
63
- } else if (input.type === 'number') {
64
- updatedConfig[input.id] = input.value ? Number(input.value) : input.value;
65
- } else if (input.type === 'hidden') {
85
+ inputs.forEach((input: Element) => {
86
+ const inputEl = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
87
+ if (inputEl.id) {
88
+ if (inputEl instanceof HTMLInputElement && inputEl.type === 'checkbox') {
89
+ updatedConfig[inputEl.id] = inputEl.checked;
90
+ } else if (inputEl instanceof HTMLInputElement && inputEl.type === 'number') {
91
+ updatedConfig[inputEl.id] = inputEl.value ? Number(inputEl.value) : inputEl.value;
92
+ } else if (inputEl instanceof HTMLInputElement && inputEl.type === 'hidden') {
66
93
  // Parse hidden field values that might be JSON
67
94
  try {
68
- const parsed = JSON.parse(input.value);
69
- updatedConfig[input.id] = parsed;
95
+ const parsed = JSON.parse(inputEl.value);
96
+ updatedConfig[inputEl.id] = parsed;
70
97
  } catch {
71
98
  // If not JSON, use raw value
72
- updatedConfig[input.id] = input.value;
99
+ updatedConfig[inputEl.id] = inputEl.value;
73
100
  }
74
101
  } else {
75
- updatedConfig[input.id] = input.value;
102
+ updatedConfig[inputEl.id] = inputEl.value;
76
103
  }
77
104
  }
78
105
  });
79
106
  }
80
107
 
81
108
  // Preserve hidden field values from original config if not collected from form
82
- if (node.data.config && configSchema?.properties) {
83
- Object.entries(configSchema.properties).forEach(([key, property]: [string, any]) => {
84
- if (property.format === 'hidden' && !(key in updatedConfig) && key in node.data.config) {
85
- updatedConfig[key] = node.data.config[key];
109
+ if (initialConfig && configSchema?.properties) {
110
+ Object.entries(configSchema.properties).forEach(
111
+ ([key, property]: [string, Record<string, unknown>]) => {
112
+ if (property.format === 'hidden' && !(key in updatedConfig) && key in initialConfig) {
113
+ updatedConfig[key] = initialConfig[key];
114
+ }
86
115
  }
87
- });
116
+ );
88
117
  }
89
118
 
90
119
  onSave(updatedConfig);
@@ -92,304 +121,741 @@
92
121
  </script>
93
122
 
94
123
  {#if configSchema}
95
- <div class="flowdrop-config-sidebar__form">
124
+ <form
125
+ class="config-form"
126
+ onsubmit={(e) => {
127
+ e.preventDefault();
128
+ handleSave();
129
+ }}
130
+ >
96
131
  {#if configSchema.properties}
97
- {#each Object.entries(configSchema.properties) as [key, field] (key)}
98
- {@const fieldConfig = field as any}
99
- {#if fieldConfig.format !== 'hidden'}
100
- <div class="flowdrop-config-sidebar__field">
101
- <label class="flowdrop-config-sidebar__field-label" for={key}>
102
- {fieldConfig.title || fieldConfig.description || key}
103
- </label>
104
- {#if fieldConfig.enum && fieldConfig.multiple}
105
- <!-- Checkboxes for enum with multiple selection -->
106
- <div class="flowdrop-config-sidebar__checkbox-group">
107
- {#each fieldConfig.enum as option (String(option))}
108
- <label class="flowdrop-config-sidebar__checkbox-item">
132
+ <div class="config-form__fields">
133
+ {#each Object.entries(configSchema.properties) as [key, field], index (key)}
134
+ {@const fieldConfig = field as Record<string, unknown>}
135
+ {@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">
109
248
  <input
249
+ id={key}
110
250
  type="checkbox"
111
- class="flowdrop-config-sidebar__checkbox"
112
- value={String(option)}
113
- checked={Array.isArray(configValues[key]) &&
114
- configValues[key].includes(String(option))}
251
+ class="config-form__toggle-input"
252
+ checked={Boolean(configValues[key] || fieldConfig.default || false)}
115
253
  onchange={(e) => {
116
- const checked = e.currentTarget.checked;
117
- const currentValues = Array.isArray(configValues[key])
118
- ? [...(configValues[key] as unknown[])]
119
- : [];
120
- if (checked) {
121
- if (!currentValues.includes(String(option))) {
122
- configValues[key] = [...currentValues, String(option)];
123
- }
124
- } else {
125
- configValues[key] = currentValues.filter((v) => v !== String(option));
126
- }
254
+ configValues[key] = e.currentTarget.checked;
127
255
  }}
256
+ aria-describedby={descriptionId}
128
257
  />
129
- <span class="flowdrop-config-sidebar__checkbox-label">
130
- {String(option)}
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'}
131
263
  </span>
132
264
  </label>
133
- {/each}
134
- </div>
135
- {:else if fieldConfig.enum}
136
- <!-- Select for enum with single selection -->
137
- <select
138
- id={key}
139
- class="flowdrop-config-sidebar__select"
140
- bind:value={configValues[key]}
141
- >
142
- {#each fieldConfig.enum as option (String(option))}
143
- <option value={String(option)}>{String(option)}</option>
144
- {/each}
145
- </select>
146
- {:else if fieldConfig.type === 'string' && fieldConfig.format === 'multiline'}
147
- <!-- Textarea for multiline strings -->
148
- <textarea
149
- id={key}
150
- class="flowdrop-config-sidebar__textarea"
151
- bind:value={configValues[key]}
152
- placeholder={String(fieldConfig.placeholder || '')}
153
- rows="4"
154
- ></textarea>
155
- {:else if fieldConfig.type === 'string'}
156
- <input
157
- id={key}
158
- type="text"
159
- class="flowdrop-config-sidebar__input"
160
- bind:value={configValues[key]}
161
- placeholder={String(fieldConfig.placeholder || '')}
162
- />
163
- {:else if fieldConfig.type === 'number'}
164
- <input
165
- id={key}
166
- type="number"
167
- class="flowdrop-config-sidebar__input"
168
- bind:value={configValues[key]}
169
- placeholder={String(fieldConfig.placeholder || '')}
170
- />
171
- {:else if fieldConfig.type === 'boolean'}
172
- <input
173
- id={key}
174
- type="checkbox"
175
- class="flowdrop-config-sidebar__checkbox"
176
- checked={Boolean(configValues[key] || fieldConfig.default || false)}
177
- onchange={(e) => {
178
- configValues[key] = e.currentTarget.checked;
179
- }}
180
- />
181
- {:else if fieldConfig.type === 'select' || fieldConfig.options}
182
- <select
183
- id={key}
184
- class="flowdrop-config-sidebar__select"
185
- bind:value={configValues[key]}
186
- >
187
- {#if fieldConfig.options}
188
- {#each fieldConfig.options as option (String(option.value))}
189
- {@const optionConfig = option as any}
190
- <option value={String(optionConfig.value)}>{String(optionConfig.label)}</option>
191
- {/each}
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
+ />
192
296
  {/if}
193
- </select>
194
- {:else}
195
- <!-- Fallback for unknown field types -->
196
- <input
197
- id={key}
198
- type="text"
199
- class="flowdrop-config-sidebar__input"
200
- bind:value={configValues[key]}
201
- placeholder={String(fieldConfig.placeholder || '')}
202
- />
203
- {/if}
204
- {#if fieldConfig.description}
205
- <p class="flowdrop-config-sidebar__field-description">
206
- {String(fieldConfig.description)}
207
- </p>
208
- {/if}
209
- </div>
210
- {/if}
211
- {/each}
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}
307
+ {/each}
308
+ </div>
212
309
  {:else}
213
310
  <!-- If no properties, show the raw schema for debugging -->
214
- <div class="flowdrop-config-sidebar__debug">
215
- <p><strong>Debug - Config Schema:</strong></p>
216
- <pre>{JSON.stringify(configSchema, null, 2)}</pre>
311
+ <div class="config-form__debug">
312
+ <div class="config-form__debug-header">
313
+ <Icon icon="heroicons:bug-ant" class="config-form__debug-icon" />
314
+ <span>Debug - Config Schema</span>
315
+ </div>
316
+ <pre class="config-form__debug-content">{JSON.stringify(configSchema, null, 2)}</pre>
217
317
  </div>
218
318
  {/if}
219
- </div>
220
319
 
221
- <!-- Footer -->
222
- <div class="flowdrop-config-sidebar__footer">
223
- <button
224
- class="flowdrop-config-sidebar__button flowdrop-config-sidebar__button--secondary"
225
- onclick={onCancel}
226
- >
227
- Cancel
228
- </button>
229
- <button
230
- class="flowdrop-config-sidebar__button flowdrop-config-sidebar__button--primary"
231
- onclick={handleSave}
232
- >
233
- Save Changes
234
- </button>
235
- </div>
320
+ <!-- Footer Actions -->
321
+ <div class="config-form__footer">
322
+ <button
323
+ type="button"
324
+ class="config-form__button config-form__button--secondary"
325
+ onclick={onCancel}
326
+ >
327
+ <Icon icon="heroicons:x-mark" class="config-form__button-icon" />
328
+ <span>Cancel</span>
329
+ </button>
330
+ <button type="submit" class="config-form__button config-form__button--primary">
331
+ <Icon icon="heroicons:check" class="config-form__button-icon" />
332
+ <span>Save Changes</span>
333
+ </button>
334
+ </div>
335
+ </form>
236
336
  {:else}
237
- <p class="flowdrop-config-sidebar__no-config">
238
- No configuration options available for this node.
239
- </p>
337
+ <div class="config-form__empty">
338
+ <div class="config-form__empty-icon">
339
+ <Icon icon="heroicons:cog-6-tooth" />
340
+ </div>
341
+ <p class="config-form__empty-text">No configuration options available for this node.</p>
342
+ </div>
240
343
  {/if}
241
344
 
242
345
  <style>
243
- .flowdrop-config-sidebar__form {
346
+ /* ============================================
347
+ CONFIG FORM - Modern, Accessible Design
348
+ ============================================ */
349
+
350
+ .config-form {
351
+ display: flex;
352
+ flex-direction: column;
353
+ gap: 1.5rem;
354
+ }
355
+
356
+ .config-form__fields {
244
357
  display: flex;
245
358
  flex-direction: column;
246
- gap: 1rem;
359
+ gap: 1.25rem;
247
360
  }
248
361
 
249
- .flowdrop-config-sidebar__field {
362
+ /* ============================================
363
+ FIELD CONTAINER
364
+ ============================================ */
365
+
366
+ .config-form__field {
250
367
  display: flex;
251
368
  flex-direction: column;
252
369
  gap: 0.5rem;
370
+ animation: fieldFadeIn 0.3s ease-out forwards;
371
+ opacity: 0;
372
+ transform: translateY(4px);
253
373
  }
254
374
 
255
- .flowdrop-config-sidebar__field-label {
256
- font-size: 0.875rem;
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);
257
402
  font-weight: 500;
258
- color: #374151;
259
403
  }
260
404
 
261
- .flowdrop-config-sidebar__input,
262
- .flowdrop-config-sidebar__select {
263
- padding: 0.5rem;
264
- border: 1px solid #d1d5db;
265
- border-radius: 0.375rem;
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;
266
422
  font-size: 0.875rem;
267
- transition:
268
- border-color 0.2s,
269
- box-shadow 0.2s;
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;
270
437
  }
271
438
 
272
- .flowdrop-config-sidebar__input:focus,
273
- .flowdrop-config-sidebar__select:focus {
439
+ .config-form__input:focus {
274
440
  outline: none;
275
- border-color: #3b82f6;
276
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
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);
277
446
  }
278
447
 
279
- .flowdrop-config-sidebar__checkbox-group {
280
- display: flex;
281
- flex-direction: column;
282
- gap: 0.5rem;
448
+ .config-form__input--number {
449
+ font-variant-numeric: tabular-nums;
283
450
  }
284
451
 
285
- .flowdrop-config-sidebar__checkbox-item {
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);
286
534
  display: flex;
287
535
  align-items: center;
288
- gap: 0.5rem;
289
- cursor: pointer;
536
+ transition: color 0.2s;
290
537
  }
291
538
 
292
- .flowdrop-config-sidebar__checkbox {
539
+ .config-form__select-icon :global(svg) {
293
540
  width: 1rem;
294
541
  height: 1rem;
295
- accent-color: #3b82f6;
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;
296
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;
297
582
  }
298
583
 
299
- .flowdrop-config-sidebar__checkbox-label {
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 {
300
621
  font-size: 0.875rem;
301
- color: #374151;
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;
302
634
  cursor: pointer;
635
+ padding: 0.5rem 0;
303
636
  }
304
637
 
305
- .flowdrop-config-sidebar__textarea {
306
- width: 100%;
307
- padding: 0.5rem 0.75rem;
308
- border: 1px solid #d1d5db;
309
- border-radius: 0.375rem;
310
- font-size: 0.875rem;
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;
311
661
  background-color: #ffffff;
312
- transition: all 0.2s ease-in-out;
313
- resize: vertical;
314
- min-height: 4rem;
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);
315
665
  }
316
666
 
317
- .flowdrop-config-sidebar__textarea:focus {
318
- outline: none;
319
- border-color: #3b82f6;
320
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
667
+ .config-form__toggle-input:checked + .config-form__toggle-track {
668
+ background-color: var(--color-ref-blue-500, #3b82f6);
321
669
  }
322
670
 
323
- .flowdrop-config-sidebar__field-description {
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 {
324
695
  margin: 0;
325
696
  font-size: 0.75rem;
326
- color: #6b7280;
327
- line-height: 1.4;
697
+ color: var(--color-ref-gray-500, #6b7280);
698
+ line-height: 1.5;
699
+ padding-left: 0.125rem;
328
700
  }
329
701
 
330
- .flowdrop-config-sidebar__no-config {
331
- text-align: center;
332
- color: #6b7280;
333
- font-style: italic;
334
- padding: 2rem 1rem;
335
- }
702
+ /* ============================================
703
+ FOOTER ACTIONS
704
+ ============================================ */
336
705
 
337
- .flowdrop-config-sidebar__footer {
338
- padding: 1rem;
339
- border-top: 1px solid #e5e7eb;
340
- background-color: #f9fafb;
706
+ .config-form__footer {
341
707
  display: flex;
342
708
  gap: 0.75rem;
343
709
  justify-content: flex-end;
344
- height: 40px;
345
- align-items: center;
710
+ padding-top: 1rem;
711
+ border-top: 1px solid var(--color-ref-gray-100, #f3f4f6);
712
+ margin-top: 0.5rem;
346
713
  }
347
714
 
348
- .flowdrop-config-sidebar__button {
349
- padding: 0.5rem 1rem;
350
- border-radius: 0.375rem;
715
+ .config-form__button {
716
+ display: inline-flex;
717
+ align-items: center;
718
+ justify-content: center;
719
+ gap: 0.5rem;
720
+ padding: 0.625rem 1rem;
721
+ border-radius: 0.5rem;
351
722
  font-size: 0.875rem;
352
- font-weight: 500;
723
+ font-weight: 600;
724
+ font-family: inherit;
353
725
  cursor: pointer;
354
- transition: all 0.2s;
726
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
355
727
  border: 1px solid transparent;
728
+ min-height: 2.5rem;
356
729
  }
357
730
 
358
- .flowdrop-config-sidebar__button--secondary {
731
+ .config-form__button :global(svg) {
732
+ width: 1rem;
733
+ height: 1rem;
734
+ flex-shrink: 0;
735
+ }
736
+
737
+ .config-form__button--secondary {
359
738
  background-color: #ffffff;
360
- border-color: #d1d5db;
361
- color: #374151;
739
+ border-color: var(--color-ref-gray-200, #e5e7eb);
740
+ color: var(--color-ref-gray-700, #374151);
741
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
362
742
  }
363
743
 
364
- .flowdrop-config-sidebar__button--secondary:hover {
365
- background-color: #f9fafb;
366
- border-color: #9ca3af;
744
+ .config-form__button--secondary:hover {
745
+ background-color: var(--color-ref-gray-50, #f9fafb);
746
+ border-color: var(--color-ref-gray-300, #d1d5db);
747
+ color: var(--color-ref-gray-900, #111827);
367
748
  }
368
749
 
369
- .flowdrop-config-sidebar__button--primary {
370
- background-color: #3b82f6;
750
+ .config-form__button--secondary:focus-visible {
751
+ outline: none;
752
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
753
+ }
754
+
755
+ .config-form__button--primary {
756
+ background: linear-gradient(
757
+ 135deg,
758
+ var(--color-ref-blue-500, #3b82f6) 0%,
759
+ var(--color-ref-blue-600, #2563eb) 100%
760
+ );
371
761
  color: #ffffff;
762
+ box-shadow:
763
+ 0 1px 3px rgba(59, 130, 246, 0.3),
764
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
372
765
  }
373
766
 
374
- .flowdrop-config-sidebar__button--primary:hover {
375
- background-color: #2563eb;
767
+ .config-form__button--primary:hover {
768
+ background: linear-gradient(
769
+ 135deg,
770
+ var(--color-ref-blue-600, #2563eb) 0%,
771
+ var(--color-ref-blue-700, #1d4ed8) 100%
772
+ );
773
+ box-shadow:
774
+ 0 4px 12px rgba(59, 130, 246, 0.35),
775
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
776
+ transform: translateY(-1px);
376
777
  }
377
778
 
378
- .flowdrop-config-sidebar__debug {
379
- background-color: #f3f4f6;
380
- border: 1px solid #d1d5db;
381
- border-radius: 0.375rem;
382
- padding: 1rem;
383
- margin: 1rem 0;
779
+ .config-form__button--primary:active {
780
+ transform: translateY(0);
384
781
  }
385
782
 
386
- .flowdrop-config-sidebar__debug pre {
387
- background-color: #ffffff;
388
- border: 1px solid #e5e7eb;
389
- border-radius: 0.25rem;
390
- padding: 0.75rem;
783
+ .config-form__button--primary:focus-visible {
784
+ outline: none;
785
+ box-shadow:
786
+ 0 0 0 3px rgba(59, 130, 246, 0.4),
787
+ 0 4px 12px rgba(59, 130, 246, 0.35);
788
+ }
789
+
790
+ /* ============================================
791
+ DEBUG SECTION
792
+ ============================================ */
793
+
794
+ .config-form__debug {
795
+ background-color: var(--color-ref-amber-50, #fffbeb);
796
+ border: 1px solid var(--color-ref-amber-200, #fde68a);
797
+ border-radius: 0.5rem;
798
+ overflow: hidden;
799
+ }
800
+
801
+ .config-form__debug-header {
802
+ display: flex;
803
+ align-items: center;
804
+ gap: 0.5rem;
805
+ padding: 0.75rem 1rem;
806
+ background-color: var(--color-ref-amber-100, #fef3c7);
807
+ border-bottom: 1px solid var(--color-ref-amber-200, #fde68a);
808
+ font-size: 0.8125rem;
809
+ font-weight: 600;
810
+ color: var(--color-ref-amber-800, #92400e);
811
+ }
812
+
813
+ .config-form__debug-header :global(svg) {
814
+ width: 1rem;
815
+ height: 1rem;
816
+ }
817
+
818
+ .config-form__debug-content {
819
+ margin: 0;
820
+ padding: 1rem;
391
821
  font-size: 0.75rem;
822
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
823
+ color: var(--color-ref-gray-700, #374151);
392
824
  overflow-x: auto;
393
- margin: 0.5rem 0 0 0;
825
+ background-color: #ffffff;
826
+ line-height: 1.5;
827
+ }
828
+
829
+ /* ============================================
830
+ EMPTY STATE
831
+ ============================================ */
832
+
833
+ .config-form__empty {
834
+ display: flex;
835
+ flex-direction: column;
836
+ align-items: center;
837
+ justify-content: center;
838
+ padding: 3rem 1.5rem;
839
+ text-align: center;
840
+ }
841
+
842
+ .config-form__empty-icon {
843
+ width: 3rem;
844
+ height: 3rem;
845
+ margin-bottom: 1rem;
846
+ color: var(--color-ref-gray-300, #d1d5db);
847
+ }
848
+
849
+ .config-form__empty-icon :global(svg) {
850
+ width: 100%;
851
+ height: 100%;
852
+ }
853
+
854
+ .config-form__empty-text {
855
+ margin: 0;
856
+ font-size: 0.875rem;
857
+ color: var(--color-ref-gray-500, #6b7280);
858
+ font-style: italic;
859
+ line-height: 1.5;
394
860
  }
395
861
  </style>