import type { ComponentType } from 'react'; /** * Supported widget types for the dashboard. * clock/search/calendar/note: implemented in previous plans. * calculator/favorites/stopwatch: Phase 8 additions (der fruehere * Einzel-Schnellzugriffs-Typ wurde in quick-260916-iex entfernt — Favoriten * decken den Fall ab). */ export type WidgetType = | 'clock' | 'search' | 'calendar' | 'note' | 'calculator' | 'favorites' | '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-dyv: minW/minH = kleinste noch bedienbare Kachel je Typ im // 24-Spalten/20-px-Raster, aus dem Innenaufbau gerechnet (Suche: Auswahl 120 + // Eingabe + Knopf; Rechner: Anzeige 40 + Speicherzeile 28 + 5 Tastenreihen 28 = // 268 px -> 10 Zeilen; Stoppuhr: kompakte Bedienleiste). defaultW/defaultH = // altes 12-Spalten-Mass x2, unveraendert. Gespeicherte minW/minH werden in // dashboard-grid.tsx aus dieser Tabelle ueberschrieben. // Existing widgets clock: { minW: 2, minH: 2, defaultW: 4, defaultH: 4 }, search: { minW: 6, minH: 2, defaultW: 12, defaultH: 4 }, // quick-260916-htc: Monatsraster braucht Breite fuer 7 Spalten und Hoehe // fuer Nav-Zeile + Wochentagskopf + 6 Rasterzeilen + Naechste-Termine-Block. calendar: { minW: 6, minH: 8, defaultW: 8, defaultH: 12 }, note: { minW: 4, minH: 4, defaultW: 6, defaultH: 8 }, // Phase 8 new widgets (D-01) // minH 10, nicht 9 (260916-dyv, Browser-Messung des Orchestrators): der Rechner hat // SECHS Tastenreihen (%/CE/C/Loeschen, 1/x/x2/Wurzel/Division, 7-9, 4-6, 1-3, 0/Komma/=), // Inhalt 268 px; bei 9 Zeilen (244 px) fehlte die unterste Reihe um 25 px. calculator: { minW: 3, minH: 10, defaultW: 6, defaultH: 10 }, favorites: { minW: 3, minH: 3, defaultW: 6, defaultH: 10 }, stopwatch: { minW: 4, minH: 3, 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 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() }, 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 stopwatchWired = false; export function wireStopwatchWidget(component: ComponentType) { if (!stopwatchWired) { WIDGET_REGISTRY.stopwatch.component = component; stopwatchWired = true; } }