SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
2.5 KB · 64 lines tsx
Raw Blame History
1import type { ReactNode } from 'react';2import { t } from '@/i18n';3import { cn } from '@/lib/cn';45export interface DataTableColumn<T> {6  key: string;7  header: ReactNode;8  cell: (row: T) => ReactNode;9  numeric?: boolean;10  /** Hide on the definition-list (mobile) layout. */11  hideOnMobile?: boolean;12  className?: string;13}1415/**16 * Data table that becomes a definition list on < sm (no horizontal overflow). Numeric columns are17 * right-aligned and tabular. Pass `rowKey` for stable keys and `caption` for AT.18 */19export function DataTable<T>({ rows, columns, rowKey, caption, className, dense }: { rows: T[]; columns: DataTableColumn<T>[]; rowKey: (row: T) => string; caption?: string; className?: string; dense?: boolean }) {20  return (21    <div className={cn('min-w-0', className)}>22      <table className="hidden w-full border-collapse text-sm sm:table">23        {caption ? <caption className="sr-only">{caption}</caption> : null}24        <thead>25          <tr className="border-b border-rule text-left text-xs text-ink-3">26            {columns.map((c) => (27              <th key={c.key} scope="col" className={cn('py-2 pr-3 font-medium', c.numeric && 'text-right', c.className)}>28                {c.header}29              </th>30            ))}31          </tr>32        </thead>33        <tbody className="divide-y divide-rule">34          {rows.map((r) => (35            <tr key={rowKey(r)} className="hover:bg-surface-2/60">36              {columns.map((c) => (37                <td key={c.key} className={cn(dense ? 'py-1.5' : 'py-2.5', 'pr-3 align-top', c.numeric && 'tnum text-right', c.className)}>38                  {c.cell(r)}39                </td>40              ))}41            </tr>42          ))}43        </tbody>44      </table>45      <ul className="divide-y divide-rule sm:hidden" aria-label={caption ?? t('table.definitionList')}>46        {rows.map((r) => (47          <li key={rowKey(r)} className="py-3">48            <dl className="grid grid-cols-[minmax(0,1fr)_auto] gap-x-4 gap-y-1">49              {columns50                .filter((c) => !c.hideOnMobile)51                .map((c, i) => (52                  <div key={c.key} className={cn('contents', i === 0 && 'font-medium')}>53                    <dt className={cn('text-xs text-ink-3', i === 0 && 'sr-only')}>{c.header}</dt>54                    <dd className={cn('min-w-0 text-sm', c.numeric && 'tnum text-right', i === 0 && 'col-span-2 text-base')}>{c.cell(r)}</dd>55                  </div>56                ))}57            </dl>58          </li>59        ))}60      </ul>61    </div>62  );63}64