114 lines
3.0 KiB
TypeScript
114 lines
3.0 KiB
TypeScript
import React, { type PropsWithChildren, useState } from "react";
|
|
import Button from "./ui/Button";
|
|
import { useSwipeable } from "react-swipeable";
|
|
import { motion } from "motion/react";
|
|
import clsx from "clsx";
|
|
|
|
interface SliderItemProps {
|
|
text: string;
|
|
}
|
|
|
|
function flattenChildren(children: React.ReactNode): React.ReactElement[] {
|
|
const result: React.ReactElement[] = [];
|
|
|
|
React.Children.forEach(children, (child) => {
|
|
if (React.isValidElement(child)) {
|
|
if (child.type === React.Fragment) {
|
|
result.push(
|
|
...flattenChildren(
|
|
(child.props as { children?: React.ReactNode }).children
|
|
)
|
|
);
|
|
} else {
|
|
result.push(child);
|
|
}
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
function NewUnitSlider({ children }: PropsWithChildren) {
|
|
const [currentSlide, setCurrentSlide] = useState(0);
|
|
|
|
const flattenedChildren = flattenChildren(children);
|
|
const slides = flattenedChildren.map((element, index) => {
|
|
if (
|
|
React.isValidElement<PropsWithChildren<SliderItemProps>>(element) &&
|
|
element.props.text
|
|
) {
|
|
return {
|
|
index,
|
|
element,
|
|
text: element.props.text,
|
|
child: element.props.children,
|
|
};
|
|
}
|
|
return {
|
|
index,
|
|
text: `Slide ${index + 1}`,
|
|
element,
|
|
};
|
|
});
|
|
|
|
const handlers = useSwipeable({
|
|
onSwipedLeft: () => {
|
|
const slideText = slides[currentSlide]?.text;
|
|
if (slideText !== "Interior") {
|
|
setCurrentSlide(Math.min(currentSlide + 1, slides.length - 1));
|
|
}
|
|
},
|
|
onSwipedRight: () => setCurrentSlide(Math.max(currentSlide - 1, 0)),
|
|
preventScrollOnSwipe: true,
|
|
touchEventOptions: {
|
|
passive: false,
|
|
},
|
|
trackMouse: true,
|
|
});
|
|
|
|
return (
|
|
<div
|
|
className="relative size-full overflow-hidden 2xl:rounded-[1.111vw] rounded-xl border border-[#E2E2DC]"
|
|
{...handlers}
|
|
>
|
|
<motion.div
|
|
animate={{
|
|
x: `calc(-${currentSlide} * 100%)`,
|
|
}}
|
|
transition={{
|
|
duration: 0.5,
|
|
ease: "easeInOut",
|
|
}}
|
|
className="flex relative top-0 size-full"
|
|
>
|
|
{slides.map((slide) => slide.element)}
|
|
</motion.div>
|
|
<div className="absolute flex 2xl:gap-[0.278vw] gap-1 items-center 2xl:bottom-[1.667vw] md:max-2xl:bottom-6 left-1/2 -translate-x-1/2 max-md:hidden">
|
|
{slides.map((slide, index) => (
|
|
<Button
|
|
key={slide.text}
|
|
variant={currentSlide === index ? "cta" : "primary"}
|
|
onClick={() => setCurrentSlide(index)}
|
|
className="max-md:hidden"
|
|
>
|
|
{slide.text}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
<div className="flex absolute bottom-4 left-1/2 gap-1 -translate-x-1/2 md:hidden">
|
|
{slides.map((_, index) => (
|
|
<div
|
|
key={index}
|
|
className={clsx(
|
|
"size-2 rounded-full transition-colors duration-300",
|
|
currentSlide === index ? "bg-[#00BED7]" : "bg-[#E2E2DC]"
|
|
)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default NewUnitSlider;
|