mirror of https://github.com/Mai-with-u/MaiBot.git
docs(dashboard): add comprehensive dynamic form system documentation
- Overview of schema-driven UI system - Quick start guide with examples - Backend UI metadata guide (json_schema_extra) - Hook component creation tutorial - API reference for all components - Troubleshooting sectionpull/1496/head
parent
fa15fef1f0
commit
2de618464a
|
|
@ -0,0 +1,126 @@
|
|||
# Dynamic Config Form System
|
||||
|
||||
## Overview
|
||||
The Dynamic Config Form system is a schema-driven UI component designed to automatically generate configuration forms based on backend Pydantic models. It supports rich metadata for UI customization and a flexible Hook system for complex fields.
|
||||
|
||||
### Core Components
|
||||
- **DynamicConfigForm**: The main component that takes a `ConfigSchema` and renders the entire form.
|
||||
- **DynamicField**: A lower-level component that renders individual fields based on their type and UI metadata.
|
||||
- **FieldHookRegistry**: A registry for custom React components that can replace or wrap default field rendering.
|
||||
|
||||
## Quick Start
|
||||
To use the dynamic form in your page:
|
||||
|
||||
```typescript
|
||||
import { DynamicConfigForm } from '@/components/dynamic-form'
|
||||
import { fieldHooks } from '@/lib/field-hooks'
|
||||
|
||||
// Example usage in a component
|
||||
export function ConfigPage() {
|
||||
const [config, setConfig] = useState({})
|
||||
const schema = useConfigSchema() // Fetch from API
|
||||
|
||||
const handleChange = (fieldPath: string, value: unknown) => {
|
||||
// fieldPath can be nested, e.g., 'section.subfield'
|
||||
updateConfigAt(fieldPath, value)
|
||||
}
|
||||
|
||||
return (
|
||||
<DynamicConfigForm
|
||||
schema={schema}
|
||||
values={config}
|
||||
onChange={handleChange}
|
||||
hooks={fieldHooks}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Adding UI Metadata (Backend)
|
||||
You can customize how fields are rendered by adding `json_schema_extra` to your Pydantic `Field` definitions.
|
||||
|
||||
### Supported Metadata
|
||||
- `x-widget`: Specifies the UI component to use.
|
||||
- `slider`: A range slider (requires `ge`, `le`, and `step`).
|
||||
- `switch`: A toggle switch (for booleans).
|
||||
- `textarea`: A multi-line text input.
|
||||
- `select`: A dropdown menu (for `Literal` or enum types).
|
||||
- `custom`: Indicates that this field requires a Hook for rendering.
|
||||
- `x-icon`: A Lucide icon name (e.g., `MessageSquare`, `Settings`).
|
||||
- `step`: Incremental step for sliders or number inputs.
|
||||
|
||||
### Example
|
||||
```python
|
||||
class ChatConfig(ConfigBase):
|
||||
talk_value: float = Field(
|
||||
default=0.5,
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
json_schema_extra={
|
||||
"x-widget": "slider",
|
||||
"x-icon": "MessageSquare",
|
||||
"step": 0.1
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Creating Hook Components
|
||||
Hooks allow you to provide custom UI for complex configuration sections or fields.
|
||||
|
||||
### FieldHookComponent Interface
|
||||
A Hook component receives the following props:
|
||||
- `fieldPath`: The full path to the field.
|
||||
- `value`: The current value of the field/section.
|
||||
- `onChange`: Callback to update the value.
|
||||
- `children`: (Only for `wrapper` hooks) The default field renderer.
|
||||
|
||||
### Implementation Example
|
||||
```typescript
|
||||
import type { FieldHookComponent } from '@/lib/field-hooks'
|
||||
|
||||
export const CustomSectionHook: FieldHookComponent = ({
|
||||
fieldPath,
|
||||
value,
|
||||
onChange
|
||||
}) => {
|
||||
return (
|
||||
<div className="custom-section">
|
||||
<h3>Custom UI</h3>
|
||||
<input
|
||||
value={value.some_prop}
|
||||
onChange={(e) => onChange({ ...value, some_prop: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Registering Hooks
|
||||
Register hooks in your component's lifecycle:
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
fieldHooks.register('chat', ChatSectionHook, 'replace')
|
||||
return () => fieldHooks.unregister('chat')
|
||||
}, [])
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### DynamicConfigForm
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `schema` | `ConfigSchema` | The schema generated by the backend. |
|
||||
| `values` | `Record<string, any>` | Current configuration values. |
|
||||
| `onChange` | `(field: string, value: any) => void` | Change handler. |
|
||||
| `hooks` | `FieldHookRegistry` | Optional custom hook registry. |
|
||||
|
||||
### FieldHookRegistry
|
||||
- `register(path, component, type)`: Register a hook.
|
||||
- `get(path)`: Retrieve a registered hook.
|
||||
- `has(path)`: Check if a hook exists.
|
||||
- `unregister(path)`: Remove a hook.
|
||||
|
||||
## Troubleshooting
|
||||
- **Hook not rendering**: Ensure the registration path matches the schema field name exactly (e.g., `chat` vs `Chat`).
|
||||
- **Field missing**: Check if the field is present in the `ConfigSchema` returned by the backend.
|
||||
- **TypeScript errors**: Ensure your Hook implements the `FieldHookComponent` type.
|
||||
Loading…
Reference in New Issue