# 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 story.

### 0. Base structure

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

```dart title=widgetbook/lib/addons/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.

### 1. `fields` getter

The `fields` getter is a list of [fields](/configure/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`:

1. `IntSliderField` - to pick an integer value for `BorderSetting.width`.
1. `ColorField` - to pick a color value for `BorderSetting.color`.

```dart
@override
List<Field> get fields {
  return [
    IntSliderField(
      name: 'width',
      initialValue: initialValue.width,
      min: 1,
      max: 10,
    ),
    ColorField(
      name: 'color',
      initialValue: initialValue.color,
    ),
  ];
}
```

<Info>
  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.

    ```dart
    class BorderAddon extends Addon<BorderSetting> with SingleFieldOnly {
      // ...
    }
    ```

</Info>

### 2. `valueFromQueryGroup` method

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**.

```dart
@override
BorderSetting valueFromQueryGroup(QueryGroup? group) {
  if (group == null) return initialValue;

  return BorderSetting(
    valueOf('width', group)!,
    valueOf('color', group)!,
  );
}
```

### 3. `valueToQueryGroup` method

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.

```dart
@override
QueryGroup valueToQueryGroup(BorderSetting value) {
  return QueryGroup({
    'width': paramOf('width', value.width),
    'color': paramOf('color', value.color),
  });
}
```

### 4. `apply` method

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.

```dart
@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,
  );
}
```

### 5. 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.

```dart title=widgetbook/lib/widgetbook.config.dart
import 'package:flutter/widgets.dart';

import 'addons/border_addon.dart';  // [!code highlight]

final config = Config(
  // ...
  addons: [BorderAddon()], // [!code highlight]
);
```

## Mode

Each addon has a corresponding [Mode](/addons/modes) to lock its value in scenarios.
For a custom addon, create your own Mode subclass:

```dart
class BorderMode extends Mode<BorderSetting> {
  BorderMode(int width, Color color)
    : super(
        BorderSetting(width, color),
        BorderAddon(),
    );

  @override
  String get formattedValue => '${value.width}px';
}
```

```dart
final $Default = _Story(
  scenarios: [
    _Scenario(
      name: 'Red Border',
      modes: [
        BorderMode(2, Colors.red), // [!code highlight]
      ]
    ),
  ],
)
```
