# Weekly Agenda View

> Weekly agenda calendar listing Monday to Sunday with time-slotted event rows, confirmed, tentative, and canceled status colors, and week navigation.

A shadcn/ui block from the [@7ovr](https://7ovr.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/7ovr/calendar-2).

- Page, with a live preview: https://shadcn.exchange/r/7ovr/calendar-2
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/7ovr/calendar-2-light-871ec4b18973bc79.png
- Registry item: https://7ovr.com/r/calendar-2.json
- Categories: calendar
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/calendar-2.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@base-ui/react`
- Registry items: `button`, `popover`, `separator`

## Notes from the registry

Requires a `base-*` style in components.json (the `shadcn init` default). Legacy `new-york`/`radix-*` styles install Radix primitives, which reject the `render` prop this block uses.

## Files

### `registry/blocks/calendar/2/calendar-block.tsx`

```tsx
"use client"

import * as React from "react"
import { Button } from "@/components/ui/button"
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover"
import { Separator } from "@/components/ui/separator"
import { cn } from "@/lib/utils"
import { IconPlaceholder } from "@/components/icons/icon-placeholder"

type EventStatus = "confirmed" | "tentative" | "cancelled"

interface CalEvent {
  time: string
  duration: string
  title: string
  location?: string
  status: EventStatus
}

interface AgendaDay {
  label: string
  date: number
  isToday: boolean
  events: CalEvent[]
}

interface Week {
  range: string
  days: AgendaDay[]
}

const STANDUP: CalEvent = {
  time: "09:00",
  duration: "30 min",
  title: "Daily standup",
  location: "Zoom",
  status: "confirmed",
}

const WEEKS: Week[] = [
  {
    range: "Jun 9 – Jun 15, 2025",
    days: [
      { label: "Mon", date: 9, isToday: false, events: [STANDUP] },
      {
        label: "Tue",
        date: 10,
        isToday: false,
        events: [
          STANDUP,
          {
            time: "13:00",
            duration: "1 hr",
            title: "Hiring panel: Backend",
            location: "Conf room B",
            status: "confirmed",
          },
        ],
      },
      { label: "Wed", date: 11, isToday: false, events: [STANDUP] },
      {
        label: "Thu",
        date: 12,
        isToday: false,
        events: [
          STANDUP,
          {
            time: "15:00",
            duration: "45 min",
            title: "Budget planning",
            location: "Finance suite",
            status: "tentative",
          },
        ],
      },
      { label: "Fri", date: 13, isToday: false, events: [STANDUP] },
      { label: "Sat", date: 14, isToday: false, events: [] },
      { label: "Sun", date: 15, isToday: false, events: [] },
    ],
  },
  {
    range: "Jun 16 – Jun 22, 2025",
    days: [
      {
        label: "Mon",
        date: 16,
        isToday: false,
        events: [
          STANDUP,
          {
            time: "14:00",
            duration: "1 hr",
            title: "Product roadmap review",
            location: "Conf room A",
            status: "confirmed",
          },
        ],
      },
      {
        label: "Tue",
        date: 17,
        isToday: true,
        events: [
          STANDUP,
          {
            time: "11:00",
            duration: "2 hr",
            title: "Design system workshop",
            location: "Studio 2",
            status: "confirmed",
          },
          {
            time: "16:30",
            duration: "30 min",
            title: "1:1 with Jordan",
            status: "tentative",
          },
        ],
      },
      {
        label: "Wed",
        date: 18,
        isToday: false,
        events: [
          STANDUP,
          {
            time: "13:00",
            duration: "1 hr",
            title: "Q2 retrospective",
            location: "Main boardroom",
            status: "confirmed",
          },
        ],
      },
      {
        label: "Thu",
        date: 19,
        isToday: false,
        events: [
          STANDUP,
          {
            time: "10:30",
            duration: "45 min",
            title: "Engineering sync",
            status: "confirmed",
          },
          {
            time: "15:00",
            duration: "1 hr",
            title: "Vendor call: Acme Payments",
            location: "External",
            status: "tentative",
          },
        ],
      },
      {
        label: "Fri",
        date: 20,
        isToday: false,
        events: [
          STANDUP,
          {
            time: "12:00",
            duration: "30 min",
            title: "Sprint planning kick-off",
            status: "cancelled",
          },
        ],
      },
      { label: "Sat", date: 21, isToday: false, events: [] },
      {
        label: "Sun",
        date: 22,
        isToday: false,
        events: [
          {
            time: "10:00",
            duration: "1 hr",
            title: "Team offsite prep call",
            status: "tentative",
          },
        ],
      },
    ],
  },
  {
    range: "Jun 23 – Jun 29, 2025",
    days: [
      {
        label: "Mon",
        date: 23,
        isToday: false,
        events: [
          STANDUP,
          {
            time: "11:00",
            duration: "6 hr",
            title: "Design sprint",
            location: "Studio 2",
            status: "confirmed",
          },
        ],
      },
      { label: "Tue", date: 24, isToday: false, events: [STANDUP] },
      {
        label: "Wed",
        date: 25,
        isToday: false,
        events: [
          STANDUP,
          {
            time: "14:00",
            duration: "1 hr",
            title: "Customer advisory board",
            location: "External",
            status: "confirmed",
          },
        ],
      },
      { label: "Thu", date: 26, isToday: false, events: [STANDUP] },
      {
        label: "Fri",
        date: 27,
        isToday: false,
        events: [
          STANDUP,
          {
            time: "16:00",
            duration: "1 hr",
            title: "Team social",
            location: "Rooftop",
            status: "tentative",
          },
        ],
      },
      { label: "Sat", date: 28, isToday: false, events: [] },
      { label: "Sun", date: 29, isToday: false, events: [] },
    ],
  },
]

const TODAY_WEEK = 1

const STATUS_ACCENT: Record<EventStatus, string> = {
  confirmed: "bg-primary",
  tentative: "bg-muted-foreground",
  cancelled: "bg-destructive",
}

const STATUS_LABEL: Record<EventStatus, string> = {
  confirmed: "Confirmed",
  tentative: "Tentative",
  cancelled: "Cancelled",
}

export default function CalendarBlock() {
  const [weekIdx, setWeekIdx] = React.useState(TODAY_WEEK)
  const week = WEEKS[weekIdx]
  const totalEvents = week.days.reduce((n, d) => n + d.events.length, 0)

  return (
    <section className="flex w-full items-center justify-center bg-background px-6 py-12 text-foreground">
      <div className="w-full max-w-xl">
        <div className="overflow-hidden rounded-xl border border-border bg-card">
          <div className="flex items-center justify-between gap-4 px-4 py-3.5">
            <div className="flex items-center gap-3">
              <span className="flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
                <IconPlaceholder
                  lucide="Calendar"
                  tabler="IconCalendar"
                  hugeicons="CalendarIcon"
                  phosphor="Calendar"
                  remixicon="RiCalendarLine"
                  className="size-5"
                  aria-hidden="true"
                />
              </span>
              <div className="flex flex-col">
                <h1 className="font-heading text-sm font-semibold tracking-tight tabular-nums">
                  {week.range}
                </h1>
                <p className="text-xs text-muted-foreground tabular-nums">
                  {totalEvents} {totalEvents === 1 ? "event" : "events"} this
                  week
                </p>
              </div>
            </div>
            <div className="flex items-center gap-1">
              <Button
                variant="ghost"
                size="icon-sm"
                aria-label="Previous week"
                disabled={weekIdx === 0}
                onClick={() => setWeekIdx((i) => Math.max(0, i - 1))}
              >
                <IconPlaceholder
                  lucide="ChevronLeft"
                  tabler="IconChevronLeft"
                  hugeicons="ArrowLeft01Icon"
                  phosphor="CaretLeft"
                  remixicon="RiArrowLeftSLine"
                  aria-hidden="true"
                />
              </Button>
              <Button
                variant="outline"
                size="sm"
                onClick={() => setWeekIdx(TODAY_WEEK)}
              >
                Today
              </Button>
              <Button
                variant="ghost"
                size="icon-sm"
                aria-label="Next week"
                disabled={weekIdx === WEEKS.length - 1}
                onClick={() =>
                  setWeekIdx((i) => Math.min(WEEKS.length - 1, i + 1))
                }
              >
                <IconPlaceholder
                  lucide="ChevronRight"
                  tabler="IconChevronRight"
                  hugeicons="ArrowRight01Icon"
                  phosphor="CaretRight"
                  remixicon="RiArrowRightSLine"
                  aria-hidden="true"
                />
              </Button>
            </div>
          </div>

          <Separator />

          <div>
            {week.days.map((day, dayIdx) => (
              <div key={day.date}>
                <div
                  className={cn(
                    "flex gap-4 px-4 py-3",
                    day.isToday && "bg-primary/5"
                  )}
                >
                  <div className="flex w-11 shrink-0 flex-col items-center gap-1 pt-1">
                    <span
                      className={cn(
                        "text-[10px] font-semibold tracking-widest uppercase",
                        day.isToday ? "text-primary" : "text-muted-foreground"
                      )}
                    >
                      {day.label}
                    </span>
                    <span
                      className={cn(
                        "flex size-8 items-center justify-center rounded-md text-sm font-semibold tabular-nums",
                        day.isToday
                          ? "bg-primary text-primary-foreground"
                          : "text-foreground"
                      )}
                    >
                      {day.date}
                    </span>
                  </div>

                  <div className="flex min-w-0 flex-1 flex-col justify-center gap-1">
                    {day.events.length === 0 ? (
                      <p className="py-1.5 text-xs text-muted-foreground/70">
                        No events
                      </p>
                    ) : (
                      day.events.map((event) => (
                        <Popover key={`${event.time}-${event.title}`}>
                          <PopoverTrigger
                            render={
                              <button
                                type="button"
                                className="group flex w-full items-stretch gap-3 px-2 py-1.5 text-left transition-colors hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-none"
                              />
                            }
                          >
                            <span
                              className={cn(
                                "w-1 shrink-0",
                                STATUS_ACCENT[event.status]
                              )}
                              role="img"
                              aria-label={STATUS_LABEL[event.status]}
                            />

                            <div className="flex w-14 shrink-0 flex-col">
                              <span className="text-xs font-medium text-foreground tabular-nums">
                                {event.time}
                              </span>
                              <span className="text-[10px] text-muted-foreground">
                                {event.duration}
                              </span>
                            </div>

                            <div className="flex min-w-0 flex-1 flex-col gap-0.5">
                              <span
                                className={cn(
                                  "truncate text-sm leading-snug font-medium",
                                  event.status === "cancelled"
                                    ? "text-muted-foreground line-through"
                                    : "text-foreground"
                                )}
                              >
                                {event.title}
                              </span>
                              {event.location && (
                                <span className="flex items-center gap-1 text-[10px] text-muted-foreground">
                                  <IconPlaceholder
                                    lucide="MapPin"
                                    tabler="IconMapPin"
                                    hugeicons="MapPinIcon"
                                    phosphor="MapPin"
                                    remixicon="RiMapPinLine"
                                    className="size-3 shrink-0"
                                    aria-hidden="true"
                                  />
                                  {event.location}
                                </span>
                              )}
                            </div>

                            <IconPlaceholder
                              lucide="ChevronRight"
                              tabler="IconChevronRight"
                              hugeicons="ArrowRight01Icon"
                              phosphor="CaretRight"
                              remixicon="RiArrowRightSLine"
                              className="size-4 shrink-0 self-center text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"
                              aria-hidden="true"
                            />
                          </PopoverTrigger>

                          <PopoverContent align="start" className="w-64">
                            <PopoverHeader>
                              <PopoverTitle
                                className={cn(
                                  event.status === "cancelled" &&
                                    "text-muted-foreground line-through"
                                )}
                              >
                                {event.title}
                              </PopoverTitle>
                              <PopoverDescription>
                                {day.label} {day.date}
                              </PopoverDescription>
                            </PopoverHeader>
                            <Separator />
                            <div className="flex flex-col gap-1.5 text-muted-foreground">
                              <span className="flex items-center gap-1.5">
                                <IconPlaceholder
                                  lucide="Clock"
                                  tabler="IconClock"
                                  hugeicons="TimeIcon"
                                  phosphor="Clock"
                                  remixicon="RiTimeLine"
                                  className="size-3.5 shrink-0"
                                  aria-hidden="true"
                                />
                                {event.time} ({event.duration})
                              </span>
                              {event.location && (
                                <span className="flex items-center gap-1.5">
                                  <IconPlaceholder
                                    lucide="MapPin"
                                    tabler="IconMapPin"
                                    hugeicons="MapPinIcon"
                                    phosphor="MapPin"
                                    remixicon="RiMapPinLine"
                                    className="size-3.5 shrink-0"
                                    aria-hidden="true"
                                  />
                                  {event.location}
                                </span>
                              )}
                              <span className="flex items-center gap-1.5">
                                <span
                                  className={cn(
                                    "size-1.5 shrink-0",
                                    STATUS_ACCENT[event.status]
                                  )}
                                  aria-hidden="true"
                                />
                                {STATUS_LABEL[event.status]}
                              </span>
                            </div>
                          </PopoverContent>
                        </Popover>
                      ))
                    )}
                  </div>
                </div>

                {dayIdx < week.days.length - 1 && <Separator />}
              </div>
            ))}
          </div>

          <Separator />

          <div className="flex items-center gap-4 px-4 py-3">
            {(
              [
                ["confirmed", "Confirmed"],
                ["tentative", "Tentative"],
                ["cancelled", "Cancelled"],
              ] as [EventStatus, string][]
            ).map(([status, label]) => (
              <div key={status} className="flex items-center gap-1.5">
                <span
                  className={cn("size-1.5", STATUS_ACCENT[status])}
                  aria-hidden="true"
                />
                <span className="text-[10px] text-muted-foreground">
                  {label}
                </span>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  )
}
```
