Dashboard Builder
Dynamic widget composition where each widget owns its own signal scope. Add, remove, reorder, and resize heterogeneous widgets; the layout grid reconfigures from a memo driven by widget count.
#Preview
Dashboard Builder
1"use client"23import { createSignal, createMemo } from '@barefootjs/client'45// Each widget type is its own client component with its own signal scope.6// Two StatWidget instances each maintain independent value/trend signals —7// incrementing one does not touch the other.89function StatWidget(props: { initialValue: number; step: number }) {10 const [value, setValue] = createSignal(props.initialValue)11 const delta = createMemo(() => value() - props.initialValue)12 return (13 <div>14 <div>{value()}</div>15 <button onClick={() => setValue(value() + props.step)}>+</button>16 </div>17 )18}1920function ProgressWidget(props: { initialProgress: number }) {21 const [progress, setProgress] = createSignal(props.initialProgress)22 // ...own bar + status memos23}2425function TodoWidget() { /* own todos signal */ }26function ChartWidget(props: { initialBars: ChartBar[] }) { /* own bars + selectedIndex */ }2728type WidgetConfig = { id: number; type: 'stat' | 'progress' | 'todo' | 'chart'; title: string; size: 'sm' | 'md' | 'lg' }2930function DashboardBuilder() {31 const [widgets, setWidgets] = createSignal<WidgetConfig[]>(initialWidgets)3233 // Layout memo: grid column class recomputed whenever widgets are added/removed34 const gridCols = createMemo(() => {35 const n = widgets().length36 if (n <= 1) return 'grid-cols-1'37 if (n === 2) return 'grid-cols-1 md:grid-cols-2'38 return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'39 })4041 return (42 <div className={`grid gap-3 ${gridCols()}`}>43 {widgets().map(w => (44 <div key={w.id} className="widget-cell">45 {/* Dynamic component switching inside the loop body */}46 {w.type === 'stat' ? <StatWidget initialValue={...} step={...} /> : null}47 {w.type === 'progress' ? <ProgressWidget initialProgress={35} /> : null}48 {w.type === 'todo' ? <TodoWidget /> : null}49 {w.type === 'chart' ? <ChartWidget initialBars={...} /> : null}50 </div>51 ))}52 </div>53 )54}#Features
Per-Widget Signal Isolation
Each widget type (Stat, Progress, Todo, Chart) is a separate client component with its own signal scope. Adding a second StatWidget creates an entirely new reactive instance — its value signal, trend memo, and DOM bindings are independent of every other StatWidget on the page. Incrementing one StatWidget does not invalidate memos in sibling widgets.
Dynamic Component Switching Inside .map()
The widget loop body renders a different child component based onw.type. A ternary chain returnsStatWidget, ProgressWidget,TodoWidget, or ChartWidget. Each branch produces a structurally different subtree — the compiler must hydrate the right component per item and wire up its independent signal tree.
Layout Memo Dependent on Widget Count
gridCols memo readswidgets().length and emits a responsive Tailwind grid class. Adding or removing widgets updates the memo, which updates the container's className via a reactive CSS class binding — without re-creating the widget instances.
Loop Rebuild With Fresh Child State
Removing a widget filters the config array; adding one appends. Each new child widget mounts with a clean signal scope, initialized from constructor props. This exercises the compiler's keyed-reconciliation path inside .map() bodies that emit distinct component types.
Derived Count Badges
Four independent memos (stat / progress / todo / chart count) each filter widgets() by type and drive a badge. All four recompute on any widget list change, testing fan-out from a single upstream signal.