Peek Spread
betaA two-page peek for an event: the plan on the left, and who is going on the right.
Preview
peek
This week: Design workshop and Demo day.
spread
Design workshop
- Going
- Going
- Going
- Going
- Going
- Maybe
- No reply
- No reply
- Can't make it
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/peek-spread
Use it for
- As a HoverCard's content on an event's title, with width 480. Its title names the peek.
- plan lists the blocks in order. minutes draws the proportion bar and the total. at shows a time instead, like 15:00.
- facts are [label, value] rows under the plan. With a byline or an action, they sit at the foot, just above it. A PersonValue, like { person: 'Steve Rowe' }, shows with a photo.
- people lists who is going. rsvp adds a mark at the row's end, and group splits the list, like Design and Engineering. Leave group out when each row's meta already names the team.
- The plan sets the height and never scrolls. The people page takes that height and scrolls inside it.
Avoid it for
- A peek about one person or one thing. Use PeekCard.
- A full event page. Link to it with action.
- A plan with more than 8 blocks. The peek is a glance. Put the rest on the plan's page.
Props
- title
Required. Names the peek.
string
- subtitle
When and where, like "Thu 1 Oct · 10:00 · Room 1".
string
- icon
An icon in a soft tile before the title.
ReactNode
- iconTone
The icon tile's color role.
"neutral" | "brand" | "danger" | "success" | "warning" · default "brand"
- planTitle
Heading over the plan.
string · default "Plan"
- plan
Plan blocks in order. minutes draws the bar and the total. at shows a time instead.
{ label: string; minutes?: number; at?: string }[]
- facts
Rows under the plan. With a byline or an action, they sit at the foot of the column, just above it.
[label: string, value: ReactNode | PersonValue][]
- by
Who made the plan, at its foot with their photo: "By Sam Carter".
PersonValue
- people
Who is going. Each group lists going, maybe, no reply, then can't make it.
{ person; photo?; meta?; group?; rsvp?: "yes" | "maybe" | "none" | "no" }[]
- peopleTitle
Heading over the people. n counts going and no rsvp.
string · default "{n} going"
- peopleMeta
A muted line beside the people title, like "6 no reply".
string
- action
A link at the foot of the plan, like "Open plan".
{ label: string; href?: string; onClick? }
- height
Least height of both pages, in pixels. The plan grows past it. The people page scrolls inside it.
number · default 252
- className
Extra classes on the spread. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| title | string | - | Required. Names the peek. |
| subtitle | string | - | When and where, like "Thu 1 Oct · 10:00 · Room 1". |
| icon | ReactNode | - | An icon in a soft tile before the title. |
| iconTone | "neutral" | "brand" | "danger" | "success" | "warning" | "brand" | The icon tile's color role. |
| planTitle | string | "Plan" | Heading over the plan. |
| plan | { label: string; minutes?: number; at?: string }[] | - | Plan blocks in order. minutes draws the bar and the total. at shows a time instead. |
| facts | [label: string, value: ReactNode | PersonValue][] | - | Rows under the plan. With a byline or an action, they sit at the foot of the column, just above it. |
| by | PersonValue | - | Who made the plan, at its foot with their photo: "By Sam Carter". |
| people | { person; photo?; meta?; group?; rsvp?: "yes" | "maybe" | "none" | "no" }[] | - | Who is going. Each group lists going, maybe, no reply, then can't make it. |
| peopleTitle | string | "{n} going" | Heading over the people. n counts going and no rsvp. |
| peopleMeta | string | - | A muted line beside the people title, like "6 no reply". |
| action | { label: string; href?: string; onClick? } | - | A link at the foot of the plan, like "Open plan". |
| height | number | 252 | Least height of both pages, in pixels. The plan grows past it. The people page scrolls inside it. |
| className | string | - | Extra classes on the spread. Later classes win. |
Accessibility
- In a HoverCard, the title names the peek's dialog.
- The plan is a group named by its heading. Its bar is hidden from screen readers, since each block reads its own minutes.
- The people list scrolls, so it is a Tab stop named by its heading, and the keyboard scrolls it with the arrow keys.
- Its pages are named groups, not landmarks, so two spreads with the same headings on one page never repeat a landmark.
- Each rsvp mark has words for screen readers: Going, Maybe, No reply, or Can't make it.
- In a narrow peek the pages stack, and the people page keeps its own height.
Examples
On an event's title
<HoverCard
width={480}
glance={false}
content={
<PeekSpread
title="Design workshop"
subtitle="Thu 1 Oct · 10:00 · Room 1"
plan={[
{ label: "Welcome", minutes: 15 },
{ label: "Research", minutes: 25 },
{ label: "Prototype", minutes: 30 },
]}
facts={[["Host", { person: "Steve Rowe" }]]}
by={{ person: "Sam Carter" }}
people={[
{ person: "Josh Tait", meta: "Designer", rsvp: "yes" },
{ person: "Kate Clarke", meta: "Engineer", rsvp: "maybe" },
]}
peopleMeta="6 no reply"
/>
}
>
Design workshop
</HoverCard>