diff --git a/json-components-registry.json b/json-components-registry.json index 78ef6c5..6794fef 100644 --- a/json-components-registry.json +++ b/json-components-registry.json @@ -1236,7 +1236,7 @@ "name": "LazyBarChart", "category": "data", "canHaveChildren": true, - "description": "LazyBarChart component", + "description": "JSON wrapper for a props-driven bar chart (no lazy hooks)", "status": "json-compatible", "source": "molecules", "jsonCompatible": true @@ -1246,7 +1246,7 @@ "name": "LazyD3BarChart", "category": "data", "canHaveChildren": true, - "description": "LazyD3BarChart component", + "description": "JSON wrapper for a simple SVG bar chart (no D3 hooks)", "status": "json-compatible", "source": "molecules", "jsonCompatible": true @@ -1256,7 +1256,7 @@ "name": "LazyLineChart", "category": "data", "canHaveChildren": true, - "description": "LazyLineChart component", + "description": "JSON wrapper for a props-driven line chart (no lazy hooks)", "status": "json-compatible", "source": "molecules", "jsonCompatible": true @@ -1302,7 +1302,7 @@ "name": "SeedDataManager", "category": "data", "canHaveChildren": true, - "description": "SeedDataManager component", + "description": "JSON wrapper for seed data management with props-driven state", "status": "json-compatible", "source": "molecules", "jsonCompatible": true @@ -1809,7 +1809,7 @@ "name": "SaveIndicator", "category": "custom", "canHaveChildren": true, - "description": "SaveIndicator component", + "description": "JSON wrapper for save status indicator with pure-props API", "status": "json-compatible", "source": "molecules", "jsonCompatible": true @@ -1979,7 +1979,7 @@ "name": "StorageSettings", "category": "custom", "canHaveChildren": true, - "description": "StorageSettings component", + "description": "JSON wrapper for storage settings controls with props-driven state", "status": "json-compatible", "source": "molecules", "jsonCompatible": true diff --git a/src/lib/component-definitions.json b/src/lib/component-definitions.json index 41239ec..1f1d22b 100644 --- a/src/lib/component-definitions.json +++ b/src/lib/component-definitions.json @@ -375,6 +375,13 @@ } ] }, + { + "type": "SaveIndicator", + "label": "Save Indicator", + "category": "feedback", + "icon": "FloppyDisk", + "defaultProps": { "status": "saved", "label": "Saved", "showLabel": true, "animate": true } + }, { "type": "List", "label": "List", @@ -472,6 +479,66 @@ ] } }, + { + "type": "LazyBarChart", + "label": "Bar Chart", + "category": "data", + "icon": "ChartBar", + "defaultProps": { + "data": [ + { "name": "Q1", "value": 400 }, + { "name": "Q2", "value": 300 }, + { "name": "Q3", "value": 500 }, + { "name": "Q4", "value": 280 } + ], + "xKey": "name", + "yKey": "value", + "height": 300, + "color": "#8884d8" + } + }, + { + "type": "LazyLineChart", + "label": "Line Chart", + "category": "data", + "icon": "ChartLine", + "defaultProps": { + "data": [ + { "name": "Jan", "value": 120 }, + { "name": "Feb", "value": 260 }, + { "name": "Mar", "value": 180 }, + { "name": "Apr", "value": 320 } + ], + "xKey": "name", + "yKey": "value", + "height": 300, + "color": "#38bdf8" + } + }, + { + "type": "LazyD3BarChart", + "label": "Simple Bar Chart", + "category": "data", + "icon": "ChartBar", + "defaultProps": { + "data": [ + { "label": "Mon", "value": 12 }, + { "label": "Tue", "value": 18 }, + { "label": "Wed", "value": 9 }, + { "label": "Thu", "value": 22 } + ], + "width": 600, + "height": 300, + "color": "#22c55e" + } + }, + { + "type": "SeedDataManager", + "label": "Seed Data Manager", + "category": "data", + "icon": "Database", + "defaultProps": { "isLoaded": false, "isLoading": false } + }, { "type": "DataCard", "label": "Data Card", @@ -493,5 +560,19 @@ "icon": "Toolbox", "canHaveChildren": true, "defaultProps": { "actions": [] } + }, + { + "type": "StorageSettings", + "label": "Storage Settings", + "category": "custom", + "icon": "Database", + "defaultProps": { + "backend": "indexeddb", + "flaskUrl": "http://localhost:5000", + "isLoading": false, + "isSwitching": false, + "isExporting": false, + "isImporting": false + } } ] diff --git a/src/lib/json-ui/component-registry.ts b/src/lib/json-ui/component-registry.ts index 6206321..2912d38 100644 --- a/src/lib/json-ui/component-registry.ts +++ b/src/lib/json-ui/component-registry.ts @@ -19,6 +19,15 @@ import { Progress } from '@/components/ui/progress' import { Avatar as ShadcnAvatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import * as AtomComponents from '@/components/atoms' import * as MoleculeComponents from '@/components/molecules' +import { + BreadcrumbWrapper, + LazyBarChartWrapper, + LazyD3BarChartWrapper, + LazyLineChartWrapper, + SaveIndicatorWrapper, + SeedDataManagerWrapper, + StorageSettingsWrapper, +} from '@/lib/json-ui/wrappers' import jsonComponentsRegistry from '../../../json-components-registry.json' import { ArrowLeft, ArrowRight, Check, X, Plus, Minus, MagnifyingGlass, @@ -157,6 +166,16 @@ export const moleculeComponents: UIComponentRegistry = buildRegistryFromNames( MoleculeComponents as Record> ) +export const jsonWrapperComponents: UIComponentRegistry = { + Breadcrumb: BreadcrumbWrapper, + SaveIndicator: SaveIndicatorWrapper, + LazyBarChart: LazyBarChartWrapper, + LazyLineChart: LazyLineChartWrapper, + LazyD3BarChart: LazyD3BarChartWrapper, + SeedDataManager: SeedDataManagerWrapper, + StorageSettings: StorageSettingsWrapper, +} + export const iconComponents: UIComponentRegistry = { ArrowLeft, ArrowRight, @@ -203,6 +222,7 @@ export const uiComponentRegistry: UIComponentRegistry = { ...shadcnComponents, ...atomComponents, ...moleculeComponents, + ...jsonWrapperComponents, ...iconComponents, } diff --git a/src/lib/json-ui/wrappers/BreadcrumbWrapper.tsx b/src/lib/json-ui/wrappers/BreadcrumbWrapper.tsx new file mode 100644 index 0000000..22d0a28 --- /dev/null +++ b/src/lib/json-ui/wrappers/BreadcrumbWrapper.tsx @@ -0,0 +1,11 @@ +import { BreadcrumbNav } from '@/components/atoms' +import { cn } from '@/lib/utils' +import type { BreadcrumbWrapperProps } from './interfaces' + +export function BreadcrumbWrapper({ items = [], className }: BreadcrumbWrapperProps) { + if (items.length === 0) { + return null + } + + return +} diff --git a/src/lib/json-ui/wrappers/LazyBarChartWrapper.tsx b/src/lib/json-ui/wrappers/LazyBarChartWrapper.tsx new file mode 100644 index 0000000..6016ded --- /dev/null +++ b/src/lib/json-ui/wrappers/LazyBarChartWrapper.tsx @@ -0,0 +1,31 @@ +import { Bar, BarChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts' +import { cn } from '@/lib/utils' +import type { LazyBarChartWrapperProps } from './interfaces' + +export function LazyBarChartWrapper({ + data, + xKey, + yKey, + width = '100%', + height = 300, + color = '#8884d8', + showGrid = true, + showTooltip = true, + showLegend = true, + className, +}: LazyBarChartWrapperProps) { + return ( +
+ + + {showGrid && } + + + {showTooltip && } + {showLegend && } + + + +
+ ) +} diff --git a/src/lib/json-ui/wrappers/LazyD3BarChartWrapper.tsx b/src/lib/json-ui/wrappers/LazyD3BarChartWrapper.tsx new file mode 100644 index 0000000..5dd016b --- /dev/null +++ b/src/lib/json-ui/wrappers/LazyD3BarChartWrapper.tsx @@ -0,0 +1,55 @@ +import { cn } from '@/lib/utils' +import type { LazyD3BarChartWrapperProps } from './interfaces' + +export function LazyD3BarChartWrapper({ + data, + width = 600, + height = 300, + color = '#8884d8', + className, +}: LazyD3BarChartWrapperProps) { + const margin = { top: 20, right: 20, bottom: 30, left: 40 } + const innerWidth = Math.max(width - margin.left - margin.right, 0) + const innerHeight = Math.max(height - margin.top - margin.bottom, 0) + const maxValue = Math.max(...data.map((item) => item.value), 0) + const barGap = 8 + const barCount = data.length + const totalGap = barCount > 1 ? barGap * (barCount - 1) : 0 + const barWidth = barCount > 0 ? Math.max((innerWidth - totalGap) / barCount, 0) : 0 + + return ( + + + {data.map((item, index) => { + const barHeight = maxValue ? (item.value / maxValue) * innerHeight : 0 + const x = index * (barWidth + barGap) + const y = innerHeight - barHeight + + return ( + + + + {item.label} + + + {item.value} + + + ) + })} + + + ) +} diff --git a/src/lib/json-ui/wrappers/LazyLineChartWrapper.tsx b/src/lib/json-ui/wrappers/LazyLineChartWrapper.tsx new file mode 100644 index 0000000..c2451ba --- /dev/null +++ b/src/lib/json-ui/wrappers/LazyLineChartWrapper.tsx @@ -0,0 +1,31 @@ +import { CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts' +import { cn } from '@/lib/utils' +import type { LazyLineChartWrapperProps } from './interfaces' + +export function LazyLineChartWrapper({ + data, + xKey, + yKey, + width = '100%', + height = 300, + color = '#8884d8', + showGrid = true, + showTooltip = true, + showLegend = true, + className, +}: LazyLineChartWrapperProps) { + return ( +
+ + + {showGrid && } + + + {showTooltip && } + {showLegend && } + + + +
+ ) +} diff --git a/src/lib/json-ui/wrappers/SaveIndicatorWrapper.tsx b/src/lib/json-ui/wrappers/SaveIndicatorWrapper.tsx new file mode 100644 index 0000000..e5b72ed --- /dev/null +++ b/src/lib/json-ui/wrappers/SaveIndicatorWrapper.tsx @@ -0,0 +1,21 @@ +import { StatusIcon } from '@/components/atoms' +import { cn } from '@/lib/utils' +import type { SaveIndicatorWrapperProps } from './interfaces' + +export function SaveIndicatorWrapper({ + status = 'saved', + label, + showLabel = true, + animate, + className, +}: SaveIndicatorWrapperProps) { + const resolvedLabel = label ?? (status === 'saved' ? 'Saved' : 'Synced') + const shouldAnimate = animate ?? status === 'saved' + + return ( +
+ + {showLabel && {resolvedLabel}} +
+ ) +} diff --git a/src/lib/json-ui/wrappers/SeedDataManagerWrapper.tsx b/src/lib/json-ui/wrappers/SeedDataManagerWrapper.tsx new file mode 100644 index 0000000..54e77f7 --- /dev/null +++ b/src/lib/json-ui/wrappers/SeedDataManagerWrapper.tsx @@ -0,0 +1,122 @@ +import { Alert, AlertDescription } from '@/components/ui/alert' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { CheckCircle, CircleNotch, Database, Trash, ArrowClockwise } from '@phosphor-icons/react' +import type { SeedDataManagerWrapperProps } from './interfaces' + +export function SeedDataManagerWrapper({ + isLoaded = false, + isLoading = false, + title = 'Seed Data Management', + description = 'Load, reset, or clear application seed data from the database', + loadLabel = 'Load Seed Data', + loadingLabel = 'Loading...', + resetLabel = 'Reset to Defaults', + resettingLabel = 'Resetting...', + clearLabel = 'Clear All Data', + clearingLabel = 'Clearing...', + onLoadSeedData, + onResetSeedData, + onClearAllData, + helperText = { + load: 'Populates database with initial data if not already loaded', + reset: 'Overwrites all data with fresh seed data', + clear: 'Removes all data from the database (destructive action)', + }, +}: SeedDataManagerWrapperProps) { + return ( + + + + + {title} + + {description} + + + {isLoaded && ( + + + Seed data is loaded and available + + )} + +
+
+ + + + + +
+ +
+ {helperText.load && ( +

+ Load Seed Data: {helperText.load} +

+ )} + {helperText.reset && ( +

+ Reset to Defaults: {helperText.reset} +

+ )} + {helperText.clear && ( +

+ Clear All Data: {helperText.clear} +

+ )} +
+
+
+
+ ) +} diff --git a/src/lib/json-ui/wrappers/StorageSettingsWrapper.tsx b/src/lib/json-ui/wrappers/StorageSettingsWrapper.tsx new file mode 100644 index 0000000..ae8b1cc --- /dev/null +++ b/src/lib/json-ui/wrappers/StorageSettingsWrapper.tsx @@ -0,0 +1,146 @@ +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Cloud, Cpu, Database, Download, HardDrive, Upload } from '@phosphor-icons/react' +import { + getBackendCopy, + storageSettingsCopy, +} from '@/components/storage/storageSettingsConfig' +import type { StorageBackendKey } from '@/components/storage/storageSettingsConfig' +import type { StorageSettingsWrapperProps } from './interfaces' + +const getBackendIcon = (backend: StorageBackendKey | null) => { + switch (backend) { + case 'flask': + return + case 'indexeddb': + return + case 'sqlite': + return + case 'sparkkv': + return + default: + return + } +} + +export function StorageSettingsWrapper({ + backend = null, + isLoading = false, + flaskUrl = storageSettingsCopy.molecule.flaskUrlPlaceholder, + isSwitching = false, + onFlaskUrlChange, + onSwitchToFlask, + onSwitchToIndexedDB, + onSwitchToSQLite, + isExporting = false, + isImporting = false, + onExport, + onImport, +}: StorageSettingsWrapperProps) { + const backendCopy = getBackendCopy(backend) + + return ( +
+ + + + {getBackendIcon(backend)} + {storageSettingsCopy.molecule.title} + + {storageSettingsCopy.molecule.description} + + +
+ + {storageSettingsCopy.molecule.currentBackendLabel} + + + {getBackendIcon(backend)} + {backendCopy.moleculeLabel} + +
+ +
+
+ +
+ onFlaskUrlChange?.(e.target.value)} + placeholder={storageSettingsCopy.molecule.flaskUrlPlaceholder} + disabled={isSwitching || isLoading} + /> + +
+

{storageSettingsCopy.molecule.flaskHelp}

+
+ +
+ + +
+ +
+

{storageSettingsCopy.molecule.backendDetails.indexeddb}

+

{storageSettingsCopy.molecule.backendDetails.sqlite}

+

{storageSettingsCopy.molecule.backendDetails.flask}

+
+
+
+
+ + + + {storageSettingsCopy.molecule.dataTitle} + {storageSettingsCopy.molecule.dataDescription} + + +
+ + +
+

{storageSettingsCopy.molecule.dataHelp}

+
+
+
+ ) +} diff --git a/src/lib/json-ui/wrappers/index.ts b/src/lib/json-ui/wrappers/index.ts new file mode 100644 index 0000000..3e06399 --- /dev/null +++ b/src/lib/json-ui/wrappers/index.ts @@ -0,0 +1,7 @@ +export { BreadcrumbWrapper } from './BreadcrumbWrapper' +export { SaveIndicatorWrapper } from './SaveIndicatorWrapper' +export { LazyBarChartWrapper } from './LazyBarChartWrapper' +export { LazyLineChartWrapper } from './LazyLineChartWrapper' +export { LazyD3BarChartWrapper } from './LazyD3BarChartWrapper' +export { SeedDataManagerWrapper } from './SeedDataManagerWrapper' +export { StorageSettingsWrapper } from './StorageSettingsWrapper' diff --git a/src/lib/json-ui/wrappers/interfaces.ts b/src/lib/json-ui/wrappers/interfaces.ts new file mode 100644 index 0000000..3096b76 --- /dev/null +++ b/src/lib/json-ui/wrappers/interfaces.ts @@ -0,0 +1,91 @@ +import type { StorageBackendKey } from '@/components/storage/storageSettingsConfig' + +export interface BreadcrumbItem { + label: string + href?: string +} + +export interface BreadcrumbWrapperProps { + items?: BreadcrumbItem[] + className?: string +} + +export type SaveIndicatorStatus = 'saved' | 'synced' + +export interface SaveIndicatorWrapperProps { + status?: SaveIndicatorStatus + label?: string + showLabel?: boolean + animate?: boolean + className?: string +} + +export interface LazyBarChartWrapperProps { + data: Array> + xKey: string + yKey: string + width?: number | string + height?: number + color?: string + showGrid?: boolean + showTooltip?: boolean + showLegend?: boolean + className?: string +} + +export interface LazyLineChartWrapperProps { + data: Array> + xKey: string + yKey: string + width?: number | string + height?: number + color?: string + showGrid?: boolean + showTooltip?: boolean + showLegend?: boolean + className?: string +} + +export interface LazyD3BarChartWrapperProps { + data: Array<{ label: string; value: number }> + width?: number + height?: number + color?: string + className?: string +} + +export interface SeedDataManagerWrapperProps { + isLoaded?: boolean + isLoading?: boolean + title?: string + description?: string + loadLabel?: string + loadingLabel?: string + resetLabel?: string + resettingLabel?: string + clearLabel?: string + clearingLabel?: string + onLoadSeedData?: () => void + onResetSeedData?: () => void + onClearAllData?: () => void + helperText?: { + load?: string + reset?: string + clear?: string + } +} + +export interface StorageSettingsWrapperProps { + backend?: StorageBackendKey | null + isLoading?: boolean + flaskUrl?: string + isSwitching?: boolean + onFlaskUrlChange?: (value: string) => void + onSwitchToFlask?: () => void + onSwitchToIndexedDB?: () => void + onSwitchToSQLite?: () => void + isExporting?: boolean + isImporting?: boolean + onExport?: () => void + onImport?: () => void +} diff --git a/src/types/json-ui.ts b/src/types/json-ui.ts index ef9e4da..a9aed51 100644 --- a/src/types/json-ui.ts +++ b/src/types/json-ui.ts @@ -11,6 +11,8 @@ export type ComponentType = | 'ErrorBadge' | 'Notification' | 'StatusIcon' | 'Table' | 'KeyValue' | 'StatCard' | 'DataCard' | 'SearchInput' | 'ActionBar' | 'DataList' | 'DataTable' | 'MetricCard' | 'Timeline' + | 'LazyBarChart' | 'LazyLineChart' | 'LazyD3BarChart' | 'SeedDataManager' + | 'SaveIndicator' | 'StorageSettings' | 'AppBranding' | 'LabelWithBadge' | 'EmptyEditorState' | 'LoadingFallback' | 'LoadingState' | 'NavigationGroupHeader' export type ActionType =