@controleonline/ui-default 1.0.269 → 1.0.272

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 (91) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/manager-worker.yml +19 -0
  3. package/AGENTS.md +46 -50
  4. package/docs/technical/Contrato-de-colunas.md +67 -0
  5. package/docs/technical/Default-Components.md +76 -0
  6. package/docs/technical/DefaultForm.md +80 -0
  7. package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
  8. package/docs/technical/DefaultTable.md +203 -0
  9. package/docs/wiki.md +1 -0
  10. package/package.json +22 -21
  11. package/scripts/validate-wiki-pointer.cjs +13 -0
  12. package/src/react/components/address/DefaultAddress.js +435 -0
  13. package/src/react/components/address/DefaultAddress.styles.js +133 -0
  14. package/src/react/components/address/DefaultAddressMapPane.js +55 -0
  15. package/src/react/components/address/LatLonReadonlyFields.js +51 -0
  16. package/src/react/components/address/defaultAddressHelpers.js +163 -0
  17. package/src/react/components/address/defaultAddressStyles.js +136 -0
  18. package/src/react/components/address/defaultAddressUtils.js +129 -0
  19. package/src/react/components/extra-data/DefaultExtraData.js +152 -0
  20. package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
  21. package/src/react/components/files/DefaultFile.js +231 -46
  22. package/src/react/components/files/DefaultFileColumn.js +123 -0
  23. package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
  24. package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
  25. package/src/react/components/filters/CompactFilterSelector.js +51 -35
  26. package/src/react/components/filters/DefaultColumnFilter.js +58 -41
  27. package/src/react/components/filters/DefaultExternalFilters.js +331 -265
  28. package/src/react/components/form/DefaultForm.js +99 -79
  29. package/src/react/components/help/DefaultTooltip.js +59 -33
  30. package/src/react/components/help/DefaultTooltip.styles.js +23 -3
  31. package/src/react/components/inputs/DefaultColorInput.js +153 -0
  32. package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
  33. package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
  34. package/src/react/components/inputs/DefaultInput.js +69 -5
  35. package/src/react/components/inputs/DefaultInput.styles.js +26 -0
  36. package/src/react/components/inputs/DefaultSelect.js +134 -123
  37. package/src/react/components/inputs/defaultInputUtils.js +69 -43
  38. package/src/react/components/map/DefaultGoogleMap.styles.js +9 -2
  39. package/src/react/components/map/DefaultGoogleMap.web.js +49 -28
  40. package/src/react/components/map/DefaultMap.native.js +73 -73
  41. package/src/react/components/map/DefaultMap.shared.js +911 -762
  42. package/src/react/components/map/DefaultMap.web.js +124 -68
  43. package/src/react/components/table/DefaultColumnMenu.js +109 -108
  44. package/src/react/components/table/DefaultDebug.js +45 -38
  45. package/src/react/components/table/DefaultSearchModal.js +9 -1
  46. package/src/react/components/table/DefaultTable.grouping.js +98 -0
  47. package/src/react/components/table/DefaultTable.js +215 -109
  48. package/src/react/components/table/DefaultTable.styles.js +367 -314
  49. package/src/react/components/table/DefaultTableBody.js +13 -8
  50. package/src/react/components/table/DefaultTableCards.js +20 -1
  51. package/src/react/components/table/DefaultTableControls.js +180 -205
  52. package/src/react/components/table/DefaultTableImportModal.js +108 -0
  53. package/src/react/components/table/DefaultTableInput.js +1 -0
  54. package/src/react/components/table/DefaultTableRowActions.js +1 -1
  55. package/src/react/components/table/DefaultTableRows.js +224 -363
  56. package/src/react/components/table/DefaultTableSearch.js +28 -5
  57. package/src/react/components/table/DefaultTableToolbar.js +92 -3
  58. package/src/react/components/table/useDefaultTablePagination.js +56 -7
  59. package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
  60. package/src/react/components/upload/DefaultUpload.js +436 -0
  61. package/src/react/components/upload/DefaultUpload.styles.js +153 -0
  62. package/src/react/components/upload/DefaultUploadAttachmentsList.js +138 -0
  63. package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
  64. package/src/react/components/upload/defaultUploadHelpers.js +108 -0
  65. package/src/react/components/upload/defaultUploadLibrary.js +248 -0
  66. package/src/react/components/upload/fileUpload.js +93 -0
  67. package/src/react/hooks/usePostalCodeLookup.js +145 -0
  68. package/src/react/index.js +6 -0
  69. package/src/react/services/addressFormUtils.js +196 -0
  70. package/src/react/services/addressGeo.js +227 -0
  71. package/src/react/utils/tableVisibleColumnsPreferences.js +29 -12
  72. package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
  73. package/src/tests/react/components/files/DefaultFile.test.js +6 -6
  74. package/src/tests/react/components/form/DefaultForm.test.js +123 -0
  75. package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
  76. package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
  77. package/src/tests/react/components/table/DefaultTable.test.js +54 -2
  78. package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
  79. package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
  80. package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
  81. package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
  82. package/src/tests/react/components/upload/defaultUploadLibrary.test.js +82 -0
  83. package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
  84. package/src/tests/react/hooks/usePostalCodeLookup.test.js +40 -0
  85. package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
  86. package/src/tests/react/services/addressFormUtils.test.js +176 -0
  87. package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
  88. package/src/tests/react/services/addressGeoLookup.test.js +105 -0
  89. package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
  90. package/src/tests/react/services/smoke-637/index.html +132 -0
  91. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +84 -48
@@ -1,17 +1,17 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
- import { useStore } from '@store';
4
- import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
5
- import DefaultInput from '../inputs/DefaultInput';
6
- import {
7
- formatSaveValue,
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+ import { useStore } from '@store';
4
+ import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
5
+ import DefaultInput from '../inputs/DefaultInput';
6
+ import {
7
+ formatSaveValue,
8
8
  getColumnKey,
9
9
  isEditableColumn,
10
10
  normalizeId,
11
- normalizeText,
12
- resolveEditValue,
13
- } from '../inputs/defaultInputUtils';
14
- import { createStyles } from './DefaultForm.styles';
11
+ normalizeText,
12
+ resolveEditValue,
13
+ } from '../inputs/defaultInputUtils';
14
+ import { createStyles } from './DefaultForm.styles';
15
15
 
16
16
  const hasValue = value => {
17
17
  if (value === null || value === undefined) return false;
@@ -46,50 +46,67 @@ const shouldIncludeColumn = (column, row, mode) => {
46
46
  return column?.visibleForm !== false;
47
47
  };
48
48
 
49
- const buildDraft = (columns, row) =>
50
- columns.reduce((draft, column) => {
51
- const fieldName = getColumnKey(column);
52
- const defaultValue = resolveDefaultValue(column, row);
53
- draft[fieldName] = resolveEditValue(column, row, defaultValue);
54
- return draft;
55
- }, {});
56
-
57
- const EMPTY_ROW = {};
58
-
59
- const DefaultForm = ({
60
- actions = {},
61
- columns = [],
62
- getOptionsForColumn = null,
49
+ const buildDraft = (columns, row) =>
50
+ columns.reduce((draft, column) => {
51
+ const fieldName = getColumnKey(column);
52
+ const defaultValue = resolveDefaultValue(column, row);
53
+ draft[fieldName] = resolveEditValue(column, row, defaultValue);
54
+ return draft;
55
+ }, {});
56
+
57
+ const resolveCreatePayloadColumns = (columns, formColumns) => {
58
+ const formColumnKeys = new Set(formColumns.map(column => getColumnKey(column)));
59
+
60
+ return [
61
+ ...formColumns,
62
+ ...columns.filter(column => {
63
+ const fieldName = getColumnKey(column);
64
+ return fieldName && column?.createPayload === true && !formColumnKeys.has(fieldName);
65
+ }),
66
+ ];
67
+ };
68
+
69
+ const EMPTY_ROW = {};
70
+
71
+ const DefaultForm = ({
72
+ actions = {},
73
+ columns = [],
74
+ getOptionsForColumn = null,
63
75
  mode = 'edit',
64
76
  onBeforeOpen = null,
65
- onCancel = null,
66
- onSaved = null,
67
- row = EMPTY_ROW,
68
- storeName = '',
69
- }) => {
70
- const themeStore = useStore('theme');
71
- const { colors: themeColors } = themeStore.getters;
72
- const palette = useMemo(
73
- () => ({
74
- buttonBackground: themeColors.buttonBackground,
75
- buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
76
- buttonBorderSecondary: themeColors.buttonBorderSecondary,
77
- buttonDisabledBackground: themeColors.buttonDisabledBackground,
78
- buttonDisabledText: themeColors.buttonDisabledText,
79
- buttonText: themeColors.buttonText,
80
- buttonTextSecondary: themeColors.buttonTextSecondary,
81
- dividerBorder: themeColors.dividerBorder,
82
- textSecondary: themeColors.textSecondary,
83
- }),
84
- [themeColors],
85
- );
86
- const styles = useMemo(() => createStyles(palette), [palette]);
87
- const formColumns = useMemo(
88
- () => columns.filter(column => shouldIncludeColumn(column, row, mode)),
89
- [columns, mode, row],
77
+ onCancel = null,
78
+ onSaved = null,
79
+ row = EMPTY_ROW,
80
+ storeName = '',
81
+ }) => {
82
+ const themeStore = useStore('theme');
83
+ const { colors: themeColors } = themeStore.getters;
84
+ const palette = useMemo(
85
+ () => ({
86
+ buttonBackground: themeColors.buttonBackground,
87
+ buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
88
+ buttonBorderSecondary: themeColors.buttonBorderSecondary,
89
+ buttonDisabledBackground: themeColors.buttonDisabledBackground,
90
+ buttonDisabledText: themeColors.buttonDisabledText,
91
+ buttonText: themeColors.buttonText,
92
+ buttonTextSecondary: themeColors.buttonTextSecondary,
93
+ dividerBorder: themeColors.dividerBorder,
94
+ textSecondary: themeColors.textSecondary,
95
+ }),
96
+ [themeColors],
97
+ );
98
+ const styles = useMemo(() => createStyles(palette), [palette]);
99
+ const formColumns = useMemo(
100
+ () => columns.filter(column => shouldIncludeColumn(column, row, mode)),
101
+ [columns, mode, row],
102
+ );
103
+ const createPayloadColumns = useMemo(
104
+ () => resolveCreatePayloadColumns(columns, formColumns),
105
+ [columns, formColumns],
90
106
  );
91
107
  const [draft, setDraft] = useState(() => buildDraft(formColumns, row));
92
108
  const [isSaving, setIsSaving] = useState(false);
109
+ const formRow = useMemo(() => ({ ...row, ...draft }), [draft, row]);
93
110
 
94
111
  useEffect(() => {
95
112
  setDraft(buildDraft(formColumns, row));
@@ -106,20 +123,23 @@ const DefaultForm = ({
106
123
  ? {}
107
124
  : { id: normalizeId(row?.['@id'] || row?.id) };
108
125
 
109
- formColumns.forEach(column => {
126
+ const payloadColumns = isCreate ? createPayloadColumns : formColumns;
127
+
128
+ payloadColumns.forEach(column => {
110
129
  const fieldName = getColumnKey(column);
111
- const nextValue = draft[fieldName];
130
+ const defaultValue = resolveDefaultValue(column, row);
131
+ const nextValue = draft[fieldName] ?? resolveEditValue(column, row, defaultValue);
112
132
 
113
133
  if (isCreate) {
114
134
  if (hasValue(nextValue)) {
115
- payload[fieldName] = formatSaveValue(column, nextValue, row);
135
+ payload[fieldName] = formatSaveValue(column, nextValue, formRow);
116
136
  }
117
137
  return;
118
138
  }
119
139
 
120
140
  const currentValue = resolveEditValue(column, row);
121
141
  if (normalizeText(currentValue) !== normalizeText(nextValue)) {
122
- payload[fieldName] = formatSaveValue(column, nextValue, row);
142
+ payload[fieldName] = formatSaveValue(column, nextValue, formRow);
123
143
  }
124
144
  });
125
145
 
@@ -143,7 +163,7 @@ const DefaultForm = ({
143
163
  .finally(() => {
144
164
  setIsSaving(false);
145
165
  });
146
- }, [actions, draft, formColumns, mode, onCancel, onSaved, row]);
166
+ }, [actions, createPayloadColumns, draft, formColumns, formRow, mode, onCancel, onSaved, row]);
147
167
 
148
168
  return (
149
169
  <>
@@ -152,7 +172,7 @@ const DefaultForm = ({
152
172
  <View style={styles.formGrid}>
153
173
  {formColumns.map(column => {
154
174
  const fieldName = getColumnKey(column);
155
- const label = formatStoreColumnLabel({
175
+ const label = column?.formLabel || formatStoreColumnLabel({
156
176
  columns,
157
177
  fieldName,
158
178
  fallbackLabel: column?.label || fieldName,
@@ -161,18 +181,18 @@ const DefaultForm = ({
161
181
 
162
182
  return (
163
183
  <View key={fieldName} style={styles.formField}>
164
- <DefaultInput
165
- accentColor={themeColors.buttonBackground}
166
- autoFocus={false}
167
- autoSave={false}
168
- column={column}
184
+ <DefaultInput
185
+ accentColor={themeColors.buttonBackground}
186
+ autoFocus={false}
187
+ autoSave={false}
188
+ column={column}
169
189
  columns={columns}
170
190
  editing
171
191
  getOptionsForColumn={getOptionsForColumn}
172
192
  label={label}
173
193
  onBeforeOpen={onBeforeOpen ? () => onBeforeOpen(column) : null}
174
194
  onChangeValue={value => setDraft(prev => ({ ...prev, [fieldName]: value }))}
175
- row={row}
195
+ row={formRow}
176
196
  showLabel
177
197
  storeName={storeName}
178
198
  value={draft[fieldName]}
@@ -191,26 +211,26 @@ const DefaultForm = ({
191
211
  )}
192
212
  </ScrollView>
193
213
 
194
- <View style={styles.actions}>
195
- <TouchableOpacity style={styles.secondaryButton} onPress={onCancel}>
196
- <Text style={styles.secondaryButtonText}>
214
+ <View style={styles.actions}>
215
+ <TouchableOpacity style={styles.secondaryButton} onPress={onCancel}>
216
+ <Text style={styles.secondaryButtonText}>
197
217
  {global.t?.t(storeName, 'button', 'cancel') || 'Cancelar'}
198
218
  </Text>
199
219
  </TouchableOpacity>
200
- <TouchableOpacity
201
- style={[
202
- styles.primaryButton,
203
- { backgroundColor: palette.buttonBackground },
204
- isSaving ? styles.primaryButtonDisabled : null,
205
- ]}
206
- activeOpacity={0.86}
207
- disabled={isSaving}
208
- onPress={save}
209
- >
210
- <Text style={[styles.primaryButtonText, isSaving ? styles.primaryButtonTextDisabled : null]}>
211
- {isSaving ? 'Salvando' : global.t?.t(storeName, 'button', 'save') || 'Salvar'}
212
- </Text>
213
- </TouchableOpacity>
220
+ <TouchableOpacity
221
+ style={[
222
+ styles.primaryButton,
223
+ { backgroundColor: palette.buttonBackground },
224
+ isSaving ? styles.primaryButtonDisabled : null,
225
+ ]}
226
+ activeOpacity={0.86}
227
+ disabled={isSaving}
228
+ onPress={save}
229
+ >
230
+ <Text style={[styles.primaryButtonText, isSaving ? styles.primaryButtonTextDisabled : null]}>
231
+ {isSaving ? 'Salvando' : global.t?.t(storeName, 'button', 'save') || 'Salvar'}
232
+ </Text>
233
+ </TouchableOpacity>
214
234
  </View>
215
235
  </>
216
236
  );
@@ -4,21 +4,28 @@ import styles from './DefaultTooltip.styles';
4
4
 
5
5
  const DefaultTooltip = ({
6
6
  accentColor = '#0EA5E9',
7
- label = '?',
8
- message = '',
9
- style = null,
10
- textStyle = null,
11
- title = 'Ajuda',
12
- }) => {
7
+ label = '?',
8
+ message = '',
9
+ rows = [],
10
+ style = null,
11
+ textStyle = null,
12
+ title = 'Ajuda',
13
+ testID,
14
+ backdropTestID,
15
+ dialogTestID,
16
+ closeAccessibilityLabel,
17
+ closeLabel = 'Fechar',
18
+ accessibilityLabel,
19
+ }) => {
13
20
  const [visible, setVisible] = useState(false);
14
21
 
15
22
  const openTooltip = useCallback(() => {
16
- if (!message) {
23
+ if (!message && rows.length === 0) {
17
24
  return;
18
25
  }
19
26
 
20
27
  setVisible(true);
21
- }, [message]);
28
+ }, [message, rows.length]);
22
29
 
23
30
  const closeTooltip = useCallback(() => {
24
31
  setVisible(false);
@@ -27,9 +34,10 @@ const DefaultTooltip = ({
27
34
  return (
28
35
  <>
29
36
  <TouchableOpacity
30
- accessibilityRole="button"
31
- activeOpacity={0.85}
32
- disabled={!message}
37
+ accessibilityRole="button"
38
+ accessibilityLabel={accessibilityLabel}
39
+ activeOpacity={0.85}
40
+ disabled={!message && rows.length === 0}
33
41
  onPress={openTooltip}
34
42
  style={[
35
43
  styles.button,
@@ -37,9 +45,10 @@ const DefaultTooltip = ({
37
45
  borderColor: accentColor,
38
46
  backgroundColor: `${accentColor}14`,
39
47
  },
40
- !message && {opacity: 0.55},
41
- style,
42
- ]}>
48
+ !message && rows.length === 0 && {opacity: 0.55},
49
+ style,
50
+ ]}
51
+ testID={testID}>
43
52
  <Text style={[styles.label, {color: accentColor}, textStyle]}>
44
53
  {label}
45
54
  </Text>
@@ -50,7 +59,10 @@ const DefaultTooltip = ({
50
59
  onRequestClose={closeTooltip}
51
60
  transparent
52
61
  visible={visible}>
53
- <Pressable style={styles.overlay} onPress={closeTooltip}>
62
+ <Pressable
63
+ style={styles.overlay}
64
+ onPress={closeTooltip}
65
+ testID={backdropTestID}>
54
66
  <View
55
67
  onStartShouldSetResponder={() => true}
56
68
  style={[
@@ -59,24 +71,38 @@ const DefaultTooltip = ({
59
71
  borderColor: accentColor,
60
72
  },
61
73
  ]}>
62
- <View style={styles.cardHeader}>
63
- <Text style={[styles.title, {color: accentColor}]}>{title}</Text>
64
- <TouchableOpacity
65
- accessibilityRole="button"
66
- activeOpacity={0.8}
67
- onPress={closeTooltip}
68
- style={[
69
- styles.closeButton,
70
- {
71
- borderColor: accentColor,
72
- },
73
- ]}>
74
- <Text style={[styles.closeButtonText, {color: accentColor}]}>
75
- Fechar
76
- </Text>
77
- </TouchableOpacity>
78
- </View>
79
- <Text style={styles.message}>{message}</Text>
74
+ <View testID={dialogTestID}>
75
+ <View style={styles.cardHeader}>
76
+ <Text style={[styles.title, {color: accentColor}]}>{title}</Text>
77
+ <TouchableOpacity
78
+ accessibilityRole="button"
79
+ accessibilityLabel={closeAccessibilityLabel}
80
+ activeOpacity={0.8}
81
+ onPress={closeTooltip}
82
+ style={[
83
+ styles.closeButton,
84
+ {
85
+ borderColor: accentColor,
86
+ },
87
+ ]}>
88
+ <Text style={[styles.closeButtonText, {color: accentColor}]}>
89
+ {closeLabel}
90
+ </Text>
91
+ </TouchableOpacity>
92
+ </View>
93
+ {rows.length > 0 ? (
94
+ <View style={styles.rows}>
95
+ {rows.map(row => (
96
+ <View key={row.key || row.label} style={styles.row}>
97
+ <Text style={styles.rowLabel}>{row.label}</Text>
98
+ <Text style={styles.rowValue}>{row.value}</Text>
99
+ </View>
100
+ ))}
101
+ </View>
102
+ ) : (
103
+ <Text style={styles.message}>{message}</Text>
104
+ )}
105
+ </View>
80
106
  </View>
81
107
  </Pressable>
82
108
  </Modal>
@@ -41,11 +41,31 @@ const styles = StyleSheet.create({
41
41
  fontSize: 15,
42
42
  fontWeight: '800',
43
43
  },
44
- message: {
44
+ message: {
45
45
  color: '#334155',
46
46
  fontSize: 13,
47
- lineHeight: 19,
48
- },
47
+ lineHeight: 19,
48
+ },
49
+ rows: {
50
+ gap: 9,
51
+ },
52
+ row: {
53
+ flexDirection: 'row',
54
+ gap: 12,
55
+ justifyContent: 'space-between',
56
+ },
57
+ rowLabel: {
58
+ color: '#64748B',
59
+ flex: 1,
60
+ fontSize: 12,
61
+ },
62
+ rowValue: {
63
+ color: '#0F172A',
64
+ flex: 1,
65
+ fontSize: 12,
66
+ fontWeight: '700',
67
+ textAlign: 'right',
68
+ },
49
69
  closeButton: {
50
70
  borderRadius: 999,
51
71
  borderWidth: 1,
@@ -0,0 +1,153 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { Text, TextInput, TouchableOpacity, View } from 'react-native';
3
+ import { useStore } from '@store';
4
+ import Icon from 'react-native-vector-icons/Feather';
5
+ import {
6
+ getColumnKey,
7
+ normalizeText,
8
+ resolveColorToken,
9
+ resolveDefaultColorValue,
10
+ } from './defaultInputUtils';
11
+ import styles from './DefaultInput.styles';
12
+
13
+ const HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
14
+
15
+ const normalizeColorText = value => {
16
+ const normalized = normalizeText(value);
17
+ if (!normalized) return '';
18
+
19
+ return normalized.startsWith('#') || normalized.startsWith('$')
20
+ ? normalized
21
+ : `#${normalized}`;
22
+ };
23
+
24
+ const isValidHexColor = value => HEX_COLOR_REGEX.test(resolveColorToken(value));
25
+
26
+ const DefaultColorInput = ({
27
+ accentColor = '#2563EB',
28
+ autoFocus = true,
29
+ autoSave = true,
30
+ column,
31
+ containerStyle = null,
32
+ defaultColor: defaultColorProp = '',
33
+ editing = false,
34
+ inputStyle = null,
35
+ label = '',
36
+ onCancelEditing = null,
37
+ onChangeValue = null,
38
+ onSave = null,
39
+ onStartEditing = null,
40
+ row = {},
41
+ saving = false,
42
+ showLabel = false,
43
+ value,
44
+ variant = 'cell',
45
+ }) => {
46
+ const themeColors = useStore('theme')?.getters?.colors || {};
47
+ const fieldName = getColumnKey(column);
48
+ const rawValue = value ?? row?.[fieldName];
49
+ const defaultColor = useMemo(
50
+ () => resolveDefaultColorValue(column, themeColors, defaultColorProp),
51
+ [column, defaultColorProp, themeColors],
52
+ );
53
+ const readValue = normalizeColorText(rawValue) || defaultColor || '#CBD5E1';
54
+ const previewColor = resolveColorToken(readValue, themeColors) || defaultColor || '#CBD5E1';
55
+ const [draftValue, setDraftValue] = useState(readValue);
56
+ const isForm = variant === 'form';
57
+
58
+ useEffect(() => {
59
+ setDraftValue(readValue);
60
+ }, [readValue, editing]);
61
+
62
+ const saveDraft = () => {
63
+ const nextValue = normalizeColorText(draftValue) || defaultColor || '#CBD5E1';
64
+ if (autoSave) {
65
+ onSave?.(nextValue);
66
+ return;
67
+ }
68
+
69
+ onChangeValue?.(nextValue);
70
+ };
71
+
72
+ const updateDraft = nextValue => {
73
+ setDraftValue(nextValue);
74
+ if (!autoSave) onChangeValue?.(normalizeColorText(nextValue));
75
+ };
76
+
77
+ const applyDefaultColor = () => {
78
+ if (!defaultColor) return;
79
+ setDraftValue(defaultColor);
80
+ if (!autoSave) onChangeValue?.(defaultColor);
81
+ };
82
+
83
+ if (!editing && !isForm) {
84
+ return (
85
+ <View style={[styles.wrap, containerStyle]}>
86
+ {showLabel ? <Text style={styles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
87
+ <TouchableOpacity
88
+ style={styles.readButton}
89
+ activeOpacity={0.78}
90
+ disabled={typeof onStartEditing !== 'function'}
91
+ onPress={() => onStartEditing?.()}
92
+ >
93
+ <View style={[styles.colorPreview, { backgroundColor: previewColor }]} />
94
+ <Text style={styles.readText} numberOfLines={1}>
95
+ {readValue}
96
+ </Text>
97
+ {saving ? (
98
+ <Text style={[styles.savingText, { color: accentColor }]}>Salvando</Text>
99
+ ) : (
100
+ <Icon style={styles.editIcon} name="edit-2" size={13} color="#64748B" />
101
+ )}
102
+ </TouchableOpacity>
103
+ </View>
104
+ );
105
+ }
106
+
107
+ return (
108
+ <View style={[styles.wrap, containerStyle]}>
109
+ {showLabel ? <Text style={styles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
110
+ <View style={styles.editingWrap}>
111
+ <View style={styles.editingRow}>
112
+ <View style={[styles.colorPreview, { backgroundColor: resolveColorToken(draftValue, themeColors) || previewColor }]} />
113
+ <TextInput
114
+ autoCapitalize="characters"
115
+ autoFocus={autoFocus && !isForm}
116
+ onBlur={autoSave ? saveDraft : undefined}
117
+ onChangeText={updateDraft}
118
+ onSubmitEditing={saveDraft}
119
+ placeholder={defaultColor || '#CBD5E1'}
120
+ placeholderTextColor="#CBD5E1"
121
+ selectTextOnFocus
122
+ style={[
123
+ styles.input,
124
+ styles.colorInput,
125
+ isForm ? styles.formInput : null,
126
+ isValidHexColor(draftValue) ? null : styles.invalidColorInput,
127
+ inputStyle,
128
+ ]}
129
+ value={draftValue}
130
+ />
131
+ {defaultColor ? (
132
+ <TouchableOpacity
133
+ accessibilityRole="button"
134
+ activeOpacity={0.8}
135
+ onPress={applyDefaultColor}
136
+ style={styles.colorDefaultButton}
137
+ >
138
+ <Icon name="rotate-ccw" size={13} color="#64748B" />
139
+ </TouchableOpacity>
140
+ ) : null}
141
+ {!isForm ? (
142
+ <TouchableOpacity style={styles.cancelButton} activeOpacity={0.8} onPress={onCancelEditing}>
143
+ <Icon name="x" size={14} color="#64748B" />
144
+ </TouchableOpacity>
145
+ ) : null}
146
+ </View>
147
+ {saving ? <Text style={styles.savingText}>Salvando</Text> : null}
148
+ </View>
149
+ </View>
150
+ );
151
+ };
152
+
153
+ export default DefaultColorInput;