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,359 @@
1
+ <script lang="ts">
2
+ import {
3
+ ArrowDown24RegularIcon,
4
+ ArrowLeft24RegularIcon,
5
+ ArrowRight24RegularIcon,
6
+ ArrowUp24RegularIcon
7
+ } from '../icons/index.js';
8
+ import { Button, Icon } from '../index.js';
9
+ import { cn } from '../utils/class-names.js';
10
+ import type { Snippet } from 'svelte';
11
+ import { onMount, tick } from 'svelte';
12
+
13
+ type Slide = {
14
+ id: string | number;
15
+ content: Snippet;
16
+ };
17
+
18
+ type Props = {
19
+ slides: Slide[];
20
+ autoplay?: boolean;
21
+ autoplayInterval?: number;
22
+ loop?: boolean;
23
+ showControls?: boolean;
24
+ showIndicators?: boolean;
25
+ showNavigation?: boolean;
26
+ showCounter?: boolean;
27
+ orientation?: 'horizontal' | 'vertical';
28
+ variant?: 'primary' | 'secondary' | 'muted' | 'default';
29
+ size?: 'sm' | 'md' | 'lg';
30
+ indicatorType?: 'bar' | 'dot';
31
+ class?: string;
32
+ slideClass?: string;
33
+ onSlideChange?: (index: number) => void;
34
+ };
35
+
36
+ const {
37
+ class: className,
38
+ slideClass,
39
+ slides = [],
40
+ autoplay = false,
41
+ autoplayInterval = 3000,
42
+ loop = true,
43
+ showControls = true,
44
+ showIndicators = true,
45
+ showNavigation = false,
46
+ showCounter = false,
47
+ orientation = 'horizontal',
48
+ variant = 'default',
49
+ size = 'md',
50
+ indicatorType = 'bar',
51
+ onSlideChange
52
+ }: Props = $props();
53
+
54
+ let currentIndex = $state(0);
55
+ let containerEl = $state<HTMLElement>();
56
+ let viewportEl = $state<HTMLElement>();
57
+ let isDragging = $state(false);
58
+ let startPos = $state(0);
59
+ let currentTranslate = $state(0);
60
+ let prevTranslate = $state(0);
61
+ let autoplayTimer: ReturnType<typeof setTimeout> | null = $state(null);
62
+
63
+ const isVertical = $derived(orientation === 'vertical');
64
+ const canGoPrev = $derived(loop || currentIndex > 0);
65
+ const canGoNext = $derived(loop || currentIndex < slides.length - 1);
66
+
67
+ const sizeClasses = {
68
+ sm: 'is-sm',
69
+ md: 'is-md',
70
+ lg: 'is-lg'
71
+ };
72
+
73
+ const updateTransform = () => {
74
+ if (!containerEl) return;
75
+ const offset = -currentIndex * 100;
76
+ const property = isVertical ? 'translateY' : 'translateX';
77
+ containerEl.style.transform = `${property}(${offset}%)`;
78
+ };
79
+
80
+ const goToSlide = (index: number) => {
81
+ if (index < 0 || index >= slides.length) return;
82
+ currentIndex = index;
83
+ updateTransform();
84
+ onSlideChange?.(index);
85
+ resetAutoplay();
86
+ };
87
+
88
+ const goToPrev = () => {
89
+ if (!canGoPrev) return;
90
+ const newIndex = currentIndex === 0 ? slides.length - 1 : currentIndex - 1;
91
+ goToSlide(newIndex);
92
+ };
93
+
94
+ const goToNext = () => {
95
+ if (!canGoNext) return;
96
+ const newIndex = currentIndex === slides.length - 1 ? 0 : currentIndex + 1;
97
+ goToSlide(newIndex);
98
+ };
99
+
100
+ const startAutoplay = () => {
101
+ if (!autoplay) return;
102
+ autoplayTimer = setInterval(() => {
103
+ if (loop || currentIndex < slides.length - 1) {
104
+ goToNext();
105
+ } else {
106
+ stopAutoplay();
107
+ }
108
+ }, autoplayInterval);
109
+ };
110
+
111
+ const stopAutoplay = () => {
112
+ if (autoplayTimer) {
113
+ clearInterval(autoplayTimer);
114
+ autoplayTimer = null;
115
+ }
116
+ };
117
+
118
+ const resetAutoplay = () => {
119
+ stopAutoplay();
120
+ startAutoplay();
121
+ };
122
+
123
+ const handleDragStart = (event: MouseEvent | TouchEvent) => {
124
+ isDragging = true;
125
+ stopAutoplay();
126
+
127
+ if (event instanceof MouseEvent) {
128
+ startPos = isVertical ? event.clientY : event.clientX;
129
+ } else {
130
+ startPos = isVertical ? event.touches[0].clientY : event.touches[0].clientX;
131
+ }
132
+
133
+ if (containerEl) {
134
+ containerEl.classList.add('is-dragging');
135
+ }
136
+ };
137
+
138
+ const handleDragMove = (event: MouseEvent | TouchEvent) => {
139
+ if (!isDragging || !containerEl || !viewportEl) return;
140
+
141
+ const currentPos =
142
+ event instanceof MouseEvent
143
+ ? isVertical
144
+ ? event.clientY
145
+ : event.clientX
146
+ : isVertical
147
+ ? event.touches[0].clientY
148
+ : event.touches[0].clientX;
149
+
150
+ const diff = currentPos - startPos;
151
+ const viewportSize = isVertical ? viewportEl.offsetHeight : viewportEl.offsetWidth;
152
+ const percentageMoved = (diff / viewportSize) * 100;
153
+
154
+ currentTranslate = prevTranslate + percentageMoved;
155
+
156
+ const property = isVertical ? 'translateY' : 'translateX';
157
+ containerEl.style.transform = `${property}(calc(-${currentIndex * 100}% + ${diff}px))`;
158
+ };
159
+
160
+ const handleDragEnd = () => {
161
+ if (!isDragging) return;
162
+ isDragging = false;
163
+
164
+ if (containerEl) {
165
+ containerEl.classList.remove('is-dragging');
166
+ }
167
+
168
+ const movedBy = currentTranslate - prevTranslate;
169
+ const threshold = 15;
170
+
171
+ if (movedBy < -threshold && canGoNext) {
172
+ goToNext();
173
+ } else if (movedBy > threshold && canGoPrev) {
174
+ goToPrev();
175
+ } else {
176
+ updateTransform();
177
+ }
178
+
179
+ currentTranslate = 0;
180
+ prevTranslate = 0;
181
+
182
+ resetAutoplay();
183
+ };
184
+
185
+ const handleMouseDown = (event: MouseEvent) => {
186
+ event.preventDefault();
187
+ handleDragStart(event);
188
+ };
189
+
190
+ const handleMouseMove = (event: MouseEvent) => {
191
+ handleDragMove(event);
192
+ };
193
+
194
+ const handleMouseUp = () => {
195
+ handleDragEnd();
196
+ };
197
+
198
+ const handleTouchStart = (event: TouchEvent) => {
199
+ handleDragStart(event);
200
+ };
201
+
202
+ const handleTouchMove = (event: TouchEvent) => {
203
+ handleDragMove(event);
204
+ };
205
+
206
+ const handleTouchEnd = () => {
207
+ handleDragEnd();
208
+ };
209
+
210
+ const handleKeyDown = (event: KeyboardEvent) => {
211
+ switch (event.key) {
212
+ case 'ArrowLeft':
213
+ case 'ArrowUp':
214
+ event.preventDefault();
215
+ goToPrev();
216
+ break;
217
+ case 'ArrowRight':
218
+ case 'ArrowDown':
219
+ event.preventDefault();
220
+ goToNext();
221
+ break;
222
+ }
223
+ };
224
+
225
+ onMount(() => {
226
+ tick();
227
+ updateTransform();
228
+
229
+ if (autoplay) {
230
+ startAutoplay();
231
+ }
232
+
233
+ document.addEventListener('mousemove', handleMouseMove);
234
+ document.addEventListener('mouseup', handleMouseUp);
235
+ document.addEventListener('keydown', handleKeyDown);
236
+
237
+ return () => {
238
+ stopAutoplay();
239
+ document.removeEventListener('mousemove', handleMouseMove);
240
+ document.removeEventListener('mouseup', handleMouseUp);
241
+ document.removeEventListener('keydown', handleKeyDown);
242
+ };
243
+ });
244
+
245
+ $effect(() => {
246
+ if (containerEl) {
247
+ updateTransform();
248
+ }
249
+ });
250
+
251
+ $effect(() => {
252
+ slides;
253
+ if (containerEl) {
254
+ updateTransform();
255
+ }
256
+ });
257
+ </script>
258
+
259
+ <div class={cn('carousel', isVertical && 'is-vertical', className)}>
260
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
261
+ <div
262
+ class="carousel-viewport"
263
+ bind:this={viewportEl}
264
+ class:is-dragging={isDragging}
265
+ onmousedown={handleMouseDown}
266
+ ontouchstart={handleTouchStart}
267
+ ontouchmove={handleTouchMove}
268
+ ontouchend={handleTouchEnd}
269
+ >
270
+ <div
271
+ class={cn('carousel-container', isDragging && 'is-dragging', isVertical && 'is-vertical')}
272
+ bind:this={containerEl}
273
+ >
274
+ {#each slides as slide (slide.id)}
275
+ <div class={cn('carousel-slide', slideClass)}>
276
+ {@render slide.content()}
277
+ </div>
278
+ {/each}
279
+ </div>
280
+
281
+ {#if showNavigation}
282
+ <button
283
+ type="button"
284
+ class={cn('carousel-nav is-prev', sizeClasses[size])}
285
+ onclick={goToPrev}
286
+ disabled={!canGoPrev}
287
+ aria-label="Previous slide"
288
+ >
289
+ {#if isVertical}
290
+ <Icon icon={ArrowUp24RegularIcon} />
291
+ {:else}
292
+ <Icon icon={ArrowLeft24RegularIcon} />
293
+ {/if}
294
+ </button>
295
+
296
+ <button
297
+ type="button"
298
+ class={cn('carousel-nav is-next', sizeClasses[size])}
299
+ onclick={goToNext}
300
+ disabled={!canGoNext}
301
+ aria-label="Next slide"
302
+ >
303
+ {#if isVertical}
304
+ <Icon icon={ArrowDown24RegularIcon} />
305
+ {:else}
306
+ <Icon icon={ArrowRight24RegularIcon} />
307
+ {/if}
308
+ </button>
309
+ {/if}
310
+ </div>
311
+
312
+ {#if showControls || showIndicators || showCounter}
313
+ <div class={cn('carousel-controls', isVertical && 'is-vertical')}>
314
+ {#if showControls}
315
+ <Button isDisabled={!canGoPrev} onclick={goToPrev} variant="ghost">
316
+ {#if isVertical}
317
+ <Icon icon={ArrowUp24RegularIcon} />
318
+ {:else}
319
+ <Icon icon={ArrowLeft24RegularIcon} />
320
+ {/if}
321
+ </Button>
322
+ {/if}
323
+
324
+ {#if showIndicators}
325
+ <div class={cn('carousel-indicators', isVertical && 'is-vertical')}>
326
+ {#each slides as slide, index (slide.id)}
327
+ <button
328
+ type="button"
329
+ class={cn(
330
+ 'carousel-indicator',
331
+ sizeClasses[size],
332
+ currentIndex === index && 'is-active',
333
+ indicatorType === 'dot' && 'is-dot'
334
+ )}
335
+ onclick={() => goToSlide(index)}
336
+ aria-label={`Go to slide ${index + 1}`}
337
+ ></button>
338
+ {/each}
339
+ </div>
340
+ {/if}
341
+
342
+ {#if showCounter}
343
+ <div class="carousel-counter">
344
+ {currentIndex + 1} / {slides.length}
345
+ </div>
346
+ {/if}
347
+
348
+ {#if showControls}
349
+ <Button isDisabled={!canGoNext} onclick={goToNext} variant="ghost">
350
+ {#if isVertical}
351
+ <Icon icon={ArrowDown24RegularIcon} />
352
+ {:else}
353
+ <Icon icon={ArrowRight24RegularIcon} />
354
+ {/if}
355
+ </Button>
356
+ {/if}
357
+ </div>
358
+ {/if}
359
+ </div>
@@ -0,0 +1,25 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Slide = {
3
+ id: string | number;
4
+ content: Snippet;
5
+ };
6
+ type Props = {
7
+ slides: Slide[];
8
+ autoplay?: boolean;
9
+ autoplayInterval?: number;
10
+ loop?: boolean;
11
+ showControls?: boolean;
12
+ showIndicators?: boolean;
13
+ showNavigation?: boolean;
14
+ showCounter?: boolean;
15
+ orientation?: 'horizontal' | 'vertical';
16
+ variant?: 'primary' | 'secondary' | 'muted' | 'default';
17
+ size?: 'sm' | 'md' | 'lg';
18
+ indicatorType?: 'bar' | 'dot';
19
+ class?: string;
20
+ slideClass?: string;
21
+ onSlideChange?: (index: number) => void;
22
+ };
23
+ declare const Carousel: import("svelte").Component<Props, {}, "">;
24
+ type Carousel = ReturnType<typeof Carousel>;
25
+ export default Carousel;
@@ -0,0 +1,249 @@
1
+ <script lang="ts">
2
+ import type { ChatState } from '../hooks/use-chat.svelte.js';
3
+ import {
4
+ Attach24RegularIcon,
5
+ Camera24RegularIcon,
6
+ Microphone2LinearIcon,
7
+ MoreVertical24RegularIcon,
8
+ Search24RegularIcon,
9
+ Send24RegularIcon
10
+ } from '../icons/index.js';
11
+ import { Avatar, Record, Audio, Button, Icon } from '../index.js';
12
+ import { cn } from '../utils/class-names.js';
13
+ import type { Snippet } from 'svelte';
14
+
15
+ type Props = {
16
+ class?: string;
17
+ chat: ChatState;
18
+ currentUserId: string;
19
+ variant?:
20
+ | 'primary'
21
+ | 'secondary'
22
+ | 'success'
23
+ | 'info'
24
+ | 'warning'
25
+ | 'danger'
26
+ | 'muted'
27
+ | 'outlined';
28
+ userName: string;
29
+ userAvatar?: string;
30
+ userStatus?: string;
31
+ headerActions?: Snippet;
32
+ onVoiceNote?: (blob: Blob, url: string) => void;
33
+ onFileAttach?: (file: File) => void;
34
+ onCameraCapture?: () => void;
35
+ };
36
+
37
+ let {
38
+ class: className,
39
+ chat,
40
+ currentUserId,
41
+ variant = 'primary',
42
+ userName,
43
+ userAvatar,
44
+ userStatus = 'Online',
45
+ headerActions,
46
+ onVoiceNote,
47
+ onFileAttach,
48
+ onCameraCapture
49
+ }: Props = $props();
50
+
51
+ let messageInput = $state('');
52
+ let messagesContainer: HTMLDivElement;
53
+ let fileInput: HTMLInputElement;
54
+ let isRecording = $state(false);
55
+
56
+ function handleSend() {
57
+ if (messageInput.trim()) {
58
+ chat.sendMessage(messageInput);
59
+ messageInput = '';
60
+ scrollToBottom();
61
+ }
62
+ }
63
+
64
+ function handleKeyPress(e: KeyboardEvent) {
65
+ if (e.key === 'Enter' && !e.shiftKey) {
66
+ e.preventDefault();
67
+ handleSend();
68
+ }
69
+ }
70
+
71
+ function scrollToBottom() {
72
+ if (messagesContainer) {
73
+ setTimeout(() => {
74
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
75
+ }, 0);
76
+ }
77
+ }
78
+
79
+ function handleFileSelect(e: Event) {
80
+ const target = e.target as HTMLInputElement;
81
+ const file = target.files?.[0];
82
+ if (file) {
83
+ if (onFileAttach) {
84
+ onFileAttach(file);
85
+ } else {
86
+ const type = file.type.startsWith('image/')
87
+ ? 'image'
88
+ : file.type.startsWith('audio/')
89
+ ? 'voice'
90
+ : 'file';
91
+ chat.sendMessage(file.name, type, {
92
+ fileUrl: URL.createObjectURL(file),
93
+ fileName: file.name
94
+ });
95
+ }
96
+ }
97
+ }
98
+
99
+ function handleRecordingComplete(blob: Blob, url: string) {
100
+ if (onVoiceNote) {
101
+ onVoiceNote(blob, url);
102
+ } else {
103
+ chat.sendMessage('Voice note', 'voice', {
104
+ fileUrl: url,
105
+ fileName: 'voice-note.webm'
106
+ });
107
+ }
108
+ isRecording = false;
109
+ }
110
+
111
+ function toggleRecording() {
112
+ isRecording = !isRecording;
113
+ }
114
+
115
+ function formatTime(timestamp: Date | string) {
116
+ const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
117
+ return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
118
+ }
119
+
120
+ $effect(() => {
121
+ if (chat.messages.length > 0) {
122
+ scrollToBottom();
123
+ }
124
+ });
125
+ </script>
126
+
127
+ <div class={cn('chatbox', `chatbox-${variant}`, className)}>
128
+ <!-- Header -->
129
+ <div class="chatbox-header">
130
+ <div class="chatbox-header-start">
131
+ <Avatar src={userAvatar} alt={userName} size="sm" />
132
+ <div class="chatbox-header-center">
133
+ <div class="chatbox-header-name">{userName}</div>
134
+ <div class="chatbox-header-status">{userStatus}</div>
135
+ </div>
136
+ </div>
137
+ <div class="chatbox-header-end">
138
+ {#if headerActions}
139
+ {@render headerActions()}
140
+ {:else}
141
+ <Button size="sm" variant="ghost">
142
+ <Icon icon={Search24RegularIcon} />
143
+ </Button>
144
+ <Button size="sm" variant="ghost">
145
+ <Icon icon={MoreVertical24RegularIcon} />
146
+ </Button>
147
+ {/if}
148
+ </div>
149
+ </div>
150
+
151
+ <!-- Messages -->
152
+ <div class="chatbox-messages" bind:this={messagesContainer}>
153
+ {#if chat.hasMore}
154
+ <button class="load-more-btn" onclick={() => chat.loadMore()} disabled={chat.isLoading}>
155
+ {chat.isLoading ? 'Loading...' : 'Load more messages'}
156
+ </button>
157
+ {/if}
158
+
159
+ {#each chat.messages as message}
160
+ {@const isOwn = message.senderId === currentUserId}
161
+ <div class="message-wrapper" class:own={isOwn}>
162
+ <div class="message" class:own={isOwn}>
163
+ {#if message.type === 'image'}
164
+ <!-- svelte-ignore a11y_img_redundant_alt -->
165
+ <img src={message.metadata?.fileUrl} alt="Shared image" class="message-image" />
166
+ {:else if message.type === ('voice' as any)}
167
+ <Audio
168
+ src={message.metadata?.fileUrl || ''}
169
+ variant={isOwn ? variant : ('muted' as any)}
170
+ />
171
+ {:else if message.type === 'file'}
172
+ <div class="message-file">
173
+ <span class="message-file-icon">📎</span>
174
+ <span class="message-file-name">{message.metadata?.fileName || 'File'}</span>
175
+ </div>
176
+ {:else}
177
+ <div class="message-content">{message.content}</div>
178
+ {/if}
179
+
180
+ <div class="message-meta">
181
+ <span class="message-time">{formatTime(message.timestamp)}</span>
182
+ {#if isOwn}
183
+ <span class="message-status">
184
+ {#if message.status === 'sending'}⏱{/if}
185
+ {#if message.status === 'sent'}✓{/if}
186
+ {#if message.status === 'delivered'}✓✓{/if}
187
+ {#if message.status === 'read'}
188
+ <span class="message-status-read">✓✓</span>
189
+ {/if}
190
+ {#if message.status === 'error'}⚠{/if}
191
+ </span>
192
+ {/if}
193
+ </div>
194
+ </div>
195
+ </div>
196
+ {/each}
197
+ </div>
198
+
199
+ <!-- Recording Overlay -->
200
+ {#if isRecording}
201
+ <div class="chatbox-record-overlay">
202
+ <Record name="voice-note" {variant} onRecordingComplete={handleRecordingComplete} />
203
+ </div>
204
+ {/if}
205
+
206
+ <!-- Footer -->
207
+ <div class="chatbox-footer">
208
+ <label class="chatbox-input-wrapper">
209
+ <label class="flex-1">
210
+ <input
211
+ bind:value={messageInput}
212
+ placeholder="Type a message..."
213
+ onkeydown={handleKeyPress}
214
+ disabled={chat.isSending}
215
+ class="chatbox-input"
216
+ />
217
+ </label>
218
+
219
+ <div class="chatbox-input-actions">
220
+ <Button size="sm" variant="ghost" onclick={toggleRecording}>
221
+ <Icon icon={Microphone2LinearIcon} />
222
+ </Button>
223
+ <Button size="sm" variant="ghost" onclick={() => fileInput.click()}>
224
+ <Icon icon={Attach24RegularIcon} />
225
+ </Button>
226
+ <input
227
+ type="file"
228
+ bind:this={fileInput}
229
+ onchange={handleFileSelect}
230
+ style="display: none"
231
+ />
232
+ {#if onCameraCapture}
233
+ <Button size="sm" variant="ghost" onclick={onCameraCapture}>
234
+ <Icon icon={Camera24RegularIcon} />
235
+ </Button>
236
+ {/if}
237
+ </div>
238
+
239
+ <Button
240
+ size="sm"
241
+ {variant}
242
+ onclick={handleSend}
243
+ isDisabled={chat.isSending || !messageInput.trim()}
244
+ >
245
+ <Icon icon={Send24RegularIcon} />
246
+ </Button>
247
+ </label>
248
+ </div>
249
+ </div>
@@ -0,0 +1,18 @@
1
+ import type { ChatState } from '../hooks/use-chat.svelte.js';
2
+ import type { Snippet } from 'svelte';
3
+ type Props = {
4
+ class?: string;
5
+ chat: ChatState;
6
+ currentUserId: string;
7
+ variant?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted' | 'outlined';
8
+ userName: string;
9
+ userAvatar?: string;
10
+ userStatus?: string;
11
+ headerActions?: Snippet;
12
+ onVoiceNote?: (blob: Blob, url: string) => void;
13
+ onFileAttach?: (file: File) => void;
14
+ onCameraCapture?: () => void;
15
+ };
16
+ declare const ChatBox: import("svelte").Component<Props, {}, "">;
17
+ type ChatBox = ReturnType<typeof ChatBox>;
18
+ export default ChatBox;
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ import { cn } from '../utils/class-names.js';
3
+ import type { Snippet } from 'svelte';
4
+ import type { IconData } from './Icon.svelte';
5
+ import { Icon } from '../index.js';
6
+
7
+ type Props = {
8
+ children: Snippet;
9
+ onclose?: () => void;
10
+ type?: 'solid' | 'soft';
11
+ variant?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'danger' | 'warning';
12
+ size?: 'sm' | 'md' | 'lg';
13
+ class?: string;
14
+ startIcon?: IconData;
15
+ endIcon?: IconData;
16
+ hasShadow?: boolean;
17
+ isSolid?: boolean;
18
+ };
19
+
20
+ const {
21
+ children,
22
+ onclose,
23
+ variant = 'primary',
24
+ size = 'sm',
25
+ class: className,
26
+ startIcon,
27
+ endIcon,
28
+ hasShadow,
29
+ isSolid
30
+ }: Props = $props();
31
+
32
+ const variants = {
33
+ primary: 'is-primary',
34
+ secondary: 'is-secondary',
35
+ muted: 'is-muted',
36
+ success: 'is-success',
37
+ info: 'is-info',
38
+ warning: 'is-warning',
39
+ danger: 'is-danger'
40
+ };
41
+
42
+ const sizes = {
43
+ sm: 'is-sm',
44
+ md: 'is-md',
45
+ lg: 'is-lg'
46
+ };
47
+ </script>
48
+
49
+ <button
50
+ onclick={() => onclose?.()}
51
+ class={cn(
52
+ 'chip',
53
+ variants[variant],
54
+ sizes[size],
55
+ isSolid && 'is-solid',
56
+ hasShadow && 'has-shadow',
57
+ className
58
+ )}
59
+ >
60
+ {#if startIcon}
61
+ <Icon icon={startIcon} />
62
+ {/if}
63
+ {@render children()}
64
+ {#if endIcon}
65
+ <Icon icon={endIcon} />
66
+ {/if}
67
+ </button>