Ajout des nouveaux composants UI : Alert, Badge, Form, Sheet, Tabs et Input dans le répertoire frontend. Mise à jour du fichier package.json avec des nouvelles dépendances Radix UI et des autres bibliothèques nécessaires. Ajout d'une configuration pour TailwindCSS dans components.json.
262 lines
6.1 KiB
TypeScript
262 lines
6.1 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import useEmblaCarousel, {
|
|
type UseEmblaCarouselType,
|
|
} from "embla-carousel-react"
|
|
import { ArrowLeft, ArrowRight } from "lucide-react"
|
|
import {cn} from "apps/frontend/src/lib/utils";
|
|
import {Button} from "apps/frontend/src/components/ui/button";
|
|
|
|
type CarouselApi = UseEmblaCarouselType[1]
|
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
|
type CarouselOptions = UseCarouselParameters[0]
|
|
type CarouselPlugin = UseCarouselParameters[1]
|
|
|
|
type CarouselProps = {
|
|
opts?: CarouselOptions
|
|
plugins?: CarouselPlugin
|
|
orientation?: "horizontal" | "vertical"
|
|
setApi?: (api: CarouselApi) => void
|
|
}
|
|
|
|
type CarouselContextProps = {
|
|
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
|
api: ReturnType<typeof useEmblaCarousel>[1]
|
|
scrollPrev: () => void
|
|
scrollNext: () => void
|
|
canScrollPrev: boolean
|
|
canScrollNext: boolean
|
|
} & CarouselProps
|
|
|
|
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
|
|
|
function useCarousel() {
|
|
const context = React.useContext(CarouselContext)
|
|
|
|
if (!context) {
|
|
throw new Error("useCarousel must be used within a <Carousel />")
|
|
}
|
|
|
|
return context
|
|
}
|
|
|
|
const Carousel = React.forwardRef<
|
|
HTMLDivElement,
|
|
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
|
>(
|
|
(
|
|
{
|
|
orientation = "horizontal",
|
|
opts,
|
|
setApi,
|
|
plugins,
|
|
className,
|
|
children,
|
|
...props
|
|
},
|
|
ref
|
|
) => {
|
|
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: CarouselApi) => {
|
|
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: React.KeyboardEvent<HTMLDivElement>) => {
|
|
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 (
|
|
<CarouselContext.Provider
|
|
value={{
|
|
carouselRef,
|
|
api: api,
|
|
opts,
|
|
orientation:
|
|
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
|
scrollPrev,
|
|
scrollNext,
|
|
canScrollPrev,
|
|
canScrollNext,
|
|
}}
|
|
>
|
|
<div
|
|
ref={ref}
|
|
onKeyDownCapture={handleKeyDown}
|
|
className={cn("relative", className)}
|
|
role="region"
|
|
aria-roledescription="carousel"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</div>
|
|
</CarouselContext.Provider>
|
|
)
|
|
}
|
|
)
|
|
Carousel.displayName = "Carousel"
|
|
|
|
const CarouselContent = React.forwardRef<
|
|
HTMLDivElement,
|
|
React.HTMLAttributes<HTMLDivElement>
|
|
>(({ className, ...props }, ref) => {
|
|
const { carouselRef, orientation } = useCarousel()
|
|
|
|
return (
|
|
<div ref={carouselRef} className="overflow-hidden">
|
|
<div
|
|
ref={ref}
|
|
className={cn(
|
|
"flex",
|
|
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
</div>
|
|
)
|
|
})
|
|
CarouselContent.displayName = "CarouselContent"
|
|
|
|
const CarouselItem = React.forwardRef<
|
|
HTMLDivElement,
|
|
React.HTMLAttributes<HTMLDivElement>
|
|
>(({ className, ...props }, ref) => {
|
|
const { orientation } = useCarousel()
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
role="group"
|
|
aria-roledescription="slide"
|
|
className={cn(
|
|
"min-w-0 shrink-0 grow-0 basis-full",
|
|
orientation === "horizontal" ? "pl-4" : "pt-4",
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
})
|
|
CarouselItem.displayName = "CarouselItem"
|
|
|
|
const CarouselPrevious = React.forwardRef<
|
|
HTMLButtonElement,
|
|
React.ComponentProps<typeof Button>
|
|
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
|
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
|
|
|
return (
|
|
<Button
|
|
ref={ref}
|
|
variant={variant}
|
|
size={size}
|
|
className={cn(
|
|
"absolute h-8 w-8 rounded-full",
|
|
orientation === "horizontal"
|
|
? "-left-12 top-1/2 -translate-y-1/2"
|
|
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
className
|
|
)}
|
|
disabled={!canScrollPrev}
|
|
onClick={scrollPrev}
|
|
{...props}
|
|
>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
<span className="sr-only">Previous slide</span>
|
|
</Button>
|
|
)
|
|
})
|
|
CarouselPrevious.displayName = "CarouselPrevious"
|
|
|
|
const CarouselNext = React.forwardRef<
|
|
HTMLButtonElement,
|
|
React.ComponentProps<typeof Button>
|
|
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
|
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
|
|
|
return (
|
|
<Button
|
|
ref={ref}
|
|
variant={variant}
|
|
size={size}
|
|
className={cn(
|
|
"absolute h-8 w-8 rounded-full",
|
|
orientation === "horizontal"
|
|
? "-right-12 top-1/2 -translate-y-1/2"
|
|
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
className
|
|
)}
|
|
disabled={!canScrollNext}
|
|
onClick={scrollNext}
|
|
{...props}
|
|
>
|
|
<ArrowRight className="h-4 w-4" />
|
|
<span className="sr-only">Next slide</span>
|
|
</Button>
|
|
)
|
|
})
|
|
CarouselNext.displayName = "CarouselNext"
|
|
|
|
export {
|
|
type CarouselApi,
|
|
Carousel,
|
|
CarouselContent,
|
|
CarouselItem,
|
|
CarouselPrevious,
|
|
CarouselNext,
|
|
}
|