{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "timeline",
  "type": "registry:component",
  "title": "Timeline",
  "description": "Vertical or horizontal timeline of sequential events with completed/active/upcoming statuses and connector lines.",
  "dependencies": [
    "@vllnt/ui@^0.2.1",
    "class-variance-authority"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/timeline/timeline.tsx",
      "content": "\"use client\";\n\nimport {\n  type ComponentPropsWithoutRef,\n  createContext,\n  forwardRef,\n  type ReactNode,\n  useContext,\n  useMemo,\n} from \"react\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/**\n * Visual orientation for a {@link Timeline}.\n *\n * @public\n */\nexport type TimelineOrientation = \"horizontal\" | \"vertical\";\n\n/**\n * Status for a {@link TimelineItem}.\n *\n * @public\n */\nexport type TimelineItemStatus = \"active\" | \"completed\" | \"upcoming\";\n\n/**\n * Color theme for a {@link TimelineItem} marker.\n *\n * @public\n */\nexport type TimelineColor =\n  | \"amber\"\n  | \"blue\"\n  | \"emerald\"\n  | \"neutral\"\n  | \"purple\"\n  | \"red\"\n  | \"rose\";\n\nconst STATUS_CLASSES: Record<\n  TimelineItemStatus,\n  { connector: string; markerBorder: string; markerFill: string }\n> = {\n  active: {\n    connector: \"border-solid border-primary\",\n    markerBorder: \"border-primary\",\n    markerFill: \"bg-background ring-2 ring-primary\",\n  },\n  completed: {\n    connector: \"border-solid border-primary\",\n    markerBorder: \"border-primary\",\n    markerFill: \"bg-primary\",\n  },\n  upcoming: {\n    connector: \"border-dashed border-muted-foreground/40\",\n    markerBorder: \"border-muted-foreground/40\",\n    markerFill: \"bg-background\",\n  },\n};\n\nconst COLOR_OVERRIDES: Record<TimelineColor, { border: string; fill: string }> =\n  {\n    amber: {\n      border: \"border-amber-500\",\n      fill: \"bg-amber-500\",\n    },\n    blue: {\n      border: \"border-blue-500\",\n      fill: \"bg-blue-500\",\n    },\n    emerald: {\n      border: \"border-emerald-500\",\n      fill: \"bg-emerald-500\",\n    },\n    neutral: {\n      border: \"border-muted-foreground\",\n      fill: \"bg-muted-foreground\",\n    },\n    purple: {\n      border: \"border-purple-500\",\n      fill: \"bg-purple-500\",\n    },\n    red: {\n      border: \"border-red-500\",\n      fill: \"bg-red-500\",\n    },\n    rose: {\n      border: \"border-rose-500\",\n      fill: \"bg-rose-500\",\n    },\n  };\n\ntype TimelineContextValue = {\n  orientation: TimelineOrientation;\n};\n\nconst TimelineContext = createContext<TimelineContextValue>({\n  orientation: \"vertical\",\n});\n\n/**\n * Hook for reading the surrounding {@link Timeline}'s orientation. Useful\n * for custom children that need to adapt their layout.\n *\n * @public\n */\nexport function useTimelineOrientation(): TimelineOrientation {\n  return useContext(TimelineContext).orientation;\n}\n\nconst timelineVariants = cva(\"flex\", {\n  defaultVariants: {\n    orientation: \"vertical\",\n  },\n  variants: {\n    orientation: {\n      horizontal: \"flex-row gap-0 overflow-x-auto\",\n      vertical: \"flex-col gap-0\",\n    },\n  },\n});\n\n/**\n * Props for {@link Timeline}.\n *\n * @public\n */\nexport type TimelineProps = ComponentPropsWithoutRef<\"ol\"> &\n  VariantProps<typeof timelineVariants>;\n\n/**\n * Vertical or horizontal timeline of sequential events. Renders an\n * ordered list (`<ol>`) so the order matters semantically. Children\n * should be {@link TimelineItem}s.\n *\n * The component injects connector styling per child via context — the\n * last item drops its trailing connector automatically.\n *\n * @example\n * ```tsx\n * <Timeline>\n *   <TimelineItem title=\"Project started\" date=\"Jan 2026\" status=\"completed\" />\n *   <TimelineItem title=\"MVP launch\" date=\"Mar 2026\" status=\"completed\" />\n *   <TimelineItem title=\"V2\" date=\"Jul 2026\" status=\"active\" />\n *   <TimelineItem title=\"Public release\" date=\"Q4 2026\" status=\"upcoming\" />\n * </Timeline>\n * ```\n *\n * @public\n */\nexport const Timeline = forwardRef<HTMLOListElement, TimelineProps>(\n  ({ children, className, orientation, ...rest }, ref) => {\n    const resolvedOrientation: TimelineOrientation = orientation ?? \"vertical\";\n    const contextValue = useMemo<TimelineContextValue>(\n      () => ({ orientation: resolvedOrientation }),\n      [resolvedOrientation],\n    );\n    return (\n      <TimelineContext.Provider value={contextValue}>\n        <ol\n          className={cn(timelineVariants({ orientation }), className)}\n          data-orientation={resolvedOrientation}\n          ref={ref}\n          {...rest}\n        >\n          {children}\n        </ol>\n      </TimelineContext.Provider>\n    );\n  },\n);\nTimeline.displayName = \"Timeline\";\n\n/**\n * Props for {@link TimelineItem}.\n *\n * @public\n */\nexport type TimelineItemProps = {\n  /** Optional color override for the marker. Falls back to the status palette. */\n  color?: TimelineColor;\n  /** Optional date / time caption rendered alongside the title. */\n  date?: ReactNode;\n  /** Optional sub-headline rendered under the title. */\n  description?: ReactNode;\n  /** Optional icon rendered inside the marker. */\n  icon?: ReactNode;\n  /** When true, suppresses the trailing connector. Defaults to `false`. */\n  isLast?: boolean;\n  /** Status drives marker fill and connector style. Defaults to `\"upcoming\"`. */\n  status?: TimelineItemStatus;\n  /** Item title. */\n  title: ReactNode;\n} & ComponentPropsWithoutRef<\"li\">;\n\ntype MarkerProps = {\n  color?: TimelineColor;\n  icon?: ReactNode;\n  status: TimelineItemStatus;\n};\n\nfunction Marker({ color, icon, status }: MarkerProps): ReactNode {\n  const palette = STATUS_CLASSES[status];\n  const override = color ? COLOR_OVERRIDES[color] : undefined;\n  const borderClass = override?.border ?? palette.markerBorder;\n  const fillClass = override?.fill ?? palette.markerFill;\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={cn(\n        \"relative z-10 flex size-6 shrink-0 items-center justify-center rounded-full border-2 bg-background text-[10px]\",\n        borderClass,\n      )}\n    >\n      {icon ? (\n        <span\n          className={cn(\n            \"flex h-full w-full items-center justify-center text-foreground [&>svg]:h-3 [&>svg]:w-3\",\n            status === \"completed\" ? \"text-primary-foreground\" : \"\",\n          )}\n        >\n          {icon}\n        </span>\n      ) : (\n        <span\n          className={cn(\n            \"size-2.5 rounded-full\",\n            override ? fillClass : palette.markerFill,\n          )}\n        />\n      )}\n    </span>\n  );\n}\n\ntype ConnectorProps = {\n  orientation: TimelineOrientation;\n  status: TimelineItemStatus;\n};\n\nfunction Connector({ orientation, status }: ConnectorProps): ReactNode {\n  const palette = STATUS_CLASSES[status];\n  if (orientation === \"horizontal\") {\n    return (\n      <span\n        aria-hidden=\"true\"\n        className={cn(\n          \"absolute top-3 h-0 w-full border-t-2\",\n          palette.connector,\n        )}\n        style={{ left: \"calc(50% + 0.75rem)\" }}\n      />\n    );\n  }\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={cn(\n        \"absolute left-3 top-6 h-full w-0 -translate-x-1/2 border-l-2\",\n        palette.connector,\n      )}\n    />\n  );\n}\n\ntype ItemBodyProps = {\n  children?: ReactNode;\n  date?: ReactNode;\n  description?: ReactNode;\n  orientation: TimelineOrientation;\n  title: ReactNode;\n};\n\nfunction ItemBody({\n  children,\n  date,\n  description,\n  orientation,\n  title,\n}: ItemBodyProps): ReactNode {\n  return (\n    <div\n      className={cn(\n        \"flex min-w-0 flex-col gap-0.5\",\n        orientation === \"horizontal\" ? \"items-center text-center\" : \"\",\n      )}\n    >\n      <div\n        className={cn(\n          \"flex flex-wrap items-baseline gap-x-2 gap-y-0.5\",\n          orientation === \"horizontal\" ? \"justify-center\" : \"\",\n        )}\n      >\n        <p className=\"text-sm font-semibold tracking-tight text-foreground\">\n          {title}\n        </p>\n        {date ? (\n          <span className=\"font-mono text-xs text-muted-foreground\">\n            {date}\n          </span>\n        ) : null}\n      </div>\n      {description ? (\n        <p className=\"text-sm text-muted-foreground\">{description}</p>\n      ) : null}\n      {children ? (\n        <div className=\"mt-1 text-sm text-foreground\">{children}</div>\n      ) : null}\n    </div>\n  );\n}\n\n/**\n * One row inside a {@link Timeline}. Renders the marker, connector, title,\n * date, description, and any rich-content children.\n *\n * @public\n */\nexport const TimelineItem = forwardRef<HTMLLIElement, TimelineItemProps>(\n  (props, ref) => {\n    const {\n      children,\n      className,\n      color,\n      date,\n      description,\n      icon,\n      isLast = false,\n      status = \"upcoming\",\n      title,\n      ...rest\n    } = props;\n    const orientation = useTimelineOrientation();\n    return (\n      <li\n        className={cn(\n          \"relative\",\n          orientation === \"horizontal\"\n            ? \"flex flex-1 flex-col items-center gap-2 px-3 pt-1\"\n            : \"flex items-start gap-3 pb-6 last:pb-0\",\n          className,\n        )}\n        data-status={status}\n        ref={ref}\n        {...rest}\n      >\n        {isLast ? null : (\n          <Connector orientation={orientation} status={status} />\n        )}\n        <Marker color={color} icon={icon} status={status} />\n        <ItemBody\n          date={date}\n          description={description}\n          orientation={orientation}\n          title={title}\n        >\n          {children}\n        </ItemBody>\n      </li>\n    );\n  },\n);\nTimelineItem.displayName = \"TimelineItem\";\n\nexport { timelineVariants };\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
