feat: refactor UI components to utilize new Container, Section, and StatusBadge components for improved layout and styling consistency across the application

This commit is contained in:
Julien Froidefond
2025-10-17 11:49:28 +02:00
parent 4f28df6818
commit 482bd9b0d2
23 changed files with 669 additions and 469 deletions

View File

@@ -0,0 +1,105 @@
"use client";
import * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
export interface ScrollContainerProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
showArrows?: boolean;
scrollAmount?: number;
arrowLeftLabel?: string;
arrowRightLabel?: string;
}
const ScrollContainer = React.forwardRef<HTMLDivElement, ScrollContainerProps>(
(
{
children,
className,
showArrows = true,
scrollAmount = 400,
arrowLeftLabel = "Scroll left",
arrowRightLabel = "Scroll right",
...props
},
ref
) => {
const scrollContainerRef = React.useRef<HTMLDivElement>(null);
const [showLeftArrow, setShowLeftArrow] = React.useState(false);
const [showRightArrow, setShowRightArrow] = React.useState(true);
const handleScroll = React.useCallback(() => {
if (!scrollContainerRef.current) return;
const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
setShowLeftArrow(scrollLeft > 0);
setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 10);
}, []);
const scroll = React.useCallback(
(direction: "left" | "right") => {
if (!scrollContainerRef.current) return;
const scrollValue = direction === "left" ? -scrollAmount : scrollAmount;
scrollContainerRef.current.scrollBy({ left: scrollValue, behavior: "smooth" });
},
[scrollAmount]
);
React.useEffect(() => {
const container = scrollContainerRef.current;
if (!container) return;
handleScroll();
const resizeObserver = new ResizeObserver(handleScroll);
resizeObserver.observe(container);
return () => {
resizeObserver.disconnect();
};
}, [handleScroll]);
return (
<div className="relative" ref={ref}>
{showArrows && showLeftArrow && (
<button
onClick={() => scroll("left")}
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 p-2 rounded-full bg-background/90 shadow-md border transition-opacity hover:bg-accent"
aria-label={arrowLeftLabel}
>
<ChevronLeft className="h-6 w-6" />
</button>
)}
<div
ref={scrollContainerRef}
onScroll={handleScroll}
className={cn(
"flex gap-4 overflow-x-auto scrollbar-hide scroll-smooth pb-4",
className
)}
{...props}
>
{children}
</div>
{showArrows && showRightArrow && (
<button
onClick={() => scroll("right")}
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 p-2 rounded-full bg-background/90 shadow-md border transition-opacity hover:bg-accent"
aria-label={arrowRightLabel}
>
<ChevronRight className="h-6 w-6" />
</button>
)}
</div>
);
}
);
ScrollContainer.displayName = "ScrollContainer";
export { ScrollContainer };