App

Contact List Card

A single card listing four on-call contacts as rows with a greyscale Cloud shape avatar, name, title and an outline Email button. Rows are divided by hairlines under a header and description.

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

import {
  CloudShape,
  type CloudPalette,
} from "@/components/graphics/cloud-shapes"
import { buttonVariants } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"

// The copy: change text here, not in the markup.
const COPY = {
  title: "Who to call",
  body: "The four people on console this week, in the order the checklist names them.",
  action: "Email",
} as const

const PEOPLE = [
  {
    name: "JoAnn Morgan",
    title: "Instrumentation Controller",
    email: "joann@orbcore.co",
  },
  { name: "Gene Kranz", title: "Flight Director", email: "gene@orbcore.co" },
  {
    name: "Katherine Johnson",
    title: "Trajectory Analyst",
    email: "katherine@orbcore.co",
  },
  {
    name: "Margaret Hamilton",
    title: "Flight Software Lead",
    email: "margaret@orbcore.co",
  },
] as const

// Knob: the orb's shading; zero chroma keeps it greyscale.
const ORB: CloudPalette = {
  main: "oklch(0.55 0 0)",
  accent: "oklch(0.7 0 0)",
}

export default function ContactListCard() {
  return (
    <Card className="mx-auto w-full max-w-md">
      <CardHeader>
        <CardTitle>{COPY.title}</CardTitle>
        <CardDescription>{COPY.body}</CardDescription>
      </CardHeader>
      <CardContent>
        <ul role="list" className="divide-y divide-border">
          {PEOPLE.map((person) => (
            <li key={person.email}>
              <Item size="sm" className="rounded-none px-0 py-3">
                <ItemMedia>
                  <CloudShape palette={ORB} width={40} height={40} />
                </ItemMedia>
                <ItemContent className="gap-0.5">
                  <ItemTitle>{person.name}</ItemTitle>
                  <ItemDescription className="text-xs">
                    {person.title}
                  </ItemDescription>
                </ItemContent>
                <ItemActions>
                  <a
                    href={`mailto:${person.email}`}
                    className={cn(
                      buttonVariants({ variant: "outline", size: "sm" })
                    )}
                  >
                    <EnvelopeSimpleIcon
                      aria-hidden="true"
                      data-icon="inline-start"
                    />
                    {COPY.action}
                  </a>
                </ItemActions>
              </Item>
            </li>
          ))}
        </ul>
      </CardContent>
    </Card>
  )
}

Installation

pnpm dlx shadcn add @01orbital/contact-list-card
npx shadcn add @01orbital/contact-list-card
yarn shadcn add @01orbital/contact-list-card
bunx --bun shadcn add @01orbital/contact-list-card

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

It lands at components/blocks/app/contact-list-card/index.tsx, as source you own and edit.

Usage

import ContactListCard from "@/components/blocks/app/contact-list-card"

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

Dependencies

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

Registry pieces it installs with it: