UNPKG

@lobehub/ui

Version:

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

342 lines (341 loc) • 11.1 kB
"use client"; import { useMotionComponent } from "../../MotionProvider/index.mjs"; import ActionIcon from "../ActionIcon/ActionIcon.mjs"; import { DraggablePanelContext, useDraggablePanelContext } from "./context.mjs"; import { AXES } from "./core/axes.mjs"; import { coarsePointer, handleSize } from "./core/env.mjs"; import { timing } from "./core/transition.mjs"; import { createPanelController } from "./core/controller.mjs"; import { useIsomorphicLayoutEffect, useStore } from "./core/internal.mjs"; import { SEAM_ARROW, handleVariants, rootVariants, styles, toggleVariants } from "./style.mjs"; import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; import { cx } from "antd-style"; import { useTransform } from "motion/react"; import useMergeState from "use-merge-value"; import { ChevronLeft } from "lucide-react"; //#region src/base-ui/DraggablePanel/atoms.tsx const PAN_THRESHOLD = 3; const ARROW_BOX = (SEAM_ARROW.half + SEAM_ARROW.lead) * 2; const ARROW_C = ARROW_BOX / 2; const ARROW_PATH = (() => { const { depth: d, half: h, lead } = SEAM_ARROW; const c = ARROW_C; const r = .12; return [ `M${c} ${c - h - lead}`, `Q${c} ${c - h} ${c - d * r} ${c - h + h * r}`, `L${c - d * .88} ${c - h * r}`, `Q${c - d} ${c} ${c - d * .88} ${c + h * r}`, `L${c - d * r} ${c + h - h * r}`, `Q${c} ${c + h} ${c} ${c + h + lead}` ].join(" "); })(); const bendSign = (placement, expand) => (placement === "left" || placement === "top" ? 1 : -1) * (expand ? 1 : -1); const ORIGIN_MAP = { bottom: "center bottom", left: "left center", right: "right center", top: "center top" }; const COLLAPSED_SCALE = .97; const EDGE_INSET = { bottom: "insetBlockEnd", left: "insetInlineStart", right: "insetInlineEnd", top: "insetBlockStart" }; const DraggablePanelRoot = memo(({ backgroundColor, children, className, collapseThreshold, defaultExpand = true, defaultSize, expand, expandable = true, max, min = 0, mode = "fixed", onExpandChange, onSizeChange, onSizeDragging, placement = "right", showBorder = true, size, style, ...rest }) => { const axis = AXES[placement]; const fallbackSize = axis.vertical ? 180 : 280; const resolvedDefaultSize = defaultSize ?? fallbackSize; const [isExpand, setIsExpand] = useMergeState(defaultExpand, { onChange: onExpandChange, value: expand }); const [innerSize, setInnerSize] = useState(resolvedDefaultSize); const currentSize = size ?? innerSize; const options = { collapseThreshold, defaultSize: resolvedDefaultSize, expand: isExpand, max, min, onExpandChange: setIsExpand, onSizeChange: useCallback((next, delta) => { if (size === void 0) setInnerSize(next); onSizeChange?.(next, delta); }, [onSizeChange, size]), onSizeDragging, placement, size: currentSize }; const [controller] = useState(() => createPanelController(options)); const state = useStore(controller.subscribe, () => controller.state); const elementRef = useRef(null); useIsomorphicLayoutEffect(() => { controller.sync(options); }); useIsomorphicLayoutEffect(() => { const node = elementRef.current; return node ? controller.attach(node) : void 0; }, [controller]); const toggleExpand = useCallback(() => { if (expandable) setIsExpand(!isExpand); }, [ expandable, isExpand, setIsExpand ]); const context = useMemo(() => ({ axis, controller, expand: isExpand, expandable, placement, showBorder, state, toggleExpand }), [ axis, controller, isExpand, expandable, placement, showBorder, state, toggleExpand ]); return /* @__PURE__ */ jsx(DraggablePanelContext, { value: context, children: /* @__PURE__ */ jsx("aside", { className: cx(rootVariants({ mode, placement }), className), "data-expand": isExpand, "data-expandable": expandable, "data-resizing": state.dragging, ref: elementRef, style: { "--draggable-panel-bg": backgroundColor || "", "flexDirection": axis.vertical ? "column" : "row", ...style }, ...rest, children }) }); }); DraggablePanelRoot.displayName = "DraggablePanelRoot"; const DraggablePanelContent = memo(({ children, className, style, ...rest }) => { const Motion = useMotionComponent(); const { axis, controller, expand, placement, state } = useDraggablePanelContext(); const extent = useTransform(controller.motion.size, (value) => Math.max(0, value)); const target = useStore(controller.subscribe, () => controller.target); const clipping = state.dragging || state.folding || target === 0; const shrunk = state.collapsing || !expand; return /* @__PURE__ */ jsx(Motion.div, { inert: !expand, style: { display: "flex", flexDirection: axis.vertical ? "column" : "row", flexShrink: 0, justifyContent: axis.anchorEnd ? "flex-end" : "flex-start", overflow: clipping ? "clip" : "visible", [axis.cross]: "100%", [axis.extent]: extent }, children: /* @__PURE__ */ jsx(Motion.div, { animate: { scale: shrunk ? COLLAPSED_SCALE : 1 }, className: cx(styles.content, className), transition: timing(), style: { transformOrigin: ORIGIN_MAP[placement], [axis.cross]: "100%", [axis.extent]: controller.motion.content, ...style }, ...rest, children }) }); }); DraggablePanelContent.displayName = "DraggablePanelContent"; const DraggablePanelHandle = memo(({ className, style, wideArea = true, ...rest }) => { const { axis, controller, expand, showBorder, state } = useDraggablePanelContext(); const size = useStore(coarsePointer.subscribe, () => handleSize(wideArea)); const target = useStore(controller.subscribe, () => controller.target); useEffect(() => () => controller.drag.cancel(), [controller]); const pressedRef = useRef(null); const draggingRef = useRef(false); const draggedRef = useRef(false); const { max, min } = controller.bounds(); if (!expand) return null; return /* @__PURE__ */ jsx("div", { "aria-orientation": axis.ariaOrientation, "aria-valuemax": Number.isFinite(max) ? max : void 0, "aria-valuemin": min, "aria-valuenow": target, "aria-valuetext": `${target} pixels`, className: cx(handleVariants({ edge: axis.edge }), className), "data-border": showBorder, "data-resizing": state.dragging || void 0, role: "separator", tabIndex: 0, style: { "--draggable-panel-handle-size": `${size}px`, [EDGE_INSET[axis.edge]]: -size / 2, ...style }, onKeyDown: (event) => controller.resizeByKey(event), onDoubleClick: () => { if (!draggedRef.current) controller.reset(); }, onLostPointerCapture: () => { if (draggingRef.current) controller.drag.end(); pressedRef.current = null; draggingRef.current = false; }, onPointerCancel: () => { controller.drag.cancel(); pressedRef.current = null; draggingRef.current = false; }, onPointerDown: (event) => { draggedRef.current = false; pressedRef.current = { x: event.clientX, y: event.clientY }; try { event.currentTarget.setPointerCapture(event.pointerId); } catch {} }, onPointerMove: (event) => { if (!pressedRef.current) return; const offset = { x: event.clientX - pressedRef.current.x, y: event.clientY - pressedRef.current.y }; if (!draggingRef.current) { if (Math.hypot(offset.x, offset.y) < PAN_THRESHOLD) return; draggingRef.current = true; draggedRef.current = true; controller.drag.start(); } controller.drag.move(offset); }, onPointerUp: () => { if (draggingRef.current) controller.drag.end(); pressedRef.current = null; draggingRef.current = false; }, ...rest }); }); DraggablePanelHandle.displayName = "DraggablePanelHandle"; const DraggablePanelToggle = memo(({ className, showHandleWhenCollapsed, style, ...rest }) => { const { axis, expand, expandable, placement, showBorder, toggleExpand } = useDraggablePanelContext(); const gradientId = `seam-arrow-${useId().replaceAll(/[^\w-]/g, "")}`; if (!expandable) return null; const bend = bendSign(placement, expand); const edgeOpacity = expand && showBorder ? 1 : 0; return /* @__PURE__ */ jsx("div", { className: cx(toggleVariants({ placement }), className), style: { opacity: expand ? void 0 : showHandleWhenCollapsed ? 1 : 0, ...style }, ...rest, children: /* @__PURE__ */ jsx("button", { "aria-label": expand ? "Collapse panel" : "Expand panel", type: "button", onClick: toggleExpand, children: /* @__PURE__ */ jsxs("svg", { fill: "none", height: ARROW_BOX, style: { "--seam-bend": bend }, viewBox: `0 0 ${ARROW_BOX} ${ARROW_BOX}`, width: ARROW_BOX, children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { gradientUnits: "userSpaceOnUse", id: gradientId, x1: ARROW_C, x2: ARROW_C, y1: 0, y2: ARROW_BOX, children: [ /* @__PURE__ */ jsx("stop", { "data-end": "", offset: 0, stopOpacity: edgeOpacity }), /* @__PURE__ */ jsx("stop", { "data-tip": "", offset: .3 }), /* @__PURE__ */ jsx("stop", { "data-tip": "", offset: .7 }), /* @__PURE__ */ jsx("stop", { "data-end": "", offset: 1, stopOpacity: edgeOpacity }) ] }) }), /* @__PURE__ */ jsx("g", { transform: axis.vertical ? `rotate(90 ${ARROW_C} ${ARROW_C})` : void 0, children: /* @__PURE__ */ jsx("path", { d: ARROW_PATH, stroke: `url(#${gradientId})`, strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: SEAM_ARROW.stroke }) })] }) }) }); }); DraggablePanelToggle.displayName = "DraggablePanelToggle"; const DraggablePanelContainer = memo(({ className, ...rest }) => /* @__PURE__ */ jsx("div", { className: cx(styles.container, className), ...rest })); DraggablePanelContainer.displayName = "DraggablePanelContainer"; const DraggablePanelBody = memo(({ className, style, ...rest }) => /* @__PURE__ */ jsx("div", { className: cx(styles.body, className), style: { flex: 1, ...style }, ...rest })); DraggablePanelBody.displayName = "DraggablePanelBody"; const DraggablePanelFooter = memo(({ className, ...rest }) => /* @__PURE__ */ jsx("div", { className: cx(styles.footer, className), ...rest })); DraggablePanelFooter.displayName = "DraggablePanelFooter"; const DraggablePanelHeader = memo(({ className, extra, onCollapse, title, ...rest }) => { const { toggleExpand } = useDraggablePanelContext(); return /* @__PURE__ */ jsxs("div", { className: cx(styles.header, className), ...rest, children: [ /* @__PURE__ */ jsx(ActionIcon, { icon: ChevronLeft, size: "small", onClick: onCollapse ?? toggleExpand }), title, extra ] }); }); DraggablePanelHeader.displayName = "DraggablePanelHeader"; //#endregion export { DraggablePanelBody, DraggablePanelContainer, DraggablePanelContent, DraggablePanelFooter, DraggablePanelHandle, DraggablePanelHeader, DraggablePanelRoot, DraggablePanelToggle }; //# sourceMappingURL=atoms.mjs.map