@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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +8 -0
- package/build/components/Banner/Banner.js +25 -6
- package/build/components/Banner/Banner.props.d.ts +2 -2
- package/build/components/BottomSheet/BottomSheetHandle.js +8 -0
- package/build/components/Menu/Menu.context.d.ts +5 -0
- package/build/components/Menu/Menu.context.js +9 -0
- package/build/components/Menu/Menu.d.ts +4 -0
- package/build/components/Menu/Menu.js +25 -0
- package/build/components/Menu/Menu.props.d.ts +21 -0
- package/build/components/Menu/Menu.props.js +1 -0
- package/build/components/Menu/MenuItem.d.ts +18 -0
- package/build/components/Menu/MenuItem.js +115 -0
- package/build/components/Menu/MenuItem.props.d.ts +27 -0
- package/build/components/Menu/MenuItem.props.js +1 -0
- package/build/components/Menu/MenuTrigger.d.ts +9 -0
- package/build/components/Menu/MenuTrigger.js +11 -0
- package/build/components/Menu/MenuTrigger.props.d.ts +12 -0
- package/build/components/Menu/MenuTrigger.props.js +1 -0
- package/build/components/Menu/index.d.ts +7 -0
- package/build/components/Menu/index.js +4 -0
- package/build/components/Modal/Modal.d.ts +1 -1
- package/build/components/Modal/Modal.js +32 -30
- package/build/components/Modal/Modal.props.d.ts +1 -0
- package/build/components/Modal/Modal.web.d.ts +1 -1
- package/build/components/Modal/Modal.web.js +25 -25
- package/build/components/index.d.ts +1 -0
- package/build/components/index.js +1 -0
- package/build/tokens/components/dark/index.d.ts +3 -1
- package/build/tokens/components/dark/index.js +3 -1
- package/build/tokens/components/dark/input.d.ts +3 -0
- package/build/tokens/components/dark/input.js +3 -0
- package/build/tokens/components/dark/modal.d.ts +7 -4
- package/build/tokens/components/dark/modal.js +7 -4
- package/build/tokens/components/dark/rating.d.ts +8 -0
- package/build/tokens/components/dark/rating.js +7 -0
- package/build/tokens/components/dark/table.d.ts +0 -3
- package/build/tokens/components/dark/table.js +0 -3
- package/build/tokens/components/dark/time-picker.d.ts +29 -0
- package/build/tokens/components/dark/time-picker.js +28 -0
- package/build/tokens/components/dark/timeline.d.ts +27 -0
- package/build/tokens/components/dark/timeline.js +26 -0
- package/build/tokens/components/light/index.d.ts +3 -1
- package/build/tokens/components/light/index.js +3 -1
- package/build/tokens/components/light/input.d.ts +3 -0
- package/build/tokens/components/light/input.js +3 -0
- package/build/tokens/components/light/modal.d.ts +7 -4
- package/build/tokens/components/light/modal.js +7 -4
- package/build/tokens/components/light/rating.d.ts +8 -0
- package/build/tokens/components/light/rating.js +7 -0
- package/build/tokens/components/light/table.d.ts +0 -3
- package/build/tokens/components/light/table.js +0 -3
- package/build/tokens/components/light/time-picker.d.ts +29 -0
- package/build/tokens/components/light/time-picker.js +28 -0
- package/build/tokens/components/light/timeline.d.ts +27 -0
- package/build/tokens/components/light/timeline.js +26 -0
- package/docs/components/AllComponents.web.tsx +33 -0
- package/docs/components/BackToTopButton.tsx +1 -1
- package/package.json +2 -2
- package/src/components/Banner/Banner.docs.mdx +19 -10
- package/src/components/Banner/Banner.props.ts +2 -2
- package/src/components/Banner/Banner.stories.tsx +1 -4
- package/src/components/Banner/Banner.tsx +47 -7
- package/src/components/BottomSheet/BottomSheetHandle.tsx +12 -0
- package/src/components/DatePickerInput/DatePickerInput.docs.mdx +1 -1
- package/src/components/Menu/Menu.context.ts +15 -0
- package/src/components/Menu/Menu.docs.mdx +158 -0
- package/src/components/Menu/Menu.props.ts +24 -0
- package/src/components/Menu/Menu.stories.tsx +292 -0
- package/src/components/Menu/Menu.tsx +54 -0
- package/src/components/Menu/MenuItem.props.ts +29 -0
- package/src/components/Menu/MenuItem.tsx +145 -0
- package/src/components/Menu/MenuTrigger.props.ts +14 -0
- package/src/components/Menu/MenuTrigger.tsx +20 -0
- package/src/components/Menu/index.ts +7 -0
- package/src/components/Modal/Modal.docs.mdx +34 -5
- package/src/components/Modal/Modal.props.ts +1 -0
- package/src/components/Modal/Modal.stories.tsx +46 -0
- package/src/components/Modal/Modal.tsx +37 -33
- package/src/components/Modal/Modal.web.tsx +27 -27
- package/src/components/index.ts +1 -0
- package/src/tokens/components/dark/index.ts +3 -1
- package/src/tokens/components/dark/input.ts +3 -0
- package/src/tokens/components/dark/modal.ts +7 -4
- package/src/tokens/components/dark/rating.ts +8 -0
- package/src/tokens/components/dark/table.ts +0 -3
- package/src/tokens/components/dark/time-picker.ts +29 -0
- package/src/tokens/components/dark/timeline.ts +27 -0
- package/src/tokens/components/light/index.ts +3 -1
- package/src/tokens/components/light/input.ts +3 -0
- package/src/tokens/components/light/modal.ts +7 -4
- package/src/tokens/components/light/rating.ts +8 -0
- package/src/tokens/components/light/table.ts +0 -3
- package/src/tokens/components/light/time-picker.ts +29 -0
- package/src/tokens/components/light/timeline.ts +27 -0
- package/build/tokens/components/dark/dialog.d.ts +0 -25
- package/build/tokens/components/dark/dialog.js +0 -24
- package/build/tokens/components/light/dialog.d.ts +0 -25
- package/build/tokens/components/light/dialog.js +0 -24
- package/src/tokens/components/dark/dialog.ts +0 -25
- package/src/tokens/components/light/dialog.ts +0 -25
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-native';
|
|
2
|
+
import {
|
|
3
|
+
DownloadSmallIcon,
|
|
4
|
+
EditSmallIcon,
|
|
5
|
+
SettingsMediumIcon,
|
|
6
|
+
ShareSmallIcon,
|
|
7
|
+
TickSmallIcon,
|
|
8
|
+
TrashSmallIcon,
|
|
9
|
+
} from '@utilitywarehouse/hearth-react-native-icons';
|
|
10
|
+
import { useRef } from 'react';
|
|
11
|
+
import { Platform, View } from 'react-native';
|
|
12
|
+
import { Button } from '../Button';
|
|
13
|
+
import { Menu, MenuItem, MenuTrigger } from './';
|
|
14
|
+
import type { MenuMethods } from './Menu.props';
|
|
15
|
+
|
|
16
|
+
import { ViewWrap } from '../../../docs/components';
|
|
17
|
+
|
|
18
|
+
const meta: Meta<typeof Menu> = {
|
|
19
|
+
title: 'Stories / Menu',
|
|
20
|
+
component: Menu,
|
|
21
|
+
parameters: {
|
|
22
|
+
layout: 'centered',
|
|
23
|
+
noScroll: true,
|
|
24
|
+
},
|
|
25
|
+
argTypes: {},
|
|
26
|
+
args: {
|
|
27
|
+
heading: 'Menu Options',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default meta;
|
|
32
|
+
type Story = StoryObj<typeof meta>;
|
|
33
|
+
|
|
34
|
+
export const Playground: Story = {
|
|
35
|
+
render: ({ ...args }) => {
|
|
36
|
+
const menuRef = useRef<MenuMethods>(null);
|
|
37
|
+
|
|
38
|
+
const openMenu = () => {
|
|
39
|
+
menuRef.current?.present();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : { flex: 1 }}>
|
|
44
|
+
<ViewWrap>
|
|
45
|
+
<MenuTrigger onPress={openMenu}>
|
|
46
|
+
<Button>Open Menu</Button>
|
|
47
|
+
</MenuTrigger>
|
|
48
|
+
|
|
49
|
+
<Menu ref={menuRef} {...args}>
|
|
50
|
+
<MenuItem
|
|
51
|
+
icon={EditSmallIcon}
|
|
52
|
+
text="Edit"
|
|
53
|
+
onPress={() => console.log('Edit pressed')}
|
|
54
|
+
/>
|
|
55
|
+
<MenuItem
|
|
56
|
+
icon={ShareSmallIcon}
|
|
57
|
+
text="Share"
|
|
58
|
+
onPress={() => console.log('Share pressed')}
|
|
59
|
+
/>
|
|
60
|
+
<MenuItem
|
|
61
|
+
icon={DownloadSmallIcon}
|
|
62
|
+
text="Download"
|
|
63
|
+
onPress={() => console.log('Download pressed')}
|
|
64
|
+
/>
|
|
65
|
+
<MenuItem
|
|
66
|
+
icon={TrashSmallIcon}
|
|
67
|
+
text="Delete"
|
|
68
|
+
colorScheme="destructive"
|
|
69
|
+
onPress={() => console.log('Delete pressed')}
|
|
70
|
+
/>
|
|
71
|
+
</Menu>
|
|
72
|
+
</ViewWrap>
|
|
73
|
+
</View>
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const BasicMenu = () => {
|
|
79
|
+
const menuRef = useRef<MenuMethods>(null);
|
|
80
|
+
|
|
81
|
+
const openMenu = () => {
|
|
82
|
+
menuRef.current?.present();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : { flex: 1 }}>
|
|
87
|
+
<ViewWrap>
|
|
88
|
+
<MenuTrigger onPress={openMenu}>
|
|
89
|
+
<Button>Actions</Button>
|
|
90
|
+
</MenuTrigger>
|
|
91
|
+
|
|
92
|
+
<Menu ref={menuRef} heading="Actions">
|
|
93
|
+
<MenuItem icon={EditSmallIcon} text="Edit" onPress={() => console.log('Edit pressed')} />
|
|
94
|
+
<MenuItem
|
|
95
|
+
icon={ShareSmallIcon}
|
|
96
|
+
text="Share"
|
|
97
|
+
onPress={() => console.log('Share pressed')}
|
|
98
|
+
/>
|
|
99
|
+
<MenuItem
|
|
100
|
+
icon={DownloadSmallIcon}
|
|
101
|
+
text="Download"
|
|
102
|
+
onPress={() => console.log('Download pressed')}
|
|
103
|
+
/>
|
|
104
|
+
</Menu>
|
|
105
|
+
</ViewWrap>
|
|
106
|
+
</View>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const WithDestructiveAction = () => {
|
|
111
|
+
const menuRef = useRef<MenuMethods>(null);
|
|
112
|
+
|
|
113
|
+
const openMenu = () => {
|
|
114
|
+
menuRef.current?.present();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : {}}>
|
|
119
|
+
<ViewWrap>
|
|
120
|
+
<MenuTrigger onPress={openMenu}>
|
|
121
|
+
<Button>File Options</Button>
|
|
122
|
+
</MenuTrigger>
|
|
123
|
+
|
|
124
|
+
<Menu ref={menuRef} heading="File Options">
|
|
125
|
+
<MenuItem
|
|
126
|
+
icon={EditSmallIcon}
|
|
127
|
+
text="Rename"
|
|
128
|
+
onPress={() => console.log('Rename pressed')}
|
|
129
|
+
/>
|
|
130
|
+
<MenuItem
|
|
131
|
+
icon={ShareSmallIcon}
|
|
132
|
+
text="Share"
|
|
133
|
+
onPress={() => console.log('Share pressed')}
|
|
134
|
+
/>
|
|
135
|
+
<MenuItem
|
|
136
|
+
icon={DownloadSmallIcon}
|
|
137
|
+
text="Download"
|
|
138
|
+
onPress={() => console.log('Download pressed')}
|
|
139
|
+
/>
|
|
140
|
+
<MenuItem
|
|
141
|
+
icon={TrashSmallIcon}
|
|
142
|
+
text="Delete"
|
|
143
|
+
colorScheme="destructive"
|
|
144
|
+
onPress={() => console.log('Delete pressed')}
|
|
145
|
+
/>
|
|
146
|
+
</Menu>
|
|
147
|
+
</ViewWrap>
|
|
148
|
+
</View>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const IconOnRight = () => {
|
|
153
|
+
const menuRef = useRef<MenuMethods>(null);
|
|
154
|
+
|
|
155
|
+
const openMenu = () => {
|
|
156
|
+
menuRef.current?.present();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : {}}>
|
|
161
|
+
<ViewWrap>
|
|
162
|
+
<MenuTrigger onPress={openMenu}>
|
|
163
|
+
<Button>Settings</Button>
|
|
164
|
+
</MenuTrigger>
|
|
165
|
+
|
|
166
|
+
<Menu ref={menuRef} heading="Settings">
|
|
167
|
+
<MenuItem
|
|
168
|
+
icon={TickSmallIcon}
|
|
169
|
+
iconPosition="right"
|
|
170
|
+
text="Enable Notifications"
|
|
171
|
+
onPress={() => console.log('Toggle notifications')}
|
|
172
|
+
/>
|
|
173
|
+
<MenuItem
|
|
174
|
+
icon={TickSmallIcon}
|
|
175
|
+
iconPosition="right"
|
|
176
|
+
text="Dark Mode"
|
|
177
|
+
onPress={() => console.log('Toggle dark mode')}
|
|
178
|
+
/>
|
|
179
|
+
<MenuItem
|
|
180
|
+
icon={SettingsMediumIcon}
|
|
181
|
+
iconPosition="right"
|
|
182
|
+
text="Advanced Settings"
|
|
183
|
+
onPress={() => console.log('Open advanced settings')}
|
|
184
|
+
/>
|
|
185
|
+
</Menu>
|
|
186
|
+
</ViewWrap>
|
|
187
|
+
</View>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const WithoutIcons = () => {
|
|
192
|
+
const menuRef = useRef<MenuMethods>(null);
|
|
193
|
+
|
|
194
|
+
const openMenu = () => {
|
|
195
|
+
menuRef.current?.present();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : {}}>
|
|
200
|
+
<ViewWrap>
|
|
201
|
+
<MenuTrigger onPress={openMenu}>
|
|
202
|
+
<Button>Sort By</Button>
|
|
203
|
+
</MenuTrigger>
|
|
204
|
+
|
|
205
|
+
<Menu ref={menuRef} heading="Sort By">
|
|
206
|
+
<MenuItem text="Name" onPress={() => console.log('Sort by name')} />
|
|
207
|
+
<MenuItem text="Date" onPress={() => console.log('Sort by date')} />
|
|
208
|
+
<MenuItem text="Size" onPress={() => console.log('Sort by size')} />
|
|
209
|
+
<MenuItem text="Type" onPress={() => console.log('Sort by type')} />
|
|
210
|
+
</Menu>
|
|
211
|
+
</ViewWrap>
|
|
212
|
+
</View>
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const WithDisabledItems = () => {
|
|
217
|
+
const menuRef = useRef<MenuMethods>(null);
|
|
218
|
+
|
|
219
|
+
const openMenu = () => {
|
|
220
|
+
menuRef.current?.present();
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : {}}>
|
|
225
|
+
<ViewWrap>
|
|
226
|
+
<MenuTrigger onPress={openMenu}>
|
|
227
|
+
<Button>Document Actions</Button>
|
|
228
|
+
</MenuTrigger>
|
|
229
|
+
|
|
230
|
+
<Menu ref={menuRef} heading="Document Actions">
|
|
231
|
+
<MenuItem icon={EditSmallIcon} text="Edit" onPress={() => console.log('Edit pressed')} />
|
|
232
|
+
<MenuItem
|
|
233
|
+
icon={ShareSmallIcon}
|
|
234
|
+
text="Share"
|
|
235
|
+
disabled
|
|
236
|
+
onPress={() => console.log('Share pressed')}
|
|
237
|
+
/>
|
|
238
|
+
<MenuItem
|
|
239
|
+
icon={DownloadSmallIcon}
|
|
240
|
+
text="Download"
|
|
241
|
+
disabled
|
|
242
|
+
onPress={() => console.log('Download pressed')}
|
|
243
|
+
/>
|
|
244
|
+
<MenuItem
|
|
245
|
+
icon={TrashSmallIcon}
|
|
246
|
+
text="Delete"
|
|
247
|
+
colorScheme="destructive"
|
|
248
|
+
onPress={() => console.log('Delete pressed')}
|
|
249
|
+
/>
|
|
250
|
+
</Menu>
|
|
251
|
+
</ViewWrap>
|
|
252
|
+
</View>
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export const WithoutHeading = () => {
|
|
257
|
+
const menuRef = useRef<MenuMethods>(null);
|
|
258
|
+
|
|
259
|
+
const openMenu = () => {
|
|
260
|
+
menuRef.current?.present();
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : {}}>
|
|
265
|
+
<ViewWrap>
|
|
266
|
+
<MenuTrigger onPress={openMenu}>
|
|
267
|
+
<Button>Quick Actions</Button>
|
|
268
|
+
</MenuTrigger>
|
|
269
|
+
|
|
270
|
+
<Menu ref={menuRef}>
|
|
271
|
+
<MenuItem icon={EditSmallIcon} text="Edit" onPress={() => console.log('Edit pressed')} />
|
|
272
|
+
<MenuItem
|
|
273
|
+
icon={ShareSmallIcon}
|
|
274
|
+
text="Share"
|
|
275
|
+
onPress={() => console.log('Share pressed')}
|
|
276
|
+
/>
|
|
277
|
+
<MenuItem
|
|
278
|
+
icon={DownloadSmallIcon}
|
|
279
|
+
text="Download"
|
|
280
|
+
onPress={() => console.log('Download pressed')}
|
|
281
|
+
/>
|
|
282
|
+
<MenuItem
|
|
283
|
+
icon={TrashSmallIcon}
|
|
284
|
+
text="Delete"
|
|
285
|
+
colorScheme="destructive"
|
|
286
|
+
onPress={() => console.log('Delete pressed')}
|
|
287
|
+
/>
|
|
288
|
+
</Menu>
|
|
289
|
+
</ViewWrap>
|
|
290
|
+
</View>
|
|
291
|
+
);
|
|
292
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
|
|
2
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
3
|
+
import { BodyText } from '../BodyText';
|
|
4
|
+
import { BottomSheetModal, BottomSheetProps, BottomSheetScrollView } from '../BottomSheet';
|
|
5
|
+
import { MenuContext } from './Menu.context';
|
|
6
|
+
import type MenuProps from './Menu.props';
|
|
7
|
+
import type { MenuMethods } from './Menu.props';
|
|
8
|
+
|
|
9
|
+
const Menu = forwardRef<MenuMethods, MenuProps>(({ heading, children, bottomSheetProps }, ref) => {
|
|
10
|
+
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
|
11
|
+
|
|
12
|
+
useImperativeHandle(
|
|
13
|
+
ref,
|
|
14
|
+
() => ({
|
|
15
|
+
present: () => bottomSheetModalRef.current?.present(),
|
|
16
|
+
dismiss: () => bottomSheetModalRef.current?.dismiss(),
|
|
17
|
+
}),
|
|
18
|
+
[]
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const handleClose = useCallback(() => {
|
|
22
|
+
bottomSheetModalRef.current?.dismiss();
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
const contextValue = useMemo(() => ({ close: handleClose }), [handleClose]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<BottomSheetModal
|
|
29
|
+
ref={bottomSheetModalRef}
|
|
30
|
+
{...(bottomSheetProps as Partial<BottomSheetProps>)}
|
|
31
|
+
>
|
|
32
|
+
<BottomSheetScrollView contentContainerStyle={styles.container}>
|
|
33
|
+
<MenuContext.Provider value={contextValue}>
|
|
34
|
+
{heading && (
|
|
35
|
+
<BodyText size="md" weight="semibold">
|
|
36
|
+
{heading}
|
|
37
|
+
</BodyText>
|
|
38
|
+
)}
|
|
39
|
+
{children}
|
|
40
|
+
</MenuContext.Provider>
|
|
41
|
+
</BottomSheetScrollView>
|
|
42
|
+
</BottomSheetModal>
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
Menu.displayName = 'Menu';
|
|
47
|
+
|
|
48
|
+
const styles = StyleSheet.create(theme => ({
|
|
49
|
+
container: {
|
|
50
|
+
gap: theme.components.bottomSheet.gap,
|
|
51
|
+
},
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
export default Menu;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { PressableProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface MenuItemProps extends Omit<PressableProps, 'children'> {
|
|
5
|
+
/**
|
|
6
|
+
* Icon component to display
|
|
7
|
+
*/
|
|
8
|
+
icon?: ComponentType;
|
|
9
|
+
/**
|
|
10
|
+
* Position of the icon
|
|
11
|
+
* @default 'left'
|
|
12
|
+
*/
|
|
13
|
+
iconPosition?: 'left' | 'right';
|
|
14
|
+
/**
|
|
15
|
+
* Text to display in the menu item
|
|
16
|
+
*/
|
|
17
|
+
text: string;
|
|
18
|
+
/**
|
|
19
|
+
* Color scheme for the menu item
|
|
20
|
+
* @default 'functional'
|
|
21
|
+
*/
|
|
22
|
+
colorScheme?: 'functional' | 'destructive';
|
|
23
|
+
/**
|
|
24
|
+
* Whether the menu item is disabled
|
|
25
|
+
*/
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default MenuItemProps;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createPressable } from '@gluestack-ui/pressable';
|
|
2
|
+
import { Pressable } from 'react-native';
|
|
3
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
4
|
+
import { BodyText } from '../BodyText';
|
|
5
|
+
import { Icon } from '../Icon';
|
|
6
|
+
import { useMenuContext } from './Menu.context';
|
|
7
|
+
import type MenuItemProps from './MenuItem.props';
|
|
8
|
+
|
|
9
|
+
const MenuItemRoot = ({
|
|
10
|
+
icon,
|
|
11
|
+
iconPosition = 'left',
|
|
12
|
+
text,
|
|
13
|
+
colorScheme = 'functional',
|
|
14
|
+
disabled = false,
|
|
15
|
+
onPress,
|
|
16
|
+
states = {},
|
|
17
|
+
...props
|
|
18
|
+
}: MenuItemProps & { states?: { active?: boolean; disabled?: boolean } }) => {
|
|
19
|
+
const { active } = states;
|
|
20
|
+
const { close } = useMenuContext();
|
|
21
|
+
|
|
22
|
+
styles.useVariants({ colorScheme, disabled, iconPosition, active });
|
|
23
|
+
|
|
24
|
+
const handlePress = (event: any) => {
|
|
25
|
+
if (disabled) return;
|
|
26
|
+
onPress?.(event);
|
|
27
|
+
close();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Pressable
|
|
32
|
+
{...props}
|
|
33
|
+
onPress={handlePress}
|
|
34
|
+
disabled={disabled}
|
|
35
|
+
style={styles.container}
|
|
36
|
+
accessibilityRole="button"
|
|
37
|
+
accessibilityState={{ disabled }}
|
|
38
|
+
>
|
|
39
|
+
{!!icon && <Icon as={icon} style={styles.icon} />}
|
|
40
|
+
<BodyText size="lg" style={styles.text}>
|
|
41
|
+
{text}
|
|
42
|
+
</BodyText>
|
|
43
|
+
</Pressable>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const MenuItem = createPressable({ Root: MenuItemRoot });
|
|
48
|
+
|
|
49
|
+
MenuItem.displayName = 'MenuItem';
|
|
50
|
+
|
|
51
|
+
const styles = StyleSheet.create(theme => ({
|
|
52
|
+
container: {
|
|
53
|
+
flexDirection: 'row',
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
paddingVertical: theme.components.menu.item.padding,
|
|
56
|
+
paddingHorizontal: theme.components.menu.mobile.item.padding,
|
|
57
|
+
gap: theme.components.menu.item.gap,
|
|
58
|
+
borderRadius: theme.components.menu.item.borderRadius,
|
|
59
|
+
variants: {
|
|
60
|
+
active: {
|
|
61
|
+
true: {
|
|
62
|
+
backgroundColor: theme.color.interactive.functional.surface.subtle.active,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
disabled: {
|
|
66
|
+
true: {
|
|
67
|
+
opacity: theme.opacity.disabled,
|
|
68
|
+
cursor: 'auto',
|
|
69
|
+
},
|
|
70
|
+
false: {
|
|
71
|
+
_web: {
|
|
72
|
+
_hover: {
|
|
73
|
+
backgroundColor: theme.color.interactive.functional.surface.subtle.hover,
|
|
74
|
+
},
|
|
75
|
+
_active: {
|
|
76
|
+
backgroundColor: theme.color.interactive.functional.surface.subtle.active,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
iconPosition: {
|
|
82
|
+
left: {
|
|
83
|
+
flexDirection: 'row',
|
|
84
|
+
},
|
|
85
|
+
right: {
|
|
86
|
+
flexDirection: 'row-reverse',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
colorScheme: {
|
|
90
|
+
functional: {},
|
|
91
|
+
destructive: {},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
compoundVariants: [
|
|
95
|
+
{
|
|
96
|
+
colorScheme: 'destructive',
|
|
97
|
+
active: true,
|
|
98
|
+
styles: {
|
|
99
|
+
backgroundColor: theme.color.interactive.destructive.surface.subtle.active,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
colorScheme: 'destructive',
|
|
104
|
+
disabled: false,
|
|
105
|
+
styles: {
|
|
106
|
+
_web: {
|
|
107
|
+
_hover: {
|
|
108
|
+
backgroundColor: theme.color.interactive.destructive.surface.subtle.hover,
|
|
109
|
+
},
|
|
110
|
+
_active: {
|
|
111
|
+
backgroundColor: theme.color.interactive.destructive.surface.subtle.active,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
text: {
|
|
119
|
+
flex: 1,
|
|
120
|
+
variants: {
|
|
121
|
+
colorScheme: {
|
|
122
|
+
functional: {
|
|
123
|
+
color: theme.color.text.primary,
|
|
124
|
+
},
|
|
125
|
+
destructive: {
|
|
126
|
+
color: theme.color.interactive.destructive.foreground.subtle,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
icon: {
|
|
132
|
+
variants: {
|
|
133
|
+
colorScheme: {
|
|
134
|
+
functional: {
|
|
135
|
+
color: theme.color.icon.primary,
|
|
136
|
+
},
|
|
137
|
+
destructive: {
|
|
138
|
+
color: theme.color.interactive.destructive.foreground.subtle,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
export default MenuItem;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface MenuTriggerProps {
|
|
4
|
+
/**
|
|
5
|
+
* The child element that triggers the menu (should be a single pressable element like Button)
|
|
6
|
+
*/
|
|
7
|
+
children: ReactElement;
|
|
8
|
+
/**
|
|
9
|
+
* Called when the trigger is pressed.
|
|
10
|
+
*/
|
|
11
|
+
onPress?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default MenuTriggerProps;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cloneElement, isValidElement } from 'react';
|
|
2
|
+
import type MenuTriggerProps from './MenuTrigger.props';
|
|
3
|
+
|
|
4
|
+
interface MenuTriggerInternalProps extends MenuTriggerProps {
|
|
5
|
+
onPress: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const MenuTrigger = ({ children, onPress }: MenuTriggerInternalProps) => {
|
|
9
|
+
if (!isValidElement(children)) {
|
|
10
|
+
throw new Error('MenuTrigger: children must be a valid React element');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return cloneElement(children, {
|
|
14
|
+
onPress,
|
|
15
|
+
} as { onPress?: () => void });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
MenuTrigger.displayName = 'MenuTrigger';
|
|
19
|
+
|
|
20
|
+
export default MenuTrigger;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as Menu } from './Menu';
|
|
2
|
+
export { useMenuContext } from './Menu.context';
|
|
3
|
+
export type { MenuMethods, default as MenuProps } from './Menu.props';
|
|
4
|
+
export { default as MenuItem } from './MenuItem';
|
|
5
|
+
export type { default as MenuItemProps } from './MenuItem.props';
|
|
6
|
+
export { default as MenuTrigger } from './MenuTrigger';
|
|
7
|
+
export type { default as MenuTriggerProps } from './MenuTrigger.props';
|
|
@@ -25,11 +25,12 @@ The Modal component is ideal for displaying important information, collecting us
|
|
|
25
25
|
- [Examples](#examples)
|
|
26
26
|
- [Basic Modal](#basic-modal)
|
|
27
27
|
- [Modal with Image](#modal-with-image)
|
|
28
|
+
- [Fullscreen Modal](#fullscreen-modal)
|
|
28
29
|
- [Modal with Custom Content](#modal-with-custom-content)
|
|
29
30
|
- [Loading State](#loading-state)
|
|
30
31
|
- [Without Close Button](#without-close-button)
|
|
31
32
|
- [Single Action Modal](#single-action-modal)
|
|
32
|
-
- [
|
|
33
|
+
- [Modal in Navigation Modal](#modal-in-navigation-modal)
|
|
33
34
|
- [Integration Notes](#integration-notes)
|
|
34
35
|
- [External Resources](#external-resources)
|
|
35
36
|
|
|
@@ -104,7 +105,8 @@ The Modal component extends the `BottomSheetModal` component and accepts all of
|
|
|
104
105
|
| `primaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the primary button (colorScheme defaults to 'highlight', variant to 'solid') | - |
|
|
105
106
|
| `secondaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the secondary button (colorScheme defaults to 'functional', variant to 'outline') | - |
|
|
106
107
|
| `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Additional props to pass to the close button | - |
|
|
107
|
-
| `fullscreen` | `boolean` | Whether the modal should take up the full screen height
|
|
108
|
+
| `fullscreen` | `boolean` | Whether the modal should take up the full screen height | `false` |
|
|
109
|
+
| `inNavModal` | `boolean` | Renders the modal correctly when used inside a navigation modal | `false` |
|
|
108
110
|
|
|
109
111
|
## Features
|
|
110
112
|
|
|
@@ -292,6 +294,33 @@ const ImageModal = () => {
|
|
|
292
294
|
};
|
|
293
295
|
```
|
|
294
296
|
|
|
297
|
+
### Fullscreen Modal
|
|
298
|
+
|
|
299
|
+
Create a modal that takes up the full screen height:
|
|
300
|
+
|
|
301
|
+
<Canvas of={Stories.FullscreenModal} />
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
const FullscreenModal = () => {
|
|
305
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<>
|
|
309
|
+
<Button onPress={() => modalRef.current?.present()}>Show Fullscreen Modal</Button>
|
|
310
|
+
|
|
311
|
+
<Modal
|
|
312
|
+
ref={modalRef}
|
|
313
|
+
heading="Fullscreen Modal"
|
|
314
|
+
description="This modal takes up the full screen height"
|
|
315
|
+
primaryButtonText="Close"
|
|
316
|
+
onPressPrimaryButton={() => modalRef.current?.dismiss()}
|
|
317
|
+
fullscreen
|
|
318
|
+
/>
|
|
319
|
+
</>
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
295
324
|
### Modal with Custom Content
|
|
296
325
|
|
|
297
326
|
Add custom content between the header and footer sections:
|
|
@@ -414,9 +443,9 @@ const AlertModal = () => {
|
|
|
414
443
|
};
|
|
415
444
|
```
|
|
416
445
|
|
|
417
|
-
###
|
|
446
|
+
### Modal In Navigation Modal
|
|
418
447
|
|
|
419
|
-
When using the Modal component in a navigation context, you can set it to
|
|
448
|
+
When using the Modal component in a navigation context, you can set it to `inNavModal` mode, this will make it behave like a standard modal screen.
|
|
420
449
|
Here's an example of how to implement this with custom close animations for Android:
|
|
421
450
|
|
|
422
451
|
```tsx
|
|
@@ -465,7 +494,7 @@ export default function ModalScreen() {
|
|
|
465
494
|
return (
|
|
466
495
|
<Modal
|
|
467
496
|
ref={modalRef}
|
|
468
|
-
|
|
497
|
+
inNavModal
|
|
469
498
|
onPressCloseButton={handleClose}
|
|
470
499
|
primaryButtonText="Action"
|
|
471
500
|
onPressPrimaryButton={handleClose}
|
|
@@ -9,6 +9,7 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
|
|
|
9
9
|
showCloseButton?: boolean;
|
|
10
10
|
heading?: string;
|
|
11
11
|
description?: string;
|
|
12
|
+
inNavModal?: boolean;
|
|
12
13
|
fullscreen?: boolean;
|
|
13
14
|
children?: ViewProps['children'];
|
|
14
15
|
onPressPrimaryButton?: () => void;
|