@controleonline/ui-common 1.2.87 → 1.2.89

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 (77) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/manager-worker.yml +7 -156
  3. package/.github/workflows/publish-npm.yml +73 -27
  4. package/.github/workflows/pull-request-checks.yml +7 -5
  5. package/docs/technical/Device-Detail-Alias-UI-Sync.md +141 -0
  6. package/package.json +13 -8
  7. package/src/api/localDB.js +86 -131
  8. package/src/react/components/BottomNavigationBar.js +178 -174
  9. package/src/react/components/BottomNavigationBar.styles.js +139 -118
  10. package/src/react/components/DefaultProvider.native.js +427 -421
  11. package/src/react/components/DefaultProvider.web.js +131 -125
  12. package/src/react/components/RemoteCheckoutService.js +3 -3
  13. package/src/react/components/RuntimeFooterMarqueeText.js +88 -36
  14. package/src/react/components/RuntimeInfoFooter.js +76 -43
  15. package/src/react/components/RuntimeInfoFooter.styles.js +22 -14
  16. package/src/react/config/deviceConfigBootstrap.js +71 -233
  17. package/src/react/config/deviceConfigValues.js +238 -0
  18. package/src/react/pages/Devices/detail/DeviceChargePermissionSection.js +32 -0
  19. package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +4 -22
  20. package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +6 -2
  21. package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +4 -0
  22. package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +7 -0
  23. package/src/react/pages/Devices/detail/DeviceDetailScreen.js +24 -335
  24. package/src/react/pages/Devices/detail/createDeviceDetailContext.js +20 -0
  25. package/src/react/pages/Devices/detail/deviceDetailHelpers.js +2 -11
  26. package/src/react/pages/Devices/detail/useDeviceDetailActions.js +11 -36
  27. package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +129 -37
  28. package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +11 -38
  29. package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +9 -28
  30. package/src/react/pages/IntegrationConfigFields.js +1 -0
  31. package/src/react/pages/IntegrationConfigPage.js +101 -45
  32. package/src/react/pages/IntegrationConfigPage.utils.js +21 -13
  33. package/src/react/pages/fetchPeopleConfigs.js +21 -0
  34. package/src/react/pages/integrationsCatalog.js +15 -110
  35. package/src/react/services/Cielo/Cielo.js +9 -9
  36. package/src/react/services/Getnet/Getnet.web.js +25 -0
  37. package/src/react/utils/companyAuthority.js +36 -0
  38. package/src/react/utils/confirmAction.js +12 -0
  39. package/src/react/utils/fileUrl.js +14 -5
  40. package/src/react/utils/paymentDevices.js +375 -270
  41. package/src/react/utils/printerDevices.js +2 -2
  42. package/src/react/utils/runtimeFooter.js +178 -224
  43. package/src/react/utils/runtimeFooterText.js +160 -0
  44. package/src/react/utils/runtimeLanguage.js +5 -5
  45. package/src/react/utils/runtimeZoom.js +29 -5
  46. package/src/react/utils/shopFranchises.js +167 -12
  47. package/src/react/utils/storeColumns.js +235 -217
  48. package/src/react/utils/systemErrorMessage.js +2 -1
  49. package/src/store/integration/index.js +43 -0
  50. package/src/tests/browser/manager/device-detail-apply-config-defined.spec.js +31 -0
  51. package/src/tests/browser/manager/device-detail-composition.spec.js +295 -0
  52. package/src/tests/browser/manager/device-detail-copy-config.spec.js +11 -1
  53. package/src/tests/browser/manager/device-detail-pdv-current-company.spec.js +273 -0
  54. package/src/tests/browser/manager/runtime-footer-rotation.spec.js +397 -0
  55. package/src/tests/react/components/DefaultProviderAuthority.test.js +28 -0
  56. package/src/tests/react/components/RuntimeFooterMarqueeText.build.test.js +29 -0
  57. package/src/tests/react/config/deviceConfigBootstrap.test.js +24 -1
  58. package/src/tests/react/pages/DeviceChargePermissionSection.test.js +42 -0
  59. package/src/tests/react/pages/applyCurrentDeviceConfig.test.js +85 -0
  60. package/src/tests/react/pages/deviceDetailComposition.test.js +65 -0
  61. package/src/tests/react/pages/deviceDetailHookDependencies.test.mjs +31 -0
  62. package/src/tests/react/pages/deviceDetailLoaderDependencies.test.mjs +28 -0
  63. package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +27 -6
  64. package/src/tests/react/services/Cielo.test.js +2 -2
  65. package/src/tests/react/utils/companyAuthority.test.js +53 -0
  66. package/src/tests/react/utils/formatter.test.mjs +6 -0
  67. package/src/tests/react/utils/paymentDevices.test.js +72 -0
  68. package/src/tests/react/utils/runtimeFooter.test.js +35 -0
  69. package/src/tests/react/utils/runtimeFooterText.test.js +88 -0
  70. package/src/tests/react/utils/runtimeLanguage.test.js +4 -4
  71. package/src/tests/react/utils/runtimeZoom.test.js +20 -0
  72. package/src/tests/react/utils/shopFranchises.test.js +32 -13
  73. package/src/tests/react/utils/translate.test.mjs +6 -6
  74. package/src/utils/formatter.js +7 -3
  75. package/src/utils/translate.js +16 -16
  76. package/src/utils/translatePending.js +6 -6
  77. package/src/utils/translateStorage.js +2 -2
@@ -1,131 +1,86 @@
1
- import { openDB } from "idb";
2
-
3
- const DB_NAME = "ControleOnline";
4
-
5
- export default class localDB {
6
- constructor(state) {
7
- this.state = state;
8
-
9
- this.checkAndGetDB();
10
- }
11
-
12
- async initDB() {
13
- let dbPromise = openDB(DB_NAME, this.currentVersion, {
14
- upgrade: (db) => {
15
- if (!db.objectStoreNames.contains(this.state.resourceEndpoint)) {
16
- const store = db.createObjectStore(this.state.resourceEndpoint, {
17
- keyPath: "id",
18
- });
19
- this.state.columns
20
- .filter((col) => col.sortable || col.externalFilter === false)
21
- .forEach((col) => {
22
- store.createIndex(col.name, col.name, { unique: false });
23
- });
24
- }
25
- },
26
- });
27
- return dbPromise;
28
- }
29
-
30
- async getCurrentVersion() {
31
- // Obtém a versão atual do banco de dados
32
- return new Promise((resolve) => {
33
- const request = indexedDB.open(DB_NAME);
34
- request.onsuccess = () => {
35
- const db = request.result;
36
- const currentVersion = db.currentVersion;
37
- db.close();
38
- resolve(currentVersion);
39
- this.currentVersion = currentVersion;
40
- };
41
- request.onerror = () => resolve(1);
42
- });
43
- }
44
-
45
- async checkAndGetDB() {
46
- return this.getCurrentVersion().then(() => {
47
- return this.initDB().then((db) => {
48
- if (!this.storeExists()) {
49
- this.currentVersion = db.currentVersion + 1;
50
- db.close();
51
- return this.initDB();
52
- }
53
- return db;
54
- });
55
- });
56
- }
57
-
58
- async storeExists() {
59
- return this.checkAndGetDB().then((db) => {
60
- return db.objectStoreNames.contains(this.state.resourceEndpoint);
61
- });
62
- }
63
-
64
- async getAll() {
65
- if (!(await this.storeExists())) {
66
- return null;
67
- }
68
- const db = await this.checkAndGetDB();
69
- return db.getAll(this.state.resourceEndpoint);
70
- }
71
-
72
- async getItemsByColumn(columnName, value) {
73
- if (!(await this.storeExists())) {
74
- return null;
75
- }
76
- const db = await this.checkAndGetDB();
77
- const tx = db.transaction(this.state.resourceEndpoint, "readonly");
78
- const store = tx.objectStore(this.state.resourceEndpoint);
79
- const index = store.index(columnName);
80
- const results = await index.getAll(value);
81
- await tx.done;
82
- return results;
83
- }
84
-
85
- async get(id) {
86
- if (!(await this.storeExists())) {
87
- return null;
88
- }
89
- const db = await this.checkAndGetDB();
90
- return db.get(this.state.resourceEndpoint, id);
91
- }
92
-
93
- async saveItems(items) {
94
- const db = await this.checkAndGetDB();
95
- const tx = db.transaction(this.state.resourceEndpoint, "readwrite");
96
- const store = tx.objectStore(this.state.resourceEndpoint);
97
- await Promise.all(items.map((item) => store.put(item)));
98
- await tx.done;
99
- }
100
-
101
- async saveItem(item) {
102
- const db = await this.checkAndGetDB();
103
- return db.put(this.state.resourceEndpoint, item);
104
- }
105
-
106
- async getItemsByFilters() {
107
- if (!(await this.storeExists())) {
108
- return null;
109
- }
110
- const db = await this.checkAndGetDB();
111
- if (Object.keys(this.state.filters).length === 0) {
112
- return this.getAll();
113
- }
114
-
115
- const filterPromises = Object.entries(this.state.filters).map(
116
- async ([key, value]) => {
117
- const column = this.state.columns.find((col) => col.name === key);
118
- if (column && (column.sortable || column.externalFilter === false)) {
119
- return this.getItemsByColumn(key, value);
120
- }
121
- return null;
122
- }
123
- );
124
-
125
- const filterResults = await Promise.all(filterPromises);
126
- return filterResults.reduce((acc, curr) => {
127
- if (!acc || acc.length === 0) return curr;
128
- return acc.filter((item) => curr.some((result) => result.id === item.id));
129
- }, []);
130
- }
131
- }
1
+ import {openDB} from 'idb';
2
+
3
+ const DB_NAME = 'ControleOnline';
4
+
5
+ export default class localDB {
6
+ constructor(state) {
7
+ this.state = state;
8
+ this.db = null;
9
+ this.opening = null;
10
+ }
11
+
12
+ async initDB() {
13
+ const endpoint = this.state.resourceEndpoint;
14
+ const upgrade = db => {
15
+ if (db.objectStoreNames.contains(endpoint)) return;
16
+ const store = db.createObjectStore(endpoint, {keyPath: 'id'});
17
+ (this.state.columns || []).forEach(column => {
18
+ if (column.name && (column.sortable || column.externalFilter === false)) {
19
+ store.createIndex(column.name, column.name, {unique: false});
20
+ }
21
+ });
22
+ };
23
+ const blocking = () => { this.db?.close(); this.db = null; };
24
+ let db = await openDB(DB_NAME, undefined, {upgrade, blocking});
25
+ if (!db.objectStoreNames.contains(endpoint)) {
26
+ const version = db.version + 1;
27
+ db.close();
28
+ db = await openDB(DB_NAME, version, {upgrade, blocking});
29
+ }
30
+ this.db = db;
31
+ return db;
32
+ }
33
+
34
+ async getCurrentVersion() {
35
+ return (await this.checkAndGetDB()).version;
36
+ }
37
+
38
+ checkAndGetDB() {
39
+ if (this.db) return Promise.resolve(this.db);
40
+ if (!this.opening) {
41
+ this.opening = this.initDB().finally(() => { this.opening = null; });
42
+ }
43
+ return this.opening;
44
+ }
45
+
46
+ async storeExists() {
47
+ return (await this.checkAndGetDB()).objectStoreNames.contains(this.state.resourceEndpoint);
48
+ }
49
+
50
+ async getAll() {
51
+ return (await this.checkAndGetDB()).getAll(this.state.resourceEndpoint);
52
+ }
53
+
54
+ async getItemsByColumn(columnName, value) {
55
+ const db = await this.checkAndGetDB();
56
+ const tx = db.transaction(this.state.resourceEndpoint, 'readonly');
57
+ if (!tx.store.indexNames.contains(columnName)) return [];
58
+ const items = await tx.store.index(columnName).getAll(value);
59
+ await tx.done;
60
+ return items;
61
+ }
62
+
63
+ async get(id) {
64
+ return (await this.checkAndGetDB()).get(this.state.resourceEndpoint, id);
65
+ }
66
+
67
+ async saveItems(items) {
68
+ const db = await this.checkAndGetDB();
69
+ const tx = db.transaction(this.state.resourceEndpoint, 'readwrite');
70
+ await Promise.all(items.map(item => tx.store.put(item)));
71
+ await tx.done;
72
+ }
73
+
74
+ async saveItem(item) {
75
+ return (await this.checkAndGetDB()).put(this.state.resourceEndpoint, item);
76
+ }
77
+
78
+ async getItemsByFilters() {
79
+ const filters = Object.entries(this.state.filters || {});
80
+ if (!filters.length) return this.getAll();
81
+ const matches = await Promise.all(filters.map(([key, value]) =>
82
+ this.getItemsByColumn(key, value)));
83
+ return matches.reduce((items, next) => items.filter(item =>
84
+ next.some(result => result.id === item.id)));
85
+ }
86
+ }
@@ -1,175 +1,179 @@
1
- import React, {useLayoutEffect, useMemo} from 'react';
2
- import {Pressable, Text, View} from 'react-native';
3
- import Icon from 'react-native-vector-icons/Feather';
4
- import {useTheme} from './DefaultProvider';
5
- import RuntimeInfoFooter from './RuntimeInfoFooter';
1
+ import React, {useLayoutEffect, useMemo} from 'react';
2
+ import {Pressable, Text, View} from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import {useTheme} from './DefaultProvider';
5
+ import RuntimeInfoFooter from './RuntimeInfoFooter';
6
6
  import {resolveMenuRouteParams} from '../utils/menuNavigation';
7
- import createStyles from './BottomNavigationBar.styles';
8
-
9
- const BottomNavigationBar = ({
10
- navigation,
11
- items = [],
12
- activeRouteName,
13
- disabled = false,
14
- colors = {},
15
- testID = 'bottom-navigation',
16
- useModernWebChromeProps = false,
17
- }) => {
18
- const theme = useTheme?.() || {};
19
- const runtimeFooter = theme?.runtimeFooter || null;
20
- const registerBottomNavigation =
21
- theme?.bottomChrome?.registerBottomNavigation || null;
22
- const activeBackground = colors.navigationActiveBackground;
23
- const activeBorder = colors.navigationActiveBorder;
24
- const activeIcon = colors.navigationActiveIcon;
25
- const activeText = colors.navigationActiveText;
26
- const dockBackground = colors.navigationBackground;
27
- const dockBorder = colors.navigationBorder;
28
- const dockShadow = colors.navigationShadow;
29
- const inactiveIcon = colors.navigationIcon;
30
- const inactiveText = colors.navigationText;
31
-
32
- const styles = useMemo(
33
- () =>
34
- createStyles({
35
- activeBackground,
36
- dockBackground,
37
- dockBorder,
38
- dockShadow,
39
- activeBorder,
40
- useModernWebChromeProps,
41
- }),
42
- [
43
- activeBackground,
44
- activeBorder,
45
- dockBackground,
46
- dockBorder,
47
- dockShadow,
48
- useModernWebChromeProps,
49
- ],
50
- );
51
-
52
- const resolveItemColors = ({isActive, isDisabled}) => {
53
- if (isActive) {
54
- return {
55
- iconColor: activeIcon,
56
- textColor: activeText,
57
- };
58
- }
59
-
60
- if (isDisabled) {
61
- return {
62
- iconColor: colors.navigationDisabledIcon,
63
- textColor: colors.navigationDisabledText,
64
- };
65
- }
66
-
67
- return {
68
- iconColor: inactiveIcon,
69
- textColor: inactiveText,
70
- };
71
- };
72
-
73
- const resolveItemStateStyles = ({isActive, isDisabled}) => [
74
- styles.item,
75
- isActive && styles.itemActive,
76
- isDisabled && styles.itemDisabled,
77
- isDisabled && {
78
- backgroundColor: colors.navigationDisabledBackground,
79
- borderColor: colors.navigationDisabledBorder,
80
- },
81
- ];
82
-
83
- const resolvePressedStyles = ({pressed, isDisabled}) =>
84
- pressed && !isDisabled
85
- ? [
86
- styles.itemPressed,
87
- ]
88
- : [];
89
-
90
- useLayoutEffect(() => {
91
- if (typeof registerBottomNavigation !== 'function') {
92
- return undefined;
93
- }
94
-
95
- return registerBottomNavigation();
96
- }, [registerBottomNavigation]);
97
-
98
- const routeItems = Array.isArray(items) ? items : [];
99
- const knownRoute = routeItems.some(item => item?.route === activeRouteName);
100
- const effectiveActiveRoute = knownRoute
101
- ? activeRouteName
102
- : routeItems[0]?.route || '';
103
-
104
- const navigateTo = item => {
105
- try {
106
- navigation?.navigate?.(item?.route, resolveMenuRouteParams(item?.routeParams));
107
- } catch {
108
- // Keep the footer stable if a route is unavailable in the current app flavor.
109
- }
110
- };
111
-
112
- const hostProps = useModernWebChromeProps
113
- ? {}
114
- : {pointerEvents: 'box-none'};
115
- const hostStyle = useModernWebChromeProps
116
- ? [styles.host, styles.hostPointerEventsBoxNone]
117
- : styles.host;
118
- const footerProps = {
119
- appVersion: runtimeFooter?.appVersion,
120
- colors: runtimeFooter?.colors || {},
121
- defaultCompany: runtimeFooter?.defaultCompany,
122
- device: runtimeFooter?.device,
123
- useModernWebChromeProps,
124
- };
125
-
126
- return (
127
- <View {...hostProps} style={hostStyle}>
128
- <View style={styles.stack}>
129
- <View style={styles.dock} testID={testID}>
130
- {routeItems.map(item => {
131
- const isActive = effectiveActiveRoute === item.route;
132
- const isDisabled = disabled || item.disabled;
133
- const iconSize = item.iconSize || 18;
134
- const {iconColor, textColor} = resolveItemColors({
135
- isActive,
136
- isDisabled,
137
- });
138
-
139
- return (
140
- <Pressable
141
- key={item.route}
142
- accessibilityRole="button"
143
- disabled={isDisabled}
144
- onPress={() => navigateTo(item)}
145
- style={({pressed}) => [
146
- ...resolveItemStateStyles({isActive, isDisabled}),
147
- ...resolvePressedStyles({pressed, isDisabled}),
148
- ]}>
149
- <View style={styles.iconWrap}>
150
- <Icon color={iconColor} name={item.icon} size={iconSize} />
151
- </View>
152
- <Text
153
- numberOfLines={1}
154
- style={[
155
- styles.itemLabel,
156
- {
157
- color: textColor,
158
- },
159
- ]}>
160
- {item.label}
161
- </Text>
162
- </Pressable>
163
- );
164
- })}
165
- </View>
166
-
167
- {runtimeFooter && (
168
- <RuntimeInfoFooter {...footerProps} />
169
- )}
170
- </View>
171
- </View>
172
- );
173
- };
174
-
175
- export default BottomNavigationBar;
7
+ import createStyles from './BottomNavigationBar.styles';
8
+
9
+ const BottomNavigationBar = ({
10
+ navigation,
11
+ items = [],
12
+ activeRouteName,
13
+ disabled = false,
14
+ colors = {},
15
+ testID = 'bottom-navigation',
16
+ useModernWebChromeProps = false,
17
+ }) => {
18
+ const theme = useTheme?.() || {};
19
+ const runtimeFooter = theme?.runtimeFooter || null;
20
+ const registerBottomNavigation =
21
+ theme?.bottomChrome?.registerBottomNavigation || null;
22
+ const activeBackground = colors.navigationActiveBackground;
23
+ const activeBorder = colors.navigationActiveBorder;
24
+ const activeIcon = colors.navigationActiveIcon;
25
+ const activeText = colors.navigationActiveText;
26
+ const dockBackground = colors.navigationBackground;
27
+ const dockBorder = colors.navigationBorder;
28
+ const dockShadow = colors.navigationShadow;
29
+ const inactiveIcon = colors.navigationIcon;
30
+ const inactiveText = colors.navigationText;
31
+
32
+ const styles = useMemo(
33
+ () =>
34
+ createStyles({
35
+ activeBackground,
36
+ dockBackground,
37
+ dockBorder,
38
+ dockShadow,
39
+ activeBorder,
40
+ useModernWebChromeProps,
41
+ }),
42
+ [
43
+ activeBackground,
44
+ activeBorder,
45
+ dockBackground,
46
+ dockBorder,
47
+ dockShadow,
48
+ useModernWebChromeProps,
49
+ ],
50
+ );
51
+
52
+ const resolveItemColors = ({isActive, isDisabled}) => {
53
+ if (isActive) {
54
+ return {
55
+ iconColor: activeIcon,
56
+ textColor: activeText,
57
+ };
58
+ }
59
+
60
+ if (isDisabled) {
61
+ return {
62
+ iconColor: colors.navigationDisabledIcon,
63
+ textColor: colors.navigationDisabledText,
64
+ };
65
+ }
66
+
67
+ return {
68
+ iconColor: inactiveIcon,
69
+ textColor: inactiveText,
70
+ };
71
+ };
72
+
73
+ const resolveItemStateStyles = ({isActive, isDisabled}) => [
74
+ styles.item,
75
+ isActive && styles.itemActive,
76
+ isDisabled && styles.itemDisabled,
77
+ isDisabled && {
78
+ backgroundColor: colors.navigationDisabledBackground,
79
+ borderColor: colors.navigationDisabledBorder,
80
+ },
81
+ ];
82
+
83
+ const resolvePressedStyles = ({pressed, isDisabled}) =>
84
+ pressed && !isDisabled
85
+ ? [
86
+ styles.itemPressed,
87
+ ]
88
+ : [];
89
+
90
+ useLayoutEffect(() => {
91
+ if (typeof registerBottomNavigation !== 'function') {
92
+ return undefined;
93
+ }
94
+
95
+ return registerBottomNavigation();
96
+ }, [registerBottomNavigation]);
97
+
98
+ const routeItems = Array.isArray(items) ? items : [];
99
+ const knownRoute = routeItems.some(item => item?.route === activeRouteName);
100
+ const effectiveActiveRoute = knownRoute
101
+ ? activeRouteName
102
+ : routeItems[0]?.route || '';
103
+
104
+ const navigateTo = item => {
105
+ try {
106
+ navigation?.navigate?.(item?.route, resolveMenuRouteParams(item?.routeParams));
107
+ } catch {
108
+ // Keep the footer stable if a route is unavailable in the current app flavor.
109
+ }
110
+ };
111
+
112
+ const hostProps = useModernWebChromeProps
113
+ ? {}
114
+ : {pointerEvents: 'box-none'};
115
+ const hostStyle = useModernWebChromeProps
116
+ ? [styles.host, styles.hostPointerEventsBoxNone]
117
+ : styles.host;
118
+ const footerProps = {
119
+ appVersion: runtimeFooter?.appVersion,
120
+ colors: runtimeFooter?.colors || {},
121
+ mainCompany: runtimeFooter?.mainCompany,
122
+ device: runtimeFooter?.device,
123
+ useModernWebChromeProps,
124
+ };
125
+
126
+ return (
127
+ <View {...hostProps} style={hostStyle}>
128
+ <View style={styles.stack}>
129
+ <View style={styles.dock} testID={testID}>
130
+ <View style={styles.itemsRow}>
131
+ {routeItems.map(item => {
132
+ const isActive = effectiveActiveRoute === item.route;
133
+ const isDisabled = disabled || item.disabled;
134
+ const iconSize = item.iconSize || 18;
135
+ const {iconColor, textColor} = resolveItemColors({
136
+ isActive,
137
+ isDisabled,
138
+ });
139
+
140
+ return (
141
+ <Pressable
142
+ key={item.route}
143
+ accessibilityRole="button"
144
+ disabled={isDisabled}
145
+ onPress={() => navigateTo(item)}
146
+ style={({pressed}) => [
147
+ ...resolveItemStateStyles({isActive, isDisabled}),
148
+ ...resolvePressedStyles({pressed, isDisabled}),
149
+ ]}>
150
+ <View style={styles.iconWrap}>
151
+ <Icon color={iconColor} name={item.icon} size={iconSize} />
152
+ </View>
153
+ <Text
154
+ numberOfLines={1}
155
+ style={[
156
+ styles.itemLabel,
157
+ {
158
+ color: textColor,
159
+ },
160
+ ]}>
161
+ {item.label}
162
+ </Text>
163
+ </Pressable>
164
+ );
165
+ })}
166
+ </View>
167
+ {/* app-community#384: texto do rodapé DEPOIS dos botões (não antes) */}
168
+ {runtimeFooter ? (
169
+ <View style={styles.footerSlot} testID="bottom-navigation-footer-slot">
170
+ <RuntimeInfoFooter {...footerProps} />
171
+ </View>
172
+ ) : null}
173
+ </View>
174
+ </View>
175
+ </View>
176
+ );
177
+ };
178
+
179
+ export default BottomNavigationBar;