Skip to content
ilinxa/pro-ui

Schedule List

alphav0.2.0

Time-anchored agenda list — time or range, title, optional description and icons, per-row links, framed or bare.

Category: Data DisplayUpdated: 2026-08-11Created: 2026-05-02Author: ilinxa

Context

Use for conference programs, course curricula, podcast / broadcast schedules, meeting agendas, recipe-step lists with timing — anywhere a time-ordered list of activities renders. Migration origin: kasder kas-social-front-v0 events/[id]/page.tsx Program section. Composes naturally inside event-detail-page-01 (deferred sibling).

Installation

Initialize shadcn (once per project)Seeds lib/utils.ts and components.json. Skip if you've already used any shadcn component.
pnpm dlx shadcn@latest init
Install the component
pnpm dlx shadcn@latest add @ilinxa/schedule-list

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/schedule-list-fixtures

CLI can't resolve @ilinxa? The namespace is listed in the official shadcn registry directory, so current CLIs need no configuration. If yours can't resolve it (older or pinned versions, self-hosted mirrors), register it manually in components.json:

"registries": {
  "@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}

Preview

Program

  1. 09:00

    Kayıt ve Karşılama Kahvaltısı

    Katılımcı kayıtları ve networking

  2. 10:00

    Açılış Konuşması

    Dernek Başkanı tarafından açılış

  3. 11:00

    Panel: Geleceğin Şehirleri

    Uzman panelimizle interaktif tartışma

  4. 13:00

    Öğle Yemeği

    Networking lunch

  5. 14:30

    Çalıştaylar

    Paralel çalıştay oturumları

  6. 17:00

    Kapanış ve Networking

    Sonuç bildirisi ve kokteyl

Demo source

demo.tsxtsx
"use client"; import { Mic } from "lucide-react";import { Tabs, TabsContent, TabsTrigger } from "@/components/ui/tabs";import { SwipeTabsList } from "@/components/site/swipe-tabs-list";import { ScheduleList } from "./schedule-list";import { dummySchedule, dummyScheduleTr } from "./dummy-data";import type { ScheduleListItem } from "./types"; const speakers: Record<string, { name: string; image: string }> = {  "2": {    name: "Prof. Ahmet Yılmaz",    image:      "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=80&h=80&fit=crop&crop=faces",  },  "3": {    name: "Dr. Elif Kaya",    image:      "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=80&h=80&fit=crop&crop=faces",  },}; function SpeakerAugmentedRow({ item }: { item: ScheduleListItem }) {  const speaker = speakers[item.id];  return (    <div className="flex items-start gap-4 p-4 bg-card rounded-xl border border-border/50">      <div className="w-auto min-w-20 shrink-0">        <span className="text-lg font-bold text-primary">{item.time}</span>      </div>      {speaker && (        <img          src={speaker.image}          alt={speaker.name}          loading="lazy"          className="w-12 h-12 rounded-full object-cover shrink-0 border-2 border-primary/20"        />      )}      <div className="min-w-0 flex-1">        <h4 className="font-semibold text-foreground">{item.title}</h4>        {speaker && (          <p className="text-xs text-primary mt-0.5 inline-flex items-center gap-1">            <Mic aria-hidden="true" className="w-3 h-3" />            {speaker.name}          </p>        )}        {item.description && (          <p className="text-sm text-muted-foreground mt-0.5">            {item.description}          </p>        )}      </div>    </div>  );} export default function ScheduleListDemo() {  return (    <Tabs defaultValue="default" className="w-full">      <SwipeTabsList>        <TabsTrigger value="default">Default (TR)</TabsTrigger>        <TabsTrigger value="english">English</TabsTrigger>        <TabsTrigger value="bare">Bare rows</TabsTrigger>        <TabsTrigger value="links">Icons + links</TabsTrigger>        <TabsTrigger value="custom">Custom renderItem</TabsTrigger>      </SwipeTabsList>       {/* 1. Default — Turkish, mirrors the kasder source verbatim */}      <TabsContent value="default" className="mt-6">        <div className="max-w-3xl mx-auto">          <ScheduleList heading="Program" items={dummyScheduleTr} />        </div>      </TabsContent>       {/* 2. English — same shape, English content */}      <TabsContent value="english" className="mt-6">        <div className="max-w-3xl mx-auto">          <ScheduleList heading="Program" items={dummySchedule} />        </div>      </TabsContent>       {/* 3. Bare rows — framed=false for sidebar/widget contexts */}      <TabsContent value="bare" className="mt-6">        <div className="max-w-md mx-auto rounded-2xl border border-border/50 bg-card p-4">          <ScheduleList            heading="Today"            headingAs="h3"            framed={false}            items={dummySchedule.slice(0, 4)}          />        </div>      </TabsContent>       {/* 4. Icons + links — items with icon + clickable href */}      <TabsContent value="links" className="mt-6">        <div className="max-w-3xl mx-auto">          <p className="mb-3 text-sm text-muted-foreground">            Items with icons (Coffee / Users / Utensils) and links — the entire            row is wrapped in a polymorphic link when <code>href</code> is            provided.          </p>          <ScheduleList            heading="Conference Day 1"            items={dummySchedule.map((item) => ({              ...item,              href: `#talk-${item.id}`,            }))}          />        </div>      </TabsContent>       {/* 5. Custom renderItem — speaker-augmented row */}      <TabsContent value="custom" className="mt-6">        <div className="max-w-3xl mx-auto">          <p className="mb-3 text-sm text-muted-foreground">            Custom <code>renderItem</code> slot — drops a speaker avatar            adjacent to the time column on items 2 + 3.          </p>          <ScheduleList            heading="Program with Speakers"            items={dummySchedule}            renderItem={(item) => <SpeakerAugmentedRow item={item} />}          />        </div>      </TabsContent>    </Tabs>  );} 

Usage

When to use

Reach for ScheduleList when you need a vertical time-anchored list of activities — conference programs, course curricula, podcast / broadcast schedules, meeting agendas, recipe steps with timing. Each row is a time + title + optional description triplet, with optional icons and optional per-row links.

Minimal example

import { ScheduleList } from "@/components/schedule-list";

<ScheduleList
  heading="Program"
  items={[
    { id: "1", time: "09:00", title: "Welcome", description: "Check-in" },
    { id: "2", time: "10:00", endTime: "10:30", title: "Opening Keynote" },
    { id: "3", time: "11:00", title: "Panel: Future Cities" },
  ]}
/>;

Item shape — what's required

  • id, time, title — required.
  • endTime — optional. When present, time renders as {time}{separator}{endTime} (default separator " - ", configurable via labels.timeRangeSeparator).
  • description — optional. Description block omitted when missing.
  • icon — optional Lucide-style component (ComponentType<{ className?: string }>). When present, sits left of the time column.
  • href — optional. When present + linkComponent provided, the entire row becomes clickable via the overlay-link pattern.

Frame toggle

Default framed: true — each row gets card chrome (bg-card rounded-xl border) and rows are spaced space-y-4. Pass framed={false} for bare rows in dense contexts (sidebars, widgets) — chrome dropped, rows tighten to space-y-2.

Custom rendering

  • renderItem(item) — bypass the default row layout entirely. Useful for embedding speaker avatars, status badges, per-item actions.
  • renderTime(item)— bypass the default time string renderer (e.g. transform "09:00" → "9:00 AM"). Used inside the default row layout when renderItem is not supplied.

Polymorphic root

import NextLink from "next/link";

<ScheduleList
  heading="Conference Day 1"
  items={items.map((item) => ({ ...item, href: `/talks/${item.id}` }))}
  linkComponent={NextLink}
/>;

Empty state

When items is empty, the component renders emptyState (if provided) OR a <p role="status"> with labels.emptyText(default "No items scheduled.").

Notes

  • Renders semantic <ol role="list"> — schedules ARE ordered.
  • Section aria-labelledby resolves to the heading id (computed via useId) — only when heading is supplied.
  • Decorative icons get aria-hidden="true"; link's accessible name is the item title.
  • All hover transitions gated via motion-safe: — reduced-motion users see static rows.

Features

  • Vertical schedule list — time + title + optional description per row
  • Optional time range (start - end), separator localizable
  • Optional Lucide-style icon per row
  • Polymorphic per-row link via linkComponent + per-item href
  • Frame toggle (framed=true card chrome / framed=false bare rows)
  • Custom renderItem + renderTime slots
  • Optional section heading with configurable level (h2/h3/h4)
  • Soft-failure on optional fields (description / icon / endTime / href)
  • i18n via labels object
  • Semantic <ol role='list'> — schedules are ordered
  • Empty state slot with labels fallback

Tags

schedule-listscheduleagendatimelineevents

Dependencies

npm peer deps: lucide-react@^1.11.0