@hero-design/rn 8.42.0-alpha.0 → 8.42.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 (36) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/es/index.js +614 -696
  3. package/lib/index.js +616 -700
  4. package/package.json +7 -8
  5. package/rollup.config.js +0 -1
  6. package/src/components/Carousel/__tests__/__snapshots__/CardCarousel.spec.tsx.snap +20 -0
  7. package/src/components/Carousel/__tests__/__snapshots__/StyledCardCarousel.spec.tsx.snap +15 -0
  8. package/src/components/Carousel/__tests__/__snapshots__/index.spec.tsx.snap +96 -3
  9. package/src/components/Carousel/__tests__/index.spec.tsx +47 -1
  10. package/src/components/Carousel/index.tsx +16 -22
  11. package/src/components/Error/StyledError.tsx +2 -1
  12. package/src/components/Error/__tests__/__snapshots__/index.spec.tsx.snap +115 -97
  13. package/src/components/Error/__tests__/index.spec.tsx +9 -6
  14. package/src/components/Modal/__tests__/__snapshots__/index.spec.tsx.snap +117 -0
  15. package/src/components/Modal/__tests__/index.spec.tsx +99 -0
  16. package/src/components/Modal/index.tsx +178 -82
  17. package/src/components/PageControl/__tests__/__snapshots__/index.spec.tsx.snap +15 -0
  18. package/src/components/PageControl/index.tsx +1 -0
  19. package/src/components/Success/StyledSuccess.tsx +2 -1
  20. package/src/components/Success/__tests__/__snapshots__/index.spec.tsx.snap +115 -95
  21. package/src/components/Success/__tests__/index.spec.tsx +9 -6
  22. package/src/index.ts +0 -2
  23. package/testUtils/setup.tsx +0 -18
  24. package/types/components/Carousel/index.d.ts +1 -1
  25. package/types/components/Error/StyledError.d.ts +3 -5
  26. package/types/components/Modal/index.d.ts +12 -8
  27. package/types/components/Success/StyledSuccess.d.ts +3 -5
  28. package/types/index.d.ts +1 -2
  29. package/src/components/Modal/ModalContentWrapper.tsx +0 -112
  30. package/src/components/Modal/ModalPresenter/ModalPresenter.tsx +0 -135
  31. package/src/components/Modal/ModalPresenter/index.tsx +0 -9
  32. package/src/components/Modal/ModalProvider.tsx +0 -8
  33. package/types/components/Modal/ModalContentWrapper.d.ts +0 -16
  34. package/types/components/Modal/ModalPresenter/ModalPresenter.d.ts +0 -34
  35. package/types/components/Modal/ModalPresenter/index.d.ts +0 -3
  36. package/types/components/Modal/ModalProvider.d.ts +0 -5
@@ -1,21 +1,41 @@
1
- import React from 'react';
2
- import { BackHandler, Dimensions, View, ViewStyle } from 'react-native';
3
- import ModalContentWrapper, {
4
- ModalContentWrapperHandler,
5
- } from './ModalContentWrapper';
6
- import ModalProvider from './ModalProvider';
7
- import { ModalHandler, showModal } from './ModalPresenter';
8
-
9
- const wrapperStyle: ViewStyle = {
10
- width: Dimensions.get('window').width,
11
- height: Dimensions.get('window').height,
1
+ import React, {
2
+ ReactNode,
3
+ forwardRef,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useRef,
7
+ useState,
8
+ useCallback,
9
+ } from 'react';
10
+ import {
11
+ Animated,
12
+ BackHandler,
13
+ Dimensions,
14
+ Easing,
15
+ Platform,
16
+ StyleSheet,
17
+ } from 'react-native';
18
+ import { useTheme } from '../../theme';
19
+ import Portal from '../Portal';
20
+
21
+ type ModalHandles = {
22
+ show: () => void;
23
+ hide: (callback: () => void) => void;
24
+ };
25
+
26
+ const DEFAULT_BACKDROP_OPACITY = 0.4;
27
+
28
+ const DEFAULT_ANIMATION_CONFIG = {
29
+ easing: Easing.inOut(Easing.cubic),
30
+ useNativeDriver: Platform.OS !== 'web',
31
+ duration: 400,
12
32
  };
13
33
 
14
34
  export interface ModalProps {
15
35
  /**
16
36
  * Content of the modal.
17
37
  */
18
- children: React.ReactElement;
38
+ children: ReactNode;
19
39
  /**
20
40
  * Visibility of the modal
21
41
  */
@@ -36,86 +56,162 @@ export interface ModalProps {
36
56
  * Animation type of the modal content.
37
57
  */
38
58
  animationType?: 'none' | 'slide' | 'fade';
59
+ /**
60
+ * Whether to show the modal backdrop
61
+ */
62
+ transparent?: boolean;
63
+ /**
64
+ * Callback when the modal is dismissed. iOS only.
65
+ */
66
+ onDismiss?: () => void;
39
67
  }
40
68
 
41
- const Modal = ({
42
- children,
43
- onShow,
44
- onRequestClose,
45
- testID,
46
- visible = true,
47
- animationType = 'none',
48
- }: ModalProps) => {
49
- const [modalHandler, setModalHandler] = React.useState<ModalHandler>();
50
- const modalContentWrapperRef = React.useRef<ModalContentWrapperHandler>(null);
51
-
52
- const getModalContent = React.useCallback(
53
- (isUpdate = false) => {
54
- return animationType === 'none' ? (
55
- <View style={wrapperStyle} testID={testID}>
56
- {children}
57
- </View>
58
- ) : (
59
- <ModalContentWrapper
60
- visible={visible}
61
- style={wrapperStyle}
62
- animationType={animationType}
69
+ const Modal = forwardRef<ModalHandles, Omit<ModalProps, 'visible'>>(
70
+ (
71
+ {
72
+ children,
73
+ onShow,
74
+ onRequestClose,
75
+ testID,
76
+ animationType = 'none',
77
+ transparent = false,
78
+ onDismiss,
79
+ },
80
+ ref
81
+ ) => {
82
+ const theme = useTheme();
83
+ const animatedBackdropValue = useRef(new Animated.Value(0)).current;
84
+ const animatedModalValue = useRef(new Animated.Value(0)).current;
85
+
86
+ // Show or hide the backdrop and modal content
87
+ const animateBackdropAndContent = useCallback(
88
+ ({ toValue, callback }: { toValue: number; callback?: () => void }) => {
89
+ if (animationType !== 'none') {
90
+ // Backdrop animation
91
+ if (!transparent) {
92
+ Animated.timing(animatedBackdropValue, {
93
+ toValue,
94
+ ...DEFAULT_ANIMATION_CONFIG,
95
+ }).start();
96
+ }
97
+
98
+ // Modal content animation
99
+ Animated.timing(animatedModalValue, {
100
+ toValue,
101
+ ...DEFAULT_ANIMATION_CONFIG,
102
+ }).start(callback);
103
+ } else {
104
+ callback?.();
105
+ }
106
+ },
107
+ [animationType, onShow, transparent]
108
+ );
109
+
110
+ const backdropOpacityAnimation = animatedBackdropValue.interpolate({
111
+ inputRange: [0, 1],
112
+ outputRange: [0, DEFAULT_BACKDROP_OPACITY],
113
+ });
114
+
115
+ const modalAnimation = animatedModalValue.interpolate({
116
+ inputRange: [0, 1],
117
+ outputRange:
118
+ animationType === 'slide'
119
+ ? [Dimensions.get('window').height, 0]
120
+ : [0, 1],
121
+ });
122
+
123
+ useImperativeHandle(
124
+ ref,
125
+ () => {
126
+ return {
127
+ show: () => {
128
+ animateBackdropAndContent({ toValue: 1, callback: onShow });
129
+ },
130
+
131
+ hide: (wrapperCallback) => {
132
+ animateBackdropAndContent({
133
+ toValue: 0,
134
+ callback: () => {
135
+ if (Platform.OS === 'ios') {
136
+ onDismiss?.();
137
+ }
138
+
139
+ wrapperCallback();
140
+ },
141
+ });
142
+ },
143
+ };
144
+ },
145
+ [onDismiss, onShow, animateBackdropAndContent]
146
+ );
147
+
148
+ // Back button handler
149
+ useEffect(() => {
150
+ const backHandler = BackHandler.addEventListener(
151
+ 'hardwareBackPress',
152
+ () => {
153
+ onRequestClose?.();
154
+ return true;
155
+ }
156
+ );
157
+
158
+ return () => backHandler.remove();
159
+ }, [onRequestClose]);
160
+
161
+ return (
162
+ <Portal>
163
+ <Animated.View
164
+ style={{
165
+ ...StyleSheet.absoluteFillObject,
166
+ backgroundColor: transparent
167
+ ? 'transparent'
168
+ : theme.colors.overlayGlobalSurface,
169
+ opacity:
170
+ animationType !== 'none'
171
+ ? backdropOpacityAnimation
172
+ : DEFAULT_BACKDROP_OPACITY,
173
+ }}
174
+ />
175
+
176
+ <Animated.View
63
177
  testID={testID}
64
- onShow={onShow}
65
- ref={modalContentWrapperRef}
66
- animated={!isUpdate}
178
+ style={{
179
+ ...StyleSheet.absoluteFillObject,
180
+ opacity: animationType === 'fade' ? modalAnimation : 1,
181
+ transform: [
182
+ {
183
+ translateY: animationType === 'slide' ? modalAnimation : 0,
184
+ },
185
+ ],
186
+ }}
67
187
  >
68
188
  {children}
69
- </ModalContentWrapper>
70
- );
71
- },
72
- [visible, children, onShow, testID, animationType]
73
- );
189
+ </Animated.View>
190
+ </Portal>
191
+ );
192
+ }
193
+ );
74
194
 
75
- React.useEffect(() => {
195
+ const ModalWrapper = ({ visible = true, ...props }: ModalProps) => {
196
+ const modalRef = useRef<ModalHandles>(null);
197
+ const [internalVisible, setInternalVisible] = useState(visible);
198
+
199
+ useEffect(() => {
76
200
  if (visible) {
77
- // Modal does not exist, create a new one
78
- if (!modalHandler) {
79
- const newModalHandler = showModal(getModalContent(false));
80
- setModalHandler(newModalHandler);
81
-
82
- // If animationType is slide for fade, onShow would be run after animation on ModalContentWrapper,
83
- // else run on this component.
84
- if (animationType === 'none') {
85
- onShow?.();
86
- }
87
- }
88
- // Modal already exists, update it
89
- else {
90
- modalHandler.update(getModalContent(true));
91
- }
92
- } else if (animationType === 'none') {
93
- modalHandler?.dismiss();
94
- setModalHandler(undefined);
201
+ setInternalVisible(true);
95
202
  } else {
96
- // Wait to finish animation before dismissing
97
- modalContentWrapperRef.current?.hide(() => {
98
- modalHandler?.dismiss();
99
- setModalHandler(undefined);
100
- });
203
+ // Wait for animation to finish before hiding the modal
204
+ modalRef.current?.hide(() => setInternalVisible(false));
101
205
  }
102
- }, [getModalContent]);
206
+ }, [visible]);
103
207
 
104
- React.useEffect(() => {
105
- const backHandler = BackHandler.addEventListener(
106
- 'hardwareBackPress',
107
- () => {
108
- onRequestClose?.();
109
- return true;
110
- }
111
- );
112
-
113
- return () => backHandler.remove();
114
- }, [onRequestClose]);
208
+ useEffect(() => {
209
+ if (internalVisible) {
210
+ modalRef.current?.show();
211
+ }
212
+ }, [internalVisible]);
115
213
 
116
- return null;
214
+ return internalVisible ? <Modal ref={modalRef} {...props} /> : null;
117
215
  };
118
216
 
119
- export default Object.assign(Modal, {
120
- Provider: ModalProvider,
121
- });
217
+ export default ModalWrapper;
@@ -13,6 +13,11 @@ exports[`RefreshControl renders correctly 1`] = `
13
13
  }
14
14
  >
15
15
  <View
16
+ accessibilityState={
17
+ {
18
+ "selected": false,
19
+ }
20
+ }
16
21
  collapsable={false}
17
22
  style={
18
23
  {
@@ -27,6 +32,11 @@ exports[`RefreshControl renders correctly 1`] = `
27
32
  testID="page-control-indicator0"
28
33
  />
29
34
  <View
35
+ accessibilityState={
36
+ {
37
+ "selected": true,
38
+ }
39
+ }
30
40
  collapsable={false}
31
41
  style={
32
42
  {
@@ -41,6 +51,11 @@ exports[`RefreshControl renders correctly 1`] = `
41
51
  testID="page-control-indicator1"
42
52
  />
43
53
  <View
54
+ accessibilityState={
55
+ {
56
+ "selected": false,
57
+ }
58
+ }
44
59
  collapsable={false}
45
60
  style={
46
61
  {
@@ -61,6 +61,7 @@ const PageControl = ({
61
61
 
62
62
  return (
63
63
  <StyledPageControlAnimatedView
64
+ accessibilityState={{ selected: index === currentPage }}
64
65
  style={[{ width: indicatorWidth, opacity }]}
65
66
  key={index.toString()}
66
67
  testID={`page-control-indicator${index}`}
@@ -1,8 +1,9 @@
1
1
  import styled from '@emotion/native';
2
- import { Modal, View } from 'react-native';
2
+ import { 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';
6
7
 
7
8
  type SuccessVariant = 'full-screen' | 'in-page';
8
9
 
@@ -1,144 +1,164 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`Success renders full screen success page correctly 1`] = `
4
- <Modal
5
- animationType="slide"
6
- hardwareAccelerated={false}
7
- style={
8
- [
4
+ [
5
+ <View
6
+ collapsable={false}
7
+ style={
9
8
  {
10
- "height": "100%",
11
- "width": "100%",
12
- },
13
- undefined,
14
- ]
15
- }
16
- visible={true}
17
- >
9
+ "backgroundColor": "#000000",
10
+ "bottom": 0,
11
+ "left": 0,
12
+ "opacity": 0,
13
+ "position": "absolute",
14
+ "right": 0,
15
+ "top": 0,
16
+ }
17
+ }
18
+ />,
18
19
  <View
20
+ collapsable={false}
19
21
  style={
20
- [
21
- {
22
- "backgroundColor": "#ccd2d3",
23
- "display": "flex",
24
- "flex": 1,
25
- "flexDirection": "column",
26
- },
27
- undefined,
28
- ]
22
+ {
23
+ "bottom": 0,
24
+ "left": 0,
25
+ "opacity": 1,
26
+ "position": "absolute",
27
+ "right": 0,
28
+ "top": 0,
29
+ "transform": [
30
+ {
31
+ "translateY": 1334,
32
+ },
33
+ ],
34
+ }
29
35
  }
30
- themeVariant="full-screen"
31
36
  >
32
37
  <View
33
38
  style={
34
39
  [
35
40
  {
36
- "alignItems": "center",
41
+ "backgroundColor": "#ccd2d3",
37
42
  "display": "flex",
38
43
  "flex": 1,
39
44
  "flexDirection": "column",
40
- "justifyContent": "center",
41
- "padding": 24,
42
45
  },
43
46
  undefined,
44
47
  ]
45
48
  }
49
+ themeVariant="full-screen"
46
50
  >
47
51
  <View
48
52
  style={
49
53
  [
50
54
  {
51
- "height": 176,
52
- "marginBottom": 32,
53
- "width": 176,
55
+ "alignItems": "center",
56
+ "display": "flex",
57
+ "flex": 1,
58
+ "flexDirection": "column",
59
+ "justifyContent": "center",
60
+ "padding": 24,
54
61
  },
55
62
  undefined,
56
63
  ]
57
64
  }
58
65
  >
59
- <Image
60
- source={
61
- {
62
- "uri": "path_to_image",
63
- }
66
+ <View
67
+ style={
68
+ [
69
+ {
70
+ "height": 176,
71
+ "marginBottom": 32,
72
+ "width": 176,
73
+ },
74
+ undefined,
75
+ ]
64
76
  }
77
+ >
78
+ <Image
79
+ source={
80
+ {
81
+ "uri": "path_to_image",
82
+ }
83
+ }
84
+ style={
85
+ [
86
+ {
87
+ "borderRadius": 0,
88
+ "height": 72,
89
+ "width": 72,
90
+ },
91
+ [
92
+ {
93
+ "height": 176,
94
+ "marginBottom": 32,
95
+ "resizeMode": "contain",
96
+ "width": 176,
97
+ },
98
+ undefined,
99
+ ],
100
+ ]
101
+ }
102
+ testID="success-image"
103
+ />
104
+ </View>
105
+ <Text
106
+ allowFontScaling={false}
65
107
  style={
66
108
  [
67
109
  {
68
- "borderRadius": 0,
69
- "height": 72,
70
- "width": 72,
110
+ "color": "#001f23",
111
+ "fontFamily": "RebondGrotesque-SemiBold",
112
+ "fontSize": 24,
113
+ "letterSpacing": 0.24,
114
+ "lineHeight": 32,
71
115
  },
72
116
  [
73
117
  {
74
- "height": 176,
75
- "marginBottom": 32,
76
- "resizeMode": "contain",
77
- "width": 176,
118
+ "color": "#001f23",
119
+ "marginBottom": 8,
120
+ "textAlign": "center",
78
121
  },
79
122
  undefined,
80
123
  ],
81
124
  ]
82
125
  }
83
- testID="success-image"
84
- />
85
- </View>
86
- <Text
87
- allowFontScaling={false}
88
- style={
89
- [
90
- {
91
- "color": "#001f23",
92
- "fontFamily": "RebondGrotesque-SemiBold",
93
- "fontSize": 24,
94
- "letterSpacing": 0.24,
95
- "lineHeight": 32,
96
- },
126
+ themeIntent="body"
127
+ themeLevel="h4"
128
+ themeTypeface="playful"
129
+ >
130
+ We’re sorry, something went wrong
131
+ </Text>
132
+ <Text
133
+ allowFontScaling={false}
134
+ style={
97
135
  [
98
136
  {
99
137
  "color": "#001f23",
100
- "marginBottom": 8,
101
- "textAlign": "center",
138
+ "fontFamily": "BeVietnamPro-Regular",
139
+ "fontSize": 16,
140
+ "letterSpacing": 0.48,
141
+ "lineHeight": 24,
102
142
  },
103
- undefined,
104
- ],
105
- ]
106
- }
107
- themeIntent="body"
108
- themeLevel="h4"
109
- themeTypeface="playful"
110
- >
111
- We’re sorry, something went wrong
112
- </Text>
113
- <Text
114
- allowFontScaling={false}
115
- style={
116
- [
117
- {
118
- "color": "#001f23",
119
- "fontFamily": "BeVietnamPro-Regular",
120
- "fontSize": 16,
121
- "letterSpacing": 0.48,
122
- "lineHeight": 24,
123
- },
124
- [
125
- {
126
- "color": "#4d6265",
127
- "textAlign": "center",
128
- },
129
- undefined,
130
- ],
131
- ]
132
- }
133
- themeIntent="body"
134
- themeTypeface="neutral"
135
- themeVariant="regular"
136
- >
137
- Please try again later
138
- </Text>
143
+ [
144
+ {
145
+ "color": "#4d6265",
146
+ "textAlign": "center",
147
+ },
148
+ undefined,
149
+ ],
150
+ ]
151
+ }
152
+ themeIntent="body"
153
+ themeTypeface="neutral"
154
+ themeVariant="regular"
155
+ >
156
+ Please try again later
157
+ </Text>
158
+ </View>
139
159
  </View>
140
- </View>
141
- </Modal>
160
+ </View>,
161
+ ]
142
162
  `;
143
163
 
144
164
  exports[`Success renders succe screen with custom image element correctly 1`] = `
@@ -3,6 +3,7 @@ import { fireEvent } from '@testing-library/react-native';
3
3
  import renderWithTheme from '../../../testHelpers/renderWithTheme';
4
4
  import Success from '..';
5
5
  import Image from '../../Image';
6
+ import Portal from '../../Portal';
6
7
 
7
8
  const title = `We’re sorry, something went wrong`;
8
9
  const description = 'Please try again later';
@@ -46,12 +47,14 @@ describe('Success', () => {
46
47
  });
47
48
  it('renders full screen success page correctly', () => {
48
49
  const { toJSON, getByText, getByTestId } = renderWithTheme(
49
- <Success
50
- variant="full-screen"
51
- title={title}
52
- description={description}
53
- image="path_to_image"
54
- />
50
+ <Portal.Provider>
51
+ <Success
52
+ variant="full-screen"
53
+ title={title}
54
+ description={description}
55
+ image="path_to_image"
56
+ />
57
+ </Portal.Provider>
55
58
  );
56
59
 
57
60
  expect(getByText(title)).toBeTruthy();
package/src/index.ts CHANGED
@@ -38,7 +38,6 @@ import HeroDesignProvider from './components/HeroDesignProvider';
38
38
  import Icon from './components/Icon';
39
39
  import Image from './components/Image';
40
40
  import List from './components/List';
41
- import Modal from './components/Modal';
42
41
  import PinInput from './components/PinInput';
43
42
  import Progress from './components/Progress';
44
43
  import Slider from './components/Slider';
@@ -103,7 +102,6 @@ export {
103
102
  Image,
104
103
  HeroDesignProvider,
105
104
  List,
106
- Modal,
107
105
  PinInput,
108
106
  Progress,
109
107
  Portal,
@@ -132,22 +132,4 @@ jest.mock('react-native/Libraries/Utilities/BackHandler', () => {
132
132
  );
133
133
  });
134
134
 
135
- jest.mock('react-native-root-siblings', () => {
136
- const React = jest.requireActual('react');
137
-
138
- class RootSiblingsManager extends React.Component {
139
- constructor(props) {
140
- super(props);
141
- }
142
- render() {
143
- return React.createElement('RootSiblingsManager', this.props, this.props.children);
144
- }
145
- }
146
-
147
- return {
148
- __esModule: true,
149
- default: RootSiblingsManager,
150
- };
151
- });
152
-
153
135
  export {};
@@ -38,7 +38,7 @@ interface CarouselProps extends ViewProps {
38
38
  pageControlPosition?: 'top' | 'bottom';
39
39
  }
40
40
  export declare function useStateFromProp<T>(initialValue: T): [T, Dispatch<SetStateAction<T>>];
41
- declare const _default: (({ items, onItemIndexChange, renderActions, selectedItemIndex, style, shouldShowPagination, pageControlPosition, ...nativeProps }: CarouselProps) => JSX.Element) & {
41
+ declare const _default: (({ items, onItemIndexChange, renderActions, selectedItemIndex, style, shouldShowPagination, testID, pageControlPosition, ...nativeProps }: CarouselProps) => JSX.Element) & {
42
42
  Card: React.ForwardRefExoticComponent<Pick<import("./CardCarousel").CardCarouselProps, "style" | "onLayout" | "testID" | "items" | "onItemIndexChange" | "hidePageControl" | "autoPlay" | "autoPlayInterval"> & React.RefAttributes<import("./CardCarousel").CardCarouselHandles>>;
43
43
  };
44
44
  export default _default;