You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/packages/ui/src/components.tsx

116 lines
3.4 KiB
TypeScript

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 }) {
return (
<>
<div className="wf-page-head">
<h1>{props.title}</h1>
{props.badge !== undefined && <Badge tone="accent">{props.badge}</Badge>}
<div style={{ flex: 1 }} />
{props.actions}
</div>
{props.desc !== undefined && <p className="wf-page-desc">{props.desc}</p>}
</>
)
}
export function Toolbar(props: { children: ReactNode }) {
return <div className="wf-toolbar">{props.children}</div>
}
export function Button(props: {
children: ReactNode
onClick?: () => void
tone?: 'default' | 'primary' | 'danger'
hotkey?: string
}) {
const cls = `wf${props.tone === 'primary' ? ' primary' : props.tone === 'danger' ? ' danger' : ''}`
return (
<button type="button" className={cls} onClick={props.onClick}>
{props.children}
{props.hotkey !== undefined && <kbd>{props.hotkey}</kbd>}
</button>
)
}
export function Badge(props: { children: ReactNode; tone?: 'ok' | 'warn' | 'err' | 'accent' }) {
return <span className={`wf-badge${props.tone !== undefined ? ` ${props.tone}` : ''}`}>{props.children}</span>
}
export interface Column {
key: string
label: string
numeric?: boolean
}
export function DataTable(props: {
columns: Column[]
rows: Record<string, ReactNode>[]
onRowClick?: (row: Record<string, ReactNode>, index: number) => void
}) {
return (
<table className="wf">
<thead>
<tr>
{props.columns.map((c) => (
<th key={c.key} className={c.numeric === true ? 'num' : undefined}>{c.label}</th>
))}
</tr>
</thead>
<tbody>
{props.rows.map((row, i) => (
<tr key={i} onClick={() => props.onRowClick?.(row, i)} style={props.onRowClick !== undefined ? { cursor: 'pointer' } : undefined}>
{props.columns.map((c) => (
<td key={c.key} className={c.numeric === true ? 'num' : undefined}>{row[c.key]}</td>
))}
</tr>
))}
</tbody>
</table>
)
}
export function StatCard(props: { label: string; value: ReactNode; hint?: string }) {
return (
<div className="wf-stat">
<div className="label">{props.label}</div>
<div className="value num">{props.value}</div>
{props.hint !== undefined && <div className="hint">{props.hint}</div>}
</div>
)
}
export function Stats(props: { children: ReactNode }) {
return <div className="wf-stats">{props.children}</div>
}
export function EmptyState(props: { children: ReactNode }) {
return <div className="wf-empty">{props.children}</div>
}
export function Field(props: { label: string; children: ReactNode }) {
return (
<div className="wf-field">
<label>{props.label}</label>
{props.children}
</div>
)
}
export function Modal(props: { title: string; onClose: () => void; children: ReactNode }) {
return (
<div className="wf-modal-back" onClick={props.onClose}>
<div className="wf-modal" onClick={(e) => e.stopPropagation()}>
<h2>{props.title}</h2>
{props.children}
</div>
</div>
)
}
export function Notice(props: { tone?: 'ok' | 'warn' | 'err'; children: ReactNode }) {
return <div className={`wf-notice${props.tone !== undefined ? ` ${props.tone}` : ''}`}>{props.children}</div>
}