1
This commit is contained in:
+39
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,4 @@
|
||||
import * as React from 'react';
|
||||
const DrawerContext = /*#__PURE__*/React.createContext(null);
|
||||
export const RefContext = /*#__PURE__*/React.createContext({});
|
||||
export default DrawerContext;
|
||||
+23
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default function useFocusable(getContainer: () => HTMLElement, open: boolean, autoFocus?: boolean, focusTrap?: boolean, mask?: boolean): (ele: HTMLElement) => void;
|
||||
+18
@@ -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
@@ -0,0 +1,4 @@
|
||||
import Drawer from './Drawer';
|
||||
import type { DrawerProps, Placement } from './Drawer';
|
||||
export type { DrawerProps, Placement };
|
||||
export default Drawer;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// export this package's api
|
||||
import Drawer from "./Drawer";
|
||||
export default Drawer;
|
||||
+13
@@ -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
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+3
@@ -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
@@ -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.`);
|
||||
}
|
||||
Reference in New Issue
Block a user