@controleonline/ui-default 1.0.277 → 1.0.279

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 (37) hide show
  1. package/docs/compact-order-history-1047.md +25 -0
  2. package/package.json +1 -1
  3. package/src/react/components/filters/CompactFilterSelector.js +194 -185
  4. package/src/react/components/filters/DefaultColumnFilter.js +8 -6
  5. package/src/react/components/filters/DefaultCompactFilterPicker.js +35 -0
  6. package/src/react/components/table/DefaultColumnMenu.js +36 -2
  7. package/src/react/components/table/DefaultCompactDialog.js +36 -0
  8. package/src/react/components/table/DefaultDebug.js +199 -197
  9. package/src/react/components/table/DefaultFiltersModal.js +44 -12
  10. package/src/react/components/table/DefaultTable.js +27 -27
  11. package/src/react/components/table/DefaultTableCards.js +44 -10
  12. package/src/react/components/table/DefaultTableCompact.helpers.js +26 -0
  13. package/src/react/components/table/DefaultTableCompact.styles.js +28 -0
  14. package/src/react/components/table/DefaultTableCompactToolbar.js +112 -0
  15. package/src/react/components/table/DefaultTableInput.js +30 -11
  16. package/src/react/components/table/DefaultTablePaginationBar.js +38 -0
  17. package/src/react/components/table/DefaultTableRows.js +17 -9
  18. package/src/react/components/table/DefaultTableToolbar.js +7 -0
  19. package/src/react/components/table/DefaultTableView.js +5 -2
  20. package/src/react/components/table/useDefaultTableCreateForm.js +29 -0
  21. package/src/react/components/table/useDefaultTablePagination.js +15 -1
  22. package/src/react/components/table/useDefaultTableRowInteraction.js +19 -0
  23. package/src/react/components/table/useDefaultTableStoreSync.js +30 -21
  24. package/src/react/components/table/useDefaultTableTheme.js +44 -41
  25. package/src/tests/react/components/table/DefaultTable.group1.test.js +441 -0
  26. package/src/tests/react/components/table/DefaultTable.group2.test.js +453 -0
  27. package/src/tests/react/components/table/DefaultTable.group3.test.js +289 -0
  28. package/src/tests/react/components/table/DefaultTable.test.js +1 -1381
  29. package/src/tests/react/components/table/DefaultTableToolbar.waiter.test.js +1 -0
  30. package/src/tests/react/components/table/compactDialogs.test.js +53 -0
  31. package/src/tests/react/components/table/compactFilters.test.js +47 -0
  32. package/src/tests/react/components/table/compactPagination.test.js +66 -0
  33. package/src/tests/react/components/table/compactTheme.test.js +34 -0
  34. package/src/tests/react/components/table/compactToolbar.test.js +41 -0
  35. package/src/tests/react/components/table/defaultTableStoreHarness.cjs +35 -0
  36. package/src/tests/react/components/table/defaultTableTestSetup.cjs +207 -0
  37. package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +38 -1
@@ -0,0 +1,25 @@
1
+ # DefaultTable compact pilot — app-community #1047
2
+
3
+ The compact appearance is opt-in. Order History is the pilot; default consumers retain their existing appearance and actions. Search, controlled filters, accessible card sorting, column visibility, table/cards, numbered pagination and report totals reuse the shared table and order services.
4
+
5
+ Theme authority remains the main company/domain. Theme tokens override status colors; registered status.color follows; neutral theme text is last. Franchise switching does not override domain authority. No database writes or staging Theme bindings are shipped. Business permissions, payment/cancellation eligibility and request/security filters are preserved.
6
+
7
+ Header logos send authentication only to the exact API origin; external images use Image without headers. Inline row/card navigation stays blocked during editing/saving, preserving existing store save callbacks. Provider extraction preserves hook order and unrelated bootstrap dependencies, checked by ordered AST fingerprints.
8
+
9
+ Pre-existing published dependencies are merged by their exact gitHeads rather than importing latest dev:
10
+ - ui-default 1.0.276 f0083eb73074f402e81f4e0c832af0bb829c2047; published patch277 1dff39dc32be3320cd4a786baf5621e8a2d3ac00.
11
+ - ui-orders 1.3.42 bfe2f013dc9bcaf033ba0db80894f021bfb2fbfb.
12
+ - ui-layout 1.0.15 37f1dff3fecf1f08bc8846a319af83c9fcbff502.
13
+ - ui-common 1.2.89 4919724c04f3d6e66280db4c9e2a66db65c47cc0; published patch90 e19f14d4afefb89a0700d63753785815364af835.
14
+
15
+ Patch90 Connections.js had an unterminated searchKey quote. Only that quote is repaired to make the retained published fixes compilable. Existing DefaultTable tests are split without discarding cases; fixtures now model reactive stores, company-scoped preferences, route context and CSS sticky positioning. Legacy toolbar tests isolate the inactive compact branch; dedicated compact suites cover its controls.
16
+
17
+ Validation: 135 assertions in 22 suites, 591 React files parsed, and isolated Expo web export (5492 modules). These are source/composition checks; authenticated staging validation remains a separate promotion gate. Isolation used source-module copies; final released-package pins must be verified again by release validation.
18
+
19
+ From app-community with installed release packages:
20
+ ```sh
21
+ node scripts/validate-compact-sources.cjs
22
+ node node_modules/jest/bin/jest.js --config jest.compact.config.cjs --maxWorkers=2 --testPathPattern='compact|Compact|headerLogoProtocol|DefaultCompanyHeaderLogo|useDefaultTableStoreSync|providerEffects|DefaultProviderAuthority|deviceConfigBootstrap|mercadoLivreIntegrationHelpers|DefaultTable\.(group[123]\.)?test.js|OrderHistoryPage.test.js|DefaultTableCards.layout|DefaultTableToolbar.waiter'
23
+ ```
24
+
25
+ For isolated sibling source clones, set CONTROLEONLINE_MODULES_ROOT to their common directory. The committed configuration has no machine-specific absolute path. Published versions planned: ui-default1.0.278, ui-orders1.3.43, ui-layout1.0.16, ui-common1.2.91.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@controleonline/ui-default",
3
- "version": "1.0.277",
3
+ "version": "1.0.279",
4
4
  "author": "ControleOnline",
5
5
  "description": "ControleOnline Default Quasar UI Component",
6
6
  "license": "MIT",
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useMemo, useState } from 'react';
1
+ import React, { useCallback, useContext, useMemo, useState } from 'react';
2
2
  import {
3
3
  Image,
4
4
  Modal,
@@ -19,7 +19,9 @@ import {
19
19
  } from '@controleonline/ui-common/src/react/utils/storeColumns';
20
20
  import { isValidFeatherIcon } from '../inputs/defaultInputUtils';
21
21
  import createStyles from './CompactFilterSelector.styles';
22
-
22
+ import {DefaultTableThemeContext} from '../table/useDefaultTableTheme';
23
+ import DefaultCompactFilterPicker from './DefaultCompactFilterPicker';
24
+
23
25
  const buildTheme = ({ accentColor, themeColors = {} }) => ({
24
26
  accentColor: accentColor,
25
27
  activeBackgroundColor:
@@ -30,68 +32,68 @@ const buildTheme = ({ accentColor, themeColors = {} }) => ({
30
32
  themeColors.activeChevronColor ||
31
33
  themeColors.activeIconColor ||
32
34
  accentColor,
33
- activeIconColor: themeColors.activeIconColor || accentColor,
34
- activeTextColor: themeColors.activeTextColor || accentColor,
35
- backgroundColor: themeColors.backgroundColor || '#F8FAFC',
36
- borderColor: themeColors.borderColor || '#E2E8F0',
37
- captionColor: themeColors.captionColor || '#94A3B8',
38
- chevronColor: themeColors.chevronColor || '#94A3B8',
39
- closeIconColor: themeColors.closeIconColor || '#64748B',
40
- iconBackgroundColor: themeColors.iconBackgroundColor || '#E2E8F0',
41
- iconColor: themeColors.iconColor || '#64748B',
42
- modalBackgroundColor: themeColors.modalBackgroundColor || '#FFFFFF',
43
- modalOverlayColor:
44
- themeColors.modalOverlayColor || 'rgba(15, 23, 42, 0.35)',
45
- modalTitleColor: themeColors.modalTitleColor || '#0F172A',
46
- optionBackgroundColor: themeColors.optionBackgroundColor || '#F8FAFC',
47
- optionBorderColor: themeColors.optionBorderColor || '#E2E8F0',
35
+ activeIconColor: themeColors.activeIconColor || accentColor,
36
+ activeTextColor: themeColors.activeTextColor || accentColor,
37
+ backgroundColor: themeColors.backgroundColor || '#F8FAFC',
38
+ borderColor: themeColors.borderColor || '#E2E8F0',
39
+ captionColor: themeColors.captionColor || '#94A3B8',
40
+ chevronColor: themeColors.chevronColor || '#94A3B8',
41
+ closeIconColor: themeColors.closeIconColor || '#64748B',
42
+ iconBackgroundColor: themeColors.iconBackgroundColor || '#E2E8F0',
43
+ iconColor: themeColors.iconColor || '#64748B',
44
+ modalBackgroundColor: themeColors.modalBackgroundColor || '#FFFFFF',
45
+ modalOverlayColor:
46
+ themeColors.modalOverlayColor || 'rgba(15, 23, 42, 0.35)',
47
+ modalTitleColor: themeColors.modalTitleColor || '#0F172A',
48
+ optionBackgroundColor: themeColors.optionBackgroundColor || '#F8FAFC',
49
+ optionBorderColor: themeColors.optionBorderColor || '#E2E8F0',
48
50
  optionSelectedTextColor:
49
51
  themeColors.optionSelectedTextColor || accentColor,
50
52
  textColor: themeColors.textColor || '#0F172A',
51
53
  });
52
-
53
- const noop = () => {};
54
-
55
- const normalizeText = value => String(value || '').trim();
56
-
57
- const resolveStoreName = store => {
58
- if (typeof store === 'string') return normalizeText(store);
59
- return normalizeText(store?.storeName || store?.name || '');
60
- };
61
-
62
- const resolveStoreFieldLabel = ({ fallbackLabel = '', field = '', store = '' }) => {
63
- const storeName = resolveStoreName(store);
64
- const fieldName = normalizeText(field);
65
- if (!storeName || !fieldName) return fallbackLabel;
66
-
67
- const { columns } = resolveStoreConfigByName(storeName);
68
- const column = resolveStoreColumn(columns, fieldName);
69
- const labelKey = normalizeText(column?.label || fieldName);
70
- const translatedLabel =
71
- global.t?.t(storeName, 'label', labelKey) ||
72
- global.t?.t(storeName, 'input', labelKey);
73
-
74
- return (
75
- translatedLabel ||
76
- formatStoreColumnLabel({
77
- columns,
78
- fallbackLabel: fallbackLabel || labelKey,
79
- fieldName,
80
- storeName,
81
- }) ||
82
- fallbackLabel
83
- );
84
- };
85
-
54
+
55
+ const noop = () => {};
56
+
57
+ const normalizeText = value => String(value || '').trim();
58
+
59
+ const resolveStoreName = store => {
60
+ if (typeof store === 'string') return normalizeText(store);
61
+ return normalizeText(store?.storeName || store?.name || '');
62
+ };
63
+
64
+ const resolveStoreFieldLabel = ({ fallbackLabel = '', field = '', store = '' }) => {
65
+ const storeName = resolveStoreName(store);
66
+ const fieldName = normalizeText(field);
67
+ if (!storeName || !fieldName) return fallbackLabel;
68
+
69
+ const { columns } = resolveStoreConfigByName(storeName);
70
+ const column = resolveStoreColumn(columns, fieldName);
71
+ const labelKey = normalizeText(column?.label || fieldName);
72
+ const translatedLabel =
73
+ global.t?.t(storeName, 'label', labelKey) ||
74
+ global.t?.t(storeName, 'input', labelKey);
75
+
76
+ return (
77
+ translatedLabel ||
78
+ formatStoreColumnLabel({
79
+ columns,
80
+ fallbackLabel: fallbackLabel || labelKey,
81
+ fieldName,
82
+ storeName,
83
+ }) ||
84
+ fallbackLabel
85
+ );
86
+ };
87
+
86
88
  const CompactFilterSelector = ({
87
89
  accentColor = null,
88
90
  active = false,
89
91
  children = null,
90
92
  dense = false,
91
93
  disabled = false,
92
- icon = 'sliders',
93
- label = '',
94
- labelCaption = '',
94
+ icon = 'sliders',
95
+ label = '',
96
+ labelCaption = '',
95
97
  onClose = null,
96
98
  onBeforeOpen = null,
97
99
  onSearchChange = null,
@@ -104,6 +106,7 @@ const CompactFilterSelector = ({
104
106
  themeColors = {},
105
107
  title = '',
106
108
  }) => {
109
+ const compactTheme = useContext(DefaultTableThemeContext);
107
110
  const themeStore = useStore('theme');
108
111
  const [visible, setVisible] = useState(false);
109
112
  const [searchText, setSearchText] = useState('');
@@ -126,15 +129,15 @@ const CompactFilterSelector = ({
126
129
  }),
127
130
  [resolvedAccentColor, themeColors, themeStoreColors],
128
131
  );
129
- const styles = useMemo(
130
- () => createStyles(resolvedTheme, dense, windowWidth),
131
- [dense, resolvedTheme, windowWidth],
132
- );
133
- const storeFieldLabel = resolveStoreFieldLabel({
134
- fallbackLabel: labelCaption || title,
135
- field,
136
- store,
137
- });
132
+ const styles = useMemo(
133
+ () => createStyles(resolvedTheme, dense, windowWidth),
134
+ [dense, resolvedTheme, windowWidth],
135
+ );
136
+ const storeFieldLabel = resolveStoreFieldLabel({
137
+ fallbackLabel: labelCaption || title,
138
+ field,
139
+ store,
140
+ });
138
141
  const resolvedLabelCaption = labelCaption || storeFieldLabel;
139
142
  const resolvedTitle = title || storeFieldLabel;
140
143
  const selectedOption = useMemo(
@@ -152,35 +155,35 @@ const CompactFilterSelector = ({
152
155
  const triggerIconColor = active
153
156
  ? selectedOptionColor || resolvedTheme.activeIconColor
154
157
  : resolvedTheme.iconColor;
155
-
158
+
156
159
  const closeModal = useCallback(() => {
157
160
  setVisible(false);
158
161
  setSearchText('');
159
162
  onClose?.();
160
163
  }, [onClose]);
161
-
164
+
162
165
  const openModal = useCallback(() => {
163
- if (disabled) {
164
- return;
165
- }
166
-
167
- try {
168
- const maybePromise = onBeforeOpen?.();
169
- if (maybePromise && typeof maybePromise.then === 'function') {
170
- maybePromise
171
- .then(() => {
172
- setVisible(true);
173
- })
174
- .catch(() => {
175
- // Keep the modal closed if the lazy load fails.
176
- });
177
- return;
178
- }
179
- } catch (error) {
180
- return;
181
- }
182
-
183
- setVisible(true);
166
+ if (disabled) {
167
+ return;
168
+ }
169
+
170
+ try {
171
+ const maybePromise = onBeforeOpen?.();
172
+ if (maybePromise && typeof maybePromise.then === 'function') {
173
+ maybePromise
174
+ .then(() => {
175
+ setVisible(true);
176
+ })
177
+ .catch(() => {
178
+ // Keep the modal closed if the lazy load fails.
179
+ });
180
+ return;
181
+ }
182
+ } catch (error) {
183
+ return;
184
+ }
185
+
186
+ setVisible(true);
184
187
  }, [disabled, onBeforeOpen]);
185
188
 
186
189
  /*
@@ -203,37 +206,43 @@ const CompactFilterSelector = ({
203
206
  normalizeText(option?.key).toLowerCase().includes(normalizedSearch),
204
207
  );
205
208
  }, [onSearchChange, options, searchable, searchText]);
206
-
207
- const handleSelect = useCallback(optionKey => {
208
- const shouldClose = onSelect?.(optionKey, {
209
- close: closeModal,
210
- open: openModal,
211
- });
212
-
213
- if (shouldClose !== false) {
214
- closeModal();
215
- }
216
- }, [closeModal, onSelect, openModal]);
217
-
218
- const resolvedChildren = useMemo(() => {
219
- if (typeof children === 'function') {
220
- return children({ close: closeModal, open: openModal });
221
- }
222
-
223
- return children;
224
- }, [children, closeModal, openModal]);
225
-
226
- return (
227
- <>
228
- <TouchableOpacity
229
- style={[
230
- styles.trigger,
231
- active ? styles.triggerActive : null,
232
- ]}
233
- activeOpacity={0.9}
234
- disabled={disabled}
235
- onPress={openModal}
236
- >
209
+
210
+ const handleSelect = useCallback(optionKey => {
211
+ const shouldClose = onSelect?.(optionKey, {
212
+ close: closeModal,
213
+ open: openModal,
214
+ });
215
+
216
+ if (shouldClose !== false) {
217
+ closeModal();
218
+ }
219
+ }, [closeModal, onSelect, openModal]);
220
+
221
+ const resolvedChildren = useMemo(() => {
222
+ if (typeof children === 'function') {
223
+ return children({ close: closeModal, open: openModal });
224
+ }
225
+
226
+ return children;
227
+ }, [children, closeModal, openModal]);
228
+
229
+ if (compactTheme?.compact) return <DefaultCompactFilterPicker visible={visible} disabled={disabled}
230
+ label={label} title={resolvedTitle} selectedKey={selectedKey} options={filteredOptions} searchText={searchText}
231
+ searchable={searchable} onSearch={handleSearchChange} onOpen={openModal} onClose={closeModal} onSelect={handleSelect}>
232
+ {resolvedChildren}
233
+ </DefaultCompactFilterPicker>;
234
+
235
+ return (
236
+ <>
237
+ <TouchableOpacity
238
+ style={[
239
+ styles.trigger,
240
+ active ? styles.triggerActive : null,
241
+ ]}
242
+ activeOpacity={0.9}
243
+ disabled={disabled}
244
+ onPress={openModal}
245
+ >
237
246
  <View style={[styles.iconWrap, active ? styles.iconWrapActive : null]}>
238
247
  {active && selectedOption?.image ? (
239
248
  <Image
@@ -249,58 +258,58 @@ const CompactFilterSelector = ({
249
258
  />
250
259
  )}
251
260
  </View>
252
-
253
- <View style={styles.textWrap}>
254
- {!!resolvedLabelCaption && (
255
- <Text
256
- numberOfLines={1}
257
- style={[
261
+
262
+ <View style={styles.textWrap}>
263
+ {!!resolvedLabelCaption && (
264
+ <Text
265
+ numberOfLines={1}
266
+ style={[
258
267
  styles.triggerCaption,
259
268
  active ? styles.triggerCaptionActive : null,
260
269
  active && selectedOptionColor ? { color: selectedOptionColor } : null,
261
270
  ]}
262
- >
263
- {resolvedLabelCaption}
264
- </Text>
265
- )}
266
-
267
- <Text
268
- numberOfLines={1}
271
+ >
272
+ {resolvedLabelCaption}
273
+ </Text>
274
+ )}
275
+
276
+ <Text
277
+ numberOfLines={1}
269
278
  style={[
270
279
  styles.triggerText,
271
280
  active ? styles.triggerTextActive : null,
272
281
  active && selectedOptionColor ? { color: selectedOptionColor } : null,
273
282
  ]}
274
- >
275
- {label}
276
- </Text>
277
- </View>
278
-
279
- <Icon
280
- name="chevron-down"
281
- size={dense ? 14 : 16}
282
- color={active ? resolvedTheme.activeChevronColor : resolvedTheme.chevronColor}
283
- />
284
- </TouchableOpacity>
285
-
286
- <Modal
287
- transparent
288
- visible={visible}
289
- animationType="fade"
290
- onRequestClose={closeModal}
291
- >
292
- <TouchableWithoutFeedback onPress={closeModal}>
293
- <View style={styles.modalOverlay}>
294
- <TouchableWithoutFeedback onPress={noop}>
295
- <View style={styles.modalCard}>
296
- <View style={styles.modalHeader}>
297
- <Text style={styles.modalTitle}>{resolvedTitle}</Text>
298
-
299
- <TouchableOpacity onPress={closeModal} activeOpacity={0.8}>
300
- <Icon name="x" size={20} color={resolvedTheme.closeIconColor} />
301
- </TouchableOpacity>
302
- </View>
303
-
283
+ >
284
+ {label}
285
+ </Text>
286
+ </View>
287
+
288
+ <Icon
289
+ name="chevron-down"
290
+ size={dense ? 14 : 16}
291
+ color={active ? resolvedTheme.activeChevronColor : resolvedTheme.chevronColor}
292
+ />
293
+ </TouchableOpacity>
294
+
295
+ <Modal
296
+ transparent
297
+ visible={visible}
298
+ animationType="fade"
299
+ onRequestClose={closeModal}
300
+ >
301
+ <TouchableWithoutFeedback onPress={closeModal}>
302
+ <View style={styles.modalOverlay}>
303
+ <TouchableWithoutFeedback onPress={noop}>
304
+ <View style={styles.modalCard}>
305
+ <View style={styles.modalHeader}>
306
+ <Text style={styles.modalTitle}>{resolvedTitle}</Text>
307
+
308
+ <TouchableOpacity onPress={closeModal} activeOpacity={0.8}>
309
+ <Icon name="x" size={20} color={resolvedTheme.closeIconColor} />
310
+ </TouchableOpacity>
311
+ </View>
312
+
304
313
  <ScrollView
305
314
  style={styles.modalScroll}
306
315
  contentContainerStyle={styles.modalContent}
@@ -331,12 +340,12 @@ const CompactFilterSelector = ({
331
340
  const hasOptionVisual = Boolean(option?.image || optionIconName || shouldRenderOptionFallbackText);
332
341
 
333
342
  return (
334
- <TouchableOpacity
335
- key={option.key}
336
- style={[
337
- styles.modalOption,
338
- isSelected ? styles.modalOptionActive : null,
339
- ]}
343
+ <TouchableOpacity
344
+ key={option.key}
345
+ style={[
346
+ styles.modalOption,
347
+ isSelected ? styles.modalOptionActive : null,
348
+ ]}
340
349
  activeOpacity={0.9}
341
350
  onPress={() => handleSelect(option.key)}
342
351
  >
@@ -377,27 +386,27 @@ const CompactFilterSelector = ({
377
386
  {option.label}
378
387
  </Text>
379
388
  )}
380
-
381
- {isSelected ? (
389
+
390
+ {isSelected ? (
382
391
  <Icon
383
392
  name="check"
384
393
  size={16}
385
394
  color={optionColor || resolvedTheme.activeIconColor}
386
395
  />
387
- ) : null}
388
- </TouchableOpacity>
389
- );
390
- })}
391
-
392
- {resolvedChildren}
393
- </ScrollView>
394
- </View>
395
- </TouchableWithoutFeedback>
396
- </View>
397
- </TouchableWithoutFeedback>
398
- </Modal>
399
- </>
400
- );
401
- };
402
-
403
- export default CompactFilterSelector;
396
+ ) : null}
397
+ </TouchableOpacity>
398
+ );
399
+ })}
400
+
401
+ {resolvedChildren}
402
+ </ScrollView>
403
+ </View>
404
+ </TouchableWithoutFeedback>
405
+ </View>
406
+ </TouchableWithoutFeedback>
407
+ </Modal>
408
+ </>
409
+ );
410
+ };
411
+
412
+ export default CompactFilterSelector;
@@ -42,7 +42,7 @@ const DefaultColumnFilter = ({
42
42
  const store = useStore(storeName);
43
43
  const peopleStore = useStore('people');
44
44
  const configs = store?.getters?.configs || {};
45
- const { resolvedAccentColor } = useDefaultTableTheme(accentColor);
45
+ const { resolvedAccentColor, themeColors } = useDefaultTableTheme(accentColor);
46
46
  const [loadedItems, setLoadedItems] = useState([]);
47
47
  const [loadedKey, setLoadedKey] = useState('');
48
48
  const fieldName = getColumnKey(column);
@@ -168,7 +168,7 @@ const DefaultColumnFilter = ({
168
168
  dense
169
169
  store={storeName}
170
170
  field={fieldName}
171
- labelCaption={global.t?.t(storeName, 'label', column?.label || fieldName)}
171
+ labelCaption={themeColors.compact ? column?.compactLabel || fieldName : global.t?.t(storeName, 'label', column?.label || fieldName)}
172
172
  value={filterValue.shortcut || 'all'}
173
173
  customRange={filterValue.customRange || { from: '', to: '' }}
174
174
  onChange={optionKey =>
@@ -196,9 +196,9 @@ const DefaultColumnFilter = ({
196
196
  const explicitOptions = resolveOptions(column);
197
197
  const rawOptions = Array.isArray(explicitOptions)
198
198
  ? mapOptions(column, explicitOptions, storeName)
199
- : mapOptions(column, loadedItems, storeName);
199
+ : Array.isArray(column.list) ? buildOptionsFromColumn(column, null, storeName) : mapOptions(column, loadedItems, storeName);
200
200
  const options = [
201
- { key: '', label: global.t?.t(storeName, 'label', 'select') },
201
+ { key: '', label: themeColors.compact ? 'Todos' : global.t?.t(storeName, 'label', 'select') },
202
202
  ...rawOptions,
203
203
  ];
204
204
  const selectedKey = normalizeOptionKey(filters?.[fieldName]);
@@ -208,11 +208,12 @@ const DefaultColumnFilter = ({
208
208
  <View style={[style, styles.filterCell]}>
209
209
  <CompactFilterSelector
210
210
  dense
211
+ title={themeColors.compact ? column?.compactLabel || fieldName : undefined}
211
212
  store={storeName}
212
213
  field={fieldName}
213
214
  icon="filter"
214
215
  onBeforeOpen={onBeforeOpen || (() => loadListOptions())}
215
- onSearchChange={onSearchChange || (value => loadListOptions(value))}
216
+ onSearchChange={onSearchChange || (Array.isArray(column.list) || Array.isArray(explicitOptions) ? undefined : value => loadListOptions(value))}
216
217
  accentColor={resolvedAccentColor}
217
218
  active={Boolean(selectedKey)}
218
219
  label={selected?.label || options[0]?.label || ''}
@@ -231,7 +232,8 @@ const DefaultColumnFilter = ({
231
232
  return (
232
233
  <View style={[style, styles.filterCell]}>
233
234
  <TextInput
234
- style={styles.filterInput}
235
+ accessibilityLabel={column?.compactLabel || fieldName}
236
+ style={[styles.filterInput, themeColors.compact ? {minHeight: 46, borderRadius: 9, fontSize: 14, borderColor: themeColors.inputBorder, color: themeColors.inputText, backgroundColor: themeColors.inputBackground} : null]}
235
237
  value={normalizeText(filters?.[fieldName])}
236
238
  placeholder={global.t?.t(storeName, 'input', column?.label || fieldName)}
237
239
  onChangeText={value => requestChange(fieldName, value)}
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import {Image, StyleSheet, Text, TextInput, TouchableOpacity, View} from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import DefaultCompactDialog from '../table/DefaultCompactDialog';
5
+ import useDefaultTableTheme from '../table/useDefaultTableTheme';
6
+
7
+ // Presentation only: loading, scoped options and date validation stay in the existing selector.
8
+ export default function DefaultCompactFilterPicker({visible, disabled, label, title, selectedKey, options, searchText, searchable, onSearch, onOpen, onClose, onSelect, children}) {
9
+ const {themeColors: c} = useDefaultTableTheme();
10
+ return <>
11
+ <TouchableOpacity accessibilityRole="button" accessibilityLabel={`Selecionar ${title}`} accessibilityState={{expanded: visible, disabled}}
12
+ disabled={disabled} onPress={onOpen} style={[s.trigger, {borderColor: c.inputBorder, backgroundColor: c.inputBackground}]}>
13
+ <Text numberOfLines={1} style={{flex: 1, color: c.inputText, fontSize: 14}}>{label || 'Todos'}</Text><Icon name="chevron-down" size={17} color={c.textSecondary} />
14
+ </TouchableOpacity>
15
+ <DefaultCompactDialog visible={visible} title={title} onClose={onClose} bodyHeight={Math.min(460, 40 + options.length * 48 + (searchable ? 60 : 0) + (children ? 220 : 0))}>
16
+ {searchable ? <TextInput accessibilityLabel={`Buscar em ${title}`} placeholder="Buscar opções" placeholderTextColor={c.inputPlaceholderText}
17
+ value={searchText} onChangeText={onSearch} style={[s.search, {borderColor: c.inputBorder, color: c.inputText, backgroundColor: c.inputBackground}]} /> : null}
18
+ {options.length === 0 ? <Text style={{color: c.textSecondary}}>Nenhuma opção encontrada.</Text> : null}
19
+ {options.map(option => <TouchableOpacity key={option.key} accessibilityRole="radio" accessibilityLabel={option.label || 'Todos'} accessibilityState={{selected: option.key === selectedKey}}
20
+ onPress={() => onSelect(option.key)} style={[s.option, {backgroundColor: option.key === selectedKey ? c.chipSelectedBackground : 'transparent', borderColor: c.tableRowBorder}]}>
21
+ {option.image ? <Image source={option.image} style={{width: 24, height: 24}} resizeMode="contain" /> : null}
22
+ {option.color ? <View style={[s.dot, {backgroundColor: option.color}]} /> : null}
23
+ <Text style={{flex: 1, color: c.textPrimary, fontSize: 14}}>{option.label || 'Todos'}</Text>
24
+ {option.key === selectedKey ? <Icon name="check" size={17} color={c.primary} /> : null}
25
+ </TouchableOpacity>)}
26
+ {children}
27
+ </DefaultCompactDialog>
28
+ </>;
29
+ }
30
+ const s = StyleSheet.create({
31
+ trigger: {minHeight: 46, width: '100%', borderWidth: 1, borderRadius: 9, paddingHorizontal: 12, flexDirection: 'row', alignItems: 'center', gap: 8},
32
+ search: {minHeight: 44, borderWidth: 1, borderRadius: 9, paddingHorizontal: 12, fontSize: 16},
33
+ option: {minHeight: 46, borderRadius: 8, paddingHorizontal: 12, flexDirection: 'row', alignItems: 'center', gap: 10, borderBottomWidth: 1},
34
+ dot: {width: 9, height: 9, borderRadius: 5},
35
+ });