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

PropTypeRequiredDescription
schemasDialSchema[]YesArray of schema objects defining controls
onValueChange(name, value) => voidNoCallback when any value changes
initialValuesRecord<string, unknown>NoInitial values for uncontrolled mode
valuesRecord<string, unknown>NoCurrent values for controlled mode
childrenReactNodeYesChild 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

PropTypeRequiredDescription
schemasDialSchema[]YesArray of schema objects defining the controls
groupsGroupSchema[]NoGroup configuration for layout and organization
labelLayout'left' | 'top' | 'inline'NoDefault 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

PropertyTypeDescription
namestringGroup identifier (matches schema's grouping field)
layout'grid' | 'flex'Layout type
gridColsnumberNumber of grid columns
gridRowsnumberNumber of grid rows
gridFlow'row' | 'column'Grid flow direction
gridColTemplatestringCSS grid-template-columns value
gridRowTemplatestringCSS 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
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

  1. Always wrap DialPanel with DialProvider

    • DialPanel requires the context provided by DialProvider
  2. Keep schemas immutable

    • Don't modify schema objects after creation
    • Use useMemo to prevent unnecessary re-renders
  3. Handle value changes properly

    • Always provide onValueChange if you need to track state
    • Update state immutably in your handler
  4. Use groups for organization

    • Group related controls together
    • Configure layout per group
  5. 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>;
}

Next Steps