@controleonline/ui-default 1.0.266 → 1.0.267

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@controleonline/ui-default",
3
- "version": "1.0.266",
3
+ "version": "1.0.267",
4
4
  "author": "ControleOnline",
5
5
  "description": "ControleOnline Default Quasar UI Component",
6
6
  "license": "MIT",
@@ -1,19 +1,21 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
- import { Text, TextInput, TouchableOpacity, View } from 'react-native';
2
+ import { Image, Text, TextInput, TouchableOpacity, View } from 'react-native';
3
3
  import Icon from 'react-native-vector-icons/Feather';
4
- import {
4
+ import {
5
+ buildReadPresentationStyles,
5
6
  getColumnKey,
6
7
  isDateLikeColumn,
7
8
  isEditableColumn,
8
- normalizeText,
9
- resolveCellText,
10
- resolveEditValue,
9
+ normalizeText,
10
+ resolveCellPresentation,
11
+ resolveCellText,
12
+ resolveEditValue,
11
13
  } from './defaultInputUtils';
12
14
  import DefaultDateInput from './DefaultDateInput';
13
15
  import DefaultSelect from './DefaultSelect';
14
- import styles from './DefaultInput.styles';
15
-
16
- const DefaultInput = ({
16
+ import styles from './DefaultInput.styles';
17
+
18
+ const DefaultInput = ({
17
19
  accentColor = '#2563EB',
18
20
  autoFocus = true,
19
21
  autoSave = true,
@@ -48,8 +50,19 @@ const DefaultInput = ({
48
50
  [column, row, value],
49
51
  );
50
52
  const [draftValue, setDraftValue] = useState(editValue);
51
- const resolvedLabel =
52
- displayValue ?? resolveCellText({ column, columns, row, storeName, value });
53
+ const resolvedLabel =
54
+ displayValue ?? resolveCellText({ column, columns, row, storeName, value });
55
+ const readPresentation = useMemo(
56
+ () => resolveCellPresentation({ column, columns, row, storeName, value }),
57
+ [column, columns, row, storeName, value],
58
+ );
59
+ const readPresentationStyles = useMemo(
60
+ () => buildReadPresentationStyles({
61
+ ...readPresentation,
62
+ label: resolvedLabel,
63
+ }),
64
+ [readPresentation, resolvedLabel],
65
+ );
53
66
 
54
67
  useEffect(() => {
55
68
  setDraftValue(editValue);
@@ -128,31 +141,56 @@ const DefaultInput = ({
128
141
  if (!autoSave) onChangeValue?.(nextValue);
129
142
  };
130
143
 
131
- if (!editing && !isForm) {
132
- return (
133
- <View style={[styles.wrap, containerStyle]}>
134
- {showLabel ? <Text style={styles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
135
- <TouchableOpacity
144
+ if (!editing && !isForm) {
145
+ return (
146
+ <View style={[styles.wrap, containerStyle]}>
147
+ {showLabel ? <Text style={styles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
148
+ <TouchableOpacity
136
149
  style={styles.readButton}
137
150
  activeOpacity={canEdit ? 0.78 : 1}
138
- disabled={!canEdit}
139
- onPress={() => onStartEditing?.()}
140
- >
141
- <Text
142
- style={[
143
- styles.readText,
144
- resolvedLabel === '-' ? styles.mutedText : null,
145
- readTextStyle,
146
- ]}
147
- numberOfLines={numberOfLines}
148
- >
149
- {column?.prefix || ''}
150
- {resolvedLabel || '-'}
151
- {column?.sufix || column?.suffix || ''}
152
- </Text>
153
- {saving ? (
154
- <Text style={[styles.savingText, { color: accentColor }]}>Salvando</Text>
155
- ) : canEdit ? (
151
+ disabled={!canEdit}
152
+ onPress={() => onStartEditing?.()}
153
+ >
154
+ <View
155
+ style={[
156
+ styles.readValueWrap,
157
+ readPresentationStyles.hasDecoration ? styles.readBadge : null,
158
+ readPresentationStyles.badgeStyle,
159
+ ]}
160
+ >
161
+ {readPresentation.image ? (
162
+ <Image
163
+ source={readPresentation.image}
164
+ style={styles.readImage}
165
+ resizeMode="contain"
166
+ />
167
+ ) : null}
168
+ {readPresentationStyles.hasDecoration && readPresentation.icon && readPresentationStyles.color ? (
169
+ <Icon
170
+ style={styles.readBadgeIcon}
171
+ name={readPresentation.icon}
172
+ size={12}
173
+ color={readPresentationStyles.color}
174
+ />
175
+ ) : null}
176
+ <Text
177
+ style={[
178
+ styles.readText,
179
+ readPresentationStyles.hasDecoration ? styles.readBadgeText : null,
180
+ resolvedLabel === '-' ? styles.mutedText : null,
181
+ readPresentationStyles.color ? { color: readPresentationStyles.color } : null,
182
+ readTextStyle,
183
+ ]}
184
+ numberOfLines={numberOfLines}
185
+ >
186
+ {column?.prefix || ''}
187
+ {resolvedLabel || '-'}
188
+ {column?.sufix || column?.suffix || ''}
189
+ </Text>
190
+ </View>
191
+ {saving ? (
192
+ <Text style={[styles.savingText, { color: accentColor }]}>Salvando</Text>
193
+ ) : canEdit ? (
156
194
  <Icon style={styles.editIcon} name="edit-2" size={13} color="#64748B" />
157
195
  ) : null}
158
196
  </TouchableOpacity>
@@ -13,6 +13,38 @@ const styles = StyleSheet.create({
13
13
  alignItems: 'center',
14
14
  gap: 6,
15
15
  },
16
+ readValueWrap: {
17
+ flex: 1,
18
+ minWidth: 0,
19
+ maxWidth: '100%',
20
+ flexDirection: 'row',
21
+ alignItems: 'center',
22
+ gap: 5,
23
+ },
24
+ readBadge: {
25
+ flex: 0,
26
+ alignSelf: 'flex-start',
27
+ minHeight: 22,
28
+ maxWidth: '100%',
29
+ borderWidth: 1,
30
+ borderRadius: 6,
31
+ paddingHorizontal: 7,
32
+ paddingVertical: 2,
33
+ },
34
+ readBadgeText: {
35
+ flex: 0,
36
+ flexShrink: 1,
37
+ fontSize: 11,
38
+ fontWeight: '800',
39
+ },
40
+ readBadgeIcon: {
41
+ flexShrink: 0,
42
+ },
43
+ readImage: {
44
+ width: 16,
45
+ height: 16,
46
+ flexShrink: 0,
47
+ },
16
48
  readButtonForm: {
17
49
  minHeight: 34,
18
50
  borderWidth: 1,
@@ -25,8 +57,8 @@ const styles = StyleSheet.create({
25
57
  flex: 1,
26
58
  minWidth: 0,
27
59
  color: '#0F172A',
28
- fontSize: 12,
29
- fontWeight: '700',
60
+ fontSize: 13,
61
+ fontWeight: '600',
30
62
  },
31
63
  mutedText: {
32
64
  color: '#94A3B8',
@@ -62,8 +94,8 @@ const styles = StyleSheet.create({
62
94
  borderRadius: 8,
63
95
  paddingHorizontal: 8,
64
96
  color: '#0F172A',
65
- fontSize: 12,
66
- fontWeight: '700',
97
+ fontSize: 13,
98
+ fontWeight: '600',
67
99
  backgroundColor: '#FFFFFF',
68
100
  },
69
101
  formInput: {
@@ -1,7 +1,8 @@
1
1
  import React, { useCallback, useMemo, useRef, useState } from 'react';
2
2
  import {
3
- Modal,
4
- ScrollView,
3
+ Modal,
4
+ Image,
5
+ ScrollView,
5
6
  Text,
6
7
  TextInput,
7
8
  TouchableOpacity,
@@ -10,12 +11,14 @@ import {
10
11
  import Icon from 'react-native-vector-icons/Feather';
11
12
  import { getAllStores, useStore } from '@store';
12
13
  import {
14
+ buildReadPresentationStyles,
13
15
  buildOptionsFromColumn,
14
16
  getColumnKey,
15
17
  isEditableColumn,
16
18
  mapOptions,
17
19
  normalizeOptionKey,
18
20
  normalizeText,
21
+ resolveCellPresentation,
19
22
  resolveCellText,
20
23
  resolveEditValue,
21
24
  resolveStoreNameFromList,
@@ -140,11 +143,22 @@ const DefaultSelect = ({
140
143
  ];
141
144
  }, [column, getOptionsForColumn, loadedItems, storeName]);
142
145
  const selected = options.find(option => option.key === selectedKey);
143
- const resolvedLabel =
144
- displayValue ??
145
- selected?.label ??
146
- resolveCellText({ column, columns, row, storeName, value });
147
- const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
146
+ const resolvedLabel =
147
+ displayValue ??
148
+ selected?.label ??
149
+ resolveCellText({ column, columns, row, storeName, value });
150
+ const readPresentation = useMemo(
151
+ () => resolveCellPresentation({ column, columns, row, storeName, value }),
152
+ [column, columns, row, storeName, value],
153
+ );
154
+ const readPresentationStyles = useMemo(
155
+ () => buildReadPresentationStyles({
156
+ ...readPresentation,
157
+ label: resolvedLabel,
158
+ }),
159
+ [readPresentation, resolvedLabel],
160
+ );
161
+ const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
148
162
  const isForm = variant === 'form';
149
163
  const filteredOptions = useMemo(() => {
150
164
  const query = normalizeText(searchText).toLowerCase();
@@ -281,20 +295,45 @@ const DefaultSelect = ({
281
295
  activeOpacity={canEdit || isForm ? 0.78 : 1}
282
296
  disabled={!canEdit && !isForm}
283
297
  onPress={open}
284
- >
285
- <Text
286
- style={[
287
- inputStyles.readText,
288
- resolvedLabel === '-' ? inputStyles.mutedText : null,
289
- readTextStyle,
290
- ]}
291
- numberOfLines={numberOfLines}
292
- >
293
- {resolvedLabel || '-'}
294
- </Text>
295
- {saving ? (
296
- <Text style={[inputStyles.savingText, { color: accentColor }]}>Salvando</Text>
297
- ) : canEdit || isForm ? (
298
+ >
299
+ <View
300
+ style={[
301
+ inputStyles.readValueWrap,
302
+ readPresentationStyles.hasDecoration ? inputStyles.readBadge : null,
303
+ readPresentationStyles.badgeStyle,
304
+ ]}
305
+ >
306
+ {readPresentation.image ? (
307
+ <Image
308
+ source={readPresentation.image}
309
+ style={inputStyles.readImage}
310
+ resizeMode="contain"
311
+ />
312
+ ) : null}
313
+ {readPresentationStyles.hasDecoration && readPresentation.icon && readPresentationStyles.color ? (
314
+ <Icon
315
+ style={inputStyles.readBadgeIcon}
316
+ name={readPresentation.icon}
317
+ size={12}
318
+ color={readPresentationStyles.color}
319
+ />
320
+ ) : null}
321
+ <Text
322
+ style={[
323
+ inputStyles.readText,
324
+ readPresentationStyles.hasDecoration ? inputStyles.readBadgeText : null,
325
+ resolvedLabel === '-' ? inputStyles.mutedText : null,
326
+ readPresentationStyles.color ? { color: readPresentationStyles.color } : null,
327
+ readTextStyle,
328
+ ]}
329
+ numberOfLines={numberOfLines}
330
+ >
331
+ {resolvedLabel || '-'}
332
+ </Text>
333
+ </View>
334
+ {saving ? (
335
+ <Text style={[inputStyles.savingText, { color: accentColor }]}>Salvando</Text>
336
+ ) : canEdit || isForm ? (
298
337
  <Icon style={inputStyles.editIcon} name="chevron-down" size={14} color="#64748B" />
299
338
  ) : null}
300
339
  </TouchableOpacity>
@@ -322,24 +361,33 @@ const DefaultSelect = ({
322
361
 
323
362
  <ScrollView keyboardShouldPersistTaps="handled">
324
363
  {filteredOptions.length > 0 ? (
325
- filteredOptions.map(option => {
326
- const isSelected = option.key === selectedKey;
327
- return (
328
- <TouchableOpacity
329
- key={`${option.key}_${option.label}`}
364
+ filteredOptions.map(option => {
365
+ const isSelected = option.key === selectedKey;
366
+ return (
367
+ <TouchableOpacity
368
+ key={`${option.key}_${option.label}`}
330
369
  style={styles.optionRow}
331
370
  activeOpacity={0.78}
332
371
  onPress={() => selectOption(option)}
333
372
  >
334
- <Icon
335
- name={isSelected ? 'check-circle' : 'circle'}
336
- size={15}
337
- color={isSelected ? accentColor : '#CBD5E1'}
338
- />
339
- <Text style={styles.optionText}>
340
- {option.label}
341
- </Text>
342
- </TouchableOpacity>
373
+ {option.image ? (
374
+ <Image
375
+ source={option.image}
376
+ style={inputStyles.readImage}
377
+ resizeMode="contain"
378
+ />
379
+ ) : null}
380
+ {isSelected || (option.icon && option.color) ? (
381
+ <Icon
382
+ name={isSelected ? 'check-circle' : option.icon}
383
+ size={15}
384
+ color={isSelected ? accentColor : option.color}
385
+ />
386
+ ) : null}
387
+ <Text style={[styles.optionText, option.color ? { color: option.color } : null]}>
388
+ {option.label}
389
+ </Text>
390
+ </TouchableOpacity>
343
391
  );
344
392
  })
345
393
  ) : (
@@ -128,7 +128,7 @@ export const resolveOptionLabel = (column, option, storeName = '') => {
128
128
  export const resolveStoreNameFromList = list =>
129
129
  typeof list === 'string' ? normalizeText(list).split('/')[0] || '' : '';
130
130
 
131
- const filterItemsByListRequestParams = (items = [], column = null) => {
131
+ const filterItemsByListRequestParams = (items = [], column = null) => {
132
132
  const requestParams =
133
133
  column?.listRequestParams &&
134
134
  typeof column.listRequestParams === 'object' &&
@@ -148,14 +148,17 @@ const filterItemsByListRequestParams = (items = [], column = null) => {
148
148
 
149
149
  return normalizeText(item?.[key]) === normalizeText(expectedValue);
150
150
  }),
151
- );
152
- };
153
-
154
- export const mapOptions = (column, items = [], storeName = '') => {
155
- const seenKeys = new Set();
156
- const options = [];
157
-
158
- (Array.isArray(items) ? items : []).forEach(item => {
151
+ );
152
+ };
153
+
154
+ const normalizeObject = value =>
155
+ value && typeof value === 'object' && !Array.isArray(value) ? value : {};
156
+
157
+ export const mapOptions = (column, items = [], storeName = '') => {
158
+ const seenKeys = new Set();
159
+ const options = [];
160
+
161
+ (Array.isArray(items) ? items : []).forEach(item => {
159
162
  const formatted =
160
163
  typeof column?.formatList === 'function'
161
164
  ? column.formatList(item, null, column)
@@ -166,12 +169,31 @@ export const mapOptions = (column, items = [], storeName = '') => {
166
169
  if (dedupeKey && seenKeys.has(dedupeKey)) return;
167
170
  if (dedupeKey) seenKeys.add(dedupeKey);
168
171
 
169
- options.push({
170
- key,
171
- label: resolveOptionLabel(column, item, storeName) || '-',
172
- raw: item,
173
- });
174
- });
172
+ const formattedMeta = normalizeObject(formatted);
173
+ const itemMeta = normalizeObject(item);
174
+ const color = formattedMeta.color ?? itemMeta.color;
175
+ const icon =
176
+ formattedMeta.icon ??
177
+ formattedMeta.iconName ??
178
+ itemMeta.icon ??
179
+ itemMeta.iconName;
180
+ const image =
181
+ formattedMeta.image ??
182
+ formattedMeta.logo ??
183
+ formattedMeta.source ??
184
+ itemMeta.image ??
185
+ itemMeta.logo ??
186
+ itemMeta.source;
187
+
188
+ options.push({
189
+ ...(color ? { color } : {}),
190
+ ...(icon ? { icon } : {}),
191
+ ...(image ? { image } : {}),
192
+ key,
193
+ label: resolveOptionLabel(column, item, storeName) || '-',
194
+ raw: item,
195
+ });
196
+ });
175
197
 
176
198
  return options;
177
199
  };
@@ -197,23 +219,148 @@ export const buildOptionsFromColumn = (column, getOptionsForColumn = null, store
197
219
  );
198
220
  };
199
221
 
200
- export const resolveCellText = ({ column, columns = [], row, storeName, value }) => {
201
- const fieldName = getColumnKey(column);
202
- const rawValue = value ?? row?.[fieldName];
203
- const formattedValue = formatStoreColumnValue({
222
+ export const resolveCellText = ({ column, columns = [], row, storeName, value }) => {
223
+ const fieldName = getColumnKey(column);
224
+ const rawValue = value ?? row?.[fieldName];
225
+ const formattedValue = formatStoreColumnValue({
204
226
  columns: columns.length ? columns : [column],
205
227
  fieldName,
206
228
  row,
207
229
  storeName,
208
230
  value: rawValue,
209
231
  });
210
-
211
- return normalizeText(formattedValue) || '-';
212
- };
213
-
214
- export const resolveEditValue = (column, row, value) => {
215
- const rawValue = value ?? row?.[getColumnKey(column)];
216
- if (column?.list) {
232
+
233
+ return normalizeText(formattedValue) || '-';
234
+ };
235
+
236
+ const resolveFormattedListMeta = (column, rawValue, row) => {
237
+ if (typeof column?.formatList !== 'function') {
238
+ return {};
239
+ }
240
+
241
+ const formatted = column.formatList(rawValue, row, column);
242
+ return normalizeObject(formatted);
243
+ };
244
+
245
+ const resolveStyleMeta = (column, row) => {
246
+ if (typeof column?.style !== 'function') {
247
+ return normalizeObject(column?.style);
248
+ }
249
+
250
+ return normalizeObject(column.style(row));
251
+ };
252
+
253
+ export const resolveCellPresentation = ({
254
+ column,
255
+ columns = [],
256
+ row = {},
257
+ storeName = '',
258
+ value,
259
+ } = {}) => {
260
+ const fieldName = getColumnKey(column);
261
+ const rawValue = value ?? row?.[fieldName];
262
+ const rawMeta = normalizeObject(rawValue);
263
+ const formattedMeta = resolveFormattedListMeta(column, rawValue, row);
264
+ const styleMeta = resolveStyleMeta(column, row);
265
+ const label = resolveCellText({
266
+ column,
267
+ columns,
268
+ row,
269
+ storeName,
270
+ value,
271
+ });
272
+
273
+ return {
274
+ backgroundColor:
275
+ formattedMeta.backgroundColor ??
276
+ formattedMeta.bgColor ??
277
+ rawMeta.backgroundColor ??
278
+ rawMeta.bgColor ??
279
+ styleMeta.backgroundColor ??
280
+ styleMeta.bgColor,
281
+ borderColor:
282
+ formattedMeta.borderColor ??
283
+ rawMeta.borderColor ??
284
+ styleMeta.borderColor,
285
+ color:
286
+ formattedMeta.color ??
287
+ rawMeta.color ??
288
+ rawMeta.statusColor ??
289
+ rawMeta.categoryColor ??
290
+ styleMeta.color,
291
+ icon:
292
+ formattedMeta.icon ??
293
+ formattedMeta.iconName ??
294
+ rawMeta.icon ??
295
+ rawMeta.iconName ??
296
+ styleMeta.icon ??
297
+ styleMeta.iconName,
298
+ image:
299
+ formattedMeta.image ??
300
+ formattedMeta.logo ??
301
+ formattedMeta.source ??
302
+ rawMeta.image ??
303
+ rawMeta.logo ??
304
+ rawMeta.source ??
305
+ styleMeta.image ??
306
+ styleMeta.logo ??
307
+ styleMeta.source,
308
+ label,
309
+ };
310
+ };
311
+
312
+ const resolveAlphaColor = (color, opacity = 0.12) => {
313
+ const normalized = normalizeText(color);
314
+ const hex = normalized.match(/^#?([a-f\d]{6})$/i)?.[1];
315
+
316
+ if (!hex) return normalized || undefined;
317
+
318
+ const red = parseInt(hex.slice(0, 2), 16);
319
+ const green = parseInt(hex.slice(2, 4), 16);
320
+ const blue = parseInt(hex.slice(4, 6), 16);
321
+
322
+ return `rgba(${red}, ${green}, ${blue}, ${opacity})`;
323
+ };
324
+
325
+ export const buildReadPresentationStyles = presentation => {
326
+ const color = normalizeText(presentation?.color);
327
+ const icon = normalizeText(presentation?.icon);
328
+ const hasDecoration = Boolean(
329
+ color ||
330
+ normalizeText(presentation?.backgroundColor) ||
331
+ normalizeText(presentation?.borderColor) ||
332
+ (icon && color),
333
+ );
334
+
335
+ if (!hasDecoration || presentation?.label === '-') {
336
+ return {
337
+ badgeStyle: null,
338
+ color: null,
339
+ hasDecoration: false,
340
+ };
341
+ }
342
+
343
+ const backgroundColor =
344
+ presentation.backgroundColor ||
345
+ (color ? resolveAlphaColor(color, 0.1) : undefined);
346
+ const borderColor =
347
+ presentation.borderColor ||
348
+ presentation.backgroundColor ||
349
+ (color ? resolveAlphaColor(color, 0.32) : undefined);
350
+
351
+ return {
352
+ badgeStyle: {
353
+ backgroundColor,
354
+ borderColor,
355
+ },
356
+ color: color || undefined,
357
+ hasDecoration: true,
358
+ };
359
+ };
360
+
361
+ export const resolveEditValue = (column, row, value) => {
362
+ const rawValue = value ?? row?.[getColumnKey(column)];
363
+ if (column?.list) {
217
364
  return normalizeOptionKey(
218
365
  typeof column?.formatList === 'function'
219
366
  ? column.formatList(rawValue, row, column)
@@ -96,10 +96,11 @@ const DefaultTable = ({
96
96
  const isFocused = useIsFocused();
97
97
  const configsSignatureRef = useRef('');
98
98
  const { showError } = useMessage() || {};
99
- const { themeColors } = useDefaultTableTheme(accentColor);
99
+ const { tableBorderColors, themeColors } = useDefaultTableTheme(accentColor);
100
+ const tablePanelBorderColor = tableBorderColors.containerBorderColor;
100
101
  const floatingAddBackgroundColor =
101
- themeColors.buttonBackground || themeColors.primary || accentColor || '#2563EB';
102
- const floatingAddIconColor = themeColors.buttonText || '#FFFFFF';
102
+ themeColors.buttonBackground || themeColors.primary || accentColor;
103
+ const floatingAddIconColor = themeColors.buttonText;
103
104
  const tablePreferenceScope = useMemo(
104
105
  () =>
105
106
  resolveDefaultTablePreferenceScope({
@@ -400,7 +401,8 @@ const DefaultTable = ({
400
401
  style={[
401
402
  styles.wrap,
402
403
  {
403
- borderColor: themeColors.panelBorder,
404
+ borderWidth: tablePanelBorderColor ? 1 : 0,
405
+ borderColor: tablePanelBorderColor,
404
406
  backgroundColor: themeColors.panelBackground,
405
407
  },
406
408
  ]}
@@ -16,15 +16,14 @@ const styles = StyleSheet.create({
16
16
  flex: 1,
17
17
  width: '100%',
18
18
  minHeight: 0,
19
- borderWidth: 1,
20
- borderRadius: 10,
21
- overflow: 'visible',
19
+ borderRadius: 8,
20
+ overflow: 'hidden',
22
21
  position: 'relative',
23
22
  },
24
23
  toolbar: {
25
- minHeight: 36,
26
- paddingHorizontal: 8,
27
- paddingVertical: 4,
24
+ minHeight: 52,
25
+ paddingHorizontal: 16,
26
+ paddingVertical: 10,
28
27
  borderBottomWidth: 1,
29
28
  flexDirection: 'row',
30
29
  flexWrap: 'nowrap',
@@ -51,7 +50,7 @@ const styles = StyleSheet.create({
51
50
  flexDirection: 'row',
52
51
  flexWrap: 'nowrap',
53
52
  alignItems: 'center',
54
- gap: 6,
53
+ gap: 8,
55
54
  flexShrink: 0,
56
55
  marginLeft: 'auto',
57
56
  },
@@ -76,8 +75,8 @@ const styles = StyleSheet.create({
76
75
  marginLeft: 0,
77
76
  },
78
77
  toolbarSearch: {
79
- width: 190,
80
- maxWidth: 220,
78
+ width: 260,
79
+ maxWidth: 360,
81
80
  flexShrink: 1,
82
81
  },
83
82
  toolbarSearchButton: {
@@ -111,10 +110,10 @@ const styles = StyleSheet.create({
111
110
  fontWeight: '900',
112
111
  },
113
112
  toolbarButton: {
114
- minWidth: 28,
115
- height: 28,
116
- borderRadius: 14,
117
- paddingHorizontal: 7,
113
+ minWidth: 36,
114
+ height: 36,
115
+ borderRadius: 8,
116
+ paddingHorizontal: 10,
118
117
  borderWidth: 1,
119
118
  flexDirection: 'row',
120
119
  alignItems: 'center',
@@ -124,8 +123,8 @@ const styles = StyleSheet.create({
124
123
  toolbarButtonActive: {
125
124
  },
126
125
  toolbarAddButton: {
127
- minWidth: 30,
128
- width: 30,
126
+ minWidth: 36,
127
+ width: 36,
129
128
  paddingHorizontal: 0,
130
129
  },
131
130
  floatingAddButton: {
@@ -283,8 +282,8 @@ const styles = StyleSheet.create({
283
282
  minWidth: '100%',
284
283
  minHeight: 0,
285
284
  },
286
- headerRow: {
287
- minHeight: 34,
285
+ headerRow: {
286
+ minHeight: 40,
288
287
  width: '100%',
289
288
  flexDirection: 'row',
290
289
  alignItems: 'center',
@@ -292,8 +291,8 @@ const styles = StyleSheet.create({
292
291
  borderBottomWidth: 1,
293
292
  borderBottomColor: '#E2E8F0',
294
293
  },
295
- filterRow: {
296
- minHeight: 40,
294
+ filterRow: {
295
+ minHeight: 46,
297
296
  width: '100%',
298
297
  flexDirection: 'row',
299
298
  alignItems: 'stretch',
@@ -301,25 +300,24 @@ const styles = StyleSheet.create({
301
300
  borderBottomWidth: 1,
302
301
  borderBottomColor: '#E2E8F0',
303
302
  },
304
- row: {
305
- minHeight: 44,
303
+ row: {
304
+ minHeight: 48,
306
305
  width: '100%',
307
306
  flexDirection: 'row',
308
307
  alignItems: 'stretch',
309
308
  borderBottomWidth: 1,
310
309
  borderBottomColor: '#EEF2F7',
311
310
  },
312
- cell: {
313
- minWidth: 118,
314
- flexBasis: 118,
311
+ cell: {
312
+ minWidth: 118,
313
+ flexBasis: 118,
315
314
  flexGrow: 1,
316
315
  flexShrink: 0,
317
- paddingHorizontal: 10,
318
- paddingVertical: 8,
319
- justifyContent: 'center',
320
- borderRightWidth: 1,
321
- borderRightColor: '#EEF2F7',
322
- },
316
+ paddingHorizontal: 14,
317
+ paddingVertical: 10,
318
+ justifyContent: 'center',
319
+ borderRightWidth: 0,
320
+ },
323
321
  identityCell: {
324
322
  minWidth: 76,
325
323
  flexBasis: 76,
@@ -377,13 +375,12 @@ const styles = StyleSheet.create({
377
375
  flexBasis: 132,
378
376
  flexGrow: 0.9,
379
377
  },
380
- headerText: {
381
- color: '#64748B',
382
- fontSize: 10,
383
- fontWeight: '900',
384
- letterSpacing: 0.35,
385
- textTransform: 'uppercase',
386
- },
378
+ headerText: {
379
+ fontSize: 11,
380
+ fontWeight: '800',
381
+ letterSpacing: 0,
382
+ textTransform: 'uppercase',
383
+ },
387
384
  sortableHeader: {
388
385
  flexDirection: 'row',
389
386
  alignItems: 'center',
@@ -404,11 +401,10 @@ const styles = StyleSheet.create({
404
401
  fontSize: 11,
405
402
  fontWeight: '700',
406
403
  },
407
- cellText: {
408
- color: '#0F172A',
409
- fontSize: 12,
410
- fontWeight: '700',
411
- },
404
+ cellText: {
405
+ fontSize: 13,
406
+ fontWeight: '600',
407
+ },
412
408
  mutedCellText: {
413
409
  color: '#94A3B8',
414
410
  },
@@ -491,23 +487,30 @@ const styles = StyleSheet.create({
491
487
  flexGrow: 1,
492
488
  width: '100%',
493
489
  },
494
- cardItem: {
495
- position: 'relative',
496
- },
490
+ cardItem: {
491
+ position: 'relative',
492
+ },
493
+ cardItemWithActions: {
494
+ flexDirection: 'row',
495
+ alignItems: 'center',
496
+ gap: 8,
497
+ },
498
+ cardContent: {
499
+ flex: 1,
500
+ minWidth: 0,
501
+ },
497
502
  cardActions: {
498
- position: 'absolute',
499
- right: 10,
500
- bottom: 10,
501
503
  flexDirection: 'row',
502
504
  alignItems: 'center',
505
+ justifyContent: 'flex-end',
503
506
  gap: 6,
507
+ flexShrink: 0,
504
508
  },
505
509
  cardActionGroup: {
506
- position: 'absolute',
507
- right: 8,
508
- top: 8,
509
510
  flexDirection: 'row',
510
511
  alignItems: 'center',
512
+ justifyContent: 'flex-end',
513
+ alignSelf: 'flex-end',
511
514
  gap: 6,
512
515
  },
513
516
  defaultCard: {
@@ -81,15 +81,24 @@ const DefaultTableCards = ({ storeName }) => {
81
81
 
82
82
  if (typeof configs.renderCard === 'function') {
83
83
  return (
84
- <View key={row?.['@id'] || row?.id} style={[styles.cardItem, rowStyleValue]}>
85
- {configs.renderCard({
86
- item: row,
87
- openEdit: () => configs.onEditRow?.(row),
88
- openRow: hasRowPress ? () => configs.onRowPress(row) : null,
89
- renderField,
90
- renderValue,
91
- row,
92
- })}
84
+ <View
85
+ key={row?.['@id'] || row?.id}
86
+ style={[
87
+ styles.cardItem,
88
+ hasRowActions ? styles.cardItemWithActions : null,
89
+ rowStyleValue,
90
+ ]}
91
+ >
92
+ <View style={styles.cardContent}>
93
+ {configs.renderCard({
94
+ item: row,
95
+ openEdit: () => configs.onEditRow?.(row),
96
+ openRow: hasRowPress ? () => configs.onRowPress(row) : null,
97
+ renderField,
98
+ renderValue,
99
+ row,
100
+ })}
101
+ </View>
93
102
  {hasRowActions ? (
94
103
  <View style={styles.cardActions}>
95
104
  {customRowActions}
@@ -124,7 +124,16 @@ const DefaultTableFooter = ({ storeName }) => {
124
124
  }
125
125
 
126
126
  return (
127
- <View style={[styles.footerBar, { backgroundColor: tableFooterBackgroundColor, borderTopColor: tableFooterBorderColor }]}>
127
+ <View
128
+ style={[
129
+ styles.footerBar,
130
+ {
131
+ backgroundColor: tableFooterBackgroundColor,
132
+ borderTopColor: tableFooterBorderColor,
133
+ borderTopWidth: tableFooterBorderColor ? 1 : 0,
134
+ },
135
+ ]}
136
+ >
128
137
  {summaryEntries.length > 0 ? (
129
138
  <View style={styles.footerSummaryList}>
130
139
  {summaryEntries.map(entry => (
@@ -23,7 +23,7 @@ import useDefaultTableTheme from './useDefaultTableTheme';
23
23
  const DefaultTableRows = ({ storeName }) => {
24
24
  const store = useStore(storeName);
25
25
  const configs = store?.getters?.configs || {};
26
- const { palette, resolvedAccentColor, themeTokens } = useDefaultTableTheme();
26
+ const { palette, resolvedAccentColor, tableBorderColors, themeTokens } = useDefaultTableTheme();
27
27
  const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
28
28
  const visibleColumns = store?.getters?.visibleColumns || {};
29
29
  const tableColumns = columns.filter(
@@ -38,7 +38,8 @@ const DefaultTableRows = ({ storeName }) => {
38
38
  const emptyStateLabel = isLoading
39
39
  ? global.t?.t(storeName, 'label', 'loading')
40
40
  : global.t?.t(storeName, 'label', 'empty');
41
- const tableBorderColor = palette.border;
41
+ const tableBorderColor = tableBorderColors.rowBorderColor;
42
+ const tableHeaderBorderColor = tableBorderColors.headerBorderColor || tableBorderColor;
42
43
  const tableEvenColor = themeTokens.listItemEvenRow || themeTokens['bg-even-light'] || palette.background;
43
44
  const tableHeaderColor = themeTokens['bg-headers-light'] || resolvedAccentColor;
44
45
  const tableMutedColor = palette.textSecondary;
@@ -128,8 +129,12 @@ const DefaultTableRows = ({ storeName }) => {
128
129
  <RowComponent
129
130
  key={getRowKey(row)}
130
131
  style={[
131
- styles.row,
132
- { backgroundColor: rowBackgroundColor, borderBottomColor: tableBorderColor },
132
+ styles.row,
133
+ {
134
+ backgroundColor: rowBackgroundColor,
135
+ borderBottomColor: tableBorderColor,
136
+ borderBottomWidth: tableBorderColor ? 1 : 0,
137
+ },
133
138
  rowStyleValue,
134
139
  ]}
135
140
  {...rowPressProps}
@@ -209,7 +214,16 @@ const DefaultTableRows = ({ storeName }) => {
209
214
  contentContainerStyle={styles.horizontalScrollContent}
210
215
  >
211
216
  <View style={styles.content}>
212
- <View style={[styles.headerRow, { backgroundColor: tableHeaderColor, borderBottomColor: tableBorderColor }]}>
217
+ <View
218
+ style={[
219
+ styles.headerRow,
220
+ {
221
+ backgroundColor: tableHeaderColor,
222
+ borderBottomColor: tableHeaderBorderColor,
223
+ borderBottomWidth: tableHeaderBorderColor ? 1 : 0,
224
+ },
225
+ ]}
226
+ >
213
227
  {tableColumns.map(column => {
214
228
  const fieldName = getColumnKey(column);
215
229
  const label = formatStoreColumnLabel({
@@ -267,7 +281,16 @@ const DefaultTableRows = ({ storeName }) => {
267
281
  ) : null}
268
282
  </View>
269
283
  {hasColumnFilters ? (
270
- <View style={[styles.filterRow, { backgroundColor: tableHeaderColor, borderBottomColor: tableBorderColor }]}>
284
+ <View
285
+ style={[
286
+ styles.filterRow,
287
+ {
288
+ backgroundColor: tableHeaderColor,
289
+ borderBottomColor: tableHeaderBorderColor,
290
+ borderBottomWidth: tableHeaderBorderColor ? 1 : 0,
291
+ },
292
+ ]}
293
+ >
271
294
  {tableColumns.map(column => {
272
295
  const fieldName = getColumnKey(column);
273
296
 
@@ -20,13 +20,15 @@ const DefaultTableToolbar = ({ storeName }) => {
20
20
  const showTotalItemsInCompactToolbar = configs.showTotalItemsInCompactToolbar === true;
21
21
  const showTotalItemsInFooter = configs.showTotalItemsInFooter !== false;
22
22
  const toolbarActions = Array.isArray(configs.toolbarActions) ? configs.toolbarActions : [];
23
- const { toolbarColors } = useDefaultTableTheme();
23
+ const { tableBorderColors, toolbarColors } = useDefaultTableTheme();
24
24
  const {
25
25
  backgroundColor: toolbarBackgroundColor,
26
26
  borderColor: toolbarBorderColor,
27
27
  countBackgroundColor: toolbarCountBackgroundColor,
28
28
  countTextColor: toolbarCountTextColor,
29
29
  } = toolbarColors;
30
+ const resolvedToolbarBorderColor =
31
+ tableBorderColors.toolbarBorderColor || toolbarBorderColor;
30
32
  const storeTotalItems = store?.getters?.totalItems;
31
33
  const resolvedTotalItems = storeTotalItems;
32
34
  const totalItemsNumber = Number(resolvedTotalItems);
@@ -63,7 +65,11 @@ const DefaultTableToolbar = ({ storeName }) => {
63
65
  style={[
64
66
  styles.toolbar,
65
67
  shouldRenderCompactToolbarTotalItems ? styles.toolbarWithCompactTotal : null,
66
- { borderBottomColor: toolbarBorderColor, backgroundColor: toolbarBackgroundColor },
68
+ {
69
+ borderBottomColor: resolvedToolbarBorderColor,
70
+ borderBottomWidth: resolvedToolbarBorderColor ? 1 : 0,
71
+ backgroundColor: toolbarBackgroundColor,
72
+ },
67
73
  ]}
68
74
  >
69
75
  {shouldRenderCompactToolbarTotalItems ? (
@@ -1,20 +1,69 @@
1
- import { useMemo } from 'react';
2
- import { useStore } from '@store';
3
-
4
- const useDefaultTableTheme = (accentColor = null) => {
5
- const themeStore = useStore('theme');
6
- const themeColors = themeStore.getters.colors;
7
- const themeTokens = useMemo(
8
- () => ({ ...themeColors }),
9
- [themeColors],
1
+ import { useMemo } from 'react';
2
+ import { useStore } from '@store';
3
+
4
+ const firstThemeToken = (themeColors, keys) =>
5
+ keys.map(key => themeColors?.[key]).find(Boolean);
6
+
7
+ const useDefaultTableTheme = (accentColor = null) => {
8
+ const themeStore = useStore('theme');
9
+ const rawThemeColors = themeStore?.getters?.colors || {};
10
+ const themeColors = useMemo(
11
+ () => ({ ...rawThemeColors }),
12
+ [rawThemeColors],
13
+ );
14
+ const themeTokens = useMemo(
15
+ () => ({ ...themeColors }),
16
+ [themeColors],
10
17
  );
11
- const palette = themeColors;
12
- const resolvedAccentColor = accentColor ?? themeColors.primary;
13
-
14
- return {
15
- palette,
16
- resolvedAccentColor,
17
- themeColors,
18
+ const palette = themeColors;
19
+ const resolvedAccentColor = accentColor ?? themeColors.primary;
20
+ const tableBorderColors = {
21
+ containerBorderColor: firstThemeToken(themeColors, [
22
+ 'tableContainerBorder',
23
+ 'tableBorder',
24
+ 'tableHeaderBorder',
25
+ 'tableToolbarBorder',
26
+ 'cardBorder',
27
+ 'listItemBorder',
28
+ 'dividerBorder',
29
+ 'dividerBackground',
30
+ 'inputBorder',
31
+ 'panelBorder',
32
+ 'border',
33
+ ]),
34
+ headerBorderColor: firstThemeToken(themeColors, [
35
+ 'tableHeaderBorder',
36
+ 'tableToolbarBorder',
37
+ 'tableBorder',
38
+ 'dividerBorder',
39
+ 'dividerBackground',
40
+ 'listItemBorder',
41
+ 'border',
42
+ ]),
43
+ rowBorderColor: firstThemeToken(themeColors, [
44
+ 'tableRowBorder',
45
+ 'listItemBorder',
46
+ 'dividerBorder',
47
+ 'dividerBackground',
48
+ 'tableBorder',
49
+ 'border',
50
+ ]),
51
+ toolbarBorderColor: firstThemeToken(themeColors, [
52
+ 'tableToolbarBorder',
53
+ 'tableHeaderBorder',
54
+ 'toolbarBorder',
55
+ 'tableBorder',
56
+ 'dividerBorder',
57
+ 'dividerBackground',
58
+ 'border',
59
+ ]),
60
+ };
61
+
62
+ return {
63
+ palette,
64
+ resolvedAccentColor,
65
+ tableBorderColors,
66
+ themeColors,
18
67
  themeTokens,
19
68
  checkboxBorderColor: themeColors.checkboxBorder,
20
69
  checkboxSelectedMarkColor: themeColors.checkboxSelectedMark,
@@ -6,14 +6,18 @@ jest.mock('@store', () => ({
6
6
 
7
7
  jest.mock('@controleonline/ui-common/src/utils/formatter.js', () => ({}));
8
8
 
9
- jest.mock('@controleonline/ui-common/src/react/utils/storeColumns', () => ({
10
- formatStoreColumnValue: ({value}) => value,
11
- }));
12
-
13
- const {mapOptions} = require('../../../react/components/inputs/defaultInputUtils');
9
+ jest.mock('@controleonline/ui-common/src/react/utils/storeColumns', () => ({
10
+ formatStoreColumnValue: ({value}) => value?.status || value?.category || value,
11
+ }));
12
+
13
+ const {
14
+ buildReadPresentationStyles,
15
+ mapOptions,
16
+ resolveCellPresentation,
17
+ } = require('../../../react/components/inputs/defaultInputUtils');
14
18
 
15
19
  describe('defaultInputUtils', () => {
16
- it('deduplicates list options by normalized key', () => {
20
+ it('deduplicates list options by normalized key', () => {
17
21
  const column = {
18
22
  formatList(item) {
19
23
  return {
@@ -40,6 +44,73 @@ describe('defaultInputUtils', () => {
40
44
  label: 'Cielo',
41
45
  raw: {id: 4579, wallet: 'Cielo'},
42
46
  },
43
- ]);
44
- });
45
- });
47
+ ]);
48
+ });
49
+
50
+ it('preserves option color and icon metadata from formatList', () => {
51
+ const column = {
52
+ formatList(item) {
53
+ return {
54
+ color: item.color,
55
+ icon: item.icon,
56
+ value: item.id,
57
+ label: item.status,
58
+ };
59
+ },
60
+ };
61
+
62
+ const options = mapOptions(column, [
63
+ {id: 1, status: 'Em rota', color: '#2563EB', icon: 'truck'},
64
+ ]);
65
+
66
+ expect(options).toEqual([
67
+ {
68
+ color: '#2563EB',
69
+ icon: 'truck',
70
+ key: '1',
71
+ label: 'Em rota',
72
+ raw: {id: 1, status: 'Em rota', color: '#2563EB', icon: 'truck'},
73
+ },
74
+ ]);
75
+ });
76
+
77
+ it('resolves table cell presentation from row value and column style', () => {
78
+ const presentation = resolveCellPresentation({
79
+ column: {
80
+ name: 'status',
81
+ style: row => ({color: row.status.color}),
82
+ },
83
+ row: {
84
+ status: {
85
+ status: 'Aprovado',
86
+ color: '#16A34A',
87
+ icon: 'check-circle',
88
+ },
89
+ },
90
+ storeName: 'orders',
91
+ });
92
+
93
+ expect(presentation).toEqual({
94
+ backgroundColor: undefined,
95
+ borderColor: undefined,
96
+ color: '#16A34A',
97
+ icon: 'check-circle',
98
+ image: undefined,
99
+ label: 'Aprovado',
100
+ });
101
+ });
102
+
103
+ it('builds subtle badge colors from resource colors', () => {
104
+ expect(buildReadPresentationStyles({
105
+ color: '#16A34A',
106
+ label: 'Aprovado',
107
+ })).toEqual({
108
+ badgeStyle: {
109
+ backgroundColor: 'rgba(22, 163, 74, 0.1)',
110
+ borderColor: 'rgba(22, 163, 74, 0.32)',
111
+ },
112
+ color: '#16A34A',
113
+ hasDecoration: true,
114
+ });
115
+ });
116
+ });