@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.
- package/dist/buttons/ArchbaseSpeedDial.d.ts +59 -0
- package/dist/buttons/index.d.ts +2 -0
- package/dist/charts/ArchbaseArcGauge.d.ts +55 -0
- package/dist/charts/ArchbaseGantt.d.ts +70 -0
- package/dist/charts/ArchbaseLinearGauge.d.ts +55 -0
- package/dist/charts/ArchbaseResourceTimeline.d.ts +61 -0
- package/dist/charts/ArchbaseSparkline.d.ts +45 -0
- package/dist/charts/index.d.ts +10 -0
- package/dist/containers/ArchbaseExpansionPanel.d.ts +60 -0
- package/dist/containers/ArchbaseFloatingPanel.d.ts +91 -0
- package/dist/containers/index.d.ts +4 -0
- package/dist/datagrid/export/ArchbaseDataGridExcelExport.d.ts +67 -0
- package/dist/datagrid/export/index.d.ts +2 -0
- package/dist/datagrid/index.d.ts +1 -0
- package/dist/display/ArchbaseAnimationWrapper.d.ts +73 -0
- package/dist/display/ArchbaseBarcodeGenerator.d.ts +50 -0
- package/dist/display/ArchbaseCarousel.d.ts +100 -0
- package/dist/display/ArchbaseCodeViewer.d.ts +63 -0
- package/dist/display/ArchbaseDiffViewer.d.ts +41 -0
- package/dist/display/ArchbaseJsonTree.d.ts +34 -0
- package/dist/display/ArchbaseLightbox.d.ts +89 -0
- package/dist/display/ArchbaseMarquee.d.ts +46 -0
- package/dist/display/ArchbasePhotoAlbum.d.ts +58 -0
- package/dist/display/ArchbaseQRCode.d.ts +44 -0
- package/dist/display/ArchbaseTooltipRich.d.ts +82 -0
- package/dist/display/ArchbaseVirtualList.d.ts +116 -0
- package/dist/display/index.d.ts +24 -0
- package/dist/editors/ArchbaseAIPromptInput.d.ts +69 -0
- package/dist/editors/ArchbaseAsyncMultiSelect.d.ts +1 -1
- package/dist/editors/ArchbaseAsyncSelect.d.ts +1 -1
- package/dist/editors/ArchbaseBarcodeScanner.d.ts +63 -0
- package/dist/editors/ArchbaseCepInput.d.ts +26 -0
- package/dist/editors/ArchbaseColorGradientPicker.d.ts +80 -0
- package/dist/editors/ArchbaseCpfCnpjInput.d.ts +21 -0
- package/dist/editors/ArchbaseCurrencyInput.d.ts +25 -0
- package/dist/editors/ArchbaseDualListbox.d.ts +61 -0
- package/dist/editors/ArchbaseInPlaceEditor.d.ts +67 -0
- package/dist/editors/ArchbaseLightGrid.d.ts +56 -0
- package/dist/editors/ArchbaseMentionInput.d.ts +75 -0
- package/dist/editors/ArchbaseMultiEmail.d.ts +53 -0
- package/dist/editors/ArchbaseMultiViewCalendar.d.ts +47 -0
- package/dist/editors/ArchbaseNumberStepper.d.ts +66 -0
- package/dist/editors/ArchbaseOTPInput.d.ts +27 -0
- package/dist/editors/ArchbasePhoneInput.d.ts +52 -0
- package/dist/editors/ArchbaseRangeSlider.d.ts +22 -0
- package/dist/editors/ArchbaseSignaturePad.d.ts +60 -0
- package/dist/editors/ArchbaseSpeechToTextInput.d.ts +106 -0
- package/dist/editors/ArchbaseSpreadsheet.d.ts +56 -0
- package/dist/editors/ArchbaseTagInput.d.ts +55 -0
- package/dist/editors/index.d.ts +40 -0
- package/dist/feedback/ArchbaseChunkProgressBar.d.ts +35 -0
- package/dist/feedback/ArchbaseRipple.d.ts +43 -0
- package/dist/feedback/ArchbaseSkeleton.d.ts +115 -0
- package/dist/feedback/index.d.ts +6 -0
- package/dist/filters/ArchbaseColumnSelector.d.ts +38 -0
- package/dist/filters/index.d.ts +2 -0
- package/dist/forms/ArchbaseFieldArray.d.ts +80 -0
- package/dist/forms/ArchbaseFormWizard.d.ts +73 -0
- package/dist/forms/ArchbaseStepper.d.ts +51 -0
- package/dist/forms/index.d.ts +8 -0
- package/dist/forms/useArchbaseFormWizard.d.ts +74 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useArchbaseConfirm.d.ts +59 -0
- package/dist/image/editor/index.d.ts +43 -4
- package/dist/image/index.d.ts +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +22510 -13820
- package/dist/list/ArchbaseTreeList.d.ts +76 -0
- package/dist/list/index.d.ts +2 -0
- package/dist/navigation/ArchbaseActionSheet.d.ts +70 -0
- package/dist/navigation/ArchbaseAppBar.d.ts +63 -0
- package/dist/navigation/ArchbaseBottomNavigation.d.ts +61 -0
- package/dist/navigation/ArchbaseContextMenu.d.ts +76 -0
- package/dist/navigation/ArchbaseKeyboardShortcuts.d.ts +85 -0
- package/dist/navigation/ArchbaseSpotlight.d.ts +69 -0
- package/dist/navigation/index.d.ts +12 -0
- package/dist/notification/ArchbaseNotificationCenter.d.ts +56 -0
- package/dist/notification/index.d.ts +2 -0
- package/dist/onboarding/ArchbaseOnboardingTour.d.ts +27 -76
- package/dist/viewers/ArchbaseDocViewer.d.ts +40 -0
- package/dist/viewers/ArchbasePdfBuilder.d.ts +53 -0
- package/dist/viewers/ArchbaseXmlViewer.d.ts +36 -0
- package/dist/viewers/index.d.ts +6 -0
- package/package.json +41 -18
- package/src/buttons/ArchbaseSpeedDial.tsx +282 -0
- package/src/buttons/index.ts +3 -0
- package/src/charts/ArchbaseArcGauge.tsx +325 -0
- package/src/charts/ArchbaseGantt.tsx +494 -0
- package/src/charts/ArchbaseLinearGauge.tsx +288 -0
- package/src/charts/ArchbaseResourceTimeline.tsx +433 -0
- package/src/charts/ArchbaseSparkline.tsx +193 -0
- package/src/charts/index.ts +20 -1
- package/src/containers/ArchbaseExpansionPanel.tsx +208 -0
- package/src/containers/ArchbaseFloatingPanel.tsx +448 -0
- package/src/containers/index.ts +17 -0
- package/src/datagrid/components/pagination/grid-pagination.tsx +2 -2
- package/src/datagrid/export/ArchbaseDataGridExcelExport.tsx +293 -0
- package/src/datagrid/export/index.ts +8 -0
- package/src/datagrid/index.tsx +2 -0
- package/src/display/ArchbaseAnimationWrapper.tsx +272 -0
- package/src/display/ArchbaseBarcodeGenerator.tsx +303 -0
- package/src/display/ArchbaseCarousel.tsx +679 -0
- package/src/display/ArchbaseCodeViewer.tsx +271 -0
- package/src/display/ArchbaseDiffViewer.tsx +108 -0
- package/src/display/ArchbaseJsonTree.tsx +150 -0
- package/src/display/ArchbaseLightbox.tsx +279 -0
- package/src/display/ArchbaseMarquee.tsx +268 -0
- package/src/display/ArchbasePhotoAlbum.tsx +415 -0
- package/src/display/ArchbaseQRCode.tsx +137 -0
- package/src/display/ArchbaseTooltipRich.tsx +317 -0
- package/src/display/ArchbaseVirtualList.tsx +618 -0
- package/src/display/index.ts +94 -1
- package/src/editors/ArchbaseAIPromptInput.tsx +432 -0
- package/src/editors/ArchbaseBarcodeScanner.tsx +570 -0
- package/src/editors/ArchbaseCepInput.tsx +173 -0
- package/src/editors/ArchbaseColorGradientPicker.tsx +460 -0
- package/src/editors/ArchbaseCpfCnpjInput.tsx +193 -0
- package/src/editors/ArchbaseCurrencyInput.tsx +100 -0
- package/src/editors/ArchbaseDualListbox.tsx +671 -0
- package/src/editors/ArchbaseInPlaceEditor.tsx +406 -0
- package/src/editors/ArchbaseLightGrid.tsx +538 -0
- package/src/editors/ArchbaseMentionInput.tsx +384 -0
- package/src/editors/ArchbaseMultiEmail.tsx +367 -0
- package/src/editors/ArchbaseMultiViewCalendar.tsx +439 -0
- package/src/editors/ArchbaseNumberStepper.tsx +219 -0
- package/src/editors/ArchbaseOTPInput.tsx +133 -0
- package/src/editors/ArchbaseOperationHoursEditor.tsx +1 -1
- package/src/editors/ArchbasePhoneInput.tsx +355 -0
- package/src/editors/ArchbaseRangeSlider.tsx +109 -0
- package/src/editors/ArchbaseSignaturePad.tsx +425 -0
- package/src/editors/ArchbaseSpeechToTextInput.tsx +444 -0
- package/src/editors/ArchbaseSpreadsheet.tsx +388 -0
- package/src/editors/ArchbaseTagInput.tsx +326 -0
- package/src/editors/index.tsx +92 -0
- package/src/feedback/ArchbaseChunkProgressBar.tsx +156 -0
- package/src/feedback/ArchbaseRipple.tsx +278 -0
- package/src/feedback/ArchbaseSkeleton.tsx +419 -0
- package/src/feedback/index.ts +22 -1
- package/src/filters/ArchbaseColumnSelector.tsx +268 -0
- package/src/filters/index.ts +2 -0
- package/src/forms/ArchbaseFieldArray.tsx +305 -0
- package/src/forms/ArchbaseFormWizard.tsx +284 -0
- package/src/forms/ArchbaseStepper.tsx +137 -0
- package/src/forms/index.ts +24 -2
- package/src/forms/useArchbaseFormWizard.ts +283 -0
- package/src/hooks/index.ts +12 -0
- package/src/hooks/useArchbaseConfirm.ts +293 -0
- package/src/image/editor/functions/image-processing.ts +5 -0
- package/src/image/editor/index.tsx +305 -391
- package/src/image/index.ts +1 -0
- package/src/index.ts +3 -0
- package/src/list/ArchbaseTreeList.tsx +422 -0
- package/src/list/index.ts +3 -0
- package/src/navigation/ArchbaseActionSheet.tsx +301 -0
- package/src/navigation/ArchbaseAppBar.tsx +233 -0
- package/src/navigation/ArchbaseBottomNavigation.tsx +243 -0
- package/src/navigation/ArchbaseContextMenu.tsx +298 -0
- package/src/navigation/ArchbaseKeyboardShortcuts.tsx +472 -0
- package/src/navigation/ArchbaseSpotlight.tsx +483 -0
- package/src/navigation/index.ts +48 -1
- package/src/notification/ArchbaseNotificationCenter.tsx +502 -0
- package/src/notification/index.ts +2 -0
- package/src/onboarding/ArchbaseOnboardingTour.tsx +229 -102
- package/src/viewers/ArchbaseDocViewer.tsx +584 -0
- package/src/viewers/ArchbasePdfBuilder.tsx +291 -0
- package/src/viewers/ArchbaseXmlViewer.tsx +142 -0
- package/src/viewers/index.ts +12 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ReactNode,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
useMemo,
|
|
8
|
+
forwardRef,
|
|
9
|
+
useImperativeHandle,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import {
|
|
12
|
+
Box,
|
|
13
|
+
BoxProps,
|
|
14
|
+
Text,
|
|
15
|
+
Center,
|
|
16
|
+
Loader,
|
|
17
|
+
ScrollArea,
|
|
18
|
+
Stack,
|
|
19
|
+
} from '@mantine/core';
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Types
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
export interface VirtualListItem {
|
|
26
|
+
id: string | number;
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ArchbaseVirtualListProps<T extends VirtualListItem>
|
|
31
|
+
extends Omit<BoxProps, 'children'> {
|
|
32
|
+
/** Array de itens */
|
|
33
|
+
items: T[];
|
|
34
|
+
/** Altura de cada item em pixels */
|
|
35
|
+
itemHeight: number;
|
|
36
|
+
/** Altura do container */
|
|
37
|
+
height: number | string;
|
|
38
|
+
/** Largura do container */
|
|
39
|
+
width?: number | string;
|
|
40
|
+
/** Quantidade de itens extras para renderizar (overscan) */
|
|
41
|
+
overscan?: number;
|
|
42
|
+
/** Renderizador de item */
|
|
43
|
+
renderItem: (item: T, index: number, style: React.CSSProperties) => ReactNode;
|
|
44
|
+
/** Callback ao alcançar o final */
|
|
45
|
+
onEndReached?: () => void;
|
|
46
|
+
/** Threshold para onEndReached (em pixels) */
|
|
47
|
+
endReachedThreshold?: number;
|
|
48
|
+
/** Estado de carregamento */
|
|
49
|
+
loading?: boolean;
|
|
50
|
+
/** Componente de loading */
|
|
51
|
+
loadingComponent?: ReactNode;
|
|
52
|
+
/** Mensagem quando vazio */
|
|
53
|
+
emptyMessage?: string;
|
|
54
|
+
/** Componente quando vazio */
|
|
55
|
+
emptyComponent?: ReactNode;
|
|
56
|
+
/** Renderizar header */
|
|
57
|
+
header?: ReactNode;
|
|
58
|
+
/** Renderizar footer */
|
|
59
|
+
footer?: ReactNode;
|
|
60
|
+
/** Callback ao scrollar */
|
|
61
|
+
onScroll?: (scrollTop: number) => void;
|
|
62
|
+
/** Scroll inicial */
|
|
63
|
+
initialScrollTop?: number;
|
|
64
|
+
/** Estilo dos itens */
|
|
65
|
+
itemStyle?: React.CSSProperties;
|
|
66
|
+
/** Gap entre itens */
|
|
67
|
+
gap?: number;
|
|
68
|
+
/** Key extractor */
|
|
69
|
+
keyExtractor?: (item: T, index: number) => string | number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ArchbaseVirtualListRef {
|
|
73
|
+
scrollToIndex: (index: number, align?: 'start' | 'center' | 'end') => void;
|
|
74
|
+
scrollToTop: () => void;
|
|
75
|
+
scrollToBottom: () => void;
|
|
76
|
+
getScrollTop: () => number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// ArchbaseVirtualList Component
|
|
81
|
+
// =============================================================================
|
|
82
|
+
|
|
83
|
+
function ArchbaseVirtualListInner<T extends VirtualListItem>(
|
|
84
|
+
{
|
|
85
|
+
items,
|
|
86
|
+
itemHeight,
|
|
87
|
+
height,
|
|
88
|
+
width = '100%',
|
|
89
|
+
overscan = 3,
|
|
90
|
+
renderItem,
|
|
91
|
+
onEndReached,
|
|
92
|
+
endReachedThreshold = 200,
|
|
93
|
+
loading = false,
|
|
94
|
+
loadingComponent,
|
|
95
|
+
emptyMessage = 'Nenhum item encontrado',
|
|
96
|
+
emptyComponent,
|
|
97
|
+
header,
|
|
98
|
+
footer,
|
|
99
|
+
onScroll,
|
|
100
|
+
initialScrollTop = 0,
|
|
101
|
+
itemStyle,
|
|
102
|
+
gap = 0,
|
|
103
|
+
keyExtractor,
|
|
104
|
+
...boxProps
|
|
105
|
+
}: ArchbaseVirtualListProps<T>,
|
|
106
|
+
ref: React.Ref<ArchbaseVirtualListRef>
|
|
107
|
+
) {
|
|
108
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
109
|
+
const [scrollTop, setScrollTop] = useState(initialScrollTop);
|
|
110
|
+
const [containerHeight, setContainerHeight] = useState(0);
|
|
111
|
+
const endReachedCalled = useRef(false);
|
|
112
|
+
|
|
113
|
+
// Calculate container height
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (containerRef.current) {
|
|
116
|
+
setContainerHeight(containerRef.current.clientHeight);
|
|
117
|
+
}
|
|
118
|
+
}, [height]);
|
|
119
|
+
|
|
120
|
+
// Reset end reached flag when items change
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
endReachedCalled.current = false;
|
|
123
|
+
}, [items.length]);
|
|
124
|
+
|
|
125
|
+
// Effective item height (including gap)
|
|
126
|
+
const effectiveItemHeight = itemHeight + gap;
|
|
127
|
+
|
|
128
|
+
// Total height of all items
|
|
129
|
+
const totalHeight = items.length * effectiveItemHeight - gap;
|
|
130
|
+
|
|
131
|
+
// Calculate visible range
|
|
132
|
+
const startIndex = Math.max(0, Math.floor(scrollTop / effectiveItemHeight) - overscan);
|
|
133
|
+
const endIndex = Math.min(
|
|
134
|
+
items.length - 1,
|
|
135
|
+
Math.ceil((scrollTop + containerHeight) / effectiveItemHeight) + overscan
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Visible items
|
|
139
|
+
const visibleItems = useMemo(() => {
|
|
140
|
+
const result = [];
|
|
141
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
142
|
+
if (items[i]) {
|
|
143
|
+
result.push({ item: items[i], index: i });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}, [items, startIndex, endIndex]);
|
|
148
|
+
|
|
149
|
+
// Handle scroll
|
|
150
|
+
const handleScroll = useCallback(
|
|
151
|
+
(event: React.UIEvent<HTMLDivElement>) => {
|
|
152
|
+
const target = event.currentTarget;
|
|
153
|
+
const newScrollTop = target.scrollTop;
|
|
154
|
+
setScrollTop(newScrollTop);
|
|
155
|
+
onScroll?.(newScrollTop);
|
|
156
|
+
|
|
157
|
+
// Check if reached end
|
|
158
|
+
if (
|
|
159
|
+
onEndReached &&
|
|
160
|
+
!endReachedCalled.current &&
|
|
161
|
+
target.scrollHeight - target.scrollTop - target.clientHeight < endReachedThreshold
|
|
162
|
+
) {
|
|
163
|
+
endReachedCalled.current = true;
|
|
164
|
+
onEndReached();
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
[onScroll, onEndReached, endReachedThreshold]
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Imperative methods
|
|
171
|
+
useImperativeHandle(
|
|
172
|
+
ref,
|
|
173
|
+
() => ({
|
|
174
|
+
scrollToIndex: (index: number, align: 'start' | 'center' | 'end' = 'start') => {
|
|
175
|
+
if (!containerRef.current) return;
|
|
176
|
+
|
|
177
|
+
const itemTop = index * effectiveItemHeight;
|
|
178
|
+
let targetScrollTop = itemTop;
|
|
179
|
+
|
|
180
|
+
if (align === 'center') {
|
|
181
|
+
targetScrollTop = itemTop - containerHeight / 2 + itemHeight / 2;
|
|
182
|
+
} else if (align === 'end') {
|
|
183
|
+
targetScrollTop = itemTop - containerHeight + itemHeight;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
containerRef.current.scrollTop = Math.max(0, targetScrollTop);
|
|
187
|
+
},
|
|
188
|
+
scrollToTop: () => {
|
|
189
|
+
if (containerRef.current) {
|
|
190
|
+
containerRef.current.scrollTop = 0;
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
scrollToBottom: () => {
|
|
194
|
+
if (containerRef.current) {
|
|
195
|
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
getScrollTop: () => scrollTop,
|
|
199
|
+
}),
|
|
200
|
+
[effectiveItemHeight, containerHeight, itemHeight, scrollTop]
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Get item key
|
|
204
|
+
const getKey = useCallback(
|
|
205
|
+
(item: T, index: number) => {
|
|
206
|
+
if (keyExtractor) {
|
|
207
|
+
return keyExtractor(item, index);
|
|
208
|
+
}
|
|
209
|
+
return item.id ?? index;
|
|
210
|
+
},
|
|
211
|
+
[keyExtractor]
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Empty state
|
|
215
|
+
if (items.length === 0 && !loading) {
|
|
216
|
+
return (
|
|
217
|
+
<Box style={{ height, width }} {...boxProps}>
|
|
218
|
+
{header}
|
|
219
|
+
{emptyComponent ?? (
|
|
220
|
+
<Center style={{ height: '100%' }}>
|
|
221
|
+
<Text c="dimmed">{emptyMessage}</Text>
|
|
222
|
+
</Center>
|
|
223
|
+
)}
|
|
224
|
+
{footer}
|
|
225
|
+
</Box>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<Box {...boxProps}>
|
|
231
|
+
{header}
|
|
232
|
+
<Box
|
|
233
|
+
ref={containerRef}
|
|
234
|
+
onScroll={handleScroll}
|
|
235
|
+
style={{
|
|
236
|
+
height,
|
|
237
|
+
width,
|
|
238
|
+
overflow: 'auto',
|
|
239
|
+
position: 'relative',
|
|
240
|
+
}}
|
|
241
|
+
>
|
|
242
|
+
{/* Spacer for total height */}
|
|
243
|
+
<Box style={{ height: totalHeight, position: 'relative' }}>
|
|
244
|
+
{/* Visible items */}
|
|
245
|
+
{visibleItems.map(({ item, index }) => {
|
|
246
|
+
const style: React.CSSProperties = {
|
|
247
|
+
position: 'absolute',
|
|
248
|
+
top: index * effectiveItemHeight,
|
|
249
|
+
left: 0,
|
|
250
|
+
right: 0,
|
|
251
|
+
height: itemHeight,
|
|
252
|
+
...itemStyle,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<React.Fragment key={getKey(item, index)}>
|
|
257
|
+
{renderItem(item, index, style)}
|
|
258
|
+
</React.Fragment>
|
|
259
|
+
);
|
|
260
|
+
})}
|
|
261
|
+
</Box>
|
|
262
|
+
|
|
263
|
+
{/* Loading at bottom */}
|
|
264
|
+
{loading && (
|
|
265
|
+
<Center py="md">
|
|
266
|
+
{loadingComponent ?? <Loader size="sm" />}
|
|
267
|
+
</Center>
|
|
268
|
+
)}
|
|
269
|
+
</Box>
|
|
270
|
+
{footer}
|
|
271
|
+
</Box>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export const ArchbaseVirtualList = forwardRef(ArchbaseVirtualListInner) as <
|
|
276
|
+
T extends VirtualListItem
|
|
277
|
+
>(
|
|
278
|
+
props: ArchbaseVirtualListProps<T> & { ref?: React.Ref<ArchbaseVirtualListRef> }
|
|
279
|
+
) => ReturnType<typeof ArchbaseVirtualListInner>;
|
|
280
|
+
|
|
281
|
+
// =============================================================================
|
|
282
|
+
// ArchbaseVirtualGrid Component
|
|
283
|
+
// =============================================================================
|
|
284
|
+
|
|
285
|
+
export interface ArchbaseVirtualGridProps<T extends VirtualListItem>
|
|
286
|
+
extends Omit<BoxProps, 'children'> {
|
|
287
|
+
/** Array de itens */
|
|
288
|
+
items: T[];
|
|
289
|
+
/** Largura de cada item */
|
|
290
|
+
itemWidth: number;
|
|
291
|
+
/** Altura de cada item */
|
|
292
|
+
itemHeight: number;
|
|
293
|
+
/** Altura do container */
|
|
294
|
+
height: number | string;
|
|
295
|
+
/** Gap entre itens */
|
|
296
|
+
gap?: number;
|
|
297
|
+
/** Overscan de linhas */
|
|
298
|
+
overscan?: number;
|
|
299
|
+
/** Renderizador de item */
|
|
300
|
+
renderItem: (item: T, index: number, style: React.CSSProperties) => ReactNode;
|
|
301
|
+
/** Callback ao alcançar o final */
|
|
302
|
+
onEndReached?: () => void;
|
|
303
|
+
/** Threshold para onEndReached */
|
|
304
|
+
endReachedThreshold?: number;
|
|
305
|
+
/** Estado de carregamento */
|
|
306
|
+
loading?: boolean;
|
|
307
|
+
/** Mensagem quando vazio */
|
|
308
|
+
emptyMessage?: string;
|
|
309
|
+
/** Key extractor */
|
|
310
|
+
keyExtractor?: (item: T, index: number) => string | number;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function ArchbaseVirtualGrid<T extends VirtualListItem>({
|
|
314
|
+
items,
|
|
315
|
+
itemWidth,
|
|
316
|
+
itemHeight,
|
|
317
|
+
height,
|
|
318
|
+
gap = 16,
|
|
319
|
+
overscan = 2,
|
|
320
|
+
renderItem,
|
|
321
|
+
onEndReached,
|
|
322
|
+
endReachedThreshold = 200,
|
|
323
|
+
loading = false,
|
|
324
|
+
emptyMessage = 'Nenhum item encontrado',
|
|
325
|
+
keyExtractor,
|
|
326
|
+
...boxProps
|
|
327
|
+
}: ArchbaseVirtualGridProps<T>) {
|
|
328
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
329
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
330
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
331
|
+
const [containerHeight, setContainerHeight] = useState(0);
|
|
332
|
+
const endReachedCalled = useRef(false);
|
|
333
|
+
|
|
334
|
+
// Calculate dimensions
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
if (containerRef.current) {
|
|
337
|
+
setContainerWidth(containerRef.current.clientWidth);
|
|
338
|
+
setContainerHeight(containerRef.current.clientHeight);
|
|
339
|
+
|
|
340
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
341
|
+
const entry = entries[0];
|
|
342
|
+
if (entry) {
|
|
343
|
+
setContainerWidth(entry.contentRect.width);
|
|
344
|
+
setContainerHeight(entry.contentRect.height);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
resizeObserver.observe(containerRef.current);
|
|
349
|
+
return () => resizeObserver.disconnect();
|
|
350
|
+
}
|
|
351
|
+
}, []);
|
|
352
|
+
|
|
353
|
+
// Calculate columns
|
|
354
|
+
const columns = Math.max(1, Math.floor((containerWidth + gap) / (itemWidth + gap)));
|
|
355
|
+
const rows = Math.ceil(items.length / columns);
|
|
356
|
+
const effectiveRowHeight = itemHeight + gap;
|
|
357
|
+
const totalHeight = rows * effectiveRowHeight - gap;
|
|
358
|
+
|
|
359
|
+
// Calculate visible range
|
|
360
|
+
const startRow = Math.max(0, Math.floor(scrollTop / effectiveRowHeight) - overscan);
|
|
361
|
+
const endRow = Math.min(
|
|
362
|
+
rows - 1,
|
|
363
|
+
Math.ceil((scrollTop + containerHeight) / effectiveRowHeight) + overscan
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// Visible items
|
|
367
|
+
const visibleItems = useMemo(() => {
|
|
368
|
+
const result = [];
|
|
369
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
370
|
+
for (let col = 0; col < columns; col++) {
|
|
371
|
+
const index = row * columns + col;
|
|
372
|
+
if (index < items.length) {
|
|
373
|
+
result.push({ item: items[index], index, row, col });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
378
|
+
}, [items, startRow, endRow, columns]);
|
|
379
|
+
|
|
380
|
+
// Handle scroll
|
|
381
|
+
const handleScroll = useCallback(
|
|
382
|
+
(event: React.UIEvent<HTMLDivElement>) => {
|
|
383
|
+
const target = event.currentTarget;
|
|
384
|
+
setScrollTop(target.scrollTop);
|
|
385
|
+
|
|
386
|
+
if (
|
|
387
|
+
onEndReached &&
|
|
388
|
+
!endReachedCalled.current &&
|
|
389
|
+
target.scrollHeight - target.scrollTop - target.clientHeight < endReachedThreshold
|
|
390
|
+
) {
|
|
391
|
+
endReachedCalled.current = true;
|
|
392
|
+
onEndReached();
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
[onEndReached, endReachedThreshold]
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// Reset end reached flag
|
|
399
|
+
useEffect(() => {
|
|
400
|
+
endReachedCalled.current = false;
|
|
401
|
+
}, [items.length]);
|
|
402
|
+
|
|
403
|
+
// Get key
|
|
404
|
+
const getKey = useCallback(
|
|
405
|
+
(item: T, index: number) => {
|
|
406
|
+
if (keyExtractor) return keyExtractor(item, index);
|
|
407
|
+
return item.id ?? index;
|
|
408
|
+
},
|
|
409
|
+
[keyExtractor]
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
// Empty state
|
|
413
|
+
if (items.length === 0 && !loading) {
|
|
414
|
+
return (
|
|
415
|
+
<Box style={{ height }} {...boxProps}>
|
|
416
|
+
<Center style={{ height: '100%' }}>
|
|
417
|
+
<Text c="dimmed">{emptyMessage}</Text>
|
|
418
|
+
</Center>
|
|
419
|
+
</Box>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<Box
|
|
425
|
+
ref={containerRef}
|
|
426
|
+
onScroll={handleScroll}
|
|
427
|
+
style={{
|
|
428
|
+
height,
|
|
429
|
+
overflow: 'auto',
|
|
430
|
+
position: 'relative',
|
|
431
|
+
}}
|
|
432
|
+
{...boxProps}
|
|
433
|
+
>
|
|
434
|
+
<Box style={{ height: totalHeight, position: 'relative' }}>
|
|
435
|
+
{visibleItems.map(({ item, index, row, col }) => {
|
|
436
|
+
const style: React.CSSProperties = {
|
|
437
|
+
position: 'absolute',
|
|
438
|
+
top: row * effectiveRowHeight,
|
|
439
|
+
left: col * (itemWidth + gap),
|
|
440
|
+
width: itemWidth,
|
|
441
|
+
height: itemHeight,
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<React.Fragment key={getKey(item, index)}>
|
|
446
|
+
{renderItem(item, index, style)}
|
|
447
|
+
</React.Fragment>
|
|
448
|
+
);
|
|
449
|
+
})}
|
|
450
|
+
</Box>
|
|
451
|
+
|
|
452
|
+
{loading && (
|
|
453
|
+
<Center py="md">
|
|
454
|
+
<Loader size="sm" />
|
|
455
|
+
</Center>
|
|
456
|
+
)}
|
|
457
|
+
</Box>
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// =============================================================================
|
|
462
|
+
// ArchbaseInfiniteList Component
|
|
463
|
+
// =============================================================================
|
|
464
|
+
|
|
465
|
+
export interface ArchbaseInfiniteListProps<T extends VirtualListItem>
|
|
466
|
+
extends Omit<ArchbaseVirtualListProps<T>, 'onEndReached'> {
|
|
467
|
+
/** Função para carregar mais itens */
|
|
468
|
+
loadMore: () => Promise<void>;
|
|
469
|
+
/** Se há mais itens para carregar */
|
|
470
|
+
hasMore: boolean;
|
|
471
|
+
/** Erro ao carregar */
|
|
472
|
+
error?: string | null;
|
|
473
|
+
/** Componente de erro */
|
|
474
|
+
errorComponent?: ReactNode;
|
|
475
|
+
/** Callback para retry */
|
|
476
|
+
onRetry?: () => void;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export function ArchbaseInfiniteList<T extends VirtualListItem>({
|
|
480
|
+
loadMore,
|
|
481
|
+
hasMore,
|
|
482
|
+
error,
|
|
483
|
+
errorComponent,
|
|
484
|
+
onRetry,
|
|
485
|
+
loading,
|
|
486
|
+
...props
|
|
487
|
+
}: ArchbaseInfiniteListProps<T>) {
|
|
488
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
489
|
+
|
|
490
|
+
const handleEndReached = useCallback(async () => {
|
|
491
|
+
if (isLoadingMore || !hasMore || error) return;
|
|
492
|
+
|
|
493
|
+
setIsLoadingMore(true);
|
|
494
|
+
try {
|
|
495
|
+
await loadMore();
|
|
496
|
+
} finally {
|
|
497
|
+
setIsLoadingMore(false);
|
|
498
|
+
}
|
|
499
|
+
}, [isLoadingMore, hasMore, error, loadMore]);
|
|
500
|
+
|
|
501
|
+
const footerContent = error ? (
|
|
502
|
+
errorComponent ?? (
|
|
503
|
+
<Center py="md">
|
|
504
|
+
<Stack align="center" gap="xs">
|
|
505
|
+
<Text c="red" size="sm">
|
|
506
|
+
{error}
|
|
507
|
+
</Text>
|
|
508
|
+
{onRetry && (
|
|
509
|
+
<Text
|
|
510
|
+
size="sm"
|
|
511
|
+
c="blue"
|
|
512
|
+
style={{ cursor: 'pointer' }}
|
|
513
|
+
onClick={onRetry}
|
|
514
|
+
>
|
|
515
|
+
Tentar novamente
|
|
516
|
+
</Text>
|
|
517
|
+
)}
|
|
518
|
+
</Stack>
|
|
519
|
+
</Center>
|
|
520
|
+
)
|
|
521
|
+
) : null;
|
|
522
|
+
|
|
523
|
+
return (
|
|
524
|
+
<ArchbaseVirtualList
|
|
525
|
+
{...props}
|
|
526
|
+
loading={loading || isLoadingMore}
|
|
527
|
+
onEndReached={handleEndReached}
|
|
528
|
+
footer={
|
|
529
|
+
<>
|
|
530
|
+
{props.footer}
|
|
531
|
+
{footerContent}
|
|
532
|
+
</>
|
|
533
|
+
}
|
|
534
|
+
/>
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// =============================================================================
|
|
539
|
+
// Hook useVirtualList
|
|
540
|
+
// =============================================================================
|
|
541
|
+
|
|
542
|
+
export interface UseVirtualListOptions<T> {
|
|
543
|
+
items: T[];
|
|
544
|
+
itemHeight: number;
|
|
545
|
+
containerHeight: number;
|
|
546
|
+
overscan?: number;
|
|
547
|
+
gap?: number;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export function useVirtualList<T>({
|
|
551
|
+
items,
|
|
552
|
+
itemHeight,
|
|
553
|
+
containerHeight,
|
|
554
|
+
overscan = 3,
|
|
555
|
+
gap = 0,
|
|
556
|
+
}: UseVirtualListOptions<T>) {
|
|
557
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
558
|
+
|
|
559
|
+
const effectiveItemHeight = itemHeight + gap;
|
|
560
|
+
const totalHeight = items.length * effectiveItemHeight - gap;
|
|
561
|
+
|
|
562
|
+
const startIndex = Math.max(0, Math.floor(scrollTop / effectiveItemHeight) - overscan);
|
|
563
|
+
const endIndex = Math.min(
|
|
564
|
+
items.length - 1,
|
|
565
|
+
Math.ceil((scrollTop + containerHeight) / effectiveItemHeight) + overscan
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
const visibleItems = useMemo(() => {
|
|
569
|
+
const result: Array<{ item: T; index: number; style: React.CSSProperties }> = [];
|
|
570
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
571
|
+
if (items[i]) {
|
|
572
|
+
result.push({
|
|
573
|
+
item: items[i],
|
|
574
|
+
index: i,
|
|
575
|
+
style: {
|
|
576
|
+
position: 'absolute',
|
|
577
|
+
top: i * effectiveItemHeight,
|
|
578
|
+
left: 0,
|
|
579
|
+
right: 0,
|
|
580
|
+
height: itemHeight,
|
|
581
|
+
},
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return result;
|
|
586
|
+
}, [items, startIndex, endIndex, effectiveItemHeight, itemHeight]);
|
|
587
|
+
|
|
588
|
+
const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
|
|
589
|
+
setScrollTop(event.currentTarget.scrollTop);
|
|
590
|
+
}, []);
|
|
591
|
+
|
|
592
|
+
const scrollToIndex = useCallback(
|
|
593
|
+
(index: number, align: 'start' | 'center' | 'end' = 'start') => {
|
|
594
|
+
const itemTop = index * effectiveItemHeight;
|
|
595
|
+
let targetScrollTop = itemTop;
|
|
596
|
+
|
|
597
|
+
if (align === 'center') {
|
|
598
|
+
targetScrollTop = itemTop - containerHeight / 2 + itemHeight / 2;
|
|
599
|
+
} else if (align === 'end') {
|
|
600
|
+
targetScrollTop = itemTop - containerHeight + itemHeight;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return Math.max(0, targetScrollTop);
|
|
604
|
+
},
|
|
605
|
+
[effectiveItemHeight, containerHeight, itemHeight]
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
totalHeight,
|
|
610
|
+
visibleItems,
|
|
611
|
+
handleScroll,
|
|
612
|
+
scrollToIndex,
|
|
613
|
+
scrollTop,
|
|
614
|
+
setScrollTop,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export default ArchbaseVirtualList;
|
package/src/display/index.ts
CHANGED
|
@@ -2,4 +2,97 @@
|
|
|
2
2
|
export { Text as ArchbaseText } from '@mantine/core';
|
|
3
3
|
export { Title as ArchbaseTitle } from '@mantine/core';
|
|
4
4
|
export { Image as ArchbaseMantineImage } from '@mantine/core';
|
|
5
|
-
export { Badge as ArchbaseBadge } from '@mantine/core';
|
|
5
|
+
export { Badge as ArchbaseBadge } from '@mantine/core';
|
|
6
|
+
export { ArchbaseQRCode } from './ArchbaseQRCode';
|
|
7
|
+
export type { ArchbaseQRCodeProps, ArchbaseQRCodeImageSettings } from './ArchbaseQRCode';
|
|
8
|
+
export { ArchbaseDiffViewer, DiffMethod } from './ArchbaseDiffViewer';
|
|
9
|
+
export type { ArchbaseDiffViewerProps } from './ArchbaseDiffViewer';
|
|
10
|
+
export { ArchbaseJsonTree } from './ArchbaseJsonTree';
|
|
11
|
+
export type { ArchbaseJsonTreeProps } from './ArchbaseJsonTree';
|
|
12
|
+
export { ArchbaseCodeViewer } from './ArchbaseCodeViewer';
|
|
13
|
+
export type { ArchbaseCodeViewerProps, ArchbaseCodeViewerTab } from './ArchbaseCodeViewer';
|
|
14
|
+
|
|
15
|
+
export { ArchbaseAnimationWrapper, ArchbaseAnimatedList, ArchbaseFadeInWhenVisible } from './ArchbaseAnimationWrapper';
|
|
16
|
+
export type {
|
|
17
|
+
ArchbaseAnimationWrapperProps,
|
|
18
|
+
ArchbaseAnimatedListProps,
|
|
19
|
+
ArchbaseFadeInWhenVisibleProps,
|
|
20
|
+
ArchbaseAnimationPreset,
|
|
21
|
+
} from './ArchbaseAnimationWrapper';
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
ArchbaseBarcodeGenerator,
|
|
25
|
+
ArchbaseEAN13Generator,
|
|
26
|
+
ArchbaseEAN8Generator,
|
|
27
|
+
ArchbaseUPCGenerator,
|
|
28
|
+
ArchbaseCODE128Generator,
|
|
29
|
+
} from './ArchbaseBarcodeGenerator';
|
|
30
|
+
export type {
|
|
31
|
+
ArchbaseBarcodeGeneratorProps,
|
|
32
|
+
BarcodeFormat,
|
|
33
|
+
} from './ArchbaseBarcodeGenerator';
|
|
34
|
+
|
|
35
|
+
export { ArchbaseTooltipRich, ArchbaseTooltipUser } from './ArchbaseTooltipRich';
|
|
36
|
+
export type { ArchbaseTooltipRichProps, ArchbaseTooltipUserProps } from './ArchbaseTooltipRich';
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
ArchbaseMarquee,
|
|
40
|
+
ArchbaseMarqueeLeft,
|
|
41
|
+
ArchbaseMarqueeRight,
|
|
42
|
+
ArchbaseMarqueeUp,
|
|
43
|
+
ArchbaseMarqueeDown,
|
|
44
|
+
ArchbaseTextTicker,
|
|
45
|
+
} from './ArchbaseMarquee';
|
|
46
|
+
export type { ArchbaseMarqueeProps, MarqueeDirection, ArchbaseTextTickerProps } from './ArchbaseMarquee';
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
ArchbaseCarousel,
|
|
50
|
+
ArchbaseCarouselSlide,
|
|
51
|
+
ArchbaseImageCarousel,
|
|
52
|
+
ArchbaseCardCarousel,
|
|
53
|
+
useArchbaseCarousel,
|
|
54
|
+
} from './ArchbaseCarousel';
|
|
55
|
+
export type {
|
|
56
|
+
ArchbaseCarouselProps,
|
|
57
|
+
ArchbaseCarouselSlideProps,
|
|
58
|
+
ArchbaseImageCarouselProps,
|
|
59
|
+
ImageSlide,
|
|
60
|
+
ArchbaseCardCarouselProps,
|
|
61
|
+
UseArchbaseCarouselOptions,
|
|
62
|
+
} from './ArchbaseCarousel';
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
ArchbaseVirtualList,
|
|
66
|
+
ArchbaseVirtualGrid,
|
|
67
|
+
ArchbaseInfiniteList,
|
|
68
|
+
useVirtualList,
|
|
69
|
+
} from './ArchbaseVirtualList';
|
|
70
|
+
export type {
|
|
71
|
+
ArchbaseVirtualListProps,
|
|
72
|
+
ArchbaseVirtualListRef,
|
|
73
|
+
VirtualListItem,
|
|
74
|
+
ArchbaseVirtualGridProps,
|
|
75
|
+
ArchbaseInfiniteListProps,
|
|
76
|
+
UseVirtualListOptions,
|
|
77
|
+
} from './ArchbaseVirtualList';
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
ArchbaseLightbox,
|
|
81
|
+
ArchbaseLightboxProvider,
|
|
82
|
+
useArchbaseLightbox,
|
|
83
|
+
useArchbaseLightboxContext,
|
|
84
|
+
} from './ArchbaseLightbox';
|
|
85
|
+
export type {
|
|
86
|
+
ArchbaseLightboxProps,
|
|
87
|
+
ArchbaseLightboxSlide,
|
|
88
|
+
ArchbaseLightboxProviderProps,
|
|
89
|
+
UseArchbaseLightboxReturn,
|
|
90
|
+
} from './ArchbaseLightbox';
|
|
91
|
+
|
|
92
|
+
export { ArchbasePhotoAlbum } from './ArchbasePhotoAlbum';
|
|
93
|
+
export type {
|
|
94
|
+
ArchbasePhotoAlbumProps,
|
|
95
|
+
ArchbasePhotoAlbumPhoto,
|
|
96
|
+
ArchbasePhotoAlbumLayout,
|
|
97
|
+
} from './ArchbasePhotoAlbum';
|
|
98
|
+
|