Timeline

Flight Itinerary

Flight itinerary timeline with local times as titles, airport codes in Geist Mono tags, a layover row between legs, and a plane mark with a Boarding badge on the current leg.

Free, MIT licenseOpen in Browse
import { AirplaneTiltIcon } from "@phosphor-icons/react/dist/ssr"

import {
  Timeline,
  TimelineContent,
  TimelineDescription,
  TimelineIndicator,
  TimelineItem,
  TimelineLabel,
  TimelineTitle,
} from "@/components/editorial/timeline"
import { Badge } from "@/components/ui/badge"

// The copy: change text here, not in the markup.
const COPY = {
  title: "Lisbon to Tokyo",
  body: "Thursday 30 October · 1 stop · 15h 40m · local times",
  boarding: "Boarding",
  layover: "Layover",
} as const

const LEGS = [
  {
    time: "09:35",
    code: "LIS",
    place: "Lisbon Humberto Delgado",
    detail: "Terminal 1 · Gate 14",
    current: true,
  },
  {
    time: "13:50",
    code: "FRA",
    place: "Frankfurt",
    detail: "Arrives · Terminal 1",
    layover: "2h 10m",
  },
  {
    time: "16:00",
    code: "FRA",
    place: "Frankfurt",
    detail: "Terminal 1 · Gate Z23",
  },
  {
    time: "10:15 +1",
    code: "HND",
    place: "Tokyo Haneda",
    detail: "Arrives Friday · Terminal 3",
  },
] as const

// A trip is one subject in time order: the time is the title, the airport
// code a mono tag, the layover a muted row between legs. The plane marks the
// leg you are on.
export default function FlightItinerary() {
  return (
    <section className="mx-auto w-full max-w-md">
      <header className="mb-6">
        <h2 className="text-lg font-semibold tracking-tight">{COPY.title}</h2>
        <p className="mt-1 text-sm text-muted-foreground">{COPY.body}</p>
      </header>
      <Timeline className="[--timeline-lh:1.5rem]">
        {LEGS.map((l) => {
          const current = "current" in l
          return (
            <TimelineItem key={l.time + l.code}>
              {current ? (
                <TimelineIndicator>
                  <span className="flex size-4 items-center justify-center rounded-full bg-foreground text-background">
                    <AirplaneTiltIcon
                      className="size-2.5"
                      weight="fill"
                      aria-hidden
                    />
                  </span>
                </TimelineIndicator>
              ) : (
                <TimelineIndicator className="border-border [&_[data-slot=timeline-dot]]:bg-muted-foreground/60" />
              )}
              <TimelineContent className="pb-7">
                <div className="flex items-center gap-3">
                  <TimelineTitle className="text-base leading-6 tabular-nums">
                    {l.time}
                  </TimelineTitle>
                  <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
                    {l.code}
                  </code>
                  {current && <Badge>{COPY.boarding}</Badge>}
                </div>
                <TimelineDescription className="mt-0.5 text-foreground">
                  {l.place}
                </TimelineDescription>
                <TimelineLabel className="mt-0.5">{l.detail}</TimelineLabel>
                {"layover" in l && (
                  <TimelineLabel className="mt-3 rounded-md bg-muted px-2 py-1">
                    {COPY.layover} · {l.layover}
                  </TimelineLabel>
                )}
              </TimelineContent>
            </TimelineItem>
          )
        })}
      </Timeline>
    </section>
  )
}

Installation

pnpm dlx shadcn add @01orbital/flight-itinerary
npx shadcn add @01orbital/flight-itinerary
yarn shadcn add @01orbital/flight-itinerary
bunx --bun shadcn add @01orbital/flight-itinerary

Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.

It lands at components/blocks/timeline/flight-itinerary/index.tsx, as source you own and edit.

Usage

import FlightItinerary from "@/components/blocks/timeline/flight-itinerary"

export default function Example() {
  return <FlightItinerary />
}

Dependencies

npm: @phosphor-icons/react. The CLI installs them for you.

Registry pieces it installs with it: