WIDGET Extensions
Overview
Section titled “Overview”WIDGET extensions add standalone components to dashboards and sidebars. Perfect for metrics, tools, and status displays.
Use Cases
Section titled “Use Cases”- Dashboard metrics and KPIs
- Sidebar quick-access tools
- Status indicators and notifications
- Global actions and controls
- Real-time data displays
Basic Example
Section titled “Basic Example”{ point: 'dashboard.widgets', plugin: 'my-plugin', priority: 50, extension: { type: ExtensionType.WIDGET, id: 'my-widget', component: MyDashboardWidget, title: 'My Widget', gridSize: { cols: 2, rows: 1 } }}WIDGET Extension Properties
Section titled “WIDGET Extension Properties”Required
Section titled “Required”- type:
ExtensionType.WIDGET - id: Unique identifier
- component: React component
Optional
Section titled “Optional”- title: Widget title
- gridSize:
{ cols: number, rows: number }- Dashboard grid size - isResizable: Allow user to resize
- minSize: Minimum grid size
- maxSize: Maximum grid size
Dashboard Widget Example
Section titled “Dashboard Widget Example”export function MetricsWidget({ context }: WidgetComponentProps) { const { data: metrics } = trpc.analytics.getMetrics.useQuery({ organizationId: context.organizationId, });
return ( <Card> <Title order={4}>Key Metrics</Title> <Grid> <Grid.Col span={6}> <Text c="dimmed">Active Projects</Text> <Text size="xl" fw={700}>{metrics?.activeProjects}</Text> </Grid.Col> <Grid.Col span={6}> <Text c="dimmed">Revenue</Text> <Text size="xl" fw={700}>${metrics?.revenue}</Text> </Grid.Col> </Grid> </Card> );}Sidebar Widget Example
Section titled “Sidebar Widget Example”{ point: 'sidebar.widgets', plugin: 'timer', priority: 90, extension: { type: ExtensionType.WIDGET, id: 'sidebar-timer', component: SidebarTimer }}
export function SidebarTimer() { const [activeTimer, setActiveTimer] = useState<TimerState | null>(null);
useEffect(() => { const updateTimer = () => { const timers = getTimersFromStorage(); const active = Object.values(timers).find(t => t.isRunning); setActiveTimer(active); };
window.addEventListener('storage', updateTimer); updateTimer();
return () => window.removeEventListener('storage', updateTimer); }, []);
if (!activeTimer) return null;
return ( <Card shadow="sm" padding="sm"> <Group justify="space-between"> <div> <Text size="sm" fw={600}>{activeTimer.workUnitName}</Text> <Text size="xs" c="dimmed"> <ElapsedTime startTime={activeTimer.startTime} /> </Text> </div> <ActionIcon onClick={() => stopTimer(activeTimer.id)}> <IconPlayerStop /> </ActionIcon> </Group> </Card> );}Grid Configuration
Section titled “Grid Configuration”Control widget size and layout:
{ type: ExtensionType.WIDGET, id: 'large-chart', component: ChartWidget, gridSize: { cols: 4, rows: 2 }, isResizable: true, minSize: { cols: 2, rows: 1 }, maxSize: { cols: 6, rows: 4 }}Best Practices
Section titled “Best Practices”- Keep widgets focused on single purpose
- Handle loading and error states
- Respect user’s grid layout preferences
- Clean up subscriptions and timers
- Use real-time updates sparingly