feat(ui): RecordHeader; PageHeader breadcrumb; DataTable rowTone + mono columns

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 5 days ago
parent c299aea18c
commit 6dbddd9822

@ -2,9 +2,16 @@ import type { ReactNode } from 'react'
/** Wireframe kit: structure-first components shared by POS and back office. */ /** Wireframe kit: structure-first components shared by POS and back office. */
export function PageHeader(props: { title: string; desc?: string; badge?: string; actions?: ReactNode }) { export function PageHeader(props: {
title: string
desc?: string
badge?: string
breadcrumb?: ReactNode
actions?: ReactNode
}) {
return ( return (
<> <>
{props.breadcrumb !== undefined && <div className="wf-crumbs">{props.breadcrumb}</div>}
<div className="wf-page-head"> <div className="wf-page-head">
<h1>{props.title}</h1> <h1>{props.title}</h1>
{props.badge !== undefined && <Badge tone="accent">{props.badge}</Badge>} {props.badge !== undefined && <Badge tone="accent">{props.badge}</Badge>}
@ -43,13 +50,19 @@ export interface Column {
key: string key: string
label: string label: string
numeric?: boolean numeric?: boolean
mono?: boolean
} }
export function DataTable(props: { export function DataTable(props: {
columns: Column[] columns: Column[]
rows: Record<string, ReactNode>[] rows: Record<string, ReactNode>[]
onRowClick?: (row: Record<string, ReactNode>, index: number) => void onRowClick?: (row: Record<string, ReactNode>, index: number) => void
rowTone?: (index: number) => 'ok' | 'warn' | 'err' | undefined
}) { }) {
const cellClass = (c: Column): string | undefined => {
const cls = [c.numeric === true ? 'num' : '', c.mono === true ? 'mono' : ''].filter((x) => x !== '').join(' ')
return cls !== '' ? cls : undefined
}
return ( return (
<table className="wf"> <table className="wf">
<thead> <thead>
@ -60,13 +73,21 @@ export function DataTable(props: {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{props.rows.map((row, i) => ( {props.rows.map((row, i) => {
<tr key={i} onClick={() => props.onRowClick?.(row, i)} style={props.onRowClick !== undefined ? { cursor: 'pointer' } : undefined}> const tone = props.rowTone?.(i)
return (
<tr
key={i}
className={tone !== undefined ? `tone-${tone}` : undefined}
onClick={() => props.onRowClick?.(row, i)}
style={props.onRowClick !== undefined ? { cursor: 'pointer' } : undefined}
>
{props.columns.map((c) => ( {props.columns.map((c) => (
<td key={c.key} className={c.numeric === true ? 'num' : undefined}>{row[c.key]}</td> <td key={c.key} className={cellClass(c)}>{row[c.key]}</td>
))} ))}
</tr> </tr>
))} )
})}
</tbody> </tbody>
</table> </table>
) )

@ -5,3 +5,4 @@ export * from './ThemeSwitcher'
export * from './states' export * from './states'
export * from './chips' export * from './chips'
export * from './tabs' export * from './tabs'
export * from './record'

@ -0,0 +1,27 @@
import type { ReactNode } from 'react'
import { avatarColors, initials } from './avatar'
/** Entity header: avatar + name + status pill + one meta line + action cluster. */
export function RecordHeader(props: {
name: string
avatarSeed?: string
status?: ReactNode
meta?: ReactNode
actions?: ReactNode
}) {
const seed = props.avatarSeed ?? props.name
const { bg, fg } = avatarColors(seed)
return (
<div className="wf-rec">
<div className="wf-avatar" style={{ background: bg, color: fg }}>{initials(seed)}</div>
<div className="wf-rec-body">
<div className="wf-rec-title">
<h1>{props.name}</h1>
{props.status}
</div>
{props.meta !== undefined && <div className="wf-rec-meta">{props.meta}</div>}
</div>
{props.actions !== undefined && <div className="wf-rec-actions">{props.actions}</div>}
</div>
)
}
Loading…
Cancel
Save