Skip to content

Component System ​

The component system defines how dashboard components are declared, registered, and rendered. The central pattern is ComponentDescriptor -- a declarative, composition-based approach where all component behavior is passed as constructor parameters.

Overview ​

ComponentDescriptor ​

ComponentDescriptor is the recommended way to define dashboard components. Instead of creating a subclass of DashboardComponentConfiguration, you create an instance and pass all behavior as parameters:

dart
final statCard = ComponentDescriptor(
  schemaType: 'lms.stat_card',
  componentKey: 'lms_stat_card',
  displayName: 'Stat Card',
  description: 'Displays a single metric with trend indicator',
  icon: Icons.analytics,
  category: lmsCategory,
  defaultSize: const Size(2, 2),
  minSize: const Size(1, 1),
  isResizable: true,
  minCellHeight: 100,

  // Properties factory -- creates a fresh PropertyCollection
  properties: () => (PropertyCollectionBuilder()
    ..group('content', 'Content')
    ..string('title', 'Title', defaultValue: 'Metric')
    ..integer('value', 'Value', defaultValue: 0)
    ..string('trend', 'Trend', defaultValue: '+0%')
    ..group('appearance', 'Appearance')
    ..enumeration('trendDirection', 'Trend Direction',
      values: ['up', 'down', 'neutral'],
      defaultValue: 'up',
    )
  ).buildCollection(),

  // Render callback -- builds the widget from properties
  render: (context, props) {
    final title = props.stringValue('title') ?? 'Metric';
    final value = props.intValue('value') ?? 0;
    final trend = props.stringValue('trend') ?? '';

    return StatCardWidget(title: title, value: value, trend: trend);
  },

  // Config panel callback (optional) -- builds the settings UI
  configPanel: (context, props) {
    return StatCardConfigPanel(properties: props);
  },

  // Thumbnail callback (optional) -- builds a palette preview
  thumbnail: (context) {
    return const Icon(Icons.analytics, size: 32);
  },
);

Constructor Parameters ​

ParameterTypeRequiredDefaultDescription
schemaTypeStringYes--Unique schema type identifier (e.g., 'lms.stat_card')
componentKeyStringYes--Registry key for lookup
displayNameStringYes--Human-readable name for the palette
descriptionStringNo''Longer description for tooltips
iconIconDataNoIcons.widgetsIcon for palette and toolbar
categoryComponentCategoryNoComponentCategory.generalCategory for palette grouping
isPrimitiveboolNofalseWhether this is a framework-provided primitive
defaultSizeSizeNoSize(2, 2)Default grid size (columns, rows)
minSizeSizeNoSize(1, 1)Minimum allowed size
maxSizeSizeNonullMaximum allowed size (null = no limit)
isResizableboolNotrueWhether users can resize this component
minCellHeightdoubleNonullMinimum row height when this component is placed
propertiesPropertiesFactoryYes--Factory function returning a fresh PropertyCollection
renderComponentBuilderYes--Build function receiving context and properties
configPanelConfigPanelBuilderNonullConfig panel builder (null uses auto-generated panel)
thumbnailThumbnailBuilderNonullThumbnail builder for the component palette

Type Aliases ​

dart
typedef ComponentBuilder = Widget Function(
  BuildContext context,
  PropertyCollection properties,
);

typedef PropertiesFactory = PropertyCollection Function();

typedef ConfigPanelBuilder = Widget? Function(
  BuildContext context,
  PropertyCollection properties,
);

typedef ThumbnailBuilder = Widget? Function(BuildContext context);

Default Config Panel ​

If you do not provide a configPanel callback, the system uses TabbedPropertyCollectionEditor, which automatically generates a tabbed editor UI from the PropertyCollection's group structure. This works well for most components and requires zero custom UI code.

DashboardComponentConfiguration (Base Class) ​

For advanced cases where you need inheritance or complex initialization, you can extend DashboardComponentConfiguration directly:

dart
abstract class DashboardComponentConfiguration {
  final String schemaType;

  // Override these getters
  String get componentKey;
  String get displayName;
  String get description;
  IconData get icon;
  ComponentCategory get category;
  bool get isPrimitive;
  Size get defaultSize;
  Size? get minSize;
  Size? get maxSize;
  bool get isResizable;
  double? get minCellHeight;

  // Implement these methods
  PropertyCollection createProperties();
  Widget build(BuildContext context, PropertyCollection properties);
  Widget? buildConfigPanel(BuildContext context, PropertyCollection properties);
  Widget? buildThumbnail(BuildContext context);
  Map<String, dynamic> toJson();
}

In practice, ComponentDescriptor is preferred because it avoids boilerplate. Use the base class only when you need:

  • Shared behavior across multiple component types via a common superclass
  • Complex initialization logic that does not fit in a single constructor call
  • Integration with the framework's TypeDescriptor system for JSON polymorphism

ComponentCategory ​

Categories organize components in the editor palette:

dart
class ComponentCategory {
  final String id;            // Unique identifier
  final String displayName;   // Human-readable name
  final IconData? icon;       // Optional palette icon
  final int sortOrder;        // Sort priority (lower = first)

  // Built-in categories
  static const primitives;    // id: 'primitives', sortOrder: 1
  static const general;       // id: 'general', sortOrder: 999
}

Define custom categories for your domain:

dart
const lmsCategory = ComponentCategory(
  id: 'lms',
  displayName: 'LMS',
  icon: Icons.school,
  sortOrder: 10,
);

const analyticsCategory = ComponentCategory(
  id: 'analytics',
  displayName: 'Analytics',
  icon: Icons.bar_chart,
  sortOrder: 20,
);

DashboardDescriptor ​

A DashboardDescriptor groups related components for a specific product or domain. This follows the same "Descriptor" pattern used by FeatureDescriptor and EntityConfiguration in the Vyuh Framework.

dart
class DashboardDescriptor {
  final String name;           // Unique group name (e.g., 'LMS', 'IPQC')
  final String? description;   // Human-readable description
  final ComponentCategory? category;
  final List<String> tags;     // For filtering and searching
  final List<DashboardComponentConfiguration> components;
}

Registering Descriptors ​

dart
// Define descriptors for each domain
final lmsDashboard = DashboardDescriptor(
  name: 'LMS',
  description: 'Learning Management System dashboards',
  tags: ['education', 'analytics'],
  components: [
    statCardDescriptor,
    kpiWidgetDescriptor,
    enrollmentChartDescriptor,
    completionsTableDescriptor,
  ],
);

// Create a scoped registry for the editor/view that needs these descriptors.
final registry = DashboardEditorRegistry([
  PrimitiveComponents.descriptor,
  lmsDashboard,
]);

DashboardEditorRegistry ​

The DashboardEditorRegistry is a scoped index of dashboard descriptors and their components. Create it from a list of DashboardDescriptor values, then pass the same registry into DashboardEditorController, DashboardEngine, DashboardView, and any ComponentInstance.withDefaults calls that need to resolve a component key.

dart
class DashboardEditorRegistry {
  DashboardEditorRegistry(List<DashboardDescriptor> dashboards);

  // Dashboard queries
  DashboardDescriptor? getDashboard(String name);
  List<DashboardDescriptor> get dashboards;
  List<DashboardComponentConfiguration> getComponentsFor(String dashboardName);
  bool hasDashboard(String name);
  int get dashboardCount;

  // Component queries (flat, global)
  DashboardComponentConfiguration? get(String componentKey);
  List<DashboardComponentConfiguration> getAll();
  bool hasComponent(String componentKey);
  int get componentCount;
  List<DashboardComponentConfiguration> getByCategory(String categoryId);
  List<DashboardComponentConfiguration> getPrimitives();
  Map<String, List<DashboardComponentConfiguration>> getGroupedByCategory();

  // Category access
  ComponentCategoryRegistry get categories;
}

Lookup Flow ​

When a ComponentInstance is deserialized or a component needs to be rendered, the system looks up the component by its componentKey:

dart
// ComponentInstance references a component by key
final component = ComponentInstance(
  id: 'component-1',
  componentKey: 'lms_stat_card',  // ← key used for lookup
  properties: ...,
);

// The registry resolves the key to a ComponentDescriptor
final descriptor = registry.get('lms_stat_card');

// The descriptor provides properties, rendering, and config panel
final properties = descriptor!.createProperties();
final widget = descriptor.build(context, properties);

LMS Example: Complete Component Set ​

Here is a complete set of LMS dashboard components:

Stat Card Component ​

dart
final lmsStatCard = ComponentDescriptor(
  schemaType: 'lms.stat_card',
  componentKey: 'lms_stat_card',
  displayName: 'Enrollment Stat',
  description: 'Shows a single enrollment metric with trend',
  icon: Icons.people,
  category: lmsCategory,
  minCellHeight: 100,
  properties: () => (PropertyCollectionBuilder()
    ..string('title', 'Title', defaultValue: 'Metric')
    ..integer('value', 'Value', defaultValue: 0)
    ..string('trend', 'Trend', defaultValue: '+0%')
    ..enumeration('trendDirection', 'Direction',
      values: ['up', 'down', 'neutral'],
      defaultValue: 'up',
    )
    ..string('icon', 'Icon Name', defaultValue: 'people')
  ).buildCollection(),
  render: (context, props) => LmsStatCardView(properties: props),
);

KPI Widget Component ​

dart
final lmsKpiWidget = ComponentDescriptor(
  schemaType: 'lms.kpi_widget',
  componentKey: 'lms_kpi_widget',
  displayName: 'KPI Progress',
  description: 'Progress bar showing target vs actual',
  icon: Icons.speed,
  category: lmsCategory,
  minCellHeight: 120,
  properties: () => (PropertyCollectionBuilder()
    ..string('label', 'Label', defaultValue: 'Completion Rate')
    ..double_('target', 'Target', defaultValue: 100)
    ..double_('actual', 'Actual', defaultValue: 0)
    ..string('unit', 'Unit', defaultValue: '%')
    ..string('color', 'Color', defaultValue: '#2196F3')
  ).buildCollection(),
  render: (context, props) => LmsKpiWidgetView(properties: props),
);

Enrollment Chart Component ​

dart
final lmsEnrollmentChart = ComponentDescriptor(
  schemaType: 'lms.enrollment_chart',
  componentKey: 'lms_enrollment_chart',
  displayName: 'Enrollment Chart',
  description: 'Bar chart showing enrollments per course',
  icon: Icons.bar_chart,
  category: lmsCategory,
  minCellHeight: 250,
  defaultSize: const Size(4, 3),
  properties: () => (PropertyCollectionBuilder()
    ..group('data', 'Data Source')
    ..string('endpoint', 'API Endpoint',
      defaultValue: '/api/enrollments/by-course',
    )
    ..string('labelField', 'Label Field', defaultValue: 'courseName')
    ..string('valueField', 'Value Field', defaultValue: 'enrollmentCount')
    ..group('appearance', 'Appearance')
    ..enumeration('chartType', 'Chart Type',
      values: ['bar', 'groupedBar', 'stackedBar'],
      defaultValue: 'bar',
    )
    ..boolean('showLegend', 'Show Legend', defaultValue: true)
  ).buildCollection(),
  render: (context, props) => LmsEnrollmentChartView(properties: props),
);

Registering the LMS Suite ​

dart
final registry = DashboardEditorRegistry([
  DashboardDescriptor(
    name: 'LMS',
    description: 'Learning Management System dashboards',
    tags: ['education', 'analytics', 'enrollment'],
    components: [
      lmsStatCard,
      lmsKpiWidget,
      lmsEnrollmentChart,
    ],
  ),
);

Next Steps ​