@flowdrop/flowdrop 2.0.0-beta.3 → 2.0.0-beta.4

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 (49) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +5 -5
  3. package/dist/components/App.svelte +15 -146
  4. package/dist/components/Button.stories.svelte +65 -0
  5. package/dist/components/Button.stories.svelte.d.ts +19 -0
  6. package/dist/components/Button.svelte +62 -0
  7. package/dist/components/Button.svelte.d.ts +24 -0
  8. package/dist/components/ConfigForm.svelte +4 -4
  9. package/dist/components/EditorStatusBar.stories.svelte +44 -0
  10. package/dist/components/EditorStatusBar.stories.svelte.d.ts +27 -0
  11. package/dist/components/EditorStatusBar.svelte +99 -0
  12. package/dist/components/EditorStatusBar.svelte.d.ts +15 -0
  13. package/dist/components/IconButton.svelte +80 -0
  14. package/dist/components/IconButton.svelte.d.ts +30 -0
  15. package/dist/components/Input.svelte +74 -0
  16. package/dist/components/Input.svelte.d.ts +17 -0
  17. package/dist/components/Navbar.svelte +9 -4
  18. package/dist/components/Navbar.svelte.d.ts +3 -0
  19. package/dist/components/NodeSidebar.svelte +13 -111
  20. package/dist/components/NodeSwapPicker.svelte +10 -26
  21. package/dist/components/Select.svelte +53 -0
  22. package/dist/components/Select.svelte.d.ts +15 -0
  23. package/dist/components/Textarea.svelte +39 -0
  24. package/dist/components/Textarea.svelte.d.ts +12 -0
  25. package/dist/components/ThemeToggle.svelte +15 -89
  26. package/dist/components/form/FormArray.svelte +37 -157
  27. package/dist/components/form/FormCheckboxGroup.svelte +1 -1
  28. package/dist/components/form/FormField.svelte +5 -44
  29. package/dist/components/form/FormFieldLight.svelte +5 -44
  30. package/dist/components/form/FormFieldset.svelte +1 -1
  31. package/dist/components/form/FormNumberField.svelte +4 -32
  32. package/dist/components/form/FormRangeField.svelte +17 -7
  33. package/dist/components/form/FormSelect.svelte +13 -79
  34. package/dist/components/form/FormTextField.svelte +3 -39
  35. package/dist/components/form/FormTextarea.svelte +4 -43
  36. package/dist/components/form/resolveFieldType.d.ts +24 -0
  37. package/dist/components/form/resolveFieldType.js +55 -0
  38. package/dist/components/icons/CloseIcon.svelte +6 -0
  39. package/dist/components/icons/CloseIcon.svelte.d.ts +26 -0
  40. package/dist/components/playground/InputCollector.svelte +11 -46
  41. package/dist/messages/index.d.ts +1 -1
  42. package/dist/messages/index.js +1 -1
  43. package/dist/openapi/v1/openapi.yaml +2 -2
  44. package/dist/skins/drafter.js +41 -28
  45. package/dist/styles/base.css +247 -5
  46. package/dist/styles/tokens.css +6 -0
  47. package/dist/svelte-app.js +68 -107
  48. package/dist/utils/connections.js +14 -50
  49. package/package.json +1 -1
@@ -0,0 +1,80 @@
1
+ <!--
2
+ IconButton — typed wrapper over the `.flowdrop-btn--icon` system (base.css).
3
+
4
+ The icon-only sibling of Button.svelte: a square button that holds a single
5
+ glyph (Icon, inline SVG, or character). All geometry, variants, sizes and the
6
+ centralized focus ring live in base.css; this component is the ergonomic,
7
+ type-safe entry point so callers pick `variant`/`size` instead of hand-writing
8
+ class strings and re-declaring widths/tints in scoped styles.
9
+
10
+ Internal for now (not exported from any public entry) so the API isn't frozen
11
+ before GA. Existing hand-rolled square buttons migrate onto it incrementally.
12
+ Canvas-overlay buttons keep their bespoke components (CanvasIconButton,
13
+ NodeConfigButton) — those add absolute positioning, shadows and backdrop blur
14
+ this primitive deliberately stays out of.
15
+ -->
16
+
17
+ <script lang="ts">
18
+ import type { Snippet } from 'svelte';
19
+
20
+ interface Props {
21
+ /**
22
+ * Visual style. `ghost` (default) is transparent until hover; `default` is a
23
+ * flat surface with a resting border; `primary`/`danger`/`success` are quiet
24
+ * semantic tints (muted fill + coloured border) that go solid on press.
25
+ */
26
+ variant?: 'ghost' | 'default' | 'primary' | 'danger' | 'success';
27
+ /** Square size — `md` (32px) is the base; `sm` (28px) / `lg` (36px) add a modifier */
28
+ size?: 'sm' | 'md' | 'lg';
29
+ /** Native button type */
30
+ type?: 'button' | 'submit' | 'reset';
31
+ /** Tooltip text */
32
+ title?: string;
33
+ /** Accessible label — required, since the button is icon-only */
34
+ ariaLabel: string;
35
+ /** Toggle/pressed state — applies the active tint and sets `aria-pressed` */
36
+ active?: boolean;
37
+ /** Disabled state */
38
+ disabled?: boolean;
39
+ /** Extra classes appended to the root button */
40
+ class?: string;
41
+ /** Click handler */
42
+ onclick?: (event: MouseEvent) => void;
43
+ /** The glyph (icon, svg, or character) */
44
+ children: Snippet;
45
+ }
46
+
47
+ let {
48
+ variant = 'ghost',
49
+ size = 'md',
50
+ type = 'button',
51
+ title,
52
+ ariaLabel,
53
+ active = false,
54
+ disabled = false,
55
+ class: className = '',
56
+ onclick,
57
+ children
58
+ }: Props = $props();
59
+
60
+ // `ghost` reuses the shared `.flowdrop-btn--ghost`; the other variants have
61
+ // icon-specific tint classes so they don't collide with the solid text buttons.
62
+ const variantClass = $derived(
63
+ variant === 'ghost' ? 'flowdrop-btn--ghost' : `flowdrop-btn--icon-${variant}`
64
+ );
65
+ // 'md' is the unmodified base size; only 'sm'/'lg' need a size modifier.
66
+ const sizeClass = $derived(size === 'md' ? '' : `flowdrop-btn--${size}`);
67
+ </script>
68
+
69
+ <button
70
+ class="flowdrop-btn flowdrop-btn--icon {variantClass} {sizeClass} {className}"
71
+ class:is-active={active}
72
+ {type}
73
+ {title}
74
+ {disabled}
75
+ aria-label={ariaLabel}
76
+ aria-pressed={active ? true : undefined}
77
+ {onclick}
78
+ >
79
+ {@render children()}
80
+ </button>
@@ -0,0 +1,30 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /**
4
+ * Visual style. `ghost` (default) is transparent until hover; `default` is a
5
+ * flat surface with a resting border; `primary`/`danger`/`success` are quiet
6
+ * semantic tints (muted fill + coloured border) that go solid on press.
7
+ */
8
+ variant?: 'ghost' | 'default' | 'primary' | 'danger' | 'success';
9
+ /** Square size — `md` (32px) is the base; `sm` (28px) / `lg` (36px) add a modifier */
10
+ size?: 'sm' | 'md' | 'lg';
11
+ /** Native button type */
12
+ type?: 'button' | 'submit' | 'reset';
13
+ /** Tooltip text */
14
+ title?: string;
15
+ /** Accessible label — required, since the button is icon-only */
16
+ ariaLabel: string;
17
+ /** Toggle/pressed state — applies the active tint and sets `aria-pressed` */
18
+ active?: boolean;
19
+ /** Disabled state */
20
+ disabled?: boolean;
21
+ /** Extra classes appended to the root button */
22
+ class?: string;
23
+ /** Click handler */
24
+ onclick?: (event: MouseEvent) => void;
25
+ /** The glyph (icon, svg, or character) */
26
+ children: Snippet;
27
+ }
28
+ declare const IconButton: import("svelte").Component<Props, {}, "">;
29
+ type IconButton = ReturnType<typeof IconButton>;
30
+ export default IconButton;
@@ -0,0 +1,74 @@
1
+ <!--
2
+ Input — typed wrapper over the shared `.flowdrop-input` system (base.css).
3
+
4
+ All control styling (surface, border, radius, sizes, focus ring, disabled =
5
+ the only muted state) lives in base.css. This component is the ergonomic,
6
+ type-safe entry point so callers pick `size`/`invalid`/`leading`/`trailing`
7
+ instead of hand-writing class strings — the single place text-like fields
8
+ should route through. Mirrors Button.svelte.
9
+
10
+ Native attributes (type, value, placeholder, disabled, id, aria-*, oninput…)
11
+ are forwarded verbatim to the underlying <input>.
12
+
13
+ Internal for now (not exported from any public entry) so the API isn't frozen
14
+ before GA. Existing hand-rolled inputs migrate onto it incrementally.
15
+ -->
16
+
17
+ <script lang="ts">
18
+ import type { Snippet } from 'svelte';
19
+ import type { HTMLInputAttributes } from 'svelte/elements';
20
+
21
+ // Omit native numeric `size` so our design-token size (matching Button) wins.
22
+ interface Props extends Omit<HTMLInputAttributes, 'size'> {
23
+ /** Size — `md` is the base `.flowdrop-input`; `sm`/`lg` add a modifier */
24
+ size?: 'sm' | 'md' | 'lg';
25
+ /** Renders the error-border state */
26
+ invalid?: boolean;
27
+ /** Extra classes appended to the input */
28
+ class?: string;
29
+ /** Optional leading affordance (e.g. a search icon) */
30
+ leading?: Snippet;
31
+ /** Optional trailing affordance */
32
+ trailing?: Snippet;
33
+ }
34
+
35
+ let {
36
+ size = 'md',
37
+ invalid = false,
38
+ class: className = '',
39
+ leading,
40
+ trailing,
41
+ ...rest
42
+ }: Props = $props();
43
+
44
+ const inputClass = $derived(
45
+ [
46
+ 'flowdrop-input',
47
+ size === 'md' ? '' : `flowdrop-input--${size}`,
48
+ invalid ? 'flowdrop-input--invalid' : '',
49
+ leading ? 'flowdrop-input--has-leading' : '',
50
+ trailing ? 'flowdrop-input--has-trailing' : '',
51
+ className
52
+ ]
53
+ .filter(Boolean)
54
+ .join(' ')
55
+ );
56
+ </script>
57
+
58
+ {#if leading || trailing}
59
+ <div class="flowdrop-input-wrap">
60
+ {#if leading}
61
+ <span class="flowdrop-input-wrap__icon flowdrop-input-wrap__icon--leading">
62
+ {@render leading()}
63
+ </span>
64
+ {/if}
65
+ <input class={inputClass} {...rest} />
66
+ {#if trailing}
67
+ <span class="flowdrop-input-wrap__icon flowdrop-input-wrap__icon--trailing">
68
+ {@render trailing()}
69
+ </span>
70
+ {/if}
71
+ </div>
72
+ {:else}
73
+ <input class={inputClass} {...rest} />
74
+ {/if}
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ interface Props extends Omit<HTMLInputAttributes, 'size'> {
4
+ /** Size — `md` is the base `.flowdrop-input`; `sm`/`lg` add a modifier */
5
+ size?: 'sm' | 'md' | 'lg';
6
+ /** Renders the error-border state */
7
+ invalid?: boolean;
8
+ /** Extra classes appended to the input */
9
+ class?: string;
10
+ /** Optional leading affordance (e.g. a search icon) */
11
+ leading?: Snippet;
12
+ /** Optional trailing affordance */
13
+ trailing?: Snippet;
14
+ }
15
+ declare const Input: import("svelte").Component<Props, {}, "">;
16
+ type Input = ReturnType<typeof Input>;
17
+ export default Input;
@@ -7,6 +7,7 @@
7
7
  -->
8
8
 
9
9
  <script lang="ts">
10
+ import type { Snippet } from 'svelte';
10
11
  import Icon from '@iconify/svelte';
11
12
  import LogoWordmark from './LogoWordmark.svelte';
12
13
  import SettingsModal from './SettingsModal.svelte';
@@ -37,6 +38,8 @@
37
38
  showSettingsSyncButton?: boolean;
38
39
  /** Show the reset buttons in the settings modal */
39
40
  showSettingsResetButton?: boolean;
41
+ /** Custom content rendered in the trailing (right) region, before the settings gear */
42
+ end?: Snippet;
40
43
  }
41
44
 
42
45
  let {
@@ -47,7 +50,8 @@
47
50
  showSettings = true,
48
51
  settingsCategories,
49
52
  showSettingsSyncButton,
50
- showSettingsResetButton
53
+ showSettingsResetButton,
54
+ end
51
55
  }: Props = $props();
52
56
 
53
57
  // Dropdown state
@@ -271,6 +275,7 @@
271
275
  </div>
272
276
 
273
277
  <div class="flowdrop-navbar__end">
278
+ {@render end?.()}
274
279
  {#if showSettings}
275
280
  <button
276
281
  class="flowdrop-navbar__settings-btn"
@@ -519,7 +524,7 @@
519
524
  transition: all var(--fd-transition-normal);
520
525
  font-weight: 500;
521
526
  font-size: var(--fd-text-sm);
522
- height: 2.5rem;
527
+ height: var(--fd-size-btn-min);
523
528
  box-sizing: border-box;
524
529
  background-color: var(--fd-background);
525
530
  color: var(--fd-foreground);
@@ -535,7 +540,7 @@
535
540
  position: relative;
536
541
  display: flex;
537
542
  align-items: center;
538
- height: 2.5rem;
543
+ height: var(--fd-size-btn-min);
539
544
  }
540
545
 
541
546
  .flowdrop-navbar__dropdown-trigger {
@@ -543,7 +548,7 @@
543
548
  align-items: center;
544
549
  justify-content: center;
545
550
  width: 2rem;
546
- height: 2.5rem;
551
+ height: var(--fd-size-btn-min);
547
552
  border: 1px solid var(--fd-border-strong);
548
553
  border-left: none;
549
554
  border-radius: 0 var(--fd-radius-md) var(--fd-radius-md) 0;
@@ -1,3 +1,4 @@
1
+ import type { Snippet } from 'svelte';
1
2
  import type { SettingsCategory } from '../types/settings.js';
2
3
  import type { NavbarAction } from '../types/navbar.js';
3
4
  interface BreadcrumbItem {
@@ -22,6 +23,8 @@ interface Props {
22
23
  showSettingsSyncButton?: boolean;
23
24
  /** Show the reset buttons in the settings modal */
24
25
  showSettingsResetButton?: boolean;
26
+ /** Custom content rendered in the trailing (right) region, before the settings gear */
27
+ end?: Snippet;
25
28
  }
26
29
  declare const Navbar: import("svelte").Component<Props, {}, "">;
27
30
  type Navbar = ReturnType<typeof Navbar>;
@@ -7,6 +7,7 @@
7
7
  <script lang="ts">
8
8
  import type { NodeMetadata, NodeCategory, WorkflowFormat } from '../types/index.js';
9
9
  import LoadingSpinner from './LoadingSpinner.svelte';
10
+ import Input from './Input.svelte';
10
11
  import Icon from '@iconify/svelte';
11
12
  import { getNodeIcon, getCategoryIcon } from '../utils/icons.js';
12
13
  import { getCategoryColorToken } from '../utils/colors.js';
@@ -161,19 +162,17 @@
161
162
  >
162
163
  <!-- Search Section — visibility controlled by --fd-sidebar-search-display -->
163
164
  <div class="flowdrop-sidebar__search">
164
- <div class="flowdrop-join flowdrop-w--full">
165
- <div class="flowdrop-join__item flowdrop-flex--1">
166
- <input
167
- type="text"
168
- placeholder={m().layout.searchComponents}
169
- class="flowdrop-input flowdrop-join__item flowdrop-w--full"
170
- bind:value={searchInput}
171
- />
172
- </div>
173
- <button class="flowdrop-btn flowdrop-join__item" aria-label={m().layout.searchComponents}>
174
- <Icon icon="mdi:magnify" class="flowdrop-icon" />
175
- </button>
176
- </div>
165
+ <Input
166
+ type="text"
167
+ placeholder={m().layout.searchComponents}
168
+ aria-label={m().layout.searchComponents}
169
+ value={searchInput}
170
+ oninput={(e) => (searchInput = e.currentTarget.value)}
171
+ >
172
+ {#snippet leading()}
173
+ <Icon icon="mdi:magnify" />
174
+ {/snippet}
175
+ </Input>
177
176
  </div>
178
177
 
179
178
  <!-- Node Types List -->
@@ -594,106 +593,9 @@
594
593
  margin-left: 0;
595
594
  }
596
595
 
597
- /* Form Elements - Matching App Design System */
598
- .flowdrop-input {
599
- padding: 0.625rem 0.75rem;
600
- border: 1px solid var(--fd-border-strong);
601
- border-radius: var(--fd-radius-md);
602
- font-size: var(--fd-text-sm);
603
- color: var(--fd-foreground);
604
- background-color: var(--fd-background);
605
- transition:
606
- border-color var(--fd-transition-normal),
607
- box-shadow var(--fd-transition-normal);
608
- width: 100%;
609
- height: 2.5rem;
610
- box-sizing: border-box;
611
- }
612
-
613
- .flowdrop-input:focus {
614
- border-color: var(--fd-ring);
615
- }
616
-
617
- .flowdrop-input::placeholder {
618
- color: var(--fd-muted-foreground);
619
- }
620
-
621
- .flowdrop-btn {
622
- padding: 0.625rem 0.75rem;
623
- border-radius: var(--fd-radius-md);
624
- font-size: var(--fd-text-sm);
625
- font-weight: 500;
626
- cursor: pointer;
627
- transition: all var(--fd-transition-normal);
628
- border: 1px solid var(--fd-border-strong);
629
- display: flex;
630
- align-items: center;
631
- justify-content: center;
632
- gap: 0.5rem;
633
- background-color: var(--fd-muted);
634
- color: var(--fd-foreground);
635
- height: 2.5rem;
636
- min-width: 2.5rem;
637
- box-sizing: border-box;
638
- }
639
-
640
- .flowdrop-btn:hover {
641
- background-color: var(--fd-subtle);
642
- border-color: var(--fd-border-strong);
643
- }
644
-
645
- .flowdrop-btn:active {
646
- background-color: var(--fd-border);
647
- border-color: var(--fd-muted-foreground);
648
- }
649
-
650
- .flowdrop-btn:disabled {
651
- background-color: var(--fd-muted-foreground);
652
- border-color: var(--fd-muted-foreground);
653
- cursor: not-allowed;
654
- opacity: 0.6;
655
- }
656
-
657
- /* Join component styles - Seamless integration */
658
- .flowdrop-join {
659
- display: flex;
660
- width: 100%;
661
- border-radius: var(--fd-radius-md);
662
- overflow: hidden;
663
- border: 1px solid var(--fd-border-strong);
664
- background-color: var(--fd-background);
665
- }
666
-
667
- .flowdrop-join__item {
668
- border: none;
669
- border-radius: 0;
670
- background-color: transparent;
671
- }
672
-
673
- .flowdrop-join__item:first-child {
674
- border-right: 1px solid var(--fd-border-strong);
675
- flex: 1;
676
- }
677
-
678
- .flowdrop-join__item:last-child {
679
- border-left: none;
680
- background-color: var(--fd-muted);
681
- color: var(--fd-foreground);
682
- }
683
-
684
- .flowdrop-join__item:last-child:hover {
685
- background-color: var(--fd-subtle);
686
- }
687
-
688
- .flowdrop-join:focus-within {
689
- border-color: var(--fd-ring);
690
- }
596
+ /* Search field renders through the shared Input primitive (base.css). */
691
597
 
692
598
  /* Utility classes */
693
- .flowdrop-w--full {
694
- width: 100%;
695
- }
696
-
697
599
  .flowdrop-flex--1 {
698
600
  flex: 1;
699
601
  }
@@ -8,6 +8,7 @@
8
8
  <script lang="ts">
9
9
  import type { NodeMetadata, NodeCategory, WorkflowFormat, WorkflowNode } from '../types/index.js';
10
10
  import Icon from '@iconify/svelte';
11
+ import Input from './Input.svelte';
11
12
  import { getNodeIcon, getCategoryIcon } from '../utils/icons.js';
12
13
  import { getCategoryColorToken } from '../utils/colors.js';
13
14
  import { m } from '../messages/index.js';
@@ -115,12 +116,17 @@
115
116
 
116
117
  <!-- Search (hidden in minimal via --fd-sidebar-search-display) -->
117
118
  <div class="swap-picker__search">
118
- <input
119
+ <Input
119
120
  type="text"
120
121
  placeholder="Search node types..."
121
- class="swap-picker__input"
122
- bind:value={searchInput}
123
- />
122
+ aria-label="Search node types"
123
+ value={searchInput}
124
+ oninput={(e) => (searchInput = e.currentTarget.value)}
125
+ >
126
+ {#snippet leading()}
127
+ <Icon icon="mdi:magnify" />
128
+ {/snippet}
129
+ </Input>
124
130
  </div>
125
131
 
126
132
  <!-- Node list -->
@@ -298,28 +304,6 @@
298
304
  flex-shrink: 0;
299
305
  }
300
306
 
301
- .swap-picker__input {
302
- width: 100%;
303
- padding: 0.5rem 0.75rem;
304
- border: 1px solid var(--fd-border-strong);
305
- border-radius: var(--fd-radius-md);
306
- font-size: var(--fd-text-sm);
307
- color: var(--fd-foreground);
308
- background-color: var(--fd-background);
309
- box-sizing: border-box;
310
- transition:
311
- border-color var(--fd-transition-normal),
312
- box-shadow var(--fd-transition-normal);
313
- }
314
-
315
- .swap-picker__input:focus {
316
- border-color: var(--fd-ring);
317
- }
318
-
319
- .swap-picker__input::placeholder {
320
- color: var(--fd-muted-foreground);
321
- }
322
-
323
307
  .swap-picker__list {
324
308
  flex: 1;
325
309
  overflow-y: auto;
@@ -0,0 +1,53 @@
1
+ <!--
2
+ Select — typed wrapper over the shared `.flowdrop-input` system (base.css).
3
+
4
+ Renders the field shell (`.flowdrop-input .flowdrop-input--select`) plus a
5
+ built-in chevron overlay (`.flowdrop-select-wrap`), so selects match the exact
6
+ look of Input/Textarea. The native arrow is removed; the chevron tints to
7
+ --fd-primary on focus. Pass <option>/<optgroup> as children. Native attributes
8
+ (value, disabled, id, aria-*, onchange…) forward to the underlying <select>.
9
+
10
+ Internal for now; see Button.svelte / Input.svelte for the pattern.
11
+ -->
12
+
13
+ <script lang="ts">
14
+ import type { Snippet } from 'svelte';
15
+ import type { HTMLSelectAttributes } from 'svelte/elements';
16
+
17
+ // Omit native numeric `size` so our design-token size (matching Button) wins.
18
+ interface Props extends Omit<HTMLSelectAttributes, 'size'> {
19
+ /** Size — `md` is the base; `sm`/`lg` add a modifier */
20
+ size?: 'sm' | 'md' | 'lg';
21
+ /** Renders the error-border state */
22
+ invalid?: boolean;
23
+ /** Extra classes appended to the <select> */
24
+ class?: string;
25
+ /** The <option>/<optgroup> elements */
26
+ children: Snippet;
27
+ }
28
+
29
+ let { size = 'md', invalid = false, class: className = '', children, ...rest }: Props = $props();
30
+
31
+ const selectClass = $derived(
32
+ [
33
+ 'flowdrop-input',
34
+ 'flowdrop-input--select',
35
+ size === 'md' ? '' : `flowdrop-input--${size}`,
36
+ invalid ? 'flowdrop-input--invalid' : '',
37
+ className
38
+ ]
39
+ .filter(Boolean)
40
+ .join(' ')
41
+ );
42
+ </script>
43
+
44
+ <div class="flowdrop-select-wrap">
45
+ <select class={selectClass} {...rest}>
46
+ {@render children()}
47
+ </select>
48
+ <span class="flowdrop-select-wrap__icon" aria-hidden="true">
49
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
50
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" />
51
+ </svg>
52
+ </span>
53
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLSelectAttributes } from 'svelte/elements';
3
+ interface Props extends Omit<HTMLSelectAttributes, 'size'> {
4
+ /** Size — `md` is the base; `sm`/`lg` add a modifier */
5
+ size?: 'sm' | 'md' | 'lg';
6
+ /** Renders the error-border state */
7
+ invalid?: boolean;
8
+ /** Extra classes appended to the <select> */
9
+ class?: string;
10
+ /** The <option>/<optgroup> elements */
11
+ children: Snippet;
12
+ }
13
+ declare const Select: import("svelte").Component<Props, {}, "">;
14
+ type Select = ReturnType<typeof Select>;
15
+ export default Select;
@@ -0,0 +1,39 @@
1
+ <!--
2
+ Textarea — typed wrapper over the shared `.flowdrop-input` system (base.css),
3
+ with the `.flowdrop-input--textarea` modifier (min-height + vertical resize).
4
+
5
+ Shares the exact field look of Input/Select so multiline fields render
6
+ identically. All styling lives in base.css. Native attributes (value,
7
+ placeholder, rows, disabled, id, aria-*, oninput…) forward to <textarea>.
8
+
9
+ Internal for now; see Button.svelte / Input.svelte for the pattern.
10
+ -->
11
+
12
+ <script lang="ts">
13
+ import type { HTMLTextareaAttributes } from 'svelte/elements';
14
+
15
+ interface Props extends HTMLTextareaAttributes {
16
+ /** Size — `md` is the base; `sm`/`lg` add a modifier */
17
+ size?: 'sm' | 'md' | 'lg';
18
+ /** Renders the error-border state */
19
+ invalid?: boolean;
20
+ /** Extra classes appended to the textarea */
21
+ class?: string;
22
+ }
23
+
24
+ let { size = 'md', invalid = false, class: className = '', ...rest }: Props = $props();
25
+
26
+ const textareaClass = $derived(
27
+ [
28
+ 'flowdrop-input',
29
+ 'flowdrop-input--textarea',
30
+ size === 'md' ? '' : `flowdrop-input--${size}`,
31
+ invalid ? 'flowdrop-input--invalid' : '',
32
+ className
33
+ ]
34
+ .filter(Boolean)
35
+ .join(' ')
36
+ );
37
+ </script>
38
+
39
+ <textarea class={textareaClass} {...rest}></textarea>
@@ -0,0 +1,12 @@
1
+ import type { HTMLTextareaAttributes } from 'svelte/elements';
2
+ interface Props extends HTMLTextareaAttributes {
3
+ /** Size — `md` is the base; `sm`/`lg` add a modifier */
4
+ size?: 'sm' | 'md' | 'lg';
5
+ /** Renders the error-border state */
6
+ invalid?: boolean;
7
+ /** Extra classes appended to the textarea */
8
+ class?: string;
9
+ }
10
+ declare const Textarea: import("svelte").Component<Props, {}, "">;
11
+ type Textarea = ReturnType<typeof Textarea>;
12
+ export default Textarea;