toApproximateMaterialTheme method
Converts this FThemeData to a Material ThemeData on a best-effort basis.
This method enables interoperability between Forui and Material Design widgets by mapping Forui's theme properties to their closest Material equivalents. Use this when you need to:
- Use Material widgets within a Forui application
- Apply your Forui theme consistently to both Forui and Material widgets
- Create a gradual migration path from Material Design to Forui
Note that this conversion is approximate and experimental. Some styling properties may not map perfectly between the two design systems, and the resulting Material theme might not capture all the nuances of your Forui theme.
// Apply a Forui theme to Material widgets
MaterialApp(
theme: FThemes.zinc.light.toApproximateMaterialTheme(),
// ...
)
This method can be generated inside projects and directly modified by running:
dart run forui snippet create material-mapping
Implementation
@experimental
ThemeData toApproximateMaterialTheme() {
// Material requires height to be 1, certain widgets will overflow without it.
// TextBaseline.alphabetic is required as TextField requires it.
final textTheme = TextTheme(
displayLarge: typography.xl4.copyWith(
height: 1,
textBaseline: typography.xl4.textBaseline ?? TextBaseline.alphabetic,
),
displayMedium: typography.xl3.copyWith(
height: 1,
textBaseline: typography.xl3.textBaseline ?? TextBaseline.alphabetic,
),
displaySmall: typography.xl2.copyWith(
height: 1,
textBaseline: typography.xl2.textBaseline ?? TextBaseline.alphabetic,
),
headlineLarge: typography.xl3.copyWith(
height: 1,
textBaseline: typography.xl3.textBaseline ?? TextBaseline.alphabetic,
),
headlineMedium: typography.xl2.copyWith(
height: 1,
textBaseline: typography.xl2.textBaseline ?? TextBaseline.alphabetic,
),
headlineSmall: typography.xl.copyWith(
height: 1,
textBaseline: typography.xl.textBaseline ?? TextBaseline.alphabetic,
),
titleLarge: typography.lg.copyWith(
height: 1,
textBaseline: typography.lg.textBaseline ?? TextBaseline.alphabetic,
),
titleMedium: typography.base.copyWith(
height: 1,
textBaseline: typography.base.textBaseline ?? TextBaseline.alphabetic,
),
titleSmall: typography.sm.copyWith(
height: 1,
textBaseline: typography.sm.textBaseline ?? TextBaseline.alphabetic,
),
labelLarge: typography.base.copyWith(
height: 1,
textBaseline: typography.base.textBaseline ?? TextBaseline.alphabetic,
),
labelMedium: typography.sm.copyWith(
height: 1,
textBaseline: typography.sm.textBaseline ?? TextBaseline.alphabetic,
),
labelSmall: typography.xs.copyWith(
height: 1,
textBaseline: typography.xs.textBaseline ?? TextBaseline.alphabetic,
),
bodyLarge: typography.base.copyWith(
height: 1,
textBaseline: typography.base.textBaseline ?? TextBaseline.alphabetic,
),
bodyMedium: typography.sm.copyWith(
height: 1,
textBaseline: typography.sm.textBaseline ?? TextBaseline.alphabetic,
),
bodySmall: typography.xs.copyWith(height: 1, textBaseline: typography.xs.textBaseline ?? TextBaseline.alphabetic),
)..apply(fontFamily: typography.defaultFontFamily, bodyColor: colors.foreground, displayColor: colors.foreground);
return ThemeData(
colorScheme: ColorScheme(
brightness: colors.brightness,
primary: colors.primary,
onPrimary: colors.primaryForeground,
secondary: colors.secondary,
onSecondary: colors.secondaryForeground,
error: colors.error,
onError: colors.errorForeground,
surface: colors.background,
onSurface: colors.foreground,
secondaryContainer: colors.secondary,
onSecondaryContainer: colors.secondaryForeground,
),
fontFamily: typography.defaultFontFamily,
typography: Typography(
black: textTheme,
white: textTheme,
englishLike: textTheme,
dense: textTheme,
tall: textTheme,
),
textTheme: textTheme,
splashFactory: NoSplash.splashFactory,
useMaterial3: true,
//// Navigation Bar
navigationBarTheme: NavigationBarThemeData(
indicatorShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
//// Navigation Drawer
navigationDrawerTheme: NavigationDrawerThemeData(
indicatorShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
//// Navigation Rail
navigationRailTheme: NavigationRailThemeData(
indicatorShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
//// Card
cardTheme: CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: style.borderRadius,
side: BorderSide(width: style.borderWidth, color: colors.border),
),
),
//// Chip
chipTheme: ChipThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
//// Input
inputDecorationTheme: InputDecorationTheme(
border: WidgetStateInputBorder.resolveWith(textFieldStyle.border.resolve),
labelStyle: textFieldStyle.descriptionTextStyle.maybeResolve({}),
floatingLabelStyle: textFieldStyle.labelTextStyle.maybeResolve({}),
hintStyle: textFieldStyle.hintTextStyle.maybeResolve({}),
errorStyle: textFieldStyle.errorTextStyle,
helperStyle: textFieldStyle.descriptionTextStyle.maybeResolve({}),
counterStyle: textFieldStyle.counterTextStyle.maybeResolve({}),
contentPadding: textFieldStyle.contentPadding,
),
//// Date Picker
datePickerTheme: DatePickerThemeData(
shape: RoundedRectangleBorder(borderRadius: style.borderRadius),
dayShape: WidgetStateProperty.all(RoundedRectangleBorder(borderRadius: style.borderRadius)),
rangePickerShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
//// Time Picker
timePickerTheme: TimePickerThemeData(
hourMinuteTextColor: colors.secondaryForeground,
hourMinuteColor: colors.secondary,
hourMinuteShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
dayPeriodTextColor: colors.foreground,
dayPeriodColor: colors.secondary,
dayPeriodBorderSide: BorderSide(color: colors.border),
dayPeriodShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
dialBackgroundColor: colors.secondary,
shape: RoundedRectangleBorder(borderRadius: style.borderRadius),
),
/// Slider
sliderTheme: SliderThemeData(
activeTrackColor: sliderStyles.horizontalStyle.activeColor.maybeResolve({}),
inactiveTrackColor: sliderStyles.horizontalStyle.inactiveColor.maybeResolve({}),
disabledActiveTrackColor: sliderStyles.horizontalStyle.activeColor.maybeResolve({WidgetState.disabled}),
disabledInactiveTrackColor: sliderStyles.horizontalStyle.inactiveColor.maybeResolve({WidgetState.disabled}),
activeTickMarkColor: sliderStyles.horizontalStyle.markStyle.tickColor.maybeResolve({}),
inactiveTickMarkColor: sliderStyles.horizontalStyle.markStyle.tickColor.maybeResolve({}),
disabledActiveTickMarkColor: sliderStyles.horizontalStyle.markStyle.tickColor.maybeResolve({
WidgetState.disabled,
}),
disabledInactiveTickMarkColor: sliderStyles.horizontalStyle.markStyle.tickColor.maybeResolve({
WidgetState.disabled,
}),
thumbColor: sliderStyles.horizontalStyle.thumbStyle.borderColor.maybeResolve({}),
disabledThumbColor: sliderStyles.horizontalStyle.thumbStyle.borderColor.maybeResolve({WidgetState.disabled}),
valueIndicatorColor: sliderStyles.horizontalStyle.tooltipStyle.decoration.color,
valueIndicatorTextStyle: sliderStyles.horizontalStyle.tooltipStyle.textStyle,
),
//// Switch
switchTheme: SwitchThemeData(
thumbColor: switchStyle.thumbColor,
trackColor: switchStyle.trackColor,
trackOutlineColor: switchStyle.trackColor,
),
//// Buttons
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
textStyle: buttonStyles.secondary.contentStyle.textStyle,
backgroundColor: WidgetStateColor.resolveWith(
(states) => buttonStyles.secondary.decoration.maybeResolve(states)?.color ?? colors.secondary,
),
foregroundColor: WidgetStateColor.resolveWith(
(states) =>
buttonStyles.secondary.contentStyle.textStyle.maybeResolve(states)?.color ?? colors.secondaryForeground,
),
padding: WidgetStateProperty.all(buttonStyles.secondary.contentStyle.padding),
shape: WidgetStateProperty.all(RoundedRectangleBorder(borderRadius: style.borderRadius)),
),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
textStyle: buttonStyles.primary.contentStyle.textStyle,
backgroundColor: WidgetStateColor.resolveWith(
(states) => buttonStyles.primary.decoration.maybeResolve(states)?.color ?? colors.secondary,
),
foregroundColor: WidgetStateColor.resolveWith(
(states) => buttonStyles.secondary.decoration.maybeResolve(states)?.color ?? colors.secondaryForeground,
),
padding: WidgetStateProperty.all(buttonStyles.primary.contentStyle.padding),
shape: WidgetStateProperty.all(RoundedRectangleBorder(borderRadius: style.borderRadius)),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
textStyle: buttonStyles.outline.contentStyle.textStyle,
backgroundColor: WidgetStateColor.resolveWith(
(states) => buttonStyles.outline.decoration.maybeResolve(states)?.color ?? Colors.transparent,
),
foregroundColor: WidgetStateColor.resolveWith(
(states) => buttonStyles.outline.decoration.maybeResolve(states)?.color ?? Colors.transparent,
),
padding: WidgetStateProperty.all(buttonStyles.outline.contentStyle.padding),
side: WidgetStateBorderSide.resolveWith((states) {
final border = buttonStyles.outline.decoration.maybeResolve(states)?.border;
return BorderSide(
color:
border?.top.color ??
switch (states) {
_ when states.contains(WidgetState.disabled) => colors.disable(colors.border),
_ when states.contains(WidgetState.hovered) => colors.hover(colors.border),
_ => colors.border,
},
width: border?.top.width ?? style.borderWidth,
);
}),
shape: WidgetStateProperty.resolveWith(
(states) => RoundedRectangleBorder(
borderRadius: buttonStyles.outline.decoration.maybeResolve(states)?.borderRadius ?? style.borderRadius,
),
),
),
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
textStyle: buttonStyles.ghost.contentStyle.textStyle,
backgroundColor: WidgetStateColor.resolveWith(
(states) => buttonStyles.ghost.decoration.maybeResolve(states)?.color ?? Colors.transparent,
),
foregroundColor: WidgetStateColor.resolveWith(
(states) =>
buttonStyles.ghost.contentStyle.textStyle.maybeResolve(states)?.color ?? colors.secondaryForeground,
),
shape: WidgetStateProperty.resolveWith(
(states) => RoundedRectangleBorder(
borderRadius: buttonStyles.ghost.decoration.maybeResolve(states)?.borderRadius ?? style.borderRadius,
),
),
),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: buttonStyles.primary.decoration.maybeResolve(const {})?.color,
foregroundColor: buttonStyles.primary.contentStyle.textStyle.maybeResolve(const {})?.color,
hoverColor: buttonStyles.primary.decoration.maybeResolve(const {WidgetState.hovered})?.color,
disabledElevation: 0,
shape: RoundedRectangleBorder(
borderRadius: buttonStyles.primary.decoration.maybeResolve(const {})?.borderRadius ?? style.borderRadius,
),
),
iconButtonTheme: IconButtonThemeData(
style: ButtonStyle(
backgroundColor: WidgetStateColor.resolveWith(
(states) => buttonStyles.ghost.decoration.maybeResolve(states)?.color ?? Colors.transparent,
),
foregroundColor: WidgetStateColor.resolveWith(
(states) =>
buttonStyles.ghost.contentStyle.textStyle.maybeResolve(states)?.color ?? colors.secondaryForeground,
),
shape: WidgetStateProperty.resolveWith(
(states) => RoundedRectangleBorder(
borderRadius: buttonStyles.ghost.decoration.maybeResolve(states)?.borderRadius ?? style.borderRadius,
),
),
),
),
segmentedButtonTheme: SegmentedButtonThemeData(
style: ButtonStyle(
textStyle: buttonStyles.ghost.contentStyle.textStyle,
backgroundColor: WidgetStateColor.resolveWith(
(states) => buttonStyles.ghost.decoration.maybeResolve(states)?.color ?? Colors.transparent,
),
foregroundColor: WidgetStateColor.resolveWith(
(states) =>
buttonStyles.ghost.contentStyle.textStyle.maybeResolve(states)?.color ?? colors.secondaryForeground,
),
shape: WidgetStateProperty.resolveWith(
(states) => RoundedRectangleBorder(
borderRadius: buttonStyles.ghost.decoration.maybeResolve(states)?.borderRadius ?? style.borderRadius,
),
),
),
),
/// Dialog
dialogTheme: DialogThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
/// Bottom Sheet
bottomSheetTheme: BottomSheetThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
/// Snack Bar
snackBarTheme: SnackBarThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
/// List Tile
listTileTheme: ListTileThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),
/// Divider
dividerTheme: DividerThemeData(
color: dividerStyles.horizontalStyle.color,
thickness: dividerStyles.horizontalStyle.width,
),
iconTheme: IconThemeData(color: colors.primary, size: 20),
);
}