Ansora UI
Components

Patient header

Identity band at the top of a record: name, one identifier typeset with a slashed zero, and a named key-facts block, with slots for status flags and a correcting action.

identitydata

Install

pnpm dlx shadcn@4.23.0 add @ansora/patient-header

Examples

Usage

name and headingLevel are the only things this component renders itself besides the identifier: everything else is a slot or is delegated to @ansora/detail-fields. It is a <div>, not a <header> — header becomes a banner landmark inside SidebarInset's <main>, and a second banner beside the app shell's own is a navigation defect, not a nicety. The heading is what makes this navigable.

identifier is a separate prop and not a meta item, and that is a safety property rather than a layout preference. In a two-column <dl> a row can land in the far column, away from the name it identifies; and DetailField accepts format: "number", which is .num — right-aligned and with NO slashed zero. ISMP records a slashed zero being misread as 4, 6, 8 and 9, and 905612/3049 read as a value instead of an identifier is how the wrong record gets signed. The dedicated prop carries no format at all, so there is no code path in this component that can typeset an identifier as a value.

What the component cannot enforce and you must: do not smuggle a rodné číslo, an insurance number or a patient ID into meta with format: "number". The component's own identifier is safe by construction; a meta item is yours, and DetailFields will do exactly what you asked. Use format: "identifier" for anything read digit by digit — that is what it is for, and @ansora/detail-fields' own docs say the same thing from the other side.

variant is a decision about the VERTICAL BUDGET and about typography, nothing else. compact makes the name smaller and truncates it to one line so the band can sit above a live transcript for forty minutes without moving; full lets it wrap. It hides no fact — patient-header.test.ts refuses to let variant appear in any comparison, so it cannot grow into a prop that decides whether the identifier renders. The name is what gives way, never the identifier: the identifier wraps and never truncates, because an identifier cut off by an ellipsis is a number someone will read as complete.

flags and action are ReactNode slots, not verified: boolean / onCorrect. There is deliberately no matching state here: the moment this component knows about a match it stops being an identity band and becomes a matching widget, which locks out the four products that never match anyone. A slot also keeps the Czech word at the call site, so colour only ever arrives attached to a word. flags renders AFTER the heading, not inside it — inside, "Neověřeno" joins the heading's accessible name and the document outline.

There is no masking prop. Whether @ansora/core gets a maskIdentifier or every product masks at its own call site is an open decision, and this component is on a screen a doctor shares on a projector — ask before the first privacy review, not after.

Positioning AND padding are yours: pass className="sticky top-0 z-10 bg-background" where the screen wants it. A sticky prop would owe a background, a z-index and an offset it cannot know. The component ships no horizontal inset either, and full-bleed is the case that catches people: dropped straight into SidebarInset it sits flush against the navigation while every other band on the page is inset. Give it the same token the rest of the shell uses — className="border-b bg-card px-(--section-gap) py-2" is what makes it read as one stack with @ansora/session-bar below it. Inside a docked panel that already has padding, pass nothing: a second inset there is a double one.

Reading order: name → flags → identifier caption → identifier value → action → the named <dl>. action sits in its own grid track to the right of the name, but it is a LATER sibling in the DOM than the identifier, so that is where a screen reader reaches it — and an avatar, if you pass one, is read before the name.

Button and StatusPill in the example below are yours, not this item's: flags and action are slots, so shadcn add @ansora/patient-header installs neither.

import { Button } from "@/components/ui/button"
import { StatusPill } from "@/components/ui/ansora/status-pill"
import { PatientHeader } from "@/components/ansora/patient-header"

<PatientHeader
  headingLevel={1}
  name="Jana Nováková"
  identifier={{ caption: "Rodné číslo", value: "905612/3049" }}
  flags={<StatusPill tone="warn">Neověřeno</StatusPill>}
  meta={{
    label: "Údaje o pacientovi",
    items: [
      { label: "Číslo pojištěnce", value: "1100 405678", format: "identifier" },
      { label: "Ošetřující lékař", value: "MUDr. Petr Krejčí" },
    ],
  }}
  action={<Button size="sm" variant="outline">Otevřít kartu</Button>}
/>

Files

Dependencies

  • @ansora/base
  • @ansora/detail-fields

On this page