feat(ui): Skeleton, ErrorState, FilterChips, Tabs

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

@ -0,0 +1,27 @@
/** Saved-views-lite: a pill row that filters a list by one dimension. */
export interface ChipDef { key: string; label: string; count?: number }
export function FilterChips(props: {
chips: ChipDef[]
active: string
onChange: (key: string) => void
}) {
return (
<div className="wf-chips" role="tablist">
{props.chips.map((c) => (
<button
key={c.key}
type="button"
role="tab"
aria-selected={c.key === props.active}
className={`wf-chip${c.key === props.active ? ' active' : ''}`}
onClick={() => props.onChange(c.key)}
>
{c.label}
{c.count !== undefined && <span className="count">{c.count}</span>}
</button>
))}
</div>
)
}

@ -2,3 +2,6 @@ export * from './avatar'
export * from './components' export * from './components'
export * from './theme' export * from './theme'
export * from './ThemeSwitcher' export * from './ThemeSwitcher'
export * from './states'
export * from './chips'
export * from './tabs'

@ -0,0 +1,25 @@
/** Loading + error surfaces — every page swaps its "Loading…" text for these. */
const WIDTHS = ['92%', '70%', '84%', '55%', '78%']
export function Skeleton(props: { rows?: number }) {
const rows = props.rows ?? 3
return (
<div className="wf-skel" aria-busy="true" aria-label="Loading">
{Array.from({ length: rows }, (_x, i) => (
<div key={i} className="wf-skel-bar" style={{ width: WIDTHS[i % WIDTHS.length] }} />
))}
</div>
)
}
export function ErrorState(props: { message: string; onRetry?: () => void }) {
return (
<div className="wf-error" role="alert">
<span className="msg">{props.message}</span>
{props.onRetry !== undefined && (
<button type="button" className="wf" onClick={props.onRetry}>Try again</button>
)}
</div>
)
}

@ -0,0 +1,27 @@
/** Underline tabs with optional mono counts (Direction A v2 pattern). */
export interface TabDef { key: string; label: string; count?: number }
export function Tabs(props: {
tabs: TabDef[]
active: string
onChange: (key: string) => void
}) {
return (
<div className="wf-tabs" role="tablist">
{props.tabs.map((t) => (
<button
key={t.key}
type="button"
role="tab"
aria-selected={t.key === props.active}
className={`wf-tab${t.key === props.active ? ' active' : ''}`}
onClick={() => props.onChange(t.key)}
>
{t.label}
{t.count !== undefined && <span className="count">{t.count}</span>}
</button>
))}
</div>
)
}
Loading…
Cancel
Save