@controleonline/ui-common 1.2.70 → 1.2.71

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/package.json +31 -28
  2. package/src/api/index.js +237 -37
  3. package/src/react/components/AddImportModal.js +1 -1
  4. package/src/react/components/AnimatedModal.js +171 -0
  5. package/src/react/components/AnimatedModal.styles.js +21 -0
  6. package/src/react/components/AppTypeSwitcher.js +164 -0
  7. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  8. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  9. package/src/react/components/BottomNavigationBar.js +86 -31
  10. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  11. package/src/react/components/DefaultProvider.native.js +68 -66
  12. package/src/react/components/DefaultProvider.web.js +44 -42
  13. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  14. package/src/react/components/DeviceAlertSoundService.js +3 -3
  15. package/src/react/components/KioskModeBridge.js +2 -2
  16. package/src/react/components/LauncherModeBridge.js +2 -2
  17. package/src/react/components/ManagerPushBridge.native.js +2 -2
  18. package/src/react/components/RemoteCheckoutService.js +28 -20
  19. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  20. package/src/react/components/RuntimeInfoFooter.js +37 -9
  21. package/src/react/components/StateStore.js +258 -0
  22. package/src/react/components/WebsocketListener.native.js +11 -11
  23. package/src/react/config/deviceConfigBootstrap.js +66 -27
  24. package/src/react/css/orders.js +72 -0
  25. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  26. package/src/react/pages/SettingsPage/index.js +1266 -1167
  27. package/src/react/print/providers/local.js +1 -1
  28. package/src/react/router/publicRoutes.js +25 -0
  29. package/src/react/services/Cielo/Checkout.js +39 -0
  30. package/src/react/services/Cielo/Cielo.js +193 -0
  31. package/src/react/services/Cielo/Print.js +7 -0
  32. package/src/react/services/InfinitePay/Checkout.js +33 -0
  33. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  34. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  35. package/src/react/styles/global.js +115 -0
  36. package/src/react/utils/fileUrl.js +182 -16
  37. package/src/react/utils/menuNavigation.js +31 -0
  38. package/src/react/utils/orderIdentity.js +277 -0
  39. package/src/react/utils/remotePayment.js +115 -61
  40. package/src/react/utils/runtimeMenu.js +35 -3
  41. package/src/react/utils/shopConfig.js +50 -24
  42. package/src/react/utils/shopFranchises.js +56 -22
  43. package/src/react/utils/socketRuntimePipeline.js +14 -14
  44. package/src/store/configs/index.js +2 -2
  45. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  46. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  47. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  48. package/src/tests/react/services/Cielo.test.js +190 -0
  49. package/src/tests/react/utils/fileUrl.test.js +62 -0
  50. package/src/tests/react/utils/importStatus.test.js +2 -2
  51. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  52. package/src/tests/react/utils/remotePayment.test.js +62 -0
  53. package/src/tests/react/utils/runtimeFooter.test.js +4 -6
  54. package/src/tests/react/utils/runtimeLanguage.test.js +2 -2
  55. package/src/tests/react/utils/runtimeMenu.test.js +23 -3
  56. package/src/tests/react/utils/shopConfig.test.js +74 -0
  57. package/src/tests/react/utils/shopFranchises.test.js +79 -12
  58. package/src/tests/react/utils/translate.test.mjs +166 -102
  59. package/src/utils/formatter.js +18 -0
  60. package/src/utils/integrationConfigs.js +80 -79
  61. package/src/utils/translate.js +125 -30
package/package.json CHANGED
@@ -1,28 +1,31 @@
1
- {
2
- "name": "@controleonline/ui-common",
3
- "version": "1.2.70",
4
- "author": "ControleOnline",
5
- "description": "ControleOnline Common Quasar UI Component",
6
- "license": "MIT",
7
- "main": "src/react/index.js",
8
- "scripts": {
9
- "test": "npm run test:imports-status && npm run test:runtime-language && npm run test:runtime-footer && npm run test:invoice-presentation",
10
- "test:imports-status": "mkdir -p node_modules/@controleonline && ln -sfn ../.. node_modules/@controleonline/ui-common && node --test src/tests/react/utils/importStatus.test.js",
11
- "test:runtime-language": "node --test src/tests/react/utils/runtimeLanguage.test.js",
12
- "test:runtime-footer": "node --test src/tests/react/utils/runtimeFooter.test.js",
13
- "test:invoice-presentation": "node --test src/tests/react/utils/invoicePresentation.test.mjs",
14
- "test:coverage": "c8 --reporter=clover --report-dir coverage npm test"
15
- },
16
- "devDependencies": {
17
- "c8": "^10.1.3"
18
- },
19
- "repository": {
20
- "type": "git",
21
- "url": "https://github.com/ControleOnline/ui-common.git"
22
- },
23
- "bugs": "",
24
- "homepage": "https://www.controleonline.com/devs/plugins",
25
- "browserslist": [
26
- "last 1 version, not dead, ie >= 11"
27
- ]
28
- }
1
+ {
2
+ "name": "@controleonline/ui-common",
3
+ "version": "1.2.71",
4
+ "author": "ControleOnline",
5
+ "description": "ControleOnline Common Quasar UI Component",
6
+ "license": "MIT",
7
+ "main": "src/react/index.js",
8
+ "scripts": {
9
+ "test": "npm run test:imports-status && npm run test:runtime-language && npm run test:runtime-footer && npm run test:invoice-presentation",
10
+ "test:imports-status": "mkdir -p node_modules/@controleonline && ln -sfn ../.. node_modules/@controleonline/ui-common && node --test src/tests/react/utils/importStatus.test.js",
11
+ "test:runtime-language": "node --test src/tests/react/utils/runtimeLanguage.test.js",
12
+ "test:runtime-footer": "node --test src/tests/react/utils/runtimeFooter.test.js",
13
+ "test:invoice-presentation": "node --test src/tests/react/utils/invoicePresentation.test.mjs",
14
+ "test:coverage": "c8 --reporter=clover --report-dir coverage npm test"
15
+ },
16
+ "devDependencies": {
17
+ "c8": "^10.1.3"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/ControleOnline/ui-common.git"
22
+ },
23
+ "bugs": "",
24
+ "homepage": "https://www.controleonline.com/devs/plugins",
25
+ "browserslist": [
26
+ "last 1 version, not dead, ie >= 11"
27
+ ],
28
+ "dependencies": {
29
+ "@controleonline/ui-default": "*"
30
+ }
31
+ }
package/src/api/index.js CHANGED
@@ -3,9 +3,177 @@ import axios from 'axios';
3
3
  import { APP_ENV } from '../../../../../config/env';
4
4
  import { resolveAppDomain } from '@controleonline/ui-common/src/utils/appDomain';
5
5
  import { resolveApiEntryPoint } from '@controleonline/ui-common/src/utils/apiEntryPoint';
6
-
7
- const MIME_TYPE = 'application/ld+json';
8
-
6
+
7
+ const MIME_TYPE = 'application/ld+json';
8
+
9
+ function encodeBasicAuth(user, password) {
10
+ const credentials = `${String(user || '').trim()}:${String(password || '').trim()}`;
11
+
12
+ if (credentials === ':') {
13
+ return '';
14
+ }
15
+
16
+ if (typeof btoa === 'function') {
17
+ return btoa(credentials);
18
+ }
19
+
20
+ if (typeof Buffer !== 'undefined') {
21
+ return Buffer.from(credentials, 'utf8').toString('base64');
22
+ }
23
+
24
+ return '';
25
+ }
26
+
27
+ function resolveApiRuntimeConfig(config = {}) {
28
+ return {
29
+ apiBaseUrl: String(config.apiBaseUrl || APP_ENV.API_ENTRYPOINT || '').trim(),
30
+ domain: String(config.domain || APP_ENV.DOMAIN || '').trim(),
31
+ htaccessUser: String(config.htaccessUser || APP_ENV.HTACCESS_USER || '').trim(),
32
+ htaccessPassword: String(config.htaccessPassword || APP_ENV.HTACCESS_PASSWORD || '').trim(),
33
+ };
34
+ }
35
+
36
+ function buildApiUrl(apiBaseUrl, path) {
37
+ const apiEntryPoint = resolveApiEntryPoint(apiBaseUrl);
38
+ const entryPoint = apiEntryPoint + (apiEntryPoint.endsWith('/') ? '' : '/');
39
+ const normalizedPath = String(path || '').startsWith('/')
40
+ ? String(path || '').substring(1)
41
+ : String(path || '');
42
+
43
+ return new URL(normalizedPath, entryPoint).href;
44
+ }
45
+
46
+ async function readSmokeResponseBody(response, responseType) {
47
+ if (responseType === 'blob') {
48
+ return await response.blob();
49
+ }
50
+
51
+ if (responseType === 'text') {
52
+ return await response.text();
53
+ }
54
+
55
+ const text = await response.text();
56
+
57
+ if (!text) {
58
+ return null;
59
+ }
60
+
61
+ try {
62
+ return JSON.parse(text);
63
+ } catch (error) {
64
+ return text;
65
+ }
66
+ }
67
+
68
+ function buildSmokeHttpError(response, body) {
69
+ const message =
70
+ String(body?.message || body?.error || body || response.statusText || 'Request failed')
71
+ .trim() || 'Request failed';
72
+
73
+ const error = new Error(message);
74
+ error.code = response.status;
75
+ error.status = response.status;
76
+ error.body = body;
77
+ return error;
78
+ }
79
+
80
+ async function requestSmoke(path, options = {}, config = {}) {
81
+ const runtimeConfig = resolveApiRuntimeConfig(config);
82
+ const url = buildApiUrl(runtimeConfig.apiBaseUrl, path);
83
+ const headers = new Headers(options.headers || undefined);
84
+ const accept =
85
+ options.responseType === 'blob' || options.responseType === 'text'
86
+ ? '*/*'
87
+ : MIME_TYPE;
88
+
89
+ if (accept !== '' && !headers.has('Accept')) {
90
+ headers.set('Accept', accept);
91
+ }
92
+
93
+ if (options.responseType !== 'blob' && !headers.has('Content-Type')) {
94
+ headers.set('Content-Type', MIME_TYPE);
95
+ }
96
+
97
+ const sessionToken = await api.getToken();
98
+
99
+ if (sessionToken && !headers.has('API-TOKEN')) {
100
+ headers.set('API-TOKEN', sessionToken);
101
+ }
102
+
103
+ const basicAuth = encodeBasicAuth(runtimeConfig.htaccessUser, runtimeConfig.htaccessPassword);
104
+ if (basicAuth !== '' && !headers.has('Authorization')) {
105
+ headers.set('Authorization', `Basic ${basicAuth}`);
106
+ }
107
+
108
+ const appDomain = resolveAppDomain(runtimeConfig.domain);
109
+ if (appDomain !== '' && !headers.has('App-Domain')) {
110
+ headers.set('App-Domain', appDomain);
111
+ }
112
+
113
+ let body = options.body;
114
+ const hasBody =
115
+ body !== undefined &&
116
+ body !== null &&
117
+ options.method !== 'GET' &&
118
+ options.method !== 'HEAD';
119
+
120
+ if (hasBody && typeof body !== 'string' && !(body instanceof FormData) && !(body instanceof Blob)) {
121
+ body = JSON.stringify(body);
122
+ }
123
+
124
+ if (options.params) {
125
+ const params = api.serialize(options.params);
126
+ if (params.length > 0) {
127
+ const urlObject = new URL(url);
128
+ urlObject.search = params.join('&');
129
+ return requestSmokeUrl(urlObject.toString(), {
130
+ ...options,
131
+ body,
132
+ headers,
133
+ });
134
+ }
135
+ }
136
+
137
+ return requestSmokeUrl(url, {
138
+ ...options,
139
+ body,
140
+ headers,
141
+ });
142
+ }
143
+
144
+ async function requestSmokeUrl(url, options) {
145
+ const response = await fetch(url, options);
146
+
147
+ if (options.responseType === 'blob') {
148
+ if (!response.ok) {
149
+ throw buildSmokeHttpError(response, await response.text().catch(() => ''));
150
+ }
151
+
152
+ return await response.blob();
153
+ }
154
+
155
+ const responseBody = await readSmokeResponseBody(response, options.responseType);
156
+
157
+ if (!response.ok) {
158
+ throw buildSmokeHttpError(response, responseBody);
159
+ }
160
+
161
+ if (
162
+ responseBody &&
163
+ typeof responseBody === 'object' &&
164
+ (responseBody['@type'] === 'Error' ||
165
+ responseBody['@type'] === 'ConstraintViolationList')
166
+ ) {
167
+ throw buildSmokeHttpError(response, responseBody);
168
+ }
169
+
170
+ if (options.responseType === 'text') {
171
+ return typeof responseBody === 'string' ? responseBody : '';
172
+ }
173
+
174
+ return responseBody;
175
+ }
176
+
9
177
  export const api = {
10
178
  device: JSON.parse(localStorage.getItem('device') || '{}'),
11
179
  masterDevice: JSON.parse(localStorage.getItem('master-device') || '{}'),
@@ -17,19 +185,25 @@ export const api = {
17
185
  const apiEntryPoint = resolveApiEntryPoint(APP_ENV.API_ENTRYPOINT);
18
186
  const entryPoint = apiEntryPoint + (apiEntryPoint.endsWith("/") ? "" : "/");
19
187
  const url = new URL(uri.startsWith('/') ? uri.substring(1) : uri, entryPoint).href;
188
+ const basicAuth = encodeBasicAuth(APP_ENV.HTACCESS_USER, APP_ENV.HTACCESS_PASSWORD);
189
+ const headers = {
190
+ 'API-TOKEN': token,
191
+ 'App-Domain': appDomain,
192
+ 'Accept': '*/*',
193
+ // IMPORTANTE: Deixe vazio para o Axios/Browser definir o boundary do multipart
194
+ };
195
+
196
+ if (basicAuth !== '') {
197
+ headers.Authorization = `Basic ${basicAuth}`;
198
+ }
20
199
 
21
200
  return axios.post(url, formData, {
22
- headers: {
23
- 'API-TOKEN': token,
24
- 'App-Domain': appDomain,
25
- 'Accept': '*/*',
26
- // IMPORTANTE: Deixe vazio para o Axios/Browser definir o boundary do multipart
27
- }
28
- });
29
- },
30
- fetch: async function (uri, options = {}) {
31
- if (typeof options.headers === 'undefined')
32
- Object.assign(options, { headers: new Headers() });
201
+ headers,
202
+ });
203
+ },
204
+ fetch: async function (uri, options = {}) {
205
+ if (typeof options.headers === 'undefined')
206
+ Object.assign(options, { headers: new Headers() });
33
207
 
34
208
  // Always refresh device from localStorage in case it was set after module load
35
209
  try {
@@ -45,14 +219,19 @@ export const api = {
45
219
  } catch (e) {
46
220
  this.masterDevice = {};
47
221
  }
48
-
49
- let token = await this.getToken();
50
- if (token) options.headers.set('API-TOKEN', token);
51
-
52
- let bodyDeviceId = null;
53
- if (options.body) {
54
- if (typeof options.body === 'object') {
55
- bodyDeviceId = options.body.device || null;
222
+
223
+ let token = await this.getToken();
224
+ if (token) options.headers.set('API-TOKEN', token);
225
+
226
+ const basicAuth = encodeBasicAuth(APP_ENV.HTACCESS_USER, APP_ENV.HTACCESS_PASSWORD);
227
+ if (basicAuth !== '' && !options.headers.has('Authorization')) {
228
+ options.headers.set('Authorization', `Basic ${basicAuth}`);
229
+ }
230
+
231
+ let bodyDeviceId = null;
232
+ if (options.body) {
233
+ if (typeof options.body === 'object') {
234
+ bodyDeviceId = options.body.device || null;
56
235
  } else if (typeof options.body === 'string') {
57
236
  try {
58
237
  const parsedBody = JSON.parse(options.body);
@@ -71,10 +250,10 @@ export const api = {
71
250
  options.headers.set('Accept', MIME_TYPE);
72
251
  }
73
252
  options.headers.set('App-Domain', resolveAppDomain(APP_ENV.DOMAIN));
74
-
75
- if (options.body && typeof options.body != 'string') {
76
- options.body = JSON.stringify(options.body);
77
- }
253
+
254
+ if (options.body && typeof options.body != 'string') {
255
+ options.body = JSON.stringify(options.body);
256
+ }
78
257
  if (options.params) {
79
258
  uri = this.buildQueryString(uri, options);
80
259
  }
@@ -142,14 +321,35 @@ export const api = {
142
321
  }
143
322
  return uri;
144
323
  },
145
- post: async function (uri, body = {}) {
146
- const options = {
147
- method: 'POST',
148
- body: body,
149
- };
150
- return await this.fetch(uri, options);
151
- },
152
- execute: function (params) {
153
- return axios(params);
154
- },
155
- };
324
+ post: async function (uri, body = {}) {
325
+ const options = {
326
+ method: 'POST',
327
+ body: body,
328
+ };
329
+ return await this.fetch(uri, options);
330
+ },
331
+ loadSmokeIndex: async function (config = {}) {
332
+ return await requestSmoke('/tests', {}, config);
333
+ },
334
+ loadArtifactBlob: async function (config = {}, artifact) {
335
+ if (!artifact || typeof artifact !== 'object') {
336
+ throw new Error('Artefato inválido.');
337
+ }
338
+
339
+ if (!artifact.url) {
340
+ throw new Error('Artefato inválido.');
341
+ }
342
+
343
+ return await requestSmoke(artifact.url, {
344
+ responseType: 'blob',
345
+ }, config);
346
+ },
347
+ triggerSmokeRun: async function (config = {}) {
348
+ return await requestSmoke('/tests/run', {
349
+ method: 'POST',
350
+ }, config);
351
+ },
352
+ execute: function (params) {
353
+ return axios(params);
354
+ },
355
+ };
@@ -11,7 +11,7 @@ import {
11
11
  import Icon from 'react-native-vector-icons/MaterialIcons';
12
12
  import * as DocumentPicker from 'expo-document-picker';
13
13
  import { useStore } from '@store';
14
- import AnimatedModal from '@controleonline/ui-crm/src/react/components/AnimatedModal';
14
+ import AnimatedModal from './AnimatedModal';
15
15
  import { api } from '@controleonline/ui-common/src/api';
16
16
  import { useMessage } from '@controleonline/ui-common/src/react/components/MessageService';
17
17
  import styles from './AddImportModal.styles';
@@ -0,0 +1,171 @@
1
+ import React, {useEffect, useRef, useState} from 'react';
2
+ import {
3
+ Animated,
4
+ Dimensions,
5
+ Keyboard,
6
+ KeyboardAvoidingView,
7
+ Modal,
8
+ Platform,
9
+ StyleSheet,
10
+ Text,
11
+ } from 'react-native';
12
+ import {GestureHandlerRootView} from 'react-native-gesture-handler';
13
+ import {toast, Toasts} from '@backpackapp-io/react-native-toast';
14
+ import styles from './AnimatedModal.styles';
15
+ import {
16
+ TOAST_EXTRA_INSETS,
17
+ TOAST_MODAL_COUNT_KEY,
18
+ TOAST_PROVIDER_KEYS,
19
+ } from './toastConfig';
20
+
21
+ const {height: SCREEN_HEIGHT} = Dimensions.get('window');
22
+
23
+ const incrementModalToastDepth = () => {
24
+ const currentDepth = Number(global?.[TOAST_MODAL_COUNT_KEY] || 0);
25
+ global[TOAST_MODAL_COUNT_KEY] = currentDepth + 1;
26
+ };
27
+
28
+ const decrementModalToastDepth = () => {
29
+ const currentDepth = Number(global?.[TOAST_MODAL_COUNT_KEY] || 0);
30
+ global[TOAST_MODAL_COUNT_KEY] = Math.max(0, currentDepth - 1);
31
+ };
32
+
33
+ /**
34
+ * Modal com overlay aparecendo na hora e apenas o conteúdo subindo de baixo.
35
+ */
36
+ export default function AnimatedModal({
37
+ visible,
38
+ onRequestClose,
39
+ children,
40
+ style,
41
+ }) {
42
+ const [showing, setShowing] = useState(visible);
43
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
44
+ const overlayOpacity = useRef(new Animated.Value(0)).current;
45
+ const slideY = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
46
+
47
+ useEffect(() => {
48
+ if (visible) {
49
+ setShowing(true);
50
+ slideY.setValue(SCREEN_HEIGHT);
51
+ overlayOpacity.setValue(0);
52
+
53
+ Animated.parallel([
54
+ Animated.spring(slideY, {
55
+ toValue: 0,
56
+ damping: 24,
57
+ stiffness: 300,
58
+ useNativeDriver: true,
59
+ }),
60
+ Animated.timing(overlayOpacity, {
61
+ toValue: 1,
62
+ duration: 300,
63
+ useNativeDriver: true,
64
+ }),
65
+ ]).start();
66
+ }
67
+ }, [visible, overlayOpacity, slideY]);
68
+
69
+ useEffect(() => {
70
+ if (!visible && showing) {
71
+ Animated.parallel([
72
+ Animated.timing(overlayOpacity, {
73
+ toValue: 0,
74
+ duration: 150,
75
+ useNativeDriver: true,
76
+ }),
77
+ Animated.timing(slideY, {
78
+ toValue: SCREEN_HEIGHT,
79
+ duration: 200,
80
+ useNativeDriver: true,
81
+ }),
82
+ ]).start(() => {
83
+ setShowing(false);
84
+ setKeyboardHeight(0);
85
+ onRequestClose?.();
86
+ });
87
+ }
88
+ }, [visible, showing, overlayOpacity, slideY, onRequestClose]);
89
+
90
+ useEffect(() => {
91
+ if (Platform.OS !== 'android') {
92
+ return undefined;
93
+ }
94
+
95
+ const showSub = Keyboard.addListener('keyboardDidShow', event => {
96
+ setKeyboardHeight(event?.endCoordinates?.height || 0);
97
+ });
98
+ const hideSub = Keyboard.addListener('keyboardDidHide', () => {
99
+ setKeyboardHeight(0);
100
+ });
101
+
102
+ return () => {
103
+ showSub.remove();
104
+ hideSub.remove();
105
+ };
106
+ }, []);
107
+
108
+ useEffect(() => {
109
+ if (!visible) {
110
+ setKeyboardHeight(0);
111
+ }
112
+ }, [visible]);
113
+
114
+ useEffect(() => {
115
+ if (!showing) {
116
+ return undefined;
117
+ }
118
+
119
+ incrementModalToastDepth();
120
+ return () => {
121
+ decrementModalToastDepth();
122
+ };
123
+ }, [showing]);
124
+
125
+ const overlayStyle = [
126
+ styles.overlay,
127
+ style,
128
+ Platform.OS === 'android' ? {paddingBottom: keyboardHeight} : null,
129
+ ];
130
+
131
+ const normalizedChildren = React.Children.toArray(children).map(child => {
132
+ if (typeof child === 'string' || typeof child === 'number') {
133
+ const text = String(child).trim();
134
+ if (!text || text === '.') return null;
135
+ return <Text>{text}</Text>;
136
+ }
137
+ return child;
138
+ });
139
+
140
+ return (
141
+ <Modal
142
+ visible={showing}
143
+ animationType="none"
144
+ transparent
145
+ statusBarTranslucent
146
+ onRequestClose={onRequestClose}>
147
+ <GestureHandlerRootView style={styles.gestureRoot}>
148
+ <KeyboardAvoidingView
149
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
150
+ style={overlayStyle}>
151
+ <Animated.View
152
+ style={[
153
+ StyleSheet.absoluteFillObject,
154
+ styles.overlayBg,
155
+ {opacity: overlayOpacity},
156
+ ]}
157
+ />
158
+ <Animated.View
159
+ style={[styles.content, {transform: [{translateY: slideY}]}]}>
160
+ {normalizedChildren}
161
+ </Animated.View>
162
+ <Toasts
163
+ providerKey={TOAST_PROVIDER_KEYS.MODAL}
164
+ extraInsets={TOAST_EXTRA_INSETS}
165
+ onToastPress={currentToast => toast.dismiss(currentToast.id)}
166
+ />
167
+ </KeyboardAvoidingView>
168
+ </GestureHandlerRootView>
169
+ </Modal>
170
+ );
171
+ }
@@ -0,0 +1,21 @@
1
+ import {StyleSheet} from 'react-native';
2
+
3
+ const styles = StyleSheet.create({
4
+ gestureRoot: {
5
+ flex: 1,
6
+ },
7
+ overlay: {
8
+ flex: 1,
9
+ justifyContent: 'flex-end',
10
+ },
11
+ overlayBg: {
12
+ backgroundColor: 'rgba(15, 23, 42, 0.4)',
13
+ },
14
+ content: {
15
+ width: '100%',
16
+ flex: 1,
17
+ justifyContent: 'flex-end',
18
+ },
19
+ });
20
+
21
+ export default styles;