stunning screenshots in seconds https://moocup.jaydip.me
at from-github 6.2 kB view raw
1import * as React from "react" 2import useEmblaCarousel, { 3 type UseEmblaCarouselType, 4} from "embla-carousel-react" 5import { ArrowLeft, ArrowRight } from "lucide-react" 6 7import { cn } from "@/lib/utils" 8import { Button } from "@/components/ui/button" 9 10type CarouselApi = UseEmblaCarouselType[1] 11type UseCarouselParameters = Parameters<typeof useEmblaCarousel> 12type CarouselOptions = UseCarouselParameters[0] 13type CarouselPlugin = UseCarouselParameters[1] 14 15type CarouselProps = { 16 opts?: CarouselOptions 17 plugins?: CarouselPlugin 18 orientation?: "horizontal" | "vertical" 19 setApi?: (api: CarouselApi) => void 20} 21 22type CarouselContextProps = { 23 carouselRef: ReturnType<typeof useEmblaCarousel>[0] 24 api: ReturnType<typeof useEmblaCarousel>[1] 25 scrollPrev: () => void 26 scrollNext: () => void 27 canScrollPrev: boolean 28 canScrollNext: boolean 29} & CarouselProps 30 31const CarouselContext = React.createContext<CarouselContextProps | null>(null) 32 33function useCarousel() { 34 const context = React.useContext(CarouselContext) 35 36 if (!context) { 37 throw new Error("useCarousel must be used within a <Carousel />") 38 } 39 40 return context 41} 42 43const Carousel = React.forwardRef< 44 HTMLDivElement, 45 React.HTMLAttributes<HTMLDivElement> & CarouselProps 46>( 47 ( 48 { 49 orientation = "horizontal", 50 opts, 51 setApi, 52 plugins, 53 className, 54 children, 55 ...props 56 }, 57 ref 58 ) => { 59 const [carouselRef, api] = useEmblaCarousel( 60 { 61 ...opts, 62 axis: orientation === "horizontal" ? "x" : "y", 63 }, 64 plugins 65 ) 66 const [canScrollPrev, setCanScrollPrev] = React.useState(false) 67 const [canScrollNext, setCanScrollNext] = React.useState(false) 68 69 const onSelect = React.useCallback((api: CarouselApi) => { 70 if (!api) { 71 return 72 } 73 74 setCanScrollPrev(api.canScrollPrev()) 75 setCanScrollNext(api.canScrollNext()) 76 }, []) 77 78 const scrollPrev = React.useCallback(() => { 79 api?.scrollPrev() 80 }, [api]) 81 82 const scrollNext = React.useCallback(() => { 83 api?.scrollNext() 84 }, [api]) 85 86 const handleKeyDown = React.useCallback( 87 (event: React.KeyboardEvent<HTMLDivElement>) => { 88 if (event.key === "ArrowLeft") { 89 event.preventDefault() 90 scrollPrev() 91 } else if (event.key === "ArrowRight") { 92 event.preventDefault() 93 scrollNext() 94 } 95 }, 96 [scrollPrev, scrollNext] 97 ) 98 99 React.useEffect(() => { 100 if (!api || !setApi) { 101 return 102 } 103 104 setApi(api) 105 }, [api, setApi]) 106 107 React.useEffect(() => { 108 if (!api) { 109 return 110 } 111 112 onSelect(api) 113 api.on("reInit", onSelect) 114 api.on("select", onSelect) 115 116 return () => { 117 api?.off("select", onSelect) 118 } 119 }, [api, onSelect]) 120 121 return ( 122 <CarouselContext.Provider 123 value={{ 124 carouselRef, 125 api: api, 126 opts, 127 orientation: 128 orientation || (opts?.axis === "y" ? "vertical" : "horizontal"), 129 scrollPrev, 130 scrollNext, 131 canScrollPrev, 132 canScrollNext, 133 }} 134 > 135 <div 136 ref={ref} 137 onKeyDownCapture={handleKeyDown} 138 className={cn("relative", className)} 139 role="region" 140 aria-roledescription="carousel" 141 {...props} 142 > 143 {children} 144 </div> 145 </CarouselContext.Provider> 146 ) 147 } 148) 149Carousel.displayName = "Carousel" 150 151const CarouselContent = React.forwardRef< 152 HTMLDivElement, 153 React.HTMLAttributes<HTMLDivElement> 154>(({ className, ...props }, ref) => { 155 const { carouselRef, orientation } = useCarousel() 156 157 return ( 158 <div ref={carouselRef} className="overflow-hidden"> 159 <div 160 ref={ref} 161 className={cn( 162 "flex", 163 orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", 164 className 165 )} 166 {...props} 167 /> 168 </div> 169 ) 170}) 171CarouselContent.displayName = "CarouselContent" 172 173const CarouselItem = React.forwardRef< 174 HTMLDivElement, 175 React.HTMLAttributes<HTMLDivElement> 176>(({ className, ...props }, ref) => { 177 const { orientation } = useCarousel() 178 179 return ( 180 <div 181 ref={ref} 182 role="group" 183 aria-roledescription="slide" 184 className={cn( 185 "min-w-0 shrink-0 grow-0 basis-full", 186 orientation === "horizontal" ? "pl-4" : "pt-4", 187 className 188 )} 189 {...props} 190 /> 191 ) 192}) 193CarouselItem.displayName = "CarouselItem" 194 195const CarouselPrevious = React.forwardRef< 196 HTMLButtonElement, 197 React.ComponentProps<typeof Button> 198>(({ className, variant = "outline-solid", size = "icon", ...props }, ref) => { 199 const { orientation, scrollPrev, canScrollPrev } = useCarousel() 200 201 return ( 202 <Button 203 ref={ref} 204 variant={variant} 205 size={size} 206 className={cn( 207 "absolute h-8 w-8 rounded-full", 208 orientation === "horizontal" 209 ? "-left-12 top-1/2 -translate-y-1/2" 210 : "-top-12 left-1/2 -translate-x-1/2 rotate-90", 211 className 212 )} 213 disabled={!canScrollPrev} 214 onClick={scrollPrev} 215 {...props} 216 > 217 <ArrowLeft className="h-4 w-4" /> 218 <span className="sr-only">Previous slide</span> 219 </Button> 220 ) 221}) 222CarouselPrevious.displayName = "CarouselPrevious" 223 224const CarouselNext = React.forwardRef< 225 HTMLButtonElement, 226 React.ComponentProps<typeof Button> 227>(({ className, variant = "outline-solid", size = "icon", ...props }, ref) => { 228 const { orientation, scrollNext, canScrollNext } = useCarousel() 229 230 return ( 231 <Button 232 ref={ref} 233 variant={variant} 234 size={size} 235 className={cn( 236 "absolute h-8 w-8 rounded-full", 237 orientation === "horizontal" 238 ? "-right-12 top-1/2 -translate-y-1/2" 239 : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", 240 className 241 )} 242 disabled={!canScrollNext} 243 onClick={scrollNext} 244 {...props} 245 > 246 <ArrowRight className="h-4 w-4" /> 247 <span className="sr-only">Next slide</span> 248 </Button> 249 ) 250}) 251CarouselNext.displayName = "CarouselNext" 252 253export { 254 type CarouselApi, 255 Carousel, 256 CarouselContent, 257 CarouselItem, 258 CarouselPrevious, 259 CarouselNext, 260}