This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
MIT LICENSE
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+91
View File
@@ -0,0 +1,91 @@
# @rc-component/drawer
[![NPM version][npm-image]][npm-url]
[![npm download][download-image]][download-url]
[![build status][github-actions-image]][github-actions-url]
[![codecov][codecov-image]][codecov-url]
[![dumi][dumi-image]][dumi-url]
[npm-image]: http://img.shields.io/npm/v/@rc-component/drawer.svg?style=flat-square
[npm-url]: http://npmjs.org/package/@rc-component/drawer
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
[dumi-url]: https://github.com/umijs/dumi
[github-actions-image]: https://github.com/react-component/drawer/actions/workflows/react-component-ci.yml/badge.svg
[github-actions-url]: https://github.com/react-component/drawer/actions/workflows/react-component-ci.yml
[download-image]: https://img.shields.io/npm/dm/@rc-component/drawer.svg?style=flat-square
[download-url]: https://npmjs.org/package/@rc-component/drawer
[codecov-image]: https://codecov.io/gh/react-component/drawer/branch/master/graph/badge.svg
[codecov-url]: https://codecov.io/gh/react-component/drawer
## Example
https://drawer-react-component.vercel.app/
## Usage
```js
import Drawer from '@rc-component/drawer';
import React from 'react';
import ReactDom from 'react-dom';
ReactDom.render(
<Drawer>
{menu children}
</Drawer>
, mountNode);
```
## Install
[![@rc-component/drawer](https://nodei.co/npm/@rc-component/drawer.png)](https://npmjs.org/package/@rc-component/drawer)
## Browser Support
| ![IE](https://github.com/alrra/browser-logos/blob/master/src/edge/edge_48x48.png?raw=true) | ![Chrome](https://github.com/alrra/browser-logos/blob/master/src/chrome/chrome_48x48.png?raw=true) | ![Firefox](https://github.com/alrra/browser-logos/blob/master/src/firefox/firefox_48x48.png?raw=true) | ![Opera](https://github.com/alrra/browser-logos/blob/master/src/opera/opera_48x48.png?raw=true) | ![Safari](https://github.com/alrra/browser-logos/blob/master/src/safari/safari_48x48.png?raw=true) |
| ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| IE 10+ ✔ | Chrome 31.0+ ✔ | Firefox 31.0+ ✔ | Opera 30.0+ ✔ | Safari 7.0+ ✔ |
## API
| props | type | default | description |
| ------------------ | --------------------------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------- |
| className | string | null | - |
| classNames | { mask?: string; content?: string; wrapper?: string; } | - | pass className to target area |
| styles | { mask?: CSSProperties; content?: CSSProperties; wrapper?: CSSProperties; } | `rc-drawer` | pass style to target area |
| prefixCls | string | 'drawer' | prefix class |
| width | string \| number | null | drawer content wrapper width, drawer level transition width |
| height | string \| number | null | drawer content wrapper height, drawer level transition height |
| defaultWidth | string \| number | null | default width for uncontrolled resizable drawer |
| defaultHeight | string \| number | null | default height for uncontrolled resizable drawer |
| open | boolean | false | open or close menu |
| defaultOpen | boolean | false | default open menu |
| placement | string | `left` | `left` `top` `right` `bottom` |
| level | string \| array | `all` | With the drawer level element. `all`/ null / className / id / tagName / array |
| levelMove | number \| array \| func | null | level move value. default is drawer width |
| duration | string | `.3s` | level animation duration |
| ease | string | `cubic-bezier(0.78, 0.14, 0.15, 0.86)` | level animation timing function |
| getContainer | string \| func \| HTMLElement | `body` | Return the mount node for Drawer. if is `null` use React.creactElement |
| showMask | boolean | true | mask is show |
| maskClosable | boolean | true | Clicking on the mask (area outside the Drawer) to close the Drawer or not. |
| maskStyle | CSSProperties | null | mask style |
| afterOpenChange | func | null | transition end callback(open) |
| onClose | func | null | close click function |
| keyboard | boolean | true | Whether support press esc to close |
| autoFocus | boolean | true | Whether focusing on the drawer after it opened |
| resizable | { onResize?: (size: number) => void; onResizeStart?: () => void; onResizeEnd?: () => void; } | - | Resizable configuration with optional callbacks |
| onMouseEnter | React.MouseEventHandler\<HTMLDivElement\> | - | Trigger when mouse enter drawer panel |
| onMouseOver | React.MouseEventHandler\<HTMLDivElement\> | - | Trigger when mouse over drawer panel |
| onMouseLeave | React.MouseEventHandler\<HTMLDivElement\> | - | Trigger when mouse leave drawer panel |
| onClick | React.MouseEventHandler\<HTMLDivElement\> | - | Trigger when mouse click drawer panel |
| onKeyDown | React.MouseEventHandler\<HTMLDivElement\> | - | Trigger when mouse keydown on drawer panel |
| onKeyUp | React.MouseEventHandler\<HTMLDivElement\> | - | Trigger when mouse keyup on drawer panel |
> 2.0 Rename `onMaskClick` -> `onClose`, add `maskClosable`.
## Development
```
npm install
npm start
```
+273
View File
@@ -0,0 +1,273 @@
.rc-drawer {
inset: 0;
position: fixed;
z-index: 1050;
pointer-events: none;
}
.rc-drawer-inline {
position: absolute;
}
.rc-drawer-mask {
inset: 0;
position: absolute;
z-index: 1050;
background: rgba(0, 0, 0, 0.5);
pointer-events: auto;
}
.rc-drawer-content-wrapper {
position: absolute;
z-index: 1050;
transition: transform 0.3s;
pointer-events: auto;
}
.rc-drawer-content-wrapper-hidden {
display: none;
}
.rc-drawer-left .rc-drawer-content-wrapper {
top: 0;
bottom: 0;
left: 0;
}
.rc-drawer-right .rc-drawer-content-wrapper {
top: 0;
right: 0;
bottom: 0;
}
.rc-drawer-content {
width: 100%;
height: 100%;
overflow: auto;
background: #fff;
pointer-events: auto;
}
.rc-drawer-resizable-dragger {
position: absolute;
z-index: 2;
pointer-events: auto;
}
.rc-drawer-resizable-dragger:hover {
background-color: #1890ff !important;
}
.rc-drawer-resizable-dragger-dragging {
background-color: #1890ff !important;
}
.rc-drawer-resizable-dragger-left {
top: 0;
bottom: 0;
right: -3px;
width: 6px;
cursor: ew-resize;
}
.rc-drawer-resizable-dragger-right {
top: 0;
bottom: 0;
left: -3px;
width: 6px;
cursor: ew-resize;
}
.rc-drawer-resizable-dragger-top {
left: 0;
right: 0;
bottom: -3px;
height: 6px;
cursor: ns-resize;
}
.rc-drawer-resizable-dragger-bottom {
left: 0;
right: 0;
top: -3px;
height: 6px;
cursor: ns-resize;
}
.rc-drawer {
overflow: hidden;
z-index: 9999;
transition: width 0s ease 0.3s, height 0s ease 0.3s, transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
}
.rc-drawer > * {
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), opacity 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), box-shadow 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
}
.rc-drawer.rc-drawer-open {
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
}
.rc-drawer .rc-drawer-mask {
background: #000;
opacity: 0;
width: 100%;
height: 0;
position: absolute;
top: 0;
left: 0;
transition: opacity 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), height 0s ease 0.3s;
}
.rc-drawer-content-wrapper {
position: absolute;
background: #fff;
}
.rc-drawer-content {
overflow: auto;
z-index: 1;
position: relative;
}
.rc-drawer-handle {
position: absolute;
top: 72px;
width: 41px;
height: 40px;
cursor: pointer;
z-index: 0;
text-align: center;
line-height: 40px;
font-size: 16px;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
}
.rc-drawer-handle-icon {
width: 14px;
height: 2px;
background: #333;
position: relative;
transition: background 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
}
.rc-drawer-handle-icon::before,
.rc-drawer-handle-icon::after {
content: '';
display: block;
position: absolute;
background: #333;
width: 100%;
height: 2px;
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
}
.rc-drawer-handle-icon::before {
top: -5px;
}
.rc-drawer-handle-icon::after {
top: 5px;
}
.rc-drawer-left,
.rc-drawer-right {
width: 0%;
height: 100%;
}
.rc-drawer-left .rc-drawer-content-wrapper,
.rc-drawer-right .rc-drawer-content-wrapper,
.rc-drawer-left .rc-drawer-content,
.rc-drawer-right .rc-drawer-content {
height: 100%;
}
.rc-drawer-left.rc-drawer-open,
.rc-drawer-right.rc-drawer-open {
width: 100%;
}
.rc-drawer-left.rc-drawer-open.no-mask,
.rc-drawer-right.rc-drawer-open.no-mask {
width: 0%;
}
.rc-drawer-left {
top: 0;
left: 0;
}
.rc-drawer-left .rc-drawer-handle {
right: -40px;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
border-radius: 0 4px 4px 0;
}
.rc-drawer-left.rc-drawer-open .rc-drawer-content-wrapper {
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
}
.rc-drawer-right {
top: 0;
right: 0;
}
.rc-drawer-right .rc-drawer-content-wrapper {
right: 0;
}
.rc-drawer-right .rc-drawer-handle {
left: -40px;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
border-radius: 4px 0 0 4px;
}
.rc-drawer-right.rc-drawer-open .rc-drawer-content-wrapper {
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
}
.rc-drawer-right.rc-drawer-open.no-mask {
right: 1px;
transform: translateX(1px);
}
.rc-drawer-top,
.rc-drawer-bottom {
width: 100%;
height: 0%;
}
.rc-drawer-top .rc-drawer-content-wrapper,
.rc-drawer-bottom .rc-drawer-content-wrapper,
.rc-drawer-top .rc-drawer-content,
.rc-drawer-bottom .rc-drawer-content {
width: 100%;
}
.rc-drawer-top .rc-drawer-content,
.rc-drawer-bottom .rc-drawer-content {
height: 100%;
}
.rc-drawer-top.rc-drawer-open,
.rc-drawer-bottom.rc-drawer-open {
height: 100%;
}
.rc-drawer-top.rc-drawer-open.no-mask,
.rc-drawer-bottom.rc-drawer-open.no-mask {
height: 0%;
}
.rc-drawer-top .rc-drawer-handle,
.rc-drawer-bottom .rc-drawer-handle {
left: 50%;
margin-left: -20px;
}
.rc-drawer-top {
top: 0;
left: 0;
}
.rc-drawer-top .rc-drawer-handle {
top: auto;
bottom: -40px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
border-radius: 0 0 4px 4px;
}
.rc-drawer-top.rc-drawer-open .rc-drawer-content-wrapper {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.rc-drawer-bottom {
bottom: 0;
left: 0;
}
.rc-drawer-bottom .rc-drawer-content-wrapper {
bottom: 0;
}
.rc-drawer-bottom .rc-drawer-handle {
top: -40px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);
border-radius: 4px 4px 0 0;
}
.rc-drawer-bottom.rc-drawer-open .rc-drawer-content-wrapper {
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);
}
.rc-drawer-bottom.rc-drawer-open.no-mask {
bottom: 1px;
transform: translateY(1px);
}
.rc-drawer.rc-drawer-open .rc-drawer-mask {
opacity: 0.3;
height: 100%;
transition: opacity 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
}
.rc-drawer.rc-drawer-open .rc-drawer-handle-icon {
background: transparent;
}
.rc-drawer.rc-drawer-open .rc-drawer-handle-icon::before {
transform: translateY(5px) rotate(45deg);
}
.rc-drawer.rc-drawer-open .rc-drawer-handle-icon::after {
transform: translateY(-5px) rotate(-45deg);
}
+39
View File
@@ -0,0 +1,39 @@
import type { PortalProps } from '@rc-component/portal';
import * as React from 'react';
import type { DrawerPanelAccessibility, DrawerPanelEvents } from './DrawerPanel';
import type { DrawerPopupProps } from './DrawerPopup';
import type { DrawerClassNames, DrawerStyles } from './inter';
export type Placement = 'left' | 'top' | 'right' | 'bottom';
export interface DrawerProps extends Omit<DrawerPopupProps, 'prefixCls' | 'inline' | 'scrollLocker'>, DrawerPanelEvents, DrawerPanelAccessibility {
prefixCls?: string;
open?: boolean;
onClose?: (e: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => void;
destroyOnHidden?: boolean;
getContainer?: PortalProps['getContainer'];
panelRef?: React.Ref<HTMLDivElement>;
classNames?: DrawerClassNames;
styles?: DrawerStyles;
/**
* @deprecated Use `size` instead. Will be removed in next major version.
*/
width?: number | string;
/**
* @deprecated Use `size` instead. Will be removed in next major version.
*/
height?: number | string;
/** Size of the drawer (width for left/right placement, height for top/bottom placement) */
size?: number | string;
/** Maximum size of the drawer */
maxSize?: number;
/** Default size for uncontrolled resizable drawer */
defaultSize?: number | string;
/** Resizable configuration - boolean to enable/disable or object with optional callbacks */
resizable?: boolean | {
onResize?: (size: number) => void;
onResizeStart?: () => void;
onResizeEnd?: () => void;
};
focusTriggerAfterClose?: boolean;
}
declare const Drawer: React.FC<DrawerProps>;
export default Drawer;
+130
View File
@@ -0,0 +1,130 @@
import Portal from '@rc-component/portal';
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
import * as React from 'react';
import { RefContext } from "./context";
import DrawerPopup from "./DrawerPopup";
import { warnCheck } from "./util";
const Drawer = props => {
const {
open = false,
prefixCls = 'rc-drawer',
placement = 'right',
autoFocus = true,
keyboard = true,
width,
height,
size,
maxSize,
mask = true,
maskClosable = true,
getContainer,
forceRender,
afterOpenChange,
destroyOnHidden,
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp,
onClose,
resizable,
defaultSize,
focusTriggerAfterClose,
// Refs
panelRef
} = props;
const [animatedVisible, setAnimatedVisible] = React.useState(false);
// ============================= Warn =============================
if (process.env.NODE_ENV !== 'production') {
warnCheck(props);
}
// ============================= Open =============================
const [mounted, setMounted] = React.useState(false);
useLayoutEffect(() => {
setMounted(true);
}, []);
const mergedOpen = mounted ? open : false;
// ============================ Focus =============================
const popupRef = React.useRef(null);
const lastActiveRef = React.useRef(null);
useLayoutEffect(() => {
if (mergedOpen) {
lastActiveRef.current = document.activeElement;
}
}, [mergedOpen]);
// ============================= Open =============================
const internalAfterOpenChange = nextVisible => {
setAnimatedVisible(nextVisible);
afterOpenChange?.(nextVisible);
if (!nextVisible && focusTriggerAfterClose !== false && lastActiveRef.current && !popupRef.current?.contains(lastActiveRef.current)) {
lastActiveRef.current?.focus({
preventScroll: true
});
}
};
// =========================== Context ============================
const refContext = React.useMemo(() => ({
panel: panelRef
}), [panelRef]);
// ============================ Render ============================
if (!forceRender && !animatedVisible && !mergedOpen && destroyOnHidden) {
return null;
}
const eventHandlers = {
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp
};
const drawerPopupProps = {
...props,
open: mergedOpen,
prefixCls,
placement,
autoFocus,
keyboard,
width,
height,
size,
maxSize,
defaultSize,
mask,
maskClosable,
inline: getContainer === false,
afterOpenChange: internalAfterOpenChange,
ref: popupRef,
resizable,
...eventHandlers
};
const onEsc = ({
top,
event
}) => {
if (top && keyboard) {
event.stopPropagation();
onClose?.(event);
}
};
return /*#__PURE__*/React.createElement(RefContext.Provider, {
value: refContext
}, /*#__PURE__*/React.createElement(Portal, {
open: mergedOpen || forceRender || animatedVisible,
autoDestroy: false,
getContainer: getContainer,
autoLock: mask && (mergedOpen || animatedVisible),
onEsc: onEsc
}, /*#__PURE__*/React.createElement(DrawerPopup, drawerPopupProps)));
};
if (process.env.NODE_ENV !== 'production') {
Drawer.displayName = 'Drawer';
}
export default Drawer;
+24
View File
@@ -0,0 +1,24 @@
import * as React from 'react';
export interface DrawerPanelRef {
focus: VoidFunction;
}
export interface DrawerPanelEvents {
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseOver?: React.MouseEventHandler<HTMLDivElement>;
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
onClick?: React.MouseEventHandler<HTMLDivElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
onKeyUp?: React.KeyboardEventHandler<HTMLDivElement>;
onFocus?: React.FocusEventHandler<HTMLDivElement>;
}
export type DrawerPanelAccessibility = Pick<React.DialogHTMLAttributes<HTMLDivElement>, keyof React.AriaAttributes>;
export interface DrawerPanelProps extends DrawerPanelEvents, DrawerPanelAccessibility {
prefixCls: string;
className?: string;
id?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
containerRef?: React.Ref<HTMLDivElement>;
}
declare const DrawerPanel: React.FC<Readonly<DrawerPanelProps>>;
export default DrawerPanel;
+34
View File
@@ -0,0 +1,34 @@
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import { clsx } from 'clsx';
import * as React from 'react';
import { RefContext } from "./context";
import pickAttrs from "@rc-component/util/es/pickAttrs";
import { useComposeRef } from "@rc-component/util/es/ref";
const DrawerPanel = props => {
const {
prefixCls,
className,
containerRef,
...restProps
} = props;
const {
panel: panelRef
} = React.useContext(RefContext);
const mergedRef = useComposeRef(panelRef, containerRef);
// =============================== Render ===============================
return /*#__PURE__*/React.createElement("div", _extends({
className: clsx(`${prefixCls}-section`, className),
role: "dialog",
ref: mergedRef
}, pickAttrs(props, {
aria: true
}), {
"aria-modal": "true"
}, restProps));
};
if (process.env.NODE_ENV !== 'production') {
DrawerPanel.displayName = 'DrawerPanel';
}
export default DrawerPanel;
+52
View File
@@ -0,0 +1,52 @@
import type { CSSMotionProps } from '@rc-component/motion';
import * as React from 'react';
import type { DrawerPanelAccessibility, DrawerPanelEvents } from './DrawerPanel';
import type { DrawerClassNames, DrawerStyles } from './inter';
export type Placement = 'left' | 'right' | 'top' | 'bottom';
export interface PushConfig {
distance?: number | string;
}
export interface DrawerPopupProps extends DrawerPanelEvents, DrawerPanelAccessibility {
prefixCls: string;
open?: boolean;
inline?: boolean;
push?: boolean | PushConfig;
forceRender?: boolean;
keyboard?: boolean;
autoFocus?: boolean;
focusTrap?: boolean;
rootClassName?: string;
rootStyle?: React.CSSProperties;
zIndex?: number;
placement?: Placement;
id?: string;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
width?: number | string;
height?: number | string;
/** Size of the drawer (width for left/right placement, height for top/bottom placement) */
size?: number | string;
/** Maximum size of the drawer */
maxSize?: number;
mask?: boolean;
maskClosable?: boolean;
maskClassName?: string;
maskStyle?: React.CSSProperties;
motion?: CSSMotionProps | ((placement: Placement) => CSSMotionProps);
maskMotion?: CSSMotionProps;
afterOpenChange?: (open: boolean) => void;
onClose?: (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void;
classNames?: DrawerClassNames;
styles?: DrawerStyles;
drawerRender?: (node: React.ReactNode) => React.ReactNode;
/** Default size for uncontrolled resizable drawer */
defaultSize?: number | string;
resizable?: boolean | {
onResize?: (size: number) => void;
onResizeStart?: () => void;
onResizeEnd?: () => void;
};
}
declare const RefDrawerPopup: React.ForwardRefExoticComponent<DrawerPopupProps & React.RefAttributes<HTMLDivElement>>;
export default RefDrawerPopup;
+265
View File
@@ -0,0 +1,265 @@
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import { clsx } from 'clsx';
import CSSMotion from '@rc-component/motion';
import pickAttrs from "@rc-component/util/es/pickAttrs";
import * as React from 'react';
import DrawerContext from "./context";
import DrawerPanel from "./DrawerPanel";
import useDrag from "./hooks/useDrag";
import { parseWidthHeight } from "./util";
import { useEvent } from '@rc-component/util';
import useFocusable from "./hooks/useFocusable";
const DrawerPopup = (props, ref) => {
const {
prefixCls,
open,
placement,
inline,
push,
forceRender,
// Focus
autoFocus,
focusTrap,
// classNames
classNames: drawerClassNames,
// Root
rootClassName,
rootStyle,
zIndex,
// Drawer
className,
id,
style,
motion,
width,
height,
size,
maxSize,
children,
// Mask
mask,
maskClosable,
maskMotion,
maskClassName,
maskStyle,
// Events
afterOpenChange,
onClose,
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp,
styles,
drawerRender,
resizable,
defaultSize
} = props;
// ================================ Refs ================================
const panelRef = React.useRef(null);
React.useImperativeHandle(ref, () => panelRef.current);
// ========================= Focusable ==========================
const ignoreElement = useFocusable(() => panelRef.current, open, autoFocus, focusTrap, mask);
// ============================ Push ============================
const [pushed, setPushed] = React.useState(false);
const parentContext = React.useContext(DrawerContext);
// Merge push distance
let pushConfig;
if (typeof push === 'boolean') {
pushConfig = push ? {} : {
distance: 0
};
} else {
pushConfig = push || {};
}
const pushDistance = pushConfig?.distance ?? parentContext?.pushDistance ?? 180;
const mergedContext = React.useMemo(() => ({
pushDistance,
push: () => {
setPushed(true);
},
pull: () => {
setPushed(false);
}
}), [pushDistance]);
// ========================= ScrollLock =========================
// Tell parent to push
React.useEffect(() => {
if (open) {
parentContext?.push?.();
} else {
parentContext?.pull?.();
}
}, [open]);
// Clean up
React.useEffect(() => () => {
parentContext?.pull?.();
}, []);
// ============================ Mask ============================
const maskNode = /*#__PURE__*/React.createElement(CSSMotion, _extends({
key: "mask"
}, maskMotion, {
visible: mask && open
}), ({
className: motionMaskClassName,
style: motionMaskStyle
}, maskRef) => /*#__PURE__*/React.createElement("div", {
className: clsx(`${prefixCls}-mask`, motionMaskClassName, drawerClassNames?.mask, maskClassName),
style: {
...motionMaskStyle,
...maskStyle,
...styles?.mask
},
onClick: maskClosable && open ? onClose : undefined,
ref: maskRef
}));
// =========================== Panel ============================
const motionProps = typeof motion === 'function' ? motion(placement) : motion;
// ============================ Size ============================
const [currentSize, setCurrentSize] = React.useState();
const isHorizontal = placement === 'left' || placement === 'right';
// Aggregate size logic with backward compatibility using useMemo
const mergedSize = React.useMemo(() => {
const legacySize = isHorizontal ? width : height;
const nextMergedSize = size ?? legacySize ?? currentSize ?? defaultSize ?? (isHorizontal ? 378 : undefined);
return parseWidthHeight(nextMergedSize);
}, [size, width, height, defaultSize, isHorizontal, currentSize]);
// >>> Style
const wrapperStyle = React.useMemo(() => {
const nextWrapperStyle = {};
if (pushed && pushDistance) {
switch (placement) {
case 'top':
nextWrapperStyle.transform = `translateY(${pushDistance}px)`;
break;
case 'bottom':
nextWrapperStyle.transform = `translateY(${-pushDistance}px)`;
break;
case 'left':
nextWrapperStyle.transform = `translateX(${pushDistance}px)`;
break;
default:
nextWrapperStyle.transform = `translateX(${-pushDistance}px)`;
break;
}
}
if (isHorizontal) {
nextWrapperStyle.width = parseWidthHeight(mergedSize);
} else {
nextWrapperStyle.height = parseWidthHeight(mergedSize);
}
return nextWrapperStyle;
}, [pushed, pushDistance, placement, isHorizontal, mergedSize]);
// =========================== Resize ===========================
const wrapperRef = React.useRef(null);
const isResizable = !!resizable;
const resizeConfig = typeof resizable === 'object' && resizable || {};
const onInternalResize = useEvent(size => {
setCurrentSize(size);
resizeConfig.onResize?.(size);
});
const {
dragElementProps,
isDragging
} = useDrag({
prefixCls: `${prefixCls}-resizable`,
direction: placement,
className: drawerClassNames?.dragger,
style: styles?.dragger,
maxSize,
containerRef: wrapperRef,
currentSize: mergedSize,
onResize: onInternalResize,
onResizeStart: resizeConfig.onResizeStart,
onResizeEnd: resizeConfig.onResizeEnd
});
// =========================== Events ===========================
const eventHandlers = {
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp,
onFocus: e => {
ignoreElement(e.target);
}
};
// =========================== Render ==========================
// >>>>> Panel
const panelNode = /*#__PURE__*/React.createElement(CSSMotion, _extends({
key: "panel"
}, motionProps, {
visible: open,
forceRender: forceRender,
onVisibleChanged: afterOpenChange,
removeOnLeave: false,
leavedClassName: `${prefixCls}-content-wrapper-hidden`
}), ({
className: motionClassName,
style: motionStyle
}, motionRef) => {
const content = /*#__PURE__*/React.createElement(DrawerPanel, _extends({
id: id,
containerRef: motionRef,
prefixCls: prefixCls,
className: clsx(className, drawerClassNames?.section),
style: {
...style,
...styles?.section
}
}, pickAttrs(props, {
aria: true
}), eventHandlers), children);
return /*#__PURE__*/React.createElement("div", _extends({
ref: wrapperRef,
className: clsx(`${prefixCls}-content-wrapper`, isDragging && `${prefixCls}-content-wrapper-dragging`, drawerClassNames?.wrapper, !isDragging && motionClassName),
style: {
...motionStyle,
...wrapperStyle,
...styles?.wrapper
}
}, pickAttrs(props, {
data: true
})), isResizable && /*#__PURE__*/React.createElement("div", dragElementProps), drawerRender ? drawerRender(content) : content);
});
// >>>>> Container
const containerStyle = {
...rootStyle
};
if (zIndex) {
containerStyle.zIndex = zIndex;
}
return /*#__PURE__*/React.createElement(DrawerContext.Provider, {
value: mergedContext
}, /*#__PURE__*/React.createElement("div", {
className: clsx(prefixCls, `${prefixCls}-${placement}`, rootClassName, {
[`${prefixCls}-open`]: open,
[`${prefixCls}-inline`]: inline
}),
style: containerStyle,
tabIndex: -1,
ref: panelRef
}, maskNode, panelNode));
};
const RefDrawerPopup = /*#__PURE__*/React.forwardRef(DrawerPopup);
if (process.env.NODE_ENV !== 'production') {
RefDrawerPopup.displayName = 'DrawerPopup';
}
export default RefDrawerPopup;
+12
View File
@@ -0,0 +1,12 @@
import * as React from 'react';
export interface DrawerContextProps {
pushDistance?: number | string;
push: VoidFunction;
pull: VoidFunction;
}
declare const DrawerContext: React.Context<DrawerContextProps>;
export interface RefContextProps {
panel?: React.Ref<HTMLDivElement>;
}
export declare const RefContext: React.Context<RefContextProps>;
export default DrawerContext;
+4
View File
@@ -0,0 +1,4 @@
import * as React from 'react';
const DrawerContext = /*#__PURE__*/React.createContext(null);
export const RefContext = /*#__PURE__*/React.createContext({});
export default DrawerContext;
@@ -0,0 +1,23 @@
import * as React from 'react';
import type { Placement } from '../Drawer';
export interface UseDragOptions {
prefixCls: string;
direction: Placement;
className?: string;
style?: React.CSSProperties;
maxSize?: number;
containerRef?: React.RefObject<HTMLElement>;
currentSize?: number | string;
onResize?: (size: number) => void;
onResizeEnd?: (size: number) => void;
onResizeStart?: (size: number) => void;
}
export interface UseDragReturn {
dragElementProps: {
className: string;
style: React.CSSProperties;
onMouseDown: (e: React.MouseEvent) => void;
};
isDragging: boolean;
}
export default function useDrag(options: UseDragOptions): UseDragReturn;
+98
View File
@@ -0,0 +1,98 @@
import * as React from 'react';
import { clsx } from 'clsx';
import { useEvent } from '@rc-component/util';
export default function useDrag(options) {
const {
prefixCls,
direction,
className,
style,
maxSize,
containerRef,
currentSize,
onResize,
onResizeEnd,
onResizeStart
} = options;
const [isDragging, setIsDragging] = React.useState(false);
const [startPos, setStartPos] = React.useState(0);
const [startSize, setStartSize] = React.useState(0);
const isHorizontal = direction === 'left' || direction === 'right';
const handleMouseDown = useEvent(e => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
if (isHorizontal) {
setStartPos(e.clientX);
} else {
setStartPos(e.clientY);
}
// Use provided currentSize, or fallback to container size
let startSize;
if (typeof currentSize === 'number') {
startSize = currentSize;
} else if (containerRef?.current) {
const rect = containerRef.current.getBoundingClientRect();
startSize = isHorizontal ? rect.width : rect.height;
}
setStartSize(startSize);
onResizeStart?.(startSize);
});
const handleMouseMove = useEvent(e => {
if (!isDragging) return;
const currentPos = isHorizontal ? e.clientX : e.clientY;
let delta = currentPos - startPos;
// Adjust delta direction based on placement
if (direction === 'right' || direction === 'bottom') {
delta = -delta;
}
let newSize = startSize + delta;
// Apply min/max size limits
if (newSize < 0) {
newSize = 0;
}
// Only apply maxSize if it's a valid positive number
if (maxSize && newSize > maxSize) {
newSize = maxSize;
}
onResize?.(newSize);
});
const handleMouseUp = React.useCallback(() => {
if (isDragging) {
setIsDragging(false);
// Get the final size after resize
if (containerRef?.current) {
const rect = containerRef.current.getBoundingClientRect();
const finalSize = isHorizontal ? rect.width : rect.height;
onResizeEnd?.(finalSize);
}
}
}, [isDragging, containerRef, onResizeEnd, isHorizontal]);
React.useEffect(() => {
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}
}, [isDragging, handleMouseMove, handleMouseUp]);
const dragElementClassName = clsx(`${prefixCls}-dragger`, `${prefixCls}-dragger-${direction}`, {
[`${prefixCls}-dragger-dragging`]: isDragging,
[`${prefixCls}-dragger-horizontal`]: isHorizontal,
[`${prefixCls}-dragger-vertical`]: !isHorizontal
}, className);
return {
dragElementProps: {
className: dragElementClassName,
style,
onMouseDown: handleMouseDown
},
isDragging
};
}
@@ -0,0 +1 @@
export default function useFocusable(getContainer: () => HTMLElement, open: boolean, autoFocus?: boolean, focusTrap?: boolean, mask?: boolean): (ele: HTMLElement) => void;
@@ -0,0 +1,18 @@
import React from 'react';
import { useLockFocus } from "@rc-component/util/es/Dom/focus";
export default function useFocusable(getContainer, open, autoFocus, focusTrap, mask) {
const mergedFocusTrap = focusTrap ?? mask !== false;
// Focus lock
const [ignoreElement] = useLockFocus(open && mergedFocusTrap, getContainer);
// Auto Focus
React.useEffect(() => {
if (open && autoFocus === true) {
getContainer()?.focus({
preventScroll: true
});
}
}, [open]);
return ignoreElement;
}
+4
View File
@@ -0,0 +1,4 @@
import Drawer from './Drawer';
import type { DrawerProps, Placement } from './Drawer';
export type { DrawerProps, Placement };
export default Drawer;
+3
View File
@@ -0,0 +1,3 @@
// export this package's api
import Drawer from "./Drawer";
export default Drawer;
+13
View File
@@ -0,0 +1,13 @@
/// <reference types="react" />
export interface DrawerClassNames {
mask?: string;
wrapper?: string;
section?: string;
dragger?: string;
}
export interface DrawerStyles {
mask?: React.CSSProperties;
wrapper?: React.CSSProperties;
section?: React.CSSProperties;
dragger?: React.CSSProperties;
}
+1
View File
@@ -0,0 +1 @@
export {};
+3
View File
@@ -0,0 +1,3 @@
import type { DrawerProps } from './Drawer';
export declare function parseWidthHeight(value?: number | string): string | number;
export declare function warnCheck(props: DrawerProps): void;
+19
View File
@@ -0,0 +1,19 @@
import warning from "@rc-component/util/es/warning";
import canUseDom from "@rc-component/util/es/Dom/canUseDom";
export function parseWidthHeight(value) {
if (typeof value === 'string') {
const num = Number(value.replace(/px$/i, ''));
const floatNum = parseFloat(value);
if (floatNum === num) {
warning(false, 'Invalid value type of `width` or `height` which should be number type instead.');
}
if (!Number.isNaN(num)) {
return num;
}
}
return value;
}
export function warnCheck(props) {
warning(!('wrapperClassName' in props), `'wrapperClassName' is removed. Please use 'rootClassName' instead.`);
warning(canUseDom() || !props.open, `Drawer with 'open' in SSR is not work since no place to createPortal. Please move to 'useEffect' instead.`);
}
+39
View File
@@ -0,0 +1,39 @@
import type { PortalProps } from '@rc-component/portal';
import * as React from 'react';
import type { DrawerPanelAccessibility, DrawerPanelEvents } from './DrawerPanel';
import type { DrawerPopupProps } from './DrawerPopup';
import type { DrawerClassNames, DrawerStyles } from './inter';
export type Placement = 'left' | 'top' | 'right' | 'bottom';
export interface DrawerProps extends Omit<DrawerPopupProps, 'prefixCls' | 'inline' | 'scrollLocker'>, DrawerPanelEvents, DrawerPanelAccessibility {
prefixCls?: string;
open?: boolean;
onClose?: (e: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => void;
destroyOnHidden?: boolean;
getContainer?: PortalProps['getContainer'];
panelRef?: React.Ref<HTMLDivElement>;
classNames?: DrawerClassNames;
styles?: DrawerStyles;
/**
* @deprecated Use `size` instead. Will be removed in next major version.
*/
width?: number | string;
/**
* @deprecated Use `size` instead. Will be removed in next major version.
*/
height?: number | string;
/** Size of the drawer (width for left/right placement, height for top/bottom placement) */
size?: number | string;
/** Maximum size of the drawer */
maxSize?: number;
/** Default size for uncontrolled resizable drawer */
defaultSize?: number | string;
/** Resizable configuration - boolean to enable/disable or object with optional callbacks */
resizable?: boolean | {
onResize?: (size: number) => void;
onResizeStart?: () => void;
onResizeEnd?: () => void;
};
focusTriggerAfterClose?: boolean;
}
declare const Drawer: React.FC<DrawerProps>;
export default Drawer;
+139
View File
@@ -0,0 +1,139 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _portal = _interopRequireDefault(require("@rc-component/portal"));
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
var React = _interopRequireWildcard(require("react"));
var _context = require("./context");
var _DrawerPopup = _interopRequireDefault(require("./DrawerPopup"));
var _util = require("./util");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const Drawer = props => {
const {
open = false,
prefixCls = 'rc-drawer',
placement = 'right',
autoFocus = true,
keyboard = true,
width,
height,
size,
maxSize,
mask = true,
maskClosable = true,
getContainer,
forceRender,
afterOpenChange,
destroyOnHidden,
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp,
onClose,
resizable,
defaultSize,
focusTriggerAfterClose,
// Refs
panelRef
} = props;
const [animatedVisible, setAnimatedVisible] = React.useState(false);
// ============================= Warn =============================
if (process.env.NODE_ENV !== 'production') {
(0, _util.warnCheck)(props);
}
// ============================= Open =============================
const [mounted, setMounted] = React.useState(false);
(0, _useLayoutEffect.default)(() => {
setMounted(true);
}, []);
const mergedOpen = mounted ? open : false;
// ============================ Focus =============================
const popupRef = React.useRef(null);
const lastActiveRef = React.useRef(null);
(0, _useLayoutEffect.default)(() => {
if (mergedOpen) {
lastActiveRef.current = document.activeElement;
}
}, [mergedOpen]);
// ============================= Open =============================
const internalAfterOpenChange = nextVisible => {
setAnimatedVisible(nextVisible);
afterOpenChange?.(nextVisible);
if (!nextVisible && focusTriggerAfterClose !== false && lastActiveRef.current && !popupRef.current?.contains(lastActiveRef.current)) {
lastActiveRef.current?.focus({
preventScroll: true
});
}
};
// =========================== Context ============================
const refContext = React.useMemo(() => ({
panel: panelRef
}), [panelRef]);
// ============================ Render ============================
if (!forceRender && !animatedVisible && !mergedOpen && destroyOnHidden) {
return null;
}
const eventHandlers = {
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp
};
const drawerPopupProps = {
...props,
open: mergedOpen,
prefixCls,
placement,
autoFocus,
keyboard,
width,
height,
size,
maxSize,
defaultSize,
mask,
maskClosable,
inline: getContainer === false,
afterOpenChange: internalAfterOpenChange,
ref: popupRef,
resizable,
...eventHandlers
};
const onEsc = ({
top,
event
}) => {
if (top && keyboard) {
event.stopPropagation();
onClose?.(event);
}
};
return /*#__PURE__*/React.createElement(_context.RefContext.Provider, {
value: refContext
}, /*#__PURE__*/React.createElement(_portal.default, {
open: mergedOpen || forceRender || animatedVisible,
autoDestroy: false,
getContainer: getContainer,
autoLock: mask && (mergedOpen || animatedVisible),
onEsc: onEsc
}, /*#__PURE__*/React.createElement(_DrawerPopup.default, drawerPopupProps)));
};
if (process.env.NODE_ENV !== 'production') {
Drawer.displayName = 'Drawer';
}
var _default = exports.default = Drawer;
+24
View File
@@ -0,0 +1,24 @@
import * as React from 'react';
export interface DrawerPanelRef {
focus: VoidFunction;
}
export interface DrawerPanelEvents {
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseOver?: React.MouseEventHandler<HTMLDivElement>;
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
onClick?: React.MouseEventHandler<HTMLDivElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
onKeyUp?: React.KeyboardEventHandler<HTMLDivElement>;
onFocus?: React.FocusEventHandler<HTMLDivElement>;
}
export type DrawerPanelAccessibility = Pick<React.DialogHTMLAttributes<HTMLDivElement>, keyof React.AriaAttributes>;
export interface DrawerPanelProps extends DrawerPanelEvents, DrawerPanelAccessibility {
prefixCls: string;
className?: string;
id?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
containerRef?: React.Ref<HTMLDivElement>;
}
declare const DrawerPanel: React.FC<Readonly<DrawerPanelProps>>;
export default DrawerPanel;
+43
View File
@@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
var _context = require("./context");
var _pickAttrs = _interopRequireDefault(require("@rc-component/util/lib/pickAttrs"));
var _ref = require("@rc-component/util/lib/ref");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const DrawerPanel = props => {
const {
prefixCls,
className,
containerRef,
...restProps
} = props;
const {
panel: panelRef
} = React.useContext(_context.RefContext);
const mergedRef = (0, _ref.useComposeRef)(panelRef, containerRef);
// =============================== Render ===============================
return /*#__PURE__*/React.createElement("div", _extends({
className: (0, _clsx.clsx)(`${prefixCls}-section`, className),
role: "dialog",
ref: mergedRef
}, (0, _pickAttrs.default)(props, {
aria: true
}), {
"aria-modal": "true"
}, restProps));
};
if (process.env.NODE_ENV !== 'production') {
DrawerPanel.displayName = 'DrawerPanel';
}
var _default = exports.default = DrawerPanel;
+52
View File
@@ -0,0 +1,52 @@
import type { CSSMotionProps } from '@rc-component/motion';
import * as React from 'react';
import type { DrawerPanelAccessibility, DrawerPanelEvents } from './DrawerPanel';
import type { DrawerClassNames, DrawerStyles } from './inter';
export type Placement = 'left' | 'right' | 'top' | 'bottom';
export interface PushConfig {
distance?: number | string;
}
export interface DrawerPopupProps extends DrawerPanelEvents, DrawerPanelAccessibility {
prefixCls: string;
open?: boolean;
inline?: boolean;
push?: boolean | PushConfig;
forceRender?: boolean;
keyboard?: boolean;
autoFocus?: boolean;
focusTrap?: boolean;
rootClassName?: string;
rootStyle?: React.CSSProperties;
zIndex?: number;
placement?: Placement;
id?: string;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
width?: number | string;
height?: number | string;
/** Size of the drawer (width for left/right placement, height for top/bottom placement) */
size?: number | string;
/** Maximum size of the drawer */
maxSize?: number;
mask?: boolean;
maskClosable?: boolean;
maskClassName?: string;
maskStyle?: React.CSSProperties;
motion?: CSSMotionProps | ((placement: Placement) => CSSMotionProps);
maskMotion?: CSSMotionProps;
afterOpenChange?: (open: boolean) => void;
onClose?: (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void;
classNames?: DrawerClassNames;
styles?: DrawerStyles;
drawerRender?: (node: React.ReactNode) => React.ReactNode;
/** Default size for uncontrolled resizable drawer */
defaultSize?: number | string;
resizable?: boolean | {
onResize?: (size: number) => void;
onResizeStart?: () => void;
onResizeEnd?: () => void;
};
}
declare const RefDrawerPopup: React.ForwardRefExoticComponent<DrawerPopupProps & React.RefAttributes<HTMLDivElement>>;
export default RefDrawerPopup;
+274
View File
@@ -0,0 +1,274 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var _motion = _interopRequireDefault(require("@rc-component/motion"));
var _pickAttrs = _interopRequireDefault(require("@rc-component/util/lib/pickAttrs"));
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("./context"));
var _DrawerPanel = _interopRequireDefault(require("./DrawerPanel"));
var _useDrag = _interopRequireDefault(require("./hooks/useDrag"));
var _util = require("./util");
var _util2 = require("@rc-component/util");
var _useFocusable = _interopRequireDefault(require("./hooks/useFocusable"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const DrawerPopup = (props, ref) => {
const {
prefixCls,
open,
placement,
inline,
push,
forceRender,
// Focus
autoFocus,
focusTrap,
// classNames
classNames: drawerClassNames,
// Root
rootClassName,
rootStyle,
zIndex,
// Drawer
className,
id,
style,
motion,
width,
height,
size,
maxSize,
children,
// Mask
mask,
maskClosable,
maskMotion,
maskClassName,
maskStyle,
// Events
afterOpenChange,
onClose,
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp,
styles,
drawerRender,
resizable,
defaultSize
} = props;
// ================================ Refs ================================
const panelRef = React.useRef(null);
React.useImperativeHandle(ref, () => panelRef.current);
// ========================= Focusable ==========================
const ignoreElement = (0, _useFocusable.default)(() => panelRef.current, open, autoFocus, focusTrap, mask);
// ============================ Push ============================
const [pushed, setPushed] = React.useState(false);
const parentContext = React.useContext(_context.default);
// Merge push distance
let pushConfig;
if (typeof push === 'boolean') {
pushConfig = push ? {} : {
distance: 0
};
} else {
pushConfig = push || {};
}
const pushDistance = pushConfig?.distance ?? parentContext?.pushDistance ?? 180;
const mergedContext = React.useMemo(() => ({
pushDistance,
push: () => {
setPushed(true);
},
pull: () => {
setPushed(false);
}
}), [pushDistance]);
// ========================= ScrollLock =========================
// Tell parent to push
React.useEffect(() => {
if (open) {
parentContext?.push?.();
} else {
parentContext?.pull?.();
}
}, [open]);
// Clean up
React.useEffect(() => () => {
parentContext?.pull?.();
}, []);
// ============================ Mask ============================
const maskNode = /*#__PURE__*/React.createElement(_motion.default, _extends({
key: "mask"
}, maskMotion, {
visible: mask && open
}), ({
className: motionMaskClassName,
style: motionMaskStyle
}, maskRef) => /*#__PURE__*/React.createElement("div", {
className: (0, _clsx.clsx)(`${prefixCls}-mask`, motionMaskClassName, drawerClassNames?.mask, maskClassName),
style: {
...motionMaskStyle,
...maskStyle,
...styles?.mask
},
onClick: maskClosable && open ? onClose : undefined,
ref: maskRef
}));
// =========================== Panel ============================
const motionProps = typeof motion === 'function' ? motion(placement) : motion;
// ============================ Size ============================
const [currentSize, setCurrentSize] = React.useState();
const isHorizontal = placement === 'left' || placement === 'right';
// Aggregate size logic with backward compatibility using useMemo
const mergedSize = React.useMemo(() => {
const legacySize = isHorizontal ? width : height;
const nextMergedSize = size ?? legacySize ?? currentSize ?? defaultSize ?? (isHorizontal ? 378 : undefined);
return (0, _util.parseWidthHeight)(nextMergedSize);
}, [size, width, height, defaultSize, isHorizontal, currentSize]);
// >>> Style
const wrapperStyle = React.useMemo(() => {
const nextWrapperStyle = {};
if (pushed && pushDistance) {
switch (placement) {
case 'top':
nextWrapperStyle.transform = `translateY(${pushDistance}px)`;
break;
case 'bottom':
nextWrapperStyle.transform = `translateY(${-pushDistance}px)`;
break;
case 'left':
nextWrapperStyle.transform = `translateX(${pushDistance}px)`;
break;
default:
nextWrapperStyle.transform = `translateX(${-pushDistance}px)`;
break;
}
}
if (isHorizontal) {
nextWrapperStyle.width = (0, _util.parseWidthHeight)(mergedSize);
} else {
nextWrapperStyle.height = (0, _util.parseWidthHeight)(mergedSize);
}
return nextWrapperStyle;
}, [pushed, pushDistance, placement, isHorizontal, mergedSize]);
// =========================== Resize ===========================
const wrapperRef = React.useRef(null);
const isResizable = !!resizable;
const resizeConfig = typeof resizable === 'object' && resizable || {};
const onInternalResize = (0, _util2.useEvent)(size => {
setCurrentSize(size);
resizeConfig.onResize?.(size);
});
const {
dragElementProps,
isDragging
} = (0, _useDrag.default)({
prefixCls: `${prefixCls}-resizable`,
direction: placement,
className: drawerClassNames?.dragger,
style: styles?.dragger,
maxSize,
containerRef: wrapperRef,
currentSize: mergedSize,
onResize: onInternalResize,
onResizeStart: resizeConfig.onResizeStart,
onResizeEnd: resizeConfig.onResizeEnd
});
// =========================== Events ===========================
const eventHandlers = {
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp,
onFocus: e => {
ignoreElement(e.target);
}
};
// =========================== Render ==========================
// >>>>> Panel
const panelNode = /*#__PURE__*/React.createElement(_motion.default, _extends({
key: "panel"
}, motionProps, {
visible: open,
forceRender: forceRender,
onVisibleChanged: afterOpenChange,
removeOnLeave: false,
leavedClassName: `${prefixCls}-content-wrapper-hidden`
}), ({
className: motionClassName,
style: motionStyle
}, motionRef) => {
const content = /*#__PURE__*/React.createElement(_DrawerPanel.default, _extends({
id: id,
containerRef: motionRef,
prefixCls: prefixCls,
className: (0, _clsx.clsx)(className, drawerClassNames?.section),
style: {
...style,
...styles?.section
}
}, (0, _pickAttrs.default)(props, {
aria: true
}), eventHandlers), children);
return /*#__PURE__*/React.createElement("div", _extends({
ref: wrapperRef,
className: (0, _clsx.clsx)(`${prefixCls}-content-wrapper`, isDragging && `${prefixCls}-content-wrapper-dragging`, drawerClassNames?.wrapper, !isDragging && motionClassName),
style: {
...motionStyle,
...wrapperStyle,
...styles?.wrapper
}
}, (0, _pickAttrs.default)(props, {
data: true
})), isResizable && /*#__PURE__*/React.createElement("div", dragElementProps), drawerRender ? drawerRender(content) : content);
});
// >>>>> Container
const containerStyle = {
...rootStyle
};
if (zIndex) {
containerStyle.zIndex = zIndex;
}
return /*#__PURE__*/React.createElement(_context.default.Provider, {
value: mergedContext
}, /*#__PURE__*/React.createElement("div", {
className: (0, _clsx.clsx)(prefixCls, `${prefixCls}-${placement}`, rootClassName, {
[`${prefixCls}-open`]: open,
[`${prefixCls}-inline`]: inline
}),
style: containerStyle,
tabIndex: -1,
ref: panelRef
}, maskNode, panelNode));
};
const RefDrawerPopup = /*#__PURE__*/React.forwardRef(DrawerPopup);
if (process.env.NODE_ENV !== 'production') {
RefDrawerPopup.displayName = 'DrawerPopup';
}
var _default = exports.default = RefDrawerPopup;
+12
View File
@@ -0,0 +1,12 @@
import * as React from 'react';
export interface DrawerContextProps {
pushDistance?: number | string;
push: VoidFunction;
pull: VoidFunction;
}
declare const DrawerContext: React.Context<DrawerContextProps>;
export interface RefContextProps {
panel?: React.Ref<HTMLDivElement>;
}
export declare const RefContext: React.Context<RefContextProps>;
export default DrawerContext;
+12
View File
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.RefContext = void 0;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const DrawerContext = /*#__PURE__*/React.createContext(null);
const RefContext = exports.RefContext = /*#__PURE__*/React.createContext({});
var _default = exports.default = DrawerContext;
@@ -0,0 +1,23 @@
import * as React from 'react';
import type { Placement } from '../Drawer';
export interface UseDragOptions {
prefixCls: string;
direction: Placement;
className?: string;
style?: React.CSSProperties;
maxSize?: number;
containerRef?: React.RefObject<HTMLElement>;
currentSize?: number | string;
onResize?: (size: number) => void;
onResizeEnd?: (size: number) => void;
onResizeStart?: (size: number) => void;
}
export interface UseDragReturn {
dragElementProps: {
className: string;
style: React.CSSProperties;
onMouseDown: (e: React.MouseEvent) => void;
};
isDragging: boolean;
}
export default function useDrag(options: UseDragOptions): UseDragReturn;
+106
View File
@@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useDrag;
var React = _interopRequireWildcard(require("react"));
var _clsx = require("clsx");
var _util = require("@rc-component/util");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function useDrag(options) {
const {
prefixCls,
direction,
className,
style,
maxSize,
containerRef,
currentSize,
onResize,
onResizeEnd,
onResizeStart
} = options;
const [isDragging, setIsDragging] = React.useState(false);
const [startPos, setStartPos] = React.useState(0);
const [startSize, setStartSize] = React.useState(0);
const isHorizontal = direction === 'left' || direction === 'right';
const handleMouseDown = (0, _util.useEvent)(e => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
if (isHorizontal) {
setStartPos(e.clientX);
} else {
setStartPos(e.clientY);
}
// Use provided currentSize, or fallback to container size
let startSize;
if (typeof currentSize === 'number') {
startSize = currentSize;
} else if (containerRef?.current) {
const rect = containerRef.current.getBoundingClientRect();
startSize = isHorizontal ? rect.width : rect.height;
}
setStartSize(startSize);
onResizeStart?.(startSize);
});
const handleMouseMove = (0, _util.useEvent)(e => {
if (!isDragging) return;
const currentPos = isHorizontal ? e.clientX : e.clientY;
let delta = currentPos - startPos;
// Adjust delta direction based on placement
if (direction === 'right' || direction === 'bottom') {
delta = -delta;
}
let newSize = startSize + delta;
// Apply min/max size limits
if (newSize < 0) {
newSize = 0;
}
// Only apply maxSize if it's a valid positive number
if (maxSize && newSize > maxSize) {
newSize = maxSize;
}
onResize?.(newSize);
});
const handleMouseUp = React.useCallback(() => {
if (isDragging) {
setIsDragging(false);
// Get the final size after resize
if (containerRef?.current) {
const rect = containerRef.current.getBoundingClientRect();
const finalSize = isHorizontal ? rect.width : rect.height;
onResizeEnd?.(finalSize);
}
}
}, [isDragging, containerRef, onResizeEnd, isHorizontal]);
React.useEffect(() => {
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}
}, [isDragging, handleMouseMove, handleMouseUp]);
const dragElementClassName = (0, _clsx.clsx)(`${prefixCls}-dragger`, `${prefixCls}-dragger-${direction}`, {
[`${prefixCls}-dragger-dragging`]: isDragging,
[`${prefixCls}-dragger-horizontal`]: isHorizontal,
[`${prefixCls}-dragger-vertical`]: !isHorizontal
}, className);
return {
dragElementProps: {
className: dragElementClassName,
style,
onMouseDown: handleMouseDown
},
isDragging
};
}
@@ -0,0 +1 @@
export default function useFocusable(getContainer: () => HTMLElement, open: boolean, autoFocus?: boolean, focusTrap?: boolean, mask?: boolean): (ele: HTMLElement) => void;
@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useFocusable;
var _react = _interopRequireDefault(require("react"));
var _focus = require("@rc-component/util/lib/Dom/focus");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function useFocusable(getContainer, open, autoFocus, focusTrap, mask) {
const mergedFocusTrap = focusTrap ?? mask !== false;
// Focus lock
const [ignoreElement] = (0, _focus.useLockFocus)(open && mergedFocusTrap, getContainer);
// Auto Focus
_react.default.useEffect(() => {
if (open && autoFocus === true) {
getContainer()?.focus({
preventScroll: true
});
}
}, [open]);
return ignoreElement;
}
+4
View File
@@ -0,0 +1,4 @@
import Drawer from './Drawer';
import type { DrawerProps, Placement } from './Drawer';
export type { DrawerProps, Placement };
export default Drawer;
+10
View File
@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _Drawer = _interopRequireDefault(require("./Drawer"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// export this package's api
var _default = exports.default = _Drawer.default;
+13
View File
@@ -0,0 +1,13 @@
/// <reference types="react" />
export interface DrawerClassNames {
mask?: string;
wrapper?: string;
section?: string;
dragger?: string;
}
export interface DrawerStyles {
mask?: React.CSSProperties;
wrapper?: React.CSSProperties;
section?: React.CSSProperties;
dragger?: React.CSSProperties;
}
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
+3
View File
@@ -0,0 +1,3 @@
import type { DrawerProps } from './Drawer';
export declare function parseWidthHeight(value?: number | string): string | number;
export declare function warnCheck(props: DrawerProps): void;
+27
View File
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parseWidthHeight = parseWidthHeight;
exports.warnCheck = warnCheck;
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _canUseDom = _interopRequireDefault(require("@rc-component/util/lib/Dom/canUseDom"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function parseWidthHeight(value) {
if (typeof value === 'string') {
const num = Number(value.replace(/px$/i, ''));
const floatNum = parseFloat(value);
if (floatNum === num) {
(0, _warning.default)(false, 'Invalid value type of `width` or `height` which should be number type instead.');
}
if (!Number.isNaN(num)) {
return num;
}
}
return value;
}
function warnCheck(props) {
(0, _warning.default)(!('wrapperClassName' in props), `'wrapperClassName' is removed. Please use 'rootClassName' instead.`);
(0, _warning.default)((0, _canUseDom.default)() || !props.open, `Drawer with 'open' in SSR is not work since no place to createPortal. Please move to 'useEffect' instead.`);
}
+90
View File
@@ -0,0 +1,90 @@
{
"name": "@rc-component/drawer",
"version": "1.4.2",
"description": "drawer component for react",
"keywords": [
"react",
"react-component",
"react-drawer",
"drawer",
"drawer-menu",
"rc-drawer-menu",
"react-drawer-menu",
"animation",
"drawer-motion",
"drawer-animation"
],
"homepage": "https://github.com/react-component/drawer",
"bugs": {
"url": "https://github.com/react-component/drawer/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/react-component/drawer.git"
},
"license": "MIT",
"author": "155259966@qq.com",
"main": "./lib/index",
"module": "./es/index",
"files": [
"lib",
"assets/*.css",
"es"
],
"scripts": {
"build": "dumi build",
"compile": "father build && lessc assets/index.less assets/index.css",
"lint": "eslint src/ --ext .tsx,.ts",
"now-build": "npm run build",
"prepublishOnly": "npm run compile && rc-np",
"start": "dumi dev",
"test": "rc-test",
"prettier": "prettier --write .",
"prepare": "husky"
},
"dependencies": {
"@rc-component/motion": "^1.1.4",
"@rc-component/portal": "^2.1.3",
"@rc-component/util": "^1.9.0",
"clsx": "^2.1.1"
},
"devDependencies": {
"@ant-design/icons": "^5.3.0",
"@rc-component/father-plugin": "^2.0.2",
"@rc-component/np": "^1.0.0",
"@testing-library/jest-dom": "^6.2.0",
"@testing-library/react": "^16.3.0",
"@types/jest": "^29.5.11",
"@types/node": "^25.0.0",
"@types/raf": "^3.4.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/warning": "^3.0.0",
"antd": "^5.12.7",
"dumi": "^2.2.0",
"eslint": "^8.56.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-unicorn": "^51.0.1",
"father": "^4.0.0",
"glob": "^10.3.10",
"husky": "^9.0.10",
"less": "^4.2.0",
"lint-staged": "^16.1.5",
"prettier": "^3.0.0",
"rc-test": "^7.0.9",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"stylelint": "^17.1.0",
"stylelint-config-standard-less": "^4.0.0",
"typescript": "^5.3.3"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": "eslint --fix",
"*.{css,less}": "stylelint --fix",
"*.{js,jsx,ts,tsx,json,less,css,md}": "prettier --write"
}
}