@hero-design/rn 8.41.2 → 8.41.3-rc.0

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 (28) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/es/index.js +681 -583
  3. package/lib/index.js +685 -585
  4. package/package.json +8 -7
  5. package/rollup.config.js +1 -0
  6. package/src/components/Error/StyledError.tsx +1 -2
  7. package/src/components/Error/__tests__/__snapshots__/index.spec.tsx.snap +97 -115
  8. package/src/components/Error/__tests__/index.spec.tsx +6 -9
  9. package/src/components/Modal/ModalContentWrapper.tsx +112 -0
  10. package/src/components/Modal/ModalPresenter/ModalPresenter.tsx +135 -0
  11. package/src/components/Modal/ModalPresenter/index.tsx +9 -0
  12. package/src/components/Modal/ModalProvider.tsx +8 -0
  13. package/src/components/Modal/index.tsx +82 -178
  14. package/src/components/Success/StyledSuccess.tsx +1 -2
  15. package/src/components/Success/__tests__/__snapshots__/index.spec.tsx.snap +95 -115
  16. package/src/components/Success/__tests__/index.spec.tsx +6 -9
  17. package/src/index.ts +2 -0
  18. package/testUtils/setup.tsx +18 -0
  19. package/types/components/Error/StyledError.d.ts +5 -3
  20. package/types/components/Modal/ModalContentWrapper.d.ts +16 -0
  21. package/types/components/Modal/ModalPresenter/ModalPresenter.d.ts +34 -0
  22. package/types/components/Modal/ModalPresenter/index.d.ts +3 -0
  23. package/types/components/Modal/ModalProvider.d.ts +5 -0
  24. package/types/components/Modal/index.d.ts +8 -12
  25. package/types/components/Success/StyledSuccess.d.ts +5 -3
  26. package/types/index.d.ts +2 -1
  27. package/src/components/Modal/__tests__/__snapshots__/index.spec.tsx.snap +0 -117
  28. package/src/components/Modal/__tests__/index.spec.tsx +0 -99
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/rn",
3
- "version": "8.41.2",
3
+ "version": "8.41.3-rc.0",
4
4
  "license": "MIT",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -21,14 +21,14 @@
21
21
  "dependencies": {
22
22
  "@emotion/native": "^11.9.3",
23
23
  "@emotion/react": "^11.9.3",
24
- "@hero-design/colors": "8.41.2",
24
+ "@hero-design/colors": "8.41.3-rc.0",
25
25
  "date-fns": "^2.16.1",
26
26
  "events": "^3.2.0",
27
27
  "hero-editor": "^1.9.21",
28
28
  "nanoid": "^4.0.2"
29
29
  },
30
30
  "peerDependencies": {
31
- "@hero-design/react-native-month-year-picker": "^8.41.2",
31
+ "@hero-design/react-native-month-year-picker": "^8.41.3-rc.0",
32
32
  "@react-native-community/datetimepicker": "^3.5.2",
33
33
  "@react-native-community/slider": "4.1.12",
34
34
  "react": "18.0.0",
@@ -36,6 +36,7 @@
36
36
  "react-native-gesture-handler": "^1.10.3 | ~2.5.0",
37
37
  "react-native-linear-gradient": "^2.6.2",
38
38
  "react-native-pager-view": "^5.4.25",
39
+ "react-native-root-siblings": "^4.1.1",
39
40
  "react-native-safe-area-context": "^3.0.2",
40
41
  "react-native-vector-icons": "^9.1.0",
41
42
  "react-native-webview": "^11.2.5"
@@ -47,8 +48,8 @@
47
48
  "@babel/preset-typescript": "^7.17.12",
48
49
  "@babel/runtime": "^7.18.9",
49
50
  "@emotion/jest": "^11.11.0",
50
- "@hero-design/eslint-plugin": "8.41.2",
51
- "@hero-design/react-native-month-year-picker": "^8.41.2",
51
+ "@hero-design/eslint-plugin": "8.41.3-rc.0",
52
+ "@hero-design/react-native-month-year-picker": "^8.41.3-rc.0",
52
53
  "@react-native-community/datetimepicker": "^3.5.2",
53
54
  "@react-native-community/slider": "4.1.12",
54
55
  "@rollup/plugin-babel": "^5.3.1",
@@ -64,12 +65,12 @@
64
65
  "@types/react-native": "^0.67.7",
65
66
  "@types/react-native-vector-icons": "^6.4.10",
66
67
  "babel-plugin-inline-import": "^3.0.0",
67
- "eslint-config-hd": "8.41.2",
68
+ "eslint-config-hd": "8.41.3-rc.0",
68
69
  "eslint-plugin-import": "^2.27.5",
69
70
  "jest": "^29.6.2",
70
71
  "jest-environment-jsdom": "^29.6.2",
71
72
  "jest-junit": "^16.0.0",
72
- "prettier-config-hd": "8.41.2",
73
+ "prettier-config-hd": "8.41.3-rc.0",
73
74
  "react": "18.0.0",
74
75
  "react-native": "0.69.7",
75
76
  "react-native-gesture-handler": "~2.5.0",
package/rollup.config.js CHANGED
@@ -35,6 +35,7 @@ export default {
35
35
  'react-native-vector-icons',
36
36
  'react-native-linear-gradient',
37
37
  '@hero-design/react-native-month-year-picker',
38
+ 'react-native-root-siblings',
38
39
  ],
39
40
  plugins: [
40
41
  replace({
@@ -1,9 +1,8 @@
1
1
  import styled from '@emotion/native';
2
- import { View } from 'react-native';
2
+ import { Modal, View } from 'react-native';
3
3
  import Image from '../Image';
4
4
  import Typography from '../Typography';
5
5
  import Button from '../Button';
6
- import Modal from '../Modal';
7
6
 
8
7
  type ErrorVariant = 'full-screen' | 'in-page';
9
8
 
@@ -245,164 +245,146 @@ exports[`Error renders error screen with image correctly 1`] = `
245
245
  `;
246
246
 
247
247
  exports[`Error renders full screen error page correctly 1`] = `
248
- [
249
- <View
250
- collapsable={false}
251
- style={
248
+ <Modal
249
+ animationType="slide"
250
+ hardwareAccelerated={false}
251
+ onDismiss={[Function]}
252
+ onRequestClose={[Function]}
253
+ style={
254
+ [
252
255
  {
253
- "backgroundColor": "#000000",
254
- "bottom": 0,
255
- "left": 0,
256
- "opacity": 0,
257
- "position": "absolute",
258
- "right": 0,
259
- "top": 0,
260
- }
261
- }
262
- />,
256
+ "height": "100%",
257
+ "width": "100%",
258
+ },
259
+ undefined,
260
+ ]
261
+ }
262
+ visible={true}
263
+ >
263
264
  <View
264
- collapsable={false}
265
265
  style={
266
- {
267
- "bottom": 0,
268
- "left": 0,
269
- "opacity": 1,
270
- "position": "absolute",
271
- "right": 0,
272
- "top": 0,
273
- "transform": [
274
- {
275
- "translateY": 1334,
276
- },
277
- ],
278
- }
266
+ [
267
+ {
268
+ "backgroundColor": "#ccd2d3",
269
+ "display": "flex",
270
+ "flex": 1,
271
+ "flexDirection": "column",
272
+ },
273
+ undefined,
274
+ ]
279
275
  }
276
+ themeVariant="full-screen"
280
277
  >
281
278
  <View
282
279
  style={
283
280
  [
284
281
  {
285
- "backgroundColor": "#ccd2d3",
282
+ "alignItems": "center",
286
283
  "display": "flex",
287
284
  "flex": 1,
288
285
  "flexDirection": "column",
286
+ "justifyContent": "center",
287
+ "padding": 24,
289
288
  },
290
289
  undefined,
291
290
  ]
292
291
  }
293
- themeVariant="full-screen"
294
292
  >
295
293
  <View
296
294
  style={
297
295
  [
298
296
  {
299
- "alignItems": "center",
300
- "display": "flex",
301
- "flex": 1,
302
- "flexDirection": "column",
303
- "justifyContent": "center",
304
- "padding": 24,
297
+ "height": 176,
298
+ "marginBottom": 24,
299
+ "width": 176,
305
300
  },
306
301
  undefined,
307
302
  ]
308
303
  }
309
304
  >
310
- <View
311
- style={
312
- [
313
- {
314
- "height": 176,
315
- "marginBottom": 24,
316
- "width": 176,
317
- },
318
- undefined,
319
- ]
320
- }
321
- >
322
- <Image
323
- source={
324
- {
325
- "uri": "path_to_image",
326
- }
327
- }
328
- style={
329
- [
330
- {
331
- "borderRadius": 0,
332
- "height": 72,
333
- "width": 72,
334
- },
335
- [
336
- {
337
- "height": 176,
338
- "marginBottom": 24,
339
- "resizeMode": "contain",
340
- "width": 176,
341
- },
342
- undefined,
343
- ],
344
- ]
305
+ <Image
306
+ source={
307
+ {
308
+ "uri": "path_to_image",
345
309
  }
346
- testID="error-image"
347
- />
348
- </View>
349
- <Text
350
- allowFontScaling={false}
310
+ }
351
311
  style={
352
312
  [
353
313
  {
354
- "color": "#001f23",
355
- "fontFamily": "RebondGrotesque-SemiBold",
356
- "fontSize": 24,
357
- "letterSpacing": 0.24,
358
- "lineHeight": 32,
314
+ "borderRadius": 0,
315
+ "height": 72,
316
+ "width": 72,
359
317
  },
360
318
  [
361
319
  {
362
- "color": "#001f23",
363
- "marginBottom": 8,
364
- "textAlign": "center",
320
+ "height": 176,
321
+ "marginBottom": 24,
322
+ "resizeMode": "contain",
323
+ "width": 176,
365
324
  },
366
325
  undefined,
367
326
  ],
368
327
  ]
369
328
  }
370
- themeIntent="body"
371
- themeLevel="h4"
372
- themeTypeface="playful"
373
- >
374
- We’re sorry, something went wrong
375
- </Text>
376
- <Text
377
- allowFontScaling={false}
378
- style={
329
+ testID="error-image"
330
+ />
331
+ </View>
332
+ <Text
333
+ allowFontScaling={false}
334
+ style={
335
+ [
336
+ {
337
+ "color": "#001f23",
338
+ "fontFamily": "RebondGrotesque-SemiBold",
339
+ "fontSize": 24,
340
+ "letterSpacing": 0.24,
341
+ "lineHeight": 32,
342
+ },
379
343
  [
380
344
  {
381
345
  "color": "#001f23",
382
- "fontFamily": "BeVietnamPro-Regular",
383
- "fontSize": 18,
384
- "letterSpacing": 0.54,
385
- "lineHeight": 26,
346
+ "marginBottom": 8,
347
+ "textAlign": "center",
386
348
  },
387
- [
388
- {
389
- "color": "#4d6265",
390
- "textAlign": "center",
391
- },
392
- undefined,
393
- ],
394
- ]
395
- }
396
- themeIntent="body"
397
- themeTypeface="playful"
398
- themeVariant="regular"
399
- >
400
- Please try again later
401
- </Text>
402
- </View>
349
+ undefined,
350
+ ],
351
+ ]
352
+ }
353
+ themeIntent="body"
354
+ themeLevel="h4"
355
+ themeTypeface="playful"
356
+ >
357
+ We’re sorry, something went wrong
358
+ </Text>
359
+ <Text
360
+ allowFontScaling={false}
361
+ style={
362
+ [
363
+ {
364
+ "color": "#001f23",
365
+ "fontFamily": "BeVietnamPro-Regular",
366
+ "fontSize": 18,
367
+ "letterSpacing": 0.54,
368
+ "lineHeight": 26,
369
+ },
370
+ [
371
+ {
372
+ "color": "#4d6265",
373
+ "textAlign": "center",
374
+ },
375
+ undefined,
376
+ ],
377
+ ]
378
+ }
379
+ themeIntent="body"
380
+ themeTypeface="playful"
381
+ themeVariant="regular"
382
+ >
383
+ Please try again later
384
+ </Text>
403
385
  </View>
404
- </View>,
405
- ]
386
+ </View>
387
+ </Modal>
406
388
  `;
407
389
 
408
390
  exports[`Error renders title only correctly 1`] = `
@@ -3,7 +3,6 @@ import { fireEvent } from '@testing-library/react-native';
3
3
  import renderWithTheme from '../../../testHelpers/renderWithTheme';
4
4
  import Error from '..';
5
5
  import Image from '../../Image';
6
- import Portal from '../../Portal';
7
6
 
8
7
  const title = `We’re sorry, something went wrong`;
9
8
  const description = 'Please try again later';
@@ -47,14 +46,12 @@ describe('Error', () => {
47
46
  });
48
47
  it('renders full screen error page correctly', () => {
49
48
  const { toJSON, getByText, getByTestId } = renderWithTheme(
50
- <Portal.Provider>
51
- <Error
52
- variant="full-screen"
53
- title={title}
54
- description={description}
55
- image="path_to_image"
56
- />
57
- </Portal.Provider>
49
+ <Error
50
+ variant="full-screen"
51
+ title={title}
52
+ description={description}
53
+ image="path_to_image"
54
+ />
58
55
  );
59
56
 
60
57
  expect(getByText(title)).toBeTruthy();
@@ -0,0 +1,112 @@
1
+ import React, { forwardRef } from 'react';
2
+ import {
3
+ Animated,
4
+ Dimensions,
5
+ Easing,
6
+ Platform,
7
+ StyleProp,
8
+ ViewStyle,
9
+ } from 'react-native';
10
+
11
+ type ModalContentWrapperProps = {
12
+ children: React.ReactElement;
13
+ visible?: boolean;
14
+ onShow?: () => void;
15
+ testID?: string;
16
+ animationType?: 'none' | 'slide' | 'fade';
17
+ style?: StyleProp<ViewStyle>;
18
+ animated?: boolean;
19
+ };
20
+
21
+ export type ModalContentWrapperHandler = {
22
+ hide: (callback?: () => void) => void;
23
+ };
24
+
25
+ const windowHeight = Dimensions.get('window').height;
26
+ const defaultAnimationConfig = {
27
+ easing: Easing.inOut(Easing.cubic),
28
+ duration: 400,
29
+ useNativeDriver: Platform.OS !== 'web',
30
+ };
31
+
32
+ const ModalContentWrapper = forwardRef<
33
+ ModalContentWrapperHandler,
34
+ ModalContentWrapperProps
35
+ >(
36
+ (
37
+ {
38
+ animationType,
39
+ children,
40
+ testID,
41
+ onShow,
42
+ style,
43
+ visible,
44
+ animated = true,
45
+ },
46
+ ref
47
+ ) => {
48
+ const animatedValue = React.useRef(new Animated.Value(0)).current;
49
+
50
+ const modalAnimation = animatedValue.interpolate(
51
+ animationType === 'fade'
52
+ ? {
53
+ inputRange: [0, 1],
54
+ outputRange: [0, 1],
55
+ }
56
+ : {
57
+ inputRange: [0, 1],
58
+ outputRange: [windowHeight, 0],
59
+ }
60
+ );
61
+
62
+ React.useImperativeHandle(
63
+ ref,
64
+ () => ({
65
+ hide: (callback) => {
66
+ Animated.timing(animatedValue, {
67
+ toValue: 0,
68
+ ...defaultAnimationConfig,
69
+ }).start(callback);
70
+ },
71
+ }),
72
+ [animatedValue]
73
+ );
74
+
75
+ React.useEffect(() => {
76
+ // Hiding animation will be called from the modal component
77
+ if (visible) {
78
+ Animated.timing(animatedValue, {
79
+ toValue: 1,
80
+ ...defaultAnimationConfig,
81
+ // Prevent animation when updating the modal content
82
+ duration: animated ? defaultAnimationConfig.duration : 0,
83
+ }).start(() => {
84
+ onShow?.();
85
+ });
86
+ }
87
+ }, [visible, animatedValue, onShow, animated]);
88
+
89
+ return (
90
+ <Animated.View
91
+ testID={testID}
92
+ style={[
93
+ style,
94
+ {
95
+ ...(animationType === 'fade' ? { opacity: modalAnimation } : {}),
96
+ ...(animationType === 'slide'
97
+ ? {
98
+ transform: [{ translateY: modalAnimation }],
99
+ }
100
+ : {}),
101
+ },
102
+ ]}
103
+ >
104
+ {children}
105
+ </Animated.View>
106
+ );
107
+ }
108
+ );
109
+
110
+ ModalContentWrapper.displayName = 'ModalContentWrapper';
111
+
112
+ export default ModalContentWrapper;
@@ -0,0 +1,135 @@
1
+ import React, { forwardRef, useRef } from 'react';
2
+ import { Animated, StyleSheet, ViewProps } from 'react-native';
3
+ import RootSiblings from 'react-native-root-siblings';
4
+ import { useTheme } from '../../../theme';
5
+ import Box from '../../Box';
6
+
7
+ export type ModalPresenterHandles = {
8
+ animatedOut: (completion?: () => void) => void;
9
+ };
10
+ export type ModalDismissFunc = (onDismiss?: () => void) => void;
11
+ export type ModalUpdateFunc = (content: React.ReactNode) => void;
12
+
13
+ /**
14
+ * Modal handler is returned by `showModal` function.
15
+ */
16
+ export type ModalHandler = {
17
+ /**
18
+ * Same `dismiss` function as in `ModalContentProps`.
19
+ */
20
+ dismiss: ModalDismissFunc;
21
+ /**
22
+ * Same `update` function as in `ModalContentProps`.
23
+ */
24
+ update: ModalUpdateFunc;
25
+ };
26
+
27
+ const ModalPresenter = forwardRef<ModalPresenterHandles, ViewProps>(
28
+ ({ style, children, ...props }, ref) => {
29
+ const animatedOpacity = useRef(new Animated.Value(0));
30
+ const theme = useTheme();
31
+
32
+ React.useEffect(() => {
33
+ Animated.spring(animatedOpacity.current, {
34
+ toValue: 1,
35
+ useNativeDriver: true,
36
+ }).start();
37
+ }, []);
38
+
39
+ React.useImperativeHandle(ref, () => ({
40
+ animatedOut: (completion?: () => void) => {
41
+ Animated.spring(animatedOpacity.current, {
42
+ toValue: 0,
43
+ useNativeDriver: true,
44
+ }).start(() => {
45
+ completion?.();
46
+ });
47
+ },
48
+ }));
49
+
50
+ return (
51
+ <Box style={StyleSheet.absoluteFill}>
52
+ <Animated.View
53
+ style={[
54
+ {
55
+ width: '100%',
56
+ height: '100%',
57
+ backgroundColor: `${theme.colors.overlayGlobalSurface}66`, // 66 = 40% opacity as suggested by the mobile color guidelines
58
+ justifyContent: 'center',
59
+ alignItems: 'center',
60
+ opacity: animatedOpacity.current,
61
+ },
62
+ style,
63
+ ]}
64
+ {...props}
65
+ >
66
+ {children}
67
+ </Animated.View>
68
+ </Box>
69
+ );
70
+ }
71
+ );
72
+
73
+ /**
74
+ * Present a modal on screen immediately.
75
+ *
76
+ * The new presented modal will be on top of existing modals if there are any.
77
+ *
78
+ * @param Content A component to be presented as a modal on screen.
79
+ * This component will be centered horizontally and vertically on screen with
80
+ * a semitransparent black overlay underneath.
81
+ * @param contentProps Props for this modal component.
82
+ * @returns A `ModalHandler` you can use to dismiss the modal.
83
+ */
84
+
85
+ export const showModal = (content: React.ReactNode): ModalHandler => {
86
+ let ref: ModalPresenterHandles | null = null;
87
+ let rootSiblings: RootSiblings | null = null;
88
+
89
+ const dismiss: ModalDismissFunc = (onDismiss) => {
90
+ if (rootSiblings) {
91
+ const cleanup = () => {
92
+ rootSiblings?.destroy();
93
+ rootSiblings = null;
94
+ ref = null;
95
+ onDismiss?.();
96
+ };
97
+
98
+ if (ref) {
99
+ ref.animatedOut(cleanup);
100
+ } else {
101
+ cleanup();
102
+ }
103
+ }
104
+ };
105
+
106
+ const update: ModalUpdateFunc = (newContent) => {
107
+ rootSiblings?.update(
108
+ <ModalPresenter
109
+ ref={(_ref) => {
110
+ ref = _ref;
111
+ }}
112
+ >
113
+ {newContent}
114
+ </ModalPresenter>
115
+ );
116
+ };
117
+
118
+ rootSiblings = new RootSiblings(
119
+ (
120
+ <ModalPresenter
121
+ ref={(_ref) => {
122
+ ref = _ref;
123
+ }}
124
+ >
125
+ {content}
126
+ </ModalPresenter>
127
+ )
128
+ );
129
+
130
+ return { dismiss, update };
131
+ };
132
+
133
+ ModalPresenter.displayName = 'ModalPresenter';
134
+
135
+ export default ModalPresenter;
@@ -0,0 +1,9 @@
1
+ import {
2
+ ModalDismissFunc,
3
+ ModalHandler,
4
+ showModal,
5
+ ModalPresenterHandles,
6
+ } from './ModalPresenter';
7
+
8
+ export type { ModalDismissFunc, ModalHandler, ModalPresenterHandles };
9
+ export { showModal };
@@ -0,0 +1,8 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { RootSiblingParent } from 'react-native-root-siblings';
3
+
4
+ const ModalProvider = ({ children }: { children: ReactNode }) => {
5
+ return <RootSiblingParent>{children}</RootSiblingParent>;
6
+ };
7
+
8
+ export default ModalProvider;