forked from emre/www_projectmycelium_com
		
	- Removed unused DummyContent component and content prop from slider data - Simplified Card component mapping by removing unnecessary index parameter - Cleaned up whitespace in apple-cards-carousel component - Streamlined data structure by removing unused content fields from slider items
		
			
				
	
	
		
			197 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
"use client";
 | 
						|
import React, {
 | 
						|
  useEffect,
 | 
						|
  useState,
 | 
						|
} from "react";
 | 
						|
import {
 | 
						|
  IconArrowNarrowLeft,
 | 
						|
  IconArrowNarrowRight,
 | 
						|
  IconChevronRight,
 | 
						|
} from "@tabler/icons-react";
 | 
						|
import { cn } from "@/lib/utils";
 | 
						|
import { Link } from "react-router-dom";
 | 
						|
import { motion } from "motion/react";
 | 
						|
 | 
						|
interface CarouselProps {
 | 
						|
  items: JSX.Element[];
 | 
						|
  initialScroll?: number;
 | 
						|
}
 | 
						|
 | 
						|
type Card = {
 | 
						|
  src: string;
 | 
						|
  title: string;
 | 
						|
  category: string;
 | 
						|
  description: string;
 | 
						|
  link: string;
 | 
						|
  bg: any;
 | 
						|
};
 | 
						|
 | 
						|
export const Carousel = ({ items, initialScroll = 0 }: CarouselProps) => {
 | 
						|
  const carouselRef = React.useRef<HTMLDivElement>(null);
 | 
						|
  const [canScrollLeft, setCanScrollLeft] = React.useState(false);
 | 
						|
  const [canScrollRight, setCanScrollRight] = React.useState(true);
 | 
						|
 | 
						|
  useEffect(() => {
 | 
						|
    if (carouselRef.current) {
 | 
						|
      carouselRef.current.scrollLeft = initialScroll;
 | 
						|
      checkScrollability();
 | 
						|
    }
 | 
						|
  }, [initialScroll]);
 | 
						|
 | 
						|
  const checkScrollability = () => {
 | 
						|
    if (carouselRef.current) {
 | 
						|
      const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current;
 | 
						|
      setCanScrollLeft(scrollLeft > 0);
 | 
						|
      setCanScrollRight(scrollLeft < scrollWidth - clientWidth);
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  const scrollLeft = () => {
 | 
						|
    if (carouselRef.current) {
 | 
						|
      carouselRef.current.scrollBy({ left: -300, behavior: "smooth" });
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  const scrollRight = () => {
 | 
						|
    if (carouselRef.current) {
 | 
						|
      carouselRef.current.scrollBy({ left: 300, behavior: "smooth" });
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  return (
 | 
						|
      <div className="relative w-full">
 | 
						|
        <div
 | 
						|
          className="flex w-full overflow-x-scroll overscroll-x-auto scroll-smooth py-10 [scrollbar-width:none] md:py-20"
 | 
						|
          ref={carouselRef}
 | 
						|
          onScroll={checkScrollability}
 | 
						|
        >
 | 
						|
          <div
 | 
						|
            className={cn(
 | 
						|
              "absolute right-0 z-[1000] h-auto w-[5%] overflow-hidden bg-gradient-to-l",
 | 
						|
            )}
 | 
						|
          ></div>
 | 
						|
 | 
						|
          <div
 | 
						|
            className={cn(
 | 
						|
              "flex flex-row justify-start gap-4 pl-4",
 | 
						|
              "mx-auto max-w-7xl", // remove max-w-4xl if you want the carousel to span the full width of its container
 | 
						|
            )}
 | 
						|
          >
 | 
						|
            {items.map((item, index) => (
 | 
						|
              <motion.div
 | 
						|
                initial={{
 | 
						|
                  opacity: 0,
 | 
						|
                  y: 20,
 | 
						|
                }}
 | 
						|
                animate={{
 | 
						|
                  opacity: 1,
 | 
						|
                  y: 0,
 | 
						|
                  transition: {
 | 
						|
                    duration: 0.5,
 | 
						|
                    delay: 0.2 * index,
 | 
						|
                    ease: "easeOut",
 | 
						|
                  },
 | 
						|
                }}
 | 
						|
                key={"card" + index}
 | 
						|
                className="rounded-3xl last:pr-[5%] md:last:pr-[33%]"
 | 
						|
              >
 | 
						|
                {item}
 | 
						|
              </motion.div>
 | 
						|
            ))}
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
        <div className="mr-10 flex justify-end gap-2">
 | 
						|
          <button
 | 
						|
            className="relative z-40 flex h-10 w-10 items-center justify-center rounded-full bg-neutral-800 disabled:opacity-50"
 | 
						|
            onClick={scrollLeft}
 | 
						|
            disabled={!canScrollLeft}
 | 
						|
          >
 | 
						|
            <IconArrowNarrowLeft className="h-6 w-6 text-white" />
 | 
						|
          </button>
 | 
						|
          <button
 | 
						|
            className="relative z-40 flex h-10 w-10 items-center justify-center rounded-full bg-neutral-800 disabled:opacity-50"
 | 
						|
            onClick={scrollRight}
 | 
						|
            disabled={!canScrollRight}
 | 
						|
          >
 | 
						|
            <IconArrowNarrowRight className="h-6 w-6 text-white" />
 | 
						|
          </button>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
export const Card = ({
 | 
						|
  card,
 | 
						|
  layout = false,
 | 
						|
}: {
 | 
						|
  card: Card;
 | 
						|
  layout?: boolean;
 | 
						|
}) => {
 | 
						|
  
 | 
						|
  return (
 | 
						|
    <Link to={card.link}>
 | 
						|
      <motion.div
 | 
						|
        layoutId={layout ? `card-${card.title}` : undefined}
 | 
						|
        className="relative z-10 flex h-60 w-56 flex-col items-start justify-start overflow-hidden rounded-3xl md:h-120 md:w-96 hover:scale-105 transition-transform duration-200"
 | 
						|
        style={{
 | 
						|
          backgroundImage: `url(${card.bg})`,
 | 
						|
          backgroundSize: 'cover',
 | 
						|
          backgroundPosition: 'center',
 | 
						|
        }}
 | 
						|
      >
 | 
						|
        <div className="pointer-events-none absolute inset-x-0 top-0 z-30 h-full bg-gradient-to-b from-black/50 via-transparent to-transparent" />
 | 
						|
        <div className="relative z-40 p-8 w-full">
 | 
						|
          <motion.p
 | 
						|
            layoutId={layout ? `category-${card.category}` : undefined}
 | 
						|
            className="text-left font-sans text-sm font-medium text-white md:text-base"
 | 
						|
          >
 | 
						|
            {card.category}
 | 
						|
          </motion.p>
 | 
						|
          <motion.p
 | 
						|
            layoutId={layout ? `title-${card.title}` : undefined}
 | 
						|
            className="mt-2 max-w-xs text-left font-sans text-xl font-semibold [text-wrap:balance] text-white md:text-3xl"
 | 
						|
          >
 | 
						|
            {card.title}
 | 
						|
          </motion.p>
 | 
						|
          <div className="flex flex-row justify-between items-center w-full mt-4">
 | 
						|
            <motion.p className="max-w-xs text-left font-sans text-sm text-neutral-300">
 | 
						|
              {card.description}
 | 
						|
            </motion.p>
 | 
						|
            <div className="h-8 w-8 bg-[#212121] rounded-full flex items-center justify-center text-[#858585] shrink-0 hover:bg-[#262626] hover:text-white active:bg-[#262626] active:text-white transition-colors duration-200">
 | 
						|
              <IconChevronRight className="h-6 w-6" />
 | 
						|
            </div>
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
      </motion.div>
 | 
						|
    </Link>
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
export const BlurImage = ({
 | 
						|
  src,
 | 
						|
  className,
 | 
						|
  width,
 | 
						|
  height,
 | 
						|
  alt,
 | 
						|
  ...rest
 | 
						|
}: React.ImgHTMLAttributes<HTMLImageElement>) => {
 | 
						|
  const [isLoading, setLoading] = useState(true);
 | 
						|
  return (
 | 
						|
    <img
 | 
						|
      className={cn(
 | 
						|
        "h-full w-full transition duration-300",
 | 
						|
        isLoading ? "blur-sm" : "blur-0",
 | 
						|
        className,
 | 
						|
      )}
 | 
						|
      onLoad={() => setLoading(false)}
 | 
						|
      src={src as string}
 | 
						|
      width={width}
 | 
						|
      height={height}
 | 
						|
      loading="lazy"
 | 
						|
      decoding="async"
 | 
						|
      alt={alt ? alt : "Background of a beautiful view"}
 | 
						|
      {...rest}
 | 
						|
    />
 | 
						|
  );
 | 
						|
};
 |