diff --git a/example/lib/src/storybook/stories/primitives/modal.dart b/example/lib/src/storybook/stories/primitives/modal.dart index 341cdac9..aa6d6edb 100644 --- a/example/lib/src/storybook/stories/primitives/modal.dart +++ b/example/lib/src/storybook/stories/primitives/modal.dart @@ -10,6 +10,24 @@ class ModalStory extends StatelessWidget { @override Widget build(BuildContext context) { + final alignmentKnob = context.knobs.nullable.options( + label: "alignment", + description: "Alignment for MoonModal.", + enabled: false, + initial: AlignmentDirectional.center, + options: const [ + Option(label: "center", value: AlignmentDirectional.center), + Option(label: "centerStart", value: AlignmentDirectional.centerStart), + Option(label: "centerEnd", value: AlignmentDirectional.centerEnd), + Option(label: "topCenter", value: AlignmentDirectional.topCenter), + Option(label: "topStart", value: AlignmentDirectional.topStart), + Option(label: "topEnd", value: AlignmentDirectional.topEnd), + Option(label: "bottomCenter", value: AlignmentDirectional.bottomCenter), + Option(label: "bottomStart", value: AlignmentDirectional.bottomStart), + Option(label: "bottomEnd", value: AlignmentDirectional.bottomEnd), + ], + ); + final textColorKnob = context.knobs.nullable.options( label: "Text color", description: "MoonColors variants for MoonModal text.", @@ -51,15 +69,22 @@ class ModalStory extends StatelessWidget { max: 32, ); + final barrierDismissibleKnob = context.knobs.boolean( + label: "barrierDismissible", + description: "Modal barrier is dismissible via barrier taps.", + initial: true, + ); + Future modalBuilder(BuildContext context) { return showMoonModal( context: context, - useRootNavigator: false, barrierColor: barrierColor, + barrierDismissible: barrierDismissibleKnob, builder: (BuildContext context) { return Directionality( textDirection: Directionality.of(context), child: MoonModal( + alignment: alignmentKnob, backgroundColor: backgroundColor, borderRadius: borderRadiusKnob != null ? BorderRadius.circular(borderRadiusKnob.toDouble()) diff --git a/lib/src/widgets/modal/modal.dart b/lib/src/widgets/modal/modal.dart index 46ab7213..a2c63a82 100644 --- a/lib/src/widgets/modal/modal.dart +++ b/lib/src/widgets/modal/modal.dart @@ -1,19 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; import 'package:moon_core/moon_core.dart'; -import 'package:moon_design/src/theme/theme.dart'; import 'package:moon_design/src/theme/tokens/borders.dart'; import 'package:moon_design/src/theme/tokens/transitions.dart'; import 'package:moon_design/src/theme/tokens/typography/typography.dart'; -import 'package:moon_design/src/utils/extensions.dart'; -import 'package:moon_design/src/utils/squircle/squircle_border.dart'; import 'package:moon_tokens/moon_tokens.dart'; /// Displays a modal overlay over the app's current content, incorporating /// entrance and exit animations, modal barrier color, and modal barrier -/// behavior, enabling dialog dismissal via barrier taps. Intended for use in +/// behavior, enabling modal dismissal via barrier taps. Intended for use in /// conjunction with [MoonModal]. Future showMoonModal({ bool barrierDismissible = true, @@ -28,115 +26,32 @@ Future showMoonModal({ required BuildContext context, required WidgetBuilder builder, }) { - assert(!barrierDismissible || barrierLabel != null); - assert(_debugIsActive(context)); - - final CapturedThemes themes = InheritedTheme.capture( - from: context, - to: Navigator.of(context, rootNavigator: useRootNavigator).context, - ); - - final Color effectiveBarrierColor = barrierColor ?? - context.moonTheme?.modalTheme.colors.barrierColor ?? - MoonColors.light.zeno; + final Color effectiveBarrierColor = barrierColor ?? MoonColors.light.zeno; final Duration effectiveTransitionDuration = transitionDuration ?? - context.moonTheme?.modalTheme.properties.transitionDuration ?? MoonTransitions.transitions.defaultTransitionDuration; - final Curve effectiveTransitionCurve = transitionCurve ?? - context.moonTheme?.modalTheme.properties.transitionCurve ?? - MoonTransitions.transitions.defaultTransitionCurve; - - return Navigator.of(context, rootNavigator: useRootNavigator).push( - MoonModalRoute( - context: context, - builder: builder, - barrierDismissible: barrierDismissible, - barrierLabel: barrierLabel, - barrierColor: effectiveBarrierColor, - transitionDuration: effectiveTransitionDuration, - transitionCurve: effectiveTransitionCurve, - useSafeArea: useSafeArea, - settings: routeSettings, - anchorPoint: anchorPoint, - themes: themes, - ), + final Curve effectiveTransitionCurve = + transitionCurve ?? MoonTransitions.transitions.defaultTransitionCurve; + + return showMoonRawModal( + context: context, + builder: builder, + barrierDismissible: barrierDismissible, + barrierLabel: barrierLabel, + barrierColor: effectiveBarrierColor, + transitionDuration: effectiveTransitionDuration, + transitionCurve: effectiveTransitionCurve, + useSafeArea: useSafeArea, + routeSettings: routeSettings, + anchorPoint: anchorPoint, ); } -bool _debugIsActive(BuildContext context) { - if (context is Element && !context.debugIsActive) { - throw FlutterError.fromParts([ - ErrorSummary('This BuildContext is no longer valid.'), - ErrorDescription( - 'The showMoonModal function context parameter is a BuildContext that is ' - 'no longer valid.', - ), - ErrorHint( - 'This can commonly occur when the showMoonModal function is called after ' - 'awaiting a Future. In this situation the BuildContext might refer to a ' - 'widget that has already been disposed during the await. Consider using ' - 'a parent context instead.', - ), - ]); - } - - return true; -} - -class MoonModalRoute extends RawDialogRoute { - /// A Moon Design modal route with entrance and exit animations, modal barrier - /// color, and modal barrier behavior that allows dismissing the modal when - /// tapped on the barrier. - MoonModalRoute({ - super.anchorPoint, - required super.barrierColor, - super.barrierDismissible, - String? barrierLabel, - super.settings, - CapturedThemes? themes, - required super.transitionDuration, - required Curve transitionCurve, - bool useSafeArea = true, - required BuildContext context, - required WidgetBuilder builder, - }) : super( - barrierLabel: barrierLabel ?? - MaterialLocalizations.of(context).modalBarrierDismissLabel, - pageBuilder: ( - BuildContext buildContext, - Animation animation, - Animation secondaryAnimation, - ) { - final Widget pageChild = Builder(builder: builder); - - Widget modal = themes?.wrap(pageChild) ?? pageChild; - - if (useSafeArea) modal = SafeArea(child: modal); - - return modal; - }, - transitionBuilder: ( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child, - ) { - return RepaintBoundary( - child: FadeTransition( - opacity: CurvedAnimation( - parent: animation, - curve: transitionCurve, - ), - child: child, - ), - ); - }, - ); -} - class MoonModal extends StatelessWidget { + /// The alignment of the modal. + final AlignmentGeometry? alignment; + /// The border radius of the modal. final BorderRadiusGeometry? borderRadius; @@ -155,6 +70,7 @@ class MoonModal extends StatelessWidget { /// Creates a Moon Design modal. const MoonModal({ super.key, + this.alignment, this.borderRadius, this.backgroundColor, this.decoration, @@ -164,46 +80,39 @@ class MoonModal extends StatelessWidget { @override Widget build(BuildContext context) { - final BorderRadiusGeometry effectiveBorderRadius = borderRadius ?? - context.moonTheme?.modalTheme.properties.borderRadius ?? - MoonBorders.borders.surfaceSm; + final BorderRadiusGeometry effectiveBorderRadius = + borderRadius ?? MoonBorders.borders.surfaceSm; - final Color effectiveBackgroundColor = backgroundColor ?? - context.moonTheme?.modalTheme.colors.backgroundColor ?? - MoonColors.light.goku; + final Color effectiveBackgroundColor = + backgroundColor ?? MoonColors.light.goku; - final Color effectiveTextColor = - context.moonTheme?.modalTheme.colors.textColor ?? - MoonColors.light.textPrimary; + final Color effectiveTextColor = MoonColors.light.textPrimary; - final Color effectiveIconColor = - context.moonTheme?.modalTheme.colors.iconColor ?? - MoonColors.light.iconPrimary; + final Color effectiveIconColor = MoonColors.light.iconPrimary; final TextStyle effectiveTextStyle = - context.moonTheme?.modalTheme.properties.textStyle ?? - MoonTypography.typography.body.textDefault; + MoonTypography.typography.body.textDefault; + + final TextStyle resolvedTextStyle = + effectiveTextStyle.copyWith(color: effectiveTextColor); + final Style modalStyle = Style( + decorationToAttribute( + decoration ?? + ShapeDecorationWithPremultipliedAlpha( + color: effectiveBackgroundColor, + shape: MoonBorder(borderRadius: effectiveBorderRadius), + ), + ), + $with.align(alignment: alignment ?? Alignment.center), + $with.iconTheme.data.color(effectiveIconColor), + $with.defaultTextStyle.style.as(resolvedTextStyle), + ); return Semantics( label: semanticLabel, - child: IconTheme( - data: IconThemeData(color: effectiveIconColor), - child: DefaultTextStyle( - style: effectiveTextStyle.copyWith(color: effectiveTextColor), - child: Center( - child: Container( - decoration: decoration ?? - ShapeDecorationWithPremultipliedAlpha( - color: effectiveBackgroundColor, - shape: MoonSquircleBorder( - borderRadius: - effectiveBorderRadius.squircleBorderRadius(context), - ), - ), - child: child, - ), - ), - ), + child: Box( + style: modalStyle, + child: child, ), ); }