@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,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';
|