UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

269 lines (266 loc) 8.21 kB
'use client'; import Center_default from "../Flex/Center.mjs"; import Icon_default from "../Icon/Icon.mjs"; import { handleVariants, panelVariants, styles, toggleVariants } from "./style.mjs"; import { reversePlacement } from "./utils.mjs"; import { memo, use, useCallback, useEffect, useMemo, useReducer, useRef, useTransition } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; import { ConfigProvider } from "antd"; import { cx } from "antd-style"; import useMergeState from "use-merge-value"; import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from "lucide-react"; import { useHover } from "ahooks"; import isEqual from "fast-deep-equal"; import { Resizable } from "re-resizable"; //#region src/DraggablePanel/DraggablePanel.tsx const DEFAULT_HEIGHT = 180; const DEFAULT_WIDTH = 280; const DEFAULT_HEADER_HEIGHT = 0; const DEFAULT_PIN = true; const DEFAULT_MODE = "fixed"; const DEFAULT_EXPANDABLE = true; const DEFAULT_EXPAND = true; const DEFAULT_SHOW_HANDLE_WIDE_AREA = true; function draggablePanelReducer(state, action) { switch (action.type) { case "START_RESIZE": return { ...state, isResizing: true, showExpand: false }; case "STOP_RESIZE": return { ...state, isResizing: false, showExpand: true }; case "SET_SHOW_EXPAND": return { ...state, showExpand: action.payload }; default: return state; } } const DraggablePanel = memo(({ headerHeight = DEFAULT_HEADER_HEIGHT, fullscreen, maxHeight, pin = DEFAULT_PIN, mode = DEFAULT_MODE, children, placement = "right", resize, style, showBorder = true, showHandleHighlight = false, showHandleWideArea = DEFAULT_SHOW_HANDLE_WIDE_AREA, backgroundColor, size, defaultSize: customizeDefaultSize, minWidth, minHeight, maxWidth, onSizeChange, onSizeDragging, expandable = DEFAULT_EXPANDABLE, expand, defaultExpand = DEFAULT_EXPAND, onExpandChange, className, showHandleWhenCollapsed, destroyOnClose, styles: customStyles, classNames, dir }) => { const ref = useRef(null); const isHovering = useHover(ref); const isVertical = placement === "top" || placement === "bottom"; const [isPending, startTransition] = useTransition(); const hoverTimeoutRef = useRef(void 0); const { direction: antdDirection } = use(ConfigProvider.ConfigContext); const direction = dir ?? antdDirection; const internalPlacement = useMemo(() => { return direction === "rtl" && ["left", "right"].includes(placement) ? placement === "left" ? "right" : "left" : placement; }, [direction, placement]); const cssVariables = useMemo(() => ({ "--draggable-panel-bg": backgroundColor || "", "--draggable-panel-header-height": `${headerHeight}px` }), [backgroundColor, headerHeight]); const [isExpand, setIsExpand] = useMergeState(defaultExpand, { onChange: onExpandChange, value: expand }); const [state, dispatch] = useReducer(draggablePanelReducer, { isResizing: false, showExpand: true }); useEffect(() => { if (pin) return; if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current); if (isHovering && !isExpand) startTransition(() => { setIsExpand(true); }); else if (!isHovering && isExpand) hoverTimeoutRef.current = setTimeout(() => { startTransition(() => { setIsExpand(false); }); }, 150); }, [ pin, isHovering, isExpand, setIsExpand ]); useEffect(() => { return () => { if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current); }; }, []); const canResizing = resize !== false && isExpand; const resizing = useMemo(() => ({ bottom: false, bottomLeft: false, bottomRight: false, left: false, right: false, top: false, topLeft: false, topRight: false, [reversePlacement(internalPlacement)]: true, ...resize }), [internalPlacement, resize]); const defaultSize = useMemo(() => { if (isVertical) return { height: DEFAULT_HEIGHT, width: "100%", ...customizeDefaultSize }; return { height: "100%", width: DEFAULT_WIDTH, ...customizeDefaultSize }; }, [isVertical, customizeDefaultSize]); const sizeProps = useMemo(() => { if (!isExpand) return isVertical ? { minHeight: 0, size: { height: 0 } } : { minWidth: 0, size: { width: 0 } }; return { defaultSize, maxHeight: typeof maxHeight === "number" ? Math.max(maxHeight, 0) : maxHeight, maxWidth: typeof maxWidth === "number" ? Math.max(maxWidth, 0) : maxWidth, minHeight: typeof minHeight === "number" ? Math.max(minHeight, 0) : minHeight, minWidth: typeof minWidth === "number" ? Math.max(minWidth, 0) : minWidth, size }; }, [ isExpand, isVertical, defaultSize, maxHeight, maxWidth, minHeight, minWidth, size ]); const Arrow = useMemo(() => { switch (internalPlacement) { case "top": return ChevronDown; case "bottom": return ChevronUp; case "right": return ChevronLeft; case "left": return ChevronRight; default: return ChevronLeft; } }, [internalPlacement]); const toggleExpand = useCallback(() => { if (!expandable) return; startTransition(() => { setIsExpand(!isExpand); }); }, [ expandable, isExpand, setIsExpand ]); const handle = useMemo(() => /* @__PURE__ */ jsx(Center_default, { className: toggleVariants({ placement: internalPlacement, showHandleWideArea }), style: { opacity: isExpand ? pin ? void 0 : 0 : showHandleWhenCollapsed ? 1 : 0 }, children: /* @__PURE__ */ jsx(Center_default, { className: classNames?.handle, onClick: toggleExpand, style: customStyles?.handle, children: /* @__PURE__ */ jsx(Icon_default, { className: styles.handlerIcon, icon: Arrow, size: 16, style: { marginBottom: internalPlacement === "top" ? 4 : 0, marginLeft: internalPlacement === "right" ? 4 : 0, marginRight: internalPlacement === "left" ? 4 : 0, marginTop: internalPlacement === "bottom" ? 4 : 0, transform: `rotate(${isExpand ? 180 : 0}deg)`, transition: "transform 0.3s ease" } }) }) }), [ toggleVariants, internalPlacement, isExpand, pin, showHandleWhenCollapsed, classNames?.handle, toggleExpand, customStyles?.handle, styles.handlerIcon, Arrow ]); const handleResize = useCallback((_, _direction, reference_, delta) => { onSizeDragging?.(delta, { height: reference_.style.height, width: reference_.style.width }); }, [onSizeDragging]); const handleResizeStart = useCallback(() => { dispatch({ type: "START_RESIZE" }); }, []); const handleResizeStop = useCallback((e, direction$1, reference_, delta) => { dispatch({ type: "STOP_RESIZE" }); onSizeChange?.(delta, { height: reference_.style.height, width: reference_.style.width }); }, [onSizeChange]); const inner = useMemo(() => /* @__PURE__ */ jsx(Resizable, { ...sizeProps, className: cx(styles.panel, classNames?.content), enable: canResizing ? resizing : void 0, handleClasses: canResizing ? { [reversePlacement(internalPlacement)]: cx(handleVariants({ placement: reversePlacement(internalPlacement) }), showHandleHighlight && styles.handleHighlight) } : {}, onResize: handleResize, onResizeStart: handleResizeStart, onResizeStop: handleResizeStop, style: { ...cssVariables, opacity: isPending ? .95 : 1, transition: state.isResizing ? "unset" : void 0, ...style }, children }), [ sizeProps, styles.panel, classNames?.content, canResizing, resizing, internalPlacement, handleVariants, showHandleHighlight, styles.handleHighlight, handleResize, handleResizeStart, handleResizeStop, state.isResizing, isPending, style, children, cx ]); if (fullscreen) return /* @__PURE__ */ jsx("div", { className: cx(styles.fullscreen, className), style: cssVariables, children }); return /* @__PURE__ */ jsxs("aside", { className: cx(panelVariants({ isExpand, mode, placement: internalPlacement, showBorder }), className), dir, ref, style: cssVariables, children: [expandable && state.showExpand && handle, destroyOnClose ? isExpand && inner : inner] }); }, isEqual); DraggablePanel.displayName = "DraggablePanel"; var DraggablePanel_default = DraggablePanel; //#endregion export { DraggablePanel_default as default }; //# sourceMappingURL=DraggablePanel.mjs.map