SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
2.6 KB · 71 lines tsx
Raw Blame History
1import type { ReactNode } from 'react';23/** Editorial section: kicker, serif title, optional description and right-aligned actions. */4export function Section({5  id,6  kicker,7  title,8  description,9  actions,10  children,11  className = '',12  level = 2,13}: {14  id?: string;15  kicker?: string;16  title: ReactNode;17  description?: ReactNode;18  actions?: ReactNode;19  children: ReactNode;20  className?: string;21  level?: 2 | 3;22}) {23  const H = level === 3 ? 'h3' : 'h2';24  return (25    <section id={id} aria-labelledby={id ? `${id}-title` : undefined} className={`ci-rule pt-5 ${className}`}>26      <div className="mb-3 flex flex-wrap items-end justify-between gap-2">27        <div className="min-w-0">28          {kicker ? <p className="ci-kicker mb-1">{kicker}</p> : null}29          <H id={id ? `${id}-title` : undefined} className={level === 3 ? 'text-lg' : 'text-xl sm:text-2xl'}>30            {title}31          </H>32          {description ? <p className="mt-1 max-w-3xl text-[13.5px] text-ink-2">{description}</p> : null}33        </div>34        {actions ? <div className="flex shrink-0 items-center gap-2 text-[13px]">{actions}</div> : null}35      </div>36      {children}37    </section>38  );39}4041export function PageHeader({ kicker, title, lede, children, className = '' }: { kicker?: string; title: ReactNode; lede?: ReactNode; children?: ReactNode; className?: string }) {42  return (43    <header className={`pb-4 pt-6 sm:pt-8 ${className}`}>44      {kicker ? <p className="ci-kicker mb-2">{kicker}</p> : null}45      <h1 className="text-3xl leading-tight sm:text-4xl">{title}</h1>46      {lede ? <p className="mt-2 max-w-3xl text-[15px] leading-relaxed text-ink-2">{lede}</p> : null}47      {children}48    </header>49  );50}5152/** Definition list in two columns, dense. */53export function KV({ items, className = '' }: { items: Array<{ k: ReactNode; v: ReactNode }>; className?: string }) {54  const rows = items.filter((i) => i.v !== null && i.v !== undefined && i.v !== '');55  if (rows.length === 0) return null;56  return (57    <dl className={`grid grid-cols-[minmax(110px,auto)_1fr] gap-x-4 gap-y-1.5 text-[13.5px] ${className}`}>58      {rows.map((r, i) => (59        <div key={i} className="contents">60          <dt className="text-ink-3">{r.k}</dt>61          <dd className="min-w-0 break-words text-ink">{r.v}</dd>62        </div>63      ))}64    </dl>65  );66}6768export function Note({ children, tone = 'neutral' }: { children: ReactNode; tone?: 'neutral' | 'warn' }) {69  return <p className={`border-l-2 pl-3 text-[12.5px] leading-relaxed ${tone === 'warn' ? 'border-warn text-warn' : 'border-rule-strong text-ink-3'}`}>{children}</p>;70}71