@utilitywarehouse/hearth-react-native 0.28.6 → 0.29.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.
- package/.storybook/preview.tsx +7 -4
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +15 -18
- package/CHANGELOG.md +44 -0
- package/build/components/Combobox/Combobox.js +1 -1
- package/build/components/Modal/Modal.d.ts +1 -1
- package/build/components/Modal/Modal.js +6 -95
- package/build/components/Modal/Modal.props.d.ts +2 -31
- package/build/components/Modal/Modal.shared.types.d.ts +22 -0
- package/build/components/Modal/Modal.web.d.ts +1 -1
- package/build/components/Modal/Modal.web.js +6 -71
- package/build/components/NavModal/NavModal.d.ts +3 -0
- package/build/components/NavModal/NavModal.js +190 -0
- package/build/components/NavModal/NavModal.props.d.ts +15 -0
- package/build/components/NavModal/NavModal.props.js +1 -0
- package/build/components/NavModal/index.d.ts +2 -0
- package/build/components/NavModal/index.js +1 -0
- package/build/components/Select/Select.js +1 -1
- package/build/components/index.d.ts +2 -1
- package/build/components/index.js +2 -1
- package/docs/changelog.mdx +34 -0
- package/docs/components/AllComponents.web.tsx +709 -689
- package/package.json +3 -1
- package/src/components/Combobox/Combobox.tsx +1 -1
- package/src/components/Modal/Modal.docs.mdx +5 -122
- package/src/components/Modal/Modal.props.ts +2 -34
- package/src/components/Modal/Modal.shared.types.ts +23 -0
- package/src/components/Modal/Modal.stories.tsx +0 -1
- package/src/components/Modal/Modal.tsx +11 -174
- package/src/components/Modal/Modal.web.tsx +29 -127
- package/src/components/NavModal/NavModal.docs.mdx +178 -0
- package/src/components/NavModal/NavModal.figma.tsx +13 -0
- package/src/components/NavModal/NavModal.props.ts +23 -0
- package/src/components/NavModal/NavModal.stories.tsx +131 -0
- package/src/components/NavModal/NavModal.tsx +375 -0
- package/src/components/NavModal/index.ts +2 -0
- package/src/components/Select/Select.tsx +1 -1
- package/src/components/index.ts +3 -1
- package/build/components/SafeAreaView/SafeAreaView.d.ts +0 -5
- package/build/components/SafeAreaView/SafeAreaView.js +0 -117
- package/build/components/SafeAreaView/SafeAreaView.props.d.ts +0 -17
- package/build/components/SafeAreaView/index.d.ts +0 -2
- package/build/components/SafeAreaView/index.js +0 -1
- package/src/components/SafeAreaView/SafeAreaView.props.ts +0 -20
- package/src/components/SafeAreaView/SafeAreaView.tsx +0 -173
- package/src/components/SafeAreaView/index.ts +0 -2
- /package/build/components/{SafeAreaView/SafeAreaView.props.js → Modal/Modal.shared.types.js} +0 -0
package/.storybook/preview.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
|
2
2
|
import '@utilitywarehouse/hearth-fonts';
|
|
3
|
-
import '@utilitywarehouse/hearth-tokens/index.css';
|
|
4
3
|
import { color } from '@utilitywarehouse/hearth-tokens';
|
|
4
|
+
import '@utilitywarehouse/hearth-tokens/index.css';
|
|
5
5
|
import { useEffect } from 'react';
|
|
6
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
6
7
|
import '../../../shared/storybook/styles/diff-highlighting.css';
|
|
7
8
|
import '../../../shared/storybook/styles/preview.css';
|
|
8
9
|
import theme from '../../../shared/storybook/theme';
|
|
@@ -159,9 +160,11 @@ const preview = {
|
|
|
159
160
|
}, [isDarkMode, args.darkMode, args.surface, args.inverted]);
|
|
160
161
|
|
|
161
162
|
return (
|
|
162
|
-
<
|
|
163
|
-
<
|
|
164
|
-
|
|
163
|
+
<SafeAreaProvider>
|
|
164
|
+
<BottomSheetModalProvider>
|
|
165
|
+
<Story args={{ ...args, darkMode: isDarkMode }} />
|
|
166
|
+
</BottomSheetModalProvider>
|
|
167
|
+
</SafeAreaProvider>
|
|
165
168
|
);
|
|
166
169
|
},
|
|
167
170
|
],
|
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-lint.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @utilitywarehouse/hearth-react-native@0.
|
|
2
|
+
> @utilitywarehouse/hearth-react-native@0.29.0 lint /home/runner/work/hearth/hearth/packages/react-native
|
|
3
3
|
> TIMING=1 eslint .
|
|
4
4
|
|
|
5
5
|
|
|
@@ -30,9 +30,6 @@
|
|
|
30
30
|
/home/runner/work/hearth/hearth/packages/react-native/src/components/Input/Input.tsx
|
|
31
31
|
78:8 warning React Hook useEffect has a missing dependency: 'formFieldContext'. Either include it or remove the dependency array react-hooks/exhaustive-deps
|
|
32
32
|
|
|
33
|
-
/home/runner/work/hearth/hearth/packages/react-native/src/components/Modal/Modal.web.tsx
|
|
34
|
-
66:6 warning React Hook useCallback has an unnecessary dependency: 'Platform.OS'. Either exclude it or remove the dependency array. Outer scope values like 'Platform.OS' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
|
|
35
|
-
|
|
36
33
|
/home/runner/work/hearth/hearth/packages/react-native/src/components/PillGroup/PillGroup.tsx
|
|
37
34
|
17:9 warning The 'normalizedValue' conditional could make the dependencies of useMemo Hook (at line 33) change on every render. Move it inside the useMemo callback. Alternatively, wrap the initialization of 'normalizedValue' in its own useMemo() Hook react-hooks/exhaustive-deps
|
|
38
35
|
|
|
@@ -52,17 +49,17 @@
|
|
|
52
49
|
14:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
|
|
53
50
|
106:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
|
|
54
51
|
|
|
55
|
-
✖
|
|
56
|
-
|
|
57
|
-
Rule
|
|
58
|
-
|
|
59
|
-
@typescript-eslint/no-unused-vars
|
|
60
|
-
react-hooks/exhaustive-deps
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
no-
|
|
64
|
-
|
|
65
|
-
no-
|
|
66
|
-
no-useless-escape
|
|
67
|
-
no-
|
|
68
|
-
@typescript-eslint/
|
|
52
|
+
✖ 22 problems (0 errors, 22 warnings)
|
|
53
|
+
|
|
54
|
+
Rule | Time (ms) | Relative
|
|
55
|
+
:-----------------------------------------|----------:|--------:
|
|
56
|
+
@typescript-eslint/no-unused-vars | 1560.145 | 62.1%
|
|
57
|
+
react-hooks/exhaustive-deps | 117.093 | 4.7%
|
|
58
|
+
no-global-assign | 95.045 | 3.8%
|
|
59
|
+
react-hooks/rules-of-hooks | 70.847 | 2.8%
|
|
60
|
+
no-misleading-character-class | 41.174 | 1.6%
|
|
61
|
+
@typescript-eslint/ban-ts-comment | 40.449 | 1.6%
|
|
62
|
+
no-unexpected-multiline | 38.671 | 1.5%
|
|
63
|
+
no-useless-escape | 35.996 | 1.4%
|
|
64
|
+
@typescript-eslint/no-unused-expressions | 34.583 | 1.4%
|
|
65
|
+
@typescript-eslint/triple-slash-reference | 33.557 | 1.3%
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# @utilitywarehouse/hearth-react-native
|
|
2
2
|
|
|
3
|
+
## 0.29.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1060](https://github.com/utilitywarehouse/hearth/pull/1060) [`05d38f9`](https://github.com/utilitywarehouse/hearth/commit/05d38f9414fec6d6b4a0733829b4d290d96fccae) Thanks [@jordmccord](https://github.com/jordmccord)! - 💔 [BREAKING CHANGE]: Extract navigation-presented modal behaviour into `NavModal`
|
|
8
|
+
|
|
9
|
+
`Modal` now only supports the bottom-sheet modal behaviour. The React Navigation screen-based API that was previously exposed through `inNavModal`, `background`, and `scrollable` has moved to the new `NavModal` component.
|
|
10
|
+
|
|
11
|
+
`NavModal` also adds a `presentation` prop so the component can match the React Navigation screen presentation style for sheet-style and full-screen modal routes.
|
|
12
|
+
|
|
13
|
+
The package-owned `SafeAreaView` component has also been removed in favour of the `react-native-safe-area-context` `SafeAreaView`. Hearth now re-exports that implementation from the package root.
|
|
14
|
+
|
|
15
|
+
**Components affected**:
|
|
16
|
+
- `Modal`
|
|
17
|
+
- `NavModal`
|
|
18
|
+
- `SafeAreaView`
|
|
19
|
+
|
|
20
|
+
**Developer changes**:
|
|
21
|
+
|
|
22
|
+
Update navigation modal screens to use `NavModal` instead of `Modal`:
|
|
23
|
+
|
|
24
|
+
```diff
|
|
25
|
+
- import { Modal } from '@utilitywarehouse/hearth-react-native';
|
|
26
|
+
+ import { NavModal, type NavModalRef } from '@utilitywarehouse/hearth-react-native';
|
|
27
|
+
|
|
28
|
+
- const modalRef = useRef<Modal>(null);
|
|
29
|
+
+ const modalRef = useRef<NavModalRef>(null);
|
|
30
|
+
|
|
31
|
+
- <Modal inNavModal background="brand" scrollable={false}>
|
|
32
|
+
+ <NavModal background="brand" scrollable={false} presentation="modal">
|
|
33
|
+
{/* content */}
|
|
34
|
+
- </Modal>
|
|
35
|
+
+ </NavModal>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If you are using the bottom-sheet modal API, no changes are required.
|
|
39
|
+
|
|
40
|
+
If you were importing the old component from the component path, update it to use the package root re-export or import directly from `react-native-safe-area-context`:
|
|
41
|
+
|
|
42
|
+
```diff
|
|
43
|
+
- import { SafeAreaView } from '@utilitywarehouse/hearth-react-native';
|
|
44
|
+
+ import { SafeAreaView } from 'react-native-safe-area-context';
|
|
45
|
+
```
|
|
46
|
+
|
|
3
47
|
## 0.28.6
|
|
4
48
|
|
|
5
49
|
### Patch Changes
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { CloseSmallIcon, SearchMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
3
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { Pressable, View } from 'react-native';
|
|
5
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
5
6
|
import { StyleSheet } from 'react-native-unistyles';
|
|
6
7
|
import { useTheme } from '../../hooks';
|
|
7
8
|
import { BodyText } from '../BodyText';
|
|
@@ -10,7 +11,6 @@ import { DetailText } from '../DetailText';
|
|
|
10
11
|
import { FormField, useFormFieldContext } from '../FormField';
|
|
11
12
|
import { Icon } from '../Icon';
|
|
12
13
|
import { Input } from '../Input';
|
|
13
|
-
import { SafeAreaView } from '../SafeAreaView';
|
|
14
14
|
import { Spinner } from '../Spinner';
|
|
15
15
|
import { UnstyledIconButton } from '../UnstyledIconButton';
|
|
16
16
|
import { ComboboxContext } from './Combobox.context';
|
|
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
|
|
|
3
3
|
type Modal<T = any> = BottomSheetModalMethods<T> & {
|
|
4
4
|
triggerCloseAnimation?: () => void;
|
|
5
5
|
};
|
|
6
|
-
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps,
|
|
6
|
+
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export default Modal;
|
|
@@ -1,64 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { BottomSheetFooter, } from '@gorhom/bottom-sheet';
|
|
3
3
|
import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
4
|
-
import { useCallback,
|
|
5
|
-
import { AccessibilityInfo, Platform,
|
|
6
|
-
import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withTiming, } from 'react-native-reanimated';
|
|
4
|
+
import { useCallback, useImperativeHandle, useMemo, useRef } from 'react';
|
|
5
|
+
import { AccessibilityInfo, Platform, View, findNodeHandle } from 'react-native';
|
|
7
6
|
import { StyleSheet } from 'react-native-unistyles';
|
|
8
|
-
import { useTheme } from '../../hooks';
|
|
9
|
-
import { hexWithOpacity } from '../../utils';
|
|
10
7
|
import { BodyText } from '../BodyText';
|
|
11
8
|
import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
|
|
12
9
|
import { Button } from '../Button';
|
|
13
10
|
import { Heading } from '../Heading';
|
|
14
|
-
import { SafeAreaView } from '../SafeAreaView';
|
|
15
11
|
import { Spinner } from '../Spinner';
|
|
16
12
|
import { UnstyledIconButton } from '../UnstyledIconButton';
|
|
17
|
-
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps,
|
|
13
|
+
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, ...props }) => {
|
|
18
14
|
const bottomSheetModalRef = useRef(null);
|
|
19
15
|
const viewRef = useRef(null);
|
|
20
16
|
const scrollViewRef = useRef(null);
|
|
21
|
-
const theme = useTheme();
|
|
22
|
-
const backgroundOpacity = useSharedValue(0);
|
|
23
|
-
const pretendContentTranslateY = useSharedValue(20);
|
|
24
|
-
const isBrandBackground = background === 'brand';
|
|
25
|
-
const triggerCloseAnimation = useCallback(() => {
|
|
26
|
-
if (Platform.OS === 'android' && inNavModal) {
|
|
27
|
-
pretendContentTranslateY.value = withTiming(20, {
|
|
28
|
-
duration: 50,
|
|
29
|
-
easing: Easing.in(Easing.quad),
|
|
30
|
-
});
|
|
31
|
-
backgroundOpacity.value = withTiming(0, {
|
|
32
|
-
duration: 100,
|
|
33
|
-
easing: Easing.in(Easing.quad),
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
}, [inNavModal, pretendContentTranslateY, backgroundOpacity]);
|
|
37
17
|
useImperativeHandle(ref, () => ({
|
|
38
18
|
...bottomSheetModalRef.current,
|
|
39
|
-
triggerCloseAnimation,
|
|
40
|
-
}));
|
|
41
|
-
// Trigger animations on render for inNavModal Android modal
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (Platform.OS === 'android' && inNavModal) {
|
|
44
|
-
backgroundOpacity.value = withDelay(300, withTiming(1, {
|
|
45
|
-
duration: 200,
|
|
46
|
-
easing: Easing.out(Easing.quad),
|
|
47
|
-
}));
|
|
48
|
-
pretendContentTranslateY.value = withDelay(500, withTiming(0, {
|
|
49
|
-
duration: 300,
|
|
50
|
-
easing: Easing.out(Easing.quad),
|
|
51
|
-
}));
|
|
52
|
-
}
|
|
53
|
-
}, [inNavModal, backgroundOpacity, pretendContentTranslateY]);
|
|
54
|
-
const animatedBackgroundStyle = useAnimatedStyle(() => ({
|
|
55
|
-
backgroundColor: hexWithOpacity(theme.components.overlay.backgroundColor, backgroundOpacity.value * (theme.components.overlay.opacity / 100)),
|
|
56
|
-
}));
|
|
57
|
-
const animatedInNavModalStyle = useAnimatedStyle(() => ({
|
|
58
|
-
backgroundColor: hexWithOpacity(theme.components.overlay.backgroundColor, backgroundOpacity.value * (theme.components.overlay.opacity / 100)),
|
|
59
|
-
}));
|
|
60
|
-
const animatedPretendContentStyle = useAnimatedStyle(() => ({
|
|
61
|
-
transform: [{ translateY: pretendContentTranslateY.value }],
|
|
62
19
|
}));
|
|
63
20
|
const handleChange = (index, position, type) => {
|
|
64
21
|
if (index > -1) {
|
|
@@ -107,13 +64,10 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
107
64
|
noButtons,
|
|
108
65
|
stickyFooter,
|
|
109
66
|
showHandle: props.showHandle,
|
|
110
|
-
background: isBrandBackground ? 'brand' : 'primary',
|
|
111
67
|
});
|
|
112
|
-
const footer = useMemo(() => (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText,
|
|
68
|
+
const footer = useMemo(() => (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })), [
|
|
113
69
|
handlePrimaryButtonPress,
|
|
114
70
|
handleSecondaryButtonPress,
|
|
115
|
-
inNavModal,
|
|
116
|
-
isBrandBackground,
|
|
117
71
|
onPressPrimaryButton,
|
|
118
72
|
onPressSecondaryButton,
|
|
119
73
|
primaryButtonProps,
|
|
@@ -121,18 +75,9 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
121
75
|
secondaryButtonProps,
|
|
122
76
|
secondaryButtonText,
|
|
123
77
|
]);
|
|
124
|
-
const
|
|
125
|
-
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg", color: isBrandBackground && inNavModal ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground && inNavModal, children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground && inNavModal, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] })] })) : null, inNavModal && (_jsxs(InNavModalContainer, { style: {
|
|
126
|
-
flex: stickyFooter ? 1 : 0,
|
|
127
|
-
...(scrollable ? { marginHorizontal: -1 } : {}),
|
|
128
|
-
}, ...(scrollable ? { contentContainerStyle: { paddingHorizontal: 1 } } : {}), children: [children, !stickyFooter ? (_jsx(View, { style: styles.inNavModalFooterContainer, children: footer })) : null] })), !inNavModal && children, ((!stickyFooter && !inNavModal) || (inNavModal && stickyFooter)) && !noButtons
|
|
129
|
-
? footer
|
|
130
|
-
: null] })) }));
|
|
78
|
+
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, !stickyFooter && !noButtons ? footer : null] })) }));
|
|
131
79
|
const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [footer]);
|
|
132
|
-
return
|
|
133
|
-
flex: 1,
|
|
134
|
-
backgroundColor: theme.color.background[isBrandBackground ? 'brand' : 'primary'],
|
|
135
|
-
}, children: [Platform.OS === 'android' ? (_jsx(Animated.View, { style: [styles.androidContainer, animatedBackgroundStyle], children: _jsx(Animated.View, { style: [styles.pretendContent, animatedPretendContentStyle] }) })) : null, _jsx(Animated.View, { style: [styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle], children: _jsx(SafeAreaView, { edges: ['top', 'bottom'], style: styles.inNavModalContent, children: content }) })] })) : (_jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, style: styles.modal, footerComponent: stickyFooter && !noButtons ? renderFooter : undefined, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: styles.scrollView, ref: scrollViewRef, children: content })] }));
|
|
80
|
+
return (_jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, style: styles.modal, footerComponent: stickyFooter && !noButtons ? renderFooter : undefined, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: styles.scrollView, ref: scrollViewRef, children: content })] }));
|
|
136
81
|
};
|
|
137
82
|
const styles = StyleSheet.create((theme, rt) => ({
|
|
138
83
|
modal: {
|
|
@@ -229,39 +174,5 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
229
174
|
paddingHorizontal: theme.components.bottomSheet.padding,
|
|
230
175
|
paddingBottom: theme.components.bottomSheet.padding + rt.insets.bottom,
|
|
231
176
|
},
|
|
232
|
-
inNavModalContainer: {
|
|
233
|
-
flex: 1,
|
|
234
|
-
...(Platform.OS === 'ios' ? { backgroundColor: theme.components.overlay.backgroundColor } : {}),
|
|
235
|
-
},
|
|
236
|
-
inNavModalContent: {
|
|
237
|
-
flex: 1,
|
|
238
|
-
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
239
|
-
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
240
|
-
backgroundColor: theme.color.surface.neutral.strong,
|
|
241
|
-
padding: theme.components.bottomSheet.padding,
|
|
242
|
-
variants: {
|
|
243
|
-
background: {
|
|
244
|
-
primary: {},
|
|
245
|
-
brand: {
|
|
246
|
-
backgroundColor: theme.color.background.brand,
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
inNavModalFooterContainer: {
|
|
252
|
-
paddingTop: theme.components.bottomSheet.padding,
|
|
253
|
-
},
|
|
254
|
-
androidContainer: {
|
|
255
|
-
height: rt.insets.top + 18,
|
|
256
|
-
paddingLeft: theme.components.bottomSheet.padding,
|
|
257
|
-
paddingRight: theme.components.bottomSheet.padding,
|
|
258
|
-
justifyContent: 'flex-end',
|
|
259
|
-
},
|
|
260
|
-
pretendContent: {
|
|
261
|
-
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
262
|
-
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
263
|
-
height: 12,
|
|
264
|
-
backgroundColor: theme.components.parts.modalStack.backgroundColorCardTop,
|
|
265
|
-
},
|
|
266
177
|
}));
|
|
267
178
|
export default Modal;
|
|
@@ -1,37 +1,8 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { ViewProps } from 'react-native';
|
|
3
1
|
import { BottomSheetProps } from '../BottomSheet';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
interface ModalPropsBase extends Omit<BottomSheetProps, 'children'> {
|
|
7
|
-
loading?: boolean;
|
|
8
|
-
image?: ReactNode;
|
|
9
|
-
showCloseButton?: boolean;
|
|
10
|
-
heading?: string;
|
|
11
|
-
loadingHeading?: string;
|
|
12
|
-
description?: string;
|
|
2
|
+
import { ModalCommonProps } from './Modal.shared.types';
|
|
3
|
+
interface ModalProps extends Omit<BottomSheetProps, 'children'>, ModalCommonProps {
|
|
13
4
|
fullscreen?: boolean;
|
|
14
|
-
stickyFooter?: boolean;
|
|
15
|
-
children?: ViewProps['children'];
|
|
16
|
-
onPressPrimaryButton?: () => void;
|
|
17
|
-
primaryButtonText?: string;
|
|
18
|
-
onPressSecondaryButton?: () => void;
|
|
19
5
|
closeOnPrimaryButtonPress?: boolean;
|
|
20
|
-
secondaryButtonText?: string;
|
|
21
|
-
onPressCloseButton?: () => void;
|
|
22
6
|
closeOnSecondaryButtonPress?: boolean;
|
|
23
|
-
primaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
|
|
24
|
-
secondaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
|
|
25
|
-
closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
|
|
26
7
|
}
|
|
27
|
-
type ModalProps = (ModalPropsBase & {
|
|
28
|
-
inNavModal?: false | undefined;
|
|
29
|
-
scrollable?: never;
|
|
30
|
-
background?: never;
|
|
31
|
-
}) | (ModalPropsBase & {
|
|
32
|
-
inNavModal: true;
|
|
33
|
-
fullscreen?: never;
|
|
34
|
-
scrollable?: boolean;
|
|
35
|
-
background?: 'default' | 'brand';
|
|
36
|
-
});
|
|
37
8
|
export default ModalProps;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { ViewProps } from 'react-native';
|
|
3
|
+
import { ButtonWithoutChildrenProps } from '../Button/Button.props';
|
|
4
|
+
import { UnstyledIconButtonProps } from '../UnstyledIconButton';
|
|
5
|
+
export interface ModalCommonProps {
|
|
6
|
+
loading?: boolean;
|
|
7
|
+
image?: ReactNode;
|
|
8
|
+
showCloseButton?: boolean;
|
|
9
|
+
heading?: string;
|
|
10
|
+
loadingHeading?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
stickyFooter?: boolean;
|
|
13
|
+
children?: ViewProps['children'];
|
|
14
|
+
onPressPrimaryButton?: () => void;
|
|
15
|
+
primaryButtonText?: string;
|
|
16
|
+
onPressSecondaryButton?: () => void;
|
|
17
|
+
secondaryButtonText?: string;
|
|
18
|
+
onPressCloseButton?: () => void;
|
|
19
|
+
primaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
|
|
20
|
+
secondaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
|
|
21
|
+
closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
|
|
22
|
+
}
|
|
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
|
|
|
3
3
|
type Modal<T = any> = BottomSheetModalMethods<T> & {
|
|
4
4
|
triggerCloseAnimation?: () => void;
|
|
5
5
|
};
|
|
6
|
-
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, image, primaryButtonProps, secondaryButtonProps, closeButtonProps,
|
|
6
|
+
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export default Modal;
|
|
@@ -1,61 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
|
-
import {
|
|
3
|
+
import { useImperativeHandle, useRef } from 'react';
|
|
4
4
|
import { AccessibilityInfo, Platform, View, findNodeHandle } from 'react-native';
|
|
5
|
-
import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withTiming, } from 'react-native-reanimated';
|
|
6
5
|
import { StyleSheet } from 'react-native-unistyles';
|
|
7
|
-
import { useTheme } from '../../hooks';
|
|
8
|
-
import { hexWithOpacity } from '../../utils';
|
|
9
6
|
import { BodyText } from '../BodyText';
|
|
10
7
|
import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
|
|
11
8
|
import { Button } from '../Button';
|
|
12
9
|
import { Heading } from '../Heading';
|
|
13
10
|
import { Spinner } from '../Spinner';
|
|
14
11
|
import { UnstyledIconButton } from '../UnstyledIconButton';
|
|
15
|
-
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', image, primaryButtonProps, secondaryButtonProps, closeButtonProps,
|
|
12
|
+
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, ...props }) => {
|
|
16
13
|
const bottomSheetModalRef = useRef(null);
|
|
17
14
|
const viewRef = useRef(null);
|
|
18
15
|
const scrollViewRef = useRef(null);
|
|
19
|
-
const theme = useTheme();
|
|
20
|
-
const backgroundOpacity = useSharedValue(0);
|
|
21
|
-
const pretendContentTranslateY = useSharedValue(20);
|
|
22
|
-
const triggerCloseAnimation = useCallback(() => {
|
|
23
|
-
if (Platform.OS === 'android' && inNavModal) {
|
|
24
|
-
pretendContentTranslateY.value = withTiming(20, {
|
|
25
|
-
duration: 50,
|
|
26
|
-
easing: Easing.in(Easing.quad),
|
|
27
|
-
});
|
|
28
|
-
backgroundOpacity.value = withTiming(0, {
|
|
29
|
-
duration: 100,
|
|
30
|
-
easing: Easing.in(Easing.quad),
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
}, [Platform.OS, inNavModal, pretendContentTranslateY, backgroundOpacity]);
|
|
34
16
|
useImperativeHandle(ref, () => ({
|
|
35
17
|
...bottomSheetModalRef.current,
|
|
36
|
-
triggerCloseAnimation,
|
|
37
|
-
}));
|
|
38
|
-
// Trigger animations on render for inNavModal Android modal
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (Platform.OS === 'android' && inNavModal) {
|
|
41
|
-
backgroundOpacity.value = withDelay(300, withTiming(1, {
|
|
42
|
-
duration: 200,
|
|
43
|
-
easing: Easing.out(Easing.quad),
|
|
44
|
-
}));
|
|
45
|
-
pretendContentTranslateY.value = withDelay(500, withTiming(0, {
|
|
46
|
-
duration: 300,
|
|
47
|
-
easing: Easing.out(Easing.quad),
|
|
48
|
-
}));
|
|
49
|
-
}
|
|
50
|
-
}, [inNavModal, backgroundOpacity, pretendContentTranslateY]);
|
|
51
|
-
const animatedBackgroundStyle = useAnimatedStyle(() => ({
|
|
52
|
-
backgroundColor: hexWithOpacity(theme.components.overlay.backgroundColor, backgroundOpacity.value * (theme.components.overlay.opacity / 100)),
|
|
53
|
-
}));
|
|
54
|
-
const animatedInNavModalStyle = useAnimatedStyle(() => ({
|
|
55
|
-
backgroundColor: hexWithOpacity(theme.components.overlay.backgroundColor, backgroundOpacity.value * (theme.components.overlay.opacity / 100)),
|
|
56
|
-
}));
|
|
57
|
-
const animatedPretendContentStyle = useAnimatedStyle(() => ({
|
|
58
|
-
transform: [{ translateY: pretendContentTranslateY.value }],
|
|
59
18
|
}));
|
|
60
19
|
const handleChange = (index, position, type) => {
|
|
61
20
|
if (index > -1) {
|
|
@@ -97,10 +56,11 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
97
56
|
bottomSheetModalRef.current?.dismiss();
|
|
98
57
|
}
|
|
99
58
|
};
|
|
100
|
-
const
|
|
101
|
-
|
|
59
|
+
const noButtons = !onPressPrimaryButton && !onPressSecondaryButton;
|
|
60
|
+
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, !noButtons ? (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })) : null] })) }));
|
|
61
|
+
return (_jsx(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, ...props, onChange: handleChange, children: _jsx(BottomSheetScrollView, { contentContainerStyle: styles.container, ref: scrollViewRef, children: content }) }));
|
|
102
62
|
};
|
|
103
|
-
const styles = StyleSheet.create(
|
|
63
|
+
const styles = StyleSheet.create(theme => ({
|
|
104
64
|
container: {
|
|
105
65
|
flex: 1,
|
|
106
66
|
gap: theme.components.modal.gap,
|
|
@@ -131,30 +91,5 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
131
91
|
footer: {
|
|
132
92
|
gap: theme.components.modal.action.gap,
|
|
133
93
|
},
|
|
134
|
-
inNavModalContainer: {
|
|
135
|
-
flex: 1,
|
|
136
|
-
...(Platform.OS === 'ios' ? { backgroundColor: theme.components.overlay.backgroundColor } : {}),
|
|
137
|
-
},
|
|
138
|
-
inNavModalContent: {
|
|
139
|
-
flex: 1,
|
|
140
|
-
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
141
|
-
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
142
|
-
backgroundColor: theme.color.surface.neutral.strong,
|
|
143
|
-
gap: theme.components.modal.gap,
|
|
144
|
-
padding: theme.components.bottomSheet.padding,
|
|
145
|
-
paddingBottom: theme.components.modal.padding + rt.insets.bottom,
|
|
146
|
-
},
|
|
147
|
-
androidContainer: {
|
|
148
|
-
height: rt.insets.top + 18,
|
|
149
|
-
paddingLeft: theme.components.bottomSheet.padding,
|
|
150
|
-
paddingRight: theme.components.bottomSheet.padding,
|
|
151
|
-
justifyContent: 'flex-end',
|
|
152
|
-
},
|
|
153
|
-
pretendContent: {
|
|
154
|
-
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
155
|
-
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
156
|
-
height: 12,
|
|
157
|
-
backgroundColor: theme.components.parts.modalStack.backgroundColorCardTop,
|
|
158
|
-
},
|
|
159
94
|
}));
|
|
160
95
|
export default Modal;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import NavModalProps from './NavModal.props';
|
|
2
|
+
declare const NavModal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, loading, loadingHeading, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, background, scrollable, presentation, scrollViewProps, safeAreaViewProps, ...props }: NavModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export default NavModal;
|