ui-svelte 0.1.0

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 (238) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/dist/charts/ArcChart.svelte +320 -0
  4. package/dist/charts/ArcChart.svelte.d.ts +26 -0
  5. package/dist/charts/AreaChart.svelte +495 -0
  6. package/dist/charts/AreaChart.svelte.d.ts +32 -0
  7. package/dist/charts/BarChart.svelte +504 -0
  8. package/dist/charts/BarChart.svelte.d.ts +38 -0
  9. package/dist/charts/Candlestick.svelte +527 -0
  10. package/dist/charts/Candlestick.svelte.d.ts +38 -0
  11. package/dist/charts/LineChart.svelte +365 -0
  12. package/dist/charts/LineChart.svelte.d.ts +36 -0
  13. package/dist/charts/PieChart.svelte +311 -0
  14. package/dist/charts/PieChart.svelte.d.ts +28 -0
  15. package/dist/charts/css/arc-chart.css +237 -0
  16. package/dist/charts/css/area-chart.css +289 -0
  17. package/dist/charts/css/bar-chart.css +167 -0
  18. package/dist/charts/css/candlestick.css +197 -0
  19. package/dist/charts/css/line-chart.css +202 -0
  20. package/dist/charts/css/pie-chart.css +199 -0
  21. package/dist/control/Audio.svelte +212 -0
  22. package/dist/control/Audio.svelte.d.ts +8 -0
  23. package/dist/control/Button.svelte +116 -0
  24. package/dist/control/Button.svelte.d.ts +22 -0
  25. package/dist/control/IconButton.svelte +104 -0
  26. package/dist/control/IconButton.svelte.d.ts +17 -0
  27. package/dist/control/Record.svelte +430 -0
  28. package/dist/control/Record.svelte.d.ts +11 -0
  29. package/dist/control/ToggleTheme.svelte +21 -0
  30. package/dist/control/ToggleTheme.svelte.d.ts +8 -0
  31. package/dist/control/Video.svelte +222 -0
  32. package/dist/control/Video.svelte.d.ts +10 -0
  33. package/dist/control/css/btn.css +206 -0
  34. package/dist/control/css/media.css +78 -0
  35. package/dist/control/css/video.css +58 -0
  36. package/dist/css/animations.css +27 -0
  37. package/dist/css/base.css +192 -0
  38. package/dist/css/utilities.css +136 -0
  39. package/dist/display/Accordion.svelte +98 -0
  40. package/dist/display/Accordion.svelte.d.ts +20 -0
  41. package/dist/display/Alert.svelte +65 -0
  42. package/dist/display/Alert.svelte.d.ts +15 -0
  43. package/dist/display/Avatar.svelte +80 -0
  44. package/dist/display/Avatar.svelte.d.ts +13 -0
  45. package/dist/display/Badge.svelte +46 -0
  46. package/dist/display/Badge.svelte.d.ts +11 -0
  47. package/dist/display/Card.svelte +94 -0
  48. package/dist/display/Card.svelte.d.ts +21 -0
  49. package/dist/display/Carousel.svelte +359 -0
  50. package/dist/display/Carousel.svelte.d.ts +25 -0
  51. package/dist/display/ChatBox.svelte +249 -0
  52. package/dist/display/ChatBox.svelte.d.ts +18 -0
  53. package/dist/display/Chip.svelte +67 -0
  54. package/dist/display/Chip.svelte.d.ts +17 -0
  55. package/dist/display/Code.svelte +56 -0
  56. package/dist/display/Code.svelte.d.ts +9 -0
  57. package/dist/display/Collapsible.svelte +71 -0
  58. package/dist/display/Collapsible.svelte.d.ts +15 -0
  59. package/dist/display/Divider.svelte +32 -0
  60. package/dist/display/Divider.svelte.d.ts +10 -0
  61. package/dist/display/Empty.svelte +462 -0
  62. package/dist/display/Empty.svelte.d.ts +11 -0
  63. package/dist/display/Icon.svelte +20 -0
  64. package/dist/display/Icon.svelte.d.ts +11 -0
  65. package/dist/display/Item.svelte +119 -0
  66. package/dist/display/Item.svelte.d.ts +24 -0
  67. package/dist/display/Loading.svelte +8 -0
  68. package/dist/display/Loading.svelte.d.ts +26 -0
  69. package/dist/display/Marquee.svelte +164 -0
  70. package/dist/display/Marquee.svelte.d.ts +21 -0
  71. package/dist/display/Section.svelte +63 -0
  72. package/dist/display/Section.svelte.d.ts +16 -0
  73. package/dist/display/Table.svelte +407 -0
  74. package/dist/display/Table.svelte.d.ts +32 -0
  75. package/dist/display/TypeWriter.svelte +23 -0
  76. package/dist/display/TypeWriter.svelte.d.ts +11 -0
  77. package/dist/display/User.svelte +0 -0
  78. package/dist/display/User.svelte.d.ts +26 -0
  79. package/dist/display/css/accordion.css +98 -0
  80. package/dist/display/css/alert.css +51 -0
  81. package/dist/display/css/avatar.css +158 -0
  82. package/dist/display/css/badge.css +47 -0
  83. package/dist/display/css/card.css +231 -0
  84. package/dist/display/css/carousel.css +156 -0
  85. package/dist/display/css/chat-box.css +188 -0
  86. package/dist/display/css/chip.css +91 -0
  87. package/dist/display/css/code.css +19 -0
  88. package/dist/display/css/collapsible.css +86 -0
  89. package/dist/display/css/divider.css +54 -0
  90. package/dist/display/css/empty.css +8 -0
  91. package/dist/display/css/item.css +149 -0
  92. package/dist/display/css/listbox.css +24 -0
  93. package/dist/display/css/marquee.css +138 -0
  94. package/dist/display/css/section.css +85 -0
  95. package/dist/display/css/table.css +361 -0
  96. package/dist/form/Checkbox.svelte +45 -0
  97. package/dist/form/Checkbox.svelte.d.ts +13 -0
  98. package/dist/form/ComboBox.svelte +448 -0
  99. package/dist/form/ComboBox.svelte.d.ts +29 -0
  100. package/dist/form/CsvField.svelte +389 -0
  101. package/dist/form/CsvField.svelte.d.ts +21 -0
  102. package/dist/form/DateField.svelte +292 -0
  103. package/dist/form/DateField.svelte.d.ts +18 -0
  104. package/dist/form/Dropzone.svelte +196 -0
  105. package/dist/form/Dropzone.svelte.d.ts +30 -0
  106. package/dist/form/ImageCropper.svelte +254 -0
  107. package/dist/form/ImageCropper.svelte.d.ts +14 -0
  108. package/dist/form/PasswordField.svelte +170 -0
  109. package/dist/form/PasswordField.svelte.d.ts +28 -0
  110. package/dist/form/PhoneField.svelte +485 -0
  111. package/dist/form/PhoneField.svelte.d.ts +25 -0
  112. package/dist/form/PinField.svelte +139 -0
  113. package/dist/form/PinField.svelte.d.ts +17 -0
  114. package/dist/form/RadioGroup.svelte +70 -0
  115. package/dist/form/RadioGroup.svelte.d.ts +19 -0
  116. package/dist/form/Select.svelte +350 -0
  117. package/dist/form/Select.svelte.d.ts +26 -0
  118. package/dist/form/Slider.svelte +60 -0
  119. package/dist/form/Slider.svelte.d.ts +15 -0
  120. package/dist/form/TextField.svelte +154 -0
  121. package/dist/form/TextField.svelte.d.ts +31 -0
  122. package/dist/form/Textarea.svelte +137 -0
  123. package/dist/form/Textarea.svelte.d.ts +27 -0
  124. package/dist/form/Toggle.svelte +45 -0
  125. package/dist/form/Toggle.svelte.d.ts +13 -0
  126. package/dist/form/css/checkbox.css +46 -0
  127. package/dist/form/css/combo-box.css +69 -0
  128. package/dist/form/css/control.css +177 -0
  129. package/dist/form/css/csv-field.css +0 -0
  130. package/dist/form/css/date.css +56 -0
  131. package/dist/form/css/dropzone.css +133 -0
  132. package/dist/form/css/field.css +17 -0
  133. package/dist/form/css/image-cropper.css +155 -0
  134. package/dist/form/css/password.css +35 -0
  135. package/dist/form/css/radio-group.css +57 -0
  136. package/dist/form/css/select.css +18 -0
  137. package/dist/form/css/slider.css +80 -0
  138. package/dist/form/css/textarea.css +130 -0
  139. package/dist/form/css/toggle.css +27 -0
  140. package/dist/form/js/countries.d.ts +13 -0
  141. package/dist/form/js/countries.js +307 -0
  142. package/dist/form/js/phone-examples.d.ts +248 -0
  143. package/dist/form/js/phone-examples.js +247 -0
  144. package/dist/hooks/use-auth.svelte.d.ts +11 -0
  145. package/dist/hooks/use-auth.svelte.js +59 -0
  146. package/dist/hooks/use-chat.svelte.d.ts +40 -0
  147. package/dist/hooks/use-chat.svelte.js +265 -0
  148. package/dist/hooks/use-clipboard.svelte.d.ts +9 -0
  149. package/dist/hooks/use-clipboard.svelte.js +52 -0
  150. package/dist/hooks/use-fetch.svelte.d.ts +11 -0
  151. package/dist/hooks/use-fetch.svelte.js +38 -0
  152. package/dist/hooks/use-form.svelte.d.ts +31 -0
  153. package/dist/hooks/use-form.svelte.js +110 -0
  154. package/dist/hooks/use-localstorage.svelte.d.ts +3 -0
  155. package/dist/hooks/use-localstorage.svelte.js +26 -0
  156. package/dist/hooks/use-scroll.svelte.d.ts +6 -0
  157. package/dist/hooks/use-scroll.svelte.js +34 -0
  158. package/dist/hooks/use-search.svelte.d.ts +49 -0
  159. package/dist/hooks/use-search.svelte.js +229 -0
  160. package/dist/hooks/use-table.svelte.d.ts +85 -0
  161. package/dist/hooks/use-table.svelte.js +362 -0
  162. package/dist/hooks/use-websocket.svelte.d.ts +18 -0
  163. package/dist/hooks/use-websocket.svelte.js +79 -0
  164. package/dist/icons/index.d.ts +132 -0
  165. package/dist/icons/index.js +132 -0
  166. package/dist/index.css +115 -0
  167. package/dist/index.d.ts +76 -0
  168. package/dist/index.js +76 -0
  169. package/dist/layout/AppBar.svelte +94 -0
  170. package/dist/layout/AppBar.svelte.d.ts +17 -0
  171. package/dist/layout/Footer.svelte +94 -0
  172. package/dist/layout/Footer.svelte.d.ts +17 -0
  173. package/dist/layout/FooterLinks.svelte +28 -0
  174. package/dist/layout/FooterLinks.svelte.d.ts +11 -0
  175. package/dist/layout/Provider.svelte +52 -0
  176. package/dist/layout/Provider.svelte.d.ts +10 -0
  177. package/dist/layout/Scaffold.svelte +46 -0
  178. package/dist/layout/Scaffold.svelte.d.ts +15 -0
  179. package/dist/layout/Sidebar.svelte +40 -0
  180. package/dist/layout/Sidebar.svelte.d.ts +13 -0
  181. package/dist/layout/css/app-bar.css +35 -0
  182. package/dist/layout/css/bottom-bar.css +12 -0
  183. package/dist/layout/css/footer-links.css +17 -0
  184. package/dist/layout/css/footer.css +35 -0
  185. package/dist/layout/css/scaffold.css +15 -0
  186. package/dist/layout/css/sidebar.css +17 -0
  187. package/dist/navigation/BottomNav.svelte +0 -0
  188. package/dist/navigation/BottomNav.svelte.d.ts +26 -0
  189. package/dist/navigation/NavMenu.svelte +254 -0
  190. package/dist/navigation/SideNav.svelte +249 -0
  191. package/dist/navigation/Tabs.svelte +79 -0
  192. package/dist/navigation/Tabs.svelte.d.ts +19 -0
  193. package/dist/navigation/css/bottom-nav.css +0 -0
  194. package/dist/navigation/css/nav-menu.css +168 -0
  195. package/dist/navigation/css/side-nav.css +244 -0
  196. package/dist/navigation/css/tabs.css +118 -0
  197. package/dist/overlay/AlertDialog.svelte +0 -0
  198. package/dist/overlay/AlertDialog.svelte.d.ts +26 -0
  199. package/dist/overlay/Command.svelte +0 -0
  200. package/dist/overlay/Command.svelte.d.ts +26 -0
  201. package/dist/overlay/Drawer.svelte +129 -0
  202. package/dist/overlay/Drawer.svelte.d.ts +20 -0
  203. package/dist/overlay/Dropdown.svelte +140 -0
  204. package/dist/overlay/Modal.svelte +102 -0
  205. package/dist/overlay/Modal.svelte.d.ts +19 -0
  206. package/dist/overlay/PopoverStack.svelte +0 -0
  207. package/dist/overlay/PopoverStack.svelte.d.ts +26 -0
  208. package/dist/overlay/Toast.svelte +83 -0
  209. package/dist/overlay/Toast.svelte.d.ts +9 -0
  210. package/dist/overlay/Tooltip.svelte +140 -0
  211. package/dist/overlay/Tooltip.svelte.d.ts +12 -0
  212. package/dist/overlay/css/drawer.css +75 -0
  213. package/dist/overlay/css/dropdown.css +24 -0
  214. package/dist/overlay/css/hovercard.css +11 -0
  215. package/dist/overlay/css/modal.css +51 -0
  216. package/dist/overlay/css/toast.css +80 -0
  217. package/dist/overlay/css/tooltip.css +89 -0
  218. package/dist/stores/i18n.svelte.d.ts +16 -0
  219. package/dist/stores/i18n.svelte.js +137 -0
  220. package/dist/stores/theme.svelte.d.ts +5 -0
  221. package/dist/stores/theme.svelte.js +55 -0
  222. package/dist/stores/toast.svelte.d.ts +19 -0
  223. package/dist/stores/toast.svelte.js +38 -0
  224. package/dist/types.d.ts +75 -0
  225. package/dist/types.js +1 -0
  226. package/dist/utils/charts.d.ts +27 -0
  227. package/dist/utils/charts.js +140 -0
  228. package/dist/utils/class-names.d.ts +1 -0
  229. package/dist/utils/class-names.js +3 -0
  230. package/dist/utils/click-outside.d.ts +3 -0
  231. package/dist/utils/click-outside.js +9 -0
  232. package/dist/utils/popover.d.ts +3 -0
  233. package/dist/utils/popover.js +17 -0
  234. package/dist/utils/ulid.d.ts +1 -0
  235. package/dist/utils/ulid.js +22 -0
  236. package/dist/utils/validate-schema.d.ts +2 -0
  237. package/dist/utils/validate-schema.js +97 -0
  238. package/package.json +69 -0
@@ -0,0 +1,389 @@
1
+ <script lang="ts">
2
+ import { Button, Modal, Select, TextField, Table, useTable, Chip } from '../index.js';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ type Props = {
6
+ requiredColumns?: string[];
7
+ onMappingComplete?: (data: {
8
+ file: File;
9
+ mapping: Record<string, string>;
10
+ data: any[];
11
+ originalData: any[];
12
+ }) => void;
13
+ acceptMultipleFiles?: boolean;
14
+ variant?:
15
+ | 'primary'
16
+ | 'secondary'
17
+ | 'muted'
18
+ | 'success'
19
+ | 'info'
20
+ | 'danger'
21
+ | 'warning'
22
+ | 'outlined'
23
+ | 'ghost';
24
+ size?: 'xs' | 'sm' | 'md' | 'lg';
25
+ class?: string;
26
+ buttonText?: string;
27
+ isSolid?: boolean;
28
+ showPreview?: boolean;
29
+ previewRows?: number;
30
+ previewPageSize?: number;
31
+ };
32
+
33
+ let {
34
+ requiredColumns = [],
35
+ onMappingComplete = () => {},
36
+ acceptMultipleFiles = false,
37
+ variant = 'primary',
38
+ size = 'md',
39
+ class: className,
40
+ buttonText = 'Seleccionar archivo CSV',
41
+ isSolid = false,
42
+ showPreview = true,
43
+ previewRows = 5,
44
+ previewPageSize = 10
45
+ }: Props = $props();
46
+
47
+ let fileInput = $state<HTMLInputElement | null>(null);
48
+ let selectedFile = $state<File | null>(null);
49
+ let showModal = $state(false);
50
+ let csvColumns = $state<string[]>([]);
51
+ let columnMapping = $state<Record<string, string>>({});
52
+ let parseError = $state<string | null>(null);
53
+ let csvData = $state<any[]>([]);
54
+ let mappedData = $state<any[]>([]);
55
+ let showPreviewTable = $state(false);
56
+
57
+ function handleFileSelect(event: Event) {
58
+ const target = event.target as HTMLInputElement;
59
+ const file = target.files?.[0];
60
+ if (!file) return;
61
+
62
+ if (!file.name.endsWith('.csv')) {
63
+ parseError = 'Por favor selecciona un archivo CSV válido';
64
+ return;
65
+ }
66
+
67
+ selectedFile = file;
68
+ parseError = null;
69
+ parseCSV(file);
70
+ }
71
+
72
+ function parseCSV(file: File) {
73
+ const reader = new FileReader();
74
+
75
+ reader.onload = (e) => {
76
+ try {
77
+ const text = e.target?.result as string;
78
+ const lines = text.split('\n').filter((line) => line.trim());
79
+
80
+ if (lines.length === 0) {
81
+ parseError = 'El archivo CSV está vacío';
82
+ return;
83
+ }
84
+
85
+ const delimiter = text.includes(';') ? ';' : ',';
86
+ const headers = lines[0].split(delimiter).map((h) => h.trim().replace(/^"|"$/g, ''));
87
+ csvColumns = headers;
88
+
89
+ csvData = lines.slice(1).map((line) => {
90
+ const values = line.split(delimiter).map((v) => v.trim().replace(/^"|"$/g, ''));
91
+ const row: Record<string, string> = {};
92
+ headers.forEach((header, idx) => {
93
+ row[header] = values[idx] || '';
94
+ });
95
+ return row;
96
+ });
97
+
98
+ const initialMapping: Record<string, string> = {};
99
+ requiredColumns.forEach((reqCol) => {
100
+ const match = headers.find((h) => h.toLowerCase() === reqCol.toLowerCase());
101
+ initialMapping[reqCol] = match || '';
102
+ });
103
+ columnMapping = initialMapping;
104
+
105
+ showModal = true;
106
+ } catch (error: any) {
107
+ parseError = 'Error al parsear el archivo CSV: ' + error.message;
108
+ }
109
+ };
110
+
111
+ reader.onerror = () => {
112
+ parseError = 'Error al leer el archivo';
113
+ };
114
+
115
+ reader.readAsText(file);
116
+ }
117
+
118
+ function confirmMapping() {
119
+ const unmappedColumns = requiredColumns.filter((col) => !columnMapping[col]);
120
+
121
+ if (unmappedColumns.length > 0) {
122
+ parseError = `Por favor mapea las siguientes columnas: ${unmappedColumns.join(', ')}`;
123
+ return;
124
+ }
125
+
126
+ mappedData = csvData.map((row) => {
127
+ const newRow: Record<string, string> = {};
128
+ Object.entries(columnMapping).forEach(([requiredCol, csvCol]) => {
129
+ newRow[requiredCol] = row[csvCol] || '';
130
+ });
131
+ return newRow;
132
+ });
133
+
134
+ onMappingComplete({
135
+ file: selectedFile!,
136
+ mapping: columnMapping,
137
+ data: mappedData,
138
+ originalData: csvData
139
+ });
140
+
141
+ if (showPreview) {
142
+ showPreviewTable = true;
143
+ }
144
+
145
+ closeModal();
146
+ }
147
+
148
+ function closeModal() {
149
+ showModal = false;
150
+ parseError = null;
151
+ }
152
+
153
+ function getPreviewData(requiredCol: string): string {
154
+ const csvCol = columnMapping[requiredCol];
155
+ if (!csvCol || csvData.length === 0) return 'N/A';
156
+ return csvData[0][csvCol] || 'N/A';
157
+ }
158
+
159
+ function clearImport() {
160
+ mappedData = [];
161
+ showPreviewTable = false;
162
+ selectedFile = null;
163
+ if (fileInput) {
164
+ fileInput.value = '';
165
+ }
166
+ }
167
+
168
+ const selectOptions = $derived(
169
+ csvColumns.map((col) => ({
170
+ id: col,
171
+ label: col
172
+ }))
173
+ );
174
+
175
+ const previewTable = $derived(
176
+ mappedData.length > 0
177
+ ? useTable({
178
+ data: mappedData,
179
+ columns: requiredColumns.map((col) => ({
180
+ label: col.charAt(0).toUpperCase() + col.slice(1),
181
+ field: col,
182
+ sortable: true
183
+ })),
184
+ initialPageSize: previewPageSize
185
+ })
186
+ : null
187
+ );
188
+ </script>
189
+
190
+ <div class={className}>
191
+ <input
192
+ type="file"
193
+ accept=".csv"
194
+ bind:this={fileInput}
195
+ onchange={handleFileSelect}
196
+ style="display: none;"
197
+ />
198
+
199
+ <Button
200
+ {variant}
201
+ {size}
202
+ {isSolid}
203
+ startIcon="fluent:document-arrow-up-24-regular"
204
+ onclick={() => fileInput?.click()}
205
+ >
206
+ {selectedFile ? selectedFile.name : buttonText}
207
+ </Button>
208
+
209
+ {#if parseError}
210
+ <div class="csv-field-error">
211
+ {parseError}
212
+ </div>
213
+ {/if}
214
+
215
+ {#if showPreviewTable && mappedData.length > 0 && previewTable}
216
+ <div class="csv-preview-container">
217
+ <div class="csv-preview-header">
218
+ <div class="csv-preview-info">
219
+ <h4 class="csv-preview-title">
220
+ Datos Importados
221
+ <span class="csv-preview-count">({mappedData.length} registros)</span>
222
+ </h4>
223
+ <div class="csv-mapping-summary">
224
+ {#each Object.entries(columnMapping) as [required, csv], index}
225
+ {#if index < 3}
226
+ <Chip size="sm" variant="muted">
227
+ {required} → {csv}
228
+ </Chip>
229
+ {/if}
230
+ {/each}
231
+ {#if Object.entries(columnMapping).length > 3}
232
+ <Chip size="sm" variant="muted">
233
+ +{Object.entries(columnMapping).length - 3} más
234
+ </Chip>
235
+ {/if}
236
+ </div>
237
+ </div>
238
+ <Button variant="ghost" size="sm" onclick={clearImport}>
239
+ <span class="csv-clear-icon">✕</span>
240
+ Limpiar
241
+ </Button>
242
+ </div>
243
+
244
+ <Table table={previewTable} showPagination isStriped hasDividers size="sm" />
245
+ </div>
246
+ {/if}
247
+ </div>
248
+
249
+ <Modal bind:open={showModal} onclose={closeModal} variant="surface" closeOnOverlay>
250
+ {#snippet header()}
251
+ <h2 class="csv-modal-title">Mapear Columnas del CSV</h2>
252
+ {/snippet}
253
+
254
+ <div class="csv-modal-body">
255
+ <p class="csv-instructions">
256
+ Asocia cada columna requerida con la columna correspondiente del archivo CSV:
257
+ </p>
258
+
259
+ <div class="csv-mapping-list">
260
+ {#each requiredColumns as requiredCol, index}
261
+ <div class="csv-mapping-item">
262
+ <Select
263
+ label={requiredCol}
264
+ options={selectOptions}
265
+ bind:value={columnMapping[requiredCol]}
266
+ placeholder="-- Seleccionar columna --"
267
+ variant="outlined"
268
+ size="md"
269
+ isFloatLabel
270
+ />
271
+
272
+ {#if columnMapping[requiredCol]}
273
+ <TextField
274
+ value={getPreviewData(requiredCol)}
275
+ label="Vista previa"
276
+ variant="muted"
277
+ size="sm"
278
+ isFloatLabel
279
+ islabelActive
280
+ />
281
+ {/if}
282
+ </div>
283
+ {/each}
284
+ </div>
285
+ </div>
286
+
287
+ {#snippet footer()}
288
+ <div class="csv-modal-footer">
289
+ <Button variant="ghost" size="md" onclick={closeModal}>Cancelar</Button>
290
+ <Button variant="primary" size="md" isSolid onclick={confirmMapping}>Confirmar mapeo</Button>
291
+ </div>
292
+ {/snippet}
293
+ </Modal>
294
+
295
+ <style>
296
+ .csv-field-error {
297
+ margin-top: 0.625rem;
298
+ padding: 0.625rem;
299
+ background: rgb(254 226 226);
300
+ color: rgb(220 38 38);
301
+ border-radius: 0.375rem;
302
+ font-size: 0.875rem;
303
+ }
304
+
305
+ .csv-modal-title {
306
+ margin: 0;
307
+ font-size: 1.25rem;
308
+ font-weight: 600;
309
+ color: rgb(17 24 39);
310
+ }
311
+
312
+ .csv-modal-body {
313
+ display: flex;
314
+ flex-direction: column;
315
+ gap: 1.25rem;
316
+ }
317
+
318
+ .csv-instructions {
319
+ margin: 0;
320
+ color: rgb(107 114 128);
321
+ font-size: 0.875rem;
322
+ }
323
+
324
+ .csv-mapping-list {
325
+ display: flex;
326
+ flex-direction: column;
327
+ gap: 1.5rem;
328
+ }
329
+
330
+ .csv-mapping-item {
331
+ display: flex;
332
+ flex-direction: column;
333
+ gap: 0.75rem;
334
+ }
335
+
336
+ .csv-modal-footer {
337
+ display: flex;
338
+ justify-content: flex-end;
339
+ gap: 0.75rem;
340
+ }
341
+
342
+ .csv-preview-container {
343
+ margin-top: 1.5rem;
344
+ padding: 1.25rem;
345
+ background: rgb(249 250 251);
346
+ border: 1px solid rgb(229 231 235);
347
+ border-radius: 0.75rem;
348
+ }
349
+
350
+ .csv-preview-header {
351
+ display: flex;
352
+ justify-content: space-between;
353
+ align-items: flex-start;
354
+ margin-bottom: 1rem;
355
+ gap: 1rem;
356
+ }
357
+
358
+ .csv-preview-info {
359
+ flex: 1;
360
+ min-width: 0;
361
+ }
362
+
363
+ .csv-preview-title {
364
+ margin: 0 0 0.75rem 0;
365
+ font-size: 1rem;
366
+ font-weight: 600;
367
+ color: rgb(17 24 39);
368
+ display: flex;
369
+ align-items: center;
370
+ gap: 0.5rem;
371
+ }
372
+
373
+ .csv-preview-count {
374
+ font-size: 0.875rem;
375
+ font-weight: 400;
376
+ color: rgb(107 114 128);
377
+ }
378
+
379
+ .csv-mapping-summary {
380
+ display: flex;
381
+ flex-wrap: wrap;
382
+ gap: 0.5rem;
383
+ }
384
+
385
+ .csv-clear-icon {
386
+ font-size: 1.125rem;
387
+ line-height: 1;
388
+ }
389
+ </style>
@@ -0,0 +1,21 @@
1
+ type Props = {
2
+ requiredColumns?: string[];
3
+ onMappingComplete?: (data: {
4
+ file: File;
5
+ mapping: Record<string, string>;
6
+ data: any[];
7
+ originalData: any[];
8
+ }) => void;
9
+ acceptMultipleFiles?: boolean;
10
+ variant?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'danger' | 'warning' | 'outlined' | 'ghost';
11
+ size?: 'xs' | 'sm' | 'md' | 'lg';
12
+ class?: string;
13
+ buttonText?: string;
14
+ isSolid?: boolean;
15
+ showPreview?: boolean;
16
+ previewRows?: number;
17
+ previewPageSize?: number;
18
+ };
19
+ declare const CsvField: import("svelte").Component<Props, {}, "">;
20
+ type CsvField = ReturnType<typeof CsvField>;
21
+ export default CsvField;
@@ -0,0 +1,292 @@
1
+ <script lang="ts">
2
+ import { ArrowLeft24RegularIcon, ArrowRight24RegularIcon } from '../icons/index.js';
3
+ import { Button, formatDate, getWeekdays, Icon } from '../index.js';
4
+ import { cn } from '../utils/class-names.js';
5
+ import { onMount } from 'svelte';
6
+
7
+ type Props = {
8
+ value?: unknown;
9
+ placeholder?: string;
10
+ onchange?: (value: unknown) => void;
11
+ variant?: 'primary' | 'secondary' | 'muted' | 'outlined' | 'line';
12
+ size?: 'sm' | 'md' | 'lg';
13
+ name?: string;
14
+ class?: string;
15
+ label?: string;
16
+ isLabelActive?: boolean;
17
+ helpText?: string;
18
+ errorText?: string;
19
+ isFloatLabel?: boolean;
20
+ isSolid?: boolean;
21
+ };
22
+
23
+ let {
24
+ value = $bindable(),
25
+ placeholder = 'Select an option',
26
+ onchange,
27
+ variant = 'outlined',
28
+ size = 'md',
29
+ name,
30
+ label,
31
+ isLabelActive,
32
+ helpText,
33
+ errorText,
34
+ class: className,
35
+ isFloatLabel,
36
+ isSolid
37
+ }: Props = $props();
38
+
39
+ let selectedDate = $state<Date>(new Date());
40
+ let currentMonth = $derived(selectedDate.getMonth());
41
+ let currentYear = $derived(selectedDate.getFullYear());
42
+ let contentEl = $state<HTMLDivElement>();
43
+ let controlElement = $state<HTMLElement>();
44
+
45
+ let isOpen = $state(false);
46
+ const uid = $props.id();
47
+
48
+ let isActive = $state(false);
49
+ let isFocused = $state(false);
50
+
51
+ let position = $state({
52
+ top: 0,
53
+ left: 0,
54
+ width: 'auto',
55
+ isBottomHalf: false
56
+ });
57
+
58
+ const variantClasses = {
59
+ primary: 'is-primary',
60
+ secondary: 'is-secondary',
61
+ muted: 'is-muted',
62
+ outlined: 'is-outlined',
63
+ line: 'is-line'
64
+ };
65
+
66
+ const sizeClasses = {
67
+ sm: 'is-sm',
68
+ md: 'is-md',
69
+ lg: 'is-lg'
70
+ };
71
+
72
+ const style = $derived(
73
+ `top: ${position.top}px; left: ${position.left}px; width: ${position.width}px; transform-origin: ${position.isBottomHalf ? 'bottom' : 'top'} center;`
74
+ );
75
+
76
+ const getDaysInMonth = (month: number, year: number) => {
77
+ const firstDay = new Date(year, month, 1);
78
+ const lastDay = new Date(year, month + 1, 0);
79
+ const daysInMonth = lastDay.getDate();
80
+ const startingDayOfWeek = firstDay.getDay();
81
+
82
+ const days = [];
83
+
84
+ for (let i = 0; i < startingDayOfWeek; i++) {
85
+ days.push(null);
86
+ }
87
+
88
+ for (let day = 1; day <= daysInMonth; day++) {
89
+ days.push(new Date(year, month, day));
90
+ }
91
+
92
+ return days;
93
+ };
94
+
95
+ const changeMonth = (direction: 'prev' | 'next') => {
96
+ if (direction === 'prev') {
97
+ currentMonth--;
98
+ if (currentMonth < 0) {
99
+ currentMonth = 11;
100
+ currentYear--;
101
+ }
102
+ } else {
103
+ currentMonth++;
104
+ if (currentMonth > 11) {
105
+ currentMonth = 0;
106
+ currentYear++;
107
+ }
108
+ }
109
+ };
110
+
111
+ const selectDate = (date: Date) => {
112
+ selectedDate = new Date(date);
113
+ selectedDate;
114
+ };
115
+
116
+ const isToday = (date: Date) => {
117
+ const today = new Date();
118
+ return (
119
+ date &&
120
+ date.getDate() === today.getDate() &&
121
+ date.getMonth() === today.getMonth() &&
122
+ date.getFullYear() === today.getFullYear()
123
+ );
124
+ };
125
+
126
+ const isSelected = (date: Date) => {
127
+ return (
128
+ date &&
129
+ date.getDate() === selectedDate.getDate() &&
130
+ date.getMonth() === selectedDate.getMonth() &&
131
+ date.getFullYear() === selectedDate.getFullYear()
132
+ );
133
+ };
134
+
135
+ const monthName = $derived(
136
+ formatDate(new Date(currentYear, currentMonth, 1), {
137
+ month: 'short'
138
+ })
139
+ );
140
+
141
+ const days = $derived(getDaysInMonth(currentMonth, currentYear));
142
+ const weekdays = $derived(getWeekdays('short'));
143
+
144
+ const formattedSelectedDate = $derived(
145
+ formatDate(selectedDate, {
146
+ year: 'numeric',
147
+ month: 'short',
148
+ day: 'numeric'
149
+ })
150
+ );
151
+
152
+ const handleClickOutside = (event: Event) => {
153
+ if (
154
+ isOpen &&
155
+ !controlElement?.contains(event.target as Node) &&
156
+ !contentEl?.contains(event.target as Node)
157
+ ) {
158
+ isOpen = false;
159
+ }
160
+ };
161
+
162
+ const toggleDropdown = () => {
163
+ if (!isOpen) {
164
+ startEventListeners();
165
+ updatePosition();
166
+ isOpen = true;
167
+ } else {
168
+ stopEventListeners();
169
+ isOpen = false;
170
+ }
171
+ };
172
+
173
+ const updatePosition = () => {
174
+ if (!controlElement || !contentEl) return;
175
+
176
+ const rect = controlElement.getBoundingClientRect();
177
+ const windowHeight = window.innerHeight;
178
+ const contentHeight = contentEl.getBoundingClientRect().height;
179
+ const isBottomHalf = rect.top + rect.height / 2 > windowHeight / 2;
180
+
181
+ const top = isBottomHalf ? rect.top - contentHeight - 8 : rect.top + rect.height;
182
+ position = {
183
+ top: top,
184
+ left: rect.left + window.scrollX,
185
+ width: 'auto',
186
+ isBottomHalf
187
+ };
188
+ };
189
+
190
+ const startEventListeners = () => {
191
+ window.addEventListener('resize', updatePosition);
192
+ window.addEventListener('scroll', updatePosition, true);
193
+ document.addEventListener('click', handleClickOutside);
194
+ };
195
+
196
+ const stopEventListeners = () => {
197
+ window.removeEventListener('resize', updatePosition);
198
+ window.removeEventListener('scroll', updatePosition, true);
199
+ document.removeEventListener('click', handleClickOutside);
200
+ };
201
+
202
+ onMount(() => {
203
+ return () => stopEventListeners();
204
+ });
205
+ </script>
206
+
207
+ <div class={cn('field', className)}>
208
+ <input type="text" {name} bind:value hidden />
209
+
210
+ {#if !isFloatLabel && label}
211
+ <div class="field-label">{label}</div>
212
+ {/if}
213
+
214
+ <button
215
+ type="button"
216
+ bind:this={controlElement}
217
+ class={cn(
218
+ 'control',
219
+ variantClasses[variant],
220
+ sizeClasses[size],
221
+ isFloatLabel && 'is-float',
222
+ isSolid && 'is-solid',
223
+ (isActive || isFocused) && 'is-active'
224
+ )}
225
+ class:is-error={errorText}
226
+ onclick={toggleDropdown}
227
+ onmouseenter={() => (isActive = true)}
228
+ onmouseleave={() => (isActive = false)}
229
+ >
230
+ {#if isFloatLabel && label}
231
+ <span
232
+ class={cn(
233
+ 'control-label',
234
+ (isActive || isFocused || isLabelActive || isOpen || value !== '') && 'is-active'
235
+ )}
236
+ >
237
+ {label}
238
+ </span>
239
+ {/if}
240
+ <div class="control-selected">
241
+ {#if formattedSelectedDate}
242
+ {formattedSelectedDate}
243
+ {:else if isFloatLabel}
244
+ {#if isActive || isOpen}
245
+ {placeholder}
246
+ {/if}
247
+ {:else}
248
+ {placeholder}
249
+ {/if}
250
+ </div>
251
+ </button>
252
+
253
+ {#if errorText || helpText}
254
+ <div class={cn('field-help', errorText && 'is-error')}>{errorText || helpText}</div>
255
+ {/if}
256
+
257
+ <div class:is-open={isOpen} class="date-popover" {style} bind:this={contentEl}>
258
+ <div class="date-picker-header">
259
+ <Button variant="ghost" onclick={() => changeMonth('prev')}>
260
+ <Icon icon={ArrowLeft24RegularIcon} />
261
+ </Button>
262
+
263
+ <span>{monthName} {currentYear}</span>
264
+
265
+ <Button variant="ghost" onclick={() => changeMonth('next')}>
266
+ <Icon icon={ArrowRight24RegularIcon} />
267
+ </Button>
268
+ </div>
269
+ <div class="date-picker-weekdays">
270
+ {#each weekdays as weekday}
271
+ <div class="weekday">{weekday}</div>
272
+ {/each}
273
+ </div>
274
+ <div class="date-picker-days">
275
+ {#each days as day}
276
+ {#if day}
277
+ <button
278
+ class="date-picker-day-button"
279
+ class:today={isToday(day)}
280
+ class:selected={isSelected(day)}
281
+ onclick={() => selectDate(day)}
282
+ type="button"
283
+ >
284
+ {day.getDate()}
285
+ </button>
286
+ {:else}
287
+ <div class="date-picker-empty-day"></div>
288
+ {/if}
289
+ {/each}
290
+ </div>
291
+ </div>
292
+ </div>
@@ -0,0 +1,18 @@
1
+ type Props = {
2
+ value?: unknown;
3
+ placeholder?: string;
4
+ onchange?: (value: unknown) => void;
5
+ variant?: 'primary' | 'secondary' | 'muted' | 'outlined' | 'line';
6
+ size?: 'sm' | 'md' | 'lg';
7
+ name?: string;
8
+ class?: string;
9
+ label?: string;
10
+ isLabelActive?: boolean;
11
+ helpText?: string;
12
+ errorText?: string;
13
+ isFloatLabel?: boolean;
14
+ isSolid?: boolean;
15
+ };
16
+ declare const DateField: import("svelte").Component<Props, {}, "value">;
17
+ type DateField = ReturnType<typeof DateField>;
18
+ export default DateField;