Files
low-code-react-app-b/docs/ARCHITECTURE.md
2026-01-17 20:41:48 +00:00

465 lines
11 KiB
Markdown

# JSON-Driven UI & Atomic Components
## Overview
This project demonstrates a comprehensive JSON-driven UI architecture with atomic component design and custom React hooks. Build entire applications from declarative JSON schemas while maintaining clean, maintainable code through small, focused components.
## Key Features
### 🎯 JSON-Driven UI System
- Define complete page layouts using JSON schemas
- Automatic data source management (KV persistence, computed values, static data)
- Declarative action system (CRUD, navigation, toasts)
- Component bindings and event handlers
- Type-safe schema definitions
### 🧩 Atomic Component Library
- **Atoms** (< 50 LOC): Basic building blocks
- `Heading`, `Text`, `List`, `Grid`
- `StatusBadge`
- **Molecules** (50-100 LOC): Composed components
- `DataCard`, `SearchInput`, `ActionBar`
- **Organisms** (100-150 LOC): Complex compositions
- All components follow strict LOC limits for maintainability
### 🪝 Custom React Hooks
#### Data Management
- **`useCRUD<T>`** - Complete CRUD operations with KV persistence
- **`useSearch<T>`** - Multi-field search with filtering
- **`useFilter<T>`** - Advanced filtering with multiple operators
- **`useSort<T>`** - Multi-key sorting with direction toggle
- **`useJSONData`** - Flexible data management
- **`usePagination<T>`** - Pagination logic
- **`useLocalStorage<T>`** - Browser storage management
#### UI State
- **`useDialog`** - Dialog/modal state management
- **`useToggle`** - Boolean state with helpers
- **`useForm<T>`** - Complete form handling with validation
- **`useActionExecutor`** - Execute JSON-defined actions
## Quick Start
### Using JSON Schemas
```typescript
import { PageRenderer } from '@/lib/json-ui/page-renderer'
import { dashboardSchema } from '@/schemas/dashboard-schema'
export function DashboardPage() {
return <PageRenderer schema={dashboardSchema} />
}
```
### Using Atomic Components
```typescript
import { DataCard, SearchInput, ActionBar } from '@/components/molecules'
import { Grid, Heading } from '@/components/atoms'
import { useCRUD, useSearch } from '@/hooks/data'
export function MyPage() {
const { items, create, remove } = useCRUD({
key: 'my-items',
defaultValue: [],
persist: true
})
const { query, setQuery, filtered } = useSearch({
items,
searchFields: ['name', 'description']
})
return (
<div className="p-6 space-y-6">
<Heading level={1}>My Page</Heading>
<Grid cols={3} gap={4}>
<DataCard title="Total" value={items.length} />
</Grid>
<SearchInput
value={query}
onChange={setQuery}
placeholder="Search..."
/>
<ActionBar
title="Items"
actions={[
{ label: 'Add', onClick: () => create({...}) }
]}
/>
</div>
)
}
```
### Using Custom Hooks
```typescript
import { useForm } from '@/hooks/ui'
interface FormData {
name: string
email: string
}
export function MyForm() {
const form = useForm<FormData>({
initialValues: { name: '', email: '' },
validate: (values) => {
const errors: any = {}
if (!values.name) errors.name = 'Name is required'
if (!values.email) errors.email = 'Email is required'
return errors
},
onSubmit: async (values) => {
console.log('Form submitted:', values)
}
})
return (
<form onSubmit={form.handleSubmit}>
<input {...form.getFieldProps('name')} />
{form.errors.name && <span>{form.errors.name}</span>}
<button type="submit" disabled={!form.isValid}>
Submit
</button>
</form>
)
}
```
## JSON Schema Structure
```typescript
const mySchema: PageSchema = {
id: 'my-page',
name: 'My Page',
layout: { type: 'single' },
// Data sources
dataSources: [
{
id: 'items',
type: 'kv', // Persisted to KV store
key: 'my-items',
defaultValue: []
},
{
id: 'stats',
type: 'computed', // Computed from other data
compute: (data) => ({
total: data.items?.length || 0
}),
dependencies: ['items']
}
],
// UI components
components: [
{
id: 'root',
type: 'div',
props: { className: 'p-6' },
children: [
{
id: 'title',
type: 'Heading',
props: {
level: 1,
children: 'My Page'
}
},
{
id: 'stat-card',
type: 'DataCard',
props: { title: 'Total Items' },
bindings: {
value: { source: 'stats', path: 'total' }
}
},
{
id: 'add-button',
type: 'Button',
props: { children: 'Add Item' },
events: [
{
event: 'click',
actions: [
{
id: 'create-item',
type: 'create',
target: 'items',
compute: (data) => ({
id: Date.now(),
name: 'New Item'
})
},
{
id: 'show-toast',
type: 'show-toast',
message: 'Item added!',
variant: 'success'
}
]
}
]
}
]
}
]
}
```
## Available Components
### Atoms
- `Heading` - Semantic headings (h1-h6)
- `Text` - Text with variants (body, caption, muted, small)
- `List` - Render lists from arrays
- `Grid` - Responsive grid layouts
- `StatusBadge` - Colored status indicators
### Molecules
- `DataCard` - Stat cards with icon, trend, loading states
- `SearchInput` - Search with clear button
- `ActionBar` - Title with action buttons
### Shadcn UI Components
All shadcn components are available: `Button`, `Card`, `Input`, `Dialog`, `Tabs`, `Badge`, `Progress`, etc.
## Action Types
### CRUD Actions
- **create**: Add new items to data source
- **update**: Modify existing data
- **delete**: Remove items from data source
### UI Actions
- **show-toast**: Display notification (success, error, info, warning)
- **navigate**: Navigate to different route
- **open-dialog**: Open a dialog
- **close-dialog**: Close a dialog
### Value Actions
- **set-value**: Set data source value
- **toggle-value**: Toggle boolean value
- **increment**: Increment numeric value
- **decrement**: Decrement numeric value
## Data Source Types
### KV (Persistent)
Persists data between sessions using Spark's KV store.
```typescript
{
id: 'todos',
type: 'kv',
key: 'app-todos',
defaultValue: []
}
```
### Static (Session Only)
Data lives only in memory, reset on page reload.
```typescript
{
id: 'searchQuery',
type: 'static',
defaultValue: ''
}
```
### Computed (Derived)
Automatically recomputes when dependencies change.
```typescript
{
id: 'stats',
type: 'computed',
compute: (data) => ({
total: data.todos.length,
completed: data.todos.filter(t => t.completed).length
}),
dependencies: ['todos']
}
```
## Hook Examples
### useCRUD
```typescript
const { items, create, read, update, remove, clear } = useCRUD({
key: 'todos',
defaultValue: [],
persist: true
})
// ✅ CORRECT - Use functional updates
create({ id: Date.now(), text: 'New todo' })
update(todoId, { completed: true })
remove(todoId)
```
### useSearch
```typescript
const { query, setQuery, filtered, hasQuery, resultCount } = useSearch({
items: todos,
searchFields: ['text', 'tags'],
caseSensitive: false
})
```
### useFilter
```typescript
const { filtered, filters, addFilter, removeFilter, clearFilters } = useFilter({
items: todos,
initialFilters: [
{ field: 'status', operator: 'equals', value: 'active' }
]
})
addFilter({ field: 'priority', operator: 'in', value: ['high', 'medium'] })
```
### useForm
```typescript
const form = useForm({
initialValues: { name: '', email: '' },
validate: (values) => {
const errors: any = {}
if (!values.name) errors.name = 'Required'
return errors
},
onSubmit: async (values) => {
// Handle submission
}
})
// Use in JSX
<input {...form.getFieldProps('name')} />
```
## Demo Pages
1. **AtomicComponentDemo** - Shows all atomic components and hooks in action
2. **DashboardDemoPage** - Complete JSON-driven dashboard with projects
3. **JSONUIPage** - Original JSON UI examples
## Best Practices
### 1. Keep Components Small
- Atoms: < 50 LOC
- Molecules: 50-100 LOC
- Organisms: 100-150 LOC
### 2. Extract Logic to Hooks
❌ Bad: Logic in component
```typescript
const [items, setItems] = useState([])
const addItem = () => setItems([...items, newItem])
```
✅ Good: Logic in hook
```typescript
const { items, create } = useCRUD({ key: 'items', defaultValue: [] })
```
### 3. Use Computed Data Sources
❌ Bad: Computing in render
```typescript
const completed = todos.filter(t => t.completed).length
```
✅ Good: Computed data source
```typescript
{
id: 'stats',
type: 'computed',
compute: (data) => ({
completed: data.todos.filter(t => t.completed).length
}),
dependencies: ['todos']
}
```
### 4. Compose Atomic Components
Build complex UIs from simple atoms:
```typescript
<Card>
<CardHeader>
<CardTitle>Dashboard</CardTitle>
</CardHeader>
<CardContent>
<Grid cols={3} gap={4}>
<DataCard title="Total" value={stats.total} />
<DataCard title="Active" value={stats.active} />
<DataCard title="Done" value={stats.done} />
</Grid>
</CardContent>
</Card>
```
## File Structure
```
src/
├── components/
│ ├── atoms/ # < 50 LOC components
│ │ ├── Heading.tsx
│ │ ├── Text.tsx
│ │ ├── List.tsx
│ │ ├── Grid.tsx
│ │ └── StatusBadge.tsx
│ ├── molecules/ # 50-100 LOC components
│ │ ├── DataCard.tsx
│ │ ├── SearchInput.tsx
│ │ └── ActionBar.tsx
│ └── ui/ # shadcn components
├── hooks/
│ ├── data/ # Data management hooks
│ │ ├── use-crud.ts
│ │ ├── use-search.ts
│ │ ├── use-filter.ts
│ │ └── use-sort.ts
│ └── ui/ # UI state hooks
│ ├── use-dialog.ts
│ ├── use-toggle.ts
│ └── use-form.ts
├── lib/
│ └── json-ui/ # JSON UI system
│ ├── page-renderer.tsx
│ ├── component-renderer.tsx
│ └── component-registry.tsx
├── schemas/ # JSON page schemas
│ ├── dashboard-schema.ts
│ └── page-schemas.ts
└── types/
└── json-ui.ts # TypeScript types
```
## Documentation
- **JSON_UI_GUIDE.md** - Complete JSON UI documentation
- **PRD.md** - Product requirements and design decisions
## Contributing
When adding new components or hooks:
1. Follow LOC limits (atoms < 50, molecules < 100, organisms < 150)
2. Add TypeScript types
3. Update component registry if adding UI components
4. Document in README
5. Add examples
---
**Built with React, TypeScript, Tailwind CSS, and Shadcn UI**