import type { ComponentType } from 'react'; /** * Supported widget types for the dashboard. * clock/search/calendar/note: implemented in previous plans. * calculator/favorites/link/stopwatch: Phase 8 additions. */ export type WidgetType = | 'clock' | 'search' | 'calendar' | 'note' | 'calculator' | 'favorites' | 'link' | 'stopwatch'; /** * Props contract that every widget component must accept. */ export interface WidgetProps { instanceId: string; config: Record; isEditMode: boolean; } /** * Per-type size constraints for the react-grid-layout grid. * Values from UI-SPEC size table (D-06) and Phase-8 Grid-Constraints (D-01). */ export const WIDGET_CONSTRAINTS: Record< WidgetType, { minW: number; minH: number; defaultW: number; defaultH: number } > = { // quick-260916-bwo: Raster verdoppelt (24 Spalten / 20 px) — jeder Wert ist das Doppelte des alten 12-Spalten-Werts, Widgets bleiben optisch gleich gross. // Existing widgets clock: { minW: 4, minH: 4, defaultW: 4, defaultH: 4 }, search: { minW: 6, minH: 4, defaultW: 12, defaultH: 4 }, calendar: { minW: 6, minH: 6, defaultW: 8, defaultH: 12 }, note: { minW: 4, minH: 6, defaultW: 6, defaultH: 8 }, // Phase 8 new widgets (D-01) calculator: { minW: 4, minH: 8, defaultW: 6, defaultH: 10 }, favorites: { minW: 4, minH: 6, defaultW: 6, defaultH: 10 }, link: { minW: 4, minH: 4, defaultW: 4, defaultH: 4 }, stopwatch: { minW: 4, minH: 4, defaultW: 6, defaultH: 6 }, }; /** * Definition for a widget type in the catalog. */ export interface WidgetDefinition { type: WidgetType; nameKey: string; descriptionKey: string; /** Inline SVG icon as React component */ icon: ComponentType<{ className?: string }>; minW: number; minH: number; defaultW: number; defaultH: number; /** Lazy-loaded widget component */ component: ComponentType; } /** * Placeholder widget for types not yet implemented. * Renders the widget type name as a centered label. */ function PlaceholderWidget({ config }: WidgetProps) { const label = (config as Record).__widgetType ?? 'Widget'; return (
{label}
); } // --- Icon components (inline SVG, established project pattern) --- function ClockIcon({ className }: { className?: string }) { return ( ); } function SearchIcon({ className }: { className?: string }) { return ( ); } function CalendarIcon({ className }: { className?: string }) { return ( ); } function NoteIcon({ className }: { className?: string }) { return ( ); } function CalculatorIcon({ className }: { className?: string }) { return ( ); } function FavoritesIcon({ className }: { className?: string }) { return ( ); } function LinkIcon({ className }: { className?: string }) { return ( ); } function StopwatchIcon({ className }: { className?: string }) { return ( ); } /** * Registry of all widget types. Used by the widget catalog modal * and the grid renderer to look up components and constraints. */ export const WIDGET_REGISTRY: Record = { clock: { type: 'clock', nameKey: 'clock.name', descriptionKey: 'clock.description', icon: ClockIcon, ...WIDGET_CONSTRAINTS.clock, component: PlaceholderWidget, // Replaced via wireClockWidget() }, search: { type: 'search', nameKey: 'search.name', descriptionKey: 'search.description', icon: SearchIcon, ...WIDGET_CONSTRAINTS.search, component: PlaceholderWidget, // Replaced via wireSearchWidget() }, calendar: { type: 'calendar', nameKey: 'calendar.name', descriptionKey: 'calendar.description', icon: CalendarIcon, ...WIDGET_CONSTRAINTS.calendar, component: PlaceholderWidget, // Replaced via wireCalendarWidget() }, note: { type: 'note', nameKey: 'note.name', descriptionKey: 'note.description', icon: NoteIcon, ...WIDGET_CONSTRAINTS.note, component: PlaceholderWidget, // Replaced via wireNoteWidget() }, calculator: { type: 'calculator', nameKey: 'calculator.name', descriptionKey: 'calculator.description', icon: CalculatorIcon, ...WIDGET_CONSTRAINTS.calculator, component: PlaceholderWidget, // Replaced via wireCalculatorWidget() }, favorites: { type: 'favorites', nameKey: 'favorites.name', descriptionKey: 'favorites.description', icon: FavoritesIcon, ...WIDGET_CONSTRAINTS.favorites, component: PlaceholderWidget, // Replaced via wireFavoritesWidget() }, link: { type: 'link', nameKey: 'link.name', descriptionKey: 'link.description', icon: LinkIcon, ...WIDGET_CONSTRAINTS.link, component: PlaceholderWidget, // Replaced via wireLinkWidget() }, stopwatch: { type: 'stopwatch', nameKey: 'stopwatch.name', descriptionKey: 'stopwatch.description', icon: StopwatchIcon, ...WIDGET_CONSTRAINTS.stopwatch, component: PlaceholderWidget, // Replaced via wireStopwatchWidget() }, }; // Wire actual widget components lazily to avoid circular deps // (imports are deferred so widget-registry can be imported by tests without // pulling in the entire React tree) let clockWired = false; export function wireClockWidget(component: ComponentType) { if (!clockWired) { WIDGET_REGISTRY.clock.component = component; clockWired = true; } } let searchWired = false; export function wireSearchWidget(component: ComponentType) { if (!searchWired) { WIDGET_REGISTRY.search.component = component; searchWired = true; } } let calendarWired = false; export function wireCalendarWidget(component: ComponentType) { if (!calendarWired) { WIDGET_REGISTRY.calendar.component = component; calendarWired = true; } } let noteWired = false; export function wireNoteWidget(component: ComponentType) { if (!noteWired) { WIDGET_REGISTRY.note.component = component; noteWired = true; } } let calculatorWired = false; export function wireCalculatorWidget(component: ComponentType) { if (!calculatorWired) { WIDGET_REGISTRY.calculator.component = component; calculatorWired = true; } } let favoritesWired = false; export function wireFavoritesWidget(component: ComponentType) { if (!favoritesWired) { WIDGET_REGISTRY.favorites.component = component; favoritesWired = true; } } let linkWired = false; export function wireLinkWidget(component: ComponentType) { if (!linkWired) { WIDGET_REGISTRY.link.component = component; linkWired = true; } } let stopwatchWired = false; export function wireStopwatchWidget(component: ComponentType) { if (!stopwatchWired) { WIDGET_REGISTRY.stopwatch.component = component; stopwatchWired = true; } }