# Parcel: productivity workspace

> A five-app productivity suite in one shell: a Today page with agenda, tasks and a focus timer, plus a full mail client, week calendar, drag-and-drop board and settings. A tab bar on phones.

A shadcn/ui block from the [@ballmac](https://ui.ballmac.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/ballmac/template-workspace).

- Page, with a live preview: https://shadcn.exchange/r/ballmac/template-workspace
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/ballmac/template-workspace-light-c33eaf22cb043ad8.png
- Registry item: https://ui.ballmac.com/r/template-workspace.json
- Categories: templates
- Listed: 2026-10-14, source last changed 2026-10-14

## Install

```bash
npx shadcn@latest add https://ui.ballmac.com/r/template-workspace.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: `lucide-react`
- Registry items: `utils`, `https://ui.ballmac.com/r/calendar-1.json`, `https://ui.ballmac.com/r/command.json`, `https://ui.ballmac.com/r/kanban-1.json`, `https://ui.ballmac.com/r/mail-1.json`, `https://ui.ballmac.com/r/settings-1.json`

## Notes from the registry

Pages are at /workspace, /workspace/mail, /workspace/calendar, /workspace/board and /workspace/settings. Mail, calendar, board and settings are the Ballmac blocks: pass them your data through their props.

## Files

### `registry/ballmac/components/templates/workspace/workspace-fonts.ts`

```ts
// Ballmac UI: Parcel workspace template fonts. https://ui.ballmac.com/templates/template-workspace
import { Albert_Sans } from "next/font/google"

export const workspaceSans = Albert_Sans({ variable: "--workspace-sans", subsets: ["latin"], display: "swap", preload: false })
```

### `registry/ballmac/components/templates/workspace/workspace-theme.tsx`

```tsx
// Ballmac UI: Parcel workspace template shell. https://ui.ballmac.com/templates/template-workspace
"use client"

import * as React from "react"
import { CalendarDays, CheckSquare, Command as CommandIcon, Inbox, LayoutGrid, Plus, Search, Settings } from "lucide-react"

import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ballmac/command"
import { workspaceSans } from "@/components/ballmac/templates/workspace/workspace-fonts"
import { cn } from "@/lib/utils"

type WorkspacePage = "today" | "mail" | "calendar" | "board" | "settings"
type WorkspaceHrefs = Record<WorkspacePage, string>

const defaultHrefs: WorkspaceHrefs = { today: "/workspace", mail: "/workspace/mail", calendar: "/workspace/calendar", board: "/workspace/board", settings: "/workspace/settings" }

/** Parcel's palette: cool paper and navy ink with one blue and a touch of amber. */
const workspaceCss = `
.workspace-theme,body:not(:has([data-gallery])):has(.workspace-theme){--background:oklch(0.982 0.004 250);--foreground:oklch(0.21 0.03 262);--card:oklch(1 0 0);--card-foreground:oklch(0.21 0.03 262);--popover:oklch(1 0 0);--popover-foreground:oklch(0.21 0.03 262);--primary:oklch(0.28 0.05 262);--primary-foreground:oklch(0.985 0.004 250);--secondary:oklch(0.958 0.008 250);--secondary-foreground:oklch(0.21 0.03 262);--muted:oklch(0.958 0.008 250);--muted-foreground:oklch(0.48 0.03 258);--accent:oklch(0.94 0.012 250);--accent-foreground:oklch(0.21 0.03 262);--border:oklch(0.21 0.03 262 / 11%);--input:oklch(0.21 0.03 262 / 16%);--ring:oklch(0.5 0.17 258);--surface:oklch(0.966 0.007 250);--destructive:oklch(0.54 0.21 25);--chart-1:oklch(0.5 0.17 258);--chart-2:oklch(0.5 0.12 165);--chart-3:oklch(0.62 0.14 72);--chart-4:oklch(0.5 0.16 310);--chart-5:oklch(0.52 0.15 25);--radius:0.75rem}
.dark .workspace-theme,.dark body:not(:has([data-gallery])):has(.workspace-theme){--background:oklch(0.17 0.02 262);--foreground:oklch(0.95 0.008 250);--card:oklch(0.21 0.024 262);--card-foreground:oklch(0.95 0.008 250);--popover:oklch(0.23 0.026 262);--popover-foreground:oklch(0.95 0.008 250);--primary:oklch(0.95 0.008 250);--primary-foreground:oklch(0.21 0.03 262);--secondary:oklch(0.26 0.026 262);--secondary-foreground:oklch(0.95 0.008 250);--muted:oklch(0.25 0.026 262);--muted-foreground:oklch(0.73 0.025 255);--accent:oklch(0.28 0.03 262);--accent-foreground:oklch(0.95 0.008 250);--border:oklch(1 0 0 / 10%);--input:oklch(1 0 0 / 14%);--ring:oklch(0.72 0.14 258);--surface:oklch(0.19 0.022 262);--destructive:oklch(0.7 0.19 25);--chart-1:oklch(0.74 0.13 258);--chart-2:oklch(0.78 0.13 165);--chart-3:oklch(0.82 0.13 80);--chart-4:oklch(0.75 0.13 310);--chart-5:oklch(0.76 0.13 25)}
body:not(:has([data-gallery])):has(.workspace-theme){font-family:var(--workspace-sans),ui-sans-serif,system-ui,sans-serif}
`

const apps: { key: WorkspacePage; label: string; icon: typeof Inbox }[] = [
  { key: "today", label: "Today", icon: LayoutGrid },
  { key: "mail", label: "Mail", icon: Inbox },
  { key: "calendar", label: "Calendar", icon: CalendarDays },
  { key: "board", label: "Board", icon: CheckSquare },
  { key: "settings", label: "Settings", icon: Settings },
]

function ParcelMark({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 28 28" aria-hidden="true" className={cn("size-7", className)} fill="none">
      <rect width="28" height="28" rx="8" fill="var(--chart-1)" />
      <path d="M14 6 21 10v8l-7 4-7-4v-8z" stroke="var(--card)" strokeWidth="2" strokeLinejoin="round" />
      <path d="M7 10l7 4 7-4M14 14v8" stroke="var(--card)" strokeWidth="2" strokeLinejoin="round" />
    </svg>
  )
}

type WorkspaceShellProps = Omit<React.ComponentProps<"div">, "title"> & {
  /** The app being shown, so its icon is marked current. */
  page: WorkspacePage
  /** Override where pages live (used by previews). */
  hrefs?: Partial<WorkspaceHrefs>
  /** Let the content fill the window height (mail, calendar and board do). */
  fill?: boolean
}

/** Parcel's frame: an app rail (a tab bar on phones), a search and quick-create bar, and a command menu on ⌘K. */
function WorkspaceShell({ page, hrefs: overrides, fill, className, style, children, ...props }: WorkspaceShellProps) {
  const hrefs = { ...defaultHrefs, ...overrides }
  const [palette, setPalette] = React.useState(false)
  React.useEffect(() => {
    const cls = workspaceSans.variable
    if (!cls) return
    document.body.classList.add(cls)
    return () => document.body.classList.remove(cls)
  }, [])
  const current = apps.find((a) => a.key === page)!
  return (
    <div
      data-slot="workspace"
      className={cn("workspace-theme bg-background text-foreground flex min-h-dvh flex-col md:flex-row", fill && "h-dvh overflow-hidden", workspaceSans.variable, className)}
      style={{ fontFamily: "var(--workspace-sans), ui-sans-serif, system-ui, sans-serif", ...style }}
      {...props}
    >
      <style>{workspaceCss}</style>
      <nav aria-label="Apps" className="bg-surface order-last flex shrink-0 items-center justify-around border-t px-2 py-1.5 md:order-none md:w-[4.5rem] md:flex-col md:justify-start md:gap-1 md:border-t-0 md:border-e md:py-3">
        <a href={hrefs.today} aria-label="Parcel home" className="focus-visible:ring-ring/50 mb-3 hidden rounded-xl outline-none focus-visible:ring-[3px] md:block"><ParcelMark /></a>
        {apps.map((a) => (
          <a key={a.key} href={hrefs[a.key]} aria-current={page === a.key ? "page" : undefined} className="text-muted-foreground hover:text-foreground hover:bg-accent aria-[current=page]:bg-card aria-[current=page]:text-foreground aria-[current=page]:shadow-sm focus-visible:ring-ring/50 flex min-w-14 flex-col items-center gap-1 rounded-xl px-2 py-1.5 text-[11px] font-medium outline-none transition-colors focus-visible:ring-[3px] md:w-14">
            <a.icon className="size-5" aria-hidden="true" />
            {a.label}
          </a>
        ))}
      </nav>
      <div className={cn("flex min-w-0 flex-1 flex-col", fill && "min-h-0")}>
        <header className="flex h-14 shrink-0 items-center gap-3 border-b px-4 sm:px-6">
          <h1 className="text-[15px] font-semibold tracking-tight">{current.label}</h1>
          <button type="button" onClick={() => setPalette(true)} className="text-muted-foreground bg-card hover:bg-accent focus-visible:ring-ring/50 mx-auto flex h-9 w-full max-w-md items-center gap-2.5 rounded-xl border px-3 text-sm outline-none transition-colors focus-visible:ring-[3px]">
            <Search className="size-4" aria-hidden="true" />
            <span className="flex-1 text-start">Search or jump to…</span>
            <kbd className="bg-muted hidden items-center gap-0.5 rounded px-1.5 py-0.5 text-[11px] sm:flex"><CommandIcon className="size-3" aria-hidden="true" />K</kbd>
          </button>
          <a href={hrefs.mail} className="bg-primary text-primary-foreground focus-visible:ring-ring/50 inline-flex h-9 shrink-0 items-center gap-1.5 rounded-xl px-3 text-sm font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]"><Plus className="size-4" aria-hidden="true" /><span className="hidden sm:inline">Compose</span><span className="sr-only sm:hidden">Compose</span></a>
          <span className="bg-chart-4/25 flex size-9 shrink-0 items-center justify-center rounded-full text-xs font-bold" aria-hidden="true">MK</span>
        </header>
        <div className={cn("flex-1", fill && "min-h-0 overflow-hidden")}>{children}</div>
      </div>
      <CommandDialog open={palette} onOpenChange={setPalette} title="Search or jump to">
        <CommandInput placeholder="Search mail, events and tasks…" />
        <CommandList>
          <CommandEmpty>No results.</CommandEmpty>
          <CommandGroup heading="Apps">
            {apps.map((a) => <CommandItem key={a.key} value={a.label} onSelect={() => { setPalette(false); window.location.assign(hrefs[a.key]) }}><a.icon aria-hidden="true" /> {a.label}</CommandItem>)}
          </CommandGroup>
        </CommandList>
      </CommandDialog>
    </div>
  )
}

export { ParcelMark, WorkspaceShell, defaultHrefs as workspaceDefaultHrefs, type WorkspaceHrefs, type WorkspacePage, type WorkspaceShellProps }
```

### `registry/ballmac/components/templates/workspace/workspace-today.tsx`

```tsx
// Ballmac UI: Parcel today page. https://ui.ballmac.com/templates/template-workspace
"use client"

import * as React from "react"
import { Archive, Check, Pause, Play, Plus, Reply, RotateCcw } from "lucide-react"

import { WorkspaceShell, type WorkspaceHrefs } from "@/components/ballmac/templates/workspace/workspace-theme"
import { cn } from "@/lib/utils"

type Task = { id: string; text: string; done: boolean }

const startTasks: Task[] = [
  { id: "t1", text: "Send the Q4 plan to Priya", done: true },
  { id: "t2", text: "Review Dev’s onboarding mockups", done: false },
  { id: "t3", text: "Book the Lisbon flights", done: false },
  { id: "t4", text: "Write Friday’s team update", done: false },
]

const agenda = [
  { time: "09:00", end: "09:30", title: "Design standup", where: "Meet", tone: "bg-chart-1" },
  { time: "10:30", end: "11:30", title: "Onboarding review", where: "Room 4", tone: "bg-chart-2" },
  { time: "13:00", end: "13:45", title: "Lunch with Amara", where: "Bar Lucia", tone: "bg-chart-3" },
  { time: "15:00", end: "16:00", title: "1:1 with Dev", where: "Meet", tone: "bg-chart-4" },
]

const inbox = [
  { id: "m1", from: "Priya Raman", subject: "Re: Q4 plan — two small edits", preview: "Love it. Can we add owners to the risks?", time: "8:12 AM" },
  { id: "m2", from: "Amara Okafor", subject: "Lunch today?", preview: "Still on for 13:00 at Bar Lucia?", time: "7:48 AM" },
  { id: "m3", from: "Billing", subject: "Your October invoice", preview: "Your invoice for $48.00 is ready.", time: "Yesterday" },
]

const NOW = "09:41"
const toMin = (t: string) => Number(t.slice(0, 2)) * 60 + Number(t.slice(3))

const card = "bg-card rounded-2xl border"

type WorkspaceTodayProps = React.ComponentProps<"div"> & { hrefs?: Partial<WorkspaceHrefs> }

/** The Parcel today page: your agenda with a now line, tasks you can tick and add, inbox highlights you can archive, and a focus timer. */
function WorkspaceToday({ hrefs, ...props }: WorkspaceTodayProps) {
  const [tasks, setTasks] = React.useState(startTasks)
  const [draft, setDraft] = React.useState("")
  const [mail, setMail] = React.useState(inbox)
  const [secs, setSecs] = React.useState(25 * 60)
  const [running, setRunning] = React.useState(false)
  const [note, setNote] = React.useState("")

  React.useEffect(() => {
    if (!running) return
    const id = setInterval(() => setSecs((s) => (s <= 1 ? 0 : s - 1)), 1000)
    return () => clearInterval(id)
  }, [running])
  const finished = secs === 0
  const done = tasks.filter((t) => t.done).length
  const pct = tasks.length ? (done / tasks.length) * 100 : 0
  const mm = String(Math.floor(secs / 60)).padStart(2, "0")
  const ss = String(secs % 60).padStart(2, "0")

  function add(e: React.FormEvent) {
    e.preventDefault()
    const text = draft.trim()
    if (!text) return
    setTasks((all) => [...all, { id: `n${all.length}`, text, done: false }])
    setDraft("")
  }

  return (
    <WorkspaceShell page="today" hrefs={hrefs} {...props}>
      <main className="mx-auto max-w-6xl px-4 py-6 pb-10 sm:px-6">
        <p className="text-muted-foreground text-sm font-medium">Thursday, October 1</p>
        <h2 className="mt-1 text-3xl font-extrabold tracking-[-0.03em] sm:text-4xl">Good morning, Mina.</h2>
        <p className="text-muted-foreground mt-2 text-pretty">You have {agenda.length} meetings, {tasks.length - done} tasks left and {mail.length} messages worth a look.</p>

        <div className="mt-8 grid grid-cols-[minmax(0,1fr)] gap-4 lg:grid-cols-3">
          <section aria-labelledby="wt-agenda" className={cn(card, "p-5 lg:row-span-2")}>
            <h3 id="wt-agenda" className="font-bold">Agenda</h3>
            <ol className="mt-4 space-y-1">
              {agenda.map((e, i) => {
                const past = toMin(e.end) <= toMin(NOW)
                const live = toMin(e.time) <= toMin(NOW) && toMin(NOW) < toMin(e.end)
                const showNow = !past && !live && (i === 0 || toMin(agenda[i - 1].end) <= toMin(NOW))
                return (
                  <React.Fragment key={e.title}>
                    {showNow && (
                      <li aria-label={`Now, ${NOW}`} className="flex items-center gap-2 py-1.5"><span className="bg-destructive size-2 rounded-full" aria-hidden="true" /><span className="bg-destructive/50 h-px flex-1" aria-hidden="true" /><span className="text-destructive text-xs font-semibold tabular-nums">{NOW}</span></li>
                    )}
                    <li className={cn("flex gap-3 rounded-xl p-2.5", live && "bg-accent", past && "text-muted-foreground")}>
                      <span className={cn("w-1 shrink-0 rounded-full", e.tone)} aria-hidden="true" />
                      <div className="min-w-0 flex-1"><p className="text-sm font-semibold">{e.title}</p><p className="text-muted-foreground text-xs">{e.time}–{e.end} · {e.where}</p></div>
                      {past && <Check className="text-chart-2 size-4 shrink-0" aria-label="Done" />}
                    </li>
                  </React.Fragment>
                )
              })}
            </ol>
          </section>

          <section aria-labelledby="wt-tasks" className={cn(card, "p-5")}>
            <div className="flex items-center justify-between"><h3 id="wt-tasks" className="font-bold">Tasks</h3><p className="text-muted-foreground text-sm tabular-nums" role="status">{done} of {tasks.length} done</p></div>
            <div className="bg-muted mt-3 h-1.5 rounded-full" aria-hidden="true"><div className="bg-chart-2 h-full rounded-full transition-[width] duration-500 motion-reduce:transition-none" style={{ width: `${pct}%` }} /></div>
            <ul className="mt-4 space-y-1">
              {tasks.map((t) => (
                <li key={t.id}>
                  <label className="hover:bg-accent flex cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-sm">
                    <input type="checkbox" checked={t.done} onChange={() => setTasks((all) => all.map((x) => (x.id === t.id ? { ...x, done: !x.done } : x)))} className="accent-chart-1 size-4" />
                    <span className={cn(t.done && "text-muted-foreground line-through")}>{t.text}</span>
                  </label>
                </li>
              ))}
            </ul>
            <form onSubmit={add} className="mt-3 flex gap-2">
              <label htmlFor="wt-new" className="sr-only">Add a task</label>
              <input id="wt-new" value={draft} onChange={(e) => setDraft(e.target.value)} placeholder="Add a task" className="bg-background focus-visible:ring-ring/50 placeholder:text-muted-foreground h-9 min-w-0 flex-1 rounded-lg border px-3 text-sm outline-none focus-visible:ring-[3px]" />
              <button type="submit" aria-label="Add task" className="bg-primary text-primary-foreground focus-visible:ring-ring/50 inline-flex size-9 items-center justify-center rounded-lg outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]"><Plus className="size-4" aria-hidden="true" /></button>
            </form>
          </section>

          <section aria-labelledby="wt-focus" className={cn(card, "flex flex-col items-center p-5 text-center")}>
            <h3 id="wt-focus" className="self-start font-bold">Focus</h3>
            <p className="mt-6 text-6xl font-extrabold tracking-[-0.04em] tabular-nums" role="timer" aria-label={`${mm} minutes ${ss} seconds left`}>{mm}:{ss}</p>
            <p className="text-muted-foreground mt-2 text-sm" aria-live="polite">{finished ? "Nice work. Take five." : running ? "Stay with one thing." : "Pick a task and begin."}</p>
            <div className="mt-5 flex gap-2">
              <button type="button" onClick={() => { if (finished) setSecs(25 * 60); setRunning((r) => !r) }} className="bg-primary text-primary-foreground focus-visible:ring-ring/50 inline-flex h-10 items-center gap-2 rounded-xl px-5 text-sm font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]">
                {running ? <><Pause className="size-4" aria-hidden="true" />Pause</> : <><Play className="size-4" aria-hidden="true" />{secs === 25 * 60 ? "Start" : "Resume"}</>}
              </button>
              <button type="button" aria-label="Reset timer" onClick={() => { setRunning(false); setSecs(25 * 60) }} className="hover:bg-accent focus-visible:ring-ring/50 inline-flex size-10 items-center justify-center rounded-xl border outline-none focus-visible:ring-[3px]"><RotateCcw className="size-4" aria-hidden="true" /></button>
            </div>
          </section>

          <section aria-labelledby="wt-inbox" className={cn(card, "p-5 lg:col-span-2")}>
            <div className="flex items-center justify-between"><h3 id="wt-inbox" className="font-bold">Worth a look</h3><a href={hrefs?.mail ?? "/workspace/mail"} className="text-chart-1 rounded text-sm font-semibold underline underline-offset-4">Open inbox</a></div>
            <p className="sr-only" role="status">{note}</p>
            {mail.length === 0 ? (
              <p className="text-muted-foreground mt-6 rounded-xl border border-dashed p-8 text-center text-sm">Inbox zero. Enjoy the quiet.</p>
            ) : (
              <ul className="mt-3 divide-y">
                {mail.map((m) => (
                  <li key={m.id} className="flex items-center gap-3 py-3">
                    <span className="bg-chart-1/15 flex size-9 shrink-0 items-center justify-center rounded-full text-xs font-bold" aria-hidden="true">{m.from.split(" ").map((w) => w[0]).join("")}</span>
                    <div className="min-w-0 flex-1"><p className="truncate text-sm"><span className="font-semibold">{m.from}</span> <span className="text-muted-foreground">· {m.time}</span></p><p className="truncate text-sm font-medium">{m.subject}</p><p className="text-muted-foreground truncate text-xs">{m.preview}</p></div>
                    <div className="flex shrink-0 gap-1">
                      <a href={hrefs?.mail ?? "/workspace/mail"} aria-label={`Reply to ${m.from}`} className="hover:bg-accent focus-visible:ring-ring/50 inline-flex size-8 items-center justify-center rounded-lg outline-none focus-visible:ring-[3px]"><Reply className="size-4 rtl:-scale-x-100" aria-hidden="true" /></a>
                      <button type="button" aria-label={`Archive: ${m.subject}`} onClick={() => { setMail((all) => all.filter((x) => x.id !== m.id)); setNote(`Archived ${m.subject}`) }} className="hover:bg-accent focus-visible:ring-ring/50 inline-flex size-8 items-center justify-center rounded-lg outline-none focus-visible:ring-[3px]"><Archive className="size-4" aria-hidden="true" /></button>
                    </div>
                  </li>
                ))}
              </ul>
            )}
          </section>
        </div>
      </main>
    </WorkspaceShell>
  )
}

export { WorkspaceToday, type WorkspaceTodayProps }
```

### `registry/ballmac/components/templates/workspace/workspace-mail.tsx`

```tsx
// Ballmac UI: Parcel mail page. https://ui.ballmac.com/templates/template-workspace
import * as React from "react"

import { Mail1 } from "@/components/ballmac/blocks/mail-1/mail-1"
import { WorkspaceShell, type WorkspaceHrefs } from "@/components/ballmac/templates/workspace/workspace-theme"

type WorkspaceMailProps = React.ComponentProps<"div"> & { hrefs?: Partial<WorkspaceHrefs> }

/** Parcel mail: the three-pane inbox with search, compose and keyboard shortcuts, filling the window. */
function WorkspaceMail({ hrefs, ...props }: WorkspaceMailProps) {
  return (
    <WorkspaceShell page="mail" hrefs={hrefs} fill {...props}>
      <Mail1 height="100%" className="rounded-none border-0" />
    </WorkspaceShell>
  )
}

export { WorkspaceMail, type WorkspaceMailProps }
```

### `registry/ballmac/components/templates/workspace/workspace-calendar.tsx`

```tsx
// Ballmac UI: Parcel calendar page. https://ui.ballmac.com/templates/template-workspace
import * as React from "react"

import { Calendar1 } from "@/components/ballmac/blocks/calendar-1/calendar-1"
import { WorkspaceShell, type WorkspaceHrefs } from "@/components/ballmac/templates/workspace/workspace-theme"

type WorkspaceCalendarProps = React.ComponentProps<"div"> & { hrefs?: Partial<WorkspaceHrefs> }

/** Parcel calendar: the week view with overlapping events, a now line and a new-event dialog, filling the window. */
function WorkspaceCalendar({ hrefs, ...props }: WorkspaceCalendarProps) {
  return (
    <WorkspaceShell page="calendar" hrefs={hrefs} fill {...props}>
      <Calendar1 height="100%" today="2026-10-01" now="09:41" className="rounded-none border-0" />
    </WorkspaceShell>
  )
}

export { WorkspaceCalendar, type WorkspaceCalendarProps }
```

### `registry/ballmac/components/templates/workspace/workspace-board.tsx`

```tsx
// Ballmac UI: Parcel board page. https://ui.ballmac.com/templates/template-workspace
import * as React from "react"

import { Kanban1 } from "@/components/ballmac/blocks/kanban-1/kanban-1"
import { WorkspaceShell, type WorkspaceHrefs } from "@/components/ballmac/templates/workspace/workspace-theme"

type WorkspaceBoardProps = React.ComponentProps<"div"> & { hrefs?: Partial<WorkspaceHrefs> }

/** Parcel board: drag cards between columns, move with the keyboard, add and filter by person. */
function WorkspaceBoard({ hrefs, ...props }: WorkspaceBoardProps) {
  return (
    <WorkspaceShell page="board" hrefs={hrefs} {...props}>
      <Kanban1 title="Q4 launch" description="Drag cards between columns, or use the Move to menu on any card." height="calc(100dvh - 15rem)" />
    </WorkspaceShell>
  )
}

export { WorkspaceBoard, type WorkspaceBoardProps }
```

### `registry/ballmac/components/templates/workspace/workspace-settings.tsx`

```tsx
// Ballmac UI: Parcel settings page. https://ui.ballmac.com/templates/template-workspace
import * as React from "react"

import { Settings1 } from "@/components/ballmac/blocks/settings-1/settings-1"
import { WorkspaceShell, type WorkspaceHrefs } from "@/components/ballmac/templates/workspace/workspace-theme"

type WorkspaceSettingsProps = React.ComponentProps<"div"> & { hrefs?: Partial<WorkspaceHrefs> }

/** Parcel settings: profile, preferences and account actions with a save bar that appears only when something changed. */
function WorkspaceSettings({ hrefs, ...props }: WorkspaceSettingsProps) {
  return (
    <WorkspaceShell page="settings" hrefs={hrefs} {...props}>
      <Settings1 title="Your account" description="Manage your profile and how Parcel works for you." />
    </WorkspaceShell>
  )
}

export { WorkspaceSettings, type WorkspaceSettingsProps }
```

### `registry/ballmac/app/workspace/page.tsx`

```tsx
// Ballmac UI: Workspace template route. https://ui.ballmac.com/templates/template-workspace
import type { Metadata } from "next"

import { WorkspaceToday } from "@/components/ballmac/templates/workspace/workspace-today"

export const metadata: Metadata = {
  title: "Parcel",
  description: "Mail, calendar and tasks in one calm place.",
}

export default function Page() {
  return <WorkspaceToday />
}
```

### `registry/ballmac/app/workspace/mail/page.tsx`

```tsx
// Ballmac UI: Workspace template route. https://ui.ballmac.com/templates/template-workspace
import type { Metadata } from "next"

import { WorkspaceMail } from "@/components/ballmac/templates/workspace/workspace-mail"

export const metadata: Metadata = {
  title: "Mail · Parcel",
  description: "A fast three-pane inbox.",
}

export default function Page() {
  return <WorkspaceMail />
}
```

### `registry/ballmac/app/workspace/calendar/page.tsx`

```tsx
// Ballmac UI: Workspace template route. https://ui.ballmac.com/templates/template-workspace
import type { Metadata } from "next"

import { WorkspaceCalendar } from "@/components/ballmac/templates/workspace/workspace-calendar"

export const metadata: Metadata = {
  title: "Calendar · Parcel",
  description: "Your week at a glance.",
}

export default function Page() {
  return <WorkspaceCalendar />
}
```

### `registry/ballmac/app/workspace/board/page.tsx`

```tsx
// Ballmac UI: Workspace template route. https://ui.ballmac.com/templates/template-workspace
import type { Metadata } from "next"

import { WorkspaceBoard } from "@/components/ballmac/templates/workspace/workspace-board"

export const metadata: Metadata = {
  title: "Board · Parcel",
  description: "Tasks on a board you can drag.",
}

export default function Page() {
  return <WorkspaceBoard />
}
```

### `registry/ballmac/app/workspace/settings/page.tsx`

```tsx
// Ballmac UI: Workspace template route. https://ui.ballmac.com/templates/template-workspace
import type { Metadata } from "next"

import { WorkspaceSettings } from "@/components/ballmac/templates/workspace/workspace-settings"

export const metadata: Metadata = {
  title: "Settings · Parcel",
  description: "Profile and preferences.",
}

export default function Page() {
  return <WorkspaceSettings />
}
```
