@controleonline/ui-default 1.0.266 → 1.0.268

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.
@@ -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,15 @@ 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,
18
+ isValidFeatherIcon,
16
19
  mapOptions,
17
20
  normalizeOptionKey,
18
21
  normalizeText,
22
+ resolveCellPresentation,
19
23
  resolveCellText,
20
24
  resolveEditValue,
21
25
  resolveStoreNameFromList,
@@ -140,12 +144,33 @@ const DefaultSelect = ({
140
144
  ];
141
145
  }, [column, getOptionsForColumn, loadedItems, storeName]);
142
146
  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';
148
- const isForm = variant === 'form';
147
+ const resolvedLabel =
148
+ displayValue ??
149
+ selected?.label ??
150
+ resolveCellText({ column, columns, row, storeName, value });
151
+ const readPresentation = useMemo(
152
+ () => resolveCellPresentation({ column, columns, row, storeName, value }),
153
+ [column, columns, row, storeName, value],
154
+ );
155
+ const readPresentationStyles = useMemo(
156
+ () => buildReadPresentationStyles({
157
+ ...readPresentation,
158
+ label: resolvedLabel,
159
+ }),
160
+ [readPresentation, resolvedLabel],
161
+ );
162
+ const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
163
+ const isForm = variant === 'form';
164
+ const readIconName = isValidFeatherIcon(readPresentation.icon)
165
+ ? readPresentation.icon
166
+ : '';
167
+ const shouldRenderIconFallbackText = Boolean(
168
+ readPresentationStyles.hasDecoration &&
169
+ !readIconName &&
170
+ readPresentationStyles.color &&
171
+ normalizeText(resolvedLabel) &&
172
+ resolvedLabel !== '-',
173
+ );
149
174
  const filteredOptions = useMemo(() => {
150
175
  const query = normalizeText(searchText).toLowerCase();
151
176
  if (!query || onSearchChange) return options;
@@ -276,25 +301,64 @@ const DefaultSelect = ({
276
301
  <View style={[inputStyles.wrap, containerStyle]}>
277
302
  {showLabel ? <Text style={inputStyles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
278
303
 
279
- <TouchableOpacity
280
- style={[inputStyles.readButton, isForm ? inputStyles.readButtonForm : null]}
281
- activeOpacity={canEdit || isForm ? 0.78 : 1}
282
- disabled={!canEdit && !isForm}
283
- 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 ? (
304
+ <TouchableOpacity
305
+ style={[inputStyles.readButton, isForm ? inputStyles.readButtonForm : null]}
306
+ activeOpacity={canEdit || isForm ? 0.78 : 1}
307
+ disabled={!canEdit && !isForm}
308
+ onPress={open}
309
+ >
310
+ {shouldRenderIconFallbackText ? (
311
+ <Text
312
+ style={[
313
+ inputStyles.readText,
314
+ inputStyles.readTextBadge,
315
+ readPresentationStyles.color ? { color: readPresentationStyles.color } : null,
316
+ readTextStyle,
317
+ ]}
318
+ numberOfLines={numberOfLines}
319
+ >
320
+ {resolvedLabel}
321
+ </Text>
322
+ ) : (
323
+ <View
324
+ style={[
325
+ inputStyles.readValueWrap,
326
+ readPresentationStyles.hasDecoration ? inputStyles.readBadge : null,
327
+ readPresentationStyles.badgeStyle,
328
+ ]}
329
+ >
330
+ {readPresentation.image ? (
331
+ <Image
332
+ source={readPresentation.image}
333
+ style={inputStyles.readImage}
334
+ resizeMode="contain"
335
+ />
336
+ ) : null}
337
+ {readPresentationStyles.hasDecoration && readIconName && readPresentationStyles.color ? (
338
+ <Icon
339
+ style={inputStyles.readBadgeIcon}
340
+ name={readIconName}
341
+ size={12}
342
+ color={readPresentationStyles.color}
343
+ />
344
+ ) : null}
345
+ <Text
346
+ style={[
347
+ inputStyles.readText,
348
+ readPresentationStyles.hasDecoration ? inputStyles.readBadgeText : null,
349
+ resolvedLabel === '-' ? inputStyles.mutedText : null,
350
+ readPresentationStyles.color ? { color: readPresentationStyles.color } : null,
351
+ readTextStyle,
352
+ ]}
353
+ numberOfLines={numberOfLines}
354
+ >
355
+ {resolvedLabel || '-'}
356
+ </Text>
357
+ </View>
358
+ )}
359
+ {saving ? (
360
+ <Text style={[inputStyles.savingText, { color: accentColor }]}>Salvando</Text>
361
+ ) : canEdit || isForm ? (
298
362
  <Icon style={inputStyles.editIcon} name="chevron-down" size={14} color="#64748B" />
299
363
  ) : null}
300
364
  </TouchableOpacity>
@@ -322,24 +386,49 @@ const DefaultSelect = ({
322
386
 
323
387
  <ScrollView keyboardShouldPersistTaps="handled">
324
388
  {filteredOptions.length > 0 ? (
325
- filteredOptions.map(option => {
326
- const isSelected = option.key === selectedKey;
327
- return (
328
- <TouchableOpacity
329
- key={`${option.key}_${option.label}`}
389
+ filteredOptions.map(option => {
390
+ const isSelected = option.key === selectedKey;
391
+ const optionColor = normalizeText(option.color);
392
+ const optionIconName = isValidFeatherIcon(option.icon)
393
+ ? option.icon
394
+ : '';
395
+ const shouldRenderOptionFallbackText = Boolean(
396
+ !option.image &&
397
+ !optionIconName &&
398
+ optionColor &&
399
+ normalizeText(option.label),
400
+ );
401
+ return (
402
+ <TouchableOpacity
403
+ key={`${option.key}_${option.label}`}
330
404
  style={styles.optionRow}
331
405
  activeOpacity={0.78}
332
406
  onPress={() => selectOption(option)}
333
407
  >
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>
408
+ {option.image ? (
409
+ <Image
410
+ source={option.image}
411
+ style={inputStyles.readImage}
412
+ resizeMode="contain"
413
+ />
414
+ ) : null}
415
+ {isSelected || (optionIconName && optionColor) ? (
416
+ <Icon
417
+ name={isSelected ? 'check-circle' : optionIconName}
418
+ size={15}
419
+ color={isSelected ? accentColor : optionColor}
420
+ />
421
+ ) : null}
422
+ <Text
423
+ style={[
424
+ styles.optionText,
425
+ optionColor ? { color: optionColor } : null,
426
+ shouldRenderOptionFallbackText ? styles.optionFallbackText : null,
427
+ ]}
428
+ >
429
+ {option.label}
430
+ </Text>
431
+ </TouchableOpacity>
343
432
  );
344
433
  })
345
434
  ) : (
@@ -51,6 +51,9 @@ const styles = StyleSheet.create({
51
51
  fontSize: 13,
52
52
  fontWeight: '700',
53
53
  },
54
+ optionFallbackText: {
55
+ fontWeight: '800',
56
+ },
54
57
  emptyBox: {
55
58
  padding: 18,
56
59
  alignItems: 'center',
@@ -1,9 +1,11 @@
1
- import { getAllStores } from '@store';
2
- import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
3
- import { formatStoreColumnValue } from '@controleonline/ui-common/src/react/utils/storeColumns';
4
-
5
- export const normalizeText = value => String(value ?? '').trim();
6
- const formatHumanLabel = value =>
1
+ import { getAllStores } from '@store';
2
+ import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
3
+ import { formatStoreColumnValue } from '@controleonline/ui-common/src/react/utils/storeColumns';
4
+ import FeatherGlyphMap from 'react-native-vector-icons/glyphmaps/Feather.json';
5
+
6
+ export const normalizeText = value => String(value ?? '').trim();
7
+ export const isValidFeatherIcon = icon => Boolean(FeatherGlyphMap[normalizeText(icon)]);
8
+ const formatHumanLabel = value =>
7
9
  normalizeText(value)
8
10
  .replace(/([a-z])([A-Z])/g, '$1 $2')
9
11
  .replace(/[_-]+/g, ' ')
@@ -128,7 +130,7 @@ export const resolveOptionLabel = (column, option, storeName = '') => {
128
130
  export const resolveStoreNameFromList = list =>
129
131
  typeof list === 'string' ? normalizeText(list).split('/')[0] || '' : '';
130
132
 
131
- const filterItemsByListRequestParams = (items = [], column = null) => {
133
+ const filterItemsByListRequestParams = (items = [], column = null) => {
132
134
  const requestParams =
133
135
  column?.listRequestParams &&
134
136
  typeof column.listRequestParams === 'object' &&
@@ -148,14 +150,17 @@ const filterItemsByListRequestParams = (items = [], column = null) => {
148
150
 
149
151
  return normalizeText(item?.[key]) === normalizeText(expectedValue);
150
152
  }),
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 => {
153
+ );
154
+ };
155
+
156
+ const normalizeObject = value =>
157
+ value && typeof value === 'object' && !Array.isArray(value) ? value : {};
158
+
159
+ export const mapOptions = (column, items = [], storeName = '') => {
160
+ const seenKeys = new Set();
161
+ const options = [];
162
+
163
+ (Array.isArray(items) ? items : []).forEach(item => {
159
164
  const formatted =
160
165
  typeof column?.formatList === 'function'
161
166
  ? column.formatList(item, null, column)
@@ -166,12 +171,31 @@ export const mapOptions = (column, items = [], storeName = '') => {
166
171
  if (dedupeKey && seenKeys.has(dedupeKey)) return;
167
172
  if (dedupeKey) seenKeys.add(dedupeKey);
168
173
 
169
- options.push({
170
- key,
171
- label: resolveOptionLabel(column, item, storeName) || '-',
172
- raw: item,
173
- });
174
- });
174
+ const formattedMeta = normalizeObject(formatted);
175
+ const itemMeta = normalizeObject(item);
176
+ const color = formattedMeta.color ?? itemMeta.color;
177
+ const icon =
178
+ formattedMeta.icon ??
179
+ formattedMeta.iconName ??
180
+ itemMeta.icon ??
181
+ itemMeta.iconName;
182
+ const image =
183
+ formattedMeta.image ??
184
+ formattedMeta.logo ??
185
+ formattedMeta.source ??
186
+ itemMeta.image ??
187
+ itemMeta.logo ??
188
+ itemMeta.source;
189
+
190
+ options.push({
191
+ ...(color ? { color } : {}),
192
+ ...(icon ? { icon } : {}),
193
+ ...(image ? { image } : {}),
194
+ key,
195
+ label: resolveOptionLabel(column, item, storeName) || '-',
196
+ raw: item,
197
+ });
198
+ });
175
199
 
176
200
  return options;
177
201
  };
@@ -197,23 +221,167 @@ export const buildOptionsFromColumn = (column, getOptionsForColumn = null, store
197
221
  );
198
222
  };
199
223
 
200
- export const resolveCellText = ({ column, columns = [], row, storeName, value }) => {
201
- const fieldName = getColumnKey(column);
202
- const rawValue = value ?? row?.[fieldName];
203
- const formattedValue = formatStoreColumnValue({
224
+ export const resolveCellText = ({ column, columns = [], row, storeName, value }) => {
225
+ const fieldName = getColumnKey(column);
226
+ const rawValue = value ?? row?.[fieldName];
227
+ const formattedValue = formatStoreColumnValue({
204
228
  columns: columns.length ? columns : [column],
205
229
  fieldName,
206
230
  row,
207
231
  storeName,
208
232
  value: rawValue,
209
233
  });
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) {
234
+
235
+ return normalizeText(formattedValue) || '-';
236
+ };
237
+
238
+ const resolveFormattedListMeta = (column, rawValue, row) => {
239
+ if (typeof column?.formatList !== 'function') {
240
+ return {};
241
+ }
242
+
243
+ const formatted = column.formatList(rawValue, row, column);
244
+ return normalizeObject(formatted);
245
+ };
246
+
247
+ const resolveFormattedValueMeta = (column, rawValue, row) => {
248
+ if (typeof column?.format !== 'function') {
249
+ return {};
250
+ }
251
+
252
+ const formatted = column.format(rawValue, column, row, false);
253
+ return normalizeObject(formatted);
254
+ };
255
+
256
+ const resolveStyleMeta = (column, row) => {
257
+ if (typeof column?.style !== 'function') {
258
+ return normalizeObject(column?.style);
259
+ }
260
+
261
+ return normalizeObject(column.style(row));
262
+ };
263
+
264
+ export const resolveCellPresentation = ({
265
+ column,
266
+ columns = [],
267
+ row = {},
268
+ storeName = '',
269
+ value,
270
+ } = {}) => {
271
+ const fieldName = getColumnKey(column);
272
+ const rawValue = value ?? row?.[fieldName];
273
+ const rawMeta = normalizeObject(rawValue);
274
+ const formattedValueMeta = resolveFormattedValueMeta(column, rawValue, row);
275
+ const formattedMeta = resolveFormattedListMeta(column, rawValue, row);
276
+ const styleMeta = resolveStyleMeta(column, row);
277
+ const label = resolveCellText({
278
+ column,
279
+ columns,
280
+ row,
281
+ storeName,
282
+ value,
283
+ });
284
+
285
+ return {
286
+ backgroundColor:
287
+ formattedValueMeta.backgroundColor ??
288
+ formattedMeta.backgroundColor ??
289
+ formattedValueMeta.bgColor ??
290
+ formattedMeta.bgColor ??
291
+ rawMeta.backgroundColor ??
292
+ rawMeta.bgColor ??
293
+ styleMeta.backgroundColor ??
294
+ styleMeta.bgColor,
295
+ borderColor:
296
+ formattedValueMeta.borderColor ??
297
+ formattedMeta.borderColor ??
298
+ rawMeta.borderColor ??
299
+ styleMeta.borderColor,
300
+ color:
301
+ formattedValueMeta.color ??
302
+ formattedMeta.color ??
303
+ rawMeta.color ??
304
+ rawMeta.statusColor ??
305
+ rawMeta.categoryColor ??
306
+ styleMeta.color,
307
+ icon:
308
+ formattedValueMeta.icon ??
309
+ formattedValueMeta.iconName ??
310
+ formattedMeta.icon ??
311
+ formattedMeta.iconName ??
312
+ rawMeta.icon ??
313
+ rawMeta.iconName ??
314
+ styleMeta.icon ??
315
+ styleMeta.iconName,
316
+ image:
317
+ formattedValueMeta.image ??
318
+ formattedValueMeta.logo ??
319
+ formattedValueMeta.source ??
320
+ formattedMeta.image ??
321
+ formattedMeta.logo ??
322
+ formattedMeta.source ??
323
+ rawMeta.image ??
324
+ rawMeta.logo ??
325
+ rawMeta.source ??
326
+ styleMeta.image ??
327
+ styleMeta.logo ??
328
+ styleMeta.source,
329
+ label,
330
+ };
331
+ };
332
+
333
+ const resolveAlphaColor = (color, opacity = 0.12) => {
334
+ const normalized = normalizeText(color);
335
+ const hex = normalized.match(/^#?([a-f\d]{6})$/i)?.[1];
336
+
337
+ if (!hex) return normalized || undefined;
338
+
339
+ const red = parseInt(hex.slice(0, 2), 16);
340
+ const green = parseInt(hex.slice(2, 4), 16);
341
+ const blue = parseInt(hex.slice(4, 6), 16);
342
+
343
+ return `rgba(${red}, ${green}, ${blue}, ${opacity})`;
344
+ };
345
+
346
+ export const buildReadPresentationStyles = presentation => {
347
+ const color = normalizeText(presentation?.color);
348
+ const icon = normalizeText(presentation?.icon);
349
+ const hasDecoration = Boolean(
350
+ color ||
351
+ normalizeText(presentation?.backgroundColor) ||
352
+ normalizeText(presentation?.borderColor) ||
353
+ (icon && color),
354
+ );
355
+
356
+ if (!hasDecoration || presentation?.label === '-') {
357
+ return {
358
+ badgeStyle: null,
359
+ color: null,
360
+ hasDecoration: false,
361
+ };
362
+ }
363
+
364
+ const backgroundColor =
365
+ presentation.backgroundColor ||
366
+ (color ? resolveAlphaColor(color, 0.1) : undefined);
367
+ const borderColor =
368
+ presentation.borderColor ||
369
+ presentation.backgroundColor ||
370
+ (color ? resolveAlphaColor(color, 0.32) : undefined);
371
+
372
+ return {
373
+ badgeStyle: {
374
+ backgroundColor,
375
+ borderColor,
376
+ },
377
+ color: color || undefined,
378
+ hasDecoration: true,
379
+ };
380
+ };
381
+
382
+ export const resolveEditValue = (column, row, value) => {
383
+ const rawValue = value ?? row?.[getColumnKey(column)];
384
+ if (column?.list) {
217
385
  return normalizeOptionKey(
218
386
  typeof column?.formatList === 'function'
219
387
  ? column.formatList(rawValue, row, column)
@@ -77,6 +77,7 @@ const DefaultTable = ({
77
77
  renderCard = null,
78
78
  requestParams = {},
79
79
  rowActionsComponent = null,
80
+ rowActionsWidth = null,
80
81
  rowStyle = null,
81
82
  showColumnFiltersButton = true,
82
83
  showRowActions = true,
@@ -96,10 +97,11 @@ const DefaultTable = ({
96
97
  const isFocused = useIsFocused();
97
98
  const configsSignatureRef = useRef('');
98
99
  const { showError } = useMessage() || {};
99
- const { themeColors } = useDefaultTableTheme(accentColor);
100
+ const { tableBorderColors, themeColors } = useDefaultTableTheme(accentColor);
101
+ const tablePanelBorderColor = tableBorderColors.containerBorderColor;
100
102
  const floatingAddBackgroundColor =
101
- themeColors.buttonBackground || themeColors.primary || accentColor || '#2563EB';
102
- const floatingAddIconColor = themeColors.buttonText || '#FFFFFF';
103
+ themeColors.buttonBackground || themeColors.primary || accentColor;
104
+ const floatingAddIconColor = themeColors.buttonText;
103
105
  const tablePreferenceScope = useMemo(
104
106
  () =>
105
107
  resolveDefaultTablePreferenceScope({
@@ -220,6 +222,7 @@ const DefaultTable = ({
220
222
  requestSort,
221
223
  resolvedSort,
222
224
  rowActionsComponent,
225
+ rowActionsWidth,
223
226
  rowStyle,
224
227
  showColumnFiltersButton,
225
228
  showRowActions,
@@ -259,6 +262,7 @@ const DefaultTable = ({
259
262
  requestSort,
260
263
  resolvedSort,
261
264
  rowActionsComponent,
265
+ rowActionsWidth,
262
266
  rowStyle,
263
267
  showColumnFiltersButton,
264
268
  showRowActions,
@@ -278,12 +282,13 @@ const DefaultTable = ({
278
282
  stableSerialize({
279
283
  add,
280
284
  compactBreakpoint,
281
- debugFallbackParameters,
282
285
  effectiveViewMode,
283
286
  filters,
284
287
  forceCardsOnCompact,
285
288
  initialViewMode,
286
289
  pinRowActions,
290
+ rowActionsComponentType: rowActionsComponent ? typeof rowActionsComponent : '',
291
+ rowActionsWidth,
287
292
  resolvedSort,
288
293
  showColumnFiltersButton,
289
294
  showRowActions,
@@ -304,6 +309,8 @@ const DefaultTable = ({
304
309
  forceCardsOnCompact,
305
310
  initialViewMode,
306
311
  pinRowActions,
312
+ rowActionsComponent,
313
+ rowActionsWidth,
307
314
  resolvedSort,
308
315
  showColumnFiltersButton,
309
316
  showRowActions,
@@ -400,7 +407,8 @@ const DefaultTable = ({
400
407
  style={[
401
408
  styles.wrap,
402
409
  {
403
- borderColor: themeColors.panelBorder,
410
+ borderWidth: tablePanelBorderColor ? 1 : 0,
411
+ borderColor: tablePanelBorderColor,
404
412
  backgroundColor: themeColors.panelBackground,
405
413
  },
406
414
  ]}