# Time card

A punch clock's time card: each session is a row with its date, time in, time out and how long it ran, and the key stamps the time now onto the next line. For timesheets, focus sessions and on-call shifts.

- Category: Records
- Docs: https://bevel-ui.vercel.app/components/time-card
- Import from: `@/components/ui/time-card`

## Install

```bash
npx shadcn@latest add @bevel/time-card
```

npm dependencies (installed by the CLI): `@base-ui/react`, `cn`.

## Anatomy

```tsx
<TimeCard title="Acme redesign · week 43" entries={entries} onPunch={punch}>
  <TimeCardStatus />
  <TimeCardPunch />
</TimeCard>
```

## Parts

| Part | Renders | Description |
| --- | --- | --- |
| `TimeCard` | div + table | title (the table's caption), entries ({ in, out? }, oldest first; no out means still clocked in), onPunch({ kind, at }), rows, locale, timeZone. Durations and times print with Intl; without a timeZone they print once the browser's is known. New stamps land; the history doesn't. |
| `TimeCardStatus` | p (status) | “Clocked in at 09:04.” or “Clocked out at 17:42. 10h 46m on this card.” Describes the punch key. |
| `TimeCardPunch` | Button | Punch in, or Punch out while a session is open. Calls onPunch with the time now. |

## Example: A contractor's timesheet

Three sessions on the card already. Punch in, then out, and watch the clock stamp each time.

```tsx
"use client"

import * as React from "react"
import { TimeCard, TimeCardPunch, TimeCardStatus, type TimeCardEntry } from "@/components/ui/time-card"

/** A contractor's timesheet for one client. Punch in, punch out; the clock stamps the time now. */
export function TimeCardTimesheet() {
  const [entries, setEntries] = React.useState<TimeCardEntry[]>([
    { in: "2026-10-19T08:58:00Z", out: "2026-10-19T12:31:00Z" },
    { in: "2026-10-19T13:15:00Z", out: "2026-10-19T17:42:00Z" },
    { in: "2026-10-20T09:04:00Z", out: "2026-10-20T11:50:00Z" },
  ])
  return (
    <TimeCard
      title="Acme redesign · Sam Okafor · week 43"
      entries={entries}
      onPunch={({ kind, at }) =>
        setEntries((all) =>
          kind === "in" ? [...all, { in: at }] : all.map((e, i) => (i === all.length - 1 ? { ...e, out: at } : e))
        )
      }
    >
      <div className="flex flex-wrap items-center justify-between gap-3">
        <TimeCardStatus />
        <TimeCardPunch disabled={entries.length >= 10} />
      </div>
    </TimeCard>
  )
}
```
