@utilitywarehouse/hearth-react-native 0.8.2 → 0.10.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +16 -0
- package/build/components/Banner/Banner.js +25 -6
- package/build/components/Banner/Banner.props.d.ts +2 -2
- package/build/components/BottomSheet/BottomSheetHandle.js +8 -0
- package/build/components/Menu/Menu.context.d.ts +5 -0
- package/build/components/Menu/Menu.context.js +9 -0
- package/build/components/Menu/Menu.d.ts +4 -0
- package/build/components/Menu/Menu.js +25 -0
- package/build/components/Menu/Menu.props.d.ts +21 -0
- package/build/components/Menu/Menu.props.js +1 -0
- package/build/components/Menu/MenuItem.d.ts +18 -0
- package/build/components/Menu/MenuItem.js +115 -0
- package/build/components/Menu/MenuItem.props.d.ts +27 -0
- package/build/components/Menu/MenuItem.props.js +1 -0
- package/build/components/Menu/MenuTrigger.d.ts +9 -0
- package/build/components/Menu/MenuTrigger.js +11 -0
- package/build/components/Menu/MenuTrigger.props.d.ts +12 -0
- package/build/components/Menu/MenuTrigger.props.js +1 -0
- package/build/components/Menu/index.d.ts +7 -0
- package/build/components/Menu/index.js +4 -0
- package/build/components/Modal/Modal.d.ts +1 -1
- package/build/components/Modal/Modal.js +32 -30
- package/build/components/Modal/Modal.props.d.ts +1 -0
- package/build/components/Modal/Modal.web.d.ts +1 -1
- package/build/components/Modal/Modal.web.js +25 -25
- package/build/components/PillGroup/Pill.d.ts +16 -0
- package/build/components/PillGroup/Pill.js +94 -0
- package/build/components/PillGroup/Pill.props.d.ts +10 -0
- package/build/components/PillGroup/Pill.props.js +1 -0
- package/build/components/PillGroup/PillGroup.context.d.ts +6 -0
- package/build/components/PillGroup/PillGroup.context.js +5 -0
- package/build/components/PillGroup/PillGroup.d.ts +5 -0
- package/build/components/PillGroup/PillGroup.js +34 -0
- package/build/components/PillGroup/PillGroup.props.d.ts +15 -0
- package/build/components/PillGroup/PillGroup.props.js +1 -0
- package/build/components/PillGroup/index.d.ts +4 -0
- package/build/components/PillGroup/index.js +2 -0
- package/build/components/Select/Select.js +2 -1
- package/build/components/Toast/Toast.context.d.ts +9 -0
- package/build/components/Toast/Toast.context.js +90 -0
- package/build/components/Toast/Toast.props.d.ts +29 -0
- package/build/components/Toast/Toast.props.js +1 -0
- package/build/components/Toast/ToastItem.d.ts +10 -0
- package/build/components/Toast/ToastItem.js +129 -0
- package/build/components/Toast/index.d.ts +3 -0
- package/build/components/Toast/index.js +2 -0
- package/build/components/index.d.ts +3 -0
- package/build/components/index.js +3 -0
- package/build/tokens/components/dark/checkbox.d.ts +3 -0
- package/build/tokens/components/dark/checkbox.js +3 -0
- package/build/tokens/components/dark/index.d.ts +3 -1
- package/build/tokens/components/dark/index.js +3 -1
- package/build/tokens/components/dark/input.d.ts +9 -0
- package/build/tokens/components/dark/input.js +9 -0
- package/build/tokens/components/dark/modal.d.ts +7 -4
- package/build/tokens/components/dark/modal.js +7 -4
- package/build/tokens/components/dark/radio.d.ts +3 -0
- package/build/tokens/components/dark/radio.js +3 -0
- package/build/tokens/components/dark/rating.d.ts +8 -0
- package/build/tokens/components/dark/rating.js +7 -0
- package/build/tokens/components/dark/table.d.ts +2 -3
- package/build/tokens/components/dark/table.js +2 -3
- package/build/tokens/components/dark/time-picker.d.ts +29 -0
- package/build/tokens/components/dark/time-picker.js +28 -0
- package/build/tokens/components/dark/timeline.d.ts +27 -0
- package/build/tokens/components/dark/timeline.js +26 -0
- package/build/tokens/components/dark/toast.d.ts +6 -2
- package/build/tokens/components/dark/toast.js +6 -2
- package/build/tokens/components/light/checkbox.d.ts +3 -0
- package/build/tokens/components/light/checkbox.js +3 -0
- package/build/tokens/components/light/index.d.ts +3 -1
- package/build/tokens/components/light/index.js +3 -1
- package/build/tokens/components/light/input.d.ts +9 -0
- package/build/tokens/components/light/input.js +9 -0
- package/build/tokens/components/light/modal.d.ts +7 -4
- package/build/tokens/components/light/modal.js +7 -4
- package/build/tokens/components/light/radio.d.ts +3 -0
- package/build/tokens/components/light/radio.js +3 -0
- package/build/tokens/components/light/rating.d.ts +8 -0
- package/build/tokens/components/light/rating.js +7 -0
- package/build/tokens/components/light/table.d.ts +2 -3
- package/build/tokens/components/light/table.js +2 -3
- package/build/tokens/components/light/time-picker.d.ts +29 -0
- package/build/tokens/components/light/time-picker.js +28 -0
- package/build/tokens/components/light/timeline.d.ts +27 -0
- package/build/tokens/components/light/timeline.js +26 -0
- package/build/tokens/components/light/toast.d.ts +6 -2
- package/build/tokens/components/light/toast.js +6 -2
- package/docs/assets/toast-ios.MP4 +0 -0
- package/docs/components/AllComponents.web.tsx +59 -0
- package/docs/components/BackToTopButton.tsx +1 -1
- package/package.json +4 -4
- package/src/components/Banner/Banner.docs.mdx +19 -10
- package/src/components/Banner/Banner.props.ts +2 -2
- package/src/components/Banner/Banner.stories.tsx +1 -4
- package/src/components/Banner/Banner.tsx +47 -7
- package/src/components/BottomSheet/BottomSheetHandle.tsx +12 -0
- package/src/components/DatePickerInput/DatePickerInput.docs.mdx +1 -1
- package/src/components/Menu/Menu.context.ts +15 -0
- package/src/components/Menu/Menu.docs.mdx +158 -0
- package/src/components/Menu/Menu.props.ts +24 -0
- package/src/components/Menu/Menu.stories.tsx +292 -0
- package/src/components/Menu/Menu.tsx +54 -0
- package/src/components/Menu/MenuItem.props.ts +29 -0
- package/src/components/Menu/MenuItem.tsx +145 -0
- package/src/components/Menu/MenuTrigger.props.ts +14 -0
- package/src/components/Menu/MenuTrigger.tsx +20 -0
- package/src/components/Menu/index.ts +7 -0
- package/src/components/Modal/Modal.docs.mdx +34 -5
- package/src/components/Modal/Modal.props.ts +1 -0
- package/src/components/Modal/Modal.stories.tsx +46 -0
- package/src/components/Modal/Modal.tsx +37 -33
- package/src/components/Modal/Modal.web.tsx +27 -27
- package/src/components/PillGroup/Pill.props.ts +13 -0
- package/src/components/PillGroup/Pill.tsx +120 -0
- package/src/components/PillGroup/PillGroup.context.tsx +12 -0
- package/src/components/PillGroup/PillGroup.docs.mdx +96 -0
- package/src/components/PillGroup/PillGroup.props.ts +22 -0
- package/src/components/PillGroup/PillGroup.stories.tsx +159 -0
- package/src/components/PillGroup/PillGroup.tsx +66 -0
- package/src/components/PillGroup/index.ts +4 -0
- package/src/components/Select/Select.tsx +2 -0
- package/src/components/Toast/Toast.context.tsx +118 -0
- package/src/components/Toast/Toast.docs.mdx +164 -0
- package/src/components/Toast/Toast.props.ts +33 -0
- package/src/components/Toast/Toast.stories.tsx +356 -0
- package/src/components/Toast/ToastItem.tsx +200 -0
- package/src/components/Toast/index.ts +3 -0
- package/src/components/index.ts +3 -0
- package/src/tokens/components/dark/checkbox.ts +3 -0
- package/src/tokens/components/dark/index.ts +3 -1
- package/src/tokens/components/dark/input.ts +9 -0
- package/src/tokens/components/dark/modal.ts +7 -4
- package/src/tokens/components/dark/radio.ts +3 -0
- package/src/tokens/components/dark/rating.ts +8 -0
- package/src/tokens/components/dark/table.ts +2 -3
- package/src/tokens/components/dark/time-picker.ts +29 -0
- package/src/tokens/components/dark/timeline.ts +27 -0
- package/src/tokens/components/dark/toast.ts +6 -2
- package/src/tokens/components/light/checkbox.ts +3 -0
- package/src/tokens/components/light/index.ts +3 -1
- package/src/tokens/components/light/input.ts +9 -0
- package/src/tokens/components/light/modal.ts +7 -4
- package/src/tokens/components/light/radio.ts +3 -0
- package/src/tokens/components/light/rating.ts +8 -0
- package/src/tokens/components/light/table.ts +2 -3
- package/src/tokens/components/light/time-picker.ts +29 -0
- package/src/tokens/components/light/timeline.ts +27 -0
- package/src/tokens/components/light/toast.ts +6 -2
- package/build/tokens/components/dark/dialog.d.ts +0 -25
- package/build/tokens/components/dark/dialog.js +0 -24
- package/build/tokens/components/light/dialog.d.ts +0 -25
- package/build/tokens/components/light/dialog.js +0 -24
- package/src/tokens/components/dark/dialog.ts +0 -25
- package/src/tokens/components/light/dialog.ts +0 -25
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface MenuTriggerProps {
|
|
4
|
+
/**
|
|
5
|
+
* The child element that triggers the menu (should be a single pressable element like Button)
|
|
6
|
+
*/
|
|
7
|
+
children: ReactElement;
|
|
8
|
+
/**
|
|
9
|
+
* Called when the trigger is pressed.
|
|
10
|
+
*/
|
|
11
|
+
onPress?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default MenuTriggerProps;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cloneElement, isValidElement } from 'react';
|
|
2
|
+
import type MenuTriggerProps from './MenuTrigger.props';
|
|
3
|
+
|
|
4
|
+
interface MenuTriggerInternalProps extends MenuTriggerProps {
|
|
5
|
+
onPress: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const MenuTrigger = ({ children, onPress }: MenuTriggerInternalProps) => {
|
|
9
|
+
if (!isValidElement(children)) {
|
|
10
|
+
throw new Error('MenuTrigger: children must be a valid React element');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return cloneElement(children, {
|
|
14
|
+
onPress,
|
|
15
|
+
} as { onPress?: () => void });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
MenuTrigger.displayName = 'MenuTrigger';
|
|
19
|
+
|
|
20
|
+
export default MenuTrigger;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as Menu } from './Menu';
|
|
2
|
+
export { useMenuContext } from './Menu.context';
|
|
3
|
+
export type { MenuMethods, default as MenuProps } from './Menu.props';
|
|
4
|
+
export { default as MenuItem } from './MenuItem';
|
|
5
|
+
export type { default as MenuItemProps } from './MenuItem.props';
|
|
6
|
+
export { default as MenuTrigger } from './MenuTrigger';
|
|
7
|
+
export type { default as MenuTriggerProps } from './MenuTrigger.props';
|
|
@@ -25,11 +25,12 @@ The Modal component is ideal for displaying important information, collecting us
|
|
|
25
25
|
- [Examples](#examples)
|
|
26
26
|
- [Basic Modal](#basic-modal)
|
|
27
27
|
- [Modal with Image](#modal-with-image)
|
|
28
|
+
- [Fullscreen Modal](#fullscreen-modal)
|
|
28
29
|
- [Modal with Custom Content](#modal-with-custom-content)
|
|
29
30
|
- [Loading State](#loading-state)
|
|
30
31
|
- [Without Close Button](#without-close-button)
|
|
31
32
|
- [Single Action Modal](#single-action-modal)
|
|
32
|
-
- [
|
|
33
|
+
- [Modal in Navigation Modal](#modal-in-navigation-modal)
|
|
33
34
|
- [Integration Notes](#integration-notes)
|
|
34
35
|
- [External Resources](#external-resources)
|
|
35
36
|
|
|
@@ -104,7 +105,8 @@ The Modal component extends the `BottomSheetModal` component and accepts all of
|
|
|
104
105
|
| `primaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the primary button (colorScheme defaults to 'highlight', variant to 'solid') | - |
|
|
105
106
|
| `secondaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the secondary button (colorScheme defaults to 'functional', variant to 'outline') | - |
|
|
106
107
|
| `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Additional props to pass to the close button | - |
|
|
107
|
-
| `fullscreen` | `boolean` | Whether the modal should take up the full screen height
|
|
108
|
+
| `fullscreen` | `boolean` | Whether the modal should take up the full screen height | `false` |
|
|
109
|
+
| `inNavModal` | `boolean` | Renders the modal correctly when used inside a navigation modal | `false` |
|
|
108
110
|
|
|
109
111
|
## Features
|
|
110
112
|
|
|
@@ -292,6 +294,33 @@ const ImageModal = () => {
|
|
|
292
294
|
};
|
|
293
295
|
```
|
|
294
296
|
|
|
297
|
+
### Fullscreen Modal
|
|
298
|
+
|
|
299
|
+
Create a modal that takes up the full screen height:
|
|
300
|
+
|
|
301
|
+
<Canvas of={Stories.FullscreenModal} />
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
const FullscreenModal = () => {
|
|
305
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<>
|
|
309
|
+
<Button onPress={() => modalRef.current?.present()}>Show Fullscreen Modal</Button>
|
|
310
|
+
|
|
311
|
+
<Modal
|
|
312
|
+
ref={modalRef}
|
|
313
|
+
heading="Fullscreen Modal"
|
|
314
|
+
description="This modal takes up the full screen height"
|
|
315
|
+
primaryButtonText="Close"
|
|
316
|
+
onPressPrimaryButton={() => modalRef.current?.dismiss()}
|
|
317
|
+
fullscreen
|
|
318
|
+
/>
|
|
319
|
+
</>
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
295
324
|
### Modal with Custom Content
|
|
296
325
|
|
|
297
326
|
Add custom content between the header and footer sections:
|
|
@@ -414,9 +443,9 @@ const AlertModal = () => {
|
|
|
414
443
|
};
|
|
415
444
|
```
|
|
416
445
|
|
|
417
|
-
###
|
|
446
|
+
### Modal In Navigation Modal
|
|
418
447
|
|
|
419
|
-
When using the Modal component in a navigation context, you can set it to
|
|
448
|
+
When using the Modal component in a navigation context, you can set it to `inNavModal` mode, this will make it behave like a standard modal screen.
|
|
420
449
|
Here's an example of how to implement this with custom close animations for Android:
|
|
421
450
|
|
|
422
451
|
```tsx
|
|
@@ -465,7 +494,7 @@ export default function ModalScreen() {
|
|
|
465
494
|
return (
|
|
466
495
|
<Modal
|
|
467
496
|
ref={modalRef}
|
|
468
|
-
|
|
497
|
+
inNavModal
|
|
469
498
|
onPressCloseButton={handleClose}
|
|
470
499
|
primaryButtonText="Action"
|
|
471
500
|
onPressPrimaryButton={handleClose}
|
|
@@ -9,6 +9,7 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
|
|
|
9
9
|
showCloseButton?: boolean;
|
|
10
10
|
heading?: string;
|
|
11
11
|
description?: string;
|
|
12
|
+
inNavModal?: boolean;
|
|
12
13
|
fullscreen?: boolean;
|
|
13
14
|
children?: ViewProps['children'];
|
|
14
15
|
onPressPrimaryButton?: () => void;
|
|
@@ -41,6 +41,12 @@ const meta = {
|
|
|
41
41
|
control: 'boolean',
|
|
42
42
|
description: 'Whether the modal is in a loading state.',
|
|
43
43
|
},
|
|
44
|
+
fullscreen: {
|
|
45
|
+
control: 'boolean',
|
|
46
|
+
description: 'Whether the modal should take up the full screen height.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
actions: {
|
|
44
50
|
onPressPrimaryButton: { action: () => null },
|
|
45
51
|
onPressSecondaryButton: { action: () => null },
|
|
46
52
|
onPressCloseButton: { action: () => null },
|
|
@@ -52,6 +58,7 @@ const meta = {
|
|
|
52
58
|
primaryButtonText: 'Primary',
|
|
53
59
|
secondaryButtonText: 'Cancel',
|
|
54
60
|
loading: false,
|
|
61
|
+
fullscreen: false,
|
|
55
62
|
onPressCloseButton: () => null,
|
|
56
63
|
onPressPrimaryButton: () => null,
|
|
57
64
|
onPressSecondaryButton: () => null,
|
|
@@ -192,3 +199,42 @@ export const Loading = () => {
|
|
|
192
199
|
</View>
|
|
193
200
|
);
|
|
194
201
|
};
|
|
202
|
+
|
|
203
|
+
export const FullscreenModal: Story = {
|
|
204
|
+
render: () => {
|
|
205
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
206
|
+
|
|
207
|
+
const openModal = () => {
|
|
208
|
+
modalRef.current?.present();
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const closeModal = () => {
|
|
212
|
+
modalRef.current?.dismiss();
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 800 } : { flex: 1 }}>
|
|
217
|
+
<ViewWrap>
|
|
218
|
+
<Button onPress={openModal}>Open Fullscreen Modal</Button>
|
|
219
|
+
|
|
220
|
+
<Modal
|
|
221
|
+
ref={modalRef}
|
|
222
|
+
heading="Fullscreen Modal Heading"
|
|
223
|
+
description="This is a fullscreen modal description"
|
|
224
|
+
onPressCloseButton={closeModal}
|
|
225
|
+
primaryButtonText="Primary"
|
|
226
|
+
onPressPrimaryButton={closeModal}
|
|
227
|
+
secondaryButtonText="Cancel"
|
|
228
|
+
onPressSecondaryButton={closeModal}
|
|
229
|
+
fullscreen
|
|
230
|
+
>
|
|
231
|
+
<Box gap="200">
|
|
232
|
+
<BodyText>This is a fullscreen modal with content.</BodyText>
|
|
233
|
+
<BodyText>You can swipe it up and down to close.</BodyText>
|
|
234
|
+
</Box>
|
|
235
|
+
</Modal>
|
|
236
|
+
</ViewWrap>
|
|
237
|
+
</View>
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
};
|
|
@@ -37,11 +37,12 @@ const Modal = ({
|
|
|
37
37
|
closeOnPrimaryButtonPress = true,
|
|
38
38
|
closeOnSecondaryButtonPress = true,
|
|
39
39
|
loading,
|
|
40
|
+
fullscreen = false,
|
|
40
41
|
image,
|
|
41
42
|
primaryButtonProps,
|
|
42
43
|
secondaryButtonProps,
|
|
43
44
|
closeButtonProps,
|
|
44
|
-
|
|
45
|
+
inNavModal = false,
|
|
45
46
|
...props
|
|
46
47
|
}: ModalProps) => {
|
|
47
48
|
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
|
@@ -52,7 +53,7 @@ const Modal = ({
|
|
|
52
53
|
const pretendContentTranslateY = useSharedValue(20);
|
|
53
54
|
|
|
54
55
|
const triggerCloseAnimation = useCallback(() => {
|
|
55
|
-
if (Platform.OS === 'android' &&
|
|
56
|
+
if (Platform.OS === 'android' && inNavModal) {
|
|
56
57
|
pretendContentTranslateY.value = withTiming(20, {
|
|
57
58
|
duration: 50,
|
|
58
59
|
easing: Easing.in(Easing.quad),
|
|
@@ -62,16 +63,16 @@ const Modal = ({
|
|
|
62
63
|
easing: Easing.in(Easing.quad),
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
|
-
}, [Platform.OS,
|
|
66
|
+
}, [Platform.OS, inNavModal, pretendContentTranslateY, backgroundOpacity]);
|
|
66
67
|
|
|
67
68
|
useImperativeHandle(ref, () => ({
|
|
68
69
|
...(bottomSheetModalRef.current as BottomSheetModal),
|
|
69
70
|
triggerCloseAnimation,
|
|
70
71
|
}));
|
|
71
72
|
|
|
72
|
-
// Trigger animations on render for
|
|
73
|
+
// Trigger animations on render for inNavModal Android modal
|
|
73
74
|
useEffect(() => {
|
|
74
|
-
if (Platform.OS === 'android' &&
|
|
75
|
+
if (Platform.OS === 'android' && inNavModal) {
|
|
75
76
|
backgroundOpacity.value = withDelay(
|
|
76
77
|
300,
|
|
77
78
|
withTiming(1, {
|
|
@@ -87,7 +88,7 @@ const Modal = ({
|
|
|
87
88
|
})
|
|
88
89
|
);
|
|
89
90
|
}
|
|
90
|
-
}, [
|
|
91
|
+
}, [inNavModal, backgroundOpacity, pretendContentTranslateY]);
|
|
91
92
|
|
|
92
93
|
const animatedBackgroundStyle = useAnimatedStyle(() => ({
|
|
93
94
|
backgroundColor: hexWithOpacity(
|
|
@@ -96,7 +97,7 @@ const Modal = ({
|
|
|
96
97
|
),
|
|
97
98
|
}));
|
|
98
99
|
|
|
99
|
-
const
|
|
100
|
+
const animatedInNavModalStyle = useAnimatedStyle(() => ({
|
|
100
101
|
backgroundColor: hexWithOpacity(
|
|
101
102
|
theme.components.overlay.backgroundColor,
|
|
102
103
|
backgroundOpacity.value * (theme.components.overlay.opacity / 100)
|
|
@@ -240,7 +241,7 @@ const Modal = ({
|
|
|
240
241
|
</>
|
|
241
242
|
);
|
|
242
243
|
|
|
243
|
-
return
|
|
244
|
+
return inNavModal ? (
|
|
244
245
|
<View style={{ flex: 1, backgroundColor: theme.color.background.primary }}>
|
|
245
246
|
{Platform.OS === 'android' ? (
|
|
246
247
|
<Animated.View style={[styles.androidContainer, animatedBackgroundStyle]}>
|
|
@@ -248,18 +249,19 @@ const Modal = ({
|
|
|
248
249
|
</Animated.View>
|
|
249
250
|
) : null}
|
|
250
251
|
<Animated.View
|
|
251
|
-
style={[styles.
|
|
252
|
+
style={[styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle]}
|
|
252
253
|
>
|
|
253
|
-
<View style={styles.
|
|
254
|
+
<View style={styles.inNavModalContent}>{content}</View>
|
|
254
255
|
</Animated.View>
|
|
255
256
|
</View>
|
|
256
257
|
) : (
|
|
257
258
|
<BottomSheetModal
|
|
258
259
|
ref={bottomSheetModalRef}
|
|
259
260
|
enableDynamicSizing={true}
|
|
260
|
-
snapPoints={image ? ['90%'] : props.snapPoints}
|
|
261
|
+
snapPoints={image || fullscreen ? ['90%'] : props.snapPoints}
|
|
261
262
|
showHandle={typeof loading !== 'undefined' && loading ? false : props.showHandle}
|
|
262
263
|
accessible={false}
|
|
264
|
+
style={styles.modal}
|
|
263
265
|
{...props}
|
|
264
266
|
onChange={handleChange}
|
|
265
267
|
>
|
|
@@ -272,9 +274,12 @@ const Modal = ({
|
|
|
272
274
|
};
|
|
273
275
|
|
|
274
276
|
const styles = StyleSheet.create((theme, rt) => ({
|
|
277
|
+
modal: {
|
|
278
|
+
gap: theme.components.modal.content.gap - theme.components.bottomSheet.gap,
|
|
279
|
+
},
|
|
275
280
|
container: {
|
|
276
281
|
flex: 1,
|
|
277
|
-
gap: theme.components.
|
|
282
|
+
gap: theme.components.modal.gap,
|
|
278
283
|
variants: {
|
|
279
284
|
loading: {
|
|
280
285
|
true: {
|
|
@@ -285,11 +290,11 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
285
290
|
},
|
|
286
291
|
header: {
|
|
287
292
|
flexDirection: 'row',
|
|
288
|
-
gap: theme.components.
|
|
293
|
+
gap: theme.components.modal.gap,
|
|
289
294
|
},
|
|
290
295
|
headerTextContent: {
|
|
291
296
|
flex: 1,
|
|
292
|
-
gap: theme.components.
|
|
297
|
+
gap: theme.components.modal.content.gap,
|
|
293
298
|
},
|
|
294
299
|
image: {
|
|
295
300
|
width: 260,
|
|
@@ -297,52 +302,51 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
297
302
|
},
|
|
298
303
|
imageContainer: {
|
|
299
304
|
alignItems: 'center',
|
|
300
|
-
justifyContent: 'center',
|
|
301
305
|
flex: 1,
|
|
302
306
|
},
|
|
303
307
|
textContent: {
|
|
304
|
-
gap: theme.components.
|
|
308
|
+
gap: theme.components.modal.content.gap,
|
|
305
309
|
},
|
|
306
310
|
loadingTop: {
|
|
307
|
-
borderTopLeftRadius: theme.components.
|
|
308
|
-
borderTopRightRadius: theme.components.
|
|
311
|
+
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
312
|
+
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
309
313
|
height: 16,
|
|
310
314
|
backgroundColor: theme.color.surface.neutral.strong,
|
|
311
315
|
},
|
|
312
316
|
loadingContainer: {
|
|
313
|
-
borderTopLeftRadius: theme.components.
|
|
314
|
-
borderTopRightRadius: theme.components.
|
|
317
|
+
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
318
|
+
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
315
319
|
flex: 1,
|
|
316
320
|
alignItems: 'center',
|
|
317
321
|
justifyContent: 'center',
|
|
318
322
|
minHeight: 140,
|
|
319
|
-
gap: theme.components.
|
|
323
|
+
gap: theme.components.modal.content.gap,
|
|
320
324
|
},
|
|
321
325
|
footer: {
|
|
322
|
-
gap: theme.components.
|
|
326
|
+
gap: theme.components.modal.action.gap,
|
|
323
327
|
},
|
|
324
|
-
|
|
328
|
+
inNavModalContainer: {
|
|
325
329
|
flex: 1,
|
|
326
330
|
...(Platform.OS === 'ios' ? { backgroundColor: theme.components.overlay.backgroundColor } : {}),
|
|
327
331
|
},
|
|
328
|
-
|
|
332
|
+
inNavModalContent: {
|
|
329
333
|
flex: 1,
|
|
330
|
-
borderTopLeftRadius: theme.components.
|
|
331
|
-
borderTopRightRadius: theme.components.
|
|
334
|
+
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
335
|
+
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
332
336
|
backgroundColor: theme.color.surface.neutral.strong,
|
|
333
|
-
gap: theme.components.
|
|
334
|
-
padding: theme.components.
|
|
335
|
-
paddingBottom: theme.components.
|
|
337
|
+
gap: theme.components.modal.gap,
|
|
338
|
+
padding: theme.components.modal.padding,
|
|
339
|
+
paddingBottom: theme.components.modal.padding + rt.insets.bottom,
|
|
336
340
|
},
|
|
337
341
|
androidContainer: {
|
|
338
342
|
height: rt.insets.top + 18,
|
|
339
|
-
paddingLeft: theme.components.
|
|
340
|
-
paddingRight: theme.components.
|
|
343
|
+
paddingLeft: theme.components.modal.padding,
|
|
344
|
+
paddingRight: theme.components.modal.padding,
|
|
341
345
|
justifyContent: 'flex-end',
|
|
342
346
|
},
|
|
343
347
|
pretendContent: {
|
|
344
|
-
borderTopLeftRadius: theme.components.
|
|
345
|
-
borderTopRightRadius: theme.components.
|
|
348
|
+
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
349
|
+
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
346
350
|
height: 12,
|
|
347
351
|
backgroundColor: theme.components.parts.modalStack.backgroundColorCardTop,
|
|
348
352
|
},
|
|
@@ -41,7 +41,7 @@ const Modal = ({
|
|
|
41
41
|
primaryButtonProps,
|
|
42
42
|
secondaryButtonProps,
|
|
43
43
|
closeButtonProps,
|
|
44
|
-
|
|
44
|
+
inNavModal = false,
|
|
45
45
|
...props
|
|
46
46
|
}: ModalProps) => {
|
|
47
47
|
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
|
@@ -52,7 +52,7 @@ const Modal = ({
|
|
|
52
52
|
const pretendContentTranslateY = useSharedValue(20);
|
|
53
53
|
|
|
54
54
|
const triggerCloseAnimation = useCallback(() => {
|
|
55
|
-
if (Platform.OS === 'android' &&
|
|
55
|
+
if (Platform.OS === 'android' && inNavModal) {
|
|
56
56
|
pretendContentTranslateY.value = withTiming(20, {
|
|
57
57
|
duration: 50,
|
|
58
58
|
easing: Easing.in(Easing.quad),
|
|
@@ -62,16 +62,16 @@ const Modal = ({
|
|
|
62
62
|
easing: Easing.in(Easing.quad),
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
|
-
}, [Platform.OS,
|
|
65
|
+
}, [Platform.OS, inNavModal, pretendContentTranslateY, backgroundOpacity]);
|
|
66
66
|
|
|
67
67
|
useImperativeHandle(ref, () => ({
|
|
68
68
|
...(bottomSheetModalRef.current as BottomSheetModal),
|
|
69
69
|
triggerCloseAnimation,
|
|
70
70
|
}));
|
|
71
71
|
|
|
72
|
-
// Trigger animations on render for
|
|
72
|
+
// Trigger animations on render for inNavModal Android modal
|
|
73
73
|
useEffect(() => {
|
|
74
|
-
if (Platform.OS === 'android' &&
|
|
74
|
+
if (Platform.OS === 'android' && inNavModal) {
|
|
75
75
|
backgroundOpacity.value = withDelay(
|
|
76
76
|
300,
|
|
77
77
|
withTiming(1, {
|
|
@@ -87,7 +87,7 @@ const Modal = ({
|
|
|
87
87
|
})
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
|
-
}, [
|
|
90
|
+
}, [inNavModal, backgroundOpacity, pretendContentTranslateY]);
|
|
91
91
|
|
|
92
92
|
const animatedBackgroundStyle = useAnimatedStyle(() => ({
|
|
93
93
|
backgroundColor: hexWithOpacity(
|
|
@@ -96,7 +96,7 @@ const Modal = ({
|
|
|
96
96
|
),
|
|
97
97
|
}));
|
|
98
98
|
|
|
99
|
-
const
|
|
99
|
+
const animatedInNavModalStyle = useAnimatedStyle(() => ({
|
|
100
100
|
backgroundColor: hexWithOpacity(
|
|
101
101
|
theme.components.overlay.backgroundColor,
|
|
102
102
|
backgroundOpacity.value * (theme.components.overlay.opacity / 100)
|
|
@@ -238,7 +238,7 @@ const Modal = ({
|
|
|
238
238
|
</>
|
|
239
239
|
);
|
|
240
240
|
|
|
241
|
-
return
|
|
241
|
+
return inNavModal ? (
|
|
242
242
|
<View style={{ flex: 1, backgroundColor: theme.color.background.primary }}>
|
|
243
243
|
{Platform.OS === 'android' ? (
|
|
244
244
|
<Animated.View style={[styles.androidContainer, animatedBackgroundStyle]}>
|
|
@@ -246,9 +246,9 @@ const Modal = ({
|
|
|
246
246
|
</Animated.View>
|
|
247
247
|
) : null}
|
|
248
248
|
<Animated.View
|
|
249
|
-
style={[styles.
|
|
249
|
+
style={[styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle]}
|
|
250
250
|
>
|
|
251
|
-
<View style={styles.
|
|
251
|
+
<View style={styles.inNavModalContent}>{content}</View>
|
|
252
252
|
</Animated.View>
|
|
253
253
|
</View>
|
|
254
254
|
) : (
|
|
@@ -271,15 +271,15 @@ const Modal = ({
|
|
|
271
271
|
const styles = StyleSheet.create((theme, rt) => ({
|
|
272
272
|
container: {
|
|
273
273
|
flex: 1,
|
|
274
|
-
gap: theme.components.
|
|
274
|
+
gap: theme.components.modal.gap,
|
|
275
275
|
},
|
|
276
276
|
header: {
|
|
277
277
|
flexDirection: 'row',
|
|
278
|
-
gap: theme.components.
|
|
278
|
+
gap: theme.components.modal.gap,
|
|
279
279
|
},
|
|
280
280
|
headerTextContent: {
|
|
281
281
|
flex: 1,
|
|
282
|
-
gap: theme.components.
|
|
282
|
+
gap: theme.components.modal.content.gap,
|
|
283
283
|
},
|
|
284
284
|
image: {
|
|
285
285
|
width: 260,
|
|
@@ -291,40 +291,40 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
291
291
|
flex: 1,
|
|
292
292
|
},
|
|
293
293
|
textContent: {
|
|
294
|
-
gap: theme.components.
|
|
294
|
+
gap: theme.components.modal.content.gap,
|
|
295
295
|
},
|
|
296
296
|
loadingContainer: {
|
|
297
297
|
flex: 1,
|
|
298
298
|
alignItems: 'center',
|
|
299
299
|
justifyContent: 'center',
|
|
300
300
|
minHeight: 140,
|
|
301
|
-
gap: theme.components.
|
|
301
|
+
gap: theme.components.modal.content.gap,
|
|
302
302
|
},
|
|
303
303
|
footer: {
|
|
304
|
-
gap: theme.components.
|
|
304
|
+
gap: theme.components.modal.action.gap,
|
|
305
305
|
},
|
|
306
|
-
|
|
306
|
+
inNavModalContainer: {
|
|
307
307
|
flex: 1,
|
|
308
308
|
...(Platform.OS === 'ios' ? { backgroundColor: theme.components.overlay.backgroundColor } : {}),
|
|
309
309
|
},
|
|
310
|
-
|
|
310
|
+
inNavModalContent: {
|
|
311
311
|
flex: 1,
|
|
312
|
-
borderTopLeftRadius: theme.components.
|
|
313
|
-
borderTopRightRadius: theme.components.
|
|
312
|
+
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
313
|
+
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
314
314
|
backgroundColor: theme.color.surface.neutral.strong,
|
|
315
|
-
gap: theme.components.
|
|
316
|
-
padding: theme.components.
|
|
317
|
-
paddingBottom: theme.components.
|
|
315
|
+
gap: theme.components.modal.gap,
|
|
316
|
+
padding: theme.components.modal.padding,
|
|
317
|
+
paddingBottom: theme.components.modal.padding + rt.insets.bottom,
|
|
318
318
|
},
|
|
319
319
|
androidContainer: {
|
|
320
320
|
height: rt.insets.top + 18,
|
|
321
|
-
paddingLeft: theme.components.
|
|
322
|
-
paddingRight: theme.components.
|
|
321
|
+
paddingLeft: theme.components.modal.padding,
|
|
322
|
+
paddingRight: theme.components.modal.padding,
|
|
323
323
|
justifyContent: 'flex-end',
|
|
324
324
|
},
|
|
325
325
|
pretendContent: {
|
|
326
|
-
borderTopLeftRadius: theme.components.
|
|
327
|
-
borderTopRightRadius: theme.components.
|
|
326
|
+
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
327
|
+
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
328
328
|
height: 12,
|
|
329
329
|
backgroundColor: theme.components.parts.modalStack.backgroundColorCardTop,
|
|
330
330
|
},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PressableProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface PillProps extends Omit<PressableProps, 'children'> {
|
|
5
|
+
/** Value returned when selected */
|
|
6
|
+
value: string;
|
|
7
|
+
|
|
8
|
+
/** Text label shown inside the pill */
|
|
9
|
+
label: string;
|
|
10
|
+
|
|
11
|
+
/** Left icon */
|
|
12
|
+
icon?: React.ComponentType<any>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createPressable } from '@gluestack-ui/pressable';
|
|
2
|
+
import { Pressable } from 'react-native';
|
|
3
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
import { BodyText } from '../BodyText';
|
|
6
|
+
import { usePillGroupContext } from './PillGroup.context';
|
|
7
|
+
import type { PillProps } from './Pill.props';
|
|
8
|
+
|
|
9
|
+
const PillRoot = ({
|
|
10
|
+
value,
|
|
11
|
+
label,
|
|
12
|
+
icon,
|
|
13
|
+
states = {},
|
|
14
|
+
...props
|
|
15
|
+
}: PillProps & { states?: { active?: boolean } }) => {
|
|
16
|
+
const { active } = states;
|
|
17
|
+
const context = usePillGroupContext();
|
|
18
|
+
const isSelected = context?.value.includes(value) ?? false;
|
|
19
|
+
|
|
20
|
+
styles.useVariants({ selected: isSelected, active });
|
|
21
|
+
|
|
22
|
+
const handlePress = () => {
|
|
23
|
+
context?.onChange(value);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Pressable
|
|
28
|
+
{...props}
|
|
29
|
+
style={styles.pill}
|
|
30
|
+
accessibilityRole="button"
|
|
31
|
+
accessibilityState={{ selected: isSelected }}
|
|
32
|
+
onPress={handlePress}
|
|
33
|
+
>
|
|
34
|
+
{icon && <Icon as={icon} size="sm" style={styles.icon} />}
|
|
35
|
+
<BodyText weight="semibold" style={styles.text}>
|
|
36
|
+
{label}
|
|
37
|
+
</BodyText>
|
|
38
|
+
</Pressable>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const Pill = createPressable({ Root: PillRoot });
|
|
43
|
+
|
|
44
|
+
Pill.displayName = 'Pill';
|
|
45
|
+
|
|
46
|
+
const styles = StyleSheet.create(theme => ({
|
|
47
|
+
pill: {
|
|
48
|
+
flexDirection: 'row',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
height: theme.components.pill.height,
|
|
52
|
+
minWidth: theme.components.pill.minWidth,
|
|
53
|
+
gap: theme.components.pill.gap,
|
|
54
|
+
paddingHorizontal: theme.components.pill.paddingHorizontal,
|
|
55
|
+
paddingVertical: theme.components.pill.paddingVertical,
|
|
56
|
+
borderRadius: theme.components.pill.borderRadius,
|
|
57
|
+
borderWidth: theme.components.pill.borderWidth,
|
|
58
|
+
borderColor: theme.color.interactive.neutral.border.subtle,
|
|
59
|
+
backgroundColor: 'transparent',
|
|
60
|
+
_web: {
|
|
61
|
+
_hover: {
|
|
62
|
+
backgroundColor: theme.color.interactive.neutral.surface.subtle.hover,
|
|
63
|
+
},
|
|
64
|
+
'_focus-visible': theme.helpers.focusVisible,
|
|
65
|
+
},
|
|
66
|
+
variants: {
|
|
67
|
+
active: {
|
|
68
|
+
true: {
|
|
69
|
+
backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
selected: {
|
|
73
|
+
true: {
|
|
74
|
+
backgroundColor: theme.color.interactive.brand.surface.strong.default,
|
|
75
|
+
borderColor: theme.color.interactive.brand.surface.strong.default,
|
|
76
|
+
_web: {
|
|
77
|
+
_hover: {
|
|
78
|
+
backgroundColor: theme.color.interactive.brand.surface.strong.hover,
|
|
79
|
+
borderColor: theme.color.interactive.brand.surface.strong.hover,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
compoundVariants: [
|
|
86
|
+
{
|
|
87
|
+
selected: true,
|
|
88
|
+
active: true,
|
|
89
|
+
styles: {
|
|
90
|
+
backgroundColor: theme.color.interactive.brand.surface.strong.active,
|
|
91
|
+
borderColor: theme.color.interactive.brand.surface.strong.active,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
text: {
|
|
97
|
+
variants: {
|
|
98
|
+
selected: {
|
|
99
|
+
true: {
|
|
100
|
+
color: theme.color.text.inverted,
|
|
101
|
+
},
|
|
102
|
+
false: {
|
|
103
|
+
color: theme.color.text.primary,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
icon: {
|
|
109
|
+
variants: {
|
|
110
|
+
selected: {
|
|
111
|
+
true: {
|
|
112
|
+
color: theme.color.icon.inverted,
|
|
113
|
+
},
|
|
114
|
+
false: {
|
|
115
|
+
color: theme.color.icon.primary,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface PillGroupContextValue {
|
|
4
|
+
value: string[];
|
|
5
|
+
onChange: (value: string) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const PillGroupContext = createContext<PillGroupContextValue | null>(null);
|
|
9
|
+
|
|
10
|
+
export const usePillGroupContext = () => {
|
|
11
|
+
return useContext(PillGroupContext);
|
|
12
|
+
};
|