infoalloggi-monorepo/apps/infoalloggi/src/components/timeline.tsx
2025-08-29 16:18:32 +02:00

231 lines
4.7 KiB
TypeScript

"use client";
import { Slot } from "@radix-ui/react-slot";
import * as React from "react";
import { cn } from "~/lib/utils";
type TimelineContextProps = {
orientation: "horizontal" | "vertical";
};
const TimelineContext = React.createContext<TimelineContextProps | null>(null);
function useTimeline() {
const context = React.useContext(TimelineContext);
if (!context) {
throw new Error("useTimeline must be used within a <Timeline />.");
}
return context;
}
interface TimelineProps extends React.ComponentPropsWithoutRef<"ol"> {
orientation?: "horizontal" | "vertical";
}
function Timeline({
className,
orientation = "vertical",
...props
}: TimelineProps) {
return (
<TimelineContext value={{ orientation }}>
<ol
className={cn(
"flex",
orientation === "vertical" && "flex-col",
className,
)}
//role="list"
data-orientation={orientation}
data-slot="timeline"
{...props}
/>
</TimelineContext>
);
}
interface TimelineItemProps extends React.ComponentPropsWithoutRef<"li"> {
asChild?: boolean;
}
function TimelineItem({ className, asChild, ...props }: TimelineItemProps) {
const { orientation } = useTimeline();
const Comp = asChild ? Slot : "li";
return (
<Comp
className={cn(
"flex gap-4",
orientation === "horizontal" && "flex-col",
className,
)}
data-orientation={orientation}
data-slot="timeline-item"
{...props}
/>
);
}
interface TimelineSeparatorProps extends React.ComponentPropsWithoutRef<"div"> {
asChild?: boolean;
}
function TimelineSeparator({
className,
asChild,
...props
}: TimelineSeparatorProps) {
const { orientation } = useTimeline();
const Comp = asChild ? Slot : "div";
return (
<Comp
className={cn(
"flex items-center",
orientation === "vertical" && "flex-col",
className,
)}
data-orientation={orientation}
data-slot="timeline-separator"
{...props}
/>
);
}
interface TimelineDotProps extends React.ComponentPropsWithoutRef<"div"> {
variant?: "default" | "outline";
asChild?: boolean;
}
function TimelineDot({
variant = "default",
className,
asChild,
...props
}: TimelineDotProps) {
const { orientation } = useTimeline();
const Comp = asChild ? Slot : "div";
return (
<Comp
className={cn(
"flex size-4 items-center justify-center empty:after:block empty:after:rounded-full empty:after:outline-current [&_svg:not([class*='size-'])]:size-4",
orientation === "vertical" && "mt-1",
variant === "default" && "empty:after:size-2.5 empty:after:bg-current",
variant === "outline" && "empty:after:size-2 empty:after:outline",
className,
)}
data-orientation={orientation}
data-slot="timeline-dot"
{...props}
/>
);
}
interface TimelineConnectorProps extends React.ComponentPropsWithoutRef<"div"> {
asChild?: boolean;
}
function TimelineConnector({
className,
asChild,
...props
}: TimelineConnectorProps) {
const { orientation } = useTimeline();
const Comp = asChild ? Slot : "div";
return (
<Comp
className={cn(
"bg-border flex-1",
orientation === "vertical" && "my-2 w-0.5",
orientation === "horizontal" && "mx-2 h-0.5",
className,
)}
data-orientation={orientation}
data-slot="timeline-connector"
{...props}
/>
);
}
interface TimelineContentProps extends React.ComponentPropsWithoutRef<"div"> {
asChild?: boolean;
}
function TimelineContent({
className,
asChild,
...props
}: TimelineContentProps) {
const { orientation } = useTimeline();
const Comp = asChild ? Slot : "div";
return (
<Comp
className={cn(
"flex-1",
orientation === "vertical" && "pb-7 first:text-right last:text-left",
orientation === "horizontal" && "pr-7",
className,
)}
data-orientation={orientation}
data-slot="timeline-content"
{...props}
/>
);
}
interface TimelineTitleProps extends React.ComponentPropsWithoutRef<"div"> {
asChild?: boolean;
}
function TimelineTitle({ className, asChild, ...props }: TimelineTitleProps) {
const { orientation } = useTimeline();
const Comp = asChild ? Slot : "div";
return (
<Comp
className={className}
data-orientation={orientation}
data-slot="timeline-title"
{...props}
/>
);
}
interface TimelineDescriptionProps
extends React.ComponentPropsWithoutRef<"div"> {
asChild?: boolean;
}
function TimelineDescription({
className,
asChild,
...props
}: TimelineDescriptionProps) {
const { orientation } = useTimeline();
const Comp = asChild ? Slot : "div";
return (
<Comp
className={cn("text-muted-foreground text-[0.8em]", className)}
data-orientation={orientation}
data-slot="timeline-description"
{...props}
/>
);
}
export {
Timeline,
TimelineItem,
TimelineSeparator,
TimelineDot,
TimelineConnector,
TimelineContent,
TimelineTitle,
TimelineDescription,
useTimeline,
};