SPB Git

spb/vquant Public MIT

VibeQuant — AI-powered institutional-grade financial intelligence platform.

TypeScript 84.3% Python 11.7% JavaScript 1.6% CSS 1.5% HTML 0.7%
6.7 KB · 277 lines tsx
Raw Blame History
1/*2 * =============================================================================3 *  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 *  File:      client/src/components/ui/carousel.tsx6 *7 *  Author:    Simon-Pierre Boucher8 *  Contact:   contact@spboucher.ai9 *  Website:   https://www.spboucher.ai10 *  Demo:      https://www.vquant.ai11 *  License:   MIT (see LICENSE)12 *13 *  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import * as React from "react"18import useEmblaCarousel, {19  type UseEmblaCarouselType,20} from "embla-carousel-react"21import { ArrowLeft, ArrowRight } from "lucide-react"2223import { cn } from "@/lib/utils"24import { Button } from "@/components/ui/button"2526type CarouselApi = UseEmblaCarouselType[1]27type UseCarouselParameters = Parameters<typeof useEmblaCarousel>28type CarouselOptions = UseCarouselParameters[0]29type CarouselPlugin = UseCarouselParameters[1]3031type CarouselProps = {32  opts?: CarouselOptions33  plugins?: CarouselPlugin34  orientation?: "horizontal" | "vertical"35  setApi?: (api: CarouselApi) => void36}3738type CarouselContextProps = {39  carouselRef: ReturnType<typeof useEmblaCarousel>[0]40  api: ReturnType<typeof useEmblaCarousel>[1]41  scrollPrev: () => void42  scrollNext: () => void43  canScrollPrev: boolean44  canScrollNext: boolean45} & CarouselProps4647const CarouselContext = React.createContext<CarouselContextProps | null>(null)4849function useCarousel() {50  const context = React.useContext(CarouselContext)5152  if (!context) {53    throw new Error("useCarousel must be used within a <Carousel />")54  }5556  return context57}5859const Carousel = React.forwardRef<60  HTMLDivElement,61  React.HTMLAttributes<HTMLDivElement> & CarouselProps62>(63  (64    {65      orientation = "horizontal",66      opts,67      setApi,68      plugins,69      className,70      children,71      ...props72    },73    ref74  ) => {75    const [carouselRef, api] = useEmblaCarousel(76      {77        ...opts,78        axis: orientation === "horizontal" ? "x" : "y",79      },80      plugins81    )82    const [canScrollPrev, setCanScrollPrev] = React.useState(false)83    const [canScrollNext, setCanScrollNext] = React.useState(false)8485    const onSelect = React.useCallback((api: CarouselApi) => {86      if (!api) {87        return88      }8990      setCanScrollPrev(api.canScrollPrev())91      setCanScrollNext(api.canScrollNext())92    }, [])9394    const scrollPrev = React.useCallback(() => {95      api?.scrollPrev()96    }, [api])9798    const scrollNext = React.useCallback(() => {99      api?.scrollNext()100    }, [api])101102    const handleKeyDown = React.useCallback(103      (event: React.KeyboardEvent<HTMLDivElement>) => {104        if (event.key === "ArrowLeft") {105          event.preventDefault()106          scrollPrev()107        } else if (event.key === "ArrowRight") {108          event.preventDefault()109          scrollNext()110        }111      },112      [scrollPrev, scrollNext]113    )114115    React.useEffect(() => {116      if (!api || !setApi) {117        return118      }119120      setApi(api)121    }, [api, setApi])122123    React.useEffect(() => {124      if (!api) {125        return126      }127128      onSelect(api)129      api.on("reInit", onSelect)130      api.on("select", onSelect)131132      return () => {133        api?.off("select", onSelect)134      }135    }, [api, onSelect])136137    return (138      <CarouselContext.Provider139        value={{140          carouselRef,141          api: api,142          opts,143          orientation:144            orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),145          scrollPrev,146          scrollNext,147          canScrollPrev,148          canScrollNext,149        }}150      >151        <div152          ref={ref}153          onKeyDownCapture={handleKeyDown}154          className={cn("relative", className)}155          role="region"156          aria-roledescription="carousel"157          {...props}158        >159          {children}160        </div>161      </CarouselContext.Provider>162    )163  }164)165Carousel.displayName = "Carousel"166167const CarouselContent = React.forwardRef<168  HTMLDivElement,169  React.HTMLAttributes<HTMLDivElement>170>(({ className, ...props }, ref) => {171  const { carouselRef, orientation } = useCarousel()172173  return (174    <div ref={carouselRef} className="overflow-hidden">175      <div176        ref={ref}177        className={cn(178          "flex",179          orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",180          className181        )}182        {...props}183      />184    </div>185  )186})187CarouselContent.displayName = "CarouselContent"188189const CarouselItem = React.forwardRef<190  HTMLDivElement,191  React.HTMLAttributes<HTMLDivElement>192>(({ className, ...props }, ref) => {193  const { orientation } = useCarousel()194195  return (196    <div197      ref={ref}198      role="group"199      aria-roledescription="slide"200      className={cn(201        "min-w-0 shrink-0 grow-0 basis-full",202        orientation === "horizontal" ? "pl-4" : "pt-4",203        className204      )}205      {...props}206    />207  )208})209CarouselItem.displayName = "CarouselItem"210211const CarouselPrevious = React.forwardRef<212  HTMLButtonElement,213  React.ComponentProps<typeof Button>214>(({ className, variant = "outline", size = "icon", ...props }, ref) => {215  const { orientation, scrollPrev, canScrollPrev } = useCarousel()216217  return (218    <Button219      ref={ref}220      variant={variant}221      size={size}222      className={cn(223        "absolute  h-8 w-8 rounded-full",224        orientation === "horizontal"225          ? "-left-12 top-1/2 -translate-y-1/2"226          : "-top-12 left-1/2 -translate-x-1/2 rotate-90",227        className228      )}229      disabled={!canScrollPrev}230      onClick={scrollPrev}231      {...props}232    >233      <ArrowLeft className="h-4 w-4" />234      <span className="sr-only">Previous slide</span>235    </Button>236  )237})238CarouselPrevious.displayName = "CarouselPrevious"239240const CarouselNext = React.forwardRef<241  HTMLButtonElement,242  React.ComponentProps<typeof Button>243>(({ className, variant = "outline", size = "icon", ...props }, ref) => {244  const { orientation, scrollNext, canScrollNext } = useCarousel()245246  return (247    <Button248      ref={ref}249      variant={variant}250      size={size}251      className={cn(252        "absolute h-8 w-8 rounded-full",253        orientation === "horizontal"254          ? "-right-12 top-1/2 -translate-y-1/2"255          : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",256        className257      )}258      disabled={!canScrollNext}259      onClick={scrollNext}260      {...props}261    >262      <ArrowRight className="h-4 w-4" />263      <span className="sr-only">Next slide</span>264    </Button>265  )266})267CarouselNext.displayName = "CarouselNext"268269export {270  type CarouselApi,271  Carousel,272  CarouselContent,273  CarouselItem,274  CarouselPrevious,275  CarouselNext,276}277