@archbase/components 4.0.1 → 4.0.2

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.
@@ -0,0 +1,395 @@
1
+ import {
2
+ ActionIcon,
3
+ ActionIconVariant,
4
+ MantineSize,
5
+ TagsInput,
6
+ Tooltip,
7
+ useMantineColorScheme,
8
+ useMantineTheme,
9
+ } from '@mantine/core';
10
+ import { useForceUpdate } from '@mantine/hooks';
11
+ import type { CSSProperties, FocusEventHandler, ReactNode } from 'react';
12
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
13
+ import type { ArchbaseDataSource, DataSourceEvent } from '@archbase/data';
14
+ import { DataSourceEventNames } from '@archbase/data';
15
+ import { useArchbaseDidMount, useArchbaseDidUpdate, useArchbaseWillUnmount } from '@archbase/core';
16
+ import type { ComboboxData, ComboboxLikeRenderOptionInput, ComboboxGenericItem } from '@mantine/core';
17
+ import type { InputClearButtonProps, ScrollAreaProps } from '@mantine/core';
18
+
19
+ export interface ArchbaseTagInputEditProps<T, ID> {
20
+ /** Fonte de dados onde será atribuido o valor do edit */
21
+ dataSource?: ArchbaseDataSource<T, ID>;
22
+ /** Campo onde deverá ser atribuido o valor do edit na fonte de dados */
23
+ dataField?: string;
24
+ /** Indicador se o edit está desabilitado */
25
+ disabled?: boolean;
26
+ /** Indicador se o edit é somente leitura. Obs: usado em conjunto com o status da fonte de dados */
27
+ readOnly?: boolean;
28
+ /** Indicador se o preenchimento do edit é obrigatório */
29
+ required?: boolean;
30
+ /** Valor controlado do componente */
31
+ value?: string[];
32
+ /** Valor padrão para componente não controlado */
33
+ defaultValue?: string[];
34
+ /** Estilo do edit */
35
+ style?: CSSProperties;
36
+ /** Tamanho do edit */
37
+ size?: MantineSize;
38
+ /** Largura do edit */
39
+ width?: string | number | undefined;
40
+ /** Icone à direita */
41
+ icon?: ReactNode;
42
+ /** Dica para botão localizar */
43
+ tooltipIconSearch?: string;
44
+ /** Evento ocorre quando clica no botão localizar */
45
+ onActionSearchExecute?: () => void;
46
+ /** Texto sugestão do edit */
47
+ placeholder?: string;
48
+ /** Título do edit */
49
+ label?: string;
50
+ /** Descrição do edit */
51
+ description?: string;
52
+ /** Último erro ocorrido no edit */
53
+ error?: string;
54
+ /** Evento quando o foco sai do edit */
55
+ onFocusExit?: FocusEventHandler<T> | undefined;
56
+ /** Evento quando o edit recebe o foco */
57
+ onFocusEnter?: FocusEventHandler<T> | undefined;
58
+ /** Evento quando o valor do edit é alterado */
59
+ onChangeValue?: (value: string[]) => void;
60
+ /** Chamado quando uma tag é removida */
61
+ onRemove?: (value: string) => void;
62
+ /** Chamado quando o botão limpar é clicado */
63
+ onClear?: () => void;
64
+ onKeyDown?: (event: any) => void;
65
+ onKeyUp?: (event: any) => void;
66
+ /** Referência para o componente interno */
67
+ innerRef?: React.RefObject<HTMLInputElement> | undefined;
68
+ variant?: ActionIconVariant;
69
+
70
+ // Props específicas do TagsInput baseadas na interface oficial
71
+ /** Dados exibidos no dropdown. Valores devem ser únicos */
72
+ data?: ComboboxData;
73
+ /** Valor de busca controlado */
74
+ searchValue?: string;
75
+ /** Valor de busca padrão */
76
+ defaultSearchValue?: string;
77
+ /** Chamado quando a busca muda */
78
+ onSearchChange?: (value: string) => void;
79
+ /** Número máximo de tags, `Infinity` por padrão */
80
+ maxTags?: number;
81
+ /** Determina se tags duplicadas são permitidas, `false` por padrão */
82
+ allowDuplicates?: boolean;
83
+ /** Chamado quando usuário tenta submeter uma tag duplicada */
84
+ onDuplicate?: (value: string) => void;
85
+ /** Caracteres que devem disparar a divisão de tags, `[',']` por padrão */
86
+ splitChars?: string[];
87
+ /** Determina se o botão limpar deve ser exibido quando o componente tem valor, `false` por padrão */
88
+ clearable?: boolean;
89
+ /** Props passadas para o botão limpar */
90
+ clearButtonProps?: InputClearButtonProps & React.ComponentPropsWithoutRef<'button'>;
91
+ /** Props passadas para o input oculto */
92
+ hiddenInputProps?: Omit<React.ComponentPropsWithoutRef<'input'>, 'value'>;
93
+ /** Divisor usado para separar valores no atributo `value` do input oculto, `','` por padrão */
94
+ hiddenInputValuesDivider?: string;
95
+ /** Função para renderizar o conteúdo da opção */
96
+ renderOption?: (input: ComboboxLikeRenderOptionInput<ComboboxGenericItem>) => React.ReactNode;
97
+ /** Props passadas para o componente `ScrollArea` subjacente no dropdown */
98
+ scrollAreaProps?: ScrollAreaProps;
99
+ /** Determina se o valor digitado pelo usuário mas não submetido deve ser aceito quando o input perde o foco, `true` por padrão */
100
+ acceptValueOnBlur?: boolean;
101
+ /** Limite de itens exibidos no dropdown */
102
+ limit?: number;
103
+ /** Converter para transformar o valor antes de salvar no dataSource */
104
+ outputConverter?: (tags: string[]) => any;
105
+ /** Converter para transformar o valor do dataSource antes de exibir */
106
+ inputConverter?: (value: any) => string[];
107
+ }
108
+
109
+ export function ArchbaseTagInputEdit<T, ID>({
110
+ dataSource,
111
+ dataField,
112
+ disabled = false,
113
+ readOnly = false,
114
+ style,
115
+ placeholder,
116
+ label,
117
+ description,
118
+ error,
119
+ required,
120
+ size,
121
+ width,
122
+ innerRef,
123
+ value,
124
+ defaultValue,
125
+ icon,
126
+ onKeyDown,
127
+ onKeyUp,
128
+ onActionSearchExecute,
129
+ tooltipIconSearch = 'Clique aqui para Localizar',
130
+ onFocusExit = () => {},
131
+ onFocusEnter = () => {},
132
+ onChangeValue = () => {},
133
+ onRemove,
134
+ onClear,
135
+ variant,
136
+
137
+ // Props específicas do TagsInput
138
+ data,
139
+ searchValue,
140
+ defaultSearchValue,
141
+ onSearchChange,
142
+ maxTags = Infinity,
143
+ allowDuplicates = false,
144
+ onDuplicate,
145
+ splitChars = [','],
146
+ clearable = false,
147
+ clearButtonProps,
148
+ hiddenInputProps,
149
+ hiddenInputValuesDivider = ',',
150
+ renderOption,
151
+ scrollAreaProps,
152
+ acceptValueOnBlur = true,
153
+ limit,
154
+ outputConverter,
155
+ inputConverter,
156
+ }: ArchbaseTagInputEditProps<T, ID>) {
157
+ const [currentValue, setCurrentValue] = useState<string[]>(value || defaultValue || []);
158
+ const innerComponentRef = useRef<any>(null);
159
+ const theme = useMantineTheme();
160
+ const { colorScheme } = useMantineColorScheme();
161
+ const [internalError, setInternalError] = useState<string | undefined>(error);
162
+ const forceUpdate = useForceUpdate();
163
+
164
+ useEffect(() => {
165
+ setInternalError(undefined);
166
+ }, [currentValue]);
167
+
168
+ useEffect(() => {
169
+ if (error !== internalError) {
170
+ setInternalError(error);
171
+ }
172
+ }, [error]);
173
+
174
+ useEffect(() => {
175
+ if (value !== undefined && JSON.stringify(value) !== JSON.stringify(currentValue)) {
176
+ setCurrentValue(value);
177
+ }
178
+ }, [value]);
179
+
180
+ const loadDataSourceFieldValue = () => {
181
+ let initialValue: string[] = currentValue;
182
+
183
+ if (dataSource && dataField) {
184
+ const fieldValue = dataSource.getFieldValue(dataField);
185
+
186
+ // Usa o inputConverter se fornecido
187
+ if (inputConverter) {
188
+ initialValue = inputConverter(fieldValue);
189
+ } else {
190
+ // Lógica padrão de conversão
191
+ if (Array.isArray(fieldValue)) {
192
+ initialValue = fieldValue;
193
+ } else if (typeof fieldValue === 'string' && fieldValue) {
194
+ // Se o valor no datasource for uma string, tenta fazer parse como JSON ou separar por vírgula
195
+ try {
196
+ const parsed = JSON.parse(fieldValue);
197
+ initialValue = Array.isArray(parsed) ? parsed : [fieldValue];
198
+ } catch {
199
+ initialValue = fieldValue.split(',').map(item => item.trim()).filter(item => item);
200
+ }
201
+ } else {
202
+ initialValue = [];
203
+ }
204
+ }
205
+ }
206
+
207
+ initialValue = initialValue.filter(value => value && value.trim() !== '');
208
+ setCurrentValue(initialValue);
209
+ };
210
+
211
+ const fieldChangedListener = useCallback(() => {
212
+ loadDataSourceFieldValue();
213
+ }, []);
214
+
215
+ const dataSourceEvent = useCallback((event: DataSourceEvent<T>) => {
216
+ if (dataSource && dataField) {
217
+ if (
218
+ event.type === DataSourceEventNames.dataChanged ||
219
+ event.type === DataSourceEventNames.recordChanged ||
220
+ event.type === DataSourceEventNames.afterScroll ||
221
+ event.type === DataSourceEventNames.afterCancel ||
222
+ event.type === DataSourceEventNames.afterEdit
223
+ ) {
224
+ loadDataSourceFieldValue();
225
+ forceUpdate();
226
+ }
227
+
228
+ if (event.type === DataSourceEventNames.onFieldError && event.fieldName === dataField) {
229
+ setInternalError(event.error);
230
+ }
231
+ }
232
+ }, []);
233
+
234
+ useArchbaseDidMount(() => {
235
+ loadDataSourceFieldValue();
236
+ if (dataSource && dataField) {
237
+ dataSource.addListener(dataSourceEvent);
238
+ dataSource.addFieldChangeListener(dataField, fieldChangedListener);
239
+ }
240
+ });
241
+
242
+ useArchbaseDidUpdate(() => {
243
+ if (!value) { // Só carrega do datasource se não é controlado
244
+ loadDataSourceFieldValue();
245
+ }
246
+ }, []);
247
+
248
+ const handleChange = (changedValue: string[]) => {
249
+ // Filtra valores vazios ou que contenham apenas espaços
250
+ const filteredValue = changedValue.filter(value => value && value.trim() !== '');
251
+
252
+ setCurrentValue(filteredValue);
253
+
254
+ if (dataSource && !dataSource.isBrowsing() && dataField) {
255
+ // Usa o outputConverter se fornecido, senão salva como array
256
+ const valueToSave = outputConverter ? outputConverter(filteredValue) : filteredValue;
257
+ dataSource.setFieldValue(dataField, valueToSave);
258
+ }
259
+
260
+ if (onChangeValue) {
261
+ onChangeValue(filteredValue);
262
+ }
263
+ };
264
+
265
+ const handleRemove = (removedValue: string) => {
266
+ const newValue = currentValue.filter(value => value !== removedValue && value && value.trim() !== '');
267
+ setCurrentValue(newValue);
268
+
269
+ if (dataSource && !dataSource.isBrowsing() && dataField) {
270
+ const valueToSave = outputConverter ? outputConverter(newValue) : newValue;
271
+ dataSource.setFieldValue(dataField, valueToSave);
272
+ }
273
+
274
+ if (onChangeValue) {
275
+ onChangeValue(newValue);
276
+ }
277
+
278
+ if (onRemove) {
279
+ onRemove(removedValue);
280
+ }
281
+ };
282
+
283
+ const handleClear = () => {
284
+ const emptyValue: string[] = [];
285
+ setCurrentValue(emptyValue);
286
+
287
+ if (dataSource && !dataSource.isBrowsing() && dataField) {
288
+ // Usa o outputConverter se fornecido, senão salva como array vazio
289
+ const valueToSave = outputConverter ? outputConverter(emptyValue) : emptyValue;
290
+ dataSource.setFieldValue(dataField, valueToSave);
291
+ }
292
+
293
+ if (onChangeValue) {
294
+ onChangeValue(emptyValue);
295
+ }
296
+
297
+ if (onClear) {
298
+ onClear();
299
+ }
300
+ };
301
+
302
+ useArchbaseWillUnmount(() => {
303
+ if (dataSource && dataField) {
304
+ dataSource.removeListener(dataSourceEvent);
305
+ dataSource.removeFieldChangeListener(dataField, fieldChangedListener);
306
+ }
307
+ });
308
+
309
+ const handleOnFocusExit = (event) => {
310
+ if (onFocusExit) {
311
+ onFocusExit(event);
312
+ }
313
+ };
314
+
315
+ const handleOnFocusEnter = (event) => {
316
+ if (onFocusEnter) {
317
+ onFocusEnter(event);
318
+ }
319
+ };
320
+
321
+ const isReadOnly = () => {
322
+ let tmpReadOnly = readOnly;
323
+ if (dataSource && !readOnly) {
324
+ tmpReadOnly = dataSource.isBrowsing();
325
+ }
326
+ return tmpReadOnly;
327
+ };
328
+
329
+ return (
330
+ <TagsInput
331
+ disabled={disabled}
332
+ readOnly={isReadOnly()}
333
+ size={size!}
334
+ style={{
335
+ width,
336
+ ...style,
337
+ }}
338
+ value={currentValue}
339
+ defaultValue={defaultValue}
340
+ ref={innerRef || innerComponentRef}
341
+ required={required}
342
+ onChange={handleChange}
343
+ onRemove={handleRemove}
344
+ onClear={handleClear}
345
+ onBlur={handleOnFocusExit}
346
+ onFocus={handleOnFocusEnter}
347
+ placeholder={placeholder}
348
+ description={description}
349
+ onKeyDown={onKeyDown}
350
+ onKeyUp={onKeyUp}
351
+ label={label}
352
+ error={internalError}
353
+
354
+ // Props específicas do TagsInput
355
+ data={data}
356
+ searchValue={searchValue}
357
+ defaultSearchValue={defaultSearchValue}
358
+ onSearchChange={onSearchChange}
359
+ maxTags={maxTags}
360
+ allowDuplicates={allowDuplicates}
361
+ onDuplicate={onDuplicate}
362
+ splitChars={splitChars}
363
+ clearable={clearable}
364
+ clearButtonProps={clearButtonProps}
365
+ hiddenInputProps={hiddenInputProps}
366
+ hiddenInputValuesDivider={hiddenInputValuesDivider}
367
+ renderOption={renderOption}
368
+ scrollAreaProps={scrollAreaProps}
369
+ acceptValueOnBlur={acceptValueOnBlur}
370
+ limit={limit}
371
+
372
+ rightSection={
373
+ onActionSearchExecute ? (
374
+ <Tooltip withinPortal withArrow label={tooltipIconSearch}>
375
+ <ActionIcon
376
+ style={{
377
+ backgroundColor:
378
+ variant === 'filled'
379
+ ? colorScheme === 'dark'
380
+ ? theme.colors[theme.primaryColor][5]
381
+ : theme.colors[theme.primaryColor][6]
382
+ : undefined,
383
+ }}
384
+ tabIndex={-1}
385
+ variant={variant}
386
+ onClick={onActionSearchExecute}
387
+ >
388
+ {icon}
389
+ </ActionIcon>
390
+ </Tooltip>
391
+ ) : null
392
+ }
393
+ />
394
+ );
395
+ }
@@ -138,6 +138,12 @@ export type { ArchbaseMentionInputProps, ArchbaseMentionConfig } from './Archbas
138
138
  export { ArchbaseSignaturePad } from './ArchbaseSignaturePad'
139
139
  export type { ArchbaseSignaturePadProps } from './ArchbaseSignaturePad'
140
140
 
141
+ export { ArchbaseMultiSelect, SelectItem, SelectedItem } from './ArchbaseMultiSelect'
142
+ export type { ArchbaseMultiSelectProps } from './ArchbaseMultiSelect'
143
+
144
+ export { ArchbaseTagInputEdit } from './ArchbaseTagInputEdit'
145
+ export type { ArchbaseTagInputEditProps } from './ArchbaseTagInputEdit'
146
+
141
147
  export { ArchbaseBarcodeScanner } from './ArchbaseBarcodeScanner'
142
148
  export type { ArchbaseBarcodeScannerProps } from './ArchbaseBarcodeScanner'
143
149