@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
342 lines (341 loc) • 11.1 kB
JavaScript
"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