@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
@@ -0,0 +1,315 @@
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 type { VariantSizes } from '../../index.js';
6
+ import type { CalendarProps } from './calendar-types.js';
7
+
8
+ let {
9
+ mode = 'single',
10
+ value = $bindable<Date | null>(null),
11
+ valueStart = $bindable<Date | null>(null),
12
+ valueEnd = $bindable<Date | null>(null),
13
+ minDate,
14
+ maxDate,
15
+ initialMonth,
16
+ weekStartsOn = 1,
17
+ hideHeader = false,
18
+ disabled = false,
19
+ size = Size.MD,
20
+ class: className = '',
21
+ onselect,
22
+ testId
23
+ }: CalendarProps = $props();
24
+
25
+ // Calendar dimensions don't map cleanly onto form-size tokens — a
26
+ // date grid's "size" means cell + panel width, not input height. So
27
+ // we maintain a dedicated ladder here, tuned so a Calendar at the
28
+ // same `size` as its enclosing DatePicker feels proportional.
29
+ type CalendarDensity = {
30
+ panel: string;
31
+ padding: string;
32
+ cell: string;
33
+ navBtn: string;
34
+ navIcon: string;
35
+ monthText: string;
36
+ dayHeaderText: string;
37
+ cellText: string;
38
+ };
39
+ const calendarSize: Record<VariantSizes, CalendarDensity> = {
40
+ [Size.XS]: {
41
+ panel: 'w-48',
42
+ padding: 'p-2',
43
+ cell: 'size-6',
44
+ navBtn: 'size-5',
45
+ navIcon: 'size-3',
46
+ monthText: 'text-xs',
47
+ dayHeaderText: 'text-[9px]',
48
+ cellText: 'text-[10px]'
49
+ },
50
+ [Size.SM]: {
51
+ panel: 'w-56',
52
+ padding: 'p-2.5',
53
+ cell: 'size-7',
54
+ navBtn: 'size-6',
55
+ navIcon: 'size-3.5',
56
+ monthText: 'text-xs',
57
+ dayHeaderText: 'text-[10px]',
58
+ cellText: 'text-xs'
59
+ },
60
+ [Size.MD]: {
61
+ panel: 'w-64',
62
+ padding: 'p-3',
63
+ cell: 'size-8',
64
+ navBtn: 'size-7',
65
+ navIcon: 'size-4',
66
+ monthText: 'text-sm',
67
+ dayHeaderText: 'text-[10px]',
68
+ cellText: 'text-xs'
69
+ },
70
+ [Size.LG]: {
71
+ panel: 'w-72',
72
+ padding: 'p-3.5',
73
+ cell: 'size-9',
74
+ navBtn: 'size-8',
75
+ navIcon: 'size-4',
76
+ monthText: 'text-base',
77
+ dayHeaderText: 'text-xs',
78
+ cellText: 'text-sm'
79
+ },
80
+ [Size.XL]: {
81
+ panel: 'w-80',
82
+ padding: 'p-4',
83
+ cell: 'size-10',
84
+ navBtn: 'size-9',
85
+ navIcon: 'size-5',
86
+ monthText: 'text-lg',
87
+ dayHeaderText: 'text-xs',
88
+ cellText: 'text-sm'
89
+ },
90
+ // Form controls cap at xl — see `form-size.ts`.
91
+ [Size.XXL]: {
92
+ panel: 'w-80',
93
+ padding: 'p-4',
94
+ cell: 'size-10',
95
+ navBtn: 'size-9',
96
+ navIcon: 'size-5',
97
+ monthText: 'text-lg',
98
+ dayHeaderText: 'text-xs',
99
+ cellText: 'text-sm'
100
+ }
101
+ };
102
+ const density = $derived(calendarSize[size]);
103
+
104
+ const anchor = $derived(initialMonth ?? value ?? valueStart ?? new Date());
105
+
106
+ let viewYear = $state(anchor.getFullYear());
107
+ let viewMonth = $state(anchor.getMonth());
108
+
109
+ function sameDay(a: Date | null | undefined, b: Date | null | undefined): boolean {
110
+ if (!a || !b) return false;
111
+ return (
112
+ a.getFullYear() === b.getFullYear() &&
113
+ a.getMonth() === b.getMonth() &&
114
+ a.getDate() === b.getDate()
115
+ );
116
+ }
117
+
118
+ function atStartOfDay(d: Date): Date {
119
+ // Pure function over plain Date — no reactivity needed.
120
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
121
+ const x = new Date(d);
122
+ x.setHours(0, 0, 0, 0);
123
+ return x;
124
+ }
125
+
126
+ function isOutOfBounds(date: Date): boolean {
127
+ if (minDate && date < atStartOfDay(minDate)) return true;
128
+ if (maxDate && date > atStartOfDay(maxDate)) return true;
129
+ return false;
130
+ }
131
+
132
+ function isInRange(date: Date): boolean {
133
+ if (mode !== 'range' || !valueStart) return false;
134
+ if (!valueEnd) return sameDay(date, valueStart);
135
+ return date >= atStartOfDay(valueStart) && date <= atStartOfDay(valueEnd);
136
+ }
137
+
138
+ function isRangeEdge(date: Date): boolean {
139
+ if (mode !== 'range') return false;
140
+ return sameDay(date, valueStart) || sameDay(date, valueEnd);
141
+ }
142
+
143
+ const today = $derived(atStartOfDay(new Date()));
144
+
145
+ const dayHeaders = $derived(() => {
146
+ const all = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
147
+ return weekStartsOn === 1 ? [...all.slice(1), all[0]] : all;
148
+ });
149
+
150
+ type Cell = {
151
+ date: Date;
152
+ inMonth: boolean;
153
+ disabled: boolean;
154
+ isToday: boolean;
155
+ isSelected: boolean;
156
+ isRangeEdge: boolean;
157
+ isInRange: boolean;
158
+ };
159
+
160
+ const cells = $derived.by<Cell[]>(() => {
161
+ const first = new Date(viewYear, viewMonth, 1);
162
+ const offset = (first.getDay() - weekStartsOn + 7) % 7;
163
+ const start = new Date(viewYear, viewMonth, 1 - offset);
164
+ const out: Cell[] = [];
165
+ for (let i = 0; i < 42; i++) {
166
+ const d = new Date(start.getFullYear(), start.getMonth(), start.getDate() + i);
167
+ out.push({
168
+ date: d,
169
+ inMonth: d.getMonth() === viewMonth,
170
+ disabled: disabled || isOutOfBounds(d),
171
+ isToday: sameDay(d, today),
172
+ isSelected: mode === 'single' ? sameDay(d, value ?? undefined) : isRangeEdge(d),
173
+ isRangeEdge: isRangeEdge(d),
174
+ isInRange: isInRange(d)
175
+ });
176
+ }
177
+ return out;
178
+ });
179
+
180
+ const monthLabel = $derived(
181
+ new Date(viewYear, viewMonth, 1).toLocaleDateString(undefined, {
182
+ month: 'long',
183
+ year: 'numeric'
184
+ })
185
+ );
186
+
187
+ function prevMonth() {
188
+ if (viewMonth === 0) {
189
+ viewMonth = 11;
190
+ viewYear -= 1;
191
+ } else {
192
+ viewMonth -= 1;
193
+ }
194
+ }
195
+
196
+ function nextMonth() {
197
+ if (viewMonth === 11) {
198
+ viewMonth = 0;
199
+ viewYear += 1;
200
+ } else {
201
+ viewMonth += 1;
202
+ }
203
+ }
204
+
205
+ function pick(date: Date, cellDisabled: boolean) {
206
+ if (cellDisabled) return;
207
+ if (mode === 'single') {
208
+ value = date;
209
+ onselect?.(date);
210
+ return;
211
+ }
212
+ // range
213
+ if (!valueStart || (valueStart && valueEnd)) {
214
+ valueStart = date;
215
+ valueEnd = null;
216
+ onselect?.({ from: date, to: null });
217
+ } else {
218
+ if (date < valueStart) {
219
+ valueEnd = valueStart;
220
+ valueStart = date;
221
+ } else {
222
+ valueEnd = date;
223
+ }
224
+ onselect?.({ from: valueStart, to: valueEnd });
225
+ }
226
+ }
227
+ </script>
228
+
229
+ <div
230
+ class={cn(
231
+ 'border-default-200 inline-block rounded-lg border bg-white shadow-xs select-none',
232
+ density.panel,
233
+ density.padding,
234
+ className
235
+ )}
236
+ data-testid={buildTestId('calendar', undefined, testId)}
237
+ >
238
+ {#if !hideHeader}
239
+ <div class="mb-2 flex items-center justify-between">
240
+ <button
241
+ type="button"
242
+ onclick={prevMonth}
243
+ class={cn(
244
+ 'text-default-500 hover:bg-default-100 hover:text-default-800 flex cursor-pointer items-center justify-center rounded',
245
+ density.navBtn
246
+ )}
247
+ aria-label="Previous month"
248
+ {disabled}
249
+ >
250
+ <svg class={density.navIcon} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
251
+ <path
252
+ fill-rule="evenodd"
253
+ d="M12.78 5.22a.75.75 0 0 1 0 1.06L9.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0z"
254
+ clip-rule="evenodd"
255
+ />
256
+ </svg>
257
+ </button>
258
+ <span class={cn('text-default-800 font-semibold', density.monthText)}>{monthLabel}</span>
259
+ <button
260
+ type="button"
261
+ onclick={nextMonth}
262
+ class={cn(
263
+ 'text-default-500 hover:bg-default-100 hover:text-default-800 flex cursor-pointer items-center justify-center rounded',
264
+ density.navBtn
265
+ )}
266
+ aria-label="Next month"
267
+ {disabled}
268
+ >
269
+ <svg class={density.navIcon} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
270
+ <path
271
+ fill-rule="evenodd"
272
+ d="M7.22 14.78a.75.75 0 0 1 0-1.06L10.94 10 7.22 6.28a.75.75 0 0 1 1.06-1.06l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0z"
273
+ clip-rule="evenodd"
274
+ />
275
+ </svg>
276
+ </button>
277
+ </div>
278
+ {/if}
279
+
280
+ <div
281
+ class={cn(
282
+ 'text-default-400 mb-1 grid grid-cols-7 gap-0.5 text-center font-medium',
283
+ density.dayHeaderText
284
+ )}
285
+ >
286
+ {#each dayHeaders() as d (d)}
287
+ <div>{d}</div>
288
+ {/each}
289
+ </div>
290
+
291
+ <div class="grid grid-cols-7 gap-0.5" role="grid">
292
+ {#each cells as cell (cell.date.toISOString())}
293
+ <button
294
+ type="button"
295
+ onclick={() => pick(cell.date, cell.disabled)}
296
+ disabled={cell.disabled}
297
+ aria-pressed={cell.isSelected}
298
+ aria-label={cell.date.toLocaleDateString()}
299
+ class={cn(
300
+ 'relative flex items-center justify-center rounded transition-colors',
301
+ density.cell,
302
+ density.cellText,
303
+ !cell.inMonth && 'text-default-300',
304
+ cell.inMonth && !cell.disabled && 'text-default-700 hover:bg-default-100 cursor-pointer',
305
+ cell.disabled && 'text-default-200 cursor-not-allowed',
306
+ cell.isToday && !cell.isSelected && 'ring-default-300 font-semibold ring-1',
307
+ cell.isInRange && !cell.isRangeEdge && 'bg-primary-50 text-primary-700 rounded-none',
308
+ cell.isSelected && 'bg-primary-500 hover:bg-primary-600 text-white'
309
+ )}
310
+ >
311
+ {cell.date.getDate()}
312
+ </button>
313
+ {/each}
314
+ </div>
315
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { CalendarProps } from './calendar-types.js';
2
+ declare const Calendar: import("svelte").Component<CalendarProps, {}, "value" | "valueStart" | "valueEnd">;
3
+ type Calendar = ReturnType<typeof Calendar>;
4
+ export default Calendar;
@@ -0,0 +1,54 @@
1
+ import type { ClassValue } from 'tailwind-variants';
2
+ import type { VariantSizes } from '../../index.js';
3
+ /**
4
+ * Calendar selection mode.
5
+ * - `'single'`: one date. Bind to `value`.
6
+ * - `'range'`: two dates. Bind to `valueStart` and `valueEnd`.
7
+ */
8
+ export type CalendarMode = 'single' | 'range';
9
+ export type CalendarProps = {
10
+ /** Selection mode. @default 'single' */
11
+ mode?: CalendarMode;
12
+ /** Selected date (single mode). Bindable. */
13
+ value?: Date | null;
14
+ /** Range start (range mode). Bindable. */
15
+ valueStart?: Date | null;
16
+ /** Range end (range mode). Bindable. */
17
+ valueEnd?: Date | null;
18
+ /** Earliest selectable date (inclusive). */
19
+ minDate?: Date;
20
+ /** Latest selectable date (inclusive). */
21
+ maxDate?: Date;
22
+ /**
23
+ * Which month to display initially. Defaults to the month of the current
24
+ * selection, or today if nothing is selected.
25
+ */
26
+ initialMonth?: Date;
27
+ /**
28
+ * Day the week starts on. `0` = Sunday, `1` = Monday. @default 1
29
+ */
30
+ weekStartsOn?: 0 | 1;
31
+ /** Hide the month/year header and navigation buttons. @default false */
32
+ hideHeader?: boolean;
33
+ /** Disable all interaction. */
34
+ disabled?: boolean;
35
+ /**
36
+ * Density preset. Scales day cells, headers, and overall panel width
37
+ * to match the tight form-size ladder so a `size="sm"` Calendar fits
38
+ * inside a `size="sm"` DatePicker / DateRange popover without feeling
39
+ * oversized. `2xl` aliases `xl`. @default 'md'
40
+ */
41
+ size?: VariantSizes;
42
+ /** Wrapper class. */
43
+ class?: ClassValue;
44
+ /**
45
+ * Fires when the user picks a date. For single mode receives one Date;
46
+ * for range mode receives `{ from, to }` — `to` is `null` until the
47
+ * second click completes the range.
48
+ */
49
+ onselect?: (selection: Date | {
50
+ from: Date | null;
51
+ to: Date | null;
52
+ }) => void;
53
+ testId?: string;
54
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export { default as Calendar } from './Calendar.svelte';
2
+ export type { CalendarProps, CalendarMode } from './calendar-types.js';
@@ -0,0 +1 @@
1
+ export { default as Calendar } from './Calendar.svelte';
@@ -0,0 +1,141 @@
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 Popover from '../../elements/popover/Popover.svelte';
7
+ import Calendar from '../calendar/Calendar.svelte';
8
+ import type { DatePickerProps } from './date-picker-types.js';
9
+
10
+ let {
11
+ name,
12
+ id = name,
13
+ label,
14
+ placeholder = 'Select date',
15
+ value = $bindable<Date | null>(null),
16
+ minDate,
17
+ maxDate,
18
+ format = 'yyyy-MM-dd',
19
+ clearable = true,
20
+ disabled = false,
21
+ readonly = false,
22
+ required = false,
23
+ size = Size.MD,
24
+ errors = [],
25
+ class: className = '',
26
+ onchange,
27
+ testId
28
+ }: DatePickerProps = $props();
29
+
30
+ let open = $state(false);
31
+
32
+ function formatDate(d: Date): string {
33
+ const yyyy = String(d.getFullYear());
34
+ const MM = String(d.getMonth() + 1).padStart(2, '0');
35
+ const dd = String(d.getDate()).padStart(2, '0');
36
+ return format.replace('yyyy', yyyy).replace('MM', MM).replace('dd', dd);
37
+ }
38
+
39
+ const display = $derived(value ? formatDate(value) : '');
40
+ const hasErrors = $derived(errors.length > 0);
41
+
42
+ const tokens = $derived(formSizeTokens[size]);
43
+
44
+ function clear(e: MouseEvent) {
45
+ e.stopPropagation();
46
+ value = null;
47
+ onchange?.(null);
48
+ }
49
+
50
+ function handleSelect(date: Date) {
51
+ value = date;
52
+ open = false;
53
+ onchange?.(date);
54
+ }
55
+ </script>
56
+
57
+ <div class={cn('w-full', className)} data-testid={buildTestId('date-picker', 'wrapper', testId)}>
58
+ {#if label}
59
+ <label for={id} class="text-default-700 mb-1 block text-sm font-medium">
60
+ {label}
61
+ {#if required}<span class="text-danger-500" aria-hidden="true">*</span>{/if}
62
+ </label>
63
+ {/if}
64
+
65
+ <!-- Hidden input carries the ISO value for native form posts. -->
66
+ <input type="hidden" {name} value={value ? value.toISOString() : ''} {required} />
67
+
68
+ <Popover trigger="manual" bind:open placement="bottom" {disabled}>
69
+ <button
70
+ {id}
71
+ type="button"
72
+ {disabled}
73
+ onclick={() => (open = !open)}
74
+ aria-haspopup="dialog"
75
+ aria-expanded={open}
76
+ aria-invalid={hasErrors}
77
+ class={cn(
78
+ 'flex w-full items-center justify-between border bg-white transition-colors',
79
+ 'focus-within:ring-2 focus-within:ring-offset-2 focus-within:outline-none',
80
+ tokens.height,
81
+ tokens.padX,
82
+ tokens.text,
83
+ tokens.gap,
84
+ tokens.radius,
85
+ tokens.shadow,
86
+ hasErrors
87
+ ? 'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500'
88
+ : 'border-default-300 focus-within:border-primary-500 focus-within:ring-primary-500',
89
+ disabled && 'cursor-not-allowed opacity-50',
90
+ !disabled && 'cursor-pointer'
91
+ )}
92
+ data-testid={buildTestId('date-picker', undefined, testId)}
93
+ >
94
+ <span class={cn('flex-1 text-left', !display && 'text-default-400')}>
95
+ {display || placeholder}
96
+ </span>
97
+ {#if clearable && value && !disabled && !readonly}
98
+ <button
99
+ type="button"
100
+ onclick={clear}
101
+ aria-label="Clear date"
102
+ class="text-default-400 hover:text-default-700 flex size-5 items-center justify-center rounded"
103
+ >
104
+ <svg class="size-3" viewBox="0 0 12 12" fill="none" aria-hidden="true">
105
+ <path
106
+ d="M3 3l6 6M9 3l-6 6"
107
+ stroke="currentColor"
108
+ stroke-width="1.5"
109
+ stroke-linecap="round"
110
+ />
111
+ </svg>
112
+ </button>
113
+ {:else}
114
+ <svg
115
+ class={cn('text-default-400', tokens.iconSize)}
116
+ viewBox="0 0 20 20"
117
+ fill="currentColor"
118
+ aria-hidden="true"
119
+ >
120
+ <path
121
+ fill-rule="evenodd"
122
+ d="M5.75 2a.75.75 0 0 1 .75.75V4h7V2.75a.75.75 0 0 1 1.5 0V4h.25A2.75 2.75 0 0 1 18 6.75v8.5A2.75 2.75 0 0 1 15.25 18H4.75A2.75 2.75 0 0 1 2 15.25v-8.5A2.75 2.75 0 0 1 4.75 4H5V2.75A.75.75 0 0 1 5.75 2zM4.75 5.5c-.69 0-1.25.56-1.25 1.25V8h13V6.75c0-.69-.56-1.25-1.25-1.25H4.75zM16.5 9.5h-13v5.75c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25V9.5z"
123
+ clip-rule="evenodd"
124
+ />
125
+ </svg>
126
+ {/if}
127
+ </button>
128
+
129
+ {#snippet content()}
130
+ <Calendar {value} {minDate} {maxDate} {size} onselect={(d) => handleSelect(d as Date)} />
131
+ {/snippet}
132
+ </Popover>
133
+
134
+ {#if hasErrors}
135
+ <ul class="mt-1 space-y-0.5" role="alert">
136
+ {#each errors as error (error)}
137
+ <li class="text-danger-600 text-xs">{error}</li>
138
+ {/each}
139
+ </ul>
140
+ {/if}
141
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { DatePickerProps } from './date-picker-types.js';
2
+ declare const DatePicker: import("svelte").Component<DatePickerProps, {}, "value">;
3
+ type DatePicker = ReturnType<typeof DatePicker>;
4
+ export default DatePicker;
@@ -0,0 +1,29 @@
1
+ import type { ClassValue } from 'tailwind-variants';
2
+ import type { VariantSizes } from '../../index.js';
3
+ /** Very small subset of common date-format tokens supported by DatePicker. */
4
+ export type DateFormatToken = 'yyyy' | 'MM' | 'dd';
5
+ export type DatePickerProps = {
6
+ name: string;
7
+ id?: string;
8
+ label?: string;
9
+ placeholder?: string;
10
+ /** Bindable selected date. Null when cleared. */
11
+ value?: Date | null;
12
+ minDate?: Date;
13
+ maxDate?: Date;
14
+ /**
15
+ * Display format for the input. Supports `yyyy`, `MM`, `dd` tokens
16
+ * and any literal separator (`-`, `/`, `.`, etc.). @default 'yyyy-MM-dd'
17
+ */
18
+ format?: string;
19
+ /** Allow clearing the selection with a × button. @default true */
20
+ clearable?: boolean;
21
+ disabled?: boolean;
22
+ readonly?: boolean;
23
+ required?: boolean;
24
+ size?: VariantSizes;
25
+ errors?: string[];
26
+ class?: ClassValue;
27
+ onchange?: (date: Date | null) => void;
28
+ testId?: string;
29
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import type { VariantSizes } from '../index.js';
2
+ /**
3
+ * Canonical size tokens used by every form control — `Input`, `Textarea`,
4
+ * `NumberInput`, `Tags`, `Checkbox`, and the select-based controls.
5
+ *
6
+ * The goal is that a form using `size="sm"` across mixed controls looks
7
+ * vertically aligned and typographically coordinated. Before this map,
8
+ * each component had its own inline size map that disagreed with the
9
+ * others (e.g. `Input` only supported SM/MD/LG while `Textarea` supported
10
+ * all six, and `MD` meant `text-base` in `Input` but `text-sm` in
11
+ * `Textarea`).
12
+ *
13
+ * Multi-line controls (Textarea) read only `padX` / `padY` / `text` —
14
+ * height is content-driven. Single-line controls also read `height`.
15
+ */
16
+ export type FormSizeTokens = {
17
+ /** Control height for single-line inputs (e.g. `h-10`). */
18
+ height: string;
19
+ /** Horizontal padding token (e.g. `px-3`). */
20
+ padX: string;
21
+ /** Vertical padding, used by multi-line controls and chip rows. */
22
+ padY: string;
23
+ /** Font size token (e.g. `text-base`). */
24
+ text: string;
25
+ /** Inline gap between leading icon and text (e.g. `gap-2`). */
26
+ gap: string;
27
+ /** Inline icon dimension (e.g. `size-4`). */
28
+ iconSize: string;
29
+ /** Rounded corner radius that scales with the control (e.g. `rounded-lg`). */
30
+ radius: string;
31
+ /**
32
+ * Drop shadow. `shadow-none` at the smallest sizes so `xs` / inline
33
+ * table cells don't stand out against their container.
34
+ */
35
+ shadow: string;
36
+ };
37
+ export declare const formSizeTokens: Record<VariantSizes, FormSizeTokens>;
@@ -0,0 +1,67 @@
1
+ import { Size } from '../variants.js';
2
+ // Tight ladder by design — we prefer compact, focused form controls
3
+ // over bulky ones. `md` (the default) sits at `h-7` / `text-xs` so a
4
+ // stock form reads dense; consumers who want roomier controls opt up to
5
+ // `lg` / `xl`. `xs` is unmistakably inline-text-in-a-table-cell — 20px
6
+ // tall, no shadow, barely-there rounded corners.
7
+ //
8
+ // `2xl` (`Size.XXL`) intentionally aliases `xl` for form controls: form
9
+ // fields don't need a sixth, jumbo tier — anything larger reads as a
10
+ // display element rather than an input. We still accept the token (the
11
+ // `Size` enum exposes it library-wide) and quietly fall back to `xl`
12
+ // rather than requiring every component to exclude it from its size type.
13
+ const xl = {
14
+ height: 'h-11',
15
+ padX: 'px-3.5',
16
+ padY: 'py-2.5',
17
+ text: 'text-base',
18
+ gap: 'gap-2.5',
19
+ iconSize: 'size-5',
20
+ radius: 'rounded-lg',
21
+ shadow: 'shadow-xs'
22
+ };
23
+ export const formSizeTokens = {
24
+ [Size.XS]: {
25
+ height: 'h-5',
26
+ padX: 'px-1',
27
+ padY: 'py-0',
28
+ text: 'text-xs',
29
+ gap: 'gap-1',
30
+ iconSize: 'size-3',
31
+ radius: 'rounded-sm',
32
+ shadow: 'shadow-none'
33
+ },
34
+ [Size.SM]: {
35
+ height: 'h-6',
36
+ padX: 'px-1.5',
37
+ padY: 'py-0.5',
38
+ text: 'text-xs',
39
+ gap: 'gap-1',
40
+ iconSize: 'size-3',
41
+ radius: 'rounded',
42
+ shadow: 'shadow-xs'
43
+ },
44
+ [Size.MD]: {
45
+ height: 'h-7',
46
+ padX: 'px-2',
47
+ padY: 'py-1',
48
+ text: 'text-xs',
49
+ gap: 'gap-1.5',
50
+ iconSize: 'size-3.5',
51
+ radius: 'rounded-md',
52
+ shadow: 'shadow-xs'
53
+ },
54
+ [Size.LG]: {
55
+ height: 'h-9',
56
+ padX: 'px-2.5',
57
+ padY: 'py-1.5',
58
+ text: 'text-sm',
59
+ gap: 'gap-2',
60
+ iconSize: 'size-4',
61
+ radius: 'rounded-md',
62
+ shadow: 'shadow-xs'
63
+ },
64
+ [Size.XL]: xl,
65
+ // Form controls cap at xl visually — see comment above.
66
+ [Size.XXL]: xl
67
+ };