@utilitywarehouse/hearth-react-native 0.8.2 → 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 (102) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/CHANGELOG.md +8 -0
  4. package/build/components/Banner/Banner.js +25 -6
  5. package/build/components/Banner/Banner.props.d.ts +2 -2
  6. package/build/components/BottomSheet/BottomSheetHandle.js +8 -0
  7. package/build/components/Menu/Menu.context.d.ts +5 -0
  8. package/build/components/Menu/Menu.context.js +9 -0
  9. package/build/components/Menu/Menu.d.ts +4 -0
  10. package/build/components/Menu/Menu.js +25 -0
  11. package/build/components/Menu/Menu.props.d.ts +21 -0
  12. package/build/components/Menu/Menu.props.js +1 -0
  13. package/build/components/Menu/MenuItem.d.ts +18 -0
  14. package/build/components/Menu/MenuItem.js +115 -0
  15. package/build/components/Menu/MenuItem.props.d.ts +27 -0
  16. package/build/components/Menu/MenuItem.props.js +1 -0
  17. package/build/components/Menu/MenuTrigger.d.ts +9 -0
  18. package/build/components/Menu/MenuTrigger.js +11 -0
  19. package/build/components/Menu/MenuTrigger.props.d.ts +12 -0
  20. package/build/components/Menu/MenuTrigger.props.js +1 -0
  21. package/build/components/Menu/index.d.ts +7 -0
  22. package/build/components/Menu/index.js +4 -0
  23. package/build/components/Modal/Modal.d.ts +1 -1
  24. package/build/components/Modal/Modal.js +32 -30
  25. package/build/components/Modal/Modal.props.d.ts +1 -0
  26. package/build/components/Modal/Modal.web.d.ts +1 -1
  27. package/build/components/Modal/Modal.web.js +25 -25
  28. package/build/components/index.d.ts +1 -0
  29. package/build/components/index.js +1 -0
  30. package/build/tokens/components/dark/index.d.ts +3 -1
  31. package/build/tokens/components/dark/index.js +3 -1
  32. package/build/tokens/components/dark/input.d.ts +3 -0
  33. package/build/tokens/components/dark/input.js +3 -0
  34. package/build/tokens/components/dark/modal.d.ts +7 -4
  35. package/build/tokens/components/dark/modal.js +7 -4
  36. package/build/tokens/components/dark/rating.d.ts +8 -0
  37. package/build/tokens/components/dark/rating.js +7 -0
  38. package/build/tokens/components/dark/table.d.ts +0 -3
  39. package/build/tokens/components/dark/table.js +0 -3
  40. package/build/tokens/components/dark/time-picker.d.ts +29 -0
  41. package/build/tokens/components/dark/time-picker.js +28 -0
  42. package/build/tokens/components/dark/timeline.d.ts +27 -0
  43. package/build/tokens/components/dark/timeline.js +26 -0
  44. package/build/tokens/components/light/index.d.ts +3 -1
  45. package/build/tokens/components/light/index.js +3 -1
  46. package/build/tokens/components/light/input.d.ts +3 -0
  47. package/build/tokens/components/light/input.js +3 -0
  48. package/build/tokens/components/light/modal.d.ts +7 -4
  49. package/build/tokens/components/light/modal.js +7 -4
  50. package/build/tokens/components/light/rating.d.ts +8 -0
  51. package/build/tokens/components/light/rating.js +7 -0
  52. package/build/tokens/components/light/table.d.ts +0 -3
  53. package/build/tokens/components/light/table.js +0 -3
  54. package/build/tokens/components/light/time-picker.d.ts +29 -0
  55. package/build/tokens/components/light/time-picker.js +28 -0
  56. package/build/tokens/components/light/timeline.d.ts +27 -0
  57. package/build/tokens/components/light/timeline.js +26 -0
  58. package/docs/components/AllComponents.web.tsx +33 -0
  59. package/docs/components/BackToTopButton.tsx +1 -1
  60. package/package.json +2 -2
  61. package/src/components/Banner/Banner.docs.mdx +19 -10
  62. package/src/components/Banner/Banner.props.ts +2 -2
  63. package/src/components/Banner/Banner.stories.tsx +1 -4
  64. package/src/components/Banner/Banner.tsx +47 -7
  65. package/src/components/BottomSheet/BottomSheetHandle.tsx +12 -0
  66. package/src/components/DatePickerInput/DatePickerInput.docs.mdx +1 -1
  67. package/src/components/Menu/Menu.context.ts +15 -0
  68. package/src/components/Menu/Menu.docs.mdx +158 -0
  69. package/src/components/Menu/Menu.props.ts +24 -0
  70. package/src/components/Menu/Menu.stories.tsx +292 -0
  71. package/src/components/Menu/Menu.tsx +54 -0
  72. package/src/components/Menu/MenuItem.props.ts +29 -0
  73. package/src/components/Menu/MenuItem.tsx +145 -0
  74. package/src/components/Menu/MenuTrigger.props.ts +14 -0
  75. package/src/components/Menu/MenuTrigger.tsx +20 -0
  76. package/src/components/Menu/index.ts +7 -0
  77. package/src/components/Modal/Modal.docs.mdx +34 -5
  78. package/src/components/Modal/Modal.props.ts +1 -0
  79. package/src/components/Modal/Modal.stories.tsx +46 -0
  80. package/src/components/Modal/Modal.tsx +37 -33
  81. package/src/components/Modal/Modal.web.tsx +27 -27
  82. package/src/components/index.ts +1 -0
  83. package/src/tokens/components/dark/index.ts +3 -1
  84. package/src/tokens/components/dark/input.ts +3 -0
  85. package/src/tokens/components/dark/modal.ts +7 -4
  86. package/src/tokens/components/dark/rating.ts +8 -0
  87. package/src/tokens/components/dark/table.ts +0 -3
  88. package/src/tokens/components/dark/time-picker.ts +29 -0
  89. package/src/tokens/components/dark/timeline.ts +27 -0
  90. package/src/tokens/components/light/index.ts +3 -1
  91. package/src/tokens/components/light/input.ts +3 -0
  92. package/src/tokens/components/light/modal.ts +7 -4
  93. package/src/tokens/components/light/rating.ts +8 -0
  94. package/src/tokens/components/light/table.ts +0 -3
  95. package/src/tokens/components/light/time-picker.ts +29 -0
  96. package/src/tokens/components/light/timeline.ts +27 -0
  97. package/build/tokens/components/dark/dialog.d.ts +0 -25
  98. package/build/tokens/components/dark/dialog.js +0 -24
  99. package/build/tokens/components/light/dialog.d.ts +0 -25
  100. package/build/tokens/components/light/dialog.js +0 -24
  101. package/src/tokens/components/dark/dialog.ts +0 -25
  102. package/src/tokens/components/light/dialog.ts +0 -25
@@ -1,4 +1,4 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.8.2 build /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.9.0 build /home/runner/work/hearth/hearth/packages/react-native
3
3
  > tsc
4
4
 
@@ -1,5 +1,5 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.8.2 lint /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.9.0 lint /home/runner/work/hearth/hearth/packages/react-native
3
3
  > TIMING=1 eslint --max-warnings 0
4
4
 
5
5
  Rule | Time (ms) | Relative
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#658](https://github.com/utilitywarehouse/hearth/pull/658) [`2ac2366`](https://github.com/utilitywarehouse/hearth/commit/2ac2366aaa7f3b253fd9336664a73a64f1ab91a5) Thanks [@jordmccord](https://github.com/jordmccord)! - Adds `Menu` component
8
+
9
+ - [#656](https://github.com/utilitywarehouse/hearth/pull/656) [`a01a49a`](https://github.com/utilitywarehouse/hearth/commit/a01a49a49ec1dd3c04684ce010281030d45c77a1) Thanks [@jordmccord](https://github.com/jordmccord)! - Adds `fullscreen` and `inNavModal` props to `Modal`
10
+
3
11
  ## 0.8.2
4
12
 
5
13
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ChevronRightSmallIcon, CloseSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
3
- import { Pressable, View } from 'react-native';
3
+ import { Image, Pressable, View } from 'react-native';
4
4
  import { StyleSheet } from 'react-native-unistyles';
5
5
  import { BodyText } from '../BodyText';
6
6
  import { Card } from '../Card';
@@ -8,6 +8,9 @@ import { Heading } from '../Heading';
8
8
  import { IconContainer } from '../IconContainer';
9
9
  import { ThemedImage } from '../ThemedImage';
10
10
  import { UnstyledIconButton } from '../UnstyledIconButton';
11
+ const isThemedImageProps = (props) => {
12
+ return 'light' in props && 'dark' in props;
13
+ };
11
14
  const Banner = ({ icon, iconContainerVariant = 'subtle', iconContainerSize = 'md', iconContainerColor = 'pig', illustration, image, heading, description, direction = 'horizontal', link, button, onPress, onClose, variant = 'subtle', colorScheme = 'pig', style, ...props }) => {
12
15
  const hasIllustration = Boolean(illustration);
13
16
  styles.useVariants({ direction, hasIllustration });
@@ -16,10 +19,16 @@ const Banner = ({ icon, iconContainerVariant = 'subtle', iconContainerSize = 'md
16
19
  return (_jsx(IconContainer, { icon: icon, variant: iconContainerVariant, size: iconContainerSize, color: iconContainerColor, style: styles.media }));
17
20
  }
18
21
  if (illustration) {
19
- return (_jsx(ThemedImage, { ...illustration, resizeMode: "cover", style: [styles.media, styles.imageWrapper, illustration.style] }));
22
+ if (isThemedImageProps(illustration)) {
23
+ return (_jsx(ThemedImage, { ...illustration, resizeMode: "cover", style: [styles.media, styles.imageWrapper, illustration.style] }));
24
+ }
25
+ return (_jsx(Image, { ...illustration, resizeMode: "cover", style: [styles.media, styles.imageWrapper, illustration.style] }));
20
26
  }
21
27
  if (image) {
22
- return (_jsx(View, { style: [styles.media, styles.imageWrapper], children: _jsx(ThemedImage, { ...image, style: [styles.image, image.style] }) }));
28
+ if (isThemedImageProps(image)) {
29
+ return (_jsx(View, { style: [styles.media, styles.imageWrapper], children: _jsx(ThemedImage, { ...image, style: [styles.image, image.style] }) }));
30
+ }
31
+ return (_jsx(View, { style: [styles.media, styles.imageWrapper], children: _jsx(Image, { ...image, style: [styles.image, image.style] }) }));
23
32
  }
24
33
  return null;
25
34
  };
@@ -32,7 +41,7 @@ const Banner = ({ icon, iconContainerVariant = 'subtle', iconContainerSize = 'md
32
41
  }
33
42
  return null;
34
43
  };
35
- const content = (_jsxs(View, { style: styles.container, children: [renderIconOrImage(), _jsxs(View, { style: styles.contentContainer, children: [_jsxs(View, { style: styles.contentTextContainer, children: [_jsxs(View, { style: styles.textContainer, children: [_jsx(Heading, { size: "sm", style: styles.heading, textAlign: hasIllustration && direction === 'vertical' ? 'center' : 'left', children: heading }), _jsx(BodyText, { size: "md", style: styles.description, textAlign: hasIllustration && direction === 'vertical' ? 'center' : 'left', children: description })] }), renderAction()] }), onPress && (_jsx(UnstyledIconButton, { icon: ChevronRightSmallIcon, size: "sm", onPress: onPress, style: styles.chevron })), onClose && (_jsx(UnstyledIconButton, { icon: CloseSmallIcon, size: "sm", onPress: onClose, style: styles.closeButton, accessibilityLabel: "Close banner" }))] })] }));
44
+ const content = (_jsxs(View, { style: styles.container, children: [onClose && direction === 'vertical' && (_jsx(UnstyledIconButton, { icon: CloseSmallIcon, size: "sm", onPress: onClose, style: styles.closeButton, accessibilityLabel: "Close banner" })), renderIconOrImage(), _jsxs(View, { style: styles.contentContainer, children: [_jsxs(View, { style: styles.contentTextContainer, children: [_jsxs(View, { style: styles.textContainer, children: [_jsx(Heading, { size: "sm", style: styles.heading, textAlign: hasIllustration && direction === 'vertical' ? 'center' : 'left', children: heading }), _jsx(BodyText, { size: "md", style: styles.description, textAlign: hasIllustration && direction === 'vertical' ? 'center' : 'left', children: description })] }), renderAction()] }), onPress && (_jsx(UnstyledIconButton, { icon: ChevronRightSmallIcon, size: "sm", onPress: onPress, style: styles.chevron })), onClose && direction === 'horizontal' && (_jsx(UnstyledIconButton, { icon: CloseSmallIcon, size: "sm", onPress: onClose, style: styles.closeButton, accessibilityLabel: "Close banner" }))] })] }));
36
45
  if (onPress) {
37
46
  return (_jsx(Card, { variant: variant, style: [styles.card, style], ...props, children: _jsx(Pressable, { onPress: onPress, accessibilityRole: "button", style: styles.pressable, children: content }) }));
38
47
  }
@@ -40,9 +49,9 @@ const Banner = ({ icon, iconContainerVariant = 'subtle', iconContainerSize = 'md
40
49
  };
41
50
  Banner.displayName = 'Banner';
42
51
  const styles = StyleSheet.create(theme => ({
43
- card: {},
52
+ card: { flexDirection: 'row', _web: { flexDirection: 'column' } },
44
53
  pressable: {
45
- width: '100%',
54
+ flex: 1,
46
55
  },
47
56
  container: {
48
57
  flexDirection: 'row',
@@ -176,6 +185,16 @@ const styles = StyleSheet.create(theme => ({
176
185
  },
177
186
  closeButton: {
178
187
  alignSelf: 'flex-start',
188
+ variants: {
189
+ direction: {
190
+ vertical: {
191
+ position: 'absolute',
192
+ top: 0,
193
+ right: 0,
194
+ },
195
+ horizontal: {},
196
+ },
197
+ },
179
198
  },
180
199
  }));
181
200
  export default Banner;
@@ -28,12 +28,12 @@ export interface BannerProps extends Omit<CardProps, 'noPadding' | 'variant' | '
28
28
  * Illustration to display in the banner
29
29
  * Mutually exclusive with icon and image
30
30
  */
31
- illustration?: ThemedImageProps & ImageProps;
31
+ illustration?: ThemedImageProps | ImageProps;
32
32
  /**
33
33
  * Image to display in the banner
34
34
  * Mutually exclusive with icon and illustration
35
35
  */
36
- image?: ThemedImageProps & ImageProps;
36
+ image?: ThemedImageProps | ImageProps;
37
37
  /**
38
38
  * Heading text
39
39
  */
@@ -1,8 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { BottomSheetHandle as Handle } from '@gorhom/bottom-sheet';
3
+ import { Platform, View } from 'react-native';
3
4
  import { StyleSheet, withUnistyles } from 'react-native-unistyles';
4
5
  const StyledBottomSheetHandle = withUnistyles(Handle);
5
6
  const BottomSheetHandle = ({ style, indicatorStyle, ...props }) => {
7
+ if (Platform.OS === 'web') {
8
+ return (_jsx(View, { style: [styles.handle, style], children: _jsx(View, { style: [styles.indicator, indicatorStyle] }) }));
9
+ }
6
10
  return (_jsx(StyledBottomSheetHandle, { style: [styles.handle, style], indicatorStyle: [styles.indicator, indicatorStyle], ...props }));
7
11
  };
8
12
  const styles = StyleSheet.create(theme => ({
@@ -13,6 +17,10 @@ const styles = StyleSheet.create(theme => ({
13
17
  paddingTop: theme.components.bottomSheet.padding,
14
18
  paddingHorizontal: theme.components.bottomSheet.padding,
15
19
  paddingBottom: theme.components.bottomSheet.gap,
20
+ _web: {
21
+ alignItems: 'center',
22
+ cursor: 'grab',
23
+ },
16
24
  },
17
25
  indicator: {
18
26
  width: theme.components.bottomSheet.handle.width,
@@ -0,0 +1,5 @@
1
+ export interface IMenuContext {
2
+ close: () => void;
3
+ }
4
+ export declare const MenuContext: import("react").Context<IMenuContext | undefined>;
5
+ export declare const useMenuContext: () => IMenuContext;
@@ -0,0 +1,9 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const MenuContext = createContext(undefined);
3
+ export const useMenuContext = () => {
4
+ const context = useContext(MenuContext);
5
+ if (!context) {
6
+ throw new Error('useMenuContext must be used within a Menu component');
7
+ }
8
+ return context;
9
+ };
@@ -0,0 +1,4 @@
1
+ import type MenuProps from './Menu.props';
2
+ import type { MenuMethods } from './Menu.props';
3
+ declare const Menu: import("react").ForwardRefExoticComponent<MenuProps & import("react").RefAttributes<MenuMethods>>;
4
+ export default Menu;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { BodyText } from '../BodyText';
5
+ import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
6
+ import { MenuContext } from './Menu.context';
7
+ const Menu = forwardRef(({ heading, children, bottomSheetProps }, ref) => {
8
+ const bottomSheetModalRef = useRef(null);
9
+ useImperativeHandle(ref, () => ({
10
+ present: () => bottomSheetModalRef.current?.present(),
11
+ dismiss: () => bottomSheetModalRef.current?.dismiss(),
12
+ }), []);
13
+ const handleClose = useCallback(() => {
14
+ bottomSheetModalRef.current?.dismiss();
15
+ }, []);
16
+ const contextValue = useMemo(() => ({ close: handleClose }), [handleClose]);
17
+ return (_jsx(BottomSheetModal, { ref: bottomSheetModalRef, ...bottomSheetProps, children: _jsx(BottomSheetScrollView, { contentContainerStyle: styles.container, children: _jsxs(MenuContext.Provider, { value: contextValue, children: [heading && (_jsx(BodyText, { size: "md", weight: "semibold", children: heading })), children] }) }) }));
18
+ });
19
+ Menu.displayName = 'Menu';
20
+ const styles = StyleSheet.create(theme => ({
21
+ container: {
22
+ gap: theme.components.bottomSheet.gap,
23
+ },
24
+ }));
25
+ export default Menu;
@@ -0,0 +1,21 @@
1
+ import type { BottomSheetModalProps } from '@gorhom/bottom-sheet';
2
+ import type { ReactNode } from 'react';
3
+ export interface MenuMethods {
4
+ present: () => void;
5
+ dismiss: () => void;
6
+ }
7
+ export interface MenuProps {
8
+ /**
9
+ * Heading text displayed at the top of the menu
10
+ */
11
+ heading?: string;
12
+ /**
13
+ * Menu items to display
14
+ */
15
+ children: ReactNode;
16
+ /**
17
+ * Optional bottom sheet modal props to customise the menu behavior
18
+ */
19
+ bottomSheetProps?: Partial<BottomSheetModalProps>;
20
+ }
21
+ export default MenuProps;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import type MenuItemProps from './MenuItem.props';
2
+ declare const MenuItem: import("react").ForwardRefExoticComponent<MenuItemProps & {
3
+ states?: {
4
+ active?: boolean;
5
+ disabled?: boolean;
6
+ };
7
+ } & Omit<import("react-native").PressableProps, "children"> & {
8
+ tabIndex?: 0 | -1 | undefined;
9
+ } & {
10
+ children?: import("react").ReactNode | (({ hovered, pressed, focused, focusVisible, disabled, }: {
11
+ hovered?: boolean | undefined;
12
+ pressed?: boolean | undefined;
13
+ focused?: boolean | undefined;
14
+ focusVisible?: boolean | undefined;
15
+ disabled?: boolean | undefined;
16
+ }) => import("react").ReactNode);
17
+ } & import("react").RefAttributes<unknown>>;
18
+ export default MenuItem;
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createPressable } from '@gluestack-ui/pressable';
3
+ import { Pressable } from 'react-native';
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { BodyText } from '../BodyText';
6
+ import { Icon } from '../Icon';
7
+ import { useMenuContext } from './Menu.context';
8
+ const MenuItemRoot = ({ icon, iconPosition = 'left', text, colorScheme = 'functional', disabled = false, onPress, states = {}, ...props }) => {
9
+ const { active } = states;
10
+ const { close } = useMenuContext();
11
+ styles.useVariants({ colorScheme, disabled, iconPosition, active });
12
+ const handlePress = (event) => {
13
+ if (disabled)
14
+ return;
15
+ onPress?.(event);
16
+ close();
17
+ };
18
+ return (_jsxs(Pressable, { ...props, onPress: handlePress, disabled: disabled, style: styles.container, accessibilityRole: "button", accessibilityState: { disabled }, children: [!!icon && _jsx(Icon, { as: icon, style: styles.icon }), _jsx(BodyText, { size: "lg", style: styles.text, children: text })] }));
19
+ };
20
+ const MenuItem = createPressable({ Root: MenuItemRoot });
21
+ MenuItem.displayName = 'MenuItem';
22
+ const styles = StyleSheet.create(theme => ({
23
+ container: {
24
+ flexDirection: 'row',
25
+ alignItems: 'center',
26
+ paddingVertical: theme.components.menu.item.padding,
27
+ paddingHorizontal: theme.components.menu.mobile.item.padding,
28
+ gap: theme.components.menu.item.gap,
29
+ borderRadius: theme.components.menu.item.borderRadius,
30
+ variants: {
31
+ active: {
32
+ true: {
33
+ backgroundColor: theme.color.interactive.functional.surface.subtle.active,
34
+ },
35
+ },
36
+ disabled: {
37
+ true: {
38
+ opacity: theme.opacity.disabled,
39
+ cursor: 'auto',
40
+ },
41
+ false: {
42
+ _web: {
43
+ _hover: {
44
+ backgroundColor: theme.color.interactive.functional.surface.subtle.hover,
45
+ },
46
+ _active: {
47
+ backgroundColor: theme.color.interactive.functional.surface.subtle.active,
48
+ },
49
+ },
50
+ },
51
+ },
52
+ iconPosition: {
53
+ left: {
54
+ flexDirection: 'row',
55
+ },
56
+ right: {
57
+ flexDirection: 'row-reverse',
58
+ },
59
+ },
60
+ colorScheme: {
61
+ functional: {},
62
+ destructive: {},
63
+ },
64
+ },
65
+ compoundVariants: [
66
+ {
67
+ colorScheme: 'destructive',
68
+ active: true,
69
+ styles: {
70
+ backgroundColor: theme.color.interactive.destructive.surface.subtle.active,
71
+ },
72
+ },
73
+ {
74
+ colorScheme: 'destructive',
75
+ disabled: false,
76
+ styles: {
77
+ _web: {
78
+ _hover: {
79
+ backgroundColor: theme.color.interactive.destructive.surface.subtle.hover,
80
+ },
81
+ _active: {
82
+ backgroundColor: theme.color.interactive.destructive.surface.subtle.active,
83
+ },
84
+ },
85
+ },
86
+ },
87
+ ],
88
+ },
89
+ text: {
90
+ flex: 1,
91
+ variants: {
92
+ colorScheme: {
93
+ functional: {
94
+ color: theme.color.text.primary,
95
+ },
96
+ destructive: {
97
+ color: theme.color.interactive.destructive.foreground.subtle,
98
+ },
99
+ },
100
+ },
101
+ },
102
+ icon: {
103
+ variants: {
104
+ colorScheme: {
105
+ functional: {
106
+ color: theme.color.icon.primary,
107
+ },
108
+ destructive: {
109
+ color: theme.color.interactive.destructive.foreground.subtle,
110
+ },
111
+ },
112
+ },
113
+ },
114
+ }));
115
+ export default MenuItem;
@@ -0,0 +1,27 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { PressableProps } from 'react-native';
3
+ export interface MenuItemProps extends Omit<PressableProps, 'children'> {
4
+ /**
5
+ * Icon component to display
6
+ */
7
+ icon?: ComponentType;
8
+ /**
9
+ * Position of the icon
10
+ * @default 'left'
11
+ */
12
+ iconPosition?: 'left' | 'right';
13
+ /**
14
+ * Text to display in the menu item
15
+ */
16
+ text: string;
17
+ /**
18
+ * Color scheme for the menu item
19
+ * @default 'functional'
20
+ */
21
+ colorScheme?: 'functional' | 'destructive';
22
+ /**
23
+ * Whether the menu item is disabled
24
+ */
25
+ disabled?: boolean;
26
+ }
27
+ export default MenuItemProps;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type MenuTriggerProps from './MenuTrigger.props';
2
+ interface MenuTriggerInternalProps extends MenuTriggerProps {
3
+ onPress: () => void;
4
+ }
5
+ declare const MenuTrigger: {
6
+ ({ children, onPress }: MenuTriggerInternalProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
7
+ displayName: string;
8
+ };
9
+ export default MenuTrigger;
@@ -0,0 +1,11 @@
1
+ import { cloneElement, isValidElement } from 'react';
2
+ const MenuTrigger = ({ children, onPress }) => {
3
+ if (!isValidElement(children)) {
4
+ throw new Error('MenuTrigger: children must be a valid React element');
5
+ }
6
+ return cloneElement(children, {
7
+ onPress,
8
+ });
9
+ };
10
+ MenuTrigger.displayName = 'MenuTrigger';
11
+ export default MenuTrigger;
@@ -0,0 +1,12 @@
1
+ import type { ReactElement } from 'react';
2
+ export interface MenuTriggerProps {
3
+ /**
4
+ * The child element that triggers the menu (should be a single pressable element like Button)
5
+ */
6
+ children: ReactElement;
7
+ /**
8
+ * Called when the trigger is pressed.
9
+ */
10
+ onPress?: () => void;
11
+ }
12
+ export default MenuTriggerProps;
@@ -0,0 +1 @@
1
+ export {};
@@ -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';
@@ -0,0 +1,4 @@
1
+ export { default as Menu } from './Menu';
2
+ export { useMenuContext } from './Menu.context';
3
+ export { default as MenuItem } from './MenuItem';
4
+ export { default as MenuTrigger } from './MenuTrigger';
@@ -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, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, fullscreen, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Modal;
@@ -12,7 +12,7 @@ import { Button } from '../Button';
12
12
  import { Heading } from '../Heading';
13
13
  import { Spinner } from '../Spinner';
14
14
  import { UnstyledIconButton } from '../UnstyledIconButton';
15
- const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, fullscreen = false, ...props }) => {
15
+ const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, ...props }) => {
16
16
  const bottomSheetModalRef = useRef(null);
17
17
  const viewRef = useRef(null);
18
18
  const scrollViewRef = useRef(null);
@@ -20,7 +20,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
20
20
  const backgroundOpacity = useSharedValue(0);
21
21
  const pretendContentTranslateY = useSharedValue(20);
22
22
  const triggerCloseAnimation = useCallback(() => {
23
- if (Platform.OS === 'android' && fullscreen) {
23
+ if (Platform.OS === 'android' && inNavModal) {
24
24
  pretendContentTranslateY.value = withTiming(20, {
25
25
  duration: 50,
26
26
  easing: Easing.in(Easing.quad),
@@ -30,14 +30,14 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
30
30
  easing: Easing.in(Easing.quad),
31
31
  });
32
32
  }
33
- }, [Platform.OS, fullscreen, pretendContentTranslateY, backgroundOpacity]);
33
+ }, [Platform.OS, inNavModal, pretendContentTranslateY, backgroundOpacity]);
34
34
  useImperativeHandle(ref, () => ({
35
35
  ...bottomSheetModalRef.current,
36
36
  triggerCloseAnimation,
37
37
  }));
38
- // Trigger animations on render for fullscreen Android modal
38
+ // Trigger animations on render for inNavModal Android modal
39
39
  useEffect(() => {
40
- if (Platform.OS === 'android' && fullscreen) {
40
+ if (Platform.OS === 'android' && inNavModal) {
41
41
  backgroundOpacity.value = withDelay(300, withTiming(1, {
42
42
  duration: 200,
43
43
  easing: Easing.out(Easing.quad),
@@ -47,11 +47,11 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
47
47
  easing: Easing.out(Easing.quad),
48
48
  }));
49
49
  }
50
- }, [fullscreen, backgroundOpacity, pretendContentTranslateY]);
50
+ }, [inNavModal, backgroundOpacity, pretendContentTranslateY]);
51
51
  const animatedBackgroundStyle = useAnimatedStyle(() => ({
52
52
  backgroundColor: hexWithOpacity(theme.components.overlay.backgroundColor, backgroundOpacity.value * (theme.components.overlay.opacity / 100)),
53
53
  }));
54
- const animatedFullscreenStyle = useAnimatedStyle(() => ({
54
+ const animatedInNavModalStyle = useAnimatedStyle(() => ({
55
55
  backgroundColor: hexWithOpacity(theme.components.overlay.backgroundColor, backgroundOpacity.value * (theme.components.overlay.opacity / 100)),
56
56
  }));
57
57
  const animatedPretendContentStyle = useAnimatedStyle(() => ({
@@ -99,12 +99,15 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
99
99
  };
100
100
  styles.useVariants({ loading });
101
101
  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: "Loading..." })] })) : (_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: [_jsx(Image, { style: styles.image, ...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, _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] })] })) }));
102
- return fullscreen ? (_jsxs(View, { style: { flex: 1, backgroundColor: theme.color.background.primary }, 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.fullscreenContainer, Platform.OS === 'android' && animatedFullscreenStyle], children: _jsx(View, { style: styles.fullscreenContent, children: content }) })] })) : (_jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: styles.container, ref: scrollViewRef, children: content })] }));
102
+ return inNavModal ? (_jsxs(View, { style: { flex: 1, backgroundColor: theme.color.background.primary }, 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(View, { 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, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: styles.container, ref: scrollViewRef, children: content })] }));
103
103
  };
104
104
  const styles = StyleSheet.create((theme, rt) => ({
105
+ modal: {
106
+ gap: theme.components.modal.content.gap - theme.components.bottomSheet.gap,
107
+ },
105
108
  container: {
106
109
  flex: 1,
107
- gap: theme.components.dialog.gap,
110
+ gap: theme.components.modal.gap,
108
111
  variants: {
109
112
  loading: {
110
113
  true: {
@@ -115,11 +118,11 @@ const styles = StyleSheet.create((theme, rt) => ({
115
118
  },
116
119
  header: {
117
120
  flexDirection: 'row',
118
- gap: theme.components.dialog.gap,
121
+ gap: theme.components.modal.gap,
119
122
  },
120
123
  headerTextContent: {
121
124
  flex: 1,
122
- gap: theme.components.dialog.content.gap,
125
+ gap: theme.components.modal.content.gap,
123
126
  },
124
127
  image: {
125
128
  width: 260,
@@ -127,52 +130,51 @@ const styles = StyleSheet.create((theme, rt) => ({
127
130
  },
128
131
  imageContainer: {
129
132
  alignItems: 'center',
130
- justifyContent: 'center',
131
133
  flex: 1,
132
134
  },
133
135
  textContent: {
134
- gap: theme.components.dialog.content.gap,
136
+ gap: theme.components.modal.content.gap,
135
137
  },
136
138
  loadingTop: {
137
- borderTopLeftRadius: theme.components.dialog.borderRadius,
138
- borderTopRightRadius: theme.components.dialog.borderRadius,
139
+ borderTopLeftRadius: theme.components.modal.borderRadius,
140
+ borderTopRightRadius: theme.components.modal.borderRadius,
139
141
  height: 16,
140
142
  backgroundColor: theme.color.surface.neutral.strong,
141
143
  },
142
144
  loadingContainer: {
143
- borderTopLeftRadius: theme.components.dialog.borderRadius,
144
- borderTopRightRadius: theme.components.dialog.borderRadius,
145
+ borderTopLeftRadius: theme.components.modal.borderRadius,
146
+ borderTopRightRadius: theme.components.modal.borderRadius,
145
147
  flex: 1,
146
148
  alignItems: 'center',
147
149
  justifyContent: 'center',
148
150
  minHeight: 140,
149
- gap: theme.components.dialog.content.gap,
151
+ gap: theme.components.modal.content.gap,
150
152
  },
151
153
  footer: {
152
- gap: theme.components.dialog.action.gap,
154
+ gap: theme.components.modal.action.gap,
153
155
  },
154
- fullscreenContainer: {
156
+ inNavModalContainer: {
155
157
  flex: 1,
156
158
  ...(Platform.OS === 'ios' ? { backgroundColor: theme.components.overlay.backgroundColor } : {}),
157
159
  },
158
- fullscreenContent: {
160
+ inNavModalContent: {
159
161
  flex: 1,
160
- borderTopLeftRadius: theme.components.dialog.borderRadius,
161
- borderTopRightRadius: theme.components.dialog.borderRadius,
162
+ borderTopLeftRadius: theme.components.modal.borderRadius,
163
+ borderTopRightRadius: theme.components.modal.borderRadius,
162
164
  backgroundColor: theme.color.surface.neutral.strong,
163
- gap: theme.components.dialog.gap,
164
- padding: theme.components.dialog.padding,
165
- paddingBottom: theme.components.dialog.padding + rt.insets.bottom,
165
+ gap: theme.components.modal.gap,
166
+ padding: theme.components.modal.padding,
167
+ paddingBottom: theme.components.modal.padding + rt.insets.bottom,
166
168
  },
167
169
  androidContainer: {
168
170
  height: rt.insets.top + 18,
169
- paddingLeft: theme.components.dialog.padding,
170
- paddingRight: theme.components.dialog.padding,
171
+ paddingLeft: theme.components.modal.padding,
172
+ paddingRight: theme.components.modal.padding,
171
173
  justifyContent: 'flex-end',
172
174
  },
173
175
  pretendContent: {
174
- borderTopLeftRadius: theme.components.dialog.borderRadius,
175
- borderTopRightRadius: theme.components.dialog.borderRadius,
176
+ borderTopLeftRadius: theme.components.modal.borderRadius,
177
+ borderTopRightRadius: theme.components.modal.borderRadius,
176
178
  height: 12,
177
179
  backgroundColor: theme.components.parts.modalStack.backgroundColorCardTop,
178
180
  },
@@ -8,6 +8,7 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
8
8
  showCloseButton?: boolean;
9
9
  heading?: string;
10
10
  description?: string;
11
+ inNavModal?: boolean;
11
12
  fullscreen?: boolean;
12
13
  children?: ViewProps['children'];
13
14
  onPressPrimaryButton?: () => void;
@@ -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, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, fullscreen, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Modal;