marmar-context / web / context / src / clinician / settings / bits.tsx
bits.tsx
Raw
import type { ReactNode } from "react";
import { Button, FieldError } from "../kit";

/** Label and value rows on a hairline, as on the patient's Details rail. */
export function Facts({ rows }: { rows: [ReactNode, ReactNode][] }) {
  return (
    <dl className="m-0 flex flex-col">
      {rows.map(([label, value], i) => (
        <div key={i} className="flex flex-wrap items-center justify-between gap-x-6 gap-y-1 py-2.5 shadow-[inset_0_-1px_0_var(--color-hairline)] last:shadow-none">
          <dt className="text-[13px] text-muted">{label}</dt>
          <dd className="m-0 text-right text-[13.5px] text-ink">{value}</dd>
        </div>
      ))}
    </dl>
  );
}

/** The last row of a form: its error on the left, the one save button on the right. */
export function SaveRow({ error, busy, disabled, onSave, label }: { error?: string; busy: boolean; disabled?: boolean; onSave: () => void; label: string }) {
  return (
    <div className="flex flex-wrap items-center justify-end gap-x-4 gap-y-2 pt-1">
      {error && <FieldError className="mr-auto">{error}</FieldError>}
      <Button variant="primary" disabled={disabled || busy} onClick={onSave}>
        {busy ? "Saving…" : label}
      </Button>
    </div>
  );
}

/** One topic of a section: a title and a quiet line over its fields. */
export function Group({ title, note, id, children }: { title: string; note?: ReactNode; id?: string; children: ReactNode }) {
  return (
    <div className="flex flex-col gap-4 pt-4 shadow-[inset_0_1px_0_var(--color-hairline)] first-of-type:pt-0 first-of-type:shadow-none">
      <div className="flex flex-col gap-1">
        <h3 className="m-0 text-[14px] leading-5 font-semibold text-ink">{title}</h3>
        {note && (
          <p id={id} className="m-0 text-[13px] leading-[19px] text-muted">
            {note}
          </p>
        )}
      </div>
      {children}
    </div>
  );
}