@controleonline/ui-default 1.0.263 → 1.0.264

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 (29) hide show
  1. package/AGENTS.md +48 -47
  2. package/package.json +3 -6
  3. package/src/react/components/errors/DefaultErrors.js +360 -360
  4. package/src/react/components/files/DefaultFile.js +46 -46
  5. package/src/react/components/filters/CompactFilterSelector.js +22 -3
  6. package/src/react/components/filters/DefaultColumnFilter.js +15 -13
  7. package/src/react/components/filters/DefaultExternalFilters.js +44 -39
  8. package/src/react/components/filters/DefaultSearch.js +140 -103
  9. package/src/react/components/filters/DefaultSearch.styles.js +65 -70
  10. package/src/react/components/form/DefaultForm.js +200 -198
  11. package/src/react/components/help/DefaultTooltip.js +87 -87
  12. package/src/react/components/help/DefaultTooltip.styles.js +61 -61
  13. package/src/react/components/inputs/DefaultInput.js +162 -160
  14. package/src/react/components/inputs/DefaultSelect.js +203 -192
  15. package/src/react/components/map/DefaultGoogleMap.styles.js +115 -9
  16. package/src/react/components/map/DefaultGoogleMap.web.js +560 -446
  17. package/src/react/components/map/DefaultMap.native.js +73 -71
  18. package/src/react/components/map/DefaultMap.styles.js +16 -16
  19. package/src/react/components/map/DefaultMap.web.js +68 -66
  20. package/src/react/components/map/DefaultNativeMap.native.js +307 -183
  21. package/src/react/components/map/DefaultNativeMap.shared.js +439 -340
  22. package/src/react/components/map/DefaultNativeMap.styles.js +156 -122
  23. package/src/react/components/table/DefaultTable.js +529 -355
  24. package/src/react/components/table/DefaultTable.styles.js +30 -39
  25. package/src/react/index.js +10 -10
  26. package/src/tests/react/components/errors/DefaultErrors.test.js +162 -162
  27. package/src/tests/react/components/files/DefaultFile.test.js +80 -80
  28. package/src/tests/react/components/inputs/DefaultSelect.test.js +93 -0
  29. package/src/tests/react/components/table/DefaultTable.test.js +334 -0
@@ -1,192 +1,203 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
2
- import {
3
- Modal,
4
- ScrollView,
5
- Text,
6
- TextInput,
7
- TouchableOpacity,
8
- View,
9
- } from 'react-native';
10
- import Icon from 'react-native-vector-icons/Feather';
11
- import {
12
- buildOptionsFromColumn,
13
- getColumnKey,
14
- isEditableColumn,
15
- normalizeOptionKey,
16
- normalizeText,
17
- resolveCellText,
18
- resolveEditValue,
19
- } from './defaultInputUtils';
20
- import inputStyles from './DefaultInput.styles';
21
- import styles from './DefaultSelect.styles';
22
-
23
- const DefaultSelect = ({
24
- accentColor = '#2563EB',
25
- autoSave = true,
26
- column,
27
- columns = [],
28
- containerStyle = null,
29
- displayValue,
30
- editing = false,
31
- getOptionsForColumn = null,
32
- label = '',
33
- numberOfLines = 1,
34
- onCancelEditing = null,
35
- onChangeValue = null,
36
- onSave = null,
37
- onStartEditing = null,
38
- readTextStyle = null,
39
- row = {},
40
- saving = false,
41
- showLabel = false,
42
- storeName = '',
43
- value,
44
- variant = 'cell',
45
- }) => {
46
- const [isOpen, setIsOpen] = useState(false);
47
- const [searchText, setSearchText] = useState('');
48
-
49
- const fieldName = getColumnKey(column);
50
- const selectedKey = normalizeOptionKey(value ?? resolveEditValue(column, row));
51
- const options = useMemo(
52
- () => buildOptionsFromColumn(column, getOptionsForColumn, storeName),
53
- [column, getOptionsForColumn, storeName],
54
- );
55
- const selected = options.find(option => option.key === selectedKey);
56
- const resolvedLabel =
57
- displayValue ??
58
- selected?.label ??
59
- resolveCellText({ column, columns, row, storeName, value });
60
- const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
61
- const isForm = variant === 'form';
62
- const filteredOptions = useMemo(() => {
63
- const query = normalizeText(searchText).toLowerCase();
64
- if (!query) return options;
65
- return options.filter(option =>
66
- normalizeText(option.label).toLowerCase().includes(query) ||
67
- normalizeText(option.key).toLowerCase().includes(query),
68
- );
69
- }, [options, searchText]);
70
-
71
- useEffect(() => {
72
- if (editing && autoSave) setIsOpen(true);
73
- }, [autoSave, editing]);
74
-
75
- const close = () => {
76
- setIsOpen(false);
77
- setSearchText('');
78
- if (autoSave) onCancelEditing?.();
79
- };
80
-
81
- const selectOption = option => {
82
- setIsOpen(false);
83
- setSearchText('');
84
-
85
- if (autoSave) {
86
- onSave?.({
87
- value: option.key,
88
- label: option.label,
89
- object: option.raw,
90
- });
91
- return;
92
- }
93
-
94
- onChangeValue?.(option.key, {
95
- value: option.key,
96
- label: option.label,
97
- object: option.raw,
98
- });
99
- };
100
-
101
- const open = () => {
102
- if (!canEdit && !isForm) return;
103
- if (!editing && !isForm) onStartEditing?.();
104
- setIsOpen(true);
105
- };
106
-
107
- return (
108
- <View style={[inputStyles.wrap, containerStyle]}>
109
- {showLabel ? <Text style={inputStyles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
110
-
111
- <TouchableOpacity
112
- style={[inputStyles.readButton, isForm ? inputStyles.readButtonForm : null]}
113
- activeOpacity={canEdit || isForm ? 0.78 : 1}
114
- disabled={!canEdit && !isForm}
115
- onPress={open}
116
- >
117
- <Text
118
- style={[
119
- inputStyles.readText,
120
- resolvedLabel === '-' ? inputStyles.mutedText : null,
121
- readTextStyle,
122
- ]}
123
- numberOfLines={numberOfLines}
124
- >
125
- {resolvedLabel || '-'}
126
- </Text>
127
- {saving ? (
128
- <Text style={[inputStyles.savingText, { color: accentColor }]}>Salvando</Text>
129
- ) : canEdit || isForm ? (
130
- <Icon style={inputStyles.editIcon} name="chevron-down" size={14} color="#64748B" />
131
- ) : null}
132
- </TouchableOpacity>
133
-
134
- <Modal visible={isOpen} transparent animationType="fade" onRequestClose={close}>
135
- <View style={styles.overlay}>
136
- <View style={styles.modalCard}>
137
- <View style={styles.modalHeader}>
138
- <Text style={styles.modalTitle} numberOfLines={1}>
139
- {label || column?.label || fieldName}
140
- </Text>
141
- <TouchableOpacity style={inputStyles.cancelButton} onPress={close}>
142
- <Icon name="x" size={16} color="#64748B" />
143
- </TouchableOpacity>
144
- </View>
145
-
146
- <View style={styles.searchBox}>
147
- <TextInput
148
- style={[inputStyles.input, inputStyles.formInput]}
149
- value={searchText}
150
- placeholder={global.t?.t(storeName, 'input', column?.searchParam || 'search')}
151
- onChangeText={setSearchText}
152
- />
153
- </View>
154
-
155
- <ScrollView keyboardShouldPersistTaps="handled">
156
- {filteredOptions.length > 0 ? (
157
- filteredOptions.map(option => {
158
- const isSelected = option.key === selectedKey;
159
- return (
160
- <TouchableOpacity
161
- key={`${option.key}_${option.label}`}
162
- style={styles.optionRow}
163
- activeOpacity={0.78}
164
- onPress={() => selectOption(option)}
165
- >
166
- <Icon
167
- name={isSelected ? 'check-circle' : 'circle'}
168
- size={15}
169
- color={isSelected ? accentColor : '#CBD5E1'}
170
- />
171
- <Text style={styles.optionText}>
172
- {option.label}
173
- </Text>
174
- </TouchableOpacity>
175
- );
176
- })
177
- ) : (
178
- <View style={styles.emptyBox}>
179
- <Text style={styles.emptyText}>
180
- Nenhum resultado encontrado
181
- </Text>
182
- </View>
183
- )}
184
- </ScrollView>
185
- </View>
186
- </View>
187
- </Modal>
188
- </View>
189
- );
190
- };
191
-
192
- export default DefaultSelect;
1
+ import React, { useMemo, useState } from 'react';
2
+ import {
3
+ Modal,
4
+ ScrollView,
5
+ Text,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ View,
9
+ } from 'react-native';
10
+ import Icon from 'react-native-vector-icons/Feather';
11
+ import {
12
+ buildOptionsFromColumn,
13
+ getColumnKey,
14
+ isEditableColumn,
15
+ normalizeOptionKey,
16
+ normalizeText,
17
+ resolveCellText,
18
+ resolveEditValue,
19
+ } from './defaultInputUtils';
20
+ import inputStyles from './DefaultInput.styles';
21
+ import styles from './DefaultSelect.styles';
22
+
23
+ const DefaultSelect = ({
24
+ accentColor = '#2563EB',
25
+ autoSave = true,
26
+ column,
27
+ columns = [],
28
+ containerStyle = null,
29
+ displayValue,
30
+ editing = false,
31
+ getOptionsForColumn = null,
32
+ label = '',
33
+ numberOfLines = 1,
34
+ onBeforeOpen = null,
35
+ onCancelEditing = null,
36
+ onChangeValue = null,
37
+ onSave = null,
38
+ onStartEditing = null,
39
+ readTextStyle = null,
40
+ row = {},
41
+ saving = false,
42
+ showLabel = false,
43
+ storeName = '',
44
+ value,
45
+ variant = 'cell',
46
+ }) => {
47
+ const [isOpen, setIsOpen] = useState(false);
48
+ const [searchText, setSearchText] = useState('');
49
+
50
+ const fieldName = getColumnKey(column);
51
+ const selectedKey = normalizeOptionKey(value ?? resolveEditValue(column, row));
52
+ const options = useMemo(
53
+ () => buildOptionsFromColumn(column, getOptionsForColumn, storeName),
54
+ [column, getOptionsForColumn, storeName],
55
+ );
56
+ const selected = options.find(option => option.key === selectedKey);
57
+ const resolvedLabel =
58
+ displayValue ??
59
+ selected?.label ??
60
+ resolveCellText({ column, columns, row, storeName, value });
61
+ const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
62
+ const isForm = variant === 'form';
63
+ const filteredOptions = useMemo(() => {
64
+ const query = normalizeText(searchText).toLowerCase();
65
+ if (!query) return options;
66
+ return options.filter(option =>
67
+ normalizeText(option.label).toLowerCase().includes(query) ||
68
+ normalizeText(option.key).toLowerCase().includes(query),
69
+ );
70
+ }, [options, searchText]);
71
+
72
+ const close = () => {
73
+ setIsOpen(false);
74
+ setSearchText('');
75
+ if (autoSave) onCancelEditing?.();
76
+ };
77
+
78
+ const selectOption = option => {
79
+ setIsOpen(false);
80
+ setSearchText('');
81
+
82
+ if (autoSave) {
83
+ onSave?.({
84
+ value: option.key,
85
+ label: option.label,
86
+ object: option.raw,
87
+ });
88
+ return;
89
+ }
90
+
91
+ onChangeValue?.(option.key, {
92
+ value: option.key,
93
+ label: option.label,
94
+ object: option.raw,
95
+ });
96
+ };
97
+
98
+ const open = async () => {
99
+ if (!canEdit && !isForm) return;
100
+ try {
101
+ if (!editing && !isForm) {
102
+ const maybePromise = onStartEditing?.();
103
+ if (maybePromise && typeof maybePromise.then === 'function') {
104
+ await maybePromise;
105
+ }
106
+ }
107
+
108
+ const maybeLoad = onBeforeOpen?.();
109
+ if (maybeLoad && typeof maybeLoad.then === 'function') {
110
+ await maybeLoad;
111
+ }
112
+ } catch (error) {
113
+ return;
114
+ }
115
+ setIsOpen(true);
116
+ };
117
+
118
+ return (
119
+ <View style={[inputStyles.wrap, containerStyle]}>
120
+ {showLabel ? <Text style={inputStyles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
121
+
122
+ <TouchableOpacity
123
+ style={[inputStyles.readButton, isForm ? inputStyles.readButtonForm : null]}
124
+ activeOpacity={canEdit || isForm ? 0.78 : 1}
125
+ disabled={!canEdit && !isForm}
126
+ onPress={open}
127
+ >
128
+ <Text
129
+ style={[
130
+ inputStyles.readText,
131
+ resolvedLabel === '-' ? inputStyles.mutedText : null,
132
+ readTextStyle,
133
+ ]}
134
+ numberOfLines={numberOfLines}
135
+ >
136
+ {resolvedLabel || '-'}
137
+ </Text>
138
+ {saving ? (
139
+ <Text style={[inputStyles.savingText, { color: accentColor }]}>Salvando</Text>
140
+ ) : canEdit || isForm ? (
141
+ <Icon style={inputStyles.editIcon} name="chevron-down" size={14} color="#64748B" />
142
+ ) : null}
143
+ </TouchableOpacity>
144
+
145
+ <Modal visible={isOpen} transparent animationType="fade" onRequestClose={close}>
146
+ <View style={styles.overlay}>
147
+ <View style={styles.modalCard}>
148
+ <View style={styles.modalHeader}>
149
+ <Text style={styles.modalTitle} numberOfLines={1}>
150
+ {label || column?.label || fieldName}
151
+ </Text>
152
+ <TouchableOpacity style={inputStyles.cancelButton} onPress={close}>
153
+ <Icon name="x" size={16} color="#64748B" />
154
+ </TouchableOpacity>
155
+ </View>
156
+
157
+ <View style={styles.searchBox}>
158
+ <TextInput
159
+ style={[inputStyles.input, inputStyles.formInput]}
160
+ value={searchText}
161
+ placeholder={global.t?.t(storeName, 'input', column?.searchParam || 'search')}
162
+ onChangeText={setSearchText}
163
+ />
164
+ </View>
165
+
166
+ <ScrollView keyboardShouldPersistTaps="handled">
167
+ {filteredOptions.length > 0 ? (
168
+ filteredOptions.map(option => {
169
+ const isSelected = option.key === selectedKey;
170
+ return (
171
+ <TouchableOpacity
172
+ key={`${option.key}_${option.label}`}
173
+ style={styles.optionRow}
174
+ activeOpacity={0.78}
175
+ onPress={() => selectOption(option)}
176
+ >
177
+ <Icon
178
+ name={isSelected ? 'check-circle' : 'circle'}
179
+ size={15}
180
+ color={isSelected ? accentColor : '#CBD5E1'}
181
+ />
182
+ <Text style={styles.optionText}>
183
+ {option.label}
184
+ </Text>
185
+ </TouchableOpacity>
186
+ );
187
+ })
188
+ ) : (
189
+ <View style={styles.emptyBox}>
190
+ <Text style={styles.emptyText}>
191
+ Nenhum resultado encontrado
192
+ </Text>
193
+ </View>
194
+ )}
195
+ </ScrollView>
196
+ </View>
197
+ </View>
198
+ </Modal>
199
+ </View>
200
+ );
201
+ };
202
+
203
+ export default DefaultSelect;
@@ -1,9 +1,115 @@
1
- import {StyleSheet} from 'react-native';
2
-
3
- export default StyleSheet.create({
4
- mapViewport: {
5
- width: '100%',
6
- height: '100%',
7
- backgroundColor: '#E5EEF5',
8
- },
9
- });
1
+ import {StyleSheet} from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ mapRoot: {
5
+ flex: 1,
6
+ position: 'relative',
7
+ },
8
+ mapViewport: {
9
+ flex: 1,
10
+ backgroundColor: '#E5EEF5',
11
+ },
12
+ popupOverlay: {
13
+ ...StyleSheet.absoluteFillObject,
14
+ alignItems: 'center',
15
+ justifyContent: 'center',
16
+ paddingHorizontal: 16,
17
+ paddingVertical: 16,
18
+ },
19
+ popupCard: {
20
+ width: '100%',
21
+ maxWidth: 420,
22
+ borderRadius: 24,
23
+ paddingHorizontal: 20,
24
+ paddingTop: 18,
25
+ paddingBottom: 20,
26
+ shadowOffset: {
27
+ width: 0,
28
+ height: 18,
29
+ },
30
+ shadowOpacity: 0.18,
31
+ shadowRadius: 28,
32
+ elevation: 12,
33
+ },
34
+ popupHeaderRow: {
35
+ flexDirection: 'row',
36
+ alignItems: 'center',
37
+ marginBottom: 14,
38
+ },
39
+ popupLogoWrap: {
40
+ width: 58,
41
+ height: 58,
42
+ borderRadius: 16,
43
+ overflow: 'hidden',
44
+ borderWidth: 1,
45
+ alignItems: 'center',
46
+ justifyContent: 'center',
47
+ backgroundColor: '#FFFFFF',
48
+ flexShrink: 0,
49
+ marginRight: 12,
50
+ },
51
+ popupLogoImage: {
52
+ width: '100%',
53
+ height: '100%',
54
+ },
55
+ popupTitleWrap: {
56
+ flex: 1,
57
+ minWidth: 0,
58
+ marginRight: 8,
59
+ },
60
+ popupTitle: {
61
+ fontSize: 18,
62
+ lineHeight: 22,
63
+ fontWeight: '800',
64
+ },
65
+ popupCloseButton: {
66
+ width: 40,
67
+ height: 40,
68
+ borderRadius: 20,
69
+ alignItems: 'center',
70
+ justifyContent: 'center',
71
+ flexShrink: 0,
72
+ },
73
+ popupCloseText: {
74
+ fontSize: 28,
75
+ lineHeight: 30,
76
+ fontWeight: '400',
77
+ },
78
+ popupLine: {
79
+ fontSize: 15,
80
+ lineHeight: 22,
81
+ fontWeight: '600',
82
+ marginBottom: 4,
83
+ },
84
+ popupInfoCard: {
85
+ borderWidth: 1,
86
+ borderRadius: 18,
87
+ paddingHorizontal: 16,
88
+ paddingVertical: 14,
89
+ },
90
+ popupSummary: {
91
+ marginTop: 10,
92
+ fontSize: 14,
93
+ lineHeight: 20,
94
+ fontWeight: '700',
95
+ },
96
+ popupActionsRow: {
97
+ flexDirection: 'row',
98
+ marginTop: 18,
99
+ marginHorizontal: -5,
100
+ },
101
+ popupActionButton: {
102
+ flex: 1,
103
+ minHeight: 52,
104
+ borderRadius: 999,
105
+ alignItems: 'center',
106
+ justifyContent: 'center',
107
+ paddingHorizontal: 16,
108
+ marginHorizontal: 5,
109
+ },
110
+ popupActionText: {
111
+ fontSize: 16,
112
+ lineHeight: 20,
113
+ fontWeight: '800',
114
+ },
115
+ });