Storybook + Flutter = storybook_flutter

Hello everyone! In this article I will shamelessly talk about my library for Flutter, which allows you to create stories from isolated widgets and / or screens. Something like a Storybook from the React world. Actually, it is called that: storybook_flutter .





Why is it needed?

First, it's faster to make UI. Of course, Flutter already has a hot reload, but if the widget is buried somewhere in the wilds of the application, then you still need to get to it. And if this widget is shown only under certain conditions, then these conditions must be reproduced. Also, hot reload does not work in all cases. Therefore, it is more convenient to isolate the widget, put it in a separate story, and work with this story. In this case, you will have to think about how to remove unnecessary dependencies from this widget, so that the code will ultimately be cleaner.





Secondly, a demo of widgets / screens. For example, we are making our own design library for Flutter, and we would like to embed an interactive sandbox with widgets in the documentation, especially since Flutter for Web is already in the stable branch.





Thirdly, in the future I want to add (I was prompted for this idea in the issues) the ability to automatically generate golden tests for widgets with different combinations of parameters.





Maybe take something ready?

Probably you can. But, firstly, there is no clear favorite in the community yet. Secondly, no one has canceled the NIH syndrome. Third, I want to be able to quickly add the features that we need.





How she looks like?

Something like this:





Not very elegant, but appearance is not a priority yet. And the designer from me is so-so. In addition, I am still experimenting with the arrangement of toolbars, buttons and menus, so there is no point in polishing the design.





What can she do?

  • Navigating stories by category.





  • Parameters (knobs) of widgets.





  • Light / dark theme switch.





  • ( iframe).





  • .





  • ( device_frame) – .





  • – .





?

pubspec.yaml



( -, ):





storybook_flutter: ^0.5.0-dev.0
      
      



( ). - :





import 'package:flutter/material.dart';
import 'package:storybook_flutter/storybook_flutter.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => Storybook(
        children: [
          Story.simple(
            name: 'Button',
            child: ElevatedButton(
              onPressed: () {},
              child: const Text('Push me'),
            ),
          ),
        ],
      );
}

      
      



, , :





. simple



, builder



child



:





Story(
  name: 'Button',
  builder: (context, k) => ElevatedButton(
    onPressed:
        k.boolean(label: 'Enabled', initial: true) ? () {} : null,
    child: Text(k.text(label: 'Text', initial: 'Push me')),
  ),
),
      
      



, :





, section



:





Story(
  name: 'Button',
  section: 'Buttons',
  builder: (context, k) => ElevatedButton(
    onPressed:
        k.boolean(label: 'Enabled', initial: true) ? () {} : null,
    child: Text(k.text(label: 'Text', initial: 'Push me')),
  ),
),
      
      



section



.





?

Story



padding



background



, , :





Story(
  name: 'Button',
  section: 'Buttons',
  padding: const EdgeInsets.all(8),
  background: Colors.red,
  builder: (context, k) => ElevatedButton(
    onPressed:
        k.boolean(label: 'Enabled', initial: true) ? () {} : null,
    child: Text(k.text(label: 'Text', initial: 'Push me')),
  ),
),
      
      



. wrapperBuilder



Story



, :





Story(
  name: 'Button',
  section: 'Buttons',
  wrapperBuilder: (context, story, child) => Container(
    decoration: BoxDecoration(border: Border.all()),
    margin: const EdgeInsets.all(16),
    child: Center(child: child),
  ),
  builder: (context, k) => ElevatedButton(
    onPressed:
        k.boolean(label: 'Enabled', initial: true) ? () {} : null,
    child: Text(k.text(label: 'Text', initial: 'Push me')),
  ),
),
      
      



storyWrapperBuilder



Storybook



, .





!

, , CustomStorybook



:





class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final decoration = BoxDecoration(
      border: Border(
        right: BorderSide(color: Theme.of(context).dividerColor),
        left: BorderSide(color: Theme.of(context).dividerColor),
      ),
      color: Theme.of(context).cardColor,
    );
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: CustomStorybook(
          builder: (context) => Row(
            children: [
              Container(
                width: 200,
                decoration: decoration,
                child: const Contents(),
              ),
              const Expanded(child: CurrentStory()),
              Container(
                width: 200,
                decoration: decoration,
                child: const KnobPanel(),
              ),
            ],
          ),
          children: [
            Story(
              name: 'Button',
              builder: (context, k) => ElevatedButton(
                onPressed:
                    k.boolean(label: 'Enabled', initial: true) ? () {} : null,
                child: Text(k.text(label: 'Text', initial: 'Push me')),
              ),
            )
          ],
        ),
      ),
    );
  }
}
      
      



Contents



, CurrentStory



KnobPanel



(, , ). :





CustomStorybook



, Storybook , device_preview, . :





?

, 1st party : DeviceFramePlugin



:





, .





, , .





?

, , Flutter'. Android, iOS, Web macOS.





?

Further in the plans - to install the plugin API, think about what plugins are still needed out of the box (well, write them).





Then, most likely, I will start generating tests, which I wrote about at the beginning of the article.






That's all. I would be glad to receive comments, suggestions and bug reports (well, likes / stars, of course, to be honest).








All Articles