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.
In this guide, we will be creating a "border addon" that allows you to put a border around your story.
Start by creating a new file in your widgetbook project (e.g. border_addon.dart).
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
class BorderSetting {
const BorderSetting(this.width, this.color);
final int width;
final Color color;
}
class BorderAddon extends Addon<BorderSetting> {
BorderAddon()
: super(
name: 'Border',
initialValue: const BorderSetting(1, Color(0xFF000000)),
);
@override
List<Field<dynamic>> get fields => [
// ...
]
@override
BorderSetting valueFromQueryGroup(QueryGroup? group) {
// TODO
}
@override
QueryGroup valueToQueryGroup(BorderSetting value) {
// TODO
}
@override
Widget apply(
BuildContext context,
Widget child,
BorderSetting setting,
) {
// TODO
}
}Now let's start by implementing each of the class members step by step.
The fields getter is a list of fields that describe how the addon will be represented in both:
- The Widgetbook UI, in the "Addons" panel.
- The URL query parameters.
In this case we just need two fields to represent the BorderSetting:
IntSliderField- to pick an integer value forBorderSetting.width.ColorField- to pick a color value forBorderSetting.color.
@override
List<Field> get fields {
return [
IntSliderField(
name: 'width',
initialValue: initialValue.width,
min: 1,
max: 10,
),
ColorField(
name: 'color',
initialValue: initialValue.color,
),
];
}If your addon just has a single filed, you can use the SingleFieldOnly mixin to reduce boilerplate.
With that, you can skip steps 2 and 3 as a default valueFromQueryGroup and valueToQueryGroup implementations will be provided for you.
class BorderAddon extends Addon<BorderSetting> with SingleFieldOnly {
// ...
}The valueFromQueryGroup method is responsible for parsing the query parameters back to the addon setting (i.e. BorderSetting).
Use the valueOf helper method to extract the values from the query group by giving it the field's name.
@override
BorderSetting valueFromQueryGroup(QueryGroup? group) {
if (group == null) return initialValue;
return BorderSetting(
valueOf('width', group)!,
valueOf('color', group)!,
);
}The valueToQueryGroup method is used to be able to convert Modes to a serializable QueryGroup, so that it can be stored in the URL.
To use the same encoding technique used in the fields, you can use the paramOf helper method.
@override
QueryGroup valueToQueryGroup(BorderSetting value) {
return QueryGroup({
'width': paramOf('width', value.width),
'color': paramOf('color', value.color),
});
}The apply method is responsible for building the story 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 apply(
BuildContext context,
Widget child,
BorderSetting setting,
) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: setting.color,
width: setting.width.toDouble(),
),
),
child: child,
);
}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.
import 'package:flutter/widgets.dart';
import 'addons/border_addon.dart';
final config = Config(
// ...
addons: [BorderAddon()],
);Each addon has a corresponding Mode to lock its value in scenarios. For a custom addon, create your own Mode subclass:
class BorderMode extends Mode<BorderSetting> {
BorderMode(int width, Color color)
: super(
BorderSetting(width, color),
BorderAddon(),
);
@override
String get formattedValue => '${value.width}px';
}final $Default = _Story(
scenarios: [
_Scenario(
name: 'Red Border',
modes: [
BorderMode(2, Colors.red),
]
),
],
)
