@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
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Do not edit directly, this file was auto-generated.
3
+ */
4
+ declare const _default: {
5
+ readonly bar: {
6
+ readonly width: 2;
7
+ };
8
+ readonly content: {
9
+ readonly gap: 8;
10
+ readonly paddingBottom: 24;
11
+ readonly paddingTop: 2;
12
+ };
13
+ readonly gap: 12;
14
+ readonly progress: {
15
+ readonly circle: {
16
+ readonly height: 28;
17
+ readonly width: 28;
18
+ };
19
+ };
20
+ readonly static: {
21
+ readonly circle: {
22
+ readonly height: 12;
23
+ readonly width: 12;
24
+ };
25
+ };
26
+ };
27
+ export default _default;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Do not edit directly, this file was auto-generated.
3
+ */
4
+ export default {
5
+ bar: {
6
+ width: 2,
7
+ },
8
+ content: {
9
+ gap: 8,
10
+ paddingBottom: 24,
11
+ paddingTop: 2,
12
+ },
13
+ gap: 12,
14
+ progress: {
15
+ circle: {
16
+ height: 28,
17
+ width: 28,
18
+ },
19
+ },
20
+ static: {
21
+ circle: {
22
+ height: 12,
23
+ width: 12,
24
+ },
25
+ },
26
+ };
@@ -6,9 +6,12 @@ import {
6
6
  BroadbandMediumIcon,
7
7
  CashbackCardMediumIcon,
8
8
  ChevronRightMediumIcon,
9
+ EditSmallIcon,
9
10
  ElectricityMediumIcon,
10
11
  InsuranceMediumIcon,
11
12
  MobileMediumIcon,
13
+ ShareSmallIcon,
14
+ TrashSmallIcon,
12
15
  } from '@utilitywarehouse/hearth-react-native-icons';
13
16
  // @ts-ignore
14
17
  import SpotBillingDark from '@utilitywarehouse/hearth-svg-assets/lib/spot-billing-dark.svg';
@@ -60,6 +63,9 @@ import {
60
63
  Link,
61
64
  List,
62
65
  ListItem,
66
+ Menu,
67
+ MenuItem,
68
+ MenuTrigger,
63
69
  Modal,
64
70
  OL,
65
71
  ProgressStep,
@@ -159,6 +165,10 @@ const AllComponents: React.FC = () => {
159
165
  }, [datePickerRef]);
160
166
  const [switchEnabled, setSwitchEnabled] = React.useState(false);
161
167
  const toggleSwitch = () => setSwitchEnabled(!switchEnabled);
168
+ const menuRef = useRef<any>(null);
169
+ const handleMenuOpenPress = useCallback(() => {
170
+ menuRef.current?.present();
171
+ }, []);
162
172
 
163
173
  const [colorMode] = useColorMode();
164
174
  const isDark = colorMode === 'dark';
@@ -564,6 +574,29 @@ const AllComponents: React.FC = () => {
564
574
  </List>
565
575
  </Center>
566
576
  </ComponentWrapper>
577
+ <ComponentWrapper name="Menu" link="/?path=/docs/components-menu--docs">
578
+ <Center flex={1}>
579
+ <BottomSheetModalProvider>
580
+ <MenuTrigger onPress={handleMenuOpenPress}>
581
+ <Button>Open Menu</Button>
582
+ </MenuTrigger>
583
+ <Menu ref={menuRef} heading="Actions">
584
+ <MenuItem icon={EditSmallIcon} text="Edit" onPress={() => console.log('Edit')} />
585
+ <MenuItem
586
+ icon={ShareSmallIcon}
587
+ text="Share"
588
+ onPress={() => console.log('Share')}
589
+ />
590
+ <MenuItem
591
+ icon={TrashSmallIcon}
592
+ text="Delete"
593
+ colorScheme="destructive"
594
+ onPress={() => console.log('Delete')}
595
+ />
596
+ </Menu>
597
+ </BottomSheetModalProvider>
598
+ </Center>
599
+ </ComponentWrapper>
567
600
  <ComponentWrapper name="Modal" link="/?path=/docs/components-modal--docs">
568
601
  <Center flex={1}>
569
602
  <Button onPress={handleModalOpenPress}>Open Modal</Button>
@@ -29,7 +29,7 @@ const ScrollButton = () => {
29
29
  <Button
30
30
  onPress={scrollToTop}
31
31
  variant="ghost"
32
- colorScheme="grey"
32
+ colorScheme="functional"
33
33
  size="sm"
34
34
  // @ts-expect-error - This is a playground
35
35
  style={{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilitywarehouse/hearth-react-native",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Utility Warehouse React Native UI library",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -60,7 +60,7 @@
60
60
  "@utilitywarehouse/hearth-react-icons": "^0.7.3",
61
61
  "@utilitywarehouse/hearth-react-native-icons": "^0.7.3",
62
62
  "@utilitywarehouse/hearth-svg-assets": "^0.2.0",
63
- "@utilitywarehouse/hearth-tokens": "^0.1.3"
63
+ "@utilitywarehouse/hearth-tokens": "^0.2.0"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "@gorhom/bottom-sheet": "^5.0.0",
@@ -69,8 +69,8 @@ const MyComponent = () => (
69
69
  | iconContainerVariant | `'subtle' \| 'emphasis'` | Icon container visual style | `'subtle'` |
70
70
  | iconContainerSize | `'sm' \| 'md' \| 'lg'` | Icon container size | `'md'` |
71
71
  | iconContainerColor | `'pig' \| 'energy' \| 'broadband' \| 'mobile' \|` <br />`'insurance' \| 'cashback' \| 'highlight'` | Icon container color scheme | `'pig'` |
72
- | illustration | `{ light: ImageSource, dark: ImageSource }` | Themed illustration object (mutually exclusive with icon/image) | `-` |
73
- | image | `{ light: ImageSource, dark: ImageSource }` | Themed image object (mutually exclusive with icon/illustration) | `-` |
72
+ | illustration | `ThemedImageProps \| ImageProps` | Themed illustration object (mutually exclusive with icon/image) | `-` |
73
+ | image | `ThemedImageProps \| ImageProps` | Themed image object (mutually exclusive with icon/illustration) | `-` |
74
74
  | heading | `string` | Heading text | `-` (required) |
75
75
  | description | `string` | Description text | `-` (required) |
76
76
  | direction | `'horizontal' \| 'vertical'` | Layout direction for icon/image and content | `'horizontal'` |
@@ -156,14 +156,23 @@ Display a themed image that automatically switches between light and dark modes:
156
156
  import { Banner } from '@utilitywarehouse/hearth-react-native';
157
157
 
158
158
  const MyComponent = () => (
159
- <Banner
160
- image={{
161
- light: { uri: 'https://example.com/light-image.jpg' },
162
- dark: { uri: 'https://example.com/dark-image.jpg' },
163
- }}
164
- heading="Featured Content"
165
- description="Discover amazing content curated just for you."
166
- />
159
+ <>
160
+ <Banner
161
+ image={{
162
+ source: { uri: 'https://example.com/image.jpg' },
163
+ }}
164
+ heading="Featured Content"
165
+ description="Discover amazing content curated just for you."
166
+ />
167
+ <Banner
168
+ image={{
169
+ light: { uri: 'https://example.com/light-image.jpg' },
170
+ dark: { uri: 'https://example.com/dark-image.jpg' },
171
+ }}
172
+ heading="Featured Content"
173
+ description="Discover amazing content curated just for you."
174
+ />
175
+ </>
167
176
  );
168
177
  ```
169
178
 
@@ -51,12 +51,12 @@ export interface BannerProps
51
51
  * Illustration to display in the banner
52
52
  * Mutually exclusive with icon and image
53
53
  */
54
- illustration?: ThemedImageProps & ImageProps;
54
+ illustration?: ThemedImageProps | ImageProps;
55
55
  /**
56
56
  * Image to display in the banner
57
57
  * Mutually exclusive with icon and illustration
58
58
  */
59
- image?: ThemedImageProps & ImageProps;
59
+ image?: ThemedImageProps | ImageProps;
60
60
  /**
61
61
  * Heading text
62
62
  */
@@ -326,10 +326,7 @@ export const VerticalLayout: Story = {
326
326
  <Banner
327
327
  variant="emphasis"
328
328
  image={{
329
- light: {
330
- uri: 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=200&q=80',
331
- },
332
- dark: {
329
+ source: {
333
330
  uri: 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=200&q=80',
334
331
  },
335
332
  }}
@@ -1,14 +1,18 @@
1
1
  import { ChevronRightSmallIcon, CloseSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
2
- import { Pressable, View } from 'react-native';
2
+ import { Image, ImageProps, Pressable, View } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
  import { BodyText } from '../BodyText';
5
5
  import { Card } from '../Card';
6
6
  import { Heading } from '../Heading';
7
7
  import { IconContainer } from '../IconContainer';
8
- import { ThemedImage } from '../ThemedImage';
8
+ import { ThemedImage, ThemedImageProps } from '../ThemedImage';
9
9
  import { UnstyledIconButton } from '../UnstyledIconButton';
10
10
  import type BannerProps from './Banner.props';
11
11
 
12
+ const isThemedImageProps = (props: ThemedImageProps | ImageProps): props is ThemedImageProps => {
13
+ return 'light' in props && 'dark' in props;
14
+ };
15
+
12
16
  const Banner = ({
13
17
  icon,
14
18
  iconContainerVariant = 'subtle',
@@ -44,8 +48,17 @@ const Banner = ({
44
48
  );
45
49
  }
46
50
  if (illustration) {
51
+ if (isThemedImageProps(illustration)) {
52
+ return (
53
+ <ThemedImage
54
+ {...illustration}
55
+ resizeMode="cover"
56
+ style={[styles.media, styles.imageWrapper, illustration.style]}
57
+ />
58
+ );
59
+ }
47
60
  return (
48
- <ThemedImage
61
+ <Image
49
62
  {...illustration}
50
63
  resizeMode="cover"
51
64
  style={[styles.media, styles.imageWrapper, illustration.style]}
@@ -53,9 +66,16 @@ const Banner = ({
53
66
  );
54
67
  }
55
68
  if (image) {
69
+ if (isThemedImageProps(image)) {
70
+ return (
71
+ <View style={[styles.media, styles.imageWrapper]}>
72
+ <ThemedImage {...image} style={[styles.image, image.style]} />
73
+ </View>
74
+ );
75
+ }
56
76
  return (
57
77
  <View style={[styles.media, styles.imageWrapper]}>
58
- <ThemedImage {...image} style={[styles.image, image.style]} />
78
+ <Image {...image} style={[styles.image, image.style]} />
59
79
  </View>
60
80
  );
61
81
  }
@@ -74,7 +94,17 @@ const Banner = ({
74
94
 
75
95
  const content = (
76
96
  <View style={styles.container}>
97
+ {onClose && direction === 'vertical' && (
98
+ <UnstyledIconButton
99
+ icon={CloseSmallIcon}
100
+ size="sm"
101
+ onPress={onClose}
102
+ style={styles.closeButton}
103
+ accessibilityLabel="Close banner"
104
+ />
105
+ )}
77
106
  {renderIconOrImage()}
107
+
78
108
  <View style={styles.contentContainer}>
79
109
  <View style={styles.contentTextContainer}>
80
110
  <View style={styles.textContainer}>
@@ -104,7 +134,7 @@ const Banner = ({
104
134
  style={styles.chevron}
105
135
  />
106
136
  )}
107
- {onClose && (
137
+ {onClose && direction === 'horizontal' && (
108
138
  <UnstyledIconButton
109
139
  icon={CloseSmallIcon}
110
140
  size="sm"
@@ -137,9 +167,9 @@ const Banner = ({
137
167
  Banner.displayName = 'Banner';
138
168
 
139
169
  const styles = StyleSheet.create(theme => ({
140
- card: {},
170
+ card: { flexDirection: 'row', _web: { flexDirection: 'column' } },
141
171
  pressable: {
142
- width: '100%',
172
+ flex: 1,
143
173
  },
144
174
  container: {
145
175
  flexDirection: 'row',
@@ -273,6 +303,16 @@ const styles = StyleSheet.create(theme => ({
273
303
  },
274
304
  closeButton: {
275
305
  alignSelf: 'flex-start',
306
+ variants: {
307
+ direction: {
308
+ vertical: {
309
+ position: 'absolute',
310
+ top: 0,
311
+ right: 0,
312
+ },
313
+ horizontal: {},
314
+ },
315
+ },
276
316
  },
277
317
  }));
278
318
 
@@ -1,10 +1,18 @@
1
1
  import { BottomSheetHandle as Handle } from '@gorhom/bottom-sheet';
2
2
  import { BottomSheetDefaultHandleProps } from '@gorhom/bottom-sheet/lib/typescript/components/bottomSheetHandle/types';
3
+ import { Platform, View } from 'react-native';
3
4
  import { StyleSheet, withUnistyles } from 'react-native-unistyles';
4
5
 
5
6
  const StyledBottomSheetHandle = withUnistyles(Handle);
6
7
 
7
8
  const BottomSheetHandle = ({ style, indicatorStyle, ...props }: BottomSheetDefaultHandleProps) => {
9
+ if (Platform.OS === 'web') {
10
+ return (
11
+ <View style={[styles.handle, style]}>
12
+ <View style={[styles.indicator, indicatorStyle]} />
13
+ </View>
14
+ );
15
+ }
8
16
  return (
9
17
  <StyledBottomSheetHandle
10
18
  style={[styles.handle, style]}
@@ -22,6 +30,10 @@ const styles = StyleSheet.create(theme => ({
22
30
  paddingTop: theme.components.bottomSheet.padding,
23
31
  paddingHorizontal: theme.components.bottomSheet.padding,
24
32
  paddingBottom: theme.components.bottomSheet.gap,
33
+ _web: {
34
+ alignItems: 'center',
35
+ cursor: 'grab',
36
+ },
25
37
  },
26
38
  indicator: {
27
39
  width: theme.components.bottomSheet.handle.width,
@@ -232,6 +232,6 @@ Selection through the calendar always returns a JavaScript `Date` that is reform
232
232
  ### Best practices
233
233
 
234
234
  - Always pair with `FormField` for proper label, helper text, and error message announcements.
235
- - Use `openButtonLabel` to customize the calendar button announcement if the default doesn't fit your use case.
235
+ - Use `openButtonLabel` to customise the calendar button announcement if the default doesn't fit your use case.
236
236
  - Provide clear validation feedback through `FormField` when manual date entry doesn't match the expected format.
237
237
  - Test with VoiceOver and TalkBack to ensure the date entry flow works smoothly in your specific context.
@@ -0,0 +1,15 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ export interface IMenuContext {
4
+ close: () => void;
5
+ }
6
+
7
+ export const MenuContext = createContext<IMenuContext | undefined>(undefined);
8
+
9
+ export const useMenuContext = () => {
10
+ const context = useContext(MenuContext);
11
+ if (!context) {
12
+ throw new Error('useMenuContext must be used within a Menu component');
13
+ }
14
+ return context;
15
+ };
@@ -0,0 +1,158 @@
1
+ import { Canvas, Controls, Meta, Story } from '@storybook/addon-docs/blocks';
2
+ import { BackToTopButton, ViewFigmaButton } from '../../../docs/components';
3
+ import * as Stories from './Menu.stories';
4
+
5
+ <Meta title="Components / Menu" />
6
+
7
+ <ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens" />
8
+
9
+ <BackToTopButton />
10
+
11
+ # Menu
12
+
13
+ The `Menu` component provides a bottom sheet modal with a list of actions. It's perfect for contextual menus, action sheets, and option lists. The component uses `BottomSheetModal` under the hood and includes `MenuItem` components that can display icons, text, and support different color schemes.
14
+
15
+ - [Playground](#playground)
16
+ - [Usage](#usage)
17
+ - [Props](#props)
18
+ - [Features](#features)
19
+ - [Examples](#examples)
20
+ - [Basic Menu](#basic-menu)
21
+ - [With Destructive Action](#with-destructive-action)
22
+ - [Icon on Right](#icon-on-right)
23
+ - [Without Icons](#without-icons)
24
+ - [With Disabled Items](#with-disabled-items)
25
+ - [Without Heading](#without-heading)
26
+ - [Accessibility](#accessibility)
27
+
28
+ ## Playground
29
+
30
+ <Canvas of={Stories.Playground} />
31
+
32
+ <Controls of={Stories.Playground} />
33
+
34
+ ## Usage
35
+
36
+ ### Basic Usage
37
+
38
+ ```tsx
39
+ import { useRef } from 'react';
40
+ import { Menu, MenuItem, MenuTrigger } from '@utilitywarehouse/hearth-react-native';
41
+ import { EditSmallIcon, TrashSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
42
+
43
+ const MyComponent = () => {
44
+ const menuRef = useRef(null);
45
+
46
+ return (
47
+ <>
48
+ <MenuTrigger onPress={() => menuRef.current?.present()}>
49
+ <Button>Open Menu</Button>
50
+ </MenuTrigger>
51
+
52
+ <Menu ref={menuRef} heading="Actions">
53
+ <MenuItem icon={EditSmallIcon} text="Edit" onPress={() => console.log('Edit')} />
54
+ <MenuItem
55
+ icon={TrashSmallIcon}
56
+ text="Delete"
57
+ colorScheme="destructive"
58
+ onPress={() => console.log('Delete')}
59
+ />
60
+ </Menu>
61
+ </>
62
+ );
63
+ };
64
+ ```
65
+
66
+ ## Props
67
+
68
+ ### Menu
69
+
70
+ | Prop | Type | Default | Description |
71
+ | ------------------ | -------------------------------- | ------- | --------------------------------------------------- |
72
+ | `heading` | `string` | - | Heading text displayed at the top of the menu |
73
+ | `children` | `ReactNode` | - | Menu items to display |
74
+ | `bottomSheetProps` | `Partial<BottomSheetModalProps>` | - | Optional bottom sheet modal props for customization |
75
+
76
+ ### MenuItem
77
+
78
+ | Prop | Type | Default | Description |
79
+ | -------------- | ---------------------------------------- | -------------- | ---------------------------------- |
80
+ | `icon` | `ComponentType` | - | Icon component to display |
81
+ | `iconPosition` | `'left' \| 'right'` | `'left'` | Position of the icon |
82
+ | `text` | `string` | - | Text to display in the menu item |
83
+ | `colorScheme` | `'functional' \| 'destructive'` | `'functional'` | Color scheme for the menu item |
84
+ | `disabled` | `boolean` | `false` | Whether the menu item is disabled |
85
+ | `onPress` | `(event: GestureResponderEvent) => void` | - | Callback when menu item is pressed |
86
+
87
+ ### MenuTrigger
88
+
89
+ | Prop | Type | Description |
90
+ | ---------- | -------------- | ------------------------------------------------------------------- |
91
+ | `children` | `ReactElement` | The child element that triggers the menu (must be a single element) |
92
+ | `onPress` | `() => void` | Function that opens the menu |
93
+
94
+ ## Features
95
+
96
+ ### Icon Positioning
97
+
98
+ Icons can be positioned on either the left or right side of the menu item text using the `iconPosition` prop.
99
+
100
+ ### Color Schemes
101
+
102
+ Menu items support two color schemes:
103
+
104
+ - `functional` (default): Uses primary text and icon colors
105
+ - `destructive`: Uses danger/destructive colors for actions like delete
106
+
107
+ ### Disabled State
108
+
109
+ Menu items can be disabled using the `disabled` prop, which applies reduced opacity and prevents interaction.
110
+
111
+ ### Auto-closing
112
+
113
+ Menu items automatically close the menu when pressed, making it easy to handle actions without manual menu dismissal.
114
+
115
+ ## Examples
116
+
117
+ ### Basic Menu
118
+
119
+ A simple menu with icon-based actions:
120
+
121
+ <Canvas of={Stories.BasicMenu} />
122
+
123
+ ### With Destructive Action
124
+
125
+ Menu with a destructive action styled differently:
126
+
127
+ <Canvas of={Stories.WithDestructiveAction} />
128
+
129
+ ### Icon on Right
130
+
131
+ Menu items with icons positioned on the right side:
132
+
133
+ <Canvas of={Stories.IconOnRight} />
134
+
135
+ ### Without Icons
136
+
137
+ Simple text-only menu items:
138
+
139
+ <Canvas of={Stories.WithoutIcons} />
140
+
141
+ ### With Disabled Items
142
+
143
+ Menu with some items disabled:
144
+
145
+ <Canvas of={Stories.WithDisabledItems} />
146
+
147
+ ### Without Heading
148
+
149
+ Menu without a heading section:
150
+
151
+ <Canvas of={Stories.WithoutHeading} />
152
+
153
+ ## Accessibility
154
+
155
+ - Menu items have `accessibilityRole="button"` for proper screen reader support
156
+ - Disabled state is communicated via `accessibilityState`
157
+ - Keyboard navigation is supported on web platforms
158
+ - All interactive elements are properly focusable
@@ -0,0 +1,24 @@
1
+ import type { BottomSheetModalProps } from '@gorhom/bottom-sheet';
2
+ import type { ReactNode } from 'react';
3
+
4
+ export interface MenuMethods {
5
+ present: () => void;
6
+ dismiss: () => void;
7
+ }
8
+
9
+ export interface MenuProps {
10
+ /**
11
+ * Heading text displayed at the top of the menu
12
+ */
13
+ heading?: string;
14
+ /**
15
+ * Menu items to display
16
+ */
17
+ children: ReactNode;
18
+ /**
19
+ * Optional bottom sheet modal props to customise the menu behavior
20
+ */
21
+ bottomSheetProps?: Partial<BottomSheetModalProps>;
22
+ }
23
+
24
+ export default MenuProps;