Custom Addon

If the built-in addons do not meet your needs, you can create your own custom Addons. This allows you to extend the functionality of Widgetbook and tailor it to your specific requirements.

Guide

In this guide, we will be creating a "border addon" that allows you to put a border around your use-case.

0. Base structure

Start by creating a new file in your widgetbook project (e.g. border_addon.dart).

widgetbook/lib/addons/border_addon.dart
import 'package:flutter/widgets.dart';
import 'package:widgetbook/widgetbook.dart';

class BorderSetting {
  const BorderSetting({
    required this.width,
    required this.color,
  });

  final int width;
  final Color color;
}

class BorderAddon extends WidgetbookAddon<BorderSetting> {
  BorderAddon() : super(name: 'Border');

  @override
  List<Field> get fields {
    // TODO
  }

  @override
  BorderSetting valueFromQueryGroup(Map<String, String> group) {
    // TODO
  }

  @override
  Widget buildUseCase(
    BuildContext context,
    Widget child,
    BorderSetting setting,
  ) {
    // TODO
  }
}

Now let's start by implementing each of the class members step by step.

1. fields getter

This is a list of fields that describe how the addon addon will be represented in both:

  • The Widgetbook UI โ€“ the "Addons" panel.
  • The URL query parameters.

In this case we just need two fields to represent the BorderSetting:

  1. IntSliderField - to pick an integer value for BorderSetting.width.
  2. ColorField - to pick a color value for BorderSetting.color.
@override
List<Field> get fields {
  return [
    IntSliderField(
      name: 'width',
      initialValue: 1,
      min: 1,
      max: 10,
    ),
    ColorField(
      name: 'color',
      initialValue: const Color(0xFF000000),
    ),
  ];
}

2. valueFromQueryGroup method

This method is responsible for parsing the query parameters back to the addon setting (i.e. BorderSetting). This can be done by using the valueOf helper method to extract the values from the query group by giving it the field's name.

@override
BorderSetting valueFromQueryGroup(Map<String, String> group) {
  final width = valueOf<int>('width', group)!;
  final color = valueOf<Color>('color', group)!;

  return BorderSetting(
    width: width,
    color: color,
  );
}

3. buildUseCase method

This method is responsible for building the use-case widget with the addon applied. In this case, we will wrap the child widget with a Container that has a border with the given width and color.

@override
Widget buildUseCase(
  BuildContext context,
  Widget child,
  BorderSetting setting,
) {
  return Container(
    decoration: BoxDecoration(
      border: Border.all(
        color: setting.color,
        width: setting.width.toDouble(),
      ),
    ),
    child: child,
  );
}

4. Using the Addon

Now that we have implemented the BorderAddon, we can use it in our Widgetbook project. To do this, we need to add it to the addons list in the Widgetbook widget.

widgetbook/lib/main.dart
import 'package:flutter/widgets.dart';
import 'package:widgetbook/widgetbook.dart';

import 'addons/border_addon.dart';  

class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [BorderAddon()], 
    );
  }
}

Multi-snapshot Support

Custom addons can be used with Multi Snapshot Reviews by implementing the AddonConfig interface.

We are using String colorHex instead of Color color in the AddonConfig because we cannot convert color to hex in a constant constructor. And the constructor needs to be constant to be used in the cloudAddonsConfigs map.

widgetbook/lib/addons/border_addon.dart
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

class BorderAddonConfig extends AddonConfig {
  const BorderAddonConfig(this.width, this.colorHex)
      : super(
          'border', // addon's name in kebab-case
          'width:$width,color:$colorHex', // comma-separated fields' names/values
        );

  final int width;
  final String colorHex;
}
widgetbook/lib/main.dart
import 'package:flutter/widgets.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

import 'addons/border_addon.dart'; 

@App(
  cloudAddonsConfigs: {
    'border black (2)': [
      BorderAddonConfig(2, 'ff000000'), 
    ],
    'border white (4)': [
      BorderAddonConfig(4, 'ffffffff'), 
    ],
  },
)
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        BorderAddon(), 
      ],
    );
  }
}