Glass Timeline

PreviousNext

A timeline component with glass morphism styling, suitable for displaying events or processes in a chronological order.

Docs
einuicomponent

Preview

Loading preview…
registry/innovative/glass-timeline.tsx
"use client"

import * as React from "react"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"

interface TimelineItem {
  id: string
  title: string
  description?: string
  date?: string
  icon?: React.ReactNode
  status?: "completed" | "current" | "upcoming"
}

interface GlassTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
  items: TimelineItem[]
  orientation?: "vertical" | "horizontal"
}

const GlassTimeline = React.forwardRef<HTMLDivElement, GlassTimelineProps>(
  ({ className, items, orientation = "vertical", ...props }, ref) => {
    if (orientation === "horizontal") {
      return (
        <div ref={ref} className={cn("w-full overflow-x-auto", className)} {...props}>
          <div className="flex items-start gap-4 min-w-max px-4">
            {items.map((item, index) => (
              <div key={item.id} className="flex flex-col items-center">
                <div className="flex items-center">
                  {/* Node */}
                  <GlassTimelineNode status={item.status} icon={item.icon} />

                  {/* Connector */}
                  {index < items.length - 1 && (
                    <div
                      className={cn(
                        "w-24 h-0.5 mx-2",
                        item.status === "completed" ? "bg-linear-to-r from-cyan-500 to-blue-500" : "bg-white/20",
                      )}
                    />
                  )}
                </div>

                {/* Content */}
                <div className="mt-4 text-center max-w-37.5">
                  <h4 className="font-medium text-white text-sm">{item.title}</h4>
                  {item.date && <p className="text-xs text-white/40 mt-1">{item.date}</p>}
                  {item.description && <p className="text-xs text-white/60 mt-2">{item.description}</p>}
                </div>
              </div>
            ))}
          </div>
        </div>
      )
    }

    return (
      <div ref={ref} className={cn("relative", className)} {...props}>
        {items.map((item, index) => (
          <div key={item.id} className="flex gap-4 pb-8 last:pb-0">
            {/* Node and line */}
            <div className="flex flex-col items-center">
              <GlassTimelineNode status={item.status} icon={item.icon} />
              {index < items.length - 1 && (
                <div
                  className={cn(
                    "w-0.5 flex-1 mt-2",
                    item.status === "completed" ? "bg-linear-to-b from-cyan-500 to-blue-500" : "bg-white/20",
                  )}
                />
              )}
            </div>

            {/* Content card */}
            <div className="flex-1 pb-2">
              <GlassTimelineCard item={item} />
            </div>
          </div>
        ))}
      </div>
    )
  },
)
GlassTimeline.displayName = "GlassTimeline"

function GlassTimelineNode({ status, icon }: { status?: TimelineItem["status"]; icon?: React.ReactNode }) {
  return (
    <div className="relative">
      {/* Glow for current */}
      {status === "current" && (
        <div className="absolute -inset-2 rounded-full bg-linear-to-r from-cyan-500/50 to-blue-500/50 blur-md animate-pulse" />
      )}

      <div
        className={cn(
          "relative w-10 h-10 rounded-full flex items-center justify-center",
          "border-2 transition-all duration-300",
          status === "completed" && "bg-linear-to-br from-cyan-500 to-blue-500 border-cyan-400/50",
          status === "current" && "bg-white/20 backdrop-blur-xl border-cyan-400/50",
          status === "upcoming" && "bg-white/5 backdrop-blur-xl border-white/20",
          !status && "bg-white/10 backdrop-blur-xl border-white/20",
        )}
      >
        {status === "completed" ? (
          <Check className="w-5 h-5 text-white" />
        ) : icon ? (
          <span className={cn("text-white/80", status === "current" && "text-cyan-400")}>{icon}</span>
        ) : (
          <div className={cn("w-3 h-3 rounded-full", status === "current" ? "bg-cyan-400" : "bg-white/40")} />
        )}
      </div>
    </div>
  )
}

function GlassTimelineCard({ item }: { item: TimelineItem }) {
  const isCurrent = item.status === "current"

  return (
    <div className="relative">
      {isCurrent && (
        <div className="absolute -inset-1 rounded-xl bg-linear-to-r from-cyan-500/30 to-blue-500/30 blur-lg opacity-70" />
      )}

      <div
        className={cn(
          "relative rounded-xl border p-4",
          "backdrop-blur-xl transition-all duration-300",
          isCurrent ? "bg-white/15 border-white/30" : "bg-white/5 border-white/10 hover:bg-white/10",
        )}
      >
        <div className="flex items-start justify-between gap-2">
          <h4 className="font-medium text-white">{item.title}</h4>
          {item.date && <span className="text-xs text-white/40 shrink-0">{item.date}</span>}
        </div>
        {item.description && <p className="mt-2 text-sm text-white/60">{item.description}</p>}
      </div>
    </div>
  )
}

export { GlassTimeline }
export type { TimelineItem }

Installation

npx shadcn@latest add @einui/glass-timeline

Usage

import { GlassTimeline } from "@/components/glass-timeline"
<GlassTimeline />