@archbase/components 3.0.25 → 4.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 (168) hide show
  1. package/dist/buttons/ArchbaseSpeedDial.d.ts +59 -0
  2. package/dist/buttons/index.d.ts +2 -0
  3. package/dist/charts/ArchbaseArcGauge.d.ts +55 -0
  4. package/dist/charts/ArchbaseGantt.d.ts +70 -0
  5. package/dist/charts/ArchbaseLinearGauge.d.ts +55 -0
  6. package/dist/charts/ArchbaseResourceTimeline.d.ts +61 -0
  7. package/dist/charts/ArchbaseSparkline.d.ts +45 -0
  8. package/dist/charts/index.d.ts +10 -0
  9. package/dist/containers/ArchbaseExpansionPanel.d.ts +60 -0
  10. package/dist/containers/ArchbaseFloatingPanel.d.ts +91 -0
  11. package/dist/containers/index.d.ts +4 -0
  12. package/dist/datagrid/export/ArchbaseDataGridExcelExport.d.ts +67 -0
  13. package/dist/datagrid/export/index.d.ts +2 -0
  14. package/dist/datagrid/index.d.ts +1 -0
  15. package/dist/display/ArchbaseAnimationWrapper.d.ts +73 -0
  16. package/dist/display/ArchbaseBarcodeGenerator.d.ts +50 -0
  17. package/dist/display/ArchbaseCarousel.d.ts +100 -0
  18. package/dist/display/ArchbaseCodeViewer.d.ts +63 -0
  19. package/dist/display/ArchbaseDiffViewer.d.ts +41 -0
  20. package/dist/display/ArchbaseJsonTree.d.ts +34 -0
  21. package/dist/display/ArchbaseLightbox.d.ts +89 -0
  22. package/dist/display/ArchbaseMarquee.d.ts +46 -0
  23. package/dist/display/ArchbasePhotoAlbum.d.ts +58 -0
  24. package/dist/display/ArchbaseQRCode.d.ts +44 -0
  25. package/dist/display/ArchbaseTooltipRich.d.ts +82 -0
  26. package/dist/display/ArchbaseVirtualList.d.ts +116 -0
  27. package/dist/display/index.d.ts +24 -0
  28. package/dist/editors/ArchbaseAIPromptInput.d.ts +69 -0
  29. package/dist/editors/ArchbaseAsyncMultiSelect.d.ts +1 -1
  30. package/dist/editors/ArchbaseAsyncSelect.d.ts +1 -1
  31. package/dist/editors/ArchbaseBarcodeScanner.d.ts +63 -0
  32. package/dist/editors/ArchbaseCepInput.d.ts +26 -0
  33. package/dist/editors/ArchbaseColorGradientPicker.d.ts +80 -0
  34. package/dist/editors/ArchbaseCpfCnpjInput.d.ts +21 -0
  35. package/dist/editors/ArchbaseCurrencyInput.d.ts +25 -0
  36. package/dist/editors/ArchbaseDualListbox.d.ts +61 -0
  37. package/dist/editors/ArchbaseInPlaceEditor.d.ts +67 -0
  38. package/dist/editors/ArchbaseLightGrid.d.ts +56 -0
  39. package/dist/editors/ArchbaseMentionInput.d.ts +75 -0
  40. package/dist/editors/ArchbaseMultiEmail.d.ts +53 -0
  41. package/dist/editors/ArchbaseMultiViewCalendar.d.ts +47 -0
  42. package/dist/editors/ArchbaseNumberStepper.d.ts +66 -0
  43. package/dist/editors/ArchbaseOTPInput.d.ts +27 -0
  44. package/dist/editors/ArchbasePhoneInput.d.ts +52 -0
  45. package/dist/editors/ArchbaseRangeSlider.d.ts +22 -0
  46. package/dist/editors/ArchbaseSignaturePad.d.ts +60 -0
  47. package/dist/editors/ArchbaseSpeechToTextInput.d.ts +106 -0
  48. package/dist/editors/ArchbaseSpreadsheet.d.ts +56 -0
  49. package/dist/editors/ArchbaseTagInput.d.ts +55 -0
  50. package/dist/editors/index.d.ts +40 -0
  51. package/dist/feedback/ArchbaseChunkProgressBar.d.ts +35 -0
  52. package/dist/feedback/ArchbaseRipple.d.ts +43 -0
  53. package/dist/feedback/ArchbaseSkeleton.d.ts +115 -0
  54. package/dist/feedback/index.d.ts +6 -0
  55. package/dist/filters/ArchbaseColumnSelector.d.ts +38 -0
  56. package/dist/filters/index.d.ts +2 -0
  57. package/dist/forms/ArchbaseFieldArray.d.ts +80 -0
  58. package/dist/forms/ArchbaseFormWizard.d.ts +73 -0
  59. package/dist/forms/ArchbaseStepper.d.ts +51 -0
  60. package/dist/forms/index.d.ts +8 -0
  61. package/dist/forms/useArchbaseFormWizard.d.ts +74 -0
  62. package/dist/hooks/index.d.ts +2 -0
  63. package/dist/hooks/useArchbaseConfirm.d.ts +59 -0
  64. package/dist/image/editor/index.d.ts +43 -4
  65. package/dist/image/index.d.ts +1 -0
  66. package/dist/index.css +1 -1
  67. package/dist/index.d.ts +1 -0
  68. package/dist/index.js +22510 -13820
  69. package/dist/list/ArchbaseTreeList.d.ts +76 -0
  70. package/dist/list/index.d.ts +2 -0
  71. package/dist/navigation/ArchbaseActionSheet.d.ts +70 -0
  72. package/dist/navigation/ArchbaseAppBar.d.ts +63 -0
  73. package/dist/navigation/ArchbaseBottomNavigation.d.ts +61 -0
  74. package/dist/navigation/ArchbaseContextMenu.d.ts +76 -0
  75. package/dist/navigation/ArchbaseKeyboardShortcuts.d.ts +85 -0
  76. package/dist/navigation/ArchbaseSpotlight.d.ts +69 -0
  77. package/dist/navigation/index.d.ts +12 -0
  78. package/dist/notification/ArchbaseNotificationCenter.d.ts +56 -0
  79. package/dist/notification/index.d.ts +2 -0
  80. package/dist/onboarding/ArchbaseOnboardingTour.d.ts +27 -76
  81. package/dist/viewers/ArchbaseDocViewer.d.ts +40 -0
  82. package/dist/viewers/ArchbasePdfBuilder.d.ts +53 -0
  83. package/dist/viewers/ArchbaseXmlViewer.d.ts +36 -0
  84. package/dist/viewers/index.d.ts +6 -0
  85. package/package.json +41 -18
  86. package/src/buttons/ArchbaseSpeedDial.tsx +282 -0
  87. package/src/buttons/index.ts +3 -0
  88. package/src/charts/ArchbaseArcGauge.tsx +325 -0
  89. package/src/charts/ArchbaseGantt.tsx +494 -0
  90. package/src/charts/ArchbaseLinearGauge.tsx +288 -0
  91. package/src/charts/ArchbaseResourceTimeline.tsx +433 -0
  92. package/src/charts/ArchbaseSparkline.tsx +193 -0
  93. package/src/charts/index.ts +20 -1
  94. package/src/containers/ArchbaseExpansionPanel.tsx +208 -0
  95. package/src/containers/ArchbaseFloatingPanel.tsx +448 -0
  96. package/src/containers/index.ts +17 -0
  97. package/src/datagrid/components/pagination/grid-pagination.tsx +2 -2
  98. package/src/datagrid/export/ArchbaseDataGridExcelExport.tsx +293 -0
  99. package/src/datagrid/export/index.ts +8 -0
  100. package/src/datagrid/index.tsx +2 -0
  101. package/src/display/ArchbaseAnimationWrapper.tsx +272 -0
  102. package/src/display/ArchbaseBarcodeGenerator.tsx +303 -0
  103. package/src/display/ArchbaseCarousel.tsx +679 -0
  104. package/src/display/ArchbaseCodeViewer.tsx +271 -0
  105. package/src/display/ArchbaseDiffViewer.tsx +108 -0
  106. package/src/display/ArchbaseJsonTree.tsx +150 -0
  107. package/src/display/ArchbaseLightbox.tsx +279 -0
  108. package/src/display/ArchbaseMarquee.tsx +268 -0
  109. package/src/display/ArchbasePhotoAlbum.tsx +415 -0
  110. package/src/display/ArchbaseQRCode.tsx +137 -0
  111. package/src/display/ArchbaseTooltipRich.tsx +317 -0
  112. package/src/display/ArchbaseVirtualList.tsx +618 -0
  113. package/src/display/index.ts +94 -1
  114. package/src/editors/ArchbaseAIPromptInput.tsx +432 -0
  115. package/src/editors/ArchbaseBarcodeScanner.tsx +570 -0
  116. package/src/editors/ArchbaseCepInput.tsx +173 -0
  117. package/src/editors/ArchbaseColorGradientPicker.tsx +460 -0
  118. package/src/editors/ArchbaseCpfCnpjInput.tsx +193 -0
  119. package/src/editors/ArchbaseCurrencyInput.tsx +100 -0
  120. package/src/editors/ArchbaseDualListbox.tsx +671 -0
  121. package/src/editors/ArchbaseInPlaceEditor.tsx +406 -0
  122. package/src/editors/ArchbaseLightGrid.tsx +538 -0
  123. package/src/editors/ArchbaseMentionInput.tsx +384 -0
  124. package/src/editors/ArchbaseMultiEmail.tsx +367 -0
  125. package/src/editors/ArchbaseMultiViewCalendar.tsx +439 -0
  126. package/src/editors/ArchbaseNumberStepper.tsx +219 -0
  127. package/src/editors/ArchbaseOTPInput.tsx +133 -0
  128. package/src/editors/ArchbaseOperationHoursEditor.tsx +1 -1
  129. package/src/editors/ArchbasePhoneInput.tsx +355 -0
  130. package/src/editors/ArchbaseRangeSlider.tsx +109 -0
  131. package/src/editors/ArchbaseSignaturePad.tsx +425 -0
  132. package/src/editors/ArchbaseSpeechToTextInput.tsx +444 -0
  133. package/src/editors/ArchbaseSpreadsheet.tsx +388 -0
  134. package/src/editors/ArchbaseTagInput.tsx +326 -0
  135. package/src/editors/index.tsx +92 -0
  136. package/src/feedback/ArchbaseChunkProgressBar.tsx +156 -0
  137. package/src/feedback/ArchbaseRipple.tsx +278 -0
  138. package/src/feedback/ArchbaseSkeleton.tsx +419 -0
  139. package/src/feedback/index.ts +22 -1
  140. package/src/filters/ArchbaseColumnSelector.tsx +268 -0
  141. package/src/filters/index.ts +2 -0
  142. package/src/forms/ArchbaseFieldArray.tsx +305 -0
  143. package/src/forms/ArchbaseFormWizard.tsx +284 -0
  144. package/src/forms/ArchbaseStepper.tsx +137 -0
  145. package/src/forms/index.ts +24 -2
  146. package/src/forms/useArchbaseFormWizard.ts +283 -0
  147. package/src/hooks/index.ts +12 -0
  148. package/src/hooks/useArchbaseConfirm.ts +293 -0
  149. package/src/image/editor/functions/image-processing.ts +5 -0
  150. package/src/image/editor/index.tsx +305 -391
  151. package/src/image/index.ts +1 -0
  152. package/src/index.ts +3 -0
  153. package/src/list/ArchbaseTreeList.tsx +422 -0
  154. package/src/list/index.ts +3 -0
  155. package/src/navigation/ArchbaseActionSheet.tsx +301 -0
  156. package/src/navigation/ArchbaseAppBar.tsx +233 -0
  157. package/src/navigation/ArchbaseBottomNavigation.tsx +243 -0
  158. package/src/navigation/ArchbaseContextMenu.tsx +298 -0
  159. package/src/navigation/ArchbaseKeyboardShortcuts.tsx +472 -0
  160. package/src/navigation/ArchbaseSpotlight.tsx +483 -0
  161. package/src/navigation/index.ts +48 -1
  162. package/src/notification/ArchbaseNotificationCenter.tsx +502 -0
  163. package/src/notification/index.ts +2 -0
  164. package/src/onboarding/ArchbaseOnboardingTour.tsx +229 -102
  165. package/src/viewers/ArchbaseDocViewer.tsx +584 -0
  166. package/src/viewers/ArchbasePdfBuilder.tsx +291 -0
  167. package/src/viewers/ArchbaseXmlViewer.tsx +142 -0
  168. package/src/viewers/index.ts +12 -0
@@ -0,0 +1,679 @@
1
+ import React, { ReactNode, useCallback, useRef, useState, useEffect } from 'react';
2
+ import {
3
+ Box,
4
+ BoxProps,
5
+ ActionIcon,
6
+ Group,
7
+ Paper,
8
+ Text,
9
+ UnstyledButton,
10
+ } from '@mantine/core';
11
+ import { useInterval } from '@mantine/hooks';
12
+ import { IconChevronLeft, IconChevronRight, IconPlayerPause, IconPlayerPlay } from '@tabler/icons-react';
13
+
14
+ // =============================================================================
15
+ // Types
16
+ // =============================================================================
17
+
18
+ export interface ArchbaseCarouselProps extends Omit<BoxProps, 'children'> {
19
+ /** Slides do carousel */
20
+ children: ReactNode[];
21
+ /** Slide inicial (0-indexed) */
22
+ initialSlide?: number;
23
+ /** Slides visíveis por vez */
24
+ slidesToShow?: number;
25
+ /** Slides para avançar por clique */
26
+ slidesToScroll?: number;
27
+ /** Gap entre slides em pixels */
28
+ gap?: number;
29
+ /** Mostrar controles de navegação */
30
+ showControls?: boolean;
31
+ /** Posição dos controles */
32
+ controlsPosition?: 'sides' | 'bottom' | 'bottom-right';
33
+ /** Tamanho dos controles */
34
+ controlSize?: number;
35
+ /** Mostrar indicadores (dots) */
36
+ showIndicators?: boolean;
37
+ /** Posição dos indicadores */
38
+ indicatorsPosition?: 'bottom' | 'top';
39
+ /** Loop infinito */
40
+ loop?: boolean;
41
+ /** Autoplay */
42
+ autoplay?: boolean;
43
+ /** Intervalo do autoplay em ms */
44
+ autoplayInterval?: number;
45
+ /** Pausar autoplay no hover */
46
+ pauseOnHover?: boolean;
47
+ /** Mostrar botão de play/pause */
48
+ showPlayPause?: boolean;
49
+ /** Animação de transição */
50
+ transitionDuration?: number;
51
+ /** Função de easing */
52
+ transitionTimingFunction?: string;
53
+ /** Callback ao mudar slide */
54
+ onSlideChange?: (index: number) => void;
55
+ /** Altura do carousel */
56
+ height?: number | string;
57
+ /** Controle externo do slide atual */
58
+ activeSlide?: number;
59
+ /** Arrastar para navegar */
60
+ draggable?: boolean;
61
+ /** Alinhamento dos slides */
62
+ align?: 'start' | 'center' | 'end';
63
+ /** Orientação */
64
+ orientation?: 'horizontal' | 'vertical';
65
+ }
66
+
67
+ // =============================================================================
68
+ // ArchbaseCarousel Component
69
+ // =============================================================================
70
+
71
+ export function ArchbaseCarousel({
72
+ children,
73
+ initialSlide = 0,
74
+ slidesToShow = 1,
75
+ slidesToScroll = 1,
76
+ gap = 16,
77
+ showControls = true,
78
+ controlsPosition = 'sides',
79
+ controlSize = 36,
80
+ showIndicators = true,
81
+ indicatorsPosition = 'bottom',
82
+ loop = false,
83
+ autoplay = false,
84
+ autoplayInterval = 3000,
85
+ pauseOnHover = true,
86
+ showPlayPause = false,
87
+ transitionDuration = 300,
88
+ transitionTimingFunction = 'ease',
89
+ onSlideChange,
90
+ height = 300,
91
+ activeSlide: controlledSlide,
92
+ draggable = true,
93
+ align = 'start',
94
+ orientation = 'horizontal',
95
+ ...boxProps
96
+ }: ArchbaseCarouselProps) {
97
+ const slides = React.Children.toArray(children);
98
+ const totalSlides = slides.length;
99
+ const maxSlide = Math.max(0, totalSlides - slidesToShow);
100
+
101
+ const [internalSlide, setInternalSlide] = useState(initialSlide);
102
+ const [isPlaying, setIsPlaying] = useState(autoplay);
103
+ const [isDragging, setIsDragging] = useState(false);
104
+ const [dragStart, setDragStart] = useState(0);
105
+ const [dragOffset, setDragOffset] = useState(0);
106
+
107
+ const isControlled = controlledSlide !== undefined;
108
+ const currentSlide = isControlled ? controlledSlide : internalSlide;
109
+
110
+ const containerRef = useRef<HTMLDivElement>(null);
111
+ const isHorizontal = orientation === 'horizontal';
112
+
113
+ // Autoplay
114
+ const interval = useInterval(() => {
115
+ if (isPlaying) {
116
+ goToNext();
117
+ }
118
+ }, autoplayInterval);
119
+
120
+ useEffect(() => {
121
+ if (autoplay && isPlaying) {
122
+ interval.start();
123
+ } else {
124
+ interval.stop();
125
+ }
126
+ return interval.stop;
127
+ }, [autoplay, isPlaying]);
128
+
129
+ // Navigation
130
+ const goToSlide = useCallback(
131
+ (index: number) => {
132
+ let newIndex = index;
133
+
134
+ if (loop) {
135
+ if (newIndex < 0) newIndex = maxSlide;
136
+ if (newIndex > maxSlide) newIndex = 0;
137
+ } else {
138
+ newIndex = Math.max(0, Math.min(newIndex, maxSlide));
139
+ }
140
+
141
+ if (!isControlled) {
142
+ setInternalSlide(newIndex);
143
+ }
144
+ onSlideChange?.(newIndex);
145
+ },
146
+ [loop, maxSlide, isControlled, onSlideChange]
147
+ );
148
+
149
+ const goToPrev = useCallback(() => {
150
+ goToSlide(currentSlide - slidesToScroll);
151
+ }, [currentSlide, slidesToScroll, goToSlide]);
152
+
153
+ const goToNext = useCallback(() => {
154
+ goToSlide(currentSlide + slidesToScroll);
155
+ }, [currentSlide, slidesToScroll, goToSlide]);
156
+
157
+ // Drag handling
158
+ const handleDragStart = useCallback(
159
+ (clientPos: number) => {
160
+ if (!draggable) return;
161
+ setIsDragging(true);
162
+ setDragStart(clientPos);
163
+ setDragOffset(0);
164
+ },
165
+ [draggable]
166
+ );
167
+
168
+ const handleDragMove = useCallback(
169
+ (clientPos: number) => {
170
+ if (!isDragging) return;
171
+ setDragOffset(clientPos - dragStart);
172
+ },
173
+ [isDragging, dragStart]
174
+ );
175
+
176
+ const handleDragEnd = useCallback(() => {
177
+ if (!isDragging) return;
178
+ setIsDragging(false);
179
+
180
+ const threshold = 50;
181
+ if (Math.abs(dragOffset) > threshold) {
182
+ if (isHorizontal) {
183
+ dragOffset > 0 ? goToPrev() : goToNext();
184
+ } else {
185
+ dragOffset > 0 ? goToPrev() : goToNext();
186
+ }
187
+ }
188
+ setDragOffset(0);
189
+ }, [isDragging, dragOffset, isHorizontal, goToPrev, goToNext]);
190
+
191
+ // Mouse events
192
+ const handleMouseDown = (e: React.MouseEvent) => {
193
+ handleDragStart(isHorizontal ? e.clientX : e.clientY);
194
+ };
195
+
196
+ const handleMouseMove = (e: React.MouseEvent) => {
197
+ handleDragMove(isHorizontal ? e.clientX : e.clientY);
198
+ };
199
+
200
+ const handleMouseUp = () => handleDragEnd();
201
+ const handleMouseLeave = () => {
202
+ if (isDragging) handleDragEnd();
203
+ if (pauseOnHover && autoplay) setIsPlaying(true);
204
+ };
205
+
206
+ const handleMouseEnter = () => {
207
+ if (pauseOnHover && autoplay) setIsPlaying(false);
208
+ };
209
+
210
+ // Touch events
211
+ const handleTouchStart = (e: React.TouchEvent) => {
212
+ handleDragStart(isHorizontal ? e.touches[0].clientX : e.touches[0].clientY);
213
+ };
214
+
215
+ const handleTouchMove = (e: React.TouchEvent) => {
216
+ handleDragMove(isHorizontal ? e.touches[0].clientX : e.touches[0].clientY);
217
+ };
218
+
219
+ const handleTouchEnd = () => handleDragEnd();
220
+
221
+ // Calculate transform
222
+ const slideWidth = containerRef.current
223
+ ? (containerRef.current.offsetWidth - gap * (slidesToShow - 1)) / slidesToShow
224
+ : 0;
225
+
226
+ const slideHeight = containerRef.current
227
+ ? (containerRef.current.offsetHeight - gap * (slidesToShow - 1)) / slidesToShow
228
+ : 0;
229
+
230
+ const translateValue = isHorizontal
231
+ ? -(currentSlide * (slideWidth + gap)) + dragOffset
232
+ : -(currentSlide * (slideHeight + gap)) + dragOffset;
233
+
234
+ // Check if can navigate
235
+ const canGoPrev = loop || currentSlide > 0;
236
+ const canGoNext = loop || currentSlide < maxSlide;
237
+
238
+ // Indicator count
239
+ const indicatorCount = Math.ceil(totalSlides / slidesToShow);
240
+
241
+ // Render controls
242
+ const renderControls = () => {
243
+ if (!showControls) return null;
244
+
245
+ const prevButton = (
246
+ <ActionIcon
247
+ variant="filled"
248
+ color="dark"
249
+ size={controlSize}
250
+ radius="xl"
251
+ onClick={goToPrev}
252
+ disabled={!canGoPrev}
253
+ style={{ opacity: canGoPrev ? 1 : 0.3 }}
254
+ >
255
+ <IconChevronLeft size={controlSize * 0.5} />
256
+ </ActionIcon>
257
+ );
258
+
259
+ const nextButton = (
260
+ <ActionIcon
261
+ variant="filled"
262
+ color="dark"
263
+ size={controlSize}
264
+ radius="xl"
265
+ onClick={goToNext}
266
+ disabled={!canGoNext}
267
+ style={{ opacity: canGoNext ? 1 : 0.3 }}
268
+ >
269
+ <IconChevronRight size={controlSize * 0.5} />
270
+ </ActionIcon>
271
+ );
272
+
273
+ if (controlsPosition === 'sides') {
274
+ return (
275
+ <>
276
+ <Box
277
+ style={{
278
+ position: 'absolute',
279
+ left: 8,
280
+ top: '50%',
281
+ transform: 'translateY(-50%)',
282
+ zIndex: 10,
283
+ }}
284
+ >
285
+ {prevButton}
286
+ </Box>
287
+ <Box
288
+ style={{
289
+ position: 'absolute',
290
+ right: 8,
291
+ top: '50%',
292
+ transform: 'translateY(-50%)',
293
+ zIndex: 10,
294
+ }}
295
+ >
296
+ {nextButton}
297
+ </Box>
298
+ </>
299
+ );
300
+ }
301
+
302
+ return null;
303
+ };
304
+
305
+ // Render indicators
306
+ const renderIndicators = () => {
307
+ if (!showIndicators) return null;
308
+
309
+ const indicators = [];
310
+ for (let i = 0; i <= maxSlide; i += slidesToScroll) {
311
+ const isActive = currentSlide === i ||
312
+ (currentSlide > i && currentSlide < i + slidesToScroll);
313
+
314
+ indicators.push(
315
+ <UnstyledButton
316
+ key={i}
317
+ onClick={() => goToSlide(i)}
318
+ style={{
319
+ width: isActive ? 24 : 8,
320
+ height: 8,
321
+ borderRadius: 4,
322
+ backgroundColor: isActive
323
+ ? 'var(--mantine-color-blue-filled)'
324
+ : 'var(--mantine-color-gray-4)',
325
+ transition: 'all 200ms ease',
326
+ }}
327
+ />
328
+ );
329
+ }
330
+
331
+ return (
332
+ <Group
333
+ gap={6}
334
+ justify="center"
335
+ style={{
336
+ position: 'absolute',
337
+ left: 0,
338
+ right: 0,
339
+ [indicatorsPosition === 'top' ? 'top' : 'bottom']: 12,
340
+ zIndex: 10,
341
+ }}
342
+ >
343
+ {indicators}
344
+ </Group>
345
+ );
346
+ };
347
+
348
+ // Render bottom controls
349
+ const renderBottomControls = () => {
350
+ if (controlsPosition !== 'bottom' && controlsPosition !== 'bottom-right') {
351
+ return null;
352
+ }
353
+
354
+ return (
355
+ <Group
356
+ justify={controlsPosition === 'bottom' ? 'center' : 'flex-end'}
357
+ p="sm"
358
+ gap="xs"
359
+ >
360
+ {showPlayPause && autoplay && (
361
+ <ActionIcon
362
+ variant="subtle"
363
+ onClick={() => setIsPlaying(!isPlaying)}
364
+ >
365
+ {isPlaying ? <IconPlayerPause size={18} /> : <IconPlayerPlay size={18} />}
366
+ </ActionIcon>
367
+ )}
368
+ <ActionIcon
369
+ variant="subtle"
370
+ onClick={goToPrev}
371
+ disabled={!canGoPrev}
372
+ >
373
+ <IconChevronLeft size={18} />
374
+ </ActionIcon>
375
+ <Text size="sm" c="dimmed">
376
+ {currentSlide + 1} / {maxSlide + 1}
377
+ </Text>
378
+ <ActionIcon
379
+ variant="subtle"
380
+ onClick={goToNext}
381
+ disabled={!canGoNext}
382
+ >
383
+ <IconChevronRight size={18} />
384
+ </ActionIcon>
385
+ </Group>
386
+ );
387
+ };
388
+
389
+ return (
390
+ <Box {...boxProps}>
391
+ <Box
392
+ ref={containerRef}
393
+ style={{
394
+ position: 'relative',
395
+ height,
396
+ overflow: 'hidden',
397
+ cursor: draggable ? (isDragging ? 'grabbing' : 'grab') : 'default',
398
+ }}
399
+ onMouseDown={handleMouseDown}
400
+ onMouseMove={handleMouseMove}
401
+ onMouseUp={handleMouseUp}
402
+ onMouseLeave={handleMouseLeave}
403
+ onMouseEnter={handleMouseEnter}
404
+ onTouchStart={handleTouchStart}
405
+ onTouchMove={handleTouchMove}
406
+ onTouchEnd={handleTouchEnd}
407
+ >
408
+ {/* Track */}
409
+ <Box
410
+ style={{
411
+ display: 'flex',
412
+ flexDirection: isHorizontal ? 'row' : 'column',
413
+ gap,
414
+ height: '100%',
415
+ transform: isHorizontal
416
+ ? `translateX(${translateValue}px)`
417
+ : `translateY(${translateValue}px)`,
418
+ transition: isDragging
419
+ ? 'none'
420
+ : `transform ${transitionDuration}ms ${transitionTimingFunction}`,
421
+ userSelect: 'none',
422
+ }}
423
+ >
424
+ {slides.map((slide, index) => (
425
+ <Box
426
+ key={index}
427
+ style={{
428
+ flex: `0 0 calc((100% - ${gap * (slidesToShow - 1)}px) / ${slidesToShow})`,
429
+ minWidth: isHorizontal
430
+ ? `calc((100% - ${gap * (slidesToShow - 1)}px) / ${slidesToShow})`
431
+ : '100%',
432
+ minHeight: !isHorizontal
433
+ ? `calc((100% - ${gap * (slidesToShow - 1)}px) / ${slidesToShow})`
434
+ : '100%',
435
+ }}
436
+ >
437
+ {slide}
438
+ </Box>
439
+ ))}
440
+ </Box>
441
+
442
+ {/* Controls */}
443
+ {renderControls()}
444
+ {renderIndicators()}
445
+ </Box>
446
+
447
+ {/* Bottom controls */}
448
+ {renderBottomControls()}
449
+ </Box>
450
+ );
451
+ }
452
+
453
+ // =============================================================================
454
+ // ArchbaseCarouselSlide Component
455
+ // =============================================================================
456
+
457
+ export interface ArchbaseCarouselSlideProps extends BoxProps {
458
+ children: ReactNode;
459
+ }
460
+
461
+ export function ArchbaseCarouselSlide({ children, ...props }: ArchbaseCarouselSlideProps) {
462
+ return (
463
+ <Box
464
+ style={{
465
+ width: '100%',
466
+ height: '100%',
467
+ }}
468
+ {...props}
469
+ >
470
+ {children}
471
+ </Box>
472
+ );
473
+ }
474
+
475
+ // =============================================================================
476
+ // ArchbaseImageCarousel Component
477
+ // =============================================================================
478
+
479
+ export interface ImageSlide {
480
+ src: string;
481
+ alt?: string;
482
+ title?: string;
483
+ description?: string;
484
+ }
485
+
486
+ export interface ArchbaseImageCarouselProps
487
+ extends Omit<ArchbaseCarouselProps, 'children'> {
488
+ /** Array de imagens */
489
+ images: ImageSlide[];
490
+ /** Object fit das imagens */
491
+ objectFit?: 'cover' | 'contain' | 'fill' | 'none';
492
+ /** Mostrar overlay com título/descrição */
493
+ showOverlay?: boolean;
494
+ /** Posição do overlay */
495
+ overlayPosition?: 'bottom' | 'top' | 'center';
496
+ /** Callback ao clicar em uma imagem */
497
+ onImageClick?: (image: ImageSlide, index: number) => void;
498
+ }
499
+
500
+ export function ArchbaseImageCarousel({
501
+ images,
502
+ objectFit = 'cover',
503
+ showOverlay = true,
504
+ overlayPosition = 'bottom',
505
+ onImageClick,
506
+ ...carouselProps
507
+ }: ArchbaseImageCarouselProps) {
508
+ const getOverlayStyles = (): React.CSSProperties => {
509
+ const base: React.CSSProperties = {
510
+ position: 'absolute',
511
+ left: 0,
512
+ right: 0,
513
+ padding: 16,
514
+ background: 'linear-gradient(transparent, rgba(0,0,0,0.7))',
515
+ color: 'white',
516
+ };
517
+
518
+ switch (overlayPosition) {
519
+ case 'top':
520
+ return {
521
+ ...base,
522
+ top: 0,
523
+ background: 'linear-gradient(rgba(0,0,0,0.7), transparent)',
524
+ };
525
+ case 'center':
526
+ return {
527
+ ...base,
528
+ top: '50%',
529
+ transform: 'translateY(-50%)',
530
+ background: 'rgba(0,0,0,0.5)',
531
+ };
532
+ case 'bottom':
533
+ default:
534
+ return {
535
+ ...base,
536
+ bottom: 0,
537
+ };
538
+ }
539
+ };
540
+
541
+ return (
542
+ <ArchbaseCarousel {...carouselProps}>
543
+ {images.map((image, index) => (
544
+ <Box
545
+ key={index}
546
+ style={{
547
+ position: 'relative',
548
+ width: '100%',
549
+ height: '100%',
550
+ cursor: onImageClick ? 'pointer' : 'default',
551
+ }}
552
+ onClick={() => onImageClick?.(image, index)}
553
+ >
554
+ <img
555
+ src={image.src}
556
+ alt={image.alt ?? ''}
557
+ style={{
558
+ width: '100%',
559
+ height: '100%',
560
+ objectFit,
561
+ }}
562
+ />
563
+ {showOverlay && (image.title || image.description) && (
564
+ <Box style={getOverlayStyles()}>
565
+ {image.title && (
566
+ <Text size="lg" fw={600} c="white">
567
+ {image.title}
568
+ </Text>
569
+ )}
570
+ {image.description && (
571
+ <Text size="sm" c="white" style={{ opacity: 0.9 }}>
572
+ {image.description}
573
+ </Text>
574
+ )}
575
+ </Box>
576
+ )}
577
+ </Box>
578
+ ))}
579
+ </ArchbaseCarousel>
580
+ );
581
+ }
582
+
583
+ // =============================================================================
584
+ // ArchbaseCardCarousel Component
585
+ // =============================================================================
586
+
587
+ export interface ArchbaseCardCarouselProps
588
+ extends Omit<ArchbaseCarouselProps, 'children'> {
589
+ /** Renderizador de card */
590
+ renderCard: (index: number) => ReactNode;
591
+ /** Total de cards */
592
+ totalCards: number;
593
+ }
594
+
595
+ export function ArchbaseCardCarousel({
596
+ renderCard,
597
+ totalCards,
598
+ slidesToShow = 3,
599
+ gap = 24,
600
+ ...carouselProps
601
+ }: ArchbaseCardCarouselProps) {
602
+ const cards = [];
603
+ for (let i = 0; i < totalCards; i++) {
604
+ cards.push(renderCard(i));
605
+ }
606
+
607
+ return (
608
+ <ArchbaseCarousel slidesToShow={slidesToShow} gap={gap} {...carouselProps}>
609
+ {cards}
610
+ </ArchbaseCarousel>
611
+ );
612
+ }
613
+
614
+ // =============================================================================
615
+ // Hook useArchbaseCarousel
616
+ // =============================================================================
617
+
618
+ export interface UseArchbaseCarouselOptions {
619
+ totalSlides: number;
620
+ slidesToShow?: number;
621
+ slidesToScroll?: number;
622
+ loop?: boolean;
623
+ initialSlide?: number;
624
+ }
625
+
626
+ export function useArchbaseCarousel({
627
+ totalSlides,
628
+ slidesToShow = 1,
629
+ slidesToScroll = 1,
630
+ loop = false,
631
+ initialSlide = 0,
632
+ }: UseArchbaseCarouselOptions) {
633
+ const [activeSlide, setActiveSlide] = useState(initialSlide);
634
+ const maxSlide = Math.max(0, totalSlides - slidesToShow);
635
+
636
+ const goToSlide = useCallback(
637
+ (index: number) => {
638
+ let newIndex = index;
639
+
640
+ if (loop) {
641
+ if (newIndex < 0) newIndex = maxSlide;
642
+ if (newIndex > maxSlide) newIndex = 0;
643
+ } else {
644
+ newIndex = Math.max(0, Math.min(newIndex, maxSlide));
645
+ }
646
+
647
+ setActiveSlide(newIndex);
648
+ },
649
+ [loop, maxSlide]
650
+ );
651
+
652
+ const goToPrev = useCallback(() => {
653
+ goToSlide(activeSlide - slidesToScroll);
654
+ }, [activeSlide, slidesToScroll, goToSlide]);
655
+
656
+ const goToNext = useCallback(() => {
657
+ goToSlide(activeSlide + slidesToScroll);
658
+ }, [activeSlide, slidesToScroll, goToSlide]);
659
+
660
+ const goToFirst = useCallback(() => goToSlide(0), [goToSlide]);
661
+ const goToLast = useCallback(() => goToSlide(maxSlide), [goToSlide, maxSlide]);
662
+
663
+ const canGoPrev = loop || activeSlide > 0;
664
+ const canGoNext = loop || activeSlide < maxSlide;
665
+
666
+ return {
667
+ activeSlide,
668
+ setActiveSlide: goToSlide,
669
+ goToPrev,
670
+ goToNext,
671
+ goToFirst,
672
+ goToLast,
673
+ canGoPrev,
674
+ canGoNext,
675
+ maxSlide,
676
+ };
677
+ }
678
+
679
+ export default ArchbaseCarousel;