infoalloggi-monorepo/apps/infoalloggi/src/components/timeline.tsx

232 lines
4.7 KiB
TypeScript
Raw Normal View History

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