diff --git a/package-lock.json b/package-lock.json index 3f580b2..01705f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@base-ui/react": "^1.9.0", "class-variance-authority": "^0.7.1", "cn": "^0.5.0", + "embla-carousel-react": "^8.6.0", "lucide-react": "^1.60.0", "next": "16.4.2", "react": "19.3.0", @@ -4293,6 +4294,34 @@ "integrity": "sha512-rRaVC7bNUyGsL+GMtmZfv3AfgG6za7gE9v5bT0PIbjEerddA0IQ+tCK05NFAXqCpNlrxBZqbFqquyxHRQONHTA==", "license": "ISC" }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "license": "MIT" + }, + "node_modules/embla-carousel-react": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", + "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", + "license": "MIT", + "dependencies": { + "embla-carousel": "8.6.0", + "embla-carousel-reactive-utils": "8.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", + "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", diff --git a/package.json b/package.json index ff478f8..609d104 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@base-ui/react": "^1.9.0", "class-variance-authority": "^0.7.1", "cn": "^0.5.0", + "embla-carousel-react": "^8.6.0", "lucide-react": "^1.60.0", "next": "16.4.2", "react": "19.3.0", diff --git a/src/app/page.js b/src/app/page.js index 29bf96b..599fcbb 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -1,69 +1,40 @@ -import Image from "next/image"; +import * as React from "react" -export default function Home() { +import { Card, CardContent } from "@/components/ui/card" +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel" + +export async function CarouselDemo() { +const res = await fetch("https://db.jacekr.tech/api/collections/klasy/records?perPage=50"); + +if (!res.ok) { + throw new Error("Failed to fetch data"); +} + +const data = await res.json(); + return ( -
-
- Next.js logo -
-

- To get started, edit the{" "} - - page.js - {" "} - file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
-
- - Vercel logomark - Deploy Now - - - |Jacek - -
-
-
- ); + + + {data.map((_, index) => ( + +
+ + + {index + 1} + + +
+
+ ))} +
+ + +
+ ) } diff --git a/src/components/ui/carousel.jsx b/src/components/ui/carousel.jsx new file mode 100644 index 0000000..0716083 --- /dev/null +++ b/src/components/ui/carousel.jsx @@ -0,0 +1,216 @@ +"use client"; +import * as React from "react" +import { cn } from "cn" +import useEmblaCarousel from "embla-carousel-react"; + +import { Button } from "@/components/ui/button" +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react" + +const CarouselContext = React.createContext(null) + +function useCarousel() { + const context = React.useContext(CarouselContext) + + if (!context) { + throw new Error("useCarousel must be used within a ") + } + + return context +} + +function Carousel({ + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props +}) { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins + ) + const [canScrollPrev, setCanScrollPrev] = React.useState(false) + const [canScrollNext, setCanScrollNext] = React.useState(false) + + const onSelect = React.useCallback((api) => { + if (!api) return + setCanScrollPrev(api.canScrollPrev()) + setCanScrollNext(api.canScrollNext()) + }, []) + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev() + }, [api]) + + const scrollNext = React.useCallback(() => { + api?.scrollNext() + }, [api]) + + const handleKeyDown = React.useCallback( + (event) => { + if (event.key === "ArrowLeft") { + event.preventDefault() + scrollPrev() + } else if (event.key === "ArrowRight") { + event.preventDefault() + scrollNext() + } + }, + [scrollPrev, scrollNext] + ) + + React.useEffect(() => { + if (!api || !setApi) return + setApi(api) + }, [api, setApi]) + + React.useEffect(() => { + if (!api) return + onSelect(api) + api.on("reInit", onSelect) + api.on("select", onSelect) + + return () => { + api?.off("select", onSelect) + } + }, [api, onSelect]) + + return ( + +
+ {children} +
+
+ ) +} + +function CarouselContent({ + className, + ...props +}) { + const { carouselRef, orientation } = useCarousel() + + return ( +
+
+
+ ) +} + +function CarouselItem({ + className, + ...props +}) { + const { orientation } = useCarousel() + + return ( +
+ ) +} + +function CarouselPrevious({ + className, + variant = "outline", + size = "icon-sm", + ...props +}) { + const { orientation, scrollPrev, canScrollPrev } = useCarousel() + + return ( + + ) +} + +function CarouselNext({ + className, + variant = "outline", + size = "icon-sm", + ...props +}) { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +} + +export { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, useCarousel };