From a7c0253dd82fdaa14e49a8a3729d2a48f244e07b Mon Sep 17 00:00:00 2001 From: Kirill Bubochkin Date: Fri, 7 Jul 2023 13:34:40 +0200 Subject: [PATCH] feat!: add LayoutPlugin (#116) --- .../storybook_flutter/example/lib/main.dart | 7 -- .../lib/src/plugins/contents/contents.dart | 74 ++++++++------- .../lib/src/plugins/knobs.dart | 89 +++++++++--------- .../lib/src/plugins/layout.dart | 76 +++++++++++++++ .../lib/src/plugins/plugin.dart | 12 +-- .../lib/src/plugins/plugin_panel.dart | 14 ++- .../storybook_flutter/lib/src/storybook.dart | 8 +- .../storybook_flutter/test/golden_test.dart | 5 +- .../test/goldens/simple_story_layout.png | Bin 21761 -> 21904 bytes .../test/goldens/story_layout.png | Bin 22235 -> 22371 bytes 10 files changed, 181 insertions(+), 104 deletions(-) create mode 100644 packages/storybook_flutter/lib/src/plugins/layout.dart diff --git a/packages/storybook_flutter/example/lib/main.dart b/packages/storybook_flutter/example/lib/main.dart index 24497aeb..9bbbf984 100644 --- a/packages/storybook_flutter/example/lib/main.dart +++ b/packages/storybook_flutter/example/lib/main.dart @@ -4,19 +4,12 @@ import 'package:storybook_flutter_example/router_aware_stories.dart'; void main() => runApp(const MyApp()); -final _plugins = initializePlugins( - contentsSidePanel: true, - knobsSidePanel: true, - initialDeviceFrameData: defaultDeviceFrameData, -); - class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) => Storybook( initialStory: 'Screens/Scaffold', - plugins: _plugins, stories: [ ...routerAwareStories, Story( diff --git a/packages/storybook_flutter/lib/src/plugins/contents/contents.dart b/packages/storybook_flutter/lib/src/plugins/contents/contents.dart index edba9d8c..353a3777 100644 --- a/packages/storybook_flutter/lib/src/plugins/contents/contents.dart +++ b/packages/storybook_flutter/lib/src/plugins/contents/contents.dart @@ -10,51 +10,59 @@ import 'package:storybook_flutter/storybook_flutter.dart'; /// If `sidePanel` is true, the stories are shown in a left side panel, /// otherwise as a popup. class ContentsPlugin extends Plugin { - const ContentsPlugin({bool sidePanel = false}) + const ContentsPlugin() : super( - icon: sidePanel ? null : _buildIcon, - panelBuilder: sidePanel ? null : _buildPanel, - wrapperBuilder: sidePanel ? _buildWrapper : null, + icon: _buildIcon, + panelBuilder: _buildPanel, + wrapperBuilder: _buildWrapper, ); } -Widget _buildIcon(BuildContext _) => const Icon(Icons.list); +Widget? _buildIcon(BuildContext context) => + switch (context.watch()) { + EffectiveLayout.compact => const Icon(Icons.list), + EffectiveLayout.expanded => null, + }; Widget _buildPanel(BuildContext _) => const _Contents(); -Widget _buildWrapper(BuildContext _, Widget? child) => Localizations( - delegates: const [ - DefaultMaterialLocalizations.delegate, - DefaultCupertinoLocalizations.delegate, - DefaultWidgetsLocalizations.delegate, - ], - locale: const Locale('en', 'US'), - child: Directionality( - textDirection: TextDirection.ltr, - child: Row( - children: [ - Material( - child: DecoratedBox( - decoration: const BoxDecoration( - border: Border(right: BorderSide(color: Colors.black12)), - ), - child: SizedBox( - width: 250, - child: Navigator( - onGenerateRoute: (_) => PageRouteBuilder( - pageBuilder: (_, __, ___) => const _Contents(), +Widget _buildWrapper(BuildContext context, Widget? child) => + switch (context.watch()) { + EffectiveLayout.compact => child ?? const SizedBox.shrink(), + EffectiveLayout.expanded => Localizations( + delegates: const [ + DefaultMaterialLocalizations.delegate, + DefaultCupertinoLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + ], + locale: const Locale('en', 'US'), + child: Directionality( + textDirection: TextDirection.ltr, + child: Row( + children: [ + Material( + child: DecoratedBox( + decoration: const BoxDecoration( + border: Border(right: BorderSide(color: Colors.black12)), + ), + child: SizedBox( + width: 250, + child: Navigator( + onGenerateRoute: (_) => PageRouteBuilder( + pageBuilder: (_, __, ___) => const _Contents(), + ), + ), ), ), ), - ), - ), - Expanded( - child: ClipRect(clipBehavior: Clip.hardEdge, child: child), + Expanded( + child: ClipRect(clipBehavior: Clip.hardEdge, child: child), + ), + ], ), - ], + ), ), - ), - ); + }; class _Contents extends StatefulWidget { const _Contents(); diff --git a/packages/storybook_flutter/lib/src/plugins/knobs.dart b/packages/storybook_flutter/lib/src/plugins/knobs.dart index 3ef9e471..a8d79a7c 100644 --- a/packages/storybook_flutter/lib/src/plugins/knobs.dart +++ b/packages/storybook_flutter/lib/src/plugins/knobs.dart @@ -8,19 +8,19 @@ import 'package:storybook_flutter/src/story.dart'; /// /// If `sidePanel` is true, the knobs will be displayed in the right side panel. class KnobsPlugin extends Plugin { - KnobsPlugin({bool sidePanel = false}) + const KnobsPlugin() : super( - icon: sidePanel ? null : _buildIcon, - panelBuilder: sidePanel ? null : _buildPanel, - wrapperBuilder: (context, child) => _buildWrapper( - context, - child, - sidePanel: sidePanel, - ), + icon: _buildIcon, + panelBuilder: _buildPanel, + wrapperBuilder: _buildWrapper, ); } -Widget _buildIcon(BuildContext _) => const Icon(Icons.settings); +Widget? _buildIcon(BuildContext context) => + switch (context.watch()) { + EffectiveLayout.compact => const Icon(Icons.settings), + EffectiveLayout.expanded => null, + }; Widget _buildPanel(BuildContext context) { final knobs = context.watch(); @@ -40,42 +40,38 @@ Widget _buildPanel(BuildContext context) { ); } -Widget _buildWrapper( - BuildContext _, - Widget? child, { - required bool sidePanel, -}) => +Widget _buildWrapper(BuildContext context, Widget? child) => ChangeNotifierProvider( create: (context) => KnobsNotifier(context.read()), - child: sidePanel - ? Directionality( - textDirection: TextDirection.ltr, - child: Row( - children: [ - Expanded(child: child ?? const SizedBox.shrink()), - RepaintBoundary( - child: Material( - child: DecoratedBox( - decoration: const BoxDecoration( - border: Border( - left: BorderSide(color: Colors.black12), - ), + child: switch (context.watch()) { + EffectiveLayout.compact => child, + EffectiveLayout.expanded => Directionality( + textDirection: TextDirection.ltr, + child: Row( + children: [ + Expanded(child: child ?? const SizedBox.shrink()), + RepaintBoundary( + child: Material( + child: DecoratedBox( + decoration: const BoxDecoration( + border: Border( + left: BorderSide(color: Colors.black12), ), - child: SafeArea( - left: false, - child: SizedBox( - width: 250, - child: Localizations( - delegates: const [ - DefaultMaterialLocalizations.delegate, - DefaultWidgetsLocalizations.delegate, - ], - locale: const Locale('en', 'US'), - child: Navigator( - onGenerateRoute: (_) => PageRouteBuilder( - pageBuilder: (context, _, __) => - _buildPanel(context), - ), + ), + child: SafeArea( + left: false, + child: SizedBox( + width: 250, + child: Localizations( + delegates: const [ + DefaultMaterialLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + ], + locale: const Locale('en', 'US'), + child: Navigator( + onGenerateRoute: (_) => PageRouteBuilder( + pageBuilder: (context, _, __) => + _buildPanel(context), ), ), ), @@ -83,10 +79,11 @@ Widget _buildWrapper( ), ), ), - ], - ), - ) - : child, + ), + ], + ), + ), + }, ); extension Knobs on BuildContext { diff --git a/packages/storybook_flutter/lib/src/plugins/layout.dart b/packages/storybook_flutter/lib/src/plugins/layout.dart new file mode 100644 index 00000000..046c42b9 --- /dev/null +++ b/packages/storybook_flutter/lib/src/plugins/layout.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:storybook_flutter/storybook_flutter.dart'; + +enum Layout { compact, expanded, auto } + +enum EffectiveLayout { compact, expanded } + +class LayoutProvider extends ValueNotifier { + LayoutProvider(super.value); +} + +class LayoutPlugin extends Plugin { + LayoutPlugin(Layout initialLayout) + : super( + icon: _buildIcon, + wrapperBuilder: (context, child) => + _buildWrapper(context, child, initialLayout), + onPressed: _onPressed, + ); +} + +Widget _buildIcon(BuildContext context) => + switch (context.watch().value) { + Layout.auto => const Icon(Icons.view_carousel), + Layout.compact => const Icon(Icons.view_agenda), + Layout.expanded => const Icon(Icons.width_normal), + }; + +Widget _buildWrapper( + BuildContext _, + Widget? child, + Layout initialLayout, +) => + ChangeNotifierProvider( + create: (context) => LayoutProvider(initialLayout), + child: _EffectiveLayoutBuilder(child: child), + ); + +void _onPressed(BuildContext context) { + final layout = context.read(); + final position = Layout.values.indexOf(layout.value); + layout.value = Layout.values[(position + 1) % Layout.values.length]; +} + +class _EffectiveLayoutBuilder extends StatefulWidget { + const _EffectiveLayoutBuilder({required this.child}); + + final Widget? child; + + @override + State<_EffectiveLayoutBuilder> createState() => + _EffectiveLayoutBuilderState(); +} + +class _EffectiveLayoutBuilderState extends State<_EffectiveLayoutBuilder> { + late EffectiveLayout _layout; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final width = MediaQuery.sizeOf(context).width; + _layout = switch (context.watch().value) { + Layout.auto => + width < 800 ? EffectiveLayout.compact : EffectiveLayout.expanded, + Layout.compact => EffectiveLayout.compact, + Layout.expanded => EffectiveLayout.expanded, + }; + } + + @override + Widget build(BuildContext context) => Provider.value( + value: _layout, + child: widget.child, + ); +} diff --git a/packages/storybook_flutter/lib/src/plugins/plugin.dart b/packages/storybook_flutter/lib/src/plugins/plugin.dart index 6fabc07b..fd6899f7 100644 --- a/packages/storybook_flutter/lib/src/plugins/plugin.dart +++ b/packages/storybook_flutter/lib/src/plugins/plugin.dart @@ -1,33 +1,27 @@ import 'package:flutter/widgets.dart'; -import 'package:storybook_flutter/src/plugins/contents/contents.dart'; import 'package:storybook_flutter/src/plugins/device_frame.dart'; -import 'package:storybook_flutter/src/plugins/knobs.dart'; import 'package:storybook_flutter/src/plugins/theme_mode.dart'; export 'contents/contents.dart'; export 'device_frame.dart'; export 'knobs.dart'; +export 'layout.dart'; export 'theme_mode.dart'; /// Use this method to initialize and customize built-in plugins. List initializePlugins({ - bool enableContents = true, - bool enableKnobs = true, bool enableThemeMode = true, bool enableDeviceFrame = true, DeviceFrameData initialDeviceFrameData = defaultDeviceFrameData, - bool contentsSidePanel = false, - bool knobsSidePanel = false, }) => [ - if (enableContents) ContentsPlugin(sidePanel: contentsSidePanel), - if (enableKnobs) KnobsPlugin(sidePanel: knobsSidePanel), if (enableThemeMode) ThemeModePlugin(), if (enableDeviceFrame) DeviceFramePlugin(initialData: initialDeviceFrameData), ]; typedef OnPluginButtonPressed = void Function(BuildContext context); +typedef NullableWidgetBuilder = Widget? Function(BuildContext context); class Plugin { const Plugin({ @@ -56,7 +50,7 @@ class Plugin { final TransitionBuilder? storyBuilder; /// Optional icon that will be displayed on the bottom panel. - final WidgetBuilder? icon; + final NullableWidgetBuilder? icon; /// Optional callback that will be called when user clicks on the [icon]. /// diff --git a/packages/storybook_flutter/lib/src/plugins/plugin_panel.dart b/packages/storybook_flutter/lib/src/plugins/plugin_panel.dart index ae252406..302a352f 100644 --- a/packages/storybook_flutter/lib/src/plugins/plugin_panel.dart +++ b/packages/storybook_flutter/lib/src/plugins/plugin_panel.dart @@ -22,6 +22,12 @@ class PluginPanel extends StatefulWidget { class _PluginPanelState extends State { PluginOverlay? _overlay; + @override + void dispose() { + _overlay?.remove(); + super.dispose(); + } + OverlayEntry _createEntry(WidgetBuilder childBuilder) => OverlayEntry( builder: (context) => Provider( create: (context) => OverlayController( @@ -98,12 +104,12 @@ class _PluginPanelState extends State { Widget build(BuildContext context) => Wrap( runAlignment: WrapAlignment.center, children: widget.plugins - .where((p) => p.icon != null) + .map((p) => (p, p.icon?.call(context))) + .whereType<(Plugin, Widget)>() .map( (p) => IconButton( - // ignore: avoid-non-null-assertion, checked for null - icon: p.icon!.call(context), - onPressed: () => _onPluginButtonPressed(p), + icon: p.$2, + onPressed: () => _onPluginButtonPressed(p.$1), ), ) .toList(), diff --git a/packages/storybook_flutter/lib/src/storybook.dart b/packages/storybook_flutter/lib/src/storybook.dart index c029ef57..2323d810 100644 --- a/packages/storybook_flutter/lib/src/storybook.dart +++ b/packages/storybook_flutter/lib/src/storybook.dart @@ -31,7 +31,13 @@ class Storybook extends StatefulWidget { this.initialStory, this.wrapperBuilder = materialWrapper, this.showPanel = true, - }) : plugins = UnmodifiableListView(plugins ?? _defaultPlugins), + Layout initialLayout = Layout.auto, + }) : plugins = UnmodifiableListView([ + LayoutPlugin(initialLayout), + const ContentsPlugin(), + const KnobsPlugin(), + ...plugins ?? _defaultPlugins, + ]), stories = UnmodifiableListView(stories); /// All enabled plugins. diff --git a/packages/storybook_flutter/test/golden_test.dart b/packages/storybook_flutter/test/golden_test.dart index dfd7517f..8d943e49 100644 --- a/packages/storybook_flutter/test/golden_test.dart +++ b/packages/storybook_flutter/test/golden_test.dart @@ -3,10 +3,7 @@ import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:storybook_flutter/storybook_flutter.dart'; Widget simpleStorybook(String initialStory) => Storybook( - plugins: initializePlugins( - contentsSidePanel: true, - knobsSidePanel: true, - ), + plugins: initializePlugins(), initialStory: initialStory, stories: [ Story( diff --git a/packages/storybook_flutter/test/goldens/simple_story_layout.png b/packages/storybook_flutter/test/goldens/simple_story_layout.png index 6e74e6665a6771e03d2338563a982c227f338aa4..66f3625cf20c4774768e40e76eec0028ea103c28 100644 GIT binary patch delta 4270 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 U170Ou*Pr_C^Z$+Z>#>Xf2l&0wdjJ3c delta 4120 zcmXw63s{n8`=@NVzEZcj(rM|pS+>4X%QDLoR$n*cwx`+n~G_xs)V^YDngEs3

%rx>%(9p1yktEQ=$z>fBzbj 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 VP`=)g+^Jf1yy)`Z%JTvD{|CXldd>g< diff --git a/packages/storybook_flutter/test/goldens/story_layout.png b/packages/storybook_flutter/test/goldens/story_layout.png index 0031e0cd7af7920032267f5d26ed34e1bd73b68b..3e53810ac09e5572a9be1c6285701ec7c59f3679 100644 GIT binary patch delta 1661 zcmYjReK?bA9DX-Rr!SpON3K&*Q5?mTuu4rI(kQ}W8<|oTYTKbWKJtpb^hH~u3$0E? zVwklv_C-;bgoPumnMtUr8SN}bY|iu6KfTv;UGH`Mp69-w-*eyh^C&o|cNM5Ak|#>f zBYWB{fE5qFI;Z0jmj7Wv$P%0Qnyn|O&k)WKvWQ;4?l?H>=E`O>?mU;cDbxJ?P4}K} zpJ9*6GTnIA^KAagwYEedgnoEo6}RiPHhp;9HF}574qC@lTBl@uDqCN7anW;szcv&E z*+%yCoBTCuL}P7GZoEm8>R0J&DD z(@n=w0l2;abXX1hrqcukQM{YExw(Y0E(eb`YTM}NW4aKJ{{Ch4jH1tKMKdy*+ zD`{w-9-bQWe)a0rj7NZB-*S{AV@N+~XlZHjV3d0ADiTi&6`T+Enj8^nqmCcfP)240JQiq<4K$?TFm+R(L6b`1{y!fi<;dmo4F#=x z_U>JUw^+P5sIr^KoZ{(SD9~t>2&>Kj3uxC*Y* z$_*Ox;^JcXvvE?JefdBQ<1O(kunD@g2TktdhDlR5Din%_Nxu@^ZXG6Ej1_N990`*e zzkam&sIi~=x6{8iZqSE0pp)Tb@v{MoKcg5fvek_SXv6i>3n22Y;8IT#G6B84& zOQHd;FKfkZ)3$P;e{dO)AJPBdz7!R(Ehj-#6cV1aDx@L|FH*K;Yf!p{K&9t(j1tM$o}M`RAL?MOm`TWgd{P*9MKD+Nv+^I{Hk-1n_Lq3&qnO#(^I zto(TlSwYfxIlOt^GN{zI7^DYk@mr5@?UgKIMk5=JRto91d*zJd`eR zU7r{my9Og3-Od~ksM37zw(kb#(SPg?D(eEbAx(@yI19-pY>1TE^Qaqe3$0>e8iOrp+>n2=26=rYlR_=O381 z$E`sm{Q#P`+3%F68_c6vD4$)cff=v+`wz{`e4J%Y1^cFVdV_=|4aRLO@}mxr1d6>p zc}sJ%4^V4n<~BDsx9X~@oLzUhbeX)a!q(|YOUoV_tQ5M6aUj}m?(W_(P84aOMx%La zczS7ZHU|ebjliKCJ-?WhvskR8NsS=LDKB0()N1?uyElQQgo{VV=n~{wpfq2;Bt%6; z%`eowiLEp!jw4E#EqICc*m*w?f4AN8oPbY0u4$4Zw(J?t1(z0PfJ|%R-}|+(ag-BF?1C%1eVLcD z*|$_@4QbQ$W2~xGAR>h)rI#MF=yCi!3PrPw7-{NQufpzq{OJ^$2sp9JQZo_mA761+ zV1PaH4m)2T>6L88KIp@-uULm*kX4ZXb%X_Lq|EpwxnY)lbP`g;d-?6G*dCGbKmJ@K Ab^rhX delta 1577 zcmZ`(4=~hu6t^N*97?k!lC%B{l@skce?ouMI-zW2IU8M8(Yv(~OO*N!?IlC+^a zE!D;{YwawHt;Qh#BR|{9pYty(tynA8+WY>xxtp82xtTZf=KJ3Jym_DZeeX?HBg9pR znh;M!90%`dtxsdPL0_wlIaE#VKW8~ocul9>_Y|(sD3W{BG@9-dsIj3hPN_CgAu9ZI z%tMU1=Z-StZ`|B&UU3S!bN~JfynZD1%C+>0DU)N2>tA#jKL7EweD%ntOmNF`j4Gq5 zsi|r1=u-zuZP#&6ZCD5BBM!jY)9Z+B17iGwP3q#fSN~@dB~hc!GkZZOHGwbFFr`SnQ?gB0`0DVP;dQxOg~JPd5~gr}3vD#~4jj62vNu=P|Sm4?gUVei(L8 zDvg4R*dCdQOpPrlD7cJf6ql9x^Tek)qLJ`%jL?*O+cPjSLak&VmTsTo|x9X zu|YA;_O`ONUcLG!kDY+W*~hq7G2HwOOzPX=;r2Ig@I9sOjLEj#N}MedXM2fEb|Lnb zb^|*ajm}3wY=VD&9li=@TNsIknFZ*dG*N4mO9tl$2XR!z0uPc{Sy?5mElpz@w18ho z7i#>uvotT?K6U9}mVtFW0h(W_)uJnvW> zX{sY_M-O}f-eU~vgwmsaW-T{)XNu%L;}j%ArGZ2@WTk7b8%s|j8-abR?rS|I2^^GDL^I~opu8=Ru}k; z*iyuC2p(lqGt}Ku7x%U0n2I?Hy{*?#SpLQ)r3h@lDFilO2Q*R<$EMvvv@-C!r`T*Q z*P*-MB0H1@w&^j{JGA5fgSE}M(4%Z2Dj{{ZRR-