Merge version_5_1776255067449 into main #5
@@ -35,7 +35,11 @@ export default function App() {
|
||||
text: "Browse Menu", href: "#features"}}
|
||||
secondaryButton={{
|
||||
text: "Visit Us", href: "#contact"}}
|
||||
imageSrc="https://images.unsplash.com/photo-1681838675911-625ee52549f5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4Mzc5ODl8MHwxfHNlYXJjaHwxNHx8YmFrZXJ5JTIwd2luZG93JTIwd2l0aCUyMHdhcm0lMjBsaWdodGluZ3xlbnwxfDB8fHwxNzc2MjUzMTMzfDA&ixlib=rb-4.1.0&q=80&w=1080"
|
||||
images={[
|
||||
"https://images.unsplash.com/photo-1681838675911-625ee52549f5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4Mzc5ODl8MHwxfHNlYXJjaHwxNHx8YmFrZXJ5JTIwd2luZG93JTIwd2l0aCUyMHdhcm0lMjBsaWdodGluZ3xlbnwxfDB8fHwxNzc2MjUzMTMzfDA&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
"https://images.unsplash.com/photo-1555507036-ab1f4038808a?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||
"https://images.unsplash.com/photo-1568254183919-78a4f43a2877?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { motion } from "motion/react";
|
||||
import Button from "@/components/ui/Button";
|
||||
import TextAnimation from "@/components/ui/TextAnimation";
|
||||
import ImageOrVideo from "@/components/ui/ImageOrVideo";
|
||||
import ImageSlider from "@/components/ui/ImageSlider";
|
||||
|
||||
type HeroSplitProps = {
|
||||
tag: string;
|
||||
@@ -9,7 +9,8 @@ type HeroSplitProps = {
|
||||
description: string;
|
||||
primaryButton: { text: string; href: string };
|
||||
secondaryButton: { text: string; href: string };
|
||||
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
|
||||
images: string[];
|
||||
};
|
||||
|
||||
const HeroSplit = ({
|
||||
tag,
|
||||
@@ -17,8 +18,7 @@ const HeroSplit = ({
|
||||
description,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
imageSrc,
|
||||
videoSrc,
|
||||
images,
|
||||
}: HeroSplitProps) => {
|
||||
return (
|
||||
<section aria-label="Hero section" className="flex items-center h-fit md:h-svh pt-25 pb-20 md:py-0">
|
||||
@@ -54,7 +54,7 @@ const HeroSplit = ({
|
||||
transition={{ duration: 0.6, ease: "easeOut", delay: 0.2 }}
|
||||
className="w-full md:w-1/2 h-100 md:h-[65vh] md:max-h-[75svh] p-5 card rounded overflow-hidden"
|
||||
>
|
||||
<ImageOrVideo imageSrc={imageSrc} videoSrc={videoSrc} />
|
||||
<ImageSlider images={images} />
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
30
src/components/ui/ImageSlider.tsx
Normal file
30
src/components/ui/ImageSlider.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useCarouselControls } from "@/hooks/useCarouselControls";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
|
||||
type ImageSliderProps = {
|
||||
images: string[];
|
||||
};
|
||||
|
||||
const ImageSlider = ({ images }: ImageSliderProps) => {
|
||||
const { currentIndex, prev, next } = useCarouselControls(images.length);
|
||||
|
||||
return (
|
||||
<div className="image-slider">
|
||||
<div className="slides-container" style={{ transform: `translateX(-${currentIndex * 100}%)` }}>
|
||||
{images.map((src, index) => (
|
||||
<div className="slide" key={index}>
|
||||
<img src={src} alt={`Slide ${index + 1}`} className="w-full h-full object-cover" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button onClick={prev} className="slider-control prev">
|
||||
<ChevronLeft />
|
||||
</button>
|
||||
<button onClick={next} className="slider-control next">
|
||||
<ChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageSlider;
|
||||
@@ -1,45 +1,15 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import type { EmblaCarouselType } from "embla-carousel";
|
||||
import { useState } from "react";
|
||||
|
||||
export const useCarouselControls = (emblaApi: EmblaCarouselType | undefined) => {
|
||||
const [prevDisabled, setPrevDisabled] = useState(true);
|
||||
const [nextDisabled, setNextDisabled] = useState(true);
|
||||
const [scrollProgress, setScrollProgress] = useState(0);
|
||||
export const useCarouselControls = (totalSlides: number) => {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
|
||||
const scrollPrev = useCallback(() => {
|
||||
if (!emblaApi) return;
|
||||
emblaApi.scrollPrev();
|
||||
}, [emblaApi]);
|
||||
const next = () => {
|
||||
setCurrentIndex((prevIndex) => (prevIndex + 1) % totalSlides);
|
||||
};
|
||||
|
||||
const scrollNext = useCallback(() => {
|
||||
if (!emblaApi) return;
|
||||
emblaApi.scrollNext();
|
||||
}, [emblaApi]);
|
||||
const prev = () => {
|
||||
setCurrentIndex((prevIndex) => (prevIndex - 1 + totalSlides) % totalSlides);
|
||||
};
|
||||
|
||||
const onSelect = useCallback((api: EmblaCarouselType) => {
|
||||
setPrevDisabled(!api.canScrollPrev());
|
||||
setNextDisabled(!api.canScrollNext());
|
||||
}, []);
|
||||
|
||||
const onScroll = useCallback((api: EmblaCarouselType) => {
|
||||
const progress = Math.max(0, Math.min(1, api.scrollProgress()));
|
||||
setScrollProgress(progress * 100);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return;
|
||||
|
||||
onSelect(emblaApi);
|
||||
onScroll(emblaApi);
|
||||
|
||||
emblaApi.on("reInit", onSelect).on("select", onSelect);
|
||||
emblaApi.on("reInit", onScroll).on("scroll", onScroll);
|
||||
|
||||
return () => {
|
||||
emblaApi.off("reInit", onSelect).off("select", onSelect);
|
||||
emblaApi.off("reInit", onScroll).off("scroll", onScroll);
|
||||
};
|
||||
}, [emblaApi, onSelect, onScroll]);
|
||||
|
||||
return { prevDisabled, nextDisabled, scrollPrev, scrollNext, scrollProgress };
|
||||
};
|
||||
return { currentIndex, next, prev };
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
@import "tailwindcss";
|
||||
@import "./styles/masks.css";
|
||||
@import "./styles/animations.css";
|
||||
@import "./styles/slider.css";
|
||||
|
||||
:root {
|
||||
/* @colorThemes/lightTheme/grayBlueAccent */
|
||||
|
||||
Reference in New Issue
Block a user