// Shared "pro" card chrome for every dashboard widget. Subtle border, hover // ring, an accent icon chip, title + optional subtitle, and a subtle mount // motion done in pure CSS (runtime-react does not ship framer-motion). The // chrome is identical for declarative renderers AND `kind:"custom"` federated // widgets so they visually combine in the same grid. import * as React from 'react' import { Card, CardContent, CardHeader } from '@asteby/metacore-ui' import { cn } from '@asteby/metacore-ui/lib' import { DynamicIcon, isLucideIconName } from '../dynamic-icon' import { accentClasses } from './widget-format' import type { WidgetAccent } from '../dashboard-types' export interface WidgetCardProps { title: string subtitle?: string icon?: string accent?: WidgetAccent /** Right-aligned header slot (e.g. a delta chip). */ headerExtra?: React.ReactNode /** Body content. */ children?: React.ReactNode className?: string /** Forwarded for testing/automation. */ 'data-testid'?: string } /** * The card frame shared by all widgets. Keep the chrome here so a single style * change propagates to every kind (and to federated custom widgets). */ export function WidgetCard({ title, subtitle, icon, accent, headerExtra, children, className, ...rest }: WidgetCardProps) { const a = accentClasses(accent) const showIcon = icon && isLucideIconName(icon) return (
{showIcon && ( )}
{title}
{subtitle && (
{subtitle}
)}
{headerExtra &&
{headerExtra}
}
{children}
) } /** Delta chip: green up / red down, neutral on zero. `text` is preformatted. */ export function DeltaChip({ delta, text }: { delta: number; text: string }) { const up = delta > 0 const down = delta < 0 return ( {up && '▲'} {down && '▼'} {text} ) } /** Centered empty state inside a widget body (no data / missing). */ export function WidgetEmpty({ message }: { message: string }) { return (
{message}
) } /** Per-widget error state — isolated so a broken widget never tumbles the grid. */ export function WidgetError({ message }: { message: string }) { return (
{message}
) }