Greet the user by name, drop the development-phase strip, move three metric cards (material in the Brain, content made, uploaded to the LMS) below the knowledge graph, and add a content activity section with the latest and in-progress results as Results cards that open their preview in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { Icon, type IconName } from './Icon'
|
|
|
|
export function PageHead({ title, description, actions, eyebrow }: { title: ReactNode; description: string; actions?: ReactNode; eyebrow?: string }) {
|
|
return (
|
|
<div className="page-head">
|
|
<div>{eyebrow && <small className="page-eyebrow">{eyebrow}</small>}<h1>{title}</h1><p>{description}</p></div>
|
|
{actions && <div className="head-actions">{actions}</div>}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Shimmer placeholders shaped like the content they stand in for, so a page keeps its layout while loading.
|
|
export function Skeleton({ w, h, className }: { w?: number | string; h?: number | string; className?: string }) {
|
|
return <span className={className ? `skel ${className}` : 'skel'} style={{ width: w, height: h }} aria-hidden="true" />
|
|
}
|
|
|
|
const LINE_WIDTHS = ['62%', '48%', '70%', '55%']
|
|
|
|
export function SkeletonRows({ count = 3, label }: { count?: number; label: string }) {
|
|
return (
|
|
<div className="result-list" role="status" aria-label={label}>
|
|
{Array.from({ length: count }, (_, i) => (
|
|
<div key={i} className="result-row skel-row">
|
|
<Skeleton className="skel-cover" />
|
|
<div className="result-main">
|
|
<Skeleton w={LINE_WIDTHS[i % LINE_WIDTHS.length]} h={14} />
|
|
<Skeleton w="34%" h={11} />
|
|
</div>
|
|
<Skeleton className="skel-pill" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function SkeletonCards({ count = 4, label }: { count?: number; label: string }) {
|
|
return (
|
|
<div className="content-grid" role="status" aria-label={label}>
|
|
{Array.from({ length: count }, (_, i) => (
|
|
<div key={i} className="content-card">
|
|
<Skeleton className="skel-block" />
|
|
<div className="content-card-body skel-stack">
|
|
<Skeleton w="40%" h={20} className="skel-pill" />
|
|
<Skeleton w="90%" h={14} />
|
|
<Skeleton w={LINE_WIDTHS[i % LINE_WIDTHS.length]} h={14} />
|
|
<Skeleton w="50%" h={11} />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function SkeletonLines({ count = 2, label }: { count?: number; label: string }) {
|
|
return (
|
|
<div className="skel-stack" role="status" aria-label={label}>
|
|
{Array.from({ length: count }, (_, i) => <Skeleton key={i} w={LINE_WIDTHS[i % LINE_WIDTHS.length]} h={12} />)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function Empty({ icon = 'book', title, children }: { icon?: IconName; title: string; children?: ReactNode }) {
|
|
return (
|
|
<div className="empty">
|
|
<span className="empty-icon"><Icon name={icon} /></span>
|
|
<h3>{title}</h3>
|
|
{children && <p>{children}</p>}
|
|
</div>
|
|
)
|
|
}
|