mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
Merge pull request [#459](https://github.com/mazeincoding/AppCut/issues/459)
This commit is contained in:
@@ -1,163 +1,104 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useRef, useEffect } from "react";
|
import {
|
||||||
import { motion, useMotionValue, useTransform, PanInfo } from "motion/react";
|
motion,
|
||||||
|
useMotionTemplate,
|
||||||
|
useMotionValue,
|
||||||
|
useMotionValueEvent,
|
||||||
|
useTransform,
|
||||||
|
} from "motion/react";
|
||||||
|
import { type PropsWithChildren, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
interface HandlebarsProps {
|
type HandlebarsProps = PropsWithChildren;
|
||||||
children: React.ReactNode;
|
|
||||||
minWidth?: number;
|
export function Handlebars({ children }: HandlebarsProps) {
|
||||||
maxWidth?: number;
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
onRangeChange?: (left: number, right: number) => void;
|
|
||||||
}
|
const [width, setWidth] = useState(0);
|
||||||
|
|
||||||
export function Handlebars({
|
|
||||||
children,
|
|
||||||
minWidth = 50,
|
|
||||||
maxWidth = 400,
|
|
||||||
onRangeChange,
|
|
||||||
}: HandlebarsProps) {
|
|
||||||
const [leftHandle, setLeftHandle] = useState(0);
|
const [leftHandle, setLeftHandle] = useState(0);
|
||||||
const [rightHandle, setRightHandle] = useState(maxWidth);
|
const [rightHandle, setRightHandle] = useState(width);
|
||||||
const [contentWidth, setContentWidth] = useState(maxWidth);
|
|
||||||
|
|
||||||
const leftHandleX = useMotionValue(0);
|
const leftHandleX = useMotionValue(0);
|
||||||
const rightHandleX = useMotionValue(maxWidth);
|
const rightHandleX = useMotionValue(width);
|
||||||
|
|
||||||
const visibleWidth = useTransform(
|
|
||||||
[leftHandleX, rightHandleX],
|
|
||||||
(values: number[]) => values[1] - values[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentLeft = useTransform(leftHandleX, (left: number) => -left);
|
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const measureRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!measureRef.current) return;
|
const el = containerRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
const measureContent = () => {
|
const observer = new ResizeObserver(() => {
|
||||||
if (measureRef.current) {
|
setWidth(el.offsetWidth);
|
||||||
const width = measureRef.current.scrollWidth;
|
setRightHandle(el.offsetWidth);
|
||||||
const paddedWidth = width + 32;
|
rightHandleX.set(el.offsetWidth);
|
||||||
setContentWidth(paddedWidth);
|
});
|
||||||
setRightHandle(paddedWidth);
|
|
||||||
rightHandleX.set(paddedWidth);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
measureContent();
|
observer.observe(el);
|
||||||
const timer = setTimeout(measureContent, 50);
|
return () => observer.disconnect();
|
||||||
|
}, [rightHandleX.set]);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
useMotionValueEvent(leftHandleX, "change", () => {
|
||||||
}, [children, rightHandleX]);
|
setLeftHandle(leftHandleX.get());
|
||||||
|
});
|
||||||
useEffect(() => {
|
useMotionValueEvent(rightHandleX, "change", () => {
|
||||||
leftHandleX.set(leftHandle);
|
setRightHandle(rightHandleX.get());
|
||||||
}, [leftHandle, leftHandleX]);
|
});
|
||||||
|
const leftGradient = useTransform(leftHandleX, [0, width - 10], [0, 100]);
|
||||||
useEffect(() => {
|
const rightGradient = useTransform(rightHandleX, [0, width + 10], [0, 100]);
|
||||||
rightHandleX.set(rightHandle);
|
|
||||||
}, [rightHandle, rightHandleX]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onRangeChange?.(leftHandle, rightHandle);
|
|
||||||
}, [leftHandle, rightHandle, onRangeChange]);
|
|
||||||
|
|
||||||
const handleLeftDrag = (event: any, info: PanInfo) => {
|
|
||||||
const newLeft = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(leftHandle + info.offset.x, rightHandle - minWidth)
|
|
||||||
);
|
|
||||||
setLeftHandle(newLeft);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRightDrag = (event: any, info: PanInfo) => {
|
|
||||||
const newRight = Math.max(
|
|
||||||
leftHandle + minWidth,
|
|
||||||
Math.min(contentWidth, rightHandle + info.offset.x)
|
|
||||||
);
|
|
||||||
setRightHandle(newRight);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center gap-4 leading-[4rem] mt-0 md:mt-2">
|
<div className="flex justify-center gap-4 leading-[4rem] mt-0 md:mt-2">
|
||||||
<div
|
<div ref={containerRef} className="relative -rotate-[2.76deg] mt-2">
|
||||||
ref={measureRef}
|
<div className="absolute inset-0 w-full h-full rounded-2xl border border-yellow-500 flex justify-between z-[1]">
|
||||||
className="absolute -left-[9999px] top-0 invisible px-4 whitespace-nowrap font-[inherit]"
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
ref={containerRef}
|
|
||||||
className="relative -rotate-[2.76deg] max-w-[250px] md:max-w-[454px] mt-2"
|
|
||||||
style={{ width: contentWidth }}
|
|
||||||
>
|
|
||||||
<div className="absolute inset-0 w-full h-full rounded-2xl border border-yellow-500 flex justify-between">
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="h-full border border-yellow-500 w-7 rounded-full bg-accent flex items-center justify-center cursor-ew-resize select-none"
|
className="absolute z-10 left-0 h-full border border-yellow-500 w-7 rounded-full bg-accent flex items-center justify-center select-none"
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
|
||||||
x: leftHandleX,
|
x: leftHandleX,
|
||||||
left: 0,
|
|
||||||
zIndex: 10,
|
|
||||||
}}
|
}}
|
||||||
drag="x"
|
drag="x"
|
||||||
dragConstraints={{ left: 0, right: rightHandle - minWidth }}
|
dragConstraints={{ left: 0, right: rightHandle - 60 }}
|
||||||
dragElastic={0}
|
dragElastic={0}
|
||||||
dragMomentum={false}
|
dragMomentum={false}
|
||||||
onDrag={handleLeftDrag}
|
whileHover={{ scale: 1.05, cursor: "grab" }}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileDrag={{ scale: 1.1, cursor: "grabbing" }}
|
||||||
whileDrag={{ scale: 1.1 }}
|
|
||||||
transition={{ type: "spring", stiffness: 400, damping: 30 }}
|
transition={{ type: "spring", stiffness: 400, damping: 30 }}
|
||||||
>
|
>
|
||||||
<div className="w-2 h-8 rounded-full bg-yellow-500" />
|
<div className="w-2 h-8 rounded-full bg-yellow-500"></div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="h-full border border-yellow-500 w-7 rounded-full bg-accent flex items-center justify-center cursor-ew-resize select-none"
|
className="absolute z-10 -left-[30px] h-full border border-yellow-500 w-7 rounded-full bg-accent flex items-center justify-center select-none"
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
|
||||||
x: rightHandleX,
|
x: rightHandleX,
|
||||||
left: -30,
|
|
||||||
zIndex: 10,
|
|
||||||
}}
|
}}
|
||||||
drag="x"
|
drag="x"
|
||||||
dragConstraints={{
|
dragConstraints={{
|
||||||
left: leftHandle + minWidth,
|
left: leftHandle + 60,
|
||||||
right: contentWidth,
|
right: width,
|
||||||
}}
|
}}
|
||||||
dragElastic={0}
|
dragElastic={0}
|
||||||
dragMomentum={false}
|
dragMomentum={false}
|
||||||
onDrag={handleRightDrag}
|
whileHover={{ scale: 1.05, cursor: "grab" }}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileDrag={{ scale: 1.1, cursor: "grabbing" }}
|
||||||
whileDrag={{ scale: 1.1 }}
|
|
||||||
transition={{ type: "spring", stiffness: 400, damping: 30 }}
|
transition={{ type: "spring", stiffness: 400, damping: 30 }}
|
||||||
>
|
>
|
||||||
<div className="w-2 h-8 rounded-full bg-yellow-500" />
|
<div className="w-2 h-8 rounded-full bg-yellow-500"></div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<motion.div
|
<motion.span
|
||||||
className="relative overflow-hidden rounded-2xl"
|
className="inline-flex items-center justify-center w-full h-full px-9 relative rounded-2xl will-change-auto"
|
||||||
style={{
|
style={{
|
||||||
width: visibleWidth,
|
mask: useMotionTemplate`linear-gradient(90deg,
|
||||||
x: leftHandleX,
|
rgba(255, 255, 255, 0) 0%,
|
||||||
height: "100%",
|
rgba(255, 255, 255, 0) ${leftGradient}%,
|
||||||
|
rgba(0, 0, 0) ${leftGradient}%,
|
||||||
|
rgba(0, 0, 0) ${rightGradient}%,
|
||||||
|
rgba(255, 255, 255, 0) ${rightGradient}%,
|
||||||
|
rgba(255, 255, 255, 0) 100%)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<motion.div
|
{children}
|
||||||
className="w-full h-full flex items-center justify-center px-4"
|
</motion.span>
|
||||||
style={{
|
|
||||||
x: contentLeft,
|
|
||||||
width: contentWidth,
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user