@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,671 @@
1
+ import {
2
+ Box,
3
+ Button,
4
+ Group,
5
+ Input,
6
+ MantineSize,
7
+ ScrollArea,
8
+ Stack,
9
+ Text,
10
+ TextInput,
11
+ UnstyledButton,
12
+ useMantineTheme,
13
+ useComputedColorScheme,
14
+ } from '@mantine/core';
15
+ import {
16
+ IconChevronRight,
17
+ IconChevronLeft,
18
+ IconChevronsRight,
19
+ IconChevronsLeft,
20
+ IconSearch,
21
+ } from '@tabler/icons-react';
22
+ import React, {
23
+ CSSProperties,
24
+ useCallback,
25
+ useEffect,
26
+ useMemo,
27
+ useRef,
28
+ useState,
29
+ } from 'react';
30
+ import {
31
+ DataSourceEvent,
32
+ DataSourceEventNames,
33
+ IArchbaseDataSourceBase,
34
+ useArchbaseDidUpdate,
35
+ useArchbaseV1V2Compatibility,
36
+ } from '@archbase/data';
37
+ import { useForceUpdate } from '@mantine/hooks';
38
+ import { useValidationErrors } from '@archbase/core';
39
+
40
+ export interface ArchbaseDualListboxOption {
41
+ value: any;
42
+ label: string;
43
+ group?: string;
44
+ disabled?: boolean;
45
+ }
46
+
47
+ export interface ArchbaseDualListboxProps<T, ID, O> {
48
+ /** Fonte de dados onde sera atribuido o valor (V1 ou V2) */
49
+ dataSource?: IArchbaseDataSourceBase<T>;
50
+ /** Campo onde devera ser atribuido o valor na fonte de dados */
51
+ dataField?: string;
52
+ /** Valor de entrada controlado */
53
+ value?: any[];
54
+ /** Valor padrao de entrada nao controlado */
55
+ defaultValue?: any[];
56
+ /** Opcoes disponiveis para selecao */
57
+ options: ArchbaseDualListboxOption[];
58
+ /** Titulo do componente */
59
+ label?: string;
60
+ /** Descricao do componente */
61
+ description?: string;
62
+ /** Ultimo erro ocorrido */
63
+ error?: string;
64
+ /** Indicador se o campo e obrigatorio */
65
+ required?: boolean;
66
+ /** Indicador se o componente esta desabilitado */
67
+ disabled?: boolean;
68
+ /** Indicador se o componente e somente leitura */
69
+ readOnly?: boolean;
70
+ /** Titulo da lista de itens disponiveis */
71
+ availableLabel?: string;
72
+ /** Titulo da lista de itens selecionados */
73
+ selectedLabel?: string;
74
+ /** Exibir campo de busca em cada lista */
75
+ showSearch?: boolean;
76
+ /** Exibir botoes para mover todos os itens */
77
+ showMoveAll?: boolean;
78
+ /** Altura das listas */
79
+ height?: number;
80
+ /** Evento quando o valor muda */
81
+ onChangeValue?: (value: any[]) => void;
82
+ /** Evento quando o componente recebe o foco */
83
+ onFocusEnter?: (event: React.FocusEvent) => void;
84
+ /** Evento quando o foco sai do componente */
85
+ onFocusExit?: (event: React.FocusEvent) => void;
86
+ /** Estilo do componente */
87
+ style?: CSSProperties;
88
+ /** Classe CSS do componente */
89
+ className?: string;
90
+ /** Largura do componente */
91
+ width?: string | number;
92
+ /** Tamanho do componente */
93
+ size?: MantineSize;
94
+ }
95
+
96
+ export function ArchbaseDualListbox<T, ID, O>({
97
+ dataSource,
98
+ dataField,
99
+ value,
100
+ defaultValue,
101
+ options,
102
+ label,
103
+ description,
104
+ error,
105
+ required,
106
+ disabled = false,
107
+ readOnly = false,
108
+ availableLabel = 'Disponiveis',
109
+ selectedLabel = 'Selecionados',
110
+ showSearch = true,
111
+ showMoveAll = true,
112
+ height = 300,
113
+ onChangeValue,
114
+ onFocusEnter,
115
+ onFocusExit,
116
+ style,
117
+ className,
118
+ width,
119
+ size,
120
+ }: ArchbaseDualListboxProps<T, ID, O>) {
121
+ const theme = useMantineTheme();
122
+ const colorScheme = useComputedColorScheme('light');
123
+ const isDark = colorScheme === 'dark';
124
+ const forceUpdate = useForceUpdate();
125
+
126
+ // Compatibilidade V1/V2
127
+ const v1v2Compatibility = useArchbaseV1V2Compatibility<any[]>(
128
+ 'ArchbaseDualListbox',
129
+ dataSource,
130
+ dataField,
131
+ [],
132
+ );
133
+
134
+ // Contexto de validacao
135
+ const validationContext = useValidationErrors();
136
+ const fieldKey = `${dataField}`;
137
+ const contextError = validationContext?.getError(fieldKey);
138
+
139
+ // Estado interno
140
+ const [currentValue, setCurrentValue] = useState<any[]>(
141
+ value ?? defaultValue ?? [],
142
+ );
143
+ const [selectedInAvailable, setSelectedInAvailable] = useState<Set<any>>(
144
+ new Set(),
145
+ );
146
+ const [selectedInChosen, setSelectedInChosen] = useState<Set<any>>(
147
+ new Set(),
148
+ );
149
+ const [searchAvailable, setSearchAvailable] = useState('');
150
+ const [searchChosen, setSearchChosen] = useState('');
151
+ const [internalError, setInternalError] = useState<string | undefined>(error);
152
+
153
+ // Sincronizar prop error
154
+ useEffect(() => {
155
+ if (error !== undefined && error !== internalError) {
156
+ setInternalError(error);
157
+ }
158
+ }, [error]);
159
+
160
+ // Sincronizar prop value
161
+ useEffect(() => {
162
+ if (value !== undefined) {
163
+ setCurrentValue(value);
164
+ }
165
+ }, [value]);
166
+
167
+ // Carregar valor do datasource
168
+ const loadDataSourceFieldValue = useCallback(() => {
169
+ let initialValue: any[] = value ?? defaultValue ?? [];
170
+ if (dataSource && dataField && !dataSource.isEmpty()) {
171
+ const fieldValue = dataSource.getFieldValue(dataField);
172
+ if (Array.isArray(fieldValue)) {
173
+ initialValue = fieldValue;
174
+ } else if (fieldValue !== undefined && fieldValue !== null) {
175
+ initialValue = [fieldValue];
176
+ } else {
177
+ initialValue = [];
178
+ }
179
+ }
180
+ setCurrentValue(initialValue);
181
+ }, [dataSource, dataField, value, defaultValue]);
182
+
183
+ const fieldChangedListener = useCallback(() => {
184
+ loadDataSourceFieldValue();
185
+ }, [loadDataSourceFieldValue]);
186
+
187
+ const dataSourceEvent = useCallback(
188
+ (event: DataSourceEvent<T>) => {
189
+ if (dataSource && dataField) {
190
+ if (
191
+ event.type === DataSourceEventNames.dataChanged ||
192
+ event.type === DataSourceEventNames.recordChanged ||
193
+ event.type === DataSourceEventNames.afterScroll ||
194
+ event.type === DataSourceEventNames.afterCancel ||
195
+ event.type === DataSourceEventNames.afterEdit
196
+ ) {
197
+ loadDataSourceFieldValue();
198
+ if (!v1v2Compatibility.isDataSourceV2) {
199
+ forceUpdate();
200
+ }
201
+ }
202
+ if (
203
+ event.type === DataSourceEventNames.onFieldError &&
204
+ event.fieldName === dataField
205
+ ) {
206
+ setInternalError(event.error);
207
+ validationContext?.setError(fieldKey, event.error);
208
+ }
209
+ }
210
+ },
211
+ [v1v2Compatibility.isDataSourceV2, validationContext, fieldKey, loadDataSourceFieldValue],
212
+ );
213
+
214
+ // Ref para manter callback atualizado
215
+ const dataSourceEventRef = useRef(dataSourceEvent);
216
+ useEffect(() => {
217
+ dataSourceEventRef.current = dataSourceEvent;
218
+ }, [dataSourceEvent]);
219
+
220
+ const stableDataSourceEvent = useCallback((event: DataSourceEvent<T>) => {
221
+ dataSourceEventRef.current(event);
222
+ }, []);
223
+
224
+ // Registrar listeners
225
+ useEffect(() => {
226
+ loadDataSourceFieldValue();
227
+ if (dataSource && dataField) {
228
+ const hasFieldListener =
229
+ typeof (dataSource as any).addFieldChangeListener === 'function';
230
+ dataSource.addListener(stableDataSourceEvent);
231
+ if (hasFieldListener) {
232
+ (dataSource as any).addFieldChangeListener(
233
+ dataField,
234
+ fieldChangedListener,
235
+ );
236
+ }
237
+ return () => {
238
+ dataSource.removeListener(stableDataSourceEvent);
239
+ if (hasFieldListener) {
240
+ (dataSource as any).removeFieldChangeListener(
241
+ dataField,
242
+ fieldChangedListener,
243
+ );
244
+ }
245
+ };
246
+ }
247
+ }, [dataSource, dataField, stableDataSourceEvent, fieldChangedListener]);
248
+
249
+ useArchbaseDidUpdate(() => {
250
+ loadDataSourceFieldValue();
251
+ }, []);
252
+
253
+ const isReadOnly = readOnly || v1v2Compatibility.isReadOnly;
254
+ const isDisabled = disabled;
255
+
256
+ // Listas computadas
257
+ const availableItems = useMemo(() => {
258
+ const valueSet = new Set(currentValue);
259
+ return options.filter((opt) => {
260
+ if (valueSet.has(opt.value)) return false;
261
+ if (searchAvailable) {
262
+ return opt.label
263
+ .toLowerCase()
264
+ .includes(searchAvailable.toLowerCase());
265
+ }
266
+ return true;
267
+ });
268
+ }, [options, currentValue, searchAvailable]);
269
+
270
+ const chosenItems = useMemo(() => {
271
+ const valueSet = new Set(currentValue);
272
+ const matched = options.filter((opt) => {
273
+ if (!valueSet.has(opt.value)) return false;
274
+ if (searchChosen) {
275
+ return opt.label
276
+ .toLowerCase()
277
+ .includes(searchChosen.toLowerCase());
278
+ }
279
+ return true;
280
+ });
281
+ return matched;
282
+ }, [options, currentValue, searchChosen]);
283
+
284
+ // Propagar mudanca de valor
285
+ const propagateChange = useCallback(
286
+ (newValue: any[]) => {
287
+ // Limpar erro
288
+ if (internalError || contextError) {
289
+ setInternalError(undefined);
290
+ validationContext?.clearError(fieldKey);
291
+ }
292
+
293
+ setCurrentValue(newValue);
294
+
295
+ if (dataSource && dataField && !dataSource.isBrowsing()) {
296
+ v1v2Compatibility.handleValueChange(newValue);
297
+ }
298
+
299
+ if (onChangeValue) {
300
+ onChangeValue(newValue);
301
+ }
302
+ },
303
+ [
304
+ dataSource,
305
+ dataField,
306
+ v1v2Compatibility,
307
+ onChangeValue,
308
+ internalError,
309
+ contextError,
310
+ validationContext,
311
+ fieldKey,
312
+ ],
313
+ );
314
+
315
+ // Acoes de mover
316
+ const moveRight = useCallback(() => {
317
+ if (isReadOnly || isDisabled || selectedInAvailable.size === 0) return;
318
+ const toMove = Array.from(selectedInAvailable).filter((v) => {
319
+ const opt = options.find((o) => o.value === v);
320
+ return opt && !opt.disabled;
321
+ });
322
+ const newValue = [...currentValue, ...toMove];
323
+ setSelectedInAvailable(new Set());
324
+ propagateChange(newValue);
325
+ }, [selectedInAvailable, currentValue, options, isReadOnly, isDisabled, propagateChange]);
326
+
327
+ const moveAllRight = useCallback(() => {
328
+ if (isReadOnly || isDisabled) return;
329
+ const allAvailableValues = availableItems
330
+ .filter((opt) => !opt.disabled)
331
+ .map((opt) => opt.value);
332
+ const newValue = [...currentValue, ...allAvailableValues];
333
+ setSelectedInAvailable(new Set());
334
+ propagateChange(newValue);
335
+ }, [availableItems, currentValue, isReadOnly, isDisabled, propagateChange]);
336
+
337
+ const moveLeft = useCallback(() => {
338
+ if (isReadOnly || isDisabled || selectedInChosen.size === 0) return;
339
+ const toRemove = new Set(
340
+ Array.from(selectedInChosen).filter((v) => {
341
+ const opt = options.find((o) => o.value === v);
342
+ return opt && !opt.disabled;
343
+ }),
344
+ );
345
+ const newValue = currentValue.filter((v) => !toRemove.has(v));
346
+ setSelectedInChosen(new Set());
347
+ propagateChange(newValue);
348
+ }, [selectedInChosen, currentValue, options, isReadOnly, isDisabled, propagateChange]);
349
+
350
+ const moveAllLeft = useCallback(() => {
351
+ if (isReadOnly || isDisabled) return;
352
+ const chosenDisabledValues = new Set(
353
+ chosenItems.filter((opt) => opt.disabled).map((opt) => opt.value),
354
+ );
355
+ const newValue = currentValue.filter((v) => chosenDisabledValues.has(v));
356
+ setSelectedInChosen(new Set());
357
+ propagateChange(newValue);
358
+ }, [chosenItems, currentValue, isReadOnly, isDisabled, propagateChange]);
359
+
360
+ // Toggle selecao em lista
361
+ const toggleAvailableItem = useCallback(
362
+ (itemValue: any) => {
363
+ if (isReadOnly || isDisabled) return;
364
+ setSelectedInAvailable((prev) => {
365
+ const next = new Set(prev);
366
+ if (next.has(itemValue)) {
367
+ next.delete(itemValue);
368
+ } else {
369
+ next.add(itemValue);
370
+ }
371
+ return next;
372
+ });
373
+ },
374
+ [isReadOnly, isDisabled],
375
+ );
376
+
377
+ const toggleChosenItem = useCallback(
378
+ (itemValue: any) => {
379
+ if (isReadOnly || isDisabled) return;
380
+ setSelectedInChosen((prev) => {
381
+ const next = new Set(prev);
382
+ if (next.has(itemValue)) {
383
+ next.delete(itemValue);
384
+ } else {
385
+ next.add(itemValue);
386
+ }
387
+ return next;
388
+ });
389
+ },
390
+ [isReadOnly, isDisabled],
391
+ );
392
+
393
+ // Estilos
394
+ const borderColor = isDark ? theme.colors.dark[4] : theme.colors.gray[4];
395
+ const bgColor = isDark ? theme.colors.dark[6] : theme.white;
396
+ const headerBg = isDark ? theme.colors.dark[5] : theme.colors.gray[1];
397
+ const hoverBg = isDark ? theme.colors.dark[4] : theme.colors.gray[1];
398
+ const selectedBg = isDark
399
+ ? theme.colors[theme.primaryColor][9]
400
+ : theme.colors[theme.primaryColor][1];
401
+ const selectedColor = isDark
402
+ ? theme.white
403
+ : theme.colors[theme.primaryColor][9];
404
+ const disabledColor = isDark ? theme.colors.dark[3] : theme.colors.gray[5];
405
+
406
+ const displayError = internalError || contextError;
407
+
408
+ const renderListItem = (
409
+ item: ArchbaseDualListboxOption,
410
+ isSelected: boolean,
411
+ onToggle: (value: any) => void,
412
+ ) => {
413
+ const itemDisabled = item.disabled || isDisabled || isReadOnly;
414
+ return (
415
+ <UnstyledButton
416
+ key={String(item.value)}
417
+ onClick={() => !itemDisabled && onToggle(item.value)}
418
+ style={{
419
+ display: 'block',
420
+ width: '100%',
421
+ padding: '6px 10px',
422
+ borderRadius: theme.radius.sm as any,
423
+ backgroundColor: isSelected ? selectedBg : 'transparent',
424
+ color: itemDisabled
425
+ ? disabledColor
426
+ : isSelected
427
+ ? selectedColor
428
+ : undefined,
429
+ cursor: itemDisabled ? 'not-allowed' : 'pointer',
430
+ opacity: itemDisabled ? 0.6 : 1,
431
+ transition: 'background-color 150ms ease',
432
+ }}
433
+ onMouseEnter={(e) => {
434
+ if (!isSelected && !itemDisabled) {
435
+ (e.currentTarget as HTMLElement).style.backgroundColor = hoverBg;
436
+ }
437
+ }}
438
+ onMouseLeave={(e) => {
439
+ if (!isSelected) {
440
+ (e.currentTarget as HTMLElement).style.backgroundColor =
441
+ 'transparent';
442
+ }
443
+ }}
444
+ >
445
+ <Text size={size ?? 'sm'} truncate>
446
+ {item.label}
447
+ </Text>
448
+ </UnstyledButton>
449
+ );
450
+ };
451
+
452
+ const renderList = (
453
+ items: ArchbaseDualListboxOption[],
454
+ selectedSet: Set<any>,
455
+ onToggle: (value: any) => void,
456
+ listLabel: string,
457
+ searchValue: string,
458
+ onSearchChange: (value: string) => void,
459
+ ) => {
460
+ // Agrupar itens
461
+ const grouped = new Map<string, ArchbaseDualListboxOption[]>();
462
+ const ungrouped: ArchbaseDualListboxOption[] = [];
463
+ for (const item of items) {
464
+ if (item.group) {
465
+ if (!grouped.has(item.group)) {
466
+ grouped.set(item.group, []);
467
+ }
468
+ grouped.get(item.group)!.push(item);
469
+ } else {
470
+ ungrouped.push(item);
471
+ }
472
+ }
473
+
474
+ return (
475
+ <Box
476
+ style={{
477
+ flex: 1,
478
+ border: `1px solid ${borderColor}`,
479
+ borderRadius: theme.radius.sm as any,
480
+ backgroundColor: bgColor,
481
+ overflow: 'hidden',
482
+ display: 'flex',
483
+ flexDirection: 'column',
484
+ }}
485
+ >
486
+ {/* Header */}
487
+ <Box
488
+ style={{
489
+ padding: '8px 10px',
490
+ backgroundColor: headerBg,
491
+ borderBottom: `1px solid ${borderColor}`,
492
+ }}
493
+ >
494
+ <Text fw={600} size={size ?? 'sm'}>
495
+ {listLabel}
496
+ </Text>
497
+ <Text size="xs" c="dimmed">
498
+ {items.length} {items.length === 1 ? 'item' : 'itens'}
499
+ </Text>
500
+ </Box>
501
+
502
+ {/* Busca */}
503
+ {showSearch && (
504
+ <Box style={{ padding: '6px 8px', borderBottom: `1px solid ${borderColor}` }}>
505
+ <TextInput
506
+ size="xs"
507
+ placeholder="Buscar..."
508
+ leftSection={<IconSearch size={14} />}
509
+ value={searchValue}
510
+ onChange={(e) => onSearchChange(e.currentTarget.value)}
511
+ disabled={isDisabled}
512
+ readOnly={isReadOnly}
513
+ />
514
+ </Box>
515
+ )}
516
+
517
+ {/* Itens */}
518
+ <ScrollArea style={{ flex: 1 }} h={height} offsetScrollbars>
519
+ <Box style={{ padding: '4px' }}>
520
+ {items.length === 0 && (
521
+ <Text
522
+ size="sm"
523
+ c="dimmed"
524
+ ta="center"
525
+ style={{ padding: '20px 10px' }}
526
+ >
527
+ Nenhum item
528
+ </Text>
529
+ )}
530
+ {ungrouped.map((item) =>
531
+ renderListItem(item, selectedSet.has(item.value), onToggle),
532
+ )}
533
+ {Array.from(grouped.entries()).map(([groupName, groupItems]) => (
534
+ <Box key={groupName} style={{ marginTop: ungrouped.length > 0 ? 8 : 0 }}>
535
+ <Text
536
+ size="xs"
537
+ fw={700}
538
+ tt="uppercase"
539
+ c="dimmed"
540
+ style={{ padding: '4px 10px 2px' }}
541
+ >
542
+ {groupName}
543
+ </Text>
544
+ {groupItems.map((item) =>
545
+ renderListItem(
546
+ item,
547
+ selectedSet.has(item.value),
548
+ onToggle,
549
+ ),
550
+ )}
551
+ </Box>
552
+ ))}
553
+ </Box>
554
+ </ScrollArea>
555
+ </Box>
556
+ );
557
+ };
558
+
559
+ const buttonSize = size === 'xs' ? 'xs' : size === 'sm' ? 'xs' : 'sm';
560
+ const iconSize = size === 'xs' ? 14 : size === 'sm' ? 16 : 18;
561
+
562
+ return (
563
+ <Input.Wrapper
564
+ label={label}
565
+ description={description}
566
+ error={displayError}
567
+ required={required}
568
+ className={className}
569
+ style={{ width, ...style }}
570
+ >
571
+ <Group
572
+ align="stretch"
573
+ gap="xs"
574
+ wrap="nowrap"
575
+ style={{ marginTop: label || description ? 4 : 0 }}
576
+ onFocus={onFocusEnter}
577
+ onBlur={onFocusExit}
578
+ >
579
+ {/* Lista de disponiveis */}
580
+ {renderList(
581
+ availableItems,
582
+ selectedInAvailable,
583
+ toggleAvailableItem,
584
+ availableLabel,
585
+ searchAvailable,
586
+ setSearchAvailable,
587
+ )}
588
+
589
+ {/* Botoes de acao */}
590
+ <Stack
591
+ align="center"
592
+ justify="center"
593
+ gap={6}
594
+ style={{ minWidth: 40 }}
595
+ >
596
+ <Button
597
+ variant="light"
598
+ size={buttonSize}
599
+ onClick={moveRight}
600
+ disabled={
601
+ isDisabled ||
602
+ isReadOnly ||
603
+ selectedInAvailable.size === 0
604
+ }
605
+ title="Mover selecionados para a direita"
606
+ px={6}
607
+ >
608
+ <IconChevronRight size={iconSize} />
609
+ </Button>
610
+
611
+ {showMoveAll && (
612
+ <Button
613
+ variant="light"
614
+ size={buttonSize}
615
+ onClick={moveAllRight}
616
+ disabled={
617
+ isDisabled || isReadOnly || availableItems.length === 0
618
+ }
619
+ title="Mover todos para a direita"
620
+ px={6}
621
+ >
622
+ <IconChevronsRight size={iconSize} />
623
+ </Button>
624
+ )}
625
+
626
+ {showMoveAll && (
627
+ <Button
628
+ variant="light"
629
+ size={buttonSize}
630
+ onClick={moveAllLeft}
631
+ disabled={
632
+ isDisabled || isReadOnly || chosenItems.length === 0
633
+ }
634
+ title="Mover todos para a esquerda"
635
+ px={6}
636
+ >
637
+ <IconChevronsLeft size={iconSize} />
638
+ </Button>
639
+ )}
640
+
641
+ <Button
642
+ variant="light"
643
+ size={buttonSize}
644
+ onClick={moveLeft}
645
+ disabled={
646
+ isDisabled ||
647
+ isReadOnly ||
648
+ selectedInChosen.size === 0
649
+ }
650
+ title="Mover selecionados para a esquerda"
651
+ px={6}
652
+ >
653
+ <IconChevronLeft size={iconSize} />
654
+ </Button>
655
+ </Stack>
656
+
657
+ {/* Lista de selecionados */}
658
+ {renderList(
659
+ chosenItems,
660
+ selectedInChosen,
661
+ toggleChosenItem,
662
+ selectedLabel,
663
+ searchChosen,
664
+ setSearchChosen,
665
+ )}
666
+ </Group>
667
+ </Input.Wrapper>
668
+ );
669
+ }
670
+
671
+ ArchbaseDualListbox.displayName = 'ArchbaseDualListbox';