Skip to content
This repository was archived by the owner on Feb 10, 2026. It is now read-only.

Commit cdb6ebd

Browse files
authored
feat: [MDS-1600] Modal component (#467)
Co-authored-by: GittHub-d <birgitt.majas@yolo.com>
1 parent a328b73 commit cdb6ebd

2 files changed

Lines changed: 72 additions & 138 deletions

File tree

example/lib/src/storybook/stories/primitives/modal.dart

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ class ModalStory extends StatelessWidget {
1010

1111
@override
1212
Widget build(BuildContext context) {
13+
final alignmentKnob = context.knobs.nullable.options(
14+
label: "alignment",
15+
description: "Alignment for MoonModal.",
16+
enabled: false,
17+
initial: AlignmentDirectional.center,
18+
options: const [
19+
Option(label: "center", value: AlignmentDirectional.center),
20+
Option(label: "centerStart", value: AlignmentDirectional.centerStart),
21+
Option(label: "centerEnd", value: AlignmentDirectional.centerEnd),
22+
Option(label: "topCenter", value: AlignmentDirectional.topCenter),
23+
Option(label: "topStart", value: AlignmentDirectional.topStart),
24+
Option(label: "topEnd", value: AlignmentDirectional.topEnd),
25+
Option(label: "bottomCenter", value: AlignmentDirectional.bottomCenter),
26+
Option(label: "bottomStart", value: AlignmentDirectional.bottomStart),
27+
Option(label: "bottomEnd", value: AlignmentDirectional.bottomEnd),
28+
],
29+
);
30+
1331
final textColorKnob = context.knobs.nullable.options(
1432
label: "Text color",
1533
description: "MoonColors variants for MoonModal text.",
@@ -51,15 +69,22 @@ class ModalStory extends StatelessWidget {
5169
max: 32,
5270
);
5371

72+
final barrierDismissibleKnob = context.knobs.boolean(
73+
label: "barrierDismissible",
74+
description: "Modal barrier is dismissible via barrier taps.",
75+
initial: true,
76+
);
77+
5478
Future<void> modalBuilder(BuildContext context) {
5579
return showMoonModal<void>(
5680
context: context,
57-
useRootNavigator: false,
5881
barrierColor: barrierColor,
82+
barrierDismissible: barrierDismissibleKnob,
5983
builder: (BuildContext context) {
6084
return Directionality(
6185
textDirection: Directionality.of(context),
6286
child: MoonModal(
87+
alignment: alignmentKnob,
6388
backgroundColor: backgroundColor,
6489
borderRadius: borderRadiusKnob != null
6590
? BorderRadius.circular(borderRadiusKnob.toDouble())

lib/src/widgets/modal/modal.dart

Lines changed: 46 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import 'package:flutter/material.dart';
22

3+
import 'package:mix/mix.dart';
34
import 'package:moon_core/moon_core.dart';
45

5-
import 'package:moon_design/src/theme/theme.dart';
66
import 'package:moon_design/src/theme/tokens/borders.dart';
77
import 'package:moon_design/src/theme/tokens/transitions.dart';
88
import 'package:moon_design/src/theme/tokens/typography/typography.dart';
9-
import 'package:moon_design/src/utils/extensions.dart';
10-
import 'package:moon_design/src/utils/squircle/squircle_border.dart';
119

1210
import 'package:moon_tokens/moon_tokens.dart';
1311

1412
/// Displays a modal overlay over the app's current content, incorporating
1513
/// entrance and exit animations, modal barrier color, and modal barrier
16-
/// behavior, enabling dialog dismissal via barrier taps. Intended for use in
14+
/// behavior, enabling modal dismissal via barrier taps. Intended for use in
1715
/// conjunction with [MoonModal].
1816
Future<T?> showMoonModal<T>({
1917
bool barrierDismissible = true,
@@ -28,115 +26,32 @@ Future<T?> showMoonModal<T>({
2826
required BuildContext context,
2927
required WidgetBuilder builder,
3028
}) {
31-
assert(!barrierDismissible || barrierLabel != null);
32-
assert(_debugIsActive(context));
33-
34-
final CapturedThemes themes = InheritedTheme.capture(
35-
from: context,
36-
to: Navigator.of(context, rootNavigator: useRootNavigator).context,
37-
);
38-
39-
final Color effectiveBarrierColor = barrierColor ??
40-
context.moonTheme?.modalTheme.colors.barrierColor ??
41-
MoonColors.light.zeno;
29+
final Color effectiveBarrierColor = barrierColor ?? MoonColors.light.zeno;
4230

4331
final Duration effectiveTransitionDuration = transitionDuration ??
44-
context.moonTheme?.modalTheme.properties.transitionDuration ??
4532
MoonTransitions.transitions.defaultTransitionDuration;
4633

47-
final Curve effectiveTransitionCurve = transitionCurve ??
48-
context.moonTheme?.modalTheme.properties.transitionCurve ??
49-
MoonTransitions.transitions.defaultTransitionCurve;
50-
51-
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(
52-
MoonModalRoute<T>(
53-
context: context,
54-
builder: builder,
55-
barrierDismissible: barrierDismissible,
56-
barrierLabel: barrierLabel,
57-
barrierColor: effectiveBarrierColor,
58-
transitionDuration: effectiveTransitionDuration,
59-
transitionCurve: effectiveTransitionCurve,
60-
useSafeArea: useSafeArea,
61-
settings: routeSettings,
62-
anchorPoint: anchorPoint,
63-
themes: themes,
64-
),
34+
final Curve effectiveTransitionCurve =
35+
transitionCurve ?? MoonTransitions.transitions.defaultTransitionCurve;
36+
37+
return showMoonRawModal(
38+
context: context,
39+
builder: builder,
40+
barrierDismissible: barrierDismissible,
41+
barrierLabel: barrierLabel,
42+
barrierColor: effectiveBarrierColor,
43+
transitionDuration: effectiveTransitionDuration,
44+
transitionCurve: effectiveTransitionCurve,
45+
useSafeArea: useSafeArea,
46+
routeSettings: routeSettings,
47+
anchorPoint: anchorPoint,
6548
);
6649
}
6750

68-
bool _debugIsActive(BuildContext context) {
69-
if (context is Element && !context.debugIsActive) {
70-
throw FlutterError.fromParts(<DiagnosticsNode>[
71-
ErrorSummary('This BuildContext is no longer valid.'),
72-
ErrorDescription(
73-
'The showMoonModal function context parameter is a BuildContext that is '
74-
'no longer valid.',
75-
),
76-
ErrorHint(
77-
'This can commonly occur when the showMoonModal function is called after '
78-
'awaiting a Future. In this situation the BuildContext might refer to a '
79-
'widget that has already been disposed during the await. Consider using '
80-
'a parent context instead.',
81-
),
82-
]);
83-
}
84-
85-
return true;
86-
}
87-
88-
class MoonModalRoute<T> extends RawDialogRoute<T> {
89-
/// A Moon Design modal route with entrance and exit animations, modal barrier
90-
/// color, and modal barrier behavior that allows dismissing the modal when
91-
/// tapped on the barrier.
92-
MoonModalRoute({
93-
super.anchorPoint,
94-
required super.barrierColor,
95-
super.barrierDismissible,
96-
String? barrierLabel,
97-
super.settings,
98-
CapturedThemes? themes,
99-
required super.transitionDuration,
100-
required Curve transitionCurve,
101-
bool useSafeArea = true,
102-
required BuildContext context,
103-
required WidgetBuilder builder,
104-
}) : super(
105-
barrierLabel: barrierLabel ??
106-
MaterialLocalizations.of(context).modalBarrierDismissLabel,
107-
pageBuilder: (
108-
BuildContext buildContext,
109-
Animation<double> animation,
110-
Animation<double> secondaryAnimation,
111-
) {
112-
final Widget pageChild = Builder(builder: builder);
113-
114-
Widget modal = themes?.wrap(pageChild) ?? pageChild;
115-
116-
if (useSafeArea) modal = SafeArea(child: modal);
117-
118-
return modal;
119-
},
120-
transitionBuilder: (
121-
BuildContext context,
122-
Animation<double> animation,
123-
Animation<double> secondaryAnimation,
124-
Widget child,
125-
) {
126-
return RepaintBoundary(
127-
child: FadeTransition(
128-
opacity: CurvedAnimation(
129-
parent: animation,
130-
curve: transitionCurve,
131-
),
132-
child: child,
133-
),
134-
);
135-
},
136-
);
137-
}
138-
13951
class MoonModal extends StatelessWidget {
52+
/// The alignment of the modal.
53+
final AlignmentGeometry? alignment;
54+
14055
/// The border radius of the modal.
14156
final BorderRadiusGeometry? borderRadius;
14257

@@ -155,6 +70,7 @@ class MoonModal extends StatelessWidget {
15570
/// Creates a Moon Design modal.
15671
const MoonModal({
15772
super.key,
73+
this.alignment,
15874
this.borderRadius,
15975
this.backgroundColor,
16076
this.decoration,
@@ -164,46 +80,39 @@ class MoonModal extends StatelessWidget {
16480

16581
@override
16682
Widget build(BuildContext context) {
167-
final BorderRadiusGeometry effectiveBorderRadius = borderRadius ??
168-
context.moonTheme?.modalTheme.properties.borderRadius ??
169-
MoonBorders.borders.surfaceSm;
83+
final BorderRadiusGeometry effectiveBorderRadius =
84+
borderRadius ?? MoonBorders.borders.surfaceSm;
17085

171-
final Color effectiveBackgroundColor = backgroundColor ??
172-
context.moonTheme?.modalTheme.colors.backgroundColor ??
173-
MoonColors.light.goku;
86+
final Color effectiveBackgroundColor =
87+
backgroundColor ?? MoonColors.light.goku;
17488

175-
final Color effectiveTextColor =
176-
context.moonTheme?.modalTheme.colors.textColor ??
177-
MoonColors.light.textPrimary;
89+
final Color effectiveTextColor = MoonColors.light.textPrimary;
17890

179-
final Color effectiveIconColor =
180-
context.moonTheme?.modalTheme.colors.iconColor ??
181-
MoonColors.light.iconPrimary;
91+
final Color effectiveIconColor = MoonColors.light.iconPrimary;
18292

18393
final TextStyle effectiveTextStyle =
184-
context.moonTheme?.modalTheme.properties.textStyle ??
185-
MoonTypography.typography.body.textDefault;
94+
MoonTypography.typography.body.textDefault;
95+
96+
final TextStyle resolvedTextStyle =
97+
effectiveTextStyle.copyWith(color: effectiveTextColor);
18698

99+
final Style modalStyle = Style(
100+
decorationToAttribute(
101+
decoration ??
102+
ShapeDecorationWithPremultipliedAlpha(
103+
color: effectiveBackgroundColor,
104+
shape: MoonBorder(borderRadius: effectiveBorderRadius),
105+
),
106+
),
107+
$with.align(alignment: alignment ?? Alignment.center),
108+
$with.iconTheme.data.color(effectiveIconColor),
109+
$with.defaultTextStyle.style.as(resolvedTextStyle),
110+
);
187111
return Semantics(
188112
label: semanticLabel,
189-
child: IconTheme(
190-
data: IconThemeData(color: effectiveIconColor),
191-
child: DefaultTextStyle(
192-
style: effectiveTextStyle.copyWith(color: effectiveTextColor),
193-
child: Center(
194-
child: Container(
195-
decoration: decoration ??
196-
ShapeDecorationWithPremultipliedAlpha(
197-
color: effectiveBackgroundColor,
198-
shape: MoonSquircleBorder(
199-
borderRadius:
200-
effectiveBorderRadius.squircleBorderRadius(context),
201-
),
202-
),
203-
child: child,
204-
),
205-
),
206-
),
113+
child: Box(
114+
style: modalStyle,
115+
child: child,
207116
),
208117
);
209118
}

0 commit comments

Comments
 (0)