From df73463e843fa95130807d03d1634500e5376288 Mon Sep 17 00:00:00 2001 From: Harry Sild <46851868+Kypsis@users.noreply.github.com> Date: Mon, 10 Jul 2023 12:03:09 +0300 Subject: [PATCH] feat: [MDS-590] Create Drawer widget (#204) --- example/lib/src/storybook/stories/drawer.dart | 101 +++++++++++++++++ example/lib/src/storybook/storybook.dart | 2 + lib/moon_design.dart | 2 + lib/src/theme/drawer/drawer_colors.dart | 81 ++++++++++++++ lib/src/theme/drawer/drawer_properties.dart | 64 +++++++++++ lib/src/theme/drawer/drawer_shadows.dart | 46 ++++++++ lib/src/theme/drawer/drawer_theme.dart | 70 ++++++++++++ lib/src/theme/theme.dart | 11 ++ lib/src/widgets/drawer/drawer.dart | 102 ++++++++++++++++++ 9 files changed, 479 insertions(+) create mode 100644 example/lib/src/storybook/stories/drawer.dart create mode 100644 lib/src/theme/drawer/drawer_colors.dart create mode 100644 lib/src/theme/drawer/drawer_properties.dart create mode 100644 lib/src/theme/drawer/drawer_shadows.dart create mode 100644 lib/src/theme/drawer/drawer_theme.dart create mode 100644 lib/src/widgets/drawer/drawer.dart diff --git a/example/lib/src/storybook/stories/drawer.dart b/example/lib/src/storybook/stories/drawer.dart new file mode 100644 index 00000000..664da0ff --- /dev/null +++ b/example/lib/src/storybook/stories/drawer.dart @@ -0,0 +1,101 @@ +import 'package:example/src/storybook/common/color_options.dart'; +import 'package:flutter/material.dart'; +import 'package:moon_design/moon_design.dart'; +import 'package:storybook_flutter/storybook_flutter.dart'; + +class DrawerStory extends Story { + DrawerStory() + : super( + name: "Drawer", + builder: (context) { + final backgroundColorsKnob = context.knobs.nullable.options( + label: "backgroundColor", + description: "MoonColors variants for MoonDrawer background.", + enabled: false, + initial: 0, // piccolo + options: colorOptions, + ); + + final backgroundColor = colorTable(context)[backgroundColorsKnob ?? 40]; + + final barrierColorsKnob = context.knobs.nullable.options( + label: "barrierColor", + description: "MoonColors variants for MoonDrawer barrier.", + enabled: false, + initial: 0, // piccolo + options: colorOptions, + ); + + final barrierColor = colorTable(context)[barrierColorsKnob ?? 40]; + + final borderRadiusKnob = context.knobs.nullable.sliderInt( + label: "borderRadius", + description: "Border radius for MoonDrawer.", + enabled: false, + initial: 8, + max: 32, + ); + + final drawerWidthKnob = context.knobs.nullable.sliderInt( + label: "width", + description: "The width of the MoonDrawer", + enabled: false, + initial: 200, + max: MediaQuery.of(context).size.width.round(), + ); + + return OverflowBox( + maxHeight: MediaQuery.of(context).size.height, + maxWidth: MediaQuery.of(context).size.width, + child: Scaffold( + drawerScrimColor: barrierColor, + drawer: MoonDrawer( + backgroundColor: backgroundColor, + barrierColor: barrierColorsKnob != null + ? barrierColor + : context.moonTheme?.drawerTheme.colors.barrierColor ?? MoonColors.light.zeno, + borderRadius: BorderRadiusDirectional.only( + topEnd: Radius.circular(borderRadiusKnob?.toDouble() ?? 0), + bottomEnd: Radius.circular(borderRadiusKnob?.toDouble() ?? 0), + ), + width: drawerWidthKnob?.toDouble() ?? MediaQuery.of(context).size.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 64), + const Text("MoonDrawer"), + const SizedBox(height: 32), + Builder( + builder: (context) { + return MoonFilledButton( + label: const Text("Close"), + onTap: () => Navigator.of(context).pop(), + ); + }, + ), + const SizedBox(height: 64), + ], + ), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 64), + Builder( + builder: (context) { + return MoonFilledButton( + label: const Text("Tap me"), + onTap: () => Scaffold.of(context).openDrawer(), + ); + }, + ), + const SizedBox(height: 64), + ], + ), + ), + ), + ); + }, + ); +} diff --git a/example/lib/src/storybook/storybook.dart b/example/lib/src/storybook/storybook.dart index 8f4bc793..9f3e10a7 100644 --- a/example/lib/src/storybook/storybook.dart +++ b/example/lib/src/storybook/storybook.dart @@ -11,6 +11,7 @@ import 'package:example/src/storybook/stories/chip.dart'; import 'package:example/src/storybook/stories/circular_loader.dart'; import 'package:example/src/storybook/stories/circular_progress.dart'; import 'package:example/src/storybook/stories/dot_indicator.dart'; +import 'package:example/src/storybook/stories/drawer.dart'; import 'package:example/src/storybook/stories/icons.dart'; import 'package:example/src/storybook/stories/linear_loader.dart'; import 'package:example/src/storybook/stories/linear_progress.dart'; @@ -95,6 +96,7 @@ class StorybookPage extends StatelessWidget { CircularLoaderStory(), CircularProgressStory(), DotIndicatorStory(), + DrawerStory(), IconsStory(), LinearLoaderStory(), LinearProgressStory(), diff --git a/lib/moon_design.dart b/lib/moon_design.dart index e773b22e..e51bd0b7 100644 --- a/lib/moon_design.dart +++ b/lib/moon_design.dart @@ -13,6 +13,7 @@ export 'package:moon_design/src/theme/checkbox/checkbox_theme.dart'; export 'package:moon_design/src/theme/chip/chip_theme.dart'; export 'package:moon_design/src/theme/colors.dart'; export 'package:moon_design/src/theme/dot_indicator/dot_indicator_theme.dart'; +export 'package:moon_design/src/theme/drawer/drawer_theme.dart'; export 'package:moon_design/src/theme/effects/effects.dart'; export 'package:moon_design/src/theme/icons/icon_theme.dart'; export 'package:moon_design/src/theme/loaders/circular_loader/circular_loader_theme.dart'; @@ -70,6 +71,7 @@ export 'package:moon_design/src/widgets/common/icons/moon_icon.dart'; export 'package:moon_design/src/widgets/common/progress_indicators/circular_progress_indicator.dart'; export 'package:moon_design/src/widgets/common/progress_indicators/linear_progress_indicator.dart'; export 'package:moon_design/src/widgets/dot_indicator/dot_indicator.dart'; +export 'package:moon_design/src/widgets/drawer/drawer.dart'; export 'package:moon_design/src/widgets/loaders/circular_loader.dart'; export 'package:moon_design/src/widgets/loaders/linear_loader.dart'; export 'package:moon_design/src/widgets/modal/modal.dart'; diff --git a/lib/src/theme/drawer/drawer_colors.dart b/lib/src/theme/drawer/drawer_colors.dart new file mode 100644 index 00000000..43557c11 --- /dev/null +++ b/lib/src/theme/drawer/drawer_colors.dart @@ -0,0 +1,81 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/icons/icon_theme.dart'; +import 'package:moon_design/src/theme/typography/typography.dart'; +import 'package:moon_design/src/utils/color_premul_lerp.dart'; + +@immutable +class MoonDrawerColors extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonDrawerColors( + textColor: MoonTypography.light.colors.bodyPrimary, + iconColor: MoonIconTheme.light.colors.primaryColor, + backgroundColor: MoonColors.light.gohan, + barrierColor: MoonColors.light.zeno, + ); + + static final dark = MoonDrawerColors( + textColor: MoonTypography.dark.colors.bodyPrimary, + iconColor: MoonIconTheme.dark.colors.primaryColor, + backgroundColor: MoonColors.dark.gohan, + barrierColor: MoonColors.dark.zeno, + ); + + /// Drawer text color. + final Color textColor; + + /// Drawer icon color. + final Color iconColor; + + /// Drawer background color. + final Color backgroundColor; + + /// Drawer barrier color. + final Color barrierColor; + + const MoonDrawerColors({ + required this.textColor, + required this.iconColor, + required this.backgroundColor, + required this.barrierColor, + }); + + @override + MoonDrawerColors copyWith({ + Color? textColor, + Color? iconColor, + Color? backgroundColor, + Color? barrierColor, + }) { + return MoonDrawerColors( + textColor: textColor ?? this.textColor, + iconColor: iconColor ?? this.iconColor, + backgroundColor: backgroundColor ?? this.backgroundColor, + barrierColor: barrierColor ?? this.barrierColor, + ); + } + + @override + MoonDrawerColors lerp(ThemeExtension? other, double t) { + if (other is! MoonDrawerColors) return this; + + return MoonDrawerColors( + textColor: colorPremulLerp(textColor, other.textColor, t)!, + iconColor: colorPremulLerp(iconColor, other.iconColor, t)!, + backgroundColor: colorPremulLerp(backgroundColor, other.backgroundColor, t)!, + barrierColor: colorPremulLerp(barrierColor, other.barrierColor, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonDrawerColors")) + ..add(ColorProperty("textColor", textColor)) + ..add(ColorProperty("iconColor", iconColor)) + ..add(ColorProperty("backgroundColor", backgroundColor)) + ..add(ColorProperty("barrierColor", barrierColor)); + } +} diff --git a/lib/src/theme/drawer/drawer_properties.dart b/lib/src/theme/drawer/drawer_properties.dart new file mode 100644 index 00000000..e8bd1f93 --- /dev/null +++ b/lib/src/theme/drawer/drawer_properties.dart @@ -0,0 +1,64 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/typography/text_styles.dart'; + +@immutable +class MoonDrawerProperties extends ThemeExtension with DiagnosticableTreeMixin { + static final properties = MoonDrawerProperties( + borderRadius: BorderRadius.zero, + width: 448, + textStyle: MoonTextStyles.body.textDefault, + ); + + /// Drawer border radius. + final BorderRadiusGeometry borderRadius; + + /// Drawer width. + final double width; + + /// Drawer text style. + final TextStyle textStyle; + + const MoonDrawerProperties({ + required this.borderRadius, + required this.width, + required this.textStyle, + }); + + @override + MoonDrawerProperties copyWith({ + BorderRadiusGeometry? borderRadius, + double? width, + TextStyle? textStyle, + }) { + return MoonDrawerProperties( + borderRadius: borderRadius ?? this.borderRadius, + width: width ?? this.width, + textStyle: textStyle ?? this.textStyle, + ); + } + + @override + MoonDrawerProperties lerp(ThemeExtension? other, double t) { + if (other is! MoonDrawerProperties) return this; + + return MoonDrawerProperties( + borderRadius: BorderRadiusGeometry.lerp(borderRadius, other.borderRadius, t)!, + width: lerpDouble(width, other.width, t)!, + textStyle: TextStyle.lerp(textStyle, other.textStyle, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonDrawerProperties")) + ..add(DiagnosticsProperty("borderRadius", borderRadius)) + ..add(DoubleProperty("width", width)) + ..add(DiagnosticsProperty("textStyle", textStyle)); + } +} diff --git a/lib/src/theme/drawer/drawer_shadows.dart b/lib/src/theme/drawer/drawer_shadows.dart new file mode 100644 index 00000000..aa42511f --- /dev/null +++ b/lib/src/theme/drawer/drawer_shadows.dart @@ -0,0 +1,46 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/shadows.dart'; + +@immutable +class MoonDrawerShadows extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonDrawerShadows( + drawerShadows: MoonShadows.light.lg, + ); + + static final dark = MoonDrawerShadows( + drawerShadows: MoonShadows.dark.lg, + ); + + /// Drawer shadows. + final List drawerShadows; + + const MoonDrawerShadows({ + required this.drawerShadows, + }); + + @override + MoonDrawerShadows copyWith({List? drawerShadows}) { + return MoonDrawerShadows( + drawerShadows: drawerShadows ?? this.drawerShadows, + ); + } + + @override + MoonDrawerShadows lerp(ThemeExtension? other, double t) { + if (other is! MoonDrawerShadows) return this; + + return MoonDrawerShadows( + drawerShadows: BoxShadow.lerpList(drawerShadows, other.drawerShadows, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonDrawerShadows")) + ..add(DiagnosticsProperty>("drawerShadows", drawerShadows)); + } +} diff --git a/lib/src/theme/drawer/drawer_theme.dart b/lib/src/theme/drawer/drawer_theme.dart new file mode 100644 index 00000000..4271a655 --- /dev/null +++ b/lib/src/theme/drawer/drawer_theme.dart @@ -0,0 +1,70 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/drawer/drawer_colors.dart'; +import 'package:moon_design/src/theme/drawer/drawer_properties.dart'; +import 'package:moon_design/src/theme/drawer/drawer_shadows.dart'; + +@immutable +class MoonDrawerTheme extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonDrawerTheme( + colors: MoonDrawerColors.light, + properties: MoonDrawerProperties.properties, + shadows: MoonDrawerShadows.light, + ); + + static final dark = MoonDrawerTheme( + colors: MoonDrawerColors.dark, + properties: MoonDrawerProperties.properties, + shadows: MoonDrawerShadows.dark, + ); + + /// Drawer colors. + final MoonDrawerColors colors; + + /// Drawer properties. + final MoonDrawerProperties properties; + + /// Drawer shadows. + final MoonDrawerShadows shadows; + + const MoonDrawerTheme({ + required this.colors, + required this.properties, + required this.shadows, + }); + + @override + MoonDrawerTheme copyWith({ + MoonDrawerColors? colors, + MoonDrawerProperties? properties, + MoonDrawerShadows? shadows, + }) { + return MoonDrawerTheme( + colors: colors ?? this.colors, + properties: properties ?? this.properties, + shadows: shadows ?? this.shadows, + ); + } + + @override + MoonDrawerTheme lerp(ThemeExtension? other, double t) { + if (other is! MoonDrawerTheme) return this; + + return MoonDrawerTheme( + colors: colors.lerp(other.colors, t), + properties: properties.lerp(other.properties, t), + shadows: shadows.lerp(other.shadows, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder diagnosticProperties) { + super.debugFillProperties(diagnosticProperties); + diagnosticProperties + ..add(DiagnosticsProperty("type", "MoonDrawerTheme")) + ..add(DiagnosticsProperty("colors", colors)) + ..add(DiagnosticsProperty("properties", properties)) + ..add(DiagnosticsProperty("shadows", shadows)); + } +} diff --git a/lib/src/theme/theme.dart b/lib/src/theme/theme.dart index 70f51809..1f6d2a15 100644 --- a/lib/src/theme/theme.dart +++ b/lib/src/theme/theme.dart @@ -13,6 +13,7 @@ import 'package:moon_design/src/theme/checkbox/checkbox_theme.dart'; import 'package:moon_design/src/theme/chip/chip_theme.dart'; import 'package:moon_design/src/theme/colors.dart'; import 'package:moon_design/src/theme/dot_indicator/dot_indicator_theme.dart'; +import 'package:moon_design/src/theme/drawer/drawer_theme.dart'; import 'package:moon_design/src/theme/effects/effects.dart'; import 'package:moon_design/src/theme/icons/icon_theme.dart'; import 'package:moon_design/src/theme/loaders/circular_loader/circular_loader_theme.dart'; @@ -52,6 +53,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { circularProgressTheme: MoonCircularProgressTheme.light, colors: MoonColors.light, dotIndicatorTheme: MoonDotIndicatorTheme.light, + drawerTheme: MoonDrawerTheme.light, effects: MoonEffects.light, iconTheme: MoonIconTheme.light, linearLoaderTheme: MoonLinearLoaderTheme.light, @@ -88,6 +90,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { circularProgressTheme: MoonCircularProgressTheme.dark, colors: MoonColors.dark, dotIndicatorTheme: MoonDotIndicatorTheme.dark, + drawerTheme: MoonDrawerTheme.dark, effects: MoonEffects.dark, iconTheme: MoonIconTheme.dark, linearLoaderTheme: MoonLinearLoaderTheme.dark, @@ -151,6 +154,9 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { /// Moon Design System MoonDotIndicator widget theming. final MoonDotIndicatorTheme dotIndicatorTheme; + /// Moon Design System MoonDrawer widget theming. + final MoonDrawerTheme drawerTheme; + /// Moon Design System effects. final MoonEffects effects; @@ -223,6 +229,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { required this.circularProgressTheme, required this.colors, required this.dotIndicatorTheme, + required this.drawerTheme, required this.effects, required this.iconTheme, required this.linearLoaderTheme, @@ -260,6 +267,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { MoonCircularProgressTheme? circularProgressTheme, MoonColors? colors, MoonDotIndicatorTheme? dotIndicatorTheme, + MoonDrawerTheme? drawerTheme, MoonEffects? effects, MoonIconTheme? iconTheme, MoonLinearLoaderTheme? linearLoaderTheme, @@ -295,6 +303,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { circularProgressTheme: circularProgressTheme ?? this.circularProgressTheme, colors: colors ?? this.colors, dotIndicatorTheme: dotIndicatorTheme ?? this.dotIndicatorTheme, + drawerTheme: drawerTheme ?? this.drawerTheme, effects: effects ?? this.effects, iconTheme: iconTheme ?? this.iconTheme, linearLoaderTheme: linearLoaderTheme ?? this.linearLoaderTheme, @@ -336,6 +345,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { circularProgressTheme: circularProgressTheme.lerp(other.circularProgressTheme, t), colors: colors.lerp(other.colors, t), dotIndicatorTheme: dotIndicatorTheme.lerp(other.dotIndicatorTheme, t), + drawerTheme: drawerTheme.lerp(other.drawerTheme, t), effects: effects.lerp(other.effects, t), iconTheme: iconTheme.lerp(other.iconTheme, t), linearLoaderTheme: linearLoaderTheme.lerp(other.linearLoaderTheme, t), @@ -376,6 +386,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { ..add(DiagnosticsProperty("MoonCircularProgressTheme", circularProgressTheme)) ..add(DiagnosticsProperty("MoonColors", colors)) ..add(DiagnosticsProperty("MoonDotIndicatorTheme", dotIndicatorTheme)) + ..add(DiagnosticsProperty("MoonDrawerTheme", drawerTheme)) ..add(DiagnosticsProperty("MoonEffects", effects)) ..add(DiagnosticsProperty("MoonIconTheme", iconTheme)) ..add(DiagnosticsProperty("MoonLinearLoaderTheme", linearLoaderTheme)) diff --git a/lib/src/widgets/drawer/drawer.dart b/lib/src/widgets/drawer/drawer.dart new file mode 100644 index 00000000..f0689366 --- /dev/null +++ b/lib/src/widgets/drawer/drawer.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/icons/icon_theme.dart'; +import 'package:moon_design/src/theme/shadows.dart'; +import 'package:moon_design/src/theme/theme.dart'; +import 'package:moon_design/src/theme/typography/text_styles.dart'; +import 'package:moon_design/src/theme/typography/typography.dart'; +import 'package:moon_design/src/utils/extensions.dart'; +import 'package:moon_design/src/utils/shape_decoration_premul.dart'; +import 'package:moon_design/src/utils/squircle/squircle_border.dart'; + +class MoonDrawer extends StatelessWidget { + /// The border radius of the Drawer. + final BorderRadiusGeometry? borderRadius; + + /// The background color of the Drawer. + final Color? backgroundColor; + + /// The color of the Drawer barrier. + final Color? barrierColor; + + /// Custom decoration for the Drawer. + final Decoration? decoration; + + /// The width of the Drawer. + final double? width; + + /// List of Drawer shadows. + final List? drawerShadows; + + /// The semantic label for the Drawer. + final String? semanticLabel; + + /// The child of the Drawer. + final Widget child; + + const MoonDrawer({ + super.key, + this.borderRadius, + this.backgroundColor, + this.barrierColor, + this.decoration, + this.width, + this.drawerShadows, + this.semanticLabel, + required this.child, + }); + + @override + Widget build(BuildContext context) { + final BorderRadiusGeometry effectiveBorderRadius = + borderRadius ?? context.moonTheme?.drawerTheme.properties.borderRadius ?? BorderRadius.zero; + + final Color effectiveBackgroundColor = + backgroundColor ?? context.moonTheme?.drawerTheme.colors.backgroundColor ?? MoonColors.light.gohan; + + final Color effectiveTextColor = + context.moonTheme?.drawerTheme.colors.textColor ?? MoonTypography.light.colors.bodyPrimary; + + final Color effectiveIconColor = + context.moonTheme?.drawerTheme.colors.iconColor ?? MoonIconTheme.light.colors.primaryColor; + + final double effectiveWidthFromTheme = context.moonTheme?.drawerTheme.properties.width ?? 448; + + final double effectiveWidth = width ?? + (MediaQuery.of(context).size.width < effectiveWidthFromTheme + ? MediaQuery.of(context).size.width + : effectiveWidthFromTheme); + + final List effectiveDrawerShadows = + drawerShadows ?? context.moonTheme?.drawerTheme.shadows.drawerShadows ?? MoonShadows.light.lg; + + final TextStyle effectiveTextStyle = + context.moonTheme?.drawerTheme.properties.textStyle ?? MoonTextStyles.body.textDefault; + + return Semantics( + explicitChildNodes: true, + namesRoute: true, + scopesRoute: true, + label: semanticLabel, + child: IconTheme( + data: IconThemeData(color: effectiveIconColor), + child: DefaultTextStyle( + style: effectiveTextStyle.copyWith(color: effectiveTextColor), + child: Container( + width: effectiveWidth, + decoration: decoration ?? + ShapeDecorationWithPremultipliedAlpha( + color: effectiveBackgroundColor, + shadows: effectiveDrawerShadows, + shape: MoonSquircleBorder( + borderRadius: effectiveBorderRadius.squircleBorderRadius(context), + ), + ), + child: child, + ), + ), + ), + ); + } +}