diff --git a/packages/ui/src/components.tsx b/packages/ui/src/components.tsx index 4d9f133..1e3b6fc 100644 --- a/packages/ui/src/components.tsx +++ b/packages/ui/src/components.tsx @@ -2,9 +2,16 @@ import type { ReactNode } from 'react' /** 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 ( <> + {props.breadcrumb !== undefined &&
{props.breadcrumb}
}

{props.title}

{props.badge !== undefined && {props.badge}} @@ -43,13 +50,19 @@ export interface Column { key: string label: string numeric?: boolean + mono?: boolean } export function DataTable(props: { columns: Column[] rows: Record[] onRowClick?: (row: Record, 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 ( @@ -60,13 +73,21 @@ export function DataTable(props: { - {props.rows.map((row, i) => ( - props.onRowClick?.(row, i)} style={props.onRowClick !== undefined ? { cursor: 'pointer' } : undefined}> - {props.columns.map((c) => ( - - ))} - - ))} + {props.rows.map((row, i) => { + const tone = props.rowTone?.(i) + return ( + props.onRowClick?.(row, i)} + style={props.onRowClick !== undefined ? { cursor: 'pointer' } : undefined} + > + {props.columns.map((c) => ( + + ))} + + ) + })}
{row[c.key]}
{row[c.key]}
) diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 1d8139e..2eaaf82 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -5,3 +5,4 @@ export * from './ThemeSwitcher' export * from './states' export * from './chips' export * from './tabs' +export * from './record' diff --git a/packages/ui/src/record.tsx b/packages/ui/src/record.tsx new file mode 100644 index 0000000..50f7033 --- /dev/null +++ b/packages/ui/src/record.tsx @@ -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 ( +
+
{initials(seed)}
+
+
+

{props.name}

+ {props.status} +
+ {props.meta !== undefined &&
{props.meta}
} +
+ {props.actions !== undefined &&
{props.actions}
} +
+ ) +}