@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,570 @@
1
+ /**
2
+ * ArchbaseBarcodeScanner — scanner de codigos de barras e QR codes usando a API nativa BarcodeDetector
3
+ * com fallback para navegadores que nao suportam a API.
4
+ * Integrado ao DataSource (v1/v2) com suporte a modo inline e modal.
5
+ * @status stable
6
+ */
7
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
8
+ import type { CSSProperties, ReactNode } from 'react';
9
+ import { Box, Button, Group, Input, Modal, Stack, Text, useMantineTheme } from '@mantine/core';
10
+ import { IconScan, IconCamera, IconCameraOff } from '@tabler/icons-react';
11
+ import type { DataSourceEvent, IArchbaseDataSourceBase } from '@archbase/data';
12
+ import { DataSourceEventNames, useArchbaseDidUpdate, useArchbaseV1V2Compatibility } from '@archbase/data';
13
+ import { useValidationErrors } from '@archbase/core';
14
+ import { useForceUpdate } from '@mantine/hooks';
15
+
16
+ declare global {
17
+ interface Window {
18
+ BarcodeDetector: any;
19
+ }
20
+ }
21
+
22
+ export interface ArchbaseBarcodeScannerProps<T, ID> {
23
+ /** Fonte de dados onde sera atribuido o valor do scanner (V1 ou V2) */
24
+ dataSource?: IArchbaseDataSourceBase<T>;
25
+ /** Campo onde devera ser atribuido o valor do scanner na fonte de dados */
26
+ dataField?: string;
27
+ /** Valor controlado (uso standalone) */
28
+ value?: string;
29
+ /** Titulo do campo */
30
+ label?: string;
31
+ /** Descricao do campo */
32
+ description?: string;
33
+ /** Ultimo erro ocorrido */
34
+ error?: string;
35
+ /** Indicador se o preenchimento e obrigatorio */
36
+ required?: boolean;
37
+ /** Indicador se o scanner esta desabilitado */
38
+ disabled?: boolean;
39
+ /** Indicador se e somente leitura */
40
+ readOnly?: boolean;
41
+ /** Continuar escaneando apos primeira leitura (default false) */
42
+ continuous?: boolean;
43
+ /** Formatos de codigo aceitos */
44
+ formats?: string[];
45
+ /** Intervalo entre tentativas de deteccao em ms (default 500) */
46
+ scanInterval?: number;
47
+ /** Callback quando um codigo e escaneado */
48
+ onScan?: (code: string, format: string) => void;
49
+ /** Callback quando ocorre um erro */
50
+ onError?: (error: Error) => void;
51
+ /** Largura do video (default 320) */
52
+ width?: number;
53
+ /** Altura do video (default 240) */
54
+ height?: number;
55
+ /** Mostrar botao de lanterna (default false) */
56
+ showTorchButton?: boolean;
57
+ /** Renderizar como botao que abre modal (default false) */
58
+ asModal?: boolean;
59
+ /** Titulo do modal (default 'Escanear codigo') */
60
+ modalTitle?: string;
61
+ /** Label do botao (default 'Escanear') */
62
+ buttonLabel?: string;
63
+ /** Icone do botao */
64
+ buttonIcon?: ReactNode;
65
+ /** Overlay customizado sobre o video */
66
+ overlay?: ReactNode;
67
+ /** Estilo do componente */
68
+ style?: CSSProperties;
69
+ /** Classe CSS */
70
+ className?: string;
71
+ /** Evento quando o valor e alterado */
72
+ onChangeValue?: (value: string) => void;
73
+ }
74
+
75
+ interface DetectedBarcode {
76
+ rawValue: string;
77
+ format: string;
78
+ boundingBox: DOMRectReadOnly;
79
+ cornerPoints: { x: number; y: number }[];
80
+ }
81
+
82
+ const DEFAULT_FORMATS = ['qr_code', 'ean_13', 'ean_8', 'code_128', 'code_39'];
83
+
84
+ function ArchbaseBarcodeScannerInner<T, ID>(
85
+ props: ArchbaseBarcodeScannerProps<T, ID> & { onClose?: () => void },
86
+ ) {
87
+ const {
88
+ dataSource,
89
+ dataField,
90
+ value: propValue,
91
+ continuous = false,
92
+ formats = DEFAULT_FORMATS,
93
+ scanInterval = 500,
94
+ onScan,
95
+ onError,
96
+ width = 320,
97
+ height = 240,
98
+ showTorchButton = false,
99
+ overlay,
100
+ onChangeValue,
101
+ disabled = false,
102
+ readOnly = false,
103
+ onClose,
104
+ } = props;
105
+
106
+ const theme = useMantineTheme();
107
+
108
+ // V1/V2 compatibility
109
+ const v1v2 = useArchbaseV1V2Compatibility<string>(
110
+ 'ArchbaseBarcodeScanner',
111
+ dataSource,
112
+ dataField,
113
+ propValue ?? '',
114
+ );
115
+
116
+ const [isScanning, setIsScanning] = useState(false);
117
+ const [lastScannedCode, setLastScannedCode] = useState<string>('');
118
+ const [lastScannedFormat, setLastScannedFormat] = useState<string>('');
119
+ const [cameraError, setCameraError] = useState<string | null>(null);
120
+ const [torchEnabled, setTorchEnabled] = useState(false);
121
+ const [supportsBarcodeDetector, setSupportsBarcodeDetector] = useState(true);
122
+
123
+ const videoRef = useRef<HTMLVideoElement>(null);
124
+ const canvasRef = useRef<HTMLCanvasElement>(null);
125
+ const streamRef = useRef<MediaStream | null>(null);
126
+ const detectorRef = useRef<any>(null);
127
+ const animationFrameRef = useRef<number>(0);
128
+ const lastDetectionTimeRef = useRef<number>(0);
129
+
130
+ // DataSource listener setup
131
+ const dataSourceEventRef = useRef(v1v2.dataSourceEvent);
132
+ useEffect(() => {
133
+ dataSourceEventRef.current = v1v2.dataSourceEvent;
134
+ }, [v1v2.dataSourceEvent]);
135
+
136
+ const stableDataSourceEvent = useCallback((event: DataSourceEvent<T>) => {
137
+ dataSourceEventRef.current(event);
138
+ }, []);
139
+
140
+ useEffect(() => {
141
+ v1v2.loadDataSourceFieldValue();
142
+ if (dataSource && dataField) {
143
+ dataSource.addListener(stableDataSourceEvent);
144
+ const hasFieldListener = typeof (dataSource as any).addFieldChangeListener === 'function';
145
+ if (hasFieldListener) {
146
+ (dataSource as any).addFieldChangeListener(dataField, v1v2.loadDataSourceFieldValue);
147
+ }
148
+ return () => {
149
+ dataSource.removeListener(stableDataSourceEvent);
150
+ if (hasFieldListener) {
151
+ (dataSource as any).removeFieldChangeListener(dataField, v1v2.loadDataSourceFieldValue);
152
+ }
153
+ };
154
+ }
155
+ }, [dataSource, dataField, stableDataSourceEvent]);
156
+
157
+ useArchbaseDidUpdate(() => {
158
+ v1v2.loadDataSourceFieldValue();
159
+ }, []);
160
+
161
+ // Initialize BarcodeDetector
162
+ useEffect(() => {
163
+ if (typeof window !== 'undefined' && window.BarcodeDetector) {
164
+ try {
165
+ detectorRef.current = new window.BarcodeDetector({ formats });
166
+ } catch {
167
+ setSupportsBarcodeDetector(false);
168
+ }
169
+ } else {
170
+ setSupportsBarcodeDetector(false);
171
+ }
172
+ }, [formats]);
173
+
174
+ const handleCodeDetected = useCallback(
175
+ (code: string, format: string) => {
176
+ setLastScannedCode(code);
177
+ setLastScannedFormat(format);
178
+ v1v2.handleValueChange(code);
179
+
180
+ if (onChangeValue) {
181
+ onChangeValue(code);
182
+ }
183
+ if (onScan) {
184
+ onScan(code, format);
185
+ }
186
+
187
+ if (!continuous) {
188
+ stopCamera();
189
+ if (onClose) {
190
+ onClose();
191
+ }
192
+ }
193
+ },
194
+ [continuous, onScan, onChangeValue, onClose, v1v2.handleValueChange],
195
+ );
196
+
197
+ const detectBarcodes = useCallback(async () => {
198
+ if (!videoRef.current || !canvasRef.current || !detectorRef.current) return;
199
+
200
+ const now = Date.now();
201
+ if (now - lastDetectionTimeRef.current < scanInterval) {
202
+ animationFrameRef.current = requestAnimationFrame(detectBarcodes);
203
+ return;
204
+ }
205
+ lastDetectionTimeRef.current = now;
206
+
207
+ const video = videoRef.current;
208
+ const canvas = canvasRef.current;
209
+ const ctx = canvas.getContext('2d');
210
+ if (!ctx || video.readyState < video.HAVE_ENOUGH_DATA) {
211
+ animationFrameRef.current = requestAnimationFrame(detectBarcodes);
212
+ return;
213
+ }
214
+
215
+ canvas.width = video.videoWidth;
216
+ canvas.height = video.videoHeight;
217
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
218
+
219
+ try {
220
+ const barcodes: DetectedBarcode[] = await detectorRef.current.detect(canvas);
221
+ if (barcodes && barcodes.length > 0) {
222
+ const barcode = barcodes[0];
223
+ handleCodeDetected(barcode.rawValue, barcode.format);
224
+ if (!continuous) return;
225
+ }
226
+ } catch (err) {
227
+ // Silently continue on detection errors
228
+ }
229
+
230
+ animationFrameRef.current = requestAnimationFrame(detectBarcodes);
231
+ }, [scanInterval, continuous, handleCodeDetected]);
232
+
233
+ const startCamera = useCallback(async () => {
234
+ if (disabled || readOnly) return;
235
+
236
+ if (!supportsBarcodeDetector) {
237
+ const errorMsg = 'BarcodeDetector API nao e suportada neste navegador. Use Chrome 83+ ou Edge 83+.';
238
+ setCameraError(errorMsg);
239
+ if (onError) {
240
+ onError(new Error(errorMsg));
241
+ }
242
+ return;
243
+ }
244
+
245
+ setCameraError(null);
246
+
247
+ try {
248
+ const constraints: MediaStreamConstraints = {
249
+ video: {
250
+ facingMode: 'environment',
251
+ width: { ideal: width * 2 },
252
+ height: { ideal: height * 2 },
253
+ },
254
+ };
255
+
256
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
257
+ streamRef.current = stream;
258
+
259
+ if (videoRef.current) {
260
+ videoRef.current.srcObject = stream;
261
+ await videoRef.current.play();
262
+ setIsScanning(true);
263
+ animationFrameRef.current = requestAnimationFrame(detectBarcodes);
264
+ }
265
+ } catch (err: any) {
266
+ let errorMsg = 'Erro ao acessar a camera.';
267
+ if (err.name === 'NotAllowedError') {
268
+ errorMsg = 'Permissao para acessar a camera foi negada.';
269
+ } else if (err.name === 'NotFoundError') {
270
+ errorMsg = 'Nenhuma camera encontrada no dispositivo.';
271
+ } else if (err.name === 'NotReadableError') {
272
+ errorMsg = 'Camera esta em uso por outro aplicativo.';
273
+ }
274
+ setCameraError(errorMsg);
275
+ if (onError) {
276
+ onError(new Error(errorMsg));
277
+ }
278
+ }
279
+ }, [disabled, readOnly, width, height, supportsBarcodeDetector, detectBarcodes, onError]);
280
+
281
+ const stopCamera = useCallback(() => {
282
+ if (animationFrameRef.current) {
283
+ cancelAnimationFrame(animationFrameRef.current);
284
+ animationFrameRef.current = 0;
285
+ }
286
+ if (streamRef.current) {
287
+ streamRef.current.getTracks().forEach((track) => track.stop());
288
+ streamRef.current = null;
289
+ }
290
+ if (videoRef.current) {
291
+ videoRef.current.srcObject = null;
292
+ }
293
+ setIsScanning(false);
294
+ setTorchEnabled(false);
295
+ }, []);
296
+
297
+ const toggleTorch = useCallback(async () => {
298
+ if (!streamRef.current) return;
299
+ const track = streamRef.current.getVideoTracks()[0];
300
+ if (!track) return;
301
+
302
+ try {
303
+ const capabilities = track.getCapabilities() as any;
304
+ if (capabilities.torch) {
305
+ const newTorchState = !torchEnabled;
306
+ await track.applyConstraints({
307
+ advanced: [{ torch: newTorchState } as any],
308
+ });
309
+ setTorchEnabled(newTorchState);
310
+ }
311
+ } catch {
312
+ // torch not supported on this device
313
+ }
314
+ }, [torchEnabled]);
315
+
316
+ // Cleanup on unmount
317
+ useEffect(() => {
318
+ return () => {
319
+ stopCamera();
320
+ };
321
+ }, []);
322
+
323
+ const isReadOnly = () => {
324
+ let tmpReadOnly = readOnly;
325
+ if (dataSource && !readOnly) {
326
+ tmpReadOnly = dataSource.isBrowsing();
327
+ }
328
+ return tmpReadOnly;
329
+ };
330
+
331
+ return (
332
+ <Stack gap="xs">
333
+ <Box
334
+ style={{
335
+ position: 'relative',
336
+ width,
337
+ height,
338
+ backgroundColor: theme.colors.dark[7],
339
+ borderRadius: theme.radius.sm,
340
+ overflow: 'hidden',
341
+ }}
342
+ >
343
+ <video
344
+ ref={videoRef}
345
+ style={{
346
+ width: '100%',
347
+ height: '100%',
348
+ objectFit: 'cover',
349
+ display: isScanning ? 'block' : 'none',
350
+ }}
351
+ playsInline
352
+ muted
353
+ />
354
+
355
+ {!isScanning && (
356
+ <Stack
357
+ align="center"
358
+ justify="center"
359
+ style={{
360
+ width: '100%',
361
+ height: '100%',
362
+ position: 'absolute',
363
+ top: 0,
364
+ left: 0,
365
+ }}
366
+ >
367
+ {cameraError ? (
368
+ <>
369
+ <IconCameraOff size={48} color={theme.colors.red[5]} />
370
+ <Text size="xs" c="red" ta="center" px="sm">
371
+ {cameraError}
372
+ </Text>
373
+ </>
374
+ ) : (
375
+ <>
376
+ <IconCamera size={48} color={theme.colors.gray[5]} />
377
+ <Text size="xs" c="dimmed" ta="center" px="sm">
378
+ Clique em iniciar para ativar a camera
379
+ </Text>
380
+ </>
381
+ )}
382
+ </Stack>
383
+ )}
384
+
385
+ {/* Scan overlay */}
386
+ {isScanning && !overlay && (
387
+ <Box
388
+ style={{
389
+ position: 'absolute',
390
+ top: '50%',
391
+ left: '50%',
392
+ transform: 'translate(-50%, -50%)',
393
+ width: '60%',
394
+ height: '60%',
395
+ border: `2px solid ${theme.colors[theme.primaryColor][5]}`,
396
+ borderRadius: theme.radius.sm,
397
+ pointerEvents: 'none',
398
+ opacity: 0.7,
399
+ }}
400
+ />
401
+ )}
402
+
403
+ {/* Custom overlay */}
404
+ {isScanning && overlay && (
405
+ <Box
406
+ style={{
407
+ position: 'absolute',
408
+ top: 0,
409
+ left: 0,
410
+ width: '100%',
411
+ height: '100%',
412
+ pointerEvents: 'none',
413
+ }}
414
+ >
415
+ {overlay}
416
+ </Box>
417
+ )}
418
+
419
+ {/* Hidden canvas for frame capture */}
420
+ <canvas ref={canvasRef} style={{ display: 'none' }} />
421
+ </Box>
422
+
423
+ {/* Controls */}
424
+ <Group gap="xs">
425
+ {!isScanning ? (
426
+ <Button
427
+ size="xs"
428
+ leftSection={<IconScan size={16} />}
429
+ onClick={startCamera}
430
+ disabled={disabled || isReadOnly()}
431
+ >
432
+ Iniciar scanner
433
+ </Button>
434
+ ) : (
435
+ <Button
436
+ size="xs"
437
+ variant="outline"
438
+ color="red"
439
+ leftSection={<IconCameraOff size={16} />}
440
+ onClick={stopCamera}
441
+ >
442
+ Parar scanner
443
+ </Button>
444
+ )}
445
+ {showTorchButton && isScanning && (
446
+ <Button size="xs" variant="light" onClick={toggleTorch}>
447
+ {torchEnabled ? 'Desligar lanterna' : 'Ligar lanterna'}
448
+ </Button>
449
+ )}
450
+ </Group>
451
+
452
+ {/* Last scanned code */}
453
+ {lastScannedCode && (
454
+ <Box
455
+ style={{
456
+ padding: theme.spacing.xs,
457
+ backgroundColor:
458
+ theme.colors[theme.primaryColor][0],
459
+ borderRadius: theme.radius.sm,
460
+ border: `1px solid ${theme.colors[theme.primaryColor][3]}`,
461
+ }}
462
+ >
463
+ <Text size="xs" c="dimmed">
464
+ Ultimo codigo ({lastScannedFormat}):
465
+ </Text>
466
+ <Text size="sm" fw={600}>
467
+ {lastScannedCode}
468
+ </Text>
469
+ </Box>
470
+ )}
471
+ </Stack>
472
+ );
473
+ }
474
+
475
+ export function ArchbaseBarcodeScanner<T, ID>(props: ArchbaseBarcodeScannerProps<T, ID>) {
476
+ const {
477
+ asModal = false,
478
+ modalTitle = 'Escanear codigo',
479
+ buttonLabel = 'Escanear',
480
+ buttonIcon,
481
+ label,
482
+ description,
483
+ error,
484
+ required,
485
+ style,
486
+ className,
487
+ disabled = false,
488
+ readOnly = false,
489
+ dataSource,
490
+ } = props;
491
+
492
+ const [modalOpened, setModalOpened] = useState(false);
493
+
494
+ // Validation context
495
+ const validationContext = useValidationErrors();
496
+ const fieldKey = `${props.dataField}`;
497
+ const contextError = validationContext?.getError(fieldKey);
498
+
499
+ const [internalError, setInternalError] = useState<string | undefined>(error);
500
+
501
+ useEffect(() => {
502
+ if (error !== undefined && error !== internalError) {
503
+ setInternalError(error);
504
+ }
505
+ }, [error]);
506
+
507
+ const displayError = internalError || contextError;
508
+
509
+ const isReadOnly = () => {
510
+ let tmpReadOnly = readOnly;
511
+ if (dataSource && !readOnly) {
512
+ tmpReadOnly = dataSource.isBrowsing();
513
+ }
514
+ return tmpReadOnly;
515
+ };
516
+
517
+ if (asModal) {
518
+ return (
519
+ <Box style={style} className={className}>
520
+ {label && (
521
+ <Input.Label required={required}>{label}</Input.Label>
522
+ )}
523
+ {description && (
524
+ <Input.Description>{description}</Input.Description>
525
+ )}
526
+ <Button
527
+ leftSection={buttonIcon ?? <IconScan size={16} />}
528
+ onClick={() => setModalOpened(true)}
529
+ disabled={disabled || isReadOnly()}
530
+ variant="light"
531
+ mt={4}
532
+ >
533
+ {buttonLabel}
534
+ </Button>
535
+ {displayError && (
536
+ <Input.Error mt={4}>{displayError}</Input.Error>
537
+ )}
538
+ <Modal
539
+ opened={modalOpened}
540
+ onClose={() => setModalOpened(false)}
541
+ title={modalTitle}
542
+ centered
543
+ size="auto"
544
+ >
545
+ <ArchbaseBarcodeScannerInner<T, ID>
546
+ {...props}
547
+ onClose={() => setModalOpened(false)}
548
+ />
549
+ </Modal>
550
+ </Box>
551
+ );
552
+ }
553
+
554
+ return (
555
+ <Box style={style} className={className}>
556
+ {label && (
557
+ <Input.Label required={required}>{label}</Input.Label>
558
+ )}
559
+ {description && (
560
+ <Input.Description>{description}</Input.Description>
561
+ )}
562
+ <ArchbaseBarcodeScannerInner<T, ID> {...props} />
563
+ {displayError && (
564
+ <Input.Error mt={4}>{displayError}</Input.Error>
565
+ )}
566
+ </Box>
567
+ );
568
+ }
569
+
570
+ ArchbaseBarcodeScanner.displayName = 'ArchbaseBarcodeScanner';