Skip to content

Commit

Permalink
feat!: add LayoutPlugin (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
ookami-kb authored Jul 7, 2023
1 parent 97bcd97 commit a7c0253
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 104 deletions.
7 changes: 0 additions & 7 deletions packages/storybook_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
74 changes: 41 additions & 33 deletions packages/storybook_flutter/lib/src/plugins/contents/contents.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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>()) {
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<void>(
pageBuilder: (_, __, ___) => const _Contents(),
Widget _buildWrapper(BuildContext context, Widget? child) =>
switch (context.watch<EffectiveLayout>()) {
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<void>(
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();
Expand Down
89 changes: 43 additions & 46 deletions packages/storybook_flutter/lib/src/plugins/knobs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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>()) {
EffectiveLayout.compact => const Icon(Icons.settings),
EffectiveLayout.expanded => null,
};

Widget _buildPanel(BuildContext context) {
final knobs = context.watch<KnobsNotifier>();
Expand All @@ -40,53 +40,50 @@ Widget _buildPanel(BuildContext context) {
);
}

Widget _buildWrapper(
BuildContext _,
Widget? child, {
required bool sidePanel,
}) =>
Widget _buildWrapper(BuildContext context, Widget? child) =>
ChangeNotifierProvider(
create: (context) => KnobsNotifier(context.read<StoryNotifier>()),
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>()) {
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<void>(
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<void>(
pageBuilder: (context, _, __) =>
_buildPanel(context),
),
),
),
),
),
),
),
],
),
)
: child,
),
],
),
),
},
);

extension Knobs on BuildContext {
Expand Down
76 changes: 76 additions & 0 deletions packages/storybook_flutter/lib/src/plugins/layout.dart
Original file line number Diff line number Diff line change
@@ -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<Layout> {
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<LayoutProvider>().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<LayoutProvider>();
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<LayoutProvider>().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,
);
}
12 changes: 3 additions & 9 deletions packages/storybook_flutter/lib/src/plugins/plugin.dart
Original file line number Diff line number Diff line change
@@ -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<Plugin> 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({
Expand Down Expand Up @@ -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].
///
Expand Down
14 changes: 10 additions & 4 deletions packages/storybook_flutter/lib/src/plugins/plugin_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class PluginPanel extends StatefulWidget {
class _PluginPanelState extends State<PluginPanel> {
PluginOverlay? _overlay;

@override
void dispose() {
_overlay?.remove();
super.dispose();
}

OverlayEntry _createEntry(WidgetBuilder childBuilder) => OverlayEntry(
builder: (context) => Provider<OverlayController>(
create: (context) => OverlayController(
Expand Down Expand Up @@ -98,12 +104,12 @@ class _PluginPanelState extends State<PluginPanel> {
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(),
Expand Down
8 changes: 7 additions & 1 deletion packages/storybook_flutter/lib/src/storybook.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit a7c0253

Please sign in to comment.