Custom Knob
If the built-in knobs do not meet your needs, you can create your own custom Knobs. 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 "range knob" that allows you to select a range of values using a RangeSlider
.
0. Base structure
Start by creating a new file in your widgetbook project (e.g. range_knob.dart
).
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
class RangeKnob extends Knob<RangeValues> {
RangeKnob({
required super.label,
required super.initialValue,
});
@override
List<Field> get fields {
// TODO
}
@override
RangeValues valueFromQueryGroup(Map<String, String> group) {
// 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 knob will be represented in both:
- The Widgetbook UI โ the "Knobs" panel.
- The URL query parameters.
In this case we need two fields to represent the RangeValues
:
DoubleInputField
- to pick a double value for the start of the range.DoubleInputField
- to pick a double value for the end of the range.
@override
List<Field> get fields => [
DoubleInputField(
name: '$label.min',
initialValue: initialValue.start,
),
DoubleInputField(
name: '$label.max',
initialValue: initialValue.end,
),
];
2. valueFromQueryGroup
method
This method is responsible for parsing the query parameters back to the knob value (i.e. RangeValues
). 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
RangeValues valueFromQueryGroup(Map<String, String> group) {
final start = valueOf<double>('$label.min', group)!;
final end = valueOf<double>('$label.max', group)!;
return RangeValues(start, end);
}
3. Registering the Knob
To use the custom knob, we need to create an extension on KnobsBuilder
. This extension includes a range
method that adds our RangeKnob
to the builder.
extension RangeKnobBuilder on KnobsBuilder {
RangeValues range({
required String label,
RangeValues initialValue = const RangeValues(0, 10),
}) =>
onKnobAdded(
RangeKnob(
label: label,
initialValue: initialValue,
),
)!;
}
4. Using the Knob
Now that we have implemented the RangeKnob
, we can use it in our use cases. Here's an example of how to use it with a RangeSlider
widget:
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
import '../knobs/range_knob.dart';
@UseCase(name: 'CustomRangeSlider', type: RangeSlider)
Widget buildRangeSliderUseCase(BuildContext context) {
return RangeSlider(
values: context.knobs.range(label: 'Range'),
max: 10,
min: 0,
onChanged: (_) {},
);
}
Multi-snapshot Support
Custom knobs can be used with Multi Snapshot Reviews. The configuration varies based on the number of fields in your custom knob.
Single-field Knobs
If your custom knob is a single-field knob, you can use KnobConfig
to configure it for multi-snapshot support. Here's an example:
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
@UseCase(
name: 'Default',
type: MyWidget,
cloudKnobsConfigs: {
'custom': [KnobConfig('foo', 'value')],
},
)
Widget buildUseCase(BuildContext context) {
return MyWidget(
foo: context.knobs.myKnob(label: 'foo'),
);
}
Multi-field Knobs
If your knobs has more than one field, similar to the RangeKnob
defined above, you can use MultiFieldKnobConfig
to configure it for multi-snapshot support. Here's how you can do it:
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
import '../knobs/range_knob.dart';
@widgetbook.UseCase(
name: 'CustomRangeSlider',
type: RangeSlider,
cloudKnobsConfigs: {
'same range': [
MultiFieldKnobConfig({
'range.min': 5,
'range.max': 5,
}),
],
'small range': [
MultiFieldKnobConfig({
'range.min': 4,
'range.max': 6,
}),
],
},
)
Widget buildRangeSliderUseCase(BuildContext context) {
return RangeSlider(
values: context.knobs.range(label: 'range'),
max: 10,
min: 0,
onChanged: (_) {},
);
}