@utilitywarehouse/hearth-react-native 0.8.1 → 0.9.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 (135) hide show
  1. package/.storybook/preview.tsx +1 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/.turbo/turbo-lint.log +1 -1
  4. package/CHANGELOG.md +24 -0
  5. package/build/components/Banner/Banner.js +49 -10
  6. package/build/components/Banner/Banner.props.d.ts +4 -9
  7. package/build/components/BottomSheet/BottomSheetHandle.js +8 -0
  8. package/build/components/Card/Card.props.d.ts +1 -0
  9. package/build/components/Card/CardRoot.d.ts +1 -1
  10. package/build/components/Card/CardRoot.js +28 -1
  11. package/build/components/HighlightBanner/HighlightBanner.props.d.ts +1 -1
  12. package/build/components/List/List.js +1 -1
  13. package/build/components/List/ListAction/ListActionTrailingIcon.js +2 -2
  14. package/build/components/Menu/Menu.context.d.ts +5 -0
  15. package/build/components/Menu/Menu.context.js +9 -0
  16. package/build/components/Menu/Menu.d.ts +4 -0
  17. package/build/components/Menu/Menu.js +25 -0
  18. package/build/components/Menu/Menu.props.d.ts +21 -0
  19. package/build/components/Menu/Menu.props.js +1 -0
  20. package/build/components/Menu/MenuItem.d.ts +18 -0
  21. package/build/components/Menu/MenuItem.js +115 -0
  22. package/build/components/Menu/MenuItem.props.d.ts +27 -0
  23. package/build/components/Menu/MenuItem.props.js +1 -0
  24. package/build/components/Menu/MenuTrigger.d.ts +9 -0
  25. package/build/components/Menu/MenuTrigger.js +11 -0
  26. package/build/components/Menu/MenuTrigger.props.d.ts +12 -0
  27. package/build/components/Menu/MenuTrigger.props.js +1 -0
  28. package/build/components/Menu/index.d.ts +7 -0
  29. package/build/components/Menu/index.js +4 -0
  30. package/build/components/Modal/Modal.d.ts +1 -1
  31. package/build/components/Modal/Modal.js +32 -30
  32. package/build/components/Modal/Modal.props.d.ts +1 -0
  33. package/build/components/Modal/Modal.web.d.ts +1 -1
  34. package/build/components/Modal/Modal.web.js +25 -25
  35. package/build/components/RadioCard/RadioCardGroup.context.d.ts +12 -0
  36. package/build/components/RadioCard/RadioCardGroup.context.js +3 -0
  37. package/build/components/RadioCard/RadioCardGroup.js +15 -10
  38. package/build/components/RadioCard/RadioCardLabel.d.ts +1 -1
  39. package/build/components/RadioCard/RadioCardLabel.js +7 -1
  40. package/build/components/RadioCard/RadioCardRoot.js +13 -0
  41. package/build/components/index.d.ts +1 -0
  42. package/build/components/index.js +1 -0
  43. package/build/core/themes.d.ts +40 -0
  44. package/build/core/themes.js +20 -0
  45. package/build/tokens/components/dark/index.d.ts +3 -1
  46. package/build/tokens/components/dark/index.js +3 -1
  47. package/build/tokens/components/dark/input.d.ts +3 -0
  48. package/build/tokens/components/dark/input.js +3 -0
  49. package/build/tokens/components/dark/modal.d.ts +7 -4
  50. package/build/tokens/components/dark/modal.js +7 -4
  51. package/build/tokens/components/dark/rating.d.ts +8 -0
  52. package/build/tokens/components/dark/rating.js +7 -0
  53. package/build/tokens/components/dark/table.d.ts +0 -3
  54. package/build/tokens/components/dark/table.js +0 -3
  55. package/build/tokens/components/dark/time-picker.d.ts +29 -0
  56. package/build/tokens/components/dark/time-picker.js +28 -0
  57. package/build/tokens/components/dark/timeline.d.ts +27 -0
  58. package/build/tokens/components/dark/timeline.js +26 -0
  59. package/build/tokens/components/light/index.d.ts +3 -1
  60. package/build/tokens/components/light/index.js +3 -1
  61. package/build/tokens/components/light/input.d.ts +3 -0
  62. package/build/tokens/components/light/input.js +3 -0
  63. package/build/tokens/components/light/modal.d.ts +7 -4
  64. package/build/tokens/components/light/modal.js +7 -4
  65. package/build/tokens/components/light/rating.d.ts +8 -0
  66. package/build/tokens/components/light/rating.js +7 -0
  67. package/build/tokens/components/light/table.d.ts +0 -3
  68. package/build/tokens/components/light/table.js +0 -3
  69. package/build/tokens/components/light/time-picker.d.ts +29 -0
  70. package/build/tokens/components/light/time-picker.js +28 -0
  71. package/build/tokens/components/light/timeline.d.ts +27 -0
  72. package/build/tokens/components/light/timeline.js +26 -0
  73. package/docs/adding-shadows.mdx +43 -0
  74. package/docs/components/AllComponents.web.tsx +33 -0
  75. package/docs/components/BackToTopButton.tsx +1 -1
  76. package/package.json +3 -3
  77. package/src/components/Banner/Banner.docs.mdx +20 -11
  78. package/src/components/Banner/Banner.props.ts +4 -9
  79. package/src/components/Banner/Banner.stories.tsx +17 -4
  80. package/src/components/Banner/Banner.tsx +92 -37
  81. package/src/components/BottomSheet/BottomSheetHandle.tsx +12 -0
  82. package/src/components/Card/Card.docs.mdx +20 -1
  83. package/src/components/Card/Card.props.ts +9 -0
  84. package/src/components/Card/Card.stories.tsx +39 -0
  85. package/src/components/Card/CardRoot.tsx +29 -0
  86. package/src/components/Checkbox/CheckboxGroup.stories.tsx +19 -1
  87. package/src/components/DatePickerInput/DatePickerInput.docs.mdx +1 -1
  88. package/src/components/HighlightBanner/HighlightBanner.docs.mdx +1 -1
  89. package/src/components/HighlightBanner/HighlightBanner.props.ts +1 -0
  90. package/src/components/HighlightBanner/HighlightBanner.stories.tsx +15 -1
  91. package/src/components/List/List.tsx +5 -3
  92. package/src/components/List/ListAction/ListActionTrailingIcon.tsx +2 -2
  93. package/src/components/Menu/Menu.context.ts +15 -0
  94. package/src/components/Menu/Menu.docs.mdx +158 -0
  95. package/src/components/Menu/Menu.props.ts +24 -0
  96. package/src/components/Menu/Menu.stories.tsx +292 -0
  97. package/src/components/Menu/Menu.tsx +54 -0
  98. package/src/components/Menu/MenuItem.props.ts +29 -0
  99. package/src/components/Menu/MenuItem.tsx +145 -0
  100. package/src/components/Menu/MenuTrigger.props.ts +14 -0
  101. package/src/components/Menu/MenuTrigger.tsx +20 -0
  102. package/src/components/Menu/index.ts +7 -0
  103. package/src/components/Modal/Modal.docs.mdx +34 -5
  104. package/src/components/Modal/Modal.props.ts +1 -0
  105. package/src/components/Modal/Modal.stories.tsx +46 -0
  106. package/src/components/Modal/Modal.tsx +37 -33
  107. package/src/components/Modal/Modal.web.tsx +27 -27
  108. package/src/components/Radio/RadioGroup.stories.tsx +18 -0
  109. package/src/components/RadioCard/RadioCardGroup.context.ts +16 -0
  110. package/src/components/RadioCard/RadioCardGroup.stories.tsx +24 -0
  111. package/src/components/RadioCard/RadioCardGroup.tsx +28 -19
  112. package/src/components/RadioCard/RadioCardLabel.tsx +12 -1
  113. package/src/components/RadioCard/RadioCardRoot.tsx +15 -0
  114. package/src/components/index.ts +1 -0
  115. package/src/core/themes.ts +20 -0
  116. package/src/tokens/components/dark/index.ts +3 -1
  117. package/src/tokens/components/dark/input.ts +3 -0
  118. package/src/tokens/components/dark/modal.ts +7 -4
  119. package/src/tokens/components/dark/rating.ts +8 -0
  120. package/src/tokens/components/dark/table.ts +0 -3
  121. package/src/tokens/components/dark/time-picker.ts +29 -0
  122. package/src/tokens/components/dark/timeline.ts +27 -0
  123. package/src/tokens/components/light/index.ts +3 -1
  124. package/src/tokens/components/light/input.ts +3 -0
  125. package/src/tokens/components/light/modal.ts +7 -4
  126. package/src/tokens/components/light/rating.ts +8 -0
  127. package/src/tokens/components/light/table.ts +0 -3
  128. package/src/tokens/components/light/time-picker.ts +29 -0
  129. package/src/tokens/components/light/timeline.ts +27 -0
  130. package/build/tokens/components/dark/dialog.d.ts +0 -25
  131. package/build/tokens/components/dark/dialog.js +0 -24
  132. package/build/tokens/components/light/dialog.d.ts +0 -25
  133. package/build/tokens/components/light/dialog.js +0 -24
  134. package/src/tokens/components/dark/dialog.ts +0 -25
  135. 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
- - [Fullscreen Modal (Usage with navigation modal)](#fullscreen-modal-usage-with-navigation-modal)
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 (useful for navigation modals) | `false` |
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
- ### Fullscreen Modal (Usage with navigation modal)
446
+ ### Modal In Navigation Modal
418
447
 
419
- When using the Modal component in a navigation context, you can set it to fullscreen mode, this will make it behave like a standard modal screen.
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
- fullscreen
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
- fullscreen = false,
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' && fullscreen) {
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, fullscreen, pretendContentTranslateY, backgroundOpacity]);
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 fullscreen Android modal
73
+ // Trigger animations on render for inNavModal Android modal
73
74
  useEffect(() => {
74
- if (Platform.OS === 'android' && fullscreen) {
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
- }, [fullscreen, backgroundOpacity, pretendContentTranslateY]);
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 animatedFullscreenStyle = useAnimatedStyle(() => ({
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 fullscreen ? (
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.fullscreenContainer, Platform.OS === 'android' && animatedFullscreenStyle]}
252
+ style={[styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle]}
252
253
  >
253
- <View style={styles.fullscreenContent}>{content}</View>
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.dialog.gap,
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.dialog.gap,
293
+ gap: theme.components.modal.gap,
289
294
  },
290
295
  headerTextContent: {
291
296
  flex: 1,
292
- gap: theme.components.dialog.content.gap,
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.dialog.content.gap,
308
+ gap: theme.components.modal.content.gap,
305
309
  },
306
310
  loadingTop: {
307
- borderTopLeftRadius: theme.components.dialog.borderRadius,
308
- borderTopRightRadius: theme.components.dialog.borderRadius,
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.dialog.borderRadius,
314
- borderTopRightRadius: theme.components.dialog.borderRadius,
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.dialog.content.gap,
323
+ gap: theme.components.modal.content.gap,
320
324
  },
321
325
  footer: {
322
- gap: theme.components.dialog.action.gap,
326
+ gap: theme.components.modal.action.gap,
323
327
  },
324
- fullscreenContainer: {
328
+ inNavModalContainer: {
325
329
  flex: 1,
326
330
  ...(Platform.OS === 'ios' ? { backgroundColor: theme.components.overlay.backgroundColor } : {}),
327
331
  },
328
- fullscreenContent: {
332
+ inNavModalContent: {
329
333
  flex: 1,
330
- borderTopLeftRadius: theme.components.dialog.borderRadius,
331
- borderTopRightRadius: theme.components.dialog.borderRadius,
334
+ borderTopLeftRadius: theme.components.modal.borderRadius,
335
+ borderTopRightRadius: theme.components.modal.borderRadius,
332
336
  backgroundColor: theme.color.surface.neutral.strong,
333
- gap: theme.components.dialog.gap,
334
- padding: theme.components.dialog.padding,
335
- paddingBottom: theme.components.dialog.padding + rt.insets.bottom,
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.dialog.padding,
340
- paddingRight: theme.components.dialog.padding,
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.dialog.borderRadius,
345
- borderTopRightRadius: theme.components.dialog.borderRadius,
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
- fullscreen = false,
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' && fullscreen) {
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, fullscreen, pretendContentTranslateY, backgroundOpacity]);
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 fullscreen Android modal
72
+ // Trigger animations on render for inNavModal Android modal
73
73
  useEffect(() => {
74
- if (Platform.OS === 'android' && fullscreen) {
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
- }, [fullscreen, backgroundOpacity, pretendContentTranslateY]);
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 animatedFullscreenStyle = useAnimatedStyle(() => ({
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 fullscreen ? (
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.fullscreenContainer, Platform.OS === 'android' && animatedFullscreenStyle]}
249
+ style={[styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle]}
250
250
  >
251
- <View style={styles.fullscreenContent}>{content}</View>
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.dialog.gap,
274
+ gap: theme.components.modal.gap,
275
275
  },
276
276
  header: {
277
277
  flexDirection: 'row',
278
- gap: theme.components.dialog.gap,
278
+ gap: theme.components.modal.gap,
279
279
  },
280
280
  headerTextContent: {
281
281
  flex: 1,
282
- gap: theme.components.dialog.content.gap,
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.dialog.content.gap,
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.dialog.content.gap,
301
+ gap: theme.components.modal.content.gap,
302
302
  },
303
303
  footer: {
304
- gap: theme.components.dialog.action.gap,
304
+ gap: theme.components.modal.action.gap,
305
305
  },
306
- fullscreenContainer: {
306
+ inNavModalContainer: {
307
307
  flex: 1,
308
308
  ...(Platform.OS === 'ios' ? { backgroundColor: theme.components.overlay.backgroundColor } : {}),
309
309
  },
310
- fullscreenContent: {
310
+ inNavModalContent: {
311
311
  flex: 1,
312
- borderTopLeftRadius: theme.components.dialog.borderRadius,
313
- borderTopRightRadius: theme.components.dialog.borderRadius,
312
+ borderTopLeftRadius: theme.components.modal.borderRadius,
313
+ borderTopRightRadius: theme.components.modal.borderRadius,
314
314
  backgroundColor: theme.color.surface.neutral.strong,
315
- gap: theme.components.dialog.gap,
316
- padding: theme.components.dialog.padding,
317
- paddingBottom: theme.components.dialog.padding + rt.insets.bottom,
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.dialog.padding,
322
- paddingRight: theme.components.dialog.padding,
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.dialog.borderRadius,
327
- borderTopRightRadius: theme.components.dialog.borderRadius,
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
  },
@@ -105,3 +105,21 @@ export const WithGrid: Story = {
105
105
  </RadioGroup>
106
106
  ),
107
107
  };
108
+
109
+ export const LongContent: Story = {
110
+ args: {
111
+ type: 'tile',
112
+ },
113
+ render: args => (
114
+ <RadioGroup {...args}>
115
+ <Radio aria-label="Label 1" label="Option 1" value="Option 1" nativeID="Radio-1" />
116
+ <Radio
117
+ aria-label="Label 2"
118
+ label="Option 2 with a very long content that spans multiple lines to test text wrapping"
119
+ value="Option 2"
120
+ nativeID="Radio-2"
121
+ />
122
+ <Radio aria-label="Label 3" label="Option 3" value="Option 3" nativeID="Radio-3" />
123
+ </RadioGroup>
124
+ ),
125
+ };
@@ -0,0 +1,16 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ export const RadioCardGroupContext = createContext<{
4
+ flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
5
+ flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse';
6
+ justifyContent?:
7
+ | 'flex-start'
8
+ | 'flex-end'
9
+ | 'center'
10
+ | 'space-between'
11
+ | 'space-around'
12
+ | 'space-evenly';
13
+ alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
14
+ }>({});
15
+
16
+ export const useRadioCardGroupContext = () => useContext(RadioCardGroupContext);
@@ -68,3 +68,27 @@ export const Playground: Story = {
68
68
  </RadioCardGroup>
69
69
  ),
70
70
  };
71
+
72
+ export const LongContent: Story = {
73
+ args: {
74
+ flexDirection: 'column',
75
+ },
76
+ render: args => (
77
+ <RadioCardGroup {...args}>
78
+ <RadioCard aria-label="Label 1" label="Option 1" value="Option 1" nativeID="RadioCard-1">
79
+ <BodyText>Additional content</BodyText>
80
+ </RadioCard>
81
+ <RadioCard
82
+ aria-label="Label 2"
83
+ label="Option 2 with a very long content that should wrap into multiple lines to test the layout of the RadioCard component in such scenarios."
84
+ value="Option 2"
85
+ nativeID="RadioCard-2"
86
+ >
87
+ <BodyText>Additional content</BodyText>
88
+ </RadioCard>
89
+ <RadioCard aria-label="Label 3" label="Option 3" value="Option 3" nativeID="RadioCard-3">
90
+ <BodyText>Additional content</BodyText>
91
+ </RadioCard>
92
+ </RadioCardGroup>
93
+ ),
94
+ };
@@ -1,7 +1,9 @@
1
- import RadioCardGroupProps from './RadioCardGroup.props';
1
+ import { useMemo } from 'react';
2
2
  import { View } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
  import { Grid } from '../Grid';
5
+ import { RadioCardGroupContext } from './RadioCardGroup.context';
6
+ import RadioCardGroupProps from './RadioCardGroup.props';
5
7
 
6
8
  const RadioCardGroup = ({
7
9
  children,
@@ -14,26 +16,33 @@ const RadioCardGroup = ({
14
16
  columns,
15
17
  ...props
16
18
  }: RadioCardGroupProps) => {
19
+ const context = useMemo(() => {
20
+ return { flexDirection, flexWrap, justifyContent, alignItems };
21
+ }, [flexDirection, flexWrap, justifyContent, alignItems]);
17
22
  return columns ? (
18
- <Grid {...props} gap={gap} columns={columns} style={style}>
19
- {children as any}
20
- </Grid>
23
+ <RadioCardGroupContext.Provider value={context}>
24
+ <Grid {...props} gap={gap} columns={columns} style={style}>
25
+ {children as any}
26
+ </Grid>
27
+ </RadioCardGroupContext.Provider>
21
28
  ) : (
22
- <View
23
- {...props}
24
- style={[
25
- styles.containerGap(gap),
26
- {
27
- flexDirection,
28
- flexWrap,
29
- justifyContent,
30
- alignItems,
31
- },
32
- style,
33
- ]}
34
- >
35
- {children}
36
- </View>
29
+ <RadioCardGroupContext.Provider value={context}>
30
+ <View
31
+ {...props}
32
+ style={[
33
+ styles.containerGap(gap),
34
+ {
35
+ flexDirection,
36
+ flexWrap,
37
+ justifyContent,
38
+ alignItems,
39
+ },
40
+ style,
41
+ ]}
42
+ >
43
+ {children}
44
+ </View>
45
+ </RadioCardGroupContext.Provider>
37
46
  );
38
47
  };
39
48