@makolabs/ripple 2.5.9 → 3.0.1

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 (186) hide show
  1. package/README.md +403 -497
  2. package/dist/adapters/storage/S3Adapter.d.ts +49 -1
  3. package/dist/adapters/storage/S3Adapter.js +38 -1
  4. package/dist/adapters/storage/types.d.ts +20 -0
  5. package/dist/ai/AIChatInterface.svelte +2 -1
  6. package/dist/ai/AIChatInterface.svelte.d.ts +2 -1
  7. package/dist/ai/CodeRenderer.svelte +7 -2
  8. package/dist/ai/CodeRenderer.svelte.d.ts +2 -1
  9. package/dist/ai/ComposeDropdown.svelte +1 -1
  10. package/dist/ai/MessageBox.svelte +3 -3
  11. package/dist/ai/MessageBox.svelte.d.ts +3 -2
  12. package/dist/ai/ThinkingDisplay.svelte +4 -3
  13. package/dist/ai/ThinkingDisplay.svelte.d.ts +2 -1
  14. package/dist/ai/ai-types.d.ts +55 -1
  15. package/dist/button/Button.svelte +5 -5
  16. package/dist/button/button-types.d.ts +49 -4
  17. package/dist/button/button.d.ts +9 -9
  18. package/dist/button/button.js +6 -6
  19. package/dist/charts/Chart.svelte +8 -16
  20. package/dist/charts/chart-types.d.ts +78 -1
  21. package/dist/drawer/Drawer.svelte +6 -26
  22. package/dist/drawer/drawer-types.d.ts +33 -12
  23. package/dist/drawer/drawer.d.ts +3 -3
  24. package/dist/drawer/drawer.js +1 -1
  25. package/dist/elements/accordion/Accordion.svelte +6 -17
  26. package/dist/elements/accordion/accordion-types.d.ts +53 -6
  27. package/dist/elements/alert/Alert.svelte +3 -0
  28. package/dist/elements/badge/Badge.svelte +1 -1
  29. package/dist/elements/badge/badge-types.d.ts +22 -0
  30. package/dist/elements/badge/badge.d.ts +3 -3
  31. package/dist/elements/badge/badge.js +1 -1
  32. package/dist/elements/combobox/ComboBox.svelte +244 -0
  33. package/dist/elements/combobox/ComboBox.svelte.d.ts +4 -0
  34. package/dist/elements/combobox/combobox-types.d.ts +41 -0
  35. package/dist/elements/combobox/combobox-types.js +1 -0
  36. package/dist/elements/context-menu/ContextMenu.svelte +137 -0
  37. package/dist/elements/context-menu/ContextMenu.svelte.d.ts +4 -0
  38. package/dist/elements/context-menu/context-menu-types.d.ts +40 -0
  39. package/dist/elements/context-menu/context-menu-types.js +1 -0
  40. package/dist/elements/dropdown/Dropdown.svelte +1 -1
  41. package/dist/elements/dropdown/Select.svelte +4 -1
  42. package/dist/elements/dropdown/dropdown-types.d.ts +114 -0
  43. package/dist/elements/dropdown/dropdown.d.ts +3 -3
  44. package/dist/elements/dropdown/dropdown.js +2 -2
  45. package/dist/elements/dropdown/select.d.ts +3 -108
  46. package/dist/elements/dropdown/select.js +38 -47
  47. package/dist/elements/empty-state/EmptyState.svelte +1 -1
  48. package/dist/elements/empty-state/empty-state-types.d.ts +32 -1
  49. package/dist/elements/empty-state/empty-state.d.ts +3 -3
  50. package/dist/elements/empty-state/empty-state.js +2 -2
  51. package/dist/elements/file-upload/FileUpload.svelte +5 -0
  52. package/dist/elements/file-upload/file-upload-types.d.ts +59 -0
  53. package/dist/elements/pagination/Pagination.svelte +53 -21
  54. package/dist/elements/pagination/Pagination.svelte.d.ts +33 -5
  55. package/dist/elements/popover/Popover.svelte +254 -0
  56. package/dist/elements/popover/Popover.svelte.d.ts +4 -0
  57. package/dist/elements/popover/index.d.ts +2 -0
  58. package/dist/elements/popover/index.js +1 -0
  59. package/dist/elements/popover/popover-types.d.ts +60 -0
  60. package/dist/elements/popover/popover-types.js +1 -0
  61. package/dist/elements/progress/Progress.svelte +32 -7
  62. package/dist/elements/progress/progress-types.d.ts +48 -1
  63. package/dist/elements/skeleton/Skeleton.svelte +56 -0
  64. package/dist/elements/skeleton/Skeleton.svelte.d.ts +4 -0
  65. package/dist/elements/skeleton/index.d.ts +2 -0
  66. package/dist/elements/skeleton/index.js +1 -0
  67. package/dist/elements/skeleton/skeleton-types.d.ts +50 -0
  68. package/dist/elements/skeleton/skeleton-types.js +1 -0
  69. package/dist/elements/spinner/Spinner.svelte +1 -1
  70. package/dist/elements/spinner/spinner-types.d.ts +20 -0
  71. package/dist/elements/spinner/spinner.d.ts +3 -3
  72. package/dist/elements/spinner/spinner.js +2 -2
  73. package/dist/elements/tooltip/Tooltip.svelte +108 -11
  74. package/dist/elements/tooltip/tooltip-types.d.ts +49 -1
  75. package/dist/file-browser/FileBrowser.svelte +21 -12
  76. package/dist/filters/CompactFilters.svelte +221 -33
  77. package/dist/filters/CompactFilters.svelte.d.ts +1 -1
  78. package/dist/filters/FilterBar.svelte +184 -0
  79. package/dist/filters/FilterBar.svelte.d.ts +4 -0
  80. package/dist/filters/FilterPopover.svelte +346 -0
  81. package/dist/filters/FilterPopover.svelte.d.ts +4 -0
  82. package/dist/filters/date-presets.d.ts +15 -0
  83. package/dist/filters/date-presets.js +107 -0
  84. package/dist/filters/filter-types.d.ts +69 -3
  85. package/dist/filters/index.d.ts +5 -0
  86. package/dist/filters/index.js +4 -0
  87. package/dist/filters/sync-filters-to-url.svelte.d.ts +37 -0
  88. package/dist/filters/sync-filters-to-url.svelte.js +114 -0
  89. package/dist/forms/Checkbox.svelte +24 -9
  90. package/dist/forms/DateRange.svelte +23 -6
  91. package/dist/forms/Input.svelte +19 -19
  92. package/dist/forms/MarketSelector.svelte +9 -4
  93. package/dist/forms/NumberInput.svelte +14 -18
  94. package/dist/forms/RadioGroup.svelte +127 -0
  95. package/dist/forms/RadioGroup.svelte.d.ts +4 -0
  96. package/dist/forms/SegmentedControl.svelte +11 -4
  97. package/dist/forms/Slider.svelte +72 -3
  98. package/dist/forms/Tags.svelte +44 -14
  99. package/dist/forms/Textarea.svelte +121 -0
  100. package/dist/forms/Textarea.svelte.d.ts +4 -0
  101. package/dist/forms/Toggle.svelte +30 -22
  102. package/dist/forms/calendar/Calendar.svelte +315 -0
  103. package/dist/forms/calendar/Calendar.svelte.d.ts +4 -0
  104. package/dist/forms/calendar/calendar-types.d.ts +54 -0
  105. package/dist/forms/calendar/calendar-types.js +1 -0
  106. package/dist/forms/calendar/index.d.ts +2 -0
  107. package/dist/forms/calendar/index.js +1 -0
  108. package/dist/forms/date-picker/DatePicker.svelte +141 -0
  109. package/dist/forms/date-picker/DatePicker.svelte.d.ts +4 -0
  110. package/dist/forms/date-picker/date-picker-types.d.ts +29 -0
  111. package/dist/forms/date-picker/date-picker-types.js +1 -0
  112. package/dist/forms/form-size.d.ts +37 -0
  113. package/dist/forms/form-size.js +67 -0
  114. package/dist/forms/form-types.d.ts +430 -6
  115. package/dist/forms/market/market-selector-types.d.ts +52 -1
  116. package/dist/forms/segmented-control.d.ts +5 -2
  117. package/dist/forms/segmented-control.js +25 -13
  118. package/dist/forms/slider.d.ts +3 -3
  119. package/dist/forms/slider.js +37 -30
  120. package/dist/funcs/user-management.remote.js +1 -1
  121. package/dist/header/Breadcrumbs.svelte +4 -20
  122. package/dist/header/PageHeader.svelte +6 -14
  123. package/dist/header/breadcrumbs.d.ts +3 -11
  124. package/dist/header/breadcrumbs.js +10 -5
  125. package/dist/header/header-types.d.ts +62 -11
  126. package/dist/index.d.ts +35 -9
  127. package/dist/index.js +24 -4
  128. package/dist/layout/activity-list/ActivityList.svelte +13 -7
  129. package/dist/layout/activity-list/activity-list-types.d.ts +46 -7
  130. package/dist/layout/card/Card.svelte +12 -15
  131. package/dist/layout/card/MetricCard.svelte +50 -32
  132. package/dist/layout/card/card-types.d.ts +114 -4
  133. package/dist/layout/navbar/navbar-types.d.ts +48 -0
  134. package/dist/layout/navbar/navbar.d.ts +3 -3
  135. package/dist/layout/navbar/navbar.js +2 -2
  136. package/dist/layout/sidebar/Sidebar.svelte +87 -11
  137. package/dist/layout/sidebar/sidebar-types.d.ts +60 -1
  138. package/dist/layout/stepper/Stepper.svelte +288 -0
  139. package/dist/layout/stepper/Stepper.svelte.d.ts +4 -0
  140. package/dist/layout/stepper/stepper-types.d.ts +80 -0
  141. package/dist/layout/stepper/stepper-types.js +1 -0
  142. package/dist/layout/table/Table.svelte +91 -85
  143. package/dist/layout/table/table-types.d.ts +148 -24
  144. package/dist/layout/table/table.d.ts +3 -3
  145. package/dist/layout/table/table.js +2 -2
  146. package/dist/layout/tabs/Tab.svelte +6 -2
  147. package/dist/layout/tabs/Tab.svelte.d.ts +4 -1
  148. package/dist/layout/tabs/TabGroup.svelte +9 -2
  149. package/dist/layout/tabs/tabs-types.d.ts +63 -0
  150. package/dist/layout/tabs/tabs.d.ts +3 -3
  151. package/dist/layout/tabs/tabs.js +12 -6
  152. package/dist/modal/ConfirmDialog.svelte +65 -0
  153. package/dist/modal/ConfirmDialog.svelte.d.ts +4 -0
  154. package/dist/modal/Modal.svelte +6 -26
  155. package/dist/modal/confirm-dialog-types.d.ts +39 -0
  156. package/dist/modal/confirm-dialog-types.js +1 -0
  157. package/dist/modal/modal-types.d.ts +51 -12
  158. package/dist/modal/modal.d.ts +3 -3
  159. package/dist/modal/modal.js +3 -3
  160. package/dist/pipeline/Pipeline.svelte +8 -3
  161. package/dist/pipeline/pipeline-types.d.ts +55 -3
  162. package/dist/pipeline/pipeline.d.ts +18 -3
  163. package/dist/pipeline/pipeline.js +7 -2
  164. package/dist/server/s3.d.ts +35 -3
  165. package/dist/sonner/Toaster.svelte +29 -0
  166. package/dist/sonner/Toaster.svelte.d.ts +4 -0
  167. package/dist/sonner/index.d.ts +21 -0
  168. package/dist/sonner/index.js +20 -0
  169. package/dist/user-management/UserManagement.svelte +22 -16
  170. package/dist/user-management/UserModal.svelte +10 -7
  171. package/dist/user-management/UserTable.svelte +16 -17
  172. package/dist/user-management/UserViewModal.svelte +11 -11
  173. package/dist/user-management/user-management-types.d.ts +118 -31
  174. package/dist/variants.d.ts +1 -1
  175. package/dist/variants.js +1 -1
  176. package/package.json +7 -4
  177. package/dist/config/ai.d.ts +0 -13
  178. package/dist/config/ai.js +0 -44
  179. package/dist/elements/empty-state/EmptyStateTestWrapper.svelte +0 -25
  180. package/dist/elements/empty-state/EmptyStateTestWrapper.svelte.d.ts +0 -8
  181. package/dist/elements/tooltip/TooltipTestWrapper.svelte +0 -14
  182. package/dist/elements/tooltip/TooltipTestWrapper.svelte.d.ts +0 -7
  183. package/dist/helper/deprecation.d.ts +0 -14
  184. package/dist/helper/deprecation.js +0 -24
  185. package/dist/modal/ModalFooterTestWrapper.svelte +0 -17
  186. package/dist/modal/ModalFooterTestWrapper.svelte.d.ts +0 -8
@@ -4,7 +4,7 @@
4
4
  import { buildTestId } from '../helper/testid.js';
5
5
  import { slider } from './slider.js';
6
6
  import { Size } from '../variants.js';
7
- import type { SliderProps } from '../index.js';
7
+ import type { SliderProps, SliderTick } from '../index.js';
8
8
 
9
9
  interface EnumOption {
10
10
  value: string | number;
@@ -20,7 +20,7 @@
20
20
  label,
21
21
  mode = 'single' as SliderMode,
22
22
  disabled = false,
23
- size = Size.BASE,
23
+ size = Size.MD,
24
24
  errors = [],
25
25
  class: className = '',
26
26
  min = 0,
@@ -38,9 +38,45 @@
38
38
  maximumFractionDigits: 1,
39
39
  minimumFractionDigits: 0
40
40
  },
41
+ tickInterval,
42
+ ticks,
41
43
  testId
42
44
  }: SliderProps = $props();
43
45
 
46
+ /**
47
+ * Resolved tick list for single/range modes. Explicit `ticks` wins;
48
+ * otherwise generated from `tickInterval`. Empty for enum mode.
49
+ */
50
+ const resolvedTicks = $derived.by<SliderTick[]>(() => {
51
+ if (mode === 'enum') return [];
52
+ if (ticks && ticks.length > 0) {
53
+ return ticks
54
+ .map((t) => (typeof t === 'number' ? { value: t } : t))
55
+ .filter((t) => t.value >= min && t.value <= max);
56
+ }
57
+ if (tickInterval && tickInterval > 0) {
58
+ const out: SliderTick[] = [];
59
+ for (let v = min; v <= max; v += tickInterval) {
60
+ out.push({ value: v });
61
+ }
62
+ // Include max if rounding left it out
63
+ if (out[out.length - 1]?.value !== max) out.push({ value: max });
64
+ return out;
65
+ }
66
+ return [];
67
+ });
68
+
69
+ function getTickPosition(tickValue: number): string {
70
+ return `${((tickValue - min) / (max - min)) * 100}%`;
71
+ }
72
+
73
+ function isTickInRange(tickValue: number): boolean {
74
+ if (mode === 'range') return tickValue >= valueStart && tickValue <= valueEnd;
75
+ if (mode === 'single' && typeof value === 'number')
76
+ return tickValue >= min && tickValue <= value;
77
+ return false;
78
+ }
79
+
44
80
  $effect(() => {
45
81
  if (mode === 'enum' && options.length > 0 && value === min) {
46
82
  value = options[0].value;
@@ -63,7 +99,17 @@
63
99
  })
64
100
  );
65
101
 
66
- const baseClass = $derived(cn(base(), { 'mb-12': mode === 'enum' }, className));
102
+ const hasTickLabels = $derived(resolvedTicks.some((t) => t.label !== undefined));
103
+ const baseClass = $derived(
104
+ cn(
105
+ base(),
106
+ {
107
+ 'mb-12': mode === 'enum' || hasTickLabels,
108
+ 'mb-6': !hasTickLabels && resolvedTicks.length > 0 && mode !== 'enum'
109
+ },
110
+ className
111
+ )
112
+ );
67
113
  const trackClass = $derived(cn(track()));
68
114
  const rangeClass = $derived(cn(range()));
69
115
  const thumbClass = $derived(cn(thumb()));
@@ -274,6 +320,29 @@
274
320
  aria-label={label}
275
321
  onclick={handleTrackClick}
276
322
  >
323
+ {#each resolvedTicks as tick (tick.value)}
324
+ <div
325
+ class={cn(
326
+ 'absolute top-1/2 h-2 w-px -translate-x-1/2 -translate-y-1/2',
327
+ isTickInRange(tick.value) ? 'bg-primary-400' : 'bg-default-300'
328
+ )}
329
+ style="left: {getTickPosition(tick.value)}"
330
+ aria-hidden="true"
331
+ ></div>
332
+ {#if tick.label !== undefined}
333
+ <div
334
+ class={cn(
335
+ markClass,
336
+ 'text-default-500 pointer-events-none top-4 text-[10px] whitespace-nowrap'
337
+ )}
338
+ style="left: {getTickPosition(tick.value)}"
339
+ aria-hidden="true"
340
+ >
341
+ {tick.label}
342
+ </div>
343
+ {/if}
344
+ {/each}
345
+
277
346
  {#if mode === 'range'}
278
347
  <div class={rangeClass} style="width: {getRangeWidth()}; left: {getRangeLeft()}"></div>
279
348
  <div
@@ -2,6 +2,10 @@
2
2
  import Badge from '../elements/badge/Badge.svelte';
3
3
  import { Size } from '../variants.js';
4
4
  import { cn } from '../helper/cls.js';
5
+ import { formSizeTokens } from './form-size.js';
6
+ import { fade } from 'svelte/transition';
7
+ import { flip } from 'svelte/animate';
8
+ import { quintOut } from 'svelte/easing';
5
9
  import type { TagsProps } from '../index.js';
6
10
 
7
11
  let {
@@ -10,7 +14,7 @@
10
14
  label,
11
15
  errors,
12
16
  placeholder = 'Type and press enter to add tags...',
13
- size = Size.BASE,
17
+ size = Size.MD,
14
18
  class: className = '',
15
19
  suggestions = [],
16
20
  onaddtag: onAddTag,
@@ -31,6 +35,25 @@
31
35
  showSuggestions ? suggestions.filter(isUnselected).filter(matchesInput).slice(0, 5) : []
32
36
  );
33
37
 
38
+ const tokens = $derived(formSizeTokens[size]);
39
+
40
+ // Chip size shifted one tier down from the Tags container so chip
41
+ // text matches the container text (e.g. Tags `md` uses `text-xs`, so
42
+ // Badge `sm` — which is also `text-xs` — lines up, instead of Badge
43
+ // `md` which jumps to `text-sm`).
44
+ const chipSize = $derived(
45
+ (
46
+ {
47
+ [Size.XS]: Size.XS,
48
+ [Size.SM]: Size.XS,
49
+ [Size.MD]: Size.SM,
50
+ [Size.LG]: Size.MD,
51
+ [Size.XL]: Size.LG,
52
+ [Size.XXL]: Size.LG
53
+ } as const
54
+ )[size]
55
+ );
56
+
34
57
  function handleKeydown(event: KeyboardEvent) {
35
58
  if (event.key === 'Enter') {
36
59
  event.preventDefault();
@@ -105,12 +128,14 @@
105
128
 
106
129
  const containerClass = $derived(
107
130
  cn(
108
- 'relative flex flex-wrap gap-2 rounded-lg border bg-white shadow-xs px-3 py-2',
131
+ 'relative flex flex-wrap gap-2 border bg-white',
132
+ tokens.radius,
133
+ tokens.shadow,
134
+ tokens.padX,
135
+ tokens.padY,
109
136
  'border-default-300 focus-within:border-primary-500 focus-within:ring-2 focus-within:ring-primary-500 focus-within:ring-offset-2',
110
- {
111
- 'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500':
112
- errors?.length
113
- },
137
+ errors?.length &&
138
+ 'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500',
114
139
  className
115
140
  )
116
141
  );
@@ -135,9 +160,15 @@
135
160
  {/if}
136
161
  <div class={containerClass} onfocusout={handleFocusOut}>
137
162
  {#each value as tag (tag)}
138
- <Badge {size} color="info" onclose={() => handleTagRemoval(tag)} class="shadow-xs">
139
- {tag}
140
- </Badge>
163
+ <div
164
+ class="inline-flex"
165
+ transition:fade={{ duration: 250, easing: quintOut }}
166
+ animate:flip={{ duration: 300, easing: quintOut }}
167
+ >
168
+ <Badge size={chipSize} color="info" onclose={() => handleTagRemoval(tag)} class="shadow-xs">
169
+ {tag}
170
+ </Badge>
171
+ </div>
141
172
  {/each}
142
173
  <input
143
174
  bind:this={inputRef}
@@ -145,11 +176,10 @@
145
176
  {name}
146
177
  id={name}
147
178
  {placeholder}
148
- class={cn('placeholder:text-default-400 min-w-[120px] flex-1 bg-transparent outline-none', {
149
- 'text-sm': size === Size.SM,
150
- 'text-base': size === Size.BASE,
151
- 'text-lg': size === Size.LG
152
- })}
179
+ class={cn(
180
+ 'placeholder:text-default-400 min-w-[120px] flex-1 bg-transparent outline-none',
181
+ tokens.text
182
+ )}
153
183
  type="text"
154
184
  autocomplete="off"
155
185
  onkeydown={handleKeydown}
@@ -0,0 +1,121 @@
1
+ <script lang="ts">
2
+ import { cn } from '../helper/cls.js';
3
+ import { buildTestId } from '../helper/testid.js';
4
+ import { Size } from '../variants.js';
5
+ import { formSizeTokens } from './form-size.js';
6
+ import type { TextareaProps } from '../index.js';
7
+
8
+ let {
9
+ name,
10
+ id = name,
11
+ label,
12
+ placeholder,
13
+ value = $bindable(''),
14
+ disabled = false,
15
+ readonly = false,
16
+ rows = 3,
17
+ autoGrow = false,
18
+ maxRows = 10,
19
+ maxLength,
20
+ showCount = false,
21
+ size = Size.MD,
22
+ errors = [],
23
+ class: className = '',
24
+ oninput,
25
+ onblur,
26
+ testId
27
+ }: TextareaProps = $props();
28
+
29
+ let el = $state<HTMLTextAreaElement | undefined>();
30
+
31
+ const tokens = $derived(formSizeTokens[size]);
32
+ const hasErrors = $derived(errors.length > 0);
33
+
34
+ const textareaClasses = $derived(
35
+ cn(
36
+ 'w-full border bg-white transition-colors',
37
+ 'placeholder:text-default-400',
38
+ 'focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2',
39
+ tokens.radius,
40
+ tokens.shadow,
41
+ tokens.padX,
42
+ tokens.padY,
43
+ tokens.text,
44
+ hasErrors
45
+ ? 'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500'
46
+ : 'border-default-300 focus-within:border-primary-500 focus-within:ring-primary-500',
47
+ disabled && 'opacity-50 cursor-not-allowed',
48
+ autoGrow ? 'resize-none overflow-hidden' : 'resize-y',
49
+ className
50
+ )
51
+ );
52
+
53
+ /**
54
+ * When autoGrow is on, measure scrollHeight and clamp to maxRows.
55
+ * Runs after value changes so the textarea expands/shrinks with content.
56
+ */
57
+ function resize() {
58
+ if (!autoGrow || !el) return;
59
+ el.style.height = 'auto';
60
+ const lineHeight = parseFloat(getComputedStyle(el).lineHeight || '20');
61
+ const padding =
62
+ parseFloat(getComputedStyle(el).paddingTop) + parseFloat(getComputedStyle(el).paddingBottom);
63
+ const maxHeight = lineHeight * maxRows + padding;
64
+ el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
65
+ el.style.overflowY = el.scrollHeight > maxHeight ? 'auto' : 'hidden';
66
+ }
67
+
68
+ $effect(() => {
69
+ void value;
70
+ resize();
71
+ });
72
+
73
+ function handleInput(e: Event) {
74
+ const v = (e.currentTarget as HTMLTextAreaElement).value;
75
+ value = v;
76
+ oninput?.(v);
77
+ }
78
+
79
+ function handleBlur(e: FocusEvent) {
80
+ onblur?.((e.currentTarget as HTMLTextAreaElement).value);
81
+ }
82
+ </script>
83
+
84
+ <div class="w-full" data-testid={buildTestId('textarea', 'wrapper', testId)}>
85
+ {#if label}
86
+ <label for={id} class="text-default-700 mb-1 block text-sm font-medium">
87
+ {label}
88
+ </label>
89
+ {/if}
90
+ <textarea
91
+ bind:this={el}
92
+ {id}
93
+ {name}
94
+ {placeholder}
95
+ {disabled}
96
+ {readonly}
97
+ {rows}
98
+ maxlength={maxLength}
99
+ class={textareaClasses}
100
+ aria-invalid={hasErrors}
101
+ aria-describedby={hasErrors ? `${name}-error` : undefined}
102
+ data-testid={buildTestId('textarea', undefined, testId)}
103
+ {value}
104
+ oninput={handleInput}
105
+ onblur={handleBlur}
106
+ ></textarea>
107
+
108
+ {#if showCount && maxLength !== undefined}
109
+ <div class="text-default-400 mt-1 text-right text-xs">
110
+ {value?.length ?? 0} / {maxLength}
111
+ </div>
112
+ {/if}
113
+
114
+ {#if hasErrors}
115
+ <ul id="{name}-error" class="mt-1 space-y-0.5" role="alert">
116
+ {#each errors as error (error)}
117
+ <li class="text-danger-600 text-xs">{error}</li>
118
+ {/each}
119
+ </ul>
120
+ {/if}
121
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { TextareaProps } from '../index.js';
2
+ declare const Textarea: import("svelte").Component<TextareaProps, {}, "value">;
3
+ type Textarea = ReturnType<typeof Textarea>;
4
+ export default Textarea;
@@ -2,6 +2,7 @@
2
2
  import { cn } from '../helper/cls.js';
3
3
  import { Color, Size } from '../variants.js';
4
4
  import { buildTestId } from '../helper/testid.js';
5
+ import { formSizeTokens } from './form-size.js';
5
6
  import type { ToggleProps, VariantColors, VariantSizes } from '../index.js';
6
7
 
7
8
  let {
@@ -10,18 +11,18 @@
10
11
  label,
11
12
  disabled = false,
12
13
  class: className = '',
13
- size = Size.BASE,
14
+ size = Size.MD,
14
15
  color = Color.PRIMARY,
15
16
  value = $bindable(false),
16
17
  errors = [],
17
18
  offColor = 'bg-default-200',
18
- onColor,
19
+ activeColor,
19
20
  testId,
20
21
  ...restProps
21
22
  }: ToggleProps = $props();
22
23
 
23
- const activeColor = $derived(
24
- onColor ||
24
+ const resolvedActiveColor = $derived(
25
+ activeColor ||
25
26
  (
26
27
  {
27
28
  [Color.DEFAULT]: 'bg-default-800',
@@ -35,13 +36,19 @@
35
36
  )[color]
36
37
  );
37
38
 
39
+ const tokens = $derived(formSizeTokens[size]);
40
+
41
+ // Track + thumb + on-state offset ladder. Chosen to roughly match the
42
+ // height of an Input at the same `size` (20/24/28/36/44px) so a row
43
+ // with a Toggle and an Input reads evenly. 2xl aliases xl — form
44
+ // controls cap at xl.
38
45
  const toggleSize = $derived(
39
46
  (
40
47
  {
41
- [Size.XS]: 'w-8 h-4',
42
- [Size.SM]: 'w-8 h-4',
43
- [Size.BASE]: 'w-10 h-5',
44
- [Size.LG]: 'w-12 h-6',
48
+ [Size.XS]: 'w-6 h-3',
49
+ [Size.SM]: 'w-7 h-3.5',
50
+ [Size.MD]: 'w-8 h-4',
51
+ [Size.LG]: 'w-10 h-5',
45
52
  [Size.XL]: 'w-12 h-6',
46
53
  [Size.XXL]: 'w-12 h-6'
47
54
  } satisfies Record<VariantSizes, string>
@@ -51,10 +58,10 @@
51
58
  const thumbSize = $derived(
52
59
  (
53
60
  {
54
- [Size.XS]: 'h-3 w-3',
55
- [Size.SM]: 'h-3 w-3',
56
- [Size.BASE]: 'h-4 w-4',
57
- [Size.LG]: 'h-5 w-5',
61
+ [Size.XS]: 'h-2 w-2',
62
+ [Size.SM]: 'h-2.5 w-2.5',
63
+ [Size.MD]: 'h-3 w-3',
64
+ [Size.LG]: 'h-4 w-4',
58
65
  [Size.XL]: 'h-5 w-5',
59
66
  [Size.XXL]: 'h-5 w-5'
60
67
  } satisfies Record<VariantSizes, string>
@@ -64,10 +71,10 @@
64
71
  const thumbPosition = $derived(
65
72
  (
66
73
  {
67
- [Size.XS]: value ? 'translate-x-4' : 'translate-x-0.5',
68
- [Size.SM]: value ? 'translate-x-4' : 'translate-x-0.5',
69
- [Size.BASE]: value ? 'translate-x-5' : 'translate-x-0.5',
70
- [Size.LG]: value ? 'translate-x-6' : 'translate-x-0.5',
74
+ [Size.XS]: value ? 'translate-x-3' : 'translate-x-0.5',
75
+ [Size.SM]: value ? 'translate-x-3.5' : 'translate-x-0.5',
76
+ [Size.MD]: value ? 'translate-x-4' : 'translate-x-0.5',
77
+ [Size.LG]: value ? 'translate-x-5' : 'translate-x-0.5',
71
78
  [Size.XL]: value ? 'translate-x-6' : 'translate-x-0.5',
72
79
  [Size.XXL]: value ? 'translate-x-6' : 'translate-x-0.5'
73
80
  } satisfies Record<VariantSizes, string>
@@ -92,7 +99,7 @@
92
99
  cn(
93
100
  'relative inline-flex items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
94
101
  toggleSize,
95
- value ? activeColor : offColor,
102
+ value ? resolvedActiveColor : offColor,
96
103
  {
97
104
  'opacity-50 cursor-not-allowed': disabled,
98
105
  'cursor-pointer': !disabled,
@@ -112,11 +119,12 @@
112
119
  );
113
120
 
114
121
  const labelClasses = $derived(
115
- cn('text-sm font-medium', {
116
- 'text-default-700': !errors.length,
117
- 'text-danger-600': errors.length,
118
- 'opacity-50': disabled
119
- })
122
+ cn(
123
+ 'font-medium',
124
+ tokens.text,
125
+ errors.length ? 'text-danger-600' : 'text-default-700',
126
+ disabled && 'opacity-50'
127
+ )
120
128
  );
121
129
 
122
130
  function handleKeyDown(event: KeyboardEvent) {