"use client"; import * as React from "react"; import { AnimatePresence, motion, useReducedMotion } from "framer-motion"; import { IconAlertTriangle, IconX } from "@tabler/icons-react"; import { cn } from "../lib/utils"; import { staggerDelay, transitions } from "../lib/motion"; import { Button } from "../components/button"; /** * Page-level patterns for workspace apps: the frame, toolbars, the * loading/empty/error trio, list + detail, boards, and settings. They * carry Coline's spacing and type so apps look native without styling. */ 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; }) { const reduceMotion = useReducedMotion(); return (
{eyebrow ?
{eyebrow}
: null}

{title}

{description ?

{description}

: null}
{actions ?
{actions}
: null}
{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, className, }: { title: string; description?: React.ReactNode; action?: React.ReactNode; icon?: React.ReactNode; className?: string; }) { const reduceMotion = useReducedMotion(); return ( {icon ? ( ) : null}

{title}

{description ?

{description}

: null} {action ?
{action}
: null}
); } export function AppErrorState({ message, onRetry }: { message: string; onRetry?: () => void }) { return (

Something went wrong

{message}

{onRetry ? ( ) : null}
); } export function AppLoadingState({ label = "Loading", rows = 5 }: { label?: string; rows?: number }) { return (
{Array.from({ length: Math.max(1, Math.min(rows, 12)) }, (_, index) => (
))} {label}
); } const STATUS_TONE = { neutral: "bg-muted text-muted-foreground", info: "bg-action/10 text-action", success: "bg-positive/10 text-positive-fg", warning: "bg-attention/12 text-attention-fg", danger: "bg-risk/10 text-risk-fg", } as const; export function StatusBadge({ children, tone = "neutral", }: { children: React.ReactNode; tone?: keyof typeof STATUS_TONE; }) { return ( ); } export function SplitView({ list, detail, label = "Details", }: { list: React.ReactNode; detail?: React.ReactNode; label?: string; }) { const reduceMotion = useReducedMotion(); return (
{list}
{detail ? ( {detail} ) : null}
); } export function DetailPanel({ title, subtitle, onClose, children, actions, }: { title: string; subtitle?: React.ReactNode; onClose?: () => void; children: React.ReactNode; actions?: React.ReactNode; }) { return (
{subtitle ?
{subtitle}
: null}

{title}

{onClose ? ( ) : null}
{children}
{actions ?
{actions}
: null}
); } export interface RecordColumn { key: string; label: string; render: (record: T) => React.ReactNode; secondary?: boolean; align?: "start" | "end"; } /** * A light list-style table. For sorting, selection, keyboard navigation, * and large data, use `DataGrid`. */ 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; }) { const reduceMotion = useReducedMotion(); if (!records.length) return <>{empty ?? }; return (
{columns.map((column) => ( ))} {records.map((record, index) => ( onOpen(record) : undefined} > {columns.map((column, columnIndex) => ( ))} ))}
{column.label}
{columnIndex === 0 && onOpen ? ( ) : ( column.render(record) )}
); } /** * Static board layout. For drag and drop between columns, use * `KanbanBoard`. */ 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} ) : null} {action ?
{action}
: null}
{children}
); } export function BoardCard({ title, children, footer, onOpen, }: { title: string; children?: React.ReactNode; footer?: React.ReactNode; onOpen?: () => void; }) { return (

{onOpen ? ( ) : ( title )}

{children ?
{children}
: null} {footer ? (
{footer}
) : null}
); } export function SettingsSection({ title, description, children, }: { title: string; description?: string; children: React.ReactNode; }) { const id = React.useId(); return (

{title}

{description ?

{description}

: null}
{children}
); }