React drawer component with slide-out navigation panels, animations, and flexible positioning
npx @tessl/cli install tessl/npm-rc-drawer@8.0.0RC-Drawer is a comprehensive React component for creating slide-out navigation panels and sidebar interfaces. It provides smooth animations, flexible positioning, keyboard support, focus management, and accessibility features. The component supports four placement positions, nested drawer management with push/pull behavior, and extensive customization options for modern React applications.
npm install rc-drawerimport Drawer from "rc-drawer";
import type { DrawerProps } from "rc-drawer";For CommonJS:
const Drawer = require("rc-drawer");import React, { useState } from "react";
import Drawer from "rc-drawer";
function App() {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open Drawer</button>
<Drawer
open={open}
onClose={() => setOpen(false)}
placement="right"
width={300}
>
<h2>Drawer Content</h2>
<p>This is the drawer content</p>
</Drawer>
</div>
);
}RC-Drawer is built with several key architectural components:
@rc-component/portal for rendering outside the React component treerc-motion for smooth slide animations and transitionsThe primary React component for creating drawer interfaces with comprehensive configuration options.
/**
* Main drawer component that renders a slide-out panel with backdrop
*/
declare const Drawer: React.FC<DrawerProps>;
interface DrawerProps extends
Omit<DrawerPopupProps, 'prefixCls' | 'inline'>,
DrawerPanelEvents,
DrawerPanelAccessibility {
/** CSS class prefix (default: 'rc-drawer') */
prefixCls?: string;
/** Controls drawer visibility */
open?: boolean;
/** Close event handler */
onClose?: (e: React.MouseEvent | React.KeyboardEvent) => void;
/** Whether to destroy drawer content when closed */
destroyOnClose?: boolean;
/** Portal container selector/element */
getContainer?: PortalProps['getContainer'];
/** Reference to drawer panel element */
panelRef?: React.Ref<HTMLDivElement>;
/** Custom class names for different parts */
classNames?: DrawerClassNames;
/** Custom styles for different parts */
styles?: DrawerStyles;
}Control where the drawer appears and how it animates into view.
type Placement = 'left' | 'top' | 'right' | 'bottom';
interface DrawerPopupProps {
/** Drawer placement position (default: 'right') */
placement?: Placement;
/** Drawer width for left/right placement or height for top/bottom */
width?: number | string;
/** Drawer height for top/bottom placement */
height?: number | string;
/** Custom motion configuration or function returning motion config */
motion?: CSSMotionProps | ((placement: Placement) => CSSMotionProps);
/** z-index for drawer stacking */
zIndex?: number;
}Usage Example:
// Left sidebar drawer
<Drawer placement="left" width={250} open={leftOpen}>
<Navigation />
</Drawer>
// Top notification drawer
<Drawer placement="top" height={100} open={notificationOpen}>
<Notifications />
</Drawer>
// Custom animation
<Drawer
placement="right"
motion={(placement) => ({
motionName: `slide-${placement}`,
duration: 300
})}
open={customOpen}
>
<Content />
</Drawer>Configure the backdrop overlay and its behavior.
interface DrawerPopupProps {
/** Whether to show backdrop mask (default: true) */
mask?: boolean;
/** Whether clicking mask closes drawer (default: true) */
maskClosable?: boolean;
/** Custom mask CSS class name */
maskClassName?: string;
/** Custom mask inline styles */
maskStyle?: React.CSSProperties;
/** Custom mask animation configuration */
maskMotion?: CSSMotionProps;
}Usage Example:
// Drawer without backdrop
<Drawer mask={false} open={open}>
<Content />
</Drawer>
// Drawer with custom mask styling
<Drawer
maskStyle={{ backgroundColor: 'rgba(0, 0, 0, 0.8)' }}
maskClassName="custom-mask"
open={open}
>
<Content />
</Drawer>Support for multiple drawers with push/pull behavior.
interface PushConfig {
/** Push distance for nested drawers */
distance?: number | string;
}
interface DrawerPopupProps {
/** Push configuration for nested drawers (default: true with 180px distance) */
push?: boolean | PushConfig;
}
interface DrawerContextProps {
/** Current push distance */
pushDistance?: number | string;
/** Function to push parent content */
push: VoidFunction;
/** Function to pull parent content back */
pull: VoidFunction;
}Usage Example:
// Nested drawers with custom push distance
<Drawer open={level1Open} push={{ distance: 200 }}>
<div>
<h2>Level 1 Drawer</h2>
<button onClick={() => setLevel2Open(true)}>Open Level 2</button>
<Drawer open={level2Open} push={{ distance: 150 }}>
<h3>Level 2 Drawer</h3>
</Drawer>
</div>
</Drawer>
// Disable push behavior
<Drawer push={false} open={open}>
<Content />
</Drawer>Comprehensive event handling for user interactions.
interface DrawerPanelEvents {
/** Mouse enter event on drawer panel */
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
/** Mouse over event on drawer panel */
onMouseOver?: React.MouseEventHandler<HTMLDivElement>;
/** Mouse leave event on drawer panel */
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
/** Click event on drawer panel */
onClick?: React.MouseEventHandler<HTMLDivElement>;
/** Key down event on drawer panel */
onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
/** Key up event on drawer panel */
onKeyUp?: React.KeyboardEventHandler<HTMLDivElement>;
}
interface DrawerProps {
/** Called after drawer open/close animation completes */
afterOpenChange?: (open: boolean) => void;
/** Called when drawer should be closed (ESC key, mask click, etc.) */
onClose?: (e: React.MouseEvent | React.KeyboardEvent) => void;
}Built-in accessibility features for keyboard navigation and focus management.
interface DrawerPopupProps {
/** Whether to auto-focus drawer when opened (default: true) */
autoFocus?: boolean;
/** Whether to enable keyboard support like ESC to close (default: true) */
keyboard?: boolean;
}Usage Example:
// Drawer with custom keyboard handling
<Drawer
keyboard={false} // Disable built-in ESC handling
autoFocus={false} // Don't auto-focus
onKeyDown={(e) => {
if (e.key === 'Escape' && customCondition) {
handleCustomClose();
}
}}
open={open}
>
<Content />
</Drawer>Full ARIA attributes support for screen readers and assistive technologies.
type DrawerPanelAccessibility = Pick<
React.DialogHTMLAttributes<HTMLDivElement>,
keyof React.AriaAttributes
>;
interface DrawerProps extends DrawerPanelAccessibility {
// All ARIA attributes are supported
'aria-labelledby'?: string;
'aria-describedby'?: string;
'aria-label'?: string;
// ... all other ARIA attributes
}Usage Example:
<Drawer
open={open}
aria-labelledby="drawer-title"
aria-describedby="drawer-description"
>
<h2 id="drawer-title">Settings</h2>
<p id="drawer-description">Configure your application settings</p>
<SettingsForm />
</Drawer>Comprehensive styling customization with CSS classes and inline styles.
interface DrawerClassNames {
/** Class name for mask overlay */
mask?: string;
/** Class name for content wrapper */
wrapper?: string;
/** Class name for content section */
section?: string;
}
interface DrawerStyles {
/** Styles for mask overlay */
mask?: React.CSSProperties;
/** Styles for content wrapper */
wrapper?: React.CSSProperties;
/** Styles for content section */
section?: React.CSSProperties;
}
interface DrawerPopupProps {
/** Root container CSS class */
rootClassName?: string;
/** Root container inline styles */
rootStyle?: React.CSSProperties;
/** Drawer content CSS class */
className?: string;
/** Drawer content inline styles */
style?: React.CSSProperties;
/** Element ID for the drawer */
id?: string;
/** Custom render function for drawer content */
drawerRender?: (node: React.ReactNode) => React.ReactNode;
}Usage Example:
<Drawer
classNames={{
mask: 'custom-mask-class',
wrapper: 'custom-wrapper-class',
section: 'custom-section-class'
}}
styles={{
mask: { backgroundColor: 'rgba(0, 0, 0, 0.5)' },
wrapper: { boxShadow: '0 4px 20px rgba(0, 0, 0, 0.15)' },
section: { padding: '24px' }
}}
rootClassName="drawer-container"
className="drawer-content"
drawerRender={(content) => (
<div className="custom-drawer-wrapper">
{content}
</div>
)}
open={open}
>
<Content />
</Drawer>Control when and how the drawer is rendered in the DOM.
interface DrawerPopupProps {
/** Force render drawer even when closed */
forceRender?: boolean;
/** Whether drawer is rendered inline (no portal) */
inline?: boolean;
}
interface DrawerProps {
/** Whether to destroy drawer content when closed */
destroyOnClose?: boolean;
/** Portal container for drawer rendering */
getContainer?: PortalProps['getContainer'];
}Usage Example:
// Always keep drawer in DOM
<Drawer forceRender open={open}>
<ExpensiveComponent />
</Drawer>
// Render inline (no portal)
<div className="drawer-container">
<Drawer getContainer={false} open={open}>
<Content />
</Drawer>
</div>
// Custom container
<Drawer getContainer={() => document.getElementById('drawer-root')}>
<Content />
</Drawer>The Drawer component comes with sensible defaults:
prefixCls: 'rc-drawer'placement: 'right'autoFocus: truekeyboard: truewidth: 378mask: truemaskClosable: truepushDistance: 180 (for nested drawers)The component includes built-in validation and warnings:
RC-Drawer is built with TypeScript and provides complete type definitions:
// Main component export
export default Drawer;
// Type exports
export type { DrawerProps };Note: Internal types like Placement, DrawerClassNames, DrawerStyles, and PushConfig are part of the DrawerProps interface but not directly exported as standalone types. Access them through the main DrawerProps type or define them inline in your TypeScript code when needed.