232 lines
5 KiB
TypeScript
232 lines
5 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
|
||
|
|
data-slot="timeline"
|
||
|
|
//role="list"
|
||
|
|
data-orientation={orientation}
|
||
|
|
className={cn(
|
||
|
|
"flex",
|
||
|
|
orientation === "vertical" && "flex-col",
|
||
|
|
className,
|
||
|
|
)}
|
||
|
|
{...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
|
||
|
|
data-slot="timeline-item"
|
||
|
|
data-orientation={orientation}
|
||
|
|
className={cn(
|
||
|
|
"flex gap-4",
|
||
|
|
orientation === "horizontal" && "flex-col",
|
||
|
|
className,
|
||
|
|
)}
|
||
|
|
{...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
|
||
|
|
data-slot="timeline-separator"
|
||
|
|
data-orientation={orientation}
|
||
|
|
className={cn(
|
||
|
|
"flex items-center",
|
||
|
|
orientation === "vertical" && "flex-col",
|
||
|
|
className,
|
||
|
|
)}
|
||
|
|
{...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
|
||
|
|
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}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
interface TimelineConnectorProps extends React.ComponentPropsWithoutRef<"div"> {
|
||
|
|
asChild?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
function TimelineConnector({
|
||
|
|
className,
|
||
|
|
asChild,
|
||
|
|
...props
|
||
|
|
}: TimelineConnectorProps) {
|
||
|
|
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}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
interface TimelineContentProps extends React.ComponentPropsWithoutRef<"div"> {
|
||
|
|
asChild?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
function TimelineContent({
|
||
|
|
className,
|
||
|
|
asChild,
|
||
|
|
...props
|
||
|
|
}: TimelineContentProps) {
|
||
|
|
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}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
interface TimelineTitleProps extends React.ComponentPropsWithoutRef<"div"> {
|
||
|
|
asChild?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
function TimelineTitle({ className, asChild, ...props }: TimelineTitleProps) {
|
||
|
|
const { orientation } = useTimeline();
|
||
|
|
const Comp = asChild ? Slot : "div";
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Comp
|
||
|
|
data-slot="timeline-title"
|
||
|
|
data-orientation={orientation}
|
||
|
|
className={className}
|
||
|
|
{...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
|
||
|
|
data-slot="timeline-description"
|
||
|
|
data-orientation={orientation}
|
||
|
|
className={cn("text-muted-foreground text-[0.8em]", className)}
|
||
|
|
{...props}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export {
|
||
|
|
Timeline,
|
||
|
|
TimelineItem,
|
||
|
|
TimelineSeparator,
|
||
|
|
TimelineDot,
|
||
|
|
TimelineConnector,
|
||
|
|
TimelineContent,
|
||
|
|
TimelineTitle,
|
||
|
|
TimelineDescription,
|
||
|
|
useTimeline,
|
||
|
|
};
|