@controleonline/ui-default 1.0.263 → 1.0.265

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 (61) hide show
  1. package/AGENTS.md +47 -45
  2. package/package.json +20 -20
  3. package/src/react/components/errors/DefaultErrors.js +307 -325
  4. package/src/react/components/files/DefaultFile.js +46 -46
  5. package/src/react/components/filters/CompactFilterSelector.js +78 -24
  6. package/src/react/components/filters/CompactFilterSelector.styles.js +17 -5
  7. package/src/react/components/filters/DefaultColumnFilter.js +191 -57
  8. package/src/react/components/filters/DefaultExternalFilters.js +245 -182
  9. package/src/react/components/filters/DefaultSearch.js +131 -92
  10. package/src/react/components/filters/DefaultSearch.styles.js +65 -70
  11. package/src/react/components/form/DefaultForm.js +172 -151
  12. package/src/react/components/form/DefaultForm.styles.js +71 -66
  13. package/src/react/components/help/DefaultTooltip.js +87 -87
  14. package/src/react/components/help/DefaultTooltip.styles.js +61 -61
  15. package/src/react/components/inputs/DefaultDateInput.js +150 -0
  16. package/src/react/components/inputs/DefaultInput.js +162 -127
  17. package/src/react/components/inputs/DefaultInput.styles.js +6 -0
  18. package/src/react/components/inputs/DefaultSelect.js +320 -152
  19. package/src/react/components/inputs/defaultInputUtils.js +40 -19
  20. package/src/react/components/map/DefaultGoogleMap.styles.js +115 -9
  21. package/src/react/components/map/DefaultGoogleMap.web.js +560 -446
  22. package/src/react/components/map/DefaultMap.native.js +73 -71
  23. package/src/react/components/map/DefaultMap.styles.js +16 -16
  24. package/src/react/components/map/DefaultMap.web.js +68 -66
  25. package/src/react/components/map/DefaultNativeMap.native.js +307 -183
  26. package/src/react/components/map/DefaultNativeMap.shared.js +439 -340
  27. package/src/react/components/map/DefaultNativeMap.styles.js +156 -122
  28. package/src/react/components/table/DefaultColumnMenu.js +108 -0
  29. package/src/react/components/table/DefaultDebug.js +233 -0
  30. package/src/react/components/table/DefaultFiltersModal.js +142 -0
  31. package/src/react/components/table/DefaultModalButton.js +18 -0
  32. package/src/react/components/table/DefaultSearchModal.js +49 -0
  33. package/src/react/components/table/DefaultTable.js +345 -1846
  34. package/src/react/components/table/DefaultTable.styles.js +247 -136
  35. package/src/react/components/table/DefaultTable.utils.js +502 -0
  36. package/src/react/components/table/DefaultTableBody.js +26 -0
  37. package/src/react/components/table/DefaultTableCards.js +171 -0
  38. package/src/react/components/table/DefaultTableControls.js +208 -0
  39. package/src/react/components/table/DefaultTableEmptyState.js +22 -0
  40. package/src/react/components/table/DefaultTableFooter.js +172 -0
  41. package/src/react/components/table/DefaultTableInput.js +145 -0
  42. package/src/react/components/table/DefaultTableRows.js +340 -0
  43. package/src/react/components/table/DefaultTableSearch.js +100 -0
  44. package/src/react/components/table/DefaultTableToolbar.js +98 -0
  45. package/src/react/components/table/DefaultToolbarAction.js +56 -0
  46. package/src/react/components/table/useDefaultTablePagination.js +370 -0
  47. package/src/react/components/table/useDefaultTableSorting.js +126 -0
  48. package/src/react/components/table/useDefaultTableTheme.js +58 -0
  49. package/src/react/index.js +10 -10
  50. package/src/react/utils/tableVisibleColumnsPreferences.js +239 -211
  51. package/src/store/default/actions.js +51 -13
  52. package/src/store/default/getters.js +10 -8
  53. package/src/store/default/mutation_types.js +4 -2
  54. package/src/store/default/mutations.js +10 -1
  55. package/src/tests/react/components/errors/DefaultErrors.test.js +162 -162
  56. package/src/tests/react/components/files/DefaultFile.test.js +80 -80
  57. package/src/tests/react/components/form/DefaultForm.test.js +81 -0
  58. package/src/tests/react/components/inputs/DefaultSelect.test.js +185 -0
  59. package/src/tests/react/components/table/DefaultTable.test.js +1183 -0
  60. package/src/tests/react/store/defaultActions.test.js +59 -0
  61. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +247 -168
@@ -0,0 +1,340 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { FlatList, ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { useStore } from '@store';
5
+ import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
6
+ import { getColumnKey } from '../inputs/defaultInputUtils';
7
+ import DefaultColumnFilter from '../filters/DefaultColumnFilter';
8
+ import DefaultTableEmptyState from './DefaultTableEmptyState';
9
+ import DefaultTableInput from './DefaultTableInput';
10
+ import {
11
+ END_REACHED_THRESHOLD,
12
+ getColumnStyle,
13
+ getRowKey,
14
+ getSortField,
15
+ isSortableColumn,
16
+ mergeSortedDataWithLiveItems,
17
+ shouldTriggerEndReachedFromScroll,
18
+ shouldIncludeColumn,
19
+ } from './DefaultTable.utils';
20
+ import styles from './DefaultTable.styles';
21
+ import useDefaultTableTheme from './useDefaultTableTheme';
22
+
23
+ const DefaultTableRows = ({ storeName }) => {
24
+ const store = useStore(storeName);
25
+ const configs = store?.getters?.configs || {};
26
+ const { palette, resolvedAccentColor, themeTokens } = useDefaultTableTheme();
27
+ const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
28
+ const visibleColumns = store?.getters?.visibleColumns || {};
29
+ const tableColumns = columns.filter(
30
+ column => shouldIncludeColumn(column) && visibleColumns[getColumnKey(column)] !== false,
31
+ );
32
+ const sortedData = mergeSortedDataWithLiveItems({
33
+ liveItems: store?.getters?.items,
34
+ sortedData: configs.sortedData,
35
+ });
36
+ const resolvedFilters = store?.getters?.filters || {};
37
+ const isLoading = Boolean(store?.getters?.isLoadingList || store?.getters?.isLoading);
38
+ const emptyStateLabel = isLoading
39
+ ? global.t?.t(storeName, 'label', 'loading')
40
+ : global.t?.t(storeName, 'label', 'empty');
41
+ const tableBorderColor = palette.border;
42
+ const tableEvenColor = themeTokens.listItemEvenRow || themeTokens['bg-even-light'] || palette.background;
43
+ const tableHeaderColor = themeTokens['bg-headers-light'] || resolvedAccentColor;
44
+ const tableMutedColor = palette.textSecondary;
45
+ const tableOddColor = themeTokens.listItemOddRow || themeTokens['bg-odd-light'] || palette.background;
46
+ const tableSurfaceColor = palette.background;
47
+ const tableTextColor = palette.text;
48
+ const rowStyle = configs.rowStyle;
49
+ const hasRowPress = typeof configs.onRowPress === 'function';
50
+ const hasCustomRowActions = typeof configs.rowActionsComponent === 'function';
51
+ const hasEditAction = typeof configs.onEditRow === 'function';
52
+ const hasRowActions = configs.showRowActions !== false && (hasCustomRowActions || hasEditAction);
53
+ const hasColumnFilters =
54
+ configs.showColumnFiltersButton !== false &&
55
+ configs.tableFiltersVisible === true;
56
+ const actionsCellWidth = hasRowActions ? 96 : 0;
57
+ const [horizontalMetrics, setHorizontalMetrics] = useState({
58
+ contentWidth: 0,
59
+ viewportWidth: 0,
60
+ x: 0,
61
+ });
62
+ const updateHorizontalMetrics = patch => {
63
+ setHorizontalMetrics(current => {
64
+ const next = {
65
+ ...current,
66
+ ...patch,
67
+ };
68
+
69
+ return next.contentWidth === current.contentWidth &&
70
+ next.viewportWidth === current.viewportWidth &&
71
+ next.x === current.x
72
+ ? current
73
+ : next;
74
+ });
75
+ };
76
+ const stickyBodyTransforms = useMemo(() => {
77
+ const viewportWidth = Number(horizontalMetrics.viewportWidth || 0);
78
+ const contentWidth = Number(horizontalMetrics.contentWidth || 0);
79
+ const x = Number(horizontalMetrics.x || 0);
80
+ const canScrollHorizontally = viewportWidth > 0 && contentWidth > viewportWidth;
81
+ const actionsTranslateX = canScrollHorizontally
82
+ ? x + viewportWidth - contentWidth
83
+ : 0;
84
+
85
+ return {
86
+ actions: actionsTranslateX !== 0 ? [{ translateX: actionsTranslateX }] : null,
87
+ identity: x !== 0 ? [{ translateX: x }] : null,
88
+ };
89
+ }, [horizontalMetrics]);
90
+ const handleHorizontalScroll = event => {
91
+ updateHorizontalMetrics({
92
+ x: Number(event?.nativeEvent?.contentOffset?.x || 0),
93
+ });
94
+ };
95
+ const handleHorizontalLayout = event => {
96
+ updateHorizontalMetrics({
97
+ viewportWidth: Number(event?.nativeEvent?.layout?.width || 0),
98
+ });
99
+ };
100
+ const handleHorizontalContentSizeChange = width => {
101
+ updateHorizontalMetrics({
102
+ contentWidth: Number(width || 0),
103
+ });
104
+ };
105
+ const handleListScroll = event => {
106
+ if (shouldTriggerEndReachedFromScroll(event)) {
107
+ configs.onEndReached?.();
108
+ }
109
+ };
110
+
111
+ const renderTableItem = ({ item: row, index }) => {
112
+ const RowComponent = hasRowPress ? TouchableOpacity : View;
113
+ const rowStyleValue = typeof rowStyle === 'function' ? rowStyle(row, index) : rowStyle;
114
+ const rowPressProps = hasRowPress
115
+ ? {
116
+ activeOpacity: 0.84,
117
+ onPress: () => configs.onRowPress(row),
118
+ }
119
+ : {};
120
+ const rowBackgroundColor = index % 2 === 0 ? tableOddColor : tableEvenColor;
121
+ const RowActionsComponent = configs.rowActionsComponent;
122
+
123
+ return (
124
+ <RowComponent
125
+ key={getRowKey(row)}
126
+ style={[
127
+ styles.row,
128
+ { backgroundColor: rowBackgroundColor, borderBottomColor: tableBorderColor },
129
+ rowStyleValue,
130
+ ]}
131
+ {...rowPressProps}
132
+ >
133
+ {tableColumns.map(column => (
134
+ <React.Fragment key={getColumnKey(column)}>
135
+ <DefaultTableInput
136
+ column={column}
137
+ options={column?.isIdentity ? {
138
+ cellStyle: [
139
+ styles.pinnedIdentityCell,
140
+ {
141
+ backgroundColor: rowBackgroundColor,
142
+ ...(stickyBodyTransforms.identity
143
+ ? { transform: stickyBodyTransforms.identity }
144
+ : {}),
145
+ },
146
+ ],
147
+ } : {}}
148
+ row={row}
149
+ storeName={storeName}
150
+ variant="cell"
151
+ />
152
+ </React.Fragment>
153
+ ))}
154
+ {hasRowActions ? (
155
+ <View
156
+ style={[
157
+ styles.cell,
158
+ styles.actionsCell,
159
+ styles.pinnedActionsCell,
160
+ {
161
+ minWidth: actionsCellWidth,
162
+ width: actionsCellWidth,
163
+ flexBasis: actionsCellWidth,
164
+ maxWidth: actionsCellWidth,
165
+ backgroundColor: rowBackgroundColor,
166
+ ...(stickyBodyTransforms.actions
167
+ ? { transform: stickyBodyTransforms.actions }
168
+ : {}),
169
+ },
170
+ ]}
171
+ >
172
+ <View style={styles.rowActionsGroup}>
173
+ {hasCustomRowActions ? (
174
+ <RowActionsComponent
175
+ openEdit={() => configs.onEditRow?.(row)}
176
+ openRow={hasRowPress ? () => configs.onRowPress(row) : null}
177
+ row={row}
178
+ />
179
+ ) : null}
180
+ {hasEditAction ? (
181
+ <TouchableOpacity
182
+ style={[styles.iconButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
183
+ activeOpacity={0.82}
184
+ onPress={() => configs.onEditRow?.(row)}
185
+ >
186
+ <Icon name="edit-2" size={14} color={tableMutedColor} />
187
+ </TouchableOpacity>
188
+ ) : null}
189
+ </View>
190
+ </View>
191
+ ) : null}
192
+ </RowComponent>
193
+ );
194
+ };
195
+
196
+ return (
197
+ <>
198
+ <ScrollView
199
+ horizontal
200
+ onContentSizeChange={handleHorizontalContentSizeChange}
201
+ onLayout={handleHorizontalLayout}
202
+ onScroll={handleHorizontalScroll}
203
+ scrollEventThrottle={16}
204
+ style={styles.scroll}
205
+ contentContainerStyle={styles.horizontalScrollContent}
206
+ >
207
+ <View style={styles.content}>
208
+ <View style={[styles.headerRow, { backgroundColor: tableHeaderColor, borderBottomColor: tableBorderColor }]}>
209
+ {tableColumns.map(column => {
210
+ const fieldName = getColumnKey(column);
211
+ const label = formatStoreColumnLabel({
212
+ columns,
213
+ fieldName,
214
+ fallbackLabel: column?.label || fieldName,
215
+ storeName,
216
+ });
217
+ const sortFieldName = getSortField(column);
218
+
219
+ return (
220
+ <TouchableOpacity
221
+ key={fieldName}
222
+ style={[
223
+ getColumnStyle(column),
224
+ column?.isIdentity
225
+ ? [styles.stickyIdentityCell, styles.stickyHeaderCell, { backgroundColor: tableHeaderColor }]
226
+ : null,
227
+ ]}
228
+ activeOpacity={isSortableColumn(column) ? 0.8 : 1}
229
+ onPress={() => configs.requestSort?.(column)}
230
+ >
231
+ <View style={styles.sortableHeader}>
232
+ <Text style={[styles.headerText, { color: tableTextColor }]} numberOfLines={1}>{label}</Text>
233
+ {isSortableColumn(column) && configs.resolvedSort?.field === sortFieldName ? (
234
+ <Icon name={configs.resolvedSort?.direction === 'desc' ? 'chevron-down' : 'chevron-up'} size={12} color={tableTextColor} />
235
+ ) : isSortableColumn(column) ? (
236
+ <Icon name="chevrons-up" size={12} color={tableBorderColor} />
237
+ ) : null}
238
+ {resolvedFilters?.[fieldName] ? <Icon name="filter" size={11} color={tableTextColor} /> : null}
239
+ </View>
240
+ </TouchableOpacity>
241
+ );
242
+ })}
243
+ {hasRowActions ? (
244
+ <View
245
+ style={[
246
+ styles.cell,
247
+ styles.actionsCell,
248
+ styles.stickyActionsCell,
249
+ styles.stickyHeaderCell,
250
+ {
251
+ minWidth: actionsCellWidth,
252
+ width: actionsCellWidth,
253
+ flexBasis: actionsCellWidth,
254
+ maxWidth: actionsCellWidth,
255
+ backgroundColor: tableHeaderColor,
256
+ },
257
+ ]}
258
+ >
259
+ <Text style={[styles.headerText, { color: tableTextColor }]}>
260
+ {global.t?.t(storeName, 'label', 'actions')}
261
+ </Text>
262
+ </View>
263
+ ) : null}
264
+ </View>
265
+ {hasColumnFilters ? (
266
+ <View style={[styles.filterRow, { backgroundColor: tableHeaderColor, borderBottomColor: tableBorderColor }]}>
267
+ {tableColumns.map(column => {
268
+ const fieldName = getColumnKey(column);
269
+
270
+ return (
271
+ <View
272
+ key={`${fieldName}-filter`}
273
+ style={[
274
+ getColumnStyle(column),
275
+ column?.isIdentity
276
+ ? [styles.stickyIdentityCell, { backgroundColor: tableHeaderColor }]
277
+ : null,
278
+ ]}
279
+ >
280
+ {column?.filter !== false && column?.filters !== false ? (
281
+ <DefaultColumnFilter
282
+ column={column}
283
+ filters={resolvedFilters}
284
+ storeName={storeName}
285
+ style={styles.filterCell}
286
+ />
287
+ ) : null}
288
+ </View>
289
+ );
290
+ })}
291
+ {hasRowActions ? (
292
+ <View
293
+ style={[
294
+ styles.cell,
295
+ styles.actionsCell,
296
+ styles.stickyActionsCell,
297
+ {
298
+ minWidth: actionsCellWidth,
299
+ width: actionsCellWidth,
300
+ flexBasis: actionsCellWidth,
301
+ maxWidth: actionsCellWidth,
302
+ backgroundColor: tableHeaderColor,
303
+ },
304
+ ]}
305
+ />
306
+ ) : null}
307
+ </View>
308
+ ) : null}
309
+
310
+ <FlatList
311
+ data={sortedData}
312
+ keyExtractor={getRowKey}
313
+ renderItem={renderTableItem}
314
+ style={styles.tableList}
315
+ contentContainerStyle={styles.tableListContent}
316
+ ListEmptyComponent={(
317
+ <DefaultTableEmptyState
318
+ emptyStateLabel={emptyStateLabel}
319
+ isLoading={isLoading}
320
+ isTable
321
+ tableMutedColor={tableMutedColor}
322
+ />
323
+ )}
324
+ ListFooterComponent={null}
325
+ nestedScrollEnabled
326
+ onMomentumScrollBegin={configs.onMomentumScrollBegin || undefined}
327
+ onScroll={handleListScroll}
328
+ onScrollBeginDrag={configs.onScrollBeginDrag || undefined}
329
+ onEndReached={configs.onEndReached}
330
+ onEndReachedThreshold={END_REACHED_THRESHOLD}
331
+ scrollEventThrottle={120}
332
+ showsVerticalScrollIndicator={false}
333
+ />
334
+ </View>
335
+ </ScrollView>
336
+ </>
337
+ );
338
+ };
339
+
340
+ export default DefaultTableRows;
@@ -0,0 +1,100 @@
1
+ import React from 'react';
2
+ import { TouchableOpacity, useWindowDimensions } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { useStore } from '@store';
5
+ import DefaultSearch from '../filters/DefaultSearch';
6
+ import DefaultModalButton from './DefaultModalButton';
7
+ import DefaultSearchModal from './DefaultSearchModal';
8
+ import {
9
+ COLLAPSED_SEARCH_MAX_VIEWPORT_WIDTH,
10
+ } from './DefaultTable.utils';
11
+ import styles from './DefaultTable.styles';
12
+ import useDefaultTableTheme from './useDefaultTableTheme';
13
+
14
+ const shouldRenderSearch = store => {
15
+ const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
16
+
17
+ return columns.some(column =>
18
+ column?.search === true ||
19
+ column?.searchable === true ||
20
+ column?.filter === 'search',
21
+ );
22
+ };
23
+
24
+ const useSearchState = storeName => {
25
+ const store = useStore(storeName);
26
+ const { width } = useWindowDimensions();
27
+ const configs = store?.getters?.configs || {};
28
+ const totalItemsNumber = Number(store?.getters?.totalItems);
29
+ const hasTotalItems = Number.isFinite(totalItemsNumber);
30
+ const compactBreakpoint = Number(configs.compactBreakpoint || COLLAPSED_SEARCH_MAX_VIEWPORT_WIDTH);
31
+ const rendersCompactTotal =
32
+ configs.showTotalItemsInCompactToolbar === true &&
33
+ width > 0 &&
34
+ width <= compactBreakpoint &&
35
+ hasTotalItems &&
36
+ configs.showTotalItemsInFooter === false;
37
+ const collapses =
38
+ width > 0 && width <= COLLAPSED_SEARCH_MAX_VIEWPORT_WIDTH;
39
+
40
+ return {
41
+ collapses,
42
+ renders: shouldRenderSearch(store),
43
+ rendersCompactTotal,
44
+ };
45
+ };
46
+
47
+ export const DefaultTableCompactSearch = ({ storeName }) => {
48
+ const { renders, rendersCompactTotal } = useSearchState(storeName);
49
+
50
+ return renders && rendersCompactTotal ? (
51
+ <DefaultSearch compact storeName={storeName} />
52
+ ) : null;
53
+ };
54
+
55
+ export const DefaultTableInlineSearch = ({ storeName }) => {
56
+ const { collapses, renders, rendersCompactTotal } = useSearchState(storeName);
57
+
58
+ return renders && !rendersCompactTotal && !collapses ? (
59
+ <DefaultSearch compact storeName={storeName} />
60
+ ) : null;
61
+ };
62
+
63
+ export const DefaultTableCollapsedSearch = ({ storeName }) => {
64
+ const { collapses, renders, rendersCompactTotal } = useSearchState(storeName);
65
+ const { tableButtonColors } = useDefaultTableTheme();
66
+
67
+ if (!renders || rendersCompactTotal || !collapses) {
68
+ return null;
69
+ }
70
+
71
+ return (
72
+ <DefaultModalButton
73
+ renderButton={({ open }) => (
74
+ <TouchableOpacity
75
+ style={[
76
+ styles.toolbarSearchButton,
77
+ {
78
+ borderColor: tableButtonColors.borderColor,
79
+ backgroundColor: tableButtonColors.backgroundColor,
80
+ },
81
+ ]}
82
+ accessibilityLabel={global.t?.t(storeName, 'label', 'search')}
83
+ accessibilityRole="button"
84
+ activeOpacity={0.82}
85
+ onPress={open}
86
+ >
87
+ <Icon name="search" size={14} color={tableButtonColors.iconColor} />
88
+ </TouchableOpacity>
89
+ )}
90
+ >
91
+ {({ close, isOpen }) => (
92
+ <DefaultSearchModal
93
+ storeName={storeName}
94
+ visible={isOpen}
95
+ onClose={close}
96
+ />
97
+ )}
98
+ </DefaultModalButton>
99
+ );
100
+ };
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+ import { Text, View, useWindowDimensions } from 'react-native';
3
+ import { useStore } from '@store';
4
+ import DefaultTableControls from './DefaultTableControls';
5
+ import {
6
+ DefaultTableCollapsedSearch,
7
+ DefaultTableCompactSearch,
8
+ DefaultTableInlineSearch,
9
+ } from './DefaultTableSearch';
10
+ import DefaultToolbarAction from './DefaultToolbarAction';
11
+ import styles from './DefaultTable.styles';
12
+ import useDefaultTableTheme from './useDefaultTableTheme';
13
+
14
+ const DefaultTableToolbar = ({ storeName }) => {
15
+ const store = useStore(storeName);
16
+ const { width } = useWindowDimensions();
17
+ const configs = store?.getters?.configs || {};
18
+ const compactBreakpoint = Number(configs.compactBreakpoint || 768);
19
+ const isCompactView = width > 0 && width <= compactBreakpoint;
20
+ const showTotalItemsInCompactToolbar = configs.showTotalItemsInCompactToolbar === true;
21
+ const showTotalItemsInFooter = configs.showTotalItemsInFooter !== false;
22
+ const toolbarActions = Array.isArray(configs.toolbarActions) ? configs.toolbarActions : [];
23
+ const { toolbarColors } = useDefaultTableTheme();
24
+ const {
25
+ backgroundColor: toolbarBackgroundColor,
26
+ borderColor: toolbarBorderColor,
27
+ countBackgroundColor: toolbarCountBackgroundColor,
28
+ countTextColor: toolbarCountTextColor,
29
+ } = toolbarColors;
30
+ const storeTotalItems = store?.getters?.totalItems;
31
+ const resolvedTotalItems = storeTotalItems;
32
+ const totalItemsNumber = Number(resolvedTotalItems);
33
+ const shouldRenderTotalItems =
34
+ resolvedTotalItems !== null &&
35
+ resolvedTotalItems !== undefined &&
36
+ Number.isFinite(totalItemsNumber);
37
+ const shouldRenderFooterTotalItems =
38
+ shouldRenderTotalItems &&
39
+ showTotalItemsInFooter !== false;
40
+ const shouldRenderCompactToolbarTotalItems =
41
+ showTotalItemsInCompactToolbar === true &&
42
+ isCompactView &&
43
+ shouldRenderTotalItems &&
44
+ !shouldRenderFooterTotalItems;
45
+ const resolvedTotalItemsText =
46
+ shouldRenderTotalItems
47
+ ? `${totalItemsNumber} ${global.t?.t(storeName, 'label', 'items')}`
48
+ : '';
49
+ const renderToolbarActions = () =>
50
+ Array.isArray(toolbarActions) && toolbarActions.length > 0 ? (
51
+ <View style={styles.toolbarActionGroup}>
52
+ {toolbarActions.map(action => (
53
+ <DefaultToolbarAction
54
+ key={action?.key || action?.icon || action?.label}
55
+ action={action}
56
+ />
57
+ ))}
58
+ </View>
59
+ ) : null;
60
+
61
+ return (
62
+ <View
63
+ style={[
64
+ styles.toolbar,
65
+ shouldRenderCompactToolbarTotalItems ? styles.toolbarWithCompactTotal : null,
66
+ { borderBottomColor: toolbarBorderColor, backgroundColor: toolbarBackgroundColor },
67
+ ]}
68
+ >
69
+ {shouldRenderCompactToolbarTotalItems ? (
70
+ <View style={styles.toolbarCompactLead}>
71
+ <View style={[styles.toolbarCountPill, { backgroundColor: toolbarCountBackgroundColor }]}>
72
+ <Text
73
+ style={[styles.toolbarCountText, { color: toolbarCountTextColor }]}
74
+ numberOfLines={1}
75
+ adjustsFontSizeToFit
76
+ minimumFontScale={0.82}
77
+ >
78
+ {resolvedTotalItemsText}
79
+ </Text>
80
+ </View>
81
+ <DefaultTableCompactSearch storeName={storeName} />
82
+ {renderToolbarActions()}
83
+ </View>
84
+ ) : null}
85
+
86
+ <View style={shouldRenderCompactToolbarTotalItems ? styles.toolbarCompactActions : styles.toolbarLeft}>
87
+ <DefaultTableInlineSearch storeName={storeName} />
88
+ <DefaultTableCollapsedSearch storeName={storeName} />
89
+ <View style={styles.toolbarActionGroup}>
90
+ {!shouldRenderCompactToolbarTotalItems ? renderToolbarActions() : null}
91
+ <DefaultTableControls storeName={storeName} />
92
+ </View>
93
+ </View>
94
+ </View>
95
+ );
96
+ };
97
+
98
+ export default DefaultTableToolbar;
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { Text, TouchableOpacity } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { normalizeText } from '../inputs/defaultInputUtils';
5
+ import styles from './DefaultTable.styles';
6
+ import useDefaultTableTheme from './useDefaultTableTheme';
7
+
8
+ const DefaultToolbarAction = ({ action }) => {
9
+ const { tableActionColors } = useDefaultTableTheme();
10
+ const {
11
+ backgroundColor: tableActionBackgroundColor,
12
+ borderColor: tableActionBorderColor,
13
+ textColor: tableActionTextColor,
14
+ } = tableActionColors;
15
+
16
+ if (!action || action.hidden) return null;
17
+
18
+ const hasLabel = normalizeText(action.label) !== '';
19
+ const actionColor = action.color || tableActionTextColor;
20
+
21
+ return (
22
+ <TouchableOpacity
23
+ key={action.key || action.icon || action.label}
24
+ accessibilityRole="button"
25
+ accessibilityLabel={action.accessibilityLabel || action.label || action.key || action.icon}
26
+ style={[
27
+ styles.toolbarButton,
28
+ { backgroundColor: tableActionBackgroundColor, borderColor: tableActionBorderColor },
29
+ action.style,
30
+ ]}
31
+ activeOpacity={0.82}
32
+ disabled={action.disabled === true}
33
+ onPress={action.onPress}
34
+ >
35
+ {action.icon ? (
36
+ <Icon
37
+ name={action.icon}
38
+ size={action.iconSize || 14}
39
+ color={actionColor}
40
+ />
41
+ ) : null}
42
+ {hasLabel ? (
43
+ <Text style={[styles.toolbarActionLabel, { color: actionColor }, action.labelStyle]} numberOfLines={1}>
44
+ {action.label}
45
+ </Text>
46
+ ) : null}
47
+ {action.badge !== undefined && action.badge !== null ? (
48
+ <Text style={[styles.toolbarBadgeText, { color: action.badgeColor || tableActionTextColor }]}>
49
+ {action.badge}
50
+ </Text>
51
+ ) : null}
52
+ </TouchableOpacity>
53
+ );
54
+ };
55
+
56
+ export default DefaultToolbarAction;