mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-05-02 01:24:56 +00:00
stuff
This commit is contained in:
587
docs/JSON_UI_GUIDE.md
Normal file
587
docs/JSON_UI_GUIDE.md
Normal file
@@ -0,0 +1,587 @@
|
||||
# JSON-Driven UI Architecture Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This application uses a **declarative JSON-driven architecture** that allows you to build entire user interfaces from configuration rather than code. Combined with **atomic components** (all under 150 LOC) and **custom hooks** for business logic, this creates a highly maintainable and rapidly prototype-able system.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. JSON Schema Definition
|
||||
|
||||
Define your entire UI using JSON schemas with:
|
||||
- **Data Sources**: KV store, computed values, static data
|
||||
- **Components**: Shadcn UI components with props and bindings
|
||||
- **Actions**: CRUD operations, toasts, navigation
|
||||
- **Events**: User interactions that trigger actions
|
||||
|
||||
### 2. Atomic Components
|
||||
|
||||
Small, focused, reusable components:
|
||||
- **Atoms** (< 50 LOC): ActionButton, IconButton, DataList, LoadingSpinner
|
||||
- **Molecules** (50-100 LOC): StatCard, EmptyState, SearchBar
|
||||
- **Organisms** (100-150 LOC): DataTable, FormBuilder, Dashboard
|
||||
|
||||
### 3. Custom Hooks
|
||||
|
||||
Extract business logic into reusable hooks:
|
||||
- **Data Management**: useCRUD, useSearch, useSort, useJSONData
|
||||
- **UI State**: useDialog, useActionExecutor
|
||||
- **Forms**: useForm, useFormField
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Define a Page Schema
|
||||
|
||||
```typescript
|
||||
import { PageSchema } from '@/types/json-ui'
|
||||
|
||||
export const myPageSchema: 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: 'h1',
|
||||
props: {
|
||||
className: 'text-3xl font-bold',
|
||||
children: 'My Page'
|
||||
}
|
||||
},
|
||||
{
|
||||
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'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Use Custom Hooks
|
||||
|
||||
```typescript
|
||||
import { useCRUD, useSearch, useDialog } from '@/hooks'
|
||||
|
||||
function MyComponent() {
|
||||
// CRUD operations with persistence
|
||||
const { items, create, update, remove } = useCRUD({
|
||||
key: 'my-items',
|
||||
defaultValue: [],
|
||||
persist: true
|
||||
})
|
||||
|
||||
// Search functionality
|
||||
const { query, setQuery, filtered } = useSearch({
|
||||
items,
|
||||
searchFields: ['name', 'description']
|
||||
})
|
||||
|
||||
// Dialog state management
|
||||
const dialog = useDialog()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input value={query} onChange={(e) => setQuery(e.target.value)} />
|
||||
{filtered.map(item => (
|
||||
<div key={item.id}>{item.name}</div>
|
||||
))}
|
||||
<button onClick={dialog.open}>Open</button>
|
||||
<Dialog open={dialog.isOpen} onOpenChange={dialog.setOpen}>
|
||||
{/* dialog content */}
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Build Atomic Components
|
||||
|
||||
```typescript
|
||||
// Atom: Single responsibility, no business logic
|
||||
export function ActionButton({ icon, label, onClick }: Props) {
|
||||
return (
|
||||
<Button onClick={onClick}>
|
||||
{icon}
|
||||
{label && <span className="ml-2">{label}</span>}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
// Molecule: Composition of atoms
|
||||
export function SearchBar({ value, onChange, onClear }: Props) {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Input value={value} onChange={onChange} />
|
||||
<ActionButton
|
||||
icon={<X />}
|
||||
onClick={onClear}
|
||||
variant="ghost"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Data Source Types
|
||||
|
||||
### KV (Persistent)
|
||||
```typescript
|
||||
{
|
||||
id: 'todos',
|
||||
type: 'kv',
|
||||
key: 'app-todos',
|
||||
defaultValue: []
|
||||
}
|
||||
```
|
||||
Data persists between sessions using the KV store.
|
||||
|
||||
### Static (Session Only)
|
||||
```typescript
|
||||
{
|
||||
id: 'searchQuery',
|
||||
type: 'static',
|
||||
defaultValue: ''
|
||||
}
|
||||
```
|
||||
Data lives only in memory, reset on page reload.
|
||||
|
||||
### Computed (Derived)
|
||||
```typescript
|
||||
{
|
||||
id: 'stats',
|
||||
type: 'computed',
|
||||
compute: (data) => ({
|
||||
total: data.todos.length,
|
||||
completed: data.todos.filter(t => t.completed).length
|
||||
}),
|
||||
dependencies: ['todos']
|
||||
}
|
||||
```
|
||||
Automatically recomputes when dependencies change.
|
||||
|
||||
## Action Types
|
||||
|
||||
### CRUD Actions
|
||||
|
||||
**Create**: Add new items
|
||||
```typescript
|
||||
{
|
||||
type: 'create',
|
||||
target: 'todos',
|
||||
compute: (data) => ({
|
||||
id: Date.now(),
|
||||
text: data.newTodo
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Update**: Modify existing data
|
||||
```typescript
|
||||
{
|
||||
type: 'update',
|
||||
target: 'todos',
|
||||
compute: (data) =>
|
||||
data.todos.map(t =>
|
||||
t.id === selectedId ? { ...t, completed: true } : t
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Delete**: Remove items
|
||||
```typescript
|
||||
{
|
||||
type: 'delete',
|
||||
target: 'todos',
|
||||
path: 'id',
|
||||
value: todoId
|
||||
}
|
||||
```
|
||||
|
||||
### UI Actions
|
||||
|
||||
**Show Toast**
|
||||
```typescript
|
||||
{
|
||||
type: 'show-toast',
|
||||
message: 'Task completed!',
|
||||
variant: 'success' // success | error | info | warning
|
||||
}
|
||||
```
|
||||
|
||||
**Navigate**
|
||||
```typescript
|
||||
{
|
||||
type: 'navigate',
|
||||
path: '/dashboard'
|
||||
}
|
||||
```
|
||||
|
||||
### Value Actions
|
||||
|
||||
**Set Value**
|
||||
```typescript
|
||||
{
|
||||
type: 'set-value',
|
||||
target: 'searchQuery',
|
||||
compute: (data, event) => event.target.value
|
||||
}
|
||||
```
|
||||
|
||||
**Toggle Value**
|
||||
```typescript
|
||||
{
|
||||
type: 'toggle-value',
|
||||
target: 'showCompleted'
|
||||
}
|
||||
```
|
||||
|
||||
**Increment/Decrement**
|
||||
```typescript
|
||||
{
|
||||
type: 'increment',
|
||||
target: 'counter',
|
||||
value: 1
|
||||
}
|
||||
```
|
||||
|
||||
## Component Bindings
|
||||
|
||||
Bind component props to data sources:
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: 'input',
|
||||
type: 'Input',
|
||||
bindings: {
|
||||
value: {
|
||||
source: 'searchQuery'
|
||||
},
|
||||
placeholder: {
|
||||
source: 'settings',
|
||||
path: 'inputPlaceholder'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With transformations:
|
||||
|
||||
```typescript
|
||||
{
|
||||
bindings: {
|
||||
children: {
|
||||
source: 'count',
|
||||
transform: (value) => `${value} items`
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Event Handling
|
||||
|
||||
### Simple Event
|
||||
```typescript
|
||||
{
|
||||
events: [
|
||||
{
|
||||
event: 'click',
|
||||
actions: [{ type: 'show-toast', message: 'Clicked!' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Event
|
||||
```typescript
|
||||
{
|
||||
events: [
|
||||
{
|
||||
event: 'click',
|
||||
condition: (data) => data.searchQuery.length > 0,
|
||||
actions: [/* ... */]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Actions
|
||||
```typescript
|
||||
{
|
||||
events: [
|
||||
{
|
||||
event: 'click',
|
||||
actions: [
|
||||
{ type: 'create', target: 'items', /* ... */ },
|
||||
{ type: 'set-value', target: 'input', value: '' },
|
||||
{ type: 'show-toast', message: 'Added!' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Available Custom Hooks
|
||||
|
||||
### Data Hooks
|
||||
|
||||
#### `useCRUD<T>`
|
||||
Complete CRUD operations with KV persistence
|
||||
```typescript
|
||||
const { items, create, read, update, remove, clear } = useCRUD({
|
||||
key: 'todos',
|
||||
defaultValue: [],
|
||||
persist: true
|
||||
})
|
||||
```
|
||||
|
||||
#### `useSearch<T>`
|
||||
Multi-field search with filtering
|
||||
```typescript
|
||||
const { query, setQuery, filtered, resultCount } = useSearch({
|
||||
items: todos,
|
||||
searchFields: ['text', 'tags'],
|
||||
caseSensitive: false
|
||||
})
|
||||
```
|
||||
|
||||
#### `useSort<T>`
|
||||
Multi-key sorting with direction toggle
|
||||
```typescript
|
||||
const { sorted, field, direction, toggleSort } = useSort({
|
||||
items: todos,
|
||||
initialField: 'createdAt',
|
||||
initialDirection: 'desc'
|
||||
})
|
||||
```
|
||||
|
||||
#### `useJSONData`
|
||||
Flexible data management with optional persistence
|
||||
```typescript
|
||||
const { value, setValue, updatePath, reset } = useJSONData({
|
||||
key: 'user-prefs',
|
||||
defaultValue: {},
|
||||
persist: true
|
||||
})
|
||||
```
|
||||
|
||||
### UI Hooks
|
||||
|
||||
#### `useDialog`
|
||||
Dialog/modal state management
|
||||
```typescript
|
||||
const dialog = useDialog(false)
|
||||
// dialog.isOpen, dialog.open(), dialog.close(), dialog.toggle()
|
||||
```
|
||||
|
||||
#### `useActionExecutor`
|
||||
Execute JSON-defined actions
|
||||
```typescript
|
||||
const { executeAction, executeActions } = useActionExecutor(context)
|
||||
await executeAction({ type: 'create', target: 'items', /* ... */ })
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Keep Components Small
|
||||
- **Atoms**: < 50 LOC
|
||||
- **Molecules**: 50-100 LOC
|
||||
- **Organisms**: 100-150 LOC
|
||||
- If larger, split into smaller pieces
|
||||
|
||||
### 2. Extract Logic to Hooks
|
||||
```typescript
|
||||
// ❌ Bad: Logic in component
|
||||
function TodoList() {
|
||||
const [todos, setTodos] = useState([])
|
||||
const addTodo = (text) => setTodos([...todos, { id: Date.now(), text }])
|
||||
const removeTodo = (id) => setTodos(todos.filter(t => t.id !== id))
|
||||
// ...
|
||||
}
|
||||
|
||||
// ✅ Good: Logic in hook
|
||||
function TodoList() {
|
||||
const { items: todos, create, remove } = useCRUD({
|
||||
key: 'todos',
|
||||
defaultValue: []
|
||||
})
|
||||
// Component only handles UI
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use Computed Data Sources
|
||||
```typescript
|
||||
// ❌ Bad: Computing in render
|
||||
components: [{
|
||||
type: 'div',
|
||||
props: {
|
||||
children: `${data.todos.filter(t => t.completed).length} completed`
|
||||
}
|
||||
}]
|
||||
|
||||
// ✅ Good: Computed data source
|
||||
dataSources: [
|
||||
{
|
||||
id: 'stats',
|
||||
type: 'computed',
|
||||
compute: (data) => ({
|
||||
completed: data.todos.filter(t => t.completed).length
|
||||
})
|
||||
}
|
||||
],
|
||||
components: [{
|
||||
bindings: {
|
||||
children: {
|
||||
source: 'stats',
|
||||
path: 'completed',
|
||||
transform: (v) => `${v} completed`
|
||||
}
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
### 4. Chain Actions
|
||||
```typescript
|
||||
// Multiple related actions in sequence
|
||||
events: [{
|
||||
event: 'click',
|
||||
actions: [
|
||||
{ type: 'create', /* ... */ }, // 1. Add item
|
||||
{ type: 'set-value', /* ... */ }, // 2. Clear input
|
||||
{ type: 'show-toast', /* ... */ } // 3. Show feedback
|
||||
]
|
||||
}]
|
||||
```
|
||||
|
||||
### 5. Leverage Atomic Composition
|
||||
```typescript
|
||||
// Build complex UIs from simple atoms
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Dashboard</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<DataList
|
||||
items={filtered}
|
||||
renderItem={(item) => (
|
||||
<div className="flex gap-2">
|
||||
<ActionButton icon={<Edit />} onClick={() => edit(item)} />
|
||||
<ActionButton icon={<Trash />} onClick={() => remove(item.id)} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
## Example: Complete Todo App
|
||||
|
||||
See `/src/schemas/page-schemas.ts` for a full working example with:
|
||||
- KV persistence
|
||||
- Computed statistics
|
||||
- CRUD operations
|
||||
- Action chaining
|
||||
- Conditional rendering
|
||||
- Event handling
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: Extract Hooks
|
||||
1. Identify repeated logic patterns
|
||||
2. Create custom hooks
|
||||
3. Replace inline logic with hook calls
|
||||
|
||||
### Phase 2: Break Down Components
|
||||
1. Identify large components (>150 LOC)
|
||||
2. Split into atoms, molecules, organisms
|
||||
3. Compose back together
|
||||
|
||||
### Phase 3: Define JSON Schemas
|
||||
1. Convert simple pages to JSON first
|
||||
2. Test with PageRenderer
|
||||
3. Gradually migrate complex pages
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Use `useMemo` for expensive computations**
|
||||
2. **Implement virtual scrolling for large lists**
|
||||
3. **Lazy load heavy components**
|
||||
4. **Debounce search and filter operations**
|
||||
5. **Use computed data sources instead of computing in render**
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Data not persisting?
|
||||
- Check data source type is 'kv'
|
||||
- Verify key is unique
|
||||
- Ensure useKV is called unconditionally
|
||||
|
||||
### Actions not executing?
|
||||
- Check action type spelling
|
||||
- Verify target matches data source id
|
||||
- Ensure compute function returns correct type
|
||||
|
||||
### Components not rendering?
|
||||
- Check component type is registered
|
||||
- Verify props match component API
|
||||
- Check conditional bindings
|
||||
|
||||
## Resources
|
||||
|
||||
- **Type Definitions**: `/src/types/json-ui.ts`
|
||||
- **Page Schemas**: `/src/schemas/page-schemas.ts`
|
||||
- **Custom Hooks**: `/src/hooks/data/` and `/src/hooks/ui/`
|
||||
- **Atomic Components**: `/src/components/atoms/`
|
||||
- **Component Registry**: `/src/lib/json-ui/component-registry.ts`
|
||||
|
||||
---
|
||||
|
||||
**Built with ❤️ using React, TypeScript, and Shadcn UI**
|
||||
Reference in New Issue
Block a user