Drawer
The Drawer component provides a slide-in panel for navigation, forms, or supplementary content, appearing from the edge of the screen.
Installation
$ cbui-cli install @crossbuildui/drawer
cbui-cli
globally and authenticated your account. This will ensure you can access all the necessary features.Import
1import { Drawer } from '@/crossbuildui/drawer';
2import type { DrawerPlacement, DrawerSize, DrawerRadius, DrawerBackdrop, DrawerAnimationConfig } from '@crossbuildui/drawer'; // Optional: for type safety
3import { Button } from '@/crossbuildui/button'; // Or your preferred button component
expo-blur
for the blur
backdrop effect. Ensure it's installed if you plan to use this feature:npm install expo-blur
or yarn add expo-blur
It also relies on
react-native-reanimated
for animations. Make sure it's installed and configured.Drawer Component
Basic Usage
Control the Drawer's visibility using the isOpen
and onOpenChange
props. Provide content as children.
1function App() {
2 const [isOpen, setIsOpen] = useState(false);
3
4 return (
5 <View>
6 <Button onPress={() => setIsOpen(true)}>Open Drawer</Button>
7 <Drawer
8 isOpen={isOpen}
9 onOpenChange={setIsOpen}
10 placement="left"
11 >
12 <View style={{ padding: 20, flex: 1 }}>
13 <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>Drawer Content</Text>
14 <Text>This is the content of the drawer.</Text>
15 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 20 }}>
16 Close Drawer
17 </Button>
18 </View>
19 </Drawer>
20 </View>
21 );
22}
Placement
The placement
prop determines which edge the drawer slides in from: left
(default), right
, top
, or bottom
.
1function App() {
2 const [isOpen, setIsOpen] = useState(false);
3 const [placement, setPlacement] = useState<DrawerPlacement>('left');
4
5 const openDrawer = (p: DrawerPlacement) => {
6 setPlacement(p);
7 setIsOpen(true);
8 };
9
10 return (
11 <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}>
12 <Button onPress={() => openDrawer('left')}>Open Left</Button>
13 <Button onPress={() => openDrawer('right')}>Open Right</Button>
14 <Button onPress={() => openDrawer('top')}>Open Top</Button>
15 <Button onPress={() => openDrawer('bottom')}>Open Bottom</Button>
16
17 <Drawer isOpen={isOpen} onOpenChange={setIsOpen} placement={placement}>
18 <View style={{ padding: 20, flex: 1, alignItems: 'center', justifyContent: 'center' }}>
19 <Text>Drawer from {placement}</Text>
20 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 10 }}>Close</Button>
21 </View>
22 </Drawer>
23 </View>
24 );
25}
Sizes
Adjust the drawer's size (width for left/right, height for top/bottom) using the size
prop. Options: sm
, md
(default), lg
, full
.
1function App() {
2 const [isOpen, setIsOpen] = useState(false);
3 const [size, setSize] = useState<DrawerSize>('md');
4
5 const openDrawer = (s: DrawerSize) => {
6 setSize(s);
7 setIsOpen(true);
8 };
9
10 return (
11 <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}>
12 <Button onPress={() => openDrawer('sm')}>Small</Button>
13 <Button onPress={() => openDrawer('md')}>Medium</Button>
14 <Button onPress={() => openDrawer('lg')}>Large</Button>
15 <Button onPress={() => openDrawer('full')}>Full</Button>
16
17 <Drawer isOpen={isOpen} onOpenChange={setIsOpen} size={size} placement="left">
18 <View style={{ padding: 20, flex: 1 }}>
19 <Text>Drawer size: {size}</Text>
20 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 10 }}>Close</Button>
21 </View>
22 </Drawer>
23 </View>
24 );
25}
Radius
Apply a border radius to the relevant corners of the drawer panel with the radius
prop. Options: none
, sm
, md
, lg
(default).
1function App() {
2 const [isOpen, setIsOpen] = useState(false);
3 const [radius, setRadius] = useState<DrawerRadius>('lg');
4
5 const openDrawer = (r: DrawerRadius) => {
6 setRadius(r);
7 setIsOpen(true);
8 };
9
10 return (
11 <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}>
12 <Button onPress={() => openDrawer('none')}>None</Button>
13 <Button onPress={() => openDrawer('sm')}>Small</Button>
14 <Button onPress={() => openDrawer('md')}>Medium</Button>
15 <Button onPress={() => openDrawer('lg')}>Large</Button>
16
17 <Drawer isOpen={isOpen} onOpenChange={setIsOpen} radius={radius} placement="right">
18 <View style={{ padding: 20, flex: 1 }}>
19 <Text>Drawer radius: {radius}</Text>
20 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 10 }}>Close</Button>
21 </View>
22 </Drawer>
23 </View>
24 );
25}
Backdrop
Customize the backdrop using the backdrop
prop: opaque
(default, semi-transparent black), blur
(requires expo-blur
), or transparent
.
1function App() {
2 const [isOpen, setIsOpen] = useState(false);
3 const [backdrop, setBackdrop] = useState<DrawerBackdrop>('opaque');
4
5 const openDrawer = (b: DrawerBackdrop) => {
6 setBackdrop(b);
7 setIsOpen(true);
8 };
9
10 return (
11 <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}>
12 <Button onPress={() => openDrawer('opaque')}>Opaque</Button>
13 <Button onPress={() => openDrawer('blur')}>Blur</Button>
14 <Button onPress={() => openDrawer('transparent')}>Transparent</Button>
15
16 <Drawer isOpen={isOpen} onOpenChange={setIsOpen} backdrop={backdrop}>
17 <View style={{ padding: 20, flex: 1 }}>
18 <Text>Backdrop: {backdrop}</Text>
19 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 10 }}>Close</Button>
20 </View>
21 </Drawer>
22 </View>
23 );
24}
Dismissable Behavior
By default (isDismissable=
), the drawer can be closed by pressing the backdrop or the hardware back button (Android). Set to false
to prevent this.
1function App() {
2 const [isOpen, setIsOpen] = useState(false);
3
4 return (
5 <View>
6 <Button onPress={() => setIsOpen(true)}>Open Non-Dismissable Drawer</Button>
7 <Drawer
8 isOpen={isOpen}
9 onOpenChange={setIsOpen}
10 isDismissable={false}
11 >
12 <View style={{ padding: 20, flex: 1 }}>
13 <Text>This drawer cannot be dismissed by backdrop press or back button.</Text>
14 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 20 }}>
15 Close Manually
16 </Button>
17 </View>
18 </Drawer>
19 </View>
20 );
21}
Animation Configuration
Customize the opening and closing animation using animationConfig
. Supports type: 'spring'
(default) or type: 'timing'
, with respective Reanimated configuration objects.
1function App() {
2 const [isOpenSpring, setIsOpenSpring] = useState(false);
3 const [isOpenTiming, setIsOpenTiming] = useState(false);
4
5 const springConfig: DrawerAnimationConfig = {
6 type: 'spring',
7 springConfig: { damping: 15, stiffness: 120 }
8 };
9
10 const timingConfig: DrawerAnimationConfig = {
11 type: 'timing',
12 timingConfig: { duration: 500 }
13 };
14
15 return (
16 <View style={{ flexDirection: 'row', gap: 8 }}>
17 <Button onPress={() => setIsOpenSpring(true)}>Open with Spring</Button>
18 <Button onPress={() => setIsOpenTiming(true)}>Open with Timing</Button>
19
20 <Drawer isOpen={isOpenSpring} onOpenChange={setIsOpenSpring} animationConfig={springConfig}>
21 <View style={{ padding: 20, flex: 1 }}><Text>Spring Animation</Text><Button onPress={() => setIsOpenSpring(false)}>Close</Button></View>
22 </Drawer>
23
24 <Drawer isOpen={isOpenTiming} onOpenChange={setIsOpenTiming} animationConfig={timingConfig} placement="right">
25 <View style={{ padding: 20, flex: 1 }}><Text>Timing Animation</Text><Button onPress={() => setIsOpenTiming(false)}>Close</Button></View>
26 </Drawer>
27 </View>
28 );
29}
Props Overview
PROP | TYPE | DEFAULT | DESCRIPTION |
---|---|---|---|
children | ReactNode | - | Content of the drawer panel. |
size | DrawerSize | 'md' | Size of the drawer. |
radius | DrawerRadius | 'lg' | Border radius of the panel. |
placement | DrawerPlacement | 'left' | Edge from which drawer slides. |
isOpen | boolean | - | Controlled open state. |
defaultOpen | boolean | false | Initial open state (uncontrolled). |
isDismissable | boolean | true | If dismissable by backdrop/back press. |
backdrop | DrawerBackdrop | 'opaque' | Backdrop type: transparent, opaque, blur. |
closeButton | ReactNode | - | Custom close button element (user places it). |
animationConfig | DrawerAnimationConfig | { type: 'spring' } | Animation settings (spring/timing). |
styles | DrawerSlotsStyles | - | Styles for slots (backdrop, panel). |
style | StyleProp<ViewStyle> | - | Style for the main drawer panel. |
onOpenChange | (isOpen: boolean) => void | - | Callback on open state change. |
onClose | () => void | - | Callback when drawer closes. |
Styling
Customize the Drawer's appearance using the style
prop for the main panel or the styles
prop for more granular control over internal slots.
The styles
prop accepts an object with the following keys:
backdrop
: Styles applied to the backdrop overlayView
orBlurView
.panel
: Styles applied to the main drawer panelAnimated.View
. These are merged with and can be overridden by the mainstyle
prop.
style
prop directly styles the drawer panel and will take precedence over styles defined in styles.panel
for conflicting properties.1<Drawer
2 isOpen={true} // Keep open for example
3 onOpenChange={() => {}}
4 placement="left"
5 size="md"
6 style={{ borderWidth: 2, borderColor: 'purple' }} // Styles the main panel
7 styles={{
8 backdrop: { backgroundColor: 'rgba(0, 255, 0, 0.2)' }, // Custom backdrop style
9 panel: { shadowColor: 'blue', shadowOpacity: 0.8, elevation: 10 } // Additional panel styles
10 }}
11>
12 <View style={{ padding: 20, flex: 1 }}>
13 <Text>Styled Drawer</Text>
14 </View>S
15</Drawer>
Accessibility
The Drawer component is built on top of React Native's Modal
, which handles some accessibility aspects like focus management.
- The
onRequestClose
prop of the underlyingRNModal
is connected tohandleClose
, aiding in hardware back button dismissal on Android. - Ensure any interactive elements within the drawer, including a custom close button, are properly labeled for accessibility (e.g., using
accessibilityLabel
). - The content within the drawer should be structured semantically for screen readers.
- Consider announcing the drawer's appearance or purpose to screen reader users if it's not immediately obvious from the context.