Drawer
The Drawer component is a sliding panel that appears from the edge of the screen. It's commonly used for navigation menus, filters, or additional content that complements the main view. The component is fully theme-aware and supports light/dark modes through the ThemeProvider.
Import
import { Drawer } from '@lazy-panda-ui/lazy-panda-ui';
Usage
import React, { useState } from 'react';
import { Drawer, Button, Typography } from '@lazy-panda-ui/lazy-panda-ui';
function MyComponent() {
const [open, setOpen] = useState(false);
return (
<>
<Button onPress={() => setOpen(true)}>
Open Drawer
</Button>
<Drawer open={open} onClose={() => setOpen(false)}>
<Drawer.Header>Drawer Title</Drawer.Header>
<Drawer.Content>
<Typography>This is the drawer content.</Typography>
</Drawer.Content>
<Drawer.Footer>
<Button onPress={() => setOpen(false)}>
Close
</Button>
</Drawer.Footer>
</Drawer>
</>
);
}
Props
Drawer Props
Name | Type | Default | Description |
---|---|---|---|
open | boolean | - | Whether the drawer is open/visible |
onClose | () => void | - | Callback function called when the drawer should be closed |
placement | 'left' | 'right' | 'top' | 'bottom' | 'right' | The side of the screen from which the drawer slides in |
size | 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'md' | The size of the drawer as a percentage of screen dimension |
closeOnOverlayClick | boolean | true | Whether clicking the overlay should close the drawer |
closeOnEscape | boolean | true | Whether pressing the back button (Android) should close the drawer |
trapFocus | boolean | - | Whether focus should be trapped within the drawer (for accessibility) |
returnFocus | boolean | - | Whether focus should return to the previous element when drawer closes |
overlayColor | string | theme value | Overlay background color (defaults to theme.drawer.backdrop) |
children | React.ReactNode | - | The content to be rendered inside the drawer |
style | ViewStyle | - | Additional styles for the drawer container |
Drawer.Header Props
Name | Type | Default | Description |
---|---|---|---|
children | React.ReactNode | - | The content to be rendered in the header. Can be a string or JSX |
showCloseButton | boolean | true | Whether to show the close button in the header |
closeButtonProps | any | - | Props to pass to the close button component |
style | ViewStyle | - | Additional styles for the header container |
Drawer.Content Props
Name | Type | Default | Description |
---|---|---|---|
children | React.ReactNode | - | The main content to be rendered in the drawer body |
style | ViewStyle | - | Additional styles for the content container |
Drawer.Footer Props
Name | Type | Default | Description |
---|---|---|---|
children | React.ReactNode | - | The content to be rendered in the footer (typically buttons) |
style | ViewStyle | - | Additional styles for the footer container |
Examples
Basic Drawer
<Drawer open={open} onClose={onClose}>
<Drawer.Header>Navigation</Drawer.Header>
<Drawer.Content>
<Stack spacing="md">
<Button variant="ghost" onPress={() => navigate('/')}>Home</Button>
<Button variant="ghost" onPress={() => navigate('/profile')}>Profile</Button>
<Button variant="ghost" onPress={() => navigate('/settings')}>Settings</Button>
</Stack>
</Drawer.Content>
</Drawer>
Different Placements
{['left', 'right', 'top', 'bottom'].map((placement) => (
<Button
key={placement}
onPress={() => openDrawer(placement)}
>
Open {placement} drawer
</Button>
))}
<Drawer
open={open}
onClose={onClose}
placement={placement}
>
<Drawer.Header>
{placement} Drawer
</Drawer.Header>
<Drawer.Content>
<Typography>Content for {placement} drawer</Typography>
</Drawer.Content>
</Drawer>
Different Sizes
<Drawer
open={open}
onClose={onClose}
size={size} // 'sm', 'md', 'lg', 'xl', 'full'
>
<Drawer.Header>
{size} Drawer
</Drawer.Header>
<Drawer.Content>
<Typography>Content for {size} drawer</Typography>
</Drawer.Content>
</Drawer>
Animation and Easing
You can control the animation duration and easing globally via the theme. The overlay fade uses the native driver for smoothness, and the slide uses the configured easing.
const customTheme = {
...defaultTheme,
drawer: {
...defaultTheme.drawer,
animation: {
duration: 400,
easing: 'easeOut', // 'linear' | 'easeIn' | 'easeOut' | 'easeInOut'
},
},
};
<ThemeProvider theme={customTheme}>
<Drawer open={open} onClose={onClose} />
{/* ... */}
</ThemeProvider>
Navigation Drawer
<Drawer open={open} onClose={onClose} placement="left">
<Drawer.Header>Menu</Drawer.Header>
<Drawer.Content>
<Stack spacing="md">
<Button
variant="ghost"
onPress={() => navigate('/')}
>
🏠 Home
</Button>
<Button
variant="ghost"
onPress={() => navigate('/profile')}
>
👤 Profile
</Button>
<Button
variant="ghost"
onPress={() => navigate('/settings')}
>
⚙️ Settings
</Button>
<Divider />
<Button
variant="ghost"
onPress={handleLogout}
>
🚪 Logout
</Button>
</Stack>
</Drawer.Content>
</Drawer>
Filter Drawer
<Drawer open={open} onClose={onClose} placement="right">
<Drawer.Header>Filters</Drawer.Header>
<Drawer.Content>
<Stack spacing="lg">
<Select
label="Category"
value={category}
onValueChange={setCategory}
items={categories}
/>
<Slider
label="Price Range"
value={priceRange}
onValueChange={setPriceRange}
minimumValue={0}
maximumValue={1000}
/>
<Stack spacing="sm">
<Typography variant="h6">Brands</Typography>
{availableBrands.map(brand => (
<CheckBox
key={brand}
label={brand}
value={brands.includes(brand)}
onValueChange={(checked) => toggleBrand(brand, checked)}
/>
))}
</Stack>
</Stack>
</Drawer.Content>
<Drawer.Footer>
<Stack direction="row" spacing="sm">
<Button variant="outlined" onPress={resetFilters}>
Reset
</Button>
<Button onPress={applyFilters}>
Apply Filters
</Button>
</Stack>
</Drawer.Footer>
</Drawer>
Theme Customization
The Drawer component is fully configurable through the ThemeProvider. All visual aspects can be customized through the theme configuration.
Theme Configuration
const customTheme = {
...defaultTheme,
drawer: {
backdrop: {
color: '#000000', // Overlay color
opacity: 0.6, // Overlay opacity
},
container: {
backgroundColor: '#ffffff', // Drawer background
borderRadius: 8, // Border radius for corners
shadow: {
color: '#000000',
offset: { width: 0, height: 4 },
opacity: 0.3,
radius: 6,
elevation: 8,
},
},
header: {
backgroundColor: '#f8f9fa', // Header background
borderColor: '#e9ecef', // Header border
borderWidth: 1,
padding: 20,
minHeight: 64,
},
content: {
backgroundColor: '#ffffff', // Content background
padding: 24,
},
footer: {
backgroundColor: '#f8f9fa', // Footer background
borderColor: '#e9ecef', // Footer border
borderWidth: 1,
padding: 20,
minHeight: 64,
},
animation: {
duration: 400, // Animation duration in ms
easing: 'easeInOut', // Animation easing ('linear' | 'easeIn' | 'easeOut' | 'easeInOut')
},
sizes: {
sm: 0.3, // 30% of screen dimension
md: 0.5, // 50% of screen dimension
lg: 0.7, // 70% of screen dimension
xl: 0.9, // 90% of screen dimension
},
},
};
// Use with ThemeProvider
<ThemeProvider theme={customTheme}>
<Drawer open={open} onClose={onClose}>
{/* Drawer content */}
</Drawer>
</ThemeProvider>
Custom Styles
You can also override styles directly using the style props:
<Drawer
open={open}
onClose={onClose}
style={{
backgroundColor: '#f8f9fa',
borderRadius: 16,
}}
overlayColor="rgba(0, 0, 0, 0.8)"
>
<Drawer.Header
style={{
backgroundColor: '#007AFF',
borderBottomWidth: 0,
}}
>
<Typography variant="h6" style={{ color: 'white' }}>
Custom Header
</Typography>
</Drawer.Header>
<Drawer.Content style={{ padding: 32 }}>
<Typography>Custom styled content</Typography>
</Drawer.Content>
<Drawer.Footer style={{ backgroundColor: '#f0f0f0' }}>
<Button>Custom Footer</Button>
</Drawer.Footer>
</Drawer>
Best Practices
- Choose appropriate placement: Use
left
for navigation menus,right
for filters/settings,bottom
for mobile sheets - Consider content hierarchy: Structure content with Header, Content, and Footer for consistency
- Implement proper navigation: Ensure drawer actions don't conflict with main app navigation
- Handle mobile views: Use appropriate sizes and consider touch targets
- Maintain focus management: Let the component handle focus automatically with
trapFocus
- Provide clear closing options: Always include a way to close the drawer (close button, overlay click, back button)
- Use consistent patterns: Follow the same drawer structure across your app
- Theme integration: Use ThemeProvider for consistent styling across light/dark modes
Size Reference
The drawer sizes are calculated as percentages of the screen dimension:
const drawerSizes = {
sm: '25%', // Small drawer
md: '40%', // Medium drawer (default)
lg: '60%', // Large drawer
xl: '80%', // Extra large drawer
full: '100%' // Full screen
};
For horizontal drawers (left/right), percentages apply to screen width. For vertical drawers (top/bottom), percentages apply to screen height.
Accessibility
The Drawer component includes built-in accessibility features:
React Native Accessibility
<Drawer
open={open}
onClose={onClose}
trapFocus={true} // Traps focus within drawer
returnFocus={true} // Returns focus when closed
>
<Drawer.Header>
<Typography variant="h6" accessibilityRole="heading">
Navigation Menu
</Typography>
</Drawer.Header>
<Drawer.Content>
<Button
accessibilityLabel="Go to home page"
accessibilityHint="Navigates to the main home screen"
>
Home
</Button>
</Drawer.Content>
</Drawer>
Note: Drawer.Header includes a close button by default (when showCloseButton
is true) that calls onClose
from Drawer automatically. You can override its behavior via closeButtonProps.onPress
.
Features
- Modal behavior: Uses React Native Modal for proper overlay handling
- Back button support: Android back button automatically closes drawer
- Touch overlay: Tapping outside the drawer closes it (configurable)
- Focus management: Optional focus trapping and return
- Screen reader support: Works with accessibility services
Animation Details
The Drawer uses React Native's Animated API for smooth transitions:
- Slide animation: Drawer slides in from the specified edge
- Fade animation: Overlay fades in/out simultaneously
- Duration: Configurable through theme (default: 300ms)
- Easing: Theme-configurable easing curves ('linear' | 'easeIn' | 'easeOut' | 'easeInOut')
- Performance: Uses native driver where possible for smooth animations
Platform Differences
Android
- Back button automatically closes drawer (configurable with
closeOnEscape
) - Hardware back button handling
- Material Design elevation shadows
iOS
- iOS-style shadows and styling when using iOS theme
- Respects safe area insets
- Gesture-based navigation compatibility