@d34dman/flowdrop 0.0.21 → 0.0.23

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 (51) hide show
  1. package/dist/components/App.svelte +69 -260
  2. package/dist/components/ConfigForm.svelte +357 -267
  3. package/dist/components/ConfigForm.svelte.d.ts +12 -3
  4. package/dist/components/ConfigPanel.svelte +160 -0
  5. package/dist/components/ConfigPanel.svelte.d.ts +32 -0
  6. package/dist/components/ReadOnlyDetails.svelte +168 -0
  7. package/dist/components/ReadOnlyDetails.svelte.d.ts +25 -0
  8. package/dist/components/WorkflowEditor.svelte +1 -1
  9. package/dist/components/form/FormArray.svelte +1049 -0
  10. package/dist/components/form/FormArray.svelte.d.ts +22 -0
  11. package/dist/components/form/FormCheckboxGroup.svelte +152 -0
  12. package/dist/components/form/FormCheckboxGroup.svelte.d.ts +15 -0
  13. package/dist/components/form/FormField.svelte +279 -0
  14. package/dist/components/form/FormField.svelte.d.ts +18 -0
  15. package/dist/components/form/FormFieldWrapper.svelte +133 -0
  16. package/dist/components/form/FormFieldWrapper.svelte.d.ts +18 -0
  17. package/dist/components/form/FormNumberField.svelte +109 -0
  18. package/dist/components/form/FormNumberField.svelte.d.ts +23 -0
  19. package/dist/components/form/FormSelect.svelte +126 -0
  20. package/dist/components/form/FormSelect.svelte.d.ts +18 -0
  21. package/dist/components/form/FormTextField.svelte +88 -0
  22. package/dist/components/form/FormTextField.svelte.d.ts +17 -0
  23. package/dist/components/form/FormTextarea.svelte +94 -0
  24. package/dist/components/form/FormTextarea.svelte.d.ts +19 -0
  25. package/dist/components/form/FormToggle.svelte +123 -0
  26. package/dist/components/form/FormToggle.svelte.d.ts +17 -0
  27. package/dist/components/form/index.d.ts +41 -0
  28. package/dist/components/form/index.js +45 -0
  29. package/dist/components/form/types.d.ts +208 -0
  30. package/dist/components/form/types.js +29 -0
  31. package/dist/components/nodes/GatewayNode.svelte +84 -12
  32. package/dist/components/nodes/NotesNode.svelte +89 -307
  33. package/dist/components/nodes/NotesNode.svelte.d.ts +3 -22
  34. package/dist/components/nodes/SimpleNode.svelte +41 -5
  35. package/dist/components/nodes/SimpleNode.svelte.d.ts +2 -1
  36. package/dist/components/nodes/SquareNode.svelte +41 -5
  37. package/dist/components/nodes/SquareNode.svelte.d.ts +2 -1
  38. package/dist/components/nodes/WorkflowNode.svelte +88 -5
  39. package/dist/index.d.ts +4 -4
  40. package/dist/index.js +3 -4
  41. package/dist/stores/workflowStore.d.ts +15 -0
  42. package/dist/stores/workflowStore.js +28 -0
  43. package/dist/types/index.d.ts +77 -0
  44. package/dist/types/index.js +16 -0
  45. package/package.json +3 -3
  46. package/dist/components/ConfigSidebar.svelte +0 -916
  47. package/dist/components/ConfigSidebar.svelte.d.ts +0 -51
  48. package/dist/config/demo.d.ts +0 -58
  49. package/dist/config/demo.js +0 -142
  50. package/dist/data/samples.d.ts +0 -51
  51. package/dist/data/samples.js +0 -3245
@@ -1,29 +1,55 @@
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
+ 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
+
12
+ Accessibility features:
13
+ - Proper label associations with for/id attributes
14
+ - ARIA describedby for field descriptions
15
+ - Focus-visible states for keyboard navigation
16
+ - Required field indicators
5
17
  -->
6
18
 
7
19
  <script lang="ts">
8
- import type { ConfigSchema, WorkflowNode } from '../types/index.js';
20
+ import Icon from '@iconify/svelte';
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';
9
24
 
10
25
  interface Props {
11
- node: WorkflowNode;
12
- onSave: (config: Record<string, unknown>) => void;
26
+ /** Optional workflow node (if provided, schema and values are derived from it) */
27
+ node?: WorkflowNode;
28
+ /** Direct config schema (used when node is not provided) */
29
+ schema?: ConfigSchema;
30
+ /** Direct config values (used when node is not provided) */
31
+ values?: Record<string, unknown>;
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;
36
+ /** Callback when form is cancelled */
13
37
  onCancel: () => void;
14
38
  }
15
39
 
16
- let { node, onSave, onCancel }: Props = $props();
40
+ let { node, schema, values, showUIExtensions = true, onSave, onCancel }: Props = $props();
17
41
 
18
42
  /**
19
- * Get the configuration schema from node metadata
43
+ * Get the configuration schema from node metadata or direct prop
20
44
  */
21
- const configSchema = $derived(node.data.metadata?.configSchema as ConfigSchema | undefined);
45
+ const configSchema = $derived(
46
+ schema ?? (node?.data.metadata?.configSchema as ConfigSchema | undefined)
47
+ );
22
48
 
23
49
  /**
24
- * Get the current node configuration
50
+ * Get the current configuration from node or direct prop
25
51
  */
26
- const nodeConfig = $derived(node.data.config || {});
52
+ const initialConfig = $derived(values ?? node?.data.config ?? {});
27
53
 
28
54
  /**
29
55
  * Create reactive configuration values using $state
@@ -32,361 +58,425 @@
32
58
  let configValues = $state<Record<string, unknown>>({});
33
59
 
34
60
  /**
35
- * Initialize config values when node or schema changes
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
+
77
+ /**
78
+ * Initialize config values when node/schema changes
36
79
  */
37
80
  $effect(() => {
38
81
  if (configSchema?.properties) {
39
82
  const mergedConfig: Record<string, unknown> = {};
40
83
  Object.entries(configSchema.properties).forEach(([key, field]) => {
41
- const fieldConfig = field as any;
84
+ const fieldConfig = field as Record<string, unknown>;
42
85
  // Use existing value if available, otherwise use default
43
- mergedConfig[key] = nodeConfig[key] !== undefined ? nodeConfig[key] : fieldConfig.default;
86
+ mergedConfig[key] =
87
+ initialConfig[key] !== undefined ? initialConfig[key] : fieldConfig.default;
44
88
  });
45
89
  configValues = mergedConfig;
46
90
  }
47
91
  });
48
92
 
93
+ /**
94
+ * Initialize UI extension values when node changes
95
+ */
96
+ $effect(() => {
97
+ uiExtensionValues = {
98
+ hideUnconnectedHandles: initialUIExtensions.hideUnconnectedHandles ?? false
99
+ };
100
+ });
101
+
102
+ /**
103
+ * Check if a field is required based on schema
104
+ */
105
+ function isFieldRequired(key: string): boolean {
106
+ if (!configSchema?.required) return false;
107
+ return configSchema.required.includes(key);
108
+ }
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
+
49
117
  /**
50
118
  * Handle form submission
119
+ * Collects both config values and UI extension values
51
120
  */
52
121
  function handleSave(): void {
53
122
  // Collect all form values including hidden fields
54
- const form = document.querySelector('.flowdrop-config-sidebar__form');
123
+ const form = document.querySelector('.config-form');
55
124
  const updatedConfig: Record<string, unknown> = { ...configValues };
56
125
 
57
126
  if (form) {
58
127
  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') {
128
+ inputs.forEach((input: Element) => {
129
+ const inputEl = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
130
+ // Skip UI extension fields (prefixed with ext-)
131
+ if (inputEl.id && !inputEl.id.startsWith('ext-')) {
132
+ if (inputEl instanceof HTMLInputElement && inputEl.type === 'checkbox') {
133
+ updatedConfig[inputEl.id] = inputEl.checked;
134
+ } else if (inputEl instanceof HTMLInputElement && inputEl.type === 'number') {
135
+ updatedConfig[inputEl.id] = inputEl.value ? Number(inputEl.value) : inputEl.value;
136
+ } else if (inputEl instanceof HTMLInputElement && inputEl.type === 'hidden') {
66
137
  // Parse hidden field values that might be JSON
67
138
  try {
68
- const parsed = JSON.parse(input.value);
69
- updatedConfig[input.id] = parsed;
139
+ const parsed = JSON.parse(inputEl.value);
140
+ updatedConfig[inputEl.id] = parsed;
70
141
  } catch {
71
142
  // If not JSON, use raw value
72
- updatedConfig[input.id] = input.value;
143
+ updatedConfig[inputEl.id] = inputEl.value;
73
144
  }
74
145
  } else {
75
- updatedConfig[input.id] = input.value;
146
+ updatedConfig[inputEl.id] = inputEl.value;
76
147
  }
77
148
  }
78
149
  });
79
150
  }
80
151
 
81
152
  // 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];
153
+ if (initialConfig && configSchema?.properties) {
154
+ Object.entries(configSchema.properties).forEach(
155
+ ([key, property]: [string, Record<string, unknown>]) => {
156
+ if (property.format === 'hidden' && !(key in updatedConfig) && key in initialConfig) {
157
+ updatedConfig[key] = initialConfig[key];
158
+ }
86
159
  }
87
- });
160
+ );
88
161
  }
89
162
 
90
- 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;
91
176
  }
92
177
  </script>
93
178
 
94
179
  {#if configSchema}
95
- <div class="flowdrop-config-sidebar__form">
180
+ <form
181
+ class="config-form"
182
+ onsubmit={(e) => {
183
+ e.preventDefault();
184
+ handleSave();
185
+ }}
186
+ >
96
187
  {#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">
109
- <input
110
- type="checkbox"
111
- class="flowdrop-config-sidebar__checkbox"
112
- value={String(option)}
113
- checked={Array.isArray(configValues[key]) &&
114
- configValues[key].includes(String(option))}
115
- 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
- }
127
- }}
128
- />
129
- <span class="flowdrop-config-sidebar__checkbox-label">
130
- {String(option)}
131
- </span>
132
- </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}
192
- {/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}
188
+ <div class="config-form__fields">
189
+ {#each Object.entries(configSchema.properties) as [key, field], index (key)}
190
+ {@const fieldSchema = toFieldSchema(field as Record<string, unknown>)}
191
+ {@const required = isFieldRequired(key)}
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
+ />
201
+ {/each}
202
+ </div>
212
203
  {:else}
213
204
  <!-- 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>
205
+ <div class="config-form__debug">
206
+ <div class="config-form__debug-header">
207
+ <Icon icon="heroicons:bug-ant" class="config-form__debug-icon" />
208
+ <span>Debug - Config Schema</span>
209
+ </div>
210
+ <pre class="config-form__debug-content">{JSON.stringify(configSchema, null, 2)}</pre>
217
211
  </div>
218
212
  {/if}
219
- </div>
220
213
 
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>
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
+
243
+ <!-- Footer Actions -->
244
+ <div class="config-form__footer">
245
+ <button
246
+ type="button"
247
+ class="config-form__button config-form__button--secondary"
248
+ onclick={onCancel}
249
+ >
250
+ <Icon icon="heroicons:x-mark" class="config-form__button-icon" />
251
+ <span>Cancel</span>
252
+ </button>
253
+ <button type="submit" class="config-form__button config-form__button--primary">
254
+ <Icon icon="heroicons:check" class="config-form__button-icon" />
255
+ <span>Save Changes</span>
256
+ </button>
257
+ </div>
258
+ </form>
236
259
  {:else}
237
- <p class="flowdrop-config-sidebar__no-config">
238
- No configuration options available for this node.
239
- </p>
260
+ <div class="config-form__empty">
261
+ <div class="config-form__empty-icon">
262
+ <Icon icon="heroicons:cog-6-tooth" />
263
+ </div>
264
+ <p class="config-form__empty-text">No configuration options available for this node.</p>
265
+ </div>
240
266
  {/if}
241
267
 
242
268
  <style>
243
- .flowdrop-config-sidebar__form {
269
+ /* ============================================
270
+ CONFIG FORM - Container Styles
271
+ Individual field styles are in form/ components
272
+ ============================================ */
273
+
274
+ .config-form {
244
275
  display: flex;
245
276
  flex-direction: column;
246
- gap: 1rem;
277
+ gap: 1.5rem;
247
278
  }
248
279
 
249
- .flowdrop-config-sidebar__field {
280
+ .config-form__fields {
250
281
  display: flex;
251
282
  flex-direction: column;
252
- gap: 0.5rem;
253
- }
254
-
255
- .flowdrop-config-sidebar__field-label {
256
- font-size: 0.875rem;
257
- font-weight: 500;
258
- color: #374151;
259
- }
260
-
261
- .flowdrop-config-sidebar__input,
262
- .flowdrop-config-sidebar__select {
263
- padding: 0.5rem;
264
- border: 1px solid #d1d5db;
265
- border-radius: 0.375rem;
266
- font-size: 0.875rem;
267
- transition:
268
- border-color 0.2s,
269
- box-shadow 0.2s;
283
+ gap: 1.25rem;
270
284
  }
271
285
 
272
- .flowdrop-config-sidebar__input:focus,
273
- .flowdrop-config-sidebar__select:focus {
274
- outline: none;
275
- border-color: #3b82f6;
276
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
277
- }
286
+ /* ============================================
287
+ FOOTER ACTIONS
288
+ ============================================ */
278
289
 
279
- .flowdrop-config-sidebar__checkbox-group {
290
+ .config-form__footer {
280
291
  display: flex;
281
- flex-direction: column;
282
- gap: 0.5rem;
292
+ gap: 0.75rem;
293
+ justify-content: flex-end;
294
+ padding-top: 1rem;
295
+ border-top: 1px solid var(--color-ref-gray-100, #f3f4f6);
296
+ margin-top: 0.5rem;
283
297
  }
284
298
 
285
- .flowdrop-config-sidebar__checkbox-item {
286
- display: flex;
299
+ .config-form__button {
300
+ display: inline-flex;
287
301
  align-items: center;
302
+ justify-content: center;
288
303
  gap: 0.5rem;
304
+ padding: 0.625rem 1rem;
305
+ border-radius: 0.5rem;
306
+ font-size: 0.875rem;
307
+ font-weight: 600;
308
+ font-family: inherit;
289
309
  cursor: pointer;
310
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
311
+ border: 1px solid transparent;
312
+ min-height: 2.5rem;
290
313
  }
291
314
 
292
- .flowdrop-config-sidebar__checkbox {
315
+ .config-form__button :global(svg) {
293
316
  width: 1rem;
294
317
  height: 1rem;
295
- accent-color: #3b82f6;
296
- cursor: pointer;
318
+ flex-shrink: 0;
297
319
  }
298
320
 
299
- .flowdrop-config-sidebar__checkbox-label {
300
- font-size: 0.875rem;
301
- color: #374151;
302
- cursor: pointer;
321
+ .config-form__button--secondary {
322
+ background-color: #ffffff;
323
+ border-color: var(--color-ref-gray-200, #e5e7eb);
324
+ color: var(--color-ref-gray-700, #374151);
325
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
303
326
  }
304
327
 
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;
311
- background-color: #ffffff;
312
- transition: all 0.2s ease-in-out;
313
- resize: vertical;
314
- min-height: 4rem;
328
+ .config-form__button--secondary:hover {
329
+ background-color: var(--color-ref-gray-50, #f9fafb);
330
+ border-color: var(--color-ref-gray-300, #d1d5db);
331
+ color: var(--color-ref-gray-900, #111827);
315
332
  }
316
333
 
317
- .flowdrop-config-sidebar__textarea:focus {
334
+ .config-form__button--secondary:focus-visible {
318
335
  outline: none;
319
- border-color: #3b82f6;
320
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
336
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
321
337
  }
322
338
 
323
- .flowdrop-config-sidebar__field-description {
324
- margin: 0;
325
- font-size: 0.75rem;
326
- color: #6b7280;
327
- line-height: 1.4;
339
+ .config-form__button--primary {
340
+ background: linear-gradient(
341
+ 135deg,
342
+ var(--color-ref-blue-500, #3b82f6) 0%,
343
+ var(--color-ref-blue-600, #2563eb) 100%
344
+ );
345
+ color: #ffffff;
346
+ box-shadow:
347
+ 0 1px 3px rgba(59, 130, 246, 0.3),
348
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
328
349
  }
329
350
 
330
- .flowdrop-config-sidebar__no-config {
331
- text-align: center;
332
- color: #6b7280;
333
- font-style: italic;
334
- padding: 2rem 1rem;
351
+ .config-form__button--primary:hover {
352
+ background: linear-gradient(
353
+ 135deg,
354
+ var(--color-ref-blue-600, #2563eb) 0%,
355
+ var(--color-ref-blue-700, #1d4ed8) 100%
356
+ );
357
+ box-shadow:
358
+ 0 4px 12px rgba(59, 130, 246, 0.35),
359
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
360
+ transform: translateY(-1px);
361
+ }
362
+
363
+ .config-form__button--primary:active {
364
+ transform: translateY(0);
365
+ }
366
+
367
+ .config-form__button--primary:focus-visible {
368
+ outline: none;
369
+ box-shadow:
370
+ 0 0 0 3px rgba(59, 130, 246, 0.4),
371
+ 0 4px 12px rgba(59, 130, 246, 0.35);
372
+ }
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;
335
384
  }
336
385
 
337
- .flowdrop-config-sidebar__footer {
386
+ .config-form__extensions-header {
338
387
  display: flex;
339
- gap: 0.75rem;
340
- justify-content: flex-end;
341
- height: 40px;
342
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);
343
396
  }
344
397
 
345
- .flowdrop-config-sidebar__button {
346
- padding: 0.5rem 1rem;
347
- border-radius: 0.375rem;
348
- font-size: 0.875rem;
349
- font-weight: 500;
350
- cursor: pointer;
351
- transition: all 0.2s;
352
- border: 1px solid transparent;
398
+ .config-form__extensions-header :global(svg) {
399
+ width: 1rem;
400
+ height: 1rem;
401
+ color: var(--color-ref-slate-500, #64748b);
353
402
  }
354
403
 
355
- .flowdrop-config-sidebar__button--secondary {
356
- background-color: #ffffff;
357
- border-color: #d1d5db;
358
- color: #374151;
404
+ .config-form__extensions-content {
405
+ padding: 1rem;
406
+ display: flex;
407
+ flex-direction: column;
408
+ gap: 1rem;
359
409
  }
360
410
 
361
- .flowdrop-config-sidebar__button--secondary:hover {
362
- background-color: #f9fafb;
363
- border-color: #9ca3af;
364
- }
411
+ /* ============================================
412
+ DEBUG SECTION
413
+ ============================================ */
365
414
 
366
- .flowdrop-config-sidebar__button--primary {
367
- background-color: #3b82f6;
368
- color: #ffffff;
415
+ .config-form__debug {
416
+ background-color: var(--color-ref-amber-50, #fffbeb);
417
+ border: 1px solid var(--color-ref-amber-200, #fde68a);
418
+ border-radius: 0.5rem;
419
+ overflow: hidden;
369
420
  }
370
421
 
371
- .flowdrop-config-sidebar__button--primary:hover {
372
- background-color: #2563eb;
422
+ .config-form__debug-header {
423
+ display: flex;
424
+ align-items: center;
425
+ gap: 0.5rem;
426
+ padding: 0.75rem 1rem;
427
+ background-color: var(--color-ref-amber-100, #fef3c7);
428
+ border-bottom: 1px solid var(--color-ref-amber-200, #fde68a);
429
+ font-size: 0.8125rem;
430
+ font-weight: 600;
431
+ color: var(--color-ref-amber-800, #92400e);
373
432
  }
374
433
 
375
- .flowdrop-config-sidebar__debug {
376
- background-color: #f3f4f6;
377
- border: 1px solid #d1d5db;
378
- border-radius: 0.375rem;
379
- padding: 1rem;
380
- margin: 1rem 0;
434
+ .config-form__debug-header :global(svg) {
435
+ width: 1rem;
436
+ height: 1rem;
381
437
  }
382
438
 
383
- .flowdrop-config-sidebar__debug pre {
384
- background-color: #ffffff;
385
- border: 1px solid #e5e7eb;
386
- border-radius: 0.25rem;
387
- padding: 0.75rem;
439
+ .config-form__debug-content {
440
+ margin: 0;
441
+ padding: 1rem;
388
442
  font-size: 0.75rem;
443
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
444
+ color: var(--color-ref-gray-700, #374151);
389
445
  overflow-x: auto;
390
- margin: 0.5rem 0 0 0;
446
+ background-color: #ffffff;
447
+ line-height: 1.5;
448
+ }
449
+
450
+ /* ============================================
451
+ EMPTY STATE
452
+ ============================================ */
453
+
454
+ .config-form__empty {
455
+ display: flex;
456
+ flex-direction: column;
457
+ align-items: center;
458
+ justify-content: center;
459
+ padding: 3rem 1.5rem;
460
+ text-align: center;
461
+ }
462
+
463
+ .config-form__empty-icon {
464
+ width: 3rem;
465
+ height: 3rem;
466
+ margin-bottom: 1rem;
467
+ color: var(--color-ref-gray-300, #d1d5db);
468
+ }
469
+
470
+ .config-form__empty-icon :global(svg) {
471
+ width: 100%;
472
+ height: 100%;
473
+ }
474
+
475
+ .config-form__empty-text {
476
+ margin: 0;
477
+ font-size: 0.875rem;
478
+ color: var(--color-ref-gray-500, #6b7280);
479
+ font-style: italic;
480
+ line-height: 1.5;
391
481
  }
392
482
  </style>