@controleonline/ui-default 1.0.283 → 1.0.285

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.283",
3
+ "version": "1.0.285",
4
4
  "author": "ControleOnline",
5
5
  "description": "ControleOnline Default Quasar UI Component",
6
6
  "license": "MIT",
@@ -143,6 +143,9 @@ const DefaultTable = ({
143
143
  };
144
144
  const storeSelectable = storeDeclaredConfigs.selectable === true;
145
145
  const isCompactView = width > 0 && width <= compactBreakpoint;
146
+ // Mobile: no page buttons — infinite scroll only (desktop keeps pages mode when requested).
147
+ const resolvedPaginationMode =
148
+ width > 0 && width <= 768 && paginationMode === 'pages' ? 'infinite' : paginationMode;
146
149
  const addConfig = store?.getters?.add;
147
150
  const normalizedAddButtonPlacement = normalizeText(addButtonPlacement) || 'toolbar';
148
151
  const resolvedAddLabel =
@@ -184,7 +187,7 @@ const DefaultTable = ({
184
187
  onEndReached,
185
188
  onRefresh,
186
189
  pageSize,
187
- paginationMode,
190
+ paginationMode: resolvedPaginationMode,
188
191
  requestParams: requestParamsSeed,
189
192
  resolvedActions,
190
193
  resolvedSort,
@@ -235,7 +238,7 @@ const DefaultTable = ({
235
238
  () => ({
236
239
  ...storeDeclaredConfigs,
237
240
  appearance,
238
- paginationMode,
241
+ paginationMode: resolvedPaginationMode,
239
242
  currentPage,
240
243
  pageSizeNumber,
241
244
  onPageChange: goToPage,
@@ -310,7 +313,7 @@ const DefaultTable = ({
310
313
  viewMode: viewMode ? storeDeclaredConfigs.viewMode : effectiveViewMode,
311
314
  }),
312
315
  [
313
- appearance, paginationMode, currentPage, pageSizeNumber, goToPage, resolvedIsLoading,
316
+ appearance, resolvedPaginationMode, currentPage, pageSizeNumber, goToPage, resolvedIsLoading,
314
317
  add,
315
318
  normalizedAddButtonPlacement,
316
319
  resolvedAddLabel,
@@ -376,7 +379,7 @@ const DefaultTable = ({
376
379
  const defaultTableConfigsSignature = useMemo(
377
380
  () =>
378
381
  stableSerialize({
379
- appearance, paginationMode, currentPage, pageSizeNumber, resolvedIsLoading,
382
+ appearance, resolvedPaginationMode, currentPage, pageSizeNumber, resolvedIsLoading,
380
383
  configuredImport: storeDeclaredConfigs.import || null,
381
384
  add,
382
385
  addButtonPlacement: normalizedAddButtonPlacement,
@@ -415,7 +418,7 @@ const DefaultTable = ({
415
418
  toolbarActionsLength: Array.isArray(toolbarActions) ? toolbarActions.length : 0,
416
419
  }),
417
420
  [
418
- appearance, paginationMode, currentPage, pageSizeNumber, goToPage, resolvedIsLoading,
421
+ appearance, resolvedPaginationMode, currentPage, pageSizeNumber, goToPage, resolvedIsLoading,
419
422
  add,
420
423
  normalizedAddButtonPlacement,
421
424
  resolvedAddLabel,
@@ -46,7 +46,7 @@ pinnedIdentityCell: {
46
46
  zIndex: 6,
47
47
  ...Platform.select({
48
48
  web: {
49
- boxShadow: '2px 0 0 rgba(226,232,240,0.9)',
49
+ boxShadow: '2px 0 6px rgba(15,23,42,0.08)',
50
50
  position: 'relative',
51
51
  },
52
52
  default: {},
@@ -57,7 +57,7 @@ pinnedActionsCell: {
57
57
  zIndex: 6,
58
58
  ...Platform.select({
59
59
  web: {
60
- boxShadow: '-2px 0 0 rgba(226,232,240,0.9)',
60
+ boxShadow: '-2px 0 6px rgba(15,23,42,0.08)',
61
61
  position: 'relative',
62
62
  },
63
63
  default: {},
@@ -276,9 +276,9 @@ emptyBox: {
276
276
  gap: 8,
277
277
  },
278
278
  footerBar: {
279
- minHeight: 40,
279
+ minHeight: 34,
280
280
  paddingHorizontal: 8,
281
- paddingVertical: 5,
281
+ paddingVertical: 3,
282
282
  borderTopWidth: 1,
283
283
  borderTopColor: '#E2E8F0',
284
284
  backgroundColor: '#FFFFFF',
@@ -311,13 +311,13 @@ footerSummaryItem: {
311
311
  },
312
312
  footerSummaryLabel: {
313
313
  color: '#64748B',
314
- fontSize: 9,
314
+ fontSize: 8,
315
315
  fontWeight: '900',
316
316
  textTransform: 'uppercase',
317
317
  },
318
318
  footerSummaryValue: {
319
319
  color: '#0F172A',
320
- fontSize: 13,
320
+ fontSize: 12,
321
321
  fontWeight: '900',
322
322
  },
323
323
  footerCountPill: {
@@ -329,7 +329,7 @@ footerCountPill: {
329
329
  paddingVertical: 5,
330
330
  },
331
331
  footerCountText: {
332
- fontSize: 11,
332
+ fontSize: 10,
333
333
  fontWeight: '900',
334
334
  },
335
335
  emptyText: {
@@ -9,7 +9,7 @@ export default StyleSheet.create({
9
9
  inputMobile: {fontSize: 16},
10
10
  button: {minHeight: 44, minWidth: 44, paddingHorizontal: 10, borderWidth: 1, borderRadius: 8, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6},
11
11
  label: {fontSize: 12, fontWeight: '600'},
12
- small: {fontSize: 12},
12
+ small: {fontSize: 11},
13
13
  chips: {flexDirection: 'row', flexWrap: 'wrap', gap: 6, alignItems: 'center'},
14
14
  chip: {minHeight: 30, maxWidth: '100%', borderWidth: 1, borderRadius: 18, paddingHorizontal: 9, flexDirection: 'row', alignItems: 'center', gap: 6},
15
15
  secondary: {flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8},
@@ -19,9 +19,9 @@ export default StyleSheet.create({
19
19
  sheetHeader: {flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10},
20
20
  sheetTitle: {fontSize: 17, fontWeight: '600'},
21
21
  option: {minHeight: 44, paddingVertical: 10, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8},
22
- footer: {padding: 10, flexShrink: 0, borderTopWidth: 1, flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', gap: 8},
23
- pager: {flexDirection: 'row', flexWrap: 'wrap', gap: 4, alignItems: 'center'},
24
- page: {minWidth: 44, minHeight: 44, borderWidth: 1, borderRadius: 7, justifyContent: 'center', alignItems: 'center'},
22
+ footer: {paddingVertical: 4, paddingHorizontal: 8, flexShrink: 0, borderTopWidth: 1, flexDirection: 'row', flexWrap: 'nowrap', alignItems: 'center', justifyContent: 'space-between', gap: 6, minHeight: 36},
23
+ pager: {flexDirection: 'row', flexWrap: 'nowrap', gap: 3, alignItems: 'center', flexShrink: 0},
24
+ page: {minWidth: 28, minHeight: 28, paddingHorizontal: 4, borderWidth: 1, borderRadius: 6, justifyContent: 'center', alignItems: 'center'},
25
25
  disabled: {opacity: 0.45},
26
26
  cardItem: {flexDirection: 'column', alignItems: 'stretch', borderWidth: 1, borderRadius: 12, overflow: 'hidden', margin: 0},
27
27
  cardActions: {alignSelf: 'stretch', flexDirection: 'row', justifyContent: 'flex-end', borderTopWidth: 1, paddingHorizontal: 12, paddingVertical: 8},
@@ -12,6 +12,7 @@ import {
12
12
  } from './DefaultTable.utils';
13
13
  import styles from './DefaultTable.styles';
14
14
  import useDefaultTableTheme from './useDefaultTableTheme';
15
+ import DefaultTablePaginationBar from './DefaultTablePaginationBar';
15
16
 
16
17
  const DefaultTableFooter = ({ storeName }) => {
17
18
  const store = useStore(storeName);
@@ -119,10 +120,16 @@ const DefaultTableFooter = ({ storeName }) => {
119
120
  : React.createElement(footerComponent, footerProps);
120
121
  }
121
122
 
122
- if (!shouldRenderFooterBar) {
123
+ // Keep a single footer row even when only pagination (pages mode, desktop) is present.
124
+ // PaginationBar returns null on mobile / infinite mode.
125
+ if (!shouldRenderFooterBar && configs.paginationMode !== 'pages') {
123
126
  return null;
124
127
  }
125
128
 
129
+ const hasSummaryOrCount =
130
+ (shouldRenderFooterTotalItems && !shouldRenderCompactToolbarTotalItems) ||
131
+ summaryEntries.length > 0;
132
+
126
133
  return (
127
134
  <View
128
135
  style={[
@@ -174,6 +181,7 @@ const DefaultTableFooter = ({ storeName }) => {
174
181
  </Text>
175
182
  </View>
176
183
  ) : null}
184
+ <DefaultTablePaginationBar storeName={storeName} embedded />
177
185
  </View>
178
186
  );
179
187
  };
@@ -6,12 +6,16 @@ import useDefaultTableTheme from './useDefaultTableTheme';
6
6
  import {getVisiblePageNumbers} from './DefaultTableCompact.helpers';
7
7
  import s from './DefaultTableCompact.styles';
8
8
 
9
- export default function DefaultTablePaginationBar({storeName}) {
9
+ /**
10
+ * Desktop/tablet only. Mobile keeps infinite scroll (no page buttons).
11
+ * Renders as a compact trailing segment for the unified footer row.
12
+ */
13
+ export default function DefaultTablePaginationBar({storeName, embedded = false}) {
10
14
  const mobile = useWindowDimensions().width <= 768;
11
15
  const store = useStore(storeName);
12
16
  const configs = store?.getters?.configs || {};
13
17
  const {themeColors: c} = useDefaultTableTheme();
14
- if (configs.paginationMode !== 'pages') return null;
18
+ if (mobile || configs.paginationMode !== 'pages') return null;
15
19
  const page = configs.currentPage || 1;
16
20
  const size = configs.pageSizeNumber || 20;
17
21
  const total = Number(store.getters.totalItems || 0);
@@ -20,19 +24,56 @@ export default function DefaultTablePaginationBar({storeName}) {
20
24
  const text = c.tableFooterText || c.textPrimary;
21
25
  const background = c.tableFooterBackground || c.panelBackground;
22
26
  const border = c.tableFooterBorder || c.inputBorder;
23
- const button = (value, icon, label, disabled = false) => <TouchableOpacity key={label} accessibilityRole="button" accessibilityLabel={label}
24
- accessibilityState={{disabled: disabled || busy, selected: !icon && page === value}} disabled={disabled || busy}
25
- onPress={() => configs.onPageChange?.(value)} style={[s.page, {backgroundColor: !icon && page === value ? c.chipSelectedBackground || c.badgeBackground : background, borderColor: border}, (disabled || busy) && s.disabled]}>
26
- {icon ? <Icon name={icon} size={16} color={text} /> : <Text style={[s.small, {color: text}]}>{value}</Text>}
27
- </TouchableOpacity>;
28
- return <View testID="default-table-pagination" style={[s.footer, {backgroundColor: background, borderTopColor: border}]}>
29
- <Text accessibilityLiveRegion="polite" style={[s.small, {color: text}]}>{configs.showTotalItemsInFooter === false ? `Página ${page}` : `${total ? (page - 1) * size + 1 : 0}–${Math.min(page * size, total)} de ${total}`}</Text>
30
- <View style={s.pager}>
31
- {!mobile ? button(1, 'chevrons-left', 'Primeira página', page === 1) : null}
32
- {button(page - 1, 'chevron-left', 'Página anterior', page === 1)}
33
- {getVisiblePageNumbers(page, pages).map(value => button(value, null, `Página ${value}`))}
34
- {button(page + 1, 'chevron-right', 'Próxima página', page >= pages)}
35
- {!mobile ? button(pages, 'chevrons-right', 'Última página', page >= pages) : null}
27
+ const button = (value, icon, label, disabled = false) => (
28
+ <TouchableOpacity
29
+ key={label}
30
+ accessibilityRole="button"
31
+ accessibilityLabel={label}
32
+ accessibilityState={{disabled: disabled || busy, selected: !icon && page === value}}
33
+ disabled={disabled || busy}
34
+ onPress={() => configs.onPageChange?.(value)}
35
+ style={[
36
+ s.page,
37
+ {
38
+ backgroundColor: !icon && page === value ? c.chipSelectedBackground || c.badgeBackground : background,
39
+ borderColor: border,
40
+ },
41
+ (disabled || busy) && s.disabled,
42
+ ]}>
43
+ {icon ? <Icon name={icon} size={13} color={text} /> : <Text style={[s.small, {color: text, fontSize: 11}]}>{value}</Text>}
44
+ </TouchableOpacity>
45
+ );
46
+ const t = global.t?.t;
47
+ const pageWord = t?.(storeName, 'label', 'page') || t?.('configs', 'label', 'page') || 'Página';
48
+ const ofWord = t?.(storeName, 'label', 'of') || t?.('configs', 'label', 'of') || 'de';
49
+ const rangeLabel =
50
+ configs.showTotalItemsInFooter === false
51
+ ? `${pageWord} ${page}`
52
+ : `${total ? (page - 1) * size + 1 : 0}–${Math.min(page * size, total)} ${ofWord} ${total}`;
53
+ const body = (
54
+ <>
55
+ <Text accessibilityLiveRegion="polite" style={[s.small, {color: text, fontSize: 11, flexShrink: 0}]}>
56
+ {rangeLabel}
57
+ </Text>
58
+ <View style={s.pager}>
59
+ {button(1, 'chevrons-left', 'Primeira página', page === 1)}
60
+ {button(page - 1, 'chevron-left', 'Página anterior', page === 1)}
61
+ {getVisiblePageNumbers(page, pages).map(value => button(value, null, `Página ${value}`))}
62
+ {button(page + 1, 'chevron-right', 'Próxima página', page >= pages)}
63
+ {button(pages, 'chevrons-right', 'Última página', page >= pages)}
64
+ </View>
65
+ </>
66
+ );
67
+ if (embedded) {
68
+ return (
69
+ <View testID="default-table-pagination" style={{flexDirection: 'row', alignItems: 'center', gap: 8, flexShrink: 0}}>
70
+ {body}
71
+ </View>
72
+ );
73
+ }
74
+ return (
75
+ <View testID="default-table-pagination" style={[s.footer, {backgroundColor: background, borderTopColor: border}]}>
76
+ {body}
36
77
  </View>
37
- </View>;
78
+ );
38
79
  }
@@ -27,6 +27,14 @@ import {getCompactColumnLabel} from './DefaultTableCompact.helpers';
27
27
  import useDefaultTableRowInteraction from './useDefaultTableRowInteraction';
28
28
  import useDefaultTableTheme from './useDefaultTableTheme';
29
29
 
30
+
31
+ const isIdentityColumn = column => {
32
+ if (!column) return false;
33
+ if (column.isIdentity === true) return true;
34
+ const key = String(getColumnKey(column) || '').toLowerCase();
35
+ return key === 'id' || key === 'uuid';
36
+ };
37
+
30
38
  /** Row stripes: themes-map listItemEvenRow / listItemOddRow — never status.color. */
31
39
  export const resolveRowBackgroundColor = ({ index, tableOddColor, tableEvenColor }) => {
32
40
  return index % 2 === 0 ? tableOddColor : tableEvenColor;
@@ -96,16 +104,22 @@ const DefaultTableRows = ({ storeName }) => {
96
104
  : next;
97
105
  });
98
106
  };
107
+ // Body cells live inside FlatList (own overflow). CSS position:sticky breaks there
108
+ // relative to the outer horizontal ScrollView — pin with translateX instead.
99
109
  const stickyBodyTransforms = useMemo(() => {
100
110
  const viewportWidth = Number(horizontalMetrics.viewportWidth || 0);
101
111
  const contentWidth = Number(horizontalMetrics.contentWidth || 0);
102
112
  const x = Number(horizontalMetrics.x || 0);
103
- const canScrollHorizontally = viewportWidth > 0 && contentWidth > viewportWidth;
104
- const actionsTranslateX = canScrollHorizontally
105
- ? x + viewportWidth - contentWidth
106
- : 0;
107
-
113
+ const canScrollHorizontally = viewportWidth > 0 && contentWidth > viewportWidth + 0.5;
114
+ if (!canScrollHorizontally) {
115
+ return { identity: null, actions: null };
116
+ }
117
+ // Identity stays at the left edge of the viewport.
118
+ const identityTranslateX = x;
119
+ // Actions stay at the right edge of the viewport.
120
+ const actionsTranslateX = x + viewportWidth - contentWidth;
108
121
  return {
122
+ identity: identityTranslateX !== 0 ? [{ translateX: identityTranslateX }] : null,
109
123
  actions:
110
124
  shouldPinRowActions && actionsTranslateX !== 0
111
125
  ? [{ translateX: actionsTranslateX }]
@@ -174,12 +188,15 @@ const DefaultTableRows = ({ storeName }) => {
174
188
  <React.Fragment key={getColumnKey(column)}>
175
189
  <DefaultTableInput
176
190
  column={column}
177
- options={{...(column?.isIdentity ? {
191
+ options={{...(isIdentityColumn(column) ? {
178
192
  cellStyle: [
179
193
  styles.pinnedIdentityCell,
180
- styles.stickyIdentityCell,
181
194
  {
182
195
  backgroundColor: rowBackgroundColor,
196
+ zIndex: 6,
197
+ ...(stickyBodyTransforms.identity
198
+ ? { transform: stickyBodyTransforms.identity }
199
+ : {}),
183
200
  },
184
201
  ],
185
202
  } : {
@@ -200,12 +217,12 @@ const DefaultTableRows = ({ storeName }) => {
200
217
  styles.cell,
201
218
  styles.actionsCell,
202
219
  shouldPinRowActions ? styles.pinnedActionsCell : null,
203
- shouldPinRowActions ? styles.stickyActionsCell : null,
204
220
  {
205
221
  minWidth: actionsCellWidth,
206
222
  width: actionsCellWidth,
207
223
  flexBasis: actionsCellWidth,
208
224
  maxWidth: actionsCellWidth,
225
+ zIndex: shouldPinRowActions ? 6 : undefined,
209
226
  ...(stickyBodyTransforms.actions
210
227
  ? { transform: stickyBodyTransforms.actions }
211
228
  : {}),
@@ -286,7 +303,7 @@ const DefaultTableRows = ({ storeName }) => {
286
303
  accessibilityHint={configs.resolvedSort?.field === sortFieldName ? `Ordem ${configs.resolvedSort.direction === 'asc' ? 'crescente' : 'decrescente'}` : undefined}
287
304
  style={[
288
305
  getColumnStyle(column),
289
- column?.isIdentity
306
+ isIdentityColumn(column)
290
307
  ? [styles.stickyIdentityCell, styles.stickyHeaderCell, { backgroundColor: tableHeaderColor }]
291
308
  : null,
292
309
  ]}
@@ -347,7 +364,7 @@ const DefaultTableRows = ({ storeName }) => {
347
364
  key={`${fieldName}-filter`}
348
365
  style={[
349
366
  getColumnStyle(column),
350
- column?.isIdentity
367
+ isIdentityColumn(column)
351
368
  ? [styles.stickyIdentityCell, { backgroundColor: tableHeaderColor }]
352
369
  : null,
353
370
  ]}
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Modal, Text, TouchableOpacity, View } from 'react-native';
3
3
  import Icon from 'react-native-vector-icons/Feather';
4
- import DefaultTablePaginationBar from './DefaultTablePaginationBar';
5
4
  import DefaultTableBody from './DefaultTableBody';
6
5
  import DefaultTableFooter from './DefaultTableFooter';
7
6
  import DefaultTableToolbar from './DefaultTableToolbar';
@@ -50,7 +49,6 @@ export default function DefaultTableView({
50
49
  ) : null}
51
50
  <DefaultTableBody storeName={storeName} />
52
51
  <DefaultTableFooter storeName={storeName} />
53
- <DefaultTablePaginationBar storeName={storeName} />
54
52
  {shouldRenderBottomAddButton ? (
55
53
  <View style={styles.bottomAddBar}>
56
54
  <TouchableOpacity
@@ -130,9 +130,9 @@ export const defaultUploadStyles = {
130
130
  attachmentsEmpty: {borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 10},
131
131
  attachmentsEmptyText: {color: '#666'},
132
132
  attachmentsList: {flexDirection: 'row', gap: 10},
133
- attachmentCard: {width: 240, borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 0, overflow: 'hidden', backgroundColor: '#fff'},
134
- attachmentThumb: {width: 240, height: 240, backgroundColor: '#f5f5f5', overflow: 'hidden', alignItems: 'center', justifyContent: 'center'},
135
- attachmentImage: {width: 240, height: 240},
133
+ attachmentCard: {width: 320, borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 0, overflow: 'hidden', backgroundColor: '#fff'},
134
+ attachmentThumb: {width: 320, height: 320, backgroundColor: '#f5f5f5', overflow: 'hidden', alignItems: 'center', justifyContent: 'center'},
135
+ attachmentImage: {width: 320, height: 320},
136
136
  attachmentActionsRow: {flexDirection: 'row', justifyContent: 'center', alignItems: 'center', gap: 8, marginTop: 2},
137
137
  attachmentIconButton: {width: 36, height: 36, borderRadius: 8, alignItems: 'center', justifyContent: 'center'},
138
138
  attachmentRemoveButton: {backgroundColor: '#b00020', paddingVertical: 6, borderRadius: 4},
@@ -76,20 +76,20 @@ export default function DefaultUploadAttachmentsList({
76
76
  key={row.id || fileId || index}
77
77
  style={[
78
78
  styles.attachmentCard,
79
- // app-community#813: card principal 240x240 (app-community#1275)
80
- {width: 240, padding: 0, overflow: 'hidden'},
79
+ // app-community#813: card principal 320x320
80
+ {width: 320, padding: 0, overflow: 'hidden'},
81
81
  ]}>
82
82
  <View
83
83
  style={[
84
84
  styles.attachmentThumb,
85
- {width: 240, height: 240, marginBottom: 0},
85
+ {width: 320, height: 320, marginBottom: 0},
86
86
  ]}>
87
87
  {file ? (
88
88
  <DefaultFile
89
89
  file={file}
90
90
  company={company}
91
91
  resizeMode="cover"
92
- style={{width: 240, height: 240}}
92
+ style={{width: 320, height: 320}}
93
93
  />
94
94
  ) : null}
95
95
  </View>