"use client";
import * as React from "react";
import { cn } from "../lib/utils";
import { Button } from "../components/button";
export function AppPage({
title,
description,
eyebrow,
actions,
children,
width = "wide",
className,
}: {
title: string;
description?: React.ReactNode;
eyebrow?: React.ReactNode;
actions?: React.ReactNode;
children: React.ReactNode;
width?: "wide" | "reading";
className?: string;
}) {
return (
{eyebrow &&
{eyebrow}
}
{title}
{description &&
{description}
}
{actions && {actions}
}
{children}
);
}
export function PageToolbar({
children,
label = "View controls",
className,
}: {
children: React.ReactNode;
label?: string;
className?: string;
}) {
return (
{children}
);
}
export function EmptyState({
title,
description,
action,
icon,
}: {
title: string;
description?: React.ReactNode;
action?: React.ReactNode;
icon?: React.ReactNode;
}) {
return (
{icon && (
{icon}
)}
{title}
{description && {description}
}
{action && {action}
}
);
}
export function AppErrorState({ message, onRetry }: { message: string; onRetry?: () => void }) {
return (
Something went wrong
{message}
{onRetry && (
)}
);
}
export function AppLoadingState({
label = "Loading",
rows = 5,
}: {
label?: string;
rows?: number;
}) {
return (
{Array.from({ length: Math.max(1, Math.min(rows, 12)) }, (_, i) => (
))}
{label}
);
}
export function StatusBadge({
children,
tone = "neutral",
}: {
children: React.ReactNode;
tone?: "neutral" | "info" | "success" | "warning" | "danger";
}) {
return (
{children}
);
}
export function SplitView({
list,
detail,
label = "Details",
}: {
list: React.ReactNode;
detail?: React.ReactNode;
label?: string;
}) {
return (
);
}
export function DetailPanel({
title,
subtitle,
onClose,
children,
actions,
}: {
title: string;
subtitle?: React.ReactNode;
onClose?: () => void;
children: React.ReactNode;
actions?: React.ReactNode;
}) {
return (
{subtitle &&
{subtitle}
}
{title}
{onClose && (
)}
{children}
{actions && }
);
}
export interface RecordColumn {
key: string;
label: string;
render: (record: T) => React.ReactNode;
secondary?: boolean;
align?: "start" | "end";
}
export function RecordTable({
records,
columns,
rowKey,
onOpen,
label = "Records",
empty,
}: {
records: readonly T[];
columns: readonly RecordColumn[];
rowKey: (record: T) => string;
onOpen?: (record: T) => void;
label?: string;
empty?: React.ReactNode;
}) {
if (!records.length)
return (
<>{empty ?? }>
);
return (
{columns.map((column) => (
|
{column.label}
|
))}
{records.map((record) => (
{columns.map((column, i) => (
|
{i === 0 && onOpen ? (
) : (
column.render(record)
)}
|
))}
))}
);
}
export function Board({
children,
label = "Board",
}: {
children: React.ReactNode;
label?: string;
}) {
return (
{children}
);
}
export function BoardColumn({
title,
count,
children,
action,
}: {
title: string;
count?: number;
children: React.ReactNode;
action?: React.ReactNode;
}) {
return (
{title}
{count !== undefined && {count}}
{action}
{children}
);
}
export function BoardCard({
title,
children,
footer,
onOpen,
}: {
title: string;
children?: React.ReactNode;
footer?: React.ReactNode;
onOpen?: () => void;
}) {
return (
{onOpen ? (
) : (
title
)}
{children && {children}
}
{footer && }
);
}
export function SettingsSection({
title,
description,
children,
}: {
title: string;
description?: string;
children: React.ReactNode;
}) {
const id = React.useId();
return (
{title}
{description &&
{description}
}
{children}
);
}