Components Guide
Learn how to use the core Dial components: DialProvider and DialPanel.
Component Overview
The Dial system provides two main React components for rendering controls:
- DialProvider: Context provider that manages state and configuration
- DialPanel: UI component that renders the actual controls
DialProvider
The DialProvider component is a React context provider that manages the state and configuration for all dial controls within its tree.
Basic Usage
import { DialProvider, DialPanel } from '@vuer-ai/vuer-uikit';
function App() {
const handleValueChange = (name: string, value: any) => {
console.log(`${name} changed to:`, value);
};
return (
<DialProvider
schemas={schemas}
onValueChange={handleValueChange}
>
<DialPanel schemas={schemas} />
</DialProvider>
);
}
Props
| Prop | Type | Required | Description |
|---|---|---|---|
schemas | DialSchema[] | Yes | Array of schema objects defining controls |
onValueChange | (name, value) => void | No | Callback when any value changes |
initialValues | Record<string, unknown> | No | Initial values for uncontrolled mode |
values | Record<string, unknown> | No | Current values for controlled mode |
children | ReactNode | Yes | Child components |
Advanced Usage
// Uncontrolled mode with initial values
<DialProvider
schemas={schemas}
onValueChange={handleChange}
initialValues={{
position: [0, 0, 0],
color: '#3b82f6',
visible: true
}}
>
{/* Your components */}
</DialProvider>
// Controlled mode
<DialProvider
schemas={schemas}
values={currentValues}
onValueChange={handleChange}
>
{/* Your components */}
</DialProvider>
DialPanel
The DialPanel component renders the actual UI controls based on the provided schemas.
Basic Usage
<DialPanel schemas={schemas} />
Props
| Prop | Type | Required | Description |
|---|---|---|---|
schemas | DialSchema[] | Yes | Array of schema objects defining the controls |
groups | GroupSchema[] | No | Group configuration for layout and organization |
labelLayout | 'left' | 'top' | 'inline' | No | Default label position for all controls |
With Groups
const groups: GroupSchema[] = [
{
name: 'transform',
layout: 'grid',
gridCols: 3, // 3 columns
gridFlow: 'row'
},
{
name: 'appearance',
layout: 'grid',
gridCols: 2, // 2 columns
}
];
<DialPanel
schemas={schemas}
groups={groups}
labelLayout="top"
/>
GroupSchema Properties
| Property | Type | Description |
|---|---|---|
name | string | Group identifier (matches schema's grouping field) |
layout | 'grid' | 'flex' | Layout type |
gridCols | number | Number of grid columns |
gridRows | number | Number of grid rows |
gridFlow | 'row' | 'column' | Grid flow direction |
gridColTemplate | string | CSS grid-template-columns value |
gridRowTemplate | string | CSS grid-template-rows value |
flexWrap | 'nowrap' | 'wrap' | 'wrap-reverse' | Flex wrap behavior |
flexJustifyContent | 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' | Flex justify content |
Working Together
DialProvider and DialPanel work together to create a complete control system:
DialProvider + DialPanel
transform
x
y
z
x°
y°
z°
appearance
#
View Code
<DialProvider
schemas={schemas}
values={values}
onValueChange={handleValueChange}
>
<DialPanel
schemas={schemas}
groups={groups}
/>
</DialProvider>Live Preview
View State
{
"position": [
0,
0,
0
],
"rotation": [
0,
0,
0
],
"scale": 1,
"color": "#3b82f6",
"opacity": 1,
"visible": true,
"wireframe": false
}DialProvider
- • Manages state
- • Provides context
- • Handles changes
- • Configures theme
DialPanel
- • Renders controls
- • Organizes groups
- • Handles layout
- • Shows icons/labels
Features
- • Real-time updates
- • Type-safe values
- • Custom styling
- • React hooks
State Management
Controlled Components
function ControlledExample() {
const [state, setState] = useState({
position: [0, 0, 0],
color: '#3b82f6',
opacity: 1
});
const handleValueChange = (name: string, value: any) => {
setState(prev => ({ ...prev, [name]: value }));
};
return (
<DialProvider
schemas={schemas}
onValueChange={handleValueChange}
initialValues={state}
>
<DialPanel schemas={schemas} />
{/* Use the state in your app */}
<Box3D {...state} />
</DialProvider>
);
}
Uncontrolled Components
function UncontrolledExample() {
// Let DialProvider manage its own state
return (
<DialProvider
schemas={schemas}
onValueChange={(name, value) => {
console.log('Changed:', name, value);
}}
>
<DialPanel schemas={schemas} />
</DialProvider>
);
}
Hooks
useDialSchema
Access the dial context from child components within a DialProvider:
import { useDialSchema } from '@vuer-ai/vuer-uikit';
function ChildComponent() {
const { values, setValue, schemas, getValue } = useDialSchema();
return (
<div>
<p>Current position: {JSON.stringify(getValue('position'))}</p>
<button onClick={() => setValue('position', [0, 0, 0])}>
Reset Position
</button>
<pre>{JSON.stringify(values, null, 2)}</pre>
</div>
);
}
Context Value Interface:
interface DialContextValue {
values: Record<string, unknown>; // All current values
schemas: DialSchema[]; // Schema definitions
getValue: (name: string) => DialValue; // Get a formatted value
setValue: (name: string, value: DialValue) => void; // Set a value
onValueChange?: (name: string, value: DialValue) => void; // Optional change handler
}
Performance Optimization
Memoization
import React, { memo, useCallback, useMemo } from 'react';
const MemoizedPanel = memo(DialPanel);
function OptimizedExample() {
const handleChange = useCallback((name, value) => {
// Handle change
}, []);
const memoizedSchemas = useMemo(() => schemas, []);
return (
<DialProvider
schemas={memoizedSchemas}
onValueChange={handleChange}
>
<MemoizedPanel schemas={memoizedSchemas} />
</DialProvider>
);
}
Debouncing Updates
import { debounce } from 'lodash';
function DebouncedExample() {
const debouncedChange = useMemo(
() => debounce((name, value) => {
// Update server or expensive operation
updateServer(name, value);
}, 300),
[]
);
const handleChange = (name: string, value: any) => {
// Update local state immediately
setLocalState({ [name]: value });
// Debounce expensive updates
debouncedChange(name, value);
};
return (
<DialProvider schemas={schemas} onValueChange={handleChange}>
<DialPanel schemas={schemas} />
</DialProvider>
);
}
Error Handling
Error Boundaries
class DialErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>Error loading dial controls</div>;
}
return this.props.children;
}
}
// Usage
<DialErrorBoundary>
<DialProvider schemas={schemas}>
<DialPanel schemas={schemas} />
</DialProvider>
</DialErrorBoundary>
Schema Validation
function ValidatedDialPanel({ schemas }) {
const validSchemas = schemas.filter(schema => {
// Validate required fields
if (!schema.name || !schema.dtype) {
console.warn('Invalid schema:', schema);
return false;
}
return true;
});
return (
<DialProvider schemas={validSchemas}>
<DialPanel schemas={validSchemas} />
</DialProvider>
);
}
Best Practices
-
Always wrap DialPanel with DialProvider
- DialPanel requires the context provided by DialProvider
-
Keep schemas immutable
- Don't modify schema objects after creation
- Use useMemo to prevent unnecessary re-renders
-
Handle value changes properly
- Always provide onValueChange if you need to track state
- Update state immutably in your handler
-
Use groups for organization
- Group related controls together
- Configure layout per group
-
Optimize for performance
- Use React.memo for panels that don't change often
- Debounce expensive operations
- Split large panels into smaller ones
Troubleshooting
Controls not appearing
// ❌ Wrong - Missing DialProvider
<DialPanel schemas={schemas} />
// ✅ Correct - Wrapped with DialProvider
<DialProvider schemas={schemas}>
<DialPanel schemas={schemas} />
</DialProvider>
Values not updating
// ❌ Wrong - Missing onValueChange
<DialProvider schemas={schemas}>
<DialPanel schemas={schemas} />
</DialProvider>
// ✅ Correct - With change handler
<DialProvider
schemas={schemas}
onValueChange={(name, value) => {
setState(prev => ({ ...prev, [name]: value }));
}}
>
<DialPanel schemas={schemas} />
</DialProvider>
Context not found error
// ❌ Wrong - Using hook outside provider
function App() {
const { values } = useDialSchema(); // Error!
return <div>{values}</div>;
}
// ✅ Correct - Using hook inside provider
function App() {
return (
<DialProvider schemas={schemas}>
<ChildComponent />
</DialProvider>
);
}
function ChildComponent() {
const { values } = useDialSchema(); // Works!
return <div>{values}</div>;
}