toApproximateMaterialTheme method

  1. @experimental
ThemeData toApproximateMaterialTheme()

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(),
  // ...
)

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: colorScheme.foreground,
    displayColor: colorScheme.foreground,
  );

  return ThemeData(
    colorScheme: ColorScheme(
      brightness: colorScheme.brightness,
      primary: colorScheme.primary,
      onPrimary: colorScheme.primaryForeground,
      secondary: colorScheme.secondary,
      onSecondary: colorScheme.secondaryForeground,
      error: colorScheme.error,
      onError: colorScheme.errorForeground,
      surface: colorScheme.background,
      onSurface: colorScheme.foreground,
      secondaryContainer: colorScheme.secondary,
      onSecondaryContainer: colorScheme.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: CardTheme(
      elevation: 0,
      shape: RoundedRectangleBorder(
        borderRadius: style.borderRadius,
        side: BorderSide(width: style.borderWidth, color: colorScheme.border),
      ),
    ),

    //// Chip
    chipTheme: ChipThemeData(shape: RoundedRectangleBorder(borderRadius: style.borderRadius)),

    //// Input
    inputDecorationTheme: InputDecorationTheme(
      border: OutlineInputBorder(
        borderRadius: textFieldStyle.enabledStyle.unfocusedStyle.radius,
        borderSide: BorderSide(
          color: textFieldStyle.enabledStyle.unfocusedStyle.color,
          width: textFieldStyle.enabledStyle.unfocusedStyle.width,
        ),
      ),
      enabledBorder: OutlineInputBorder(
        borderRadius: textFieldStyle.enabledStyle.unfocusedStyle.radius,
        borderSide: BorderSide(
          color: textFieldStyle.enabledStyle.unfocusedStyle.color,
          width: textFieldStyle.enabledStyle.unfocusedStyle.width,
        ),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: textFieldStyle.enabledStyle.focusedStyle.radius,
        borderSide: BorderSide(
          color: textFieldStyle.enabledStyle.focusedStyle.color,
          width: textFieldStyle.enabledStyle.focusedStyle.width,
        ),
      ),
      errorBorder: OutlineInputBorder(
        borderRadius: textFieldStyle.errorStyle.unfocusedStyle.radius,
        borderSide: BorderSide(
          color: textFieldStyle.errorStyle.unfocusedStyle.color,
          width: textFieldStyle.errorStyle.unfocusedStyle.width,
        ),
      ),
      focusedErrorBorder: OutlineInputBorder(
        borderRadius: textFieldStyle.errorStyle.focusedStyle.radius,
        borderSide: BorderSide(
          color: textFieldStyle.errorStyle.focusedStyle.color,
          width: textFieldStyle.errorStyle.focusedStyle.width,
        ),
      ),
      disabledBorder: OutlineInputBorder(
        borderRadius: textFieldStyle.disabledStyle.unfocusedStyle.radius,
        borderSide: BorderSide(
          color: textFieldStyle.disabledStyle.unfocusedStyle.color,
          width: textFieldStyle.disabledStyle.unfocusedStyle.width,
        ),
      ),
      labelStyle: textFieldStyle.enabledStyle.descriptionTextStyle,
      floatingLabelStyle: textFieldStyle.enabledStyle.labelTextStyle,
      hintStyle: textFieldStyle.enabledStyle.hintTextStyle,
      errorStyle: textFieldStyle.errorStyle.errorTextStyle,
      helperStyle: textFieldStyle.enabledStyle.descriptionTextStyle,
      counterStyle: textFieldStyle.enabledStyle.counterTextStyle,
      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: colorScheme.secondaryForeground,
      hourMinuteColor: colorScheme.secondary,
      hourMinuteShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
      dayPeriodTextColor: colorScheme.foreground,
      dayPeriodColor: colorScheme.secondary,
      dayPeriodBorderSide: BorderSide(color: colorScheme.border),
      dayPeriodShape: RoundedRectangleBorder(borderRadius: style.borderRadius),
      dialBackgroundColor: colorScheme.secondary,
      shape: RoundedRectangleBorder(borderRadius: style.borderRadius),
    ),

    /// Slider
    sliderTheme: SliderThemeData(
      activeTrackColor: sliderStyles.horizontalStyle.enabledStyle.activeColor,
      inactiveTrackColor: sliderStyles.horizontalStyle.enabledStyle.inactiveColor,
      disabledActiveTrackColor: sliderStyles.horizontalStyle.disabledStyle.activeColor,
      disabledInactiveTrackColor: sliderStyles.horizontalStyle.disabledStyle.inactiveColor,
      activeTickMarkColor: sliderStyles.horizontalStyle.enabledStyle.markStyle.tickColor,
      inactiveTickMarkColor: sliderStyles.horizontalStyle.enabledStyle.markStyle.tickColor,
      disabledActiveTickMarkColor: sliderStyles.horizontalStyle.disabledStyle.markStyle.tickColor,
      disabledInactiveTickMarkColor: sliderStyles.horizontalStyle.disabledStyle.markStyle.tickColor,
      thumbColor: sliderStyles.horizontalStyle.enabledStyle.thumbStyle.borderColor,
      disabledThumbColor: sliderStyles.horizontalStyle.disabledStyle.thumbStyle.borderColor,
      valueIndicatorColor: sliderStyles.horizontalStyle.enabledStyle.tooltipStyle.decoration.color,
      valueIndicatorTextStyle: sliderStyles.horizontalStyle.enabledStyle.tooltipStyle.textStyle,
    ),

    //// Switch
    switchTheme: SwitchThemeData(
      thumbColor: WidgetStateColor.fromMap({
        WidgetState.disabled: switchStyle.disabledStyle.thumbColor,
        WidgetState.any: switchStyle.enabledStyle.thumbColor,
      }),
      trackColor: WidgetStateColor.fromMap({
        WidgetState.disabled & WidgetState.selected: switchStyle.disabledStyle.checkedColor,
        WidgetState.disabled: switchStyle.disabledStyle.uncheckedColor,
        WidgetState.selected: switchStyle.enabledStyle.checkedColor,
        WidgetState.any: switchStyle.enabledStyle.uncheckedColor,
      }),
      trackOutlineColor: WidgetStateColor.fromMap({
        WidgetState.disabled & WidgetState.selected: switchStyle.disabledStyle.checkedColor,
        WidgetState.disabled: switchStyle.disabledStyle.uncheckedColor,
        WidgetState.selected: switchStyle.enabledStyle.checkedColor,
        WidgetState.any: switchStyle.enabledStyle.uncheckedColor,
      }),
    ),

    //// Buttons
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ButtonStyle(
        textStyle: WidgetStateTextStyle.fromMap({
          WidgetState.disabled: buttonStyles.secondary.contentStyle.disabledTextStyle,
          WidgetState.any: buttonStyles.secondary.contentStyle.enabledTextStyle,
        }),
        backgroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.secondary.disabledBoxDecoration.color,
          WidgetState.hovered: buttonStyles.secondary.enabledHoverBoxDecoration.color,
          WidgetState.any: buttonStyles.secondary.enabledBoxDecoration.color,
        }),
        foregroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.secondary.contentStyle.disabledTextStyle.color,
          WidgetState.any: buttonStyles.secondary.contentStyle.enabledTextStyle.color,
        }),
        padding: WidgetStateProperty.all(buttonStyles.secondary.contentStyle.padding),
        shape: WidgetStateProperty.all(RoundedRectangleBorder(borderRadius: style.borderRadius)),
      ),
    ),
    filledButtonTheme: FilledButtonThemeData(
      style: ButtonStyle(
        textStyle: WidgetStateTextStyle.fromMap({
          WidgetState.disabled: buttonStyles.primary.contentStyle.disabledTextStyle,
          WidgetState.any: buttonStyles.primary.contentStyle.enabledTextStyle,
        }),
        backgroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.primary.disabledBoxDecoration.color,
          WidgetState.hovered: buttonStyles.primary.enabledHoverBoxDecoration.color,
          WidgetState.any: buttonStyles.primary.enabledBoxDecoration.color,
        }),
        foregroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.primary.contentStyle.disabledTextStyle.color,
          WidgetState.any: buttonStyles.primary.contentStyle.enabledTextStyle.color,
        }),
        padding: WidgetStateProperty.all(buttonStyles.primary.contentStyle.padding),
        shape: WidgetStateProperty.all(RoundedRectangleBorder(borderRadius: style.borderRadius)),
      ),
    ),
    outlinedButtonTheme: OutlinedButtonThemeData(
      style: ButtonStyle(
        textStyle: WidgetStateTextStyle.fromMap({
          WidgetState.disabled: buttonStyles.outline.contentStyle.disabledTextStyle,
          ~WidgetState.disabled: buttonStyles.outline.contentStyle.enabledTextStyle,
        }),
        backgroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.outline.disabledBoxDecoration.color,
          WidgetState.hovered: buttonStyles.outline.enabledHoverBoxDecoration.color,
          WidgetState.any: buttonStyles.outline.enabledBoxDecoration.color,
        }),
        foregroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.outline.contentStyle.disabledTextStyle.color,
          WidgetState.any: buttonStyles.outline.contentStyle.enabledTextStyle.color,
        }),
        padding: WidgetStateProperty.all(buttonStyles.outline.contentStyle.padding),
        side: WidgetStateBorderSide.fromMap({
          WidgetState.disabled: BorderSide(
            color:
                buttonStyles.outline.disabledBoxDecoration.border?.top.color ??
                colorScheme.disable(colorScheme.border),
            width: buttonStyles.outline.disabledBoxDecoration.border?.top.width ?? style.borderWidth,
          ),
          WidgetState.hovered: BorderSide(
            color:
                buttonStyles.outline.enabledHoverBoxDecoration.border?.top.color ??
                colorScheme.hover(colorScheme.border),
            width: buttonStyles.outline.enabledHoverBoxDecoration.border?.top.width ?? style.borderWidth,
          ),
          WidgetState.any: BorderSide(
            color: buttonStyles.outline.enabledBoxDecoration.border?.top.color ?? colorScheme.border,
            width: buttonStyles.outline.enabledBoxDecoration.border?.top.width ?? style.borderWidth,
          ),
        }),
        shape: WidgetStateProperty.all(
          RoundedRectangleBorder(
            borderRadius: buttonStyles.outline.enabledBoxDecoration.borderRadius ?? style.borderRadius,
          ),
        ),
      ),
    ),
    textButtonTheme: TextButtonThemeData(
      style: ButtonStyle(
        textStyle: WidgetStateTextStyle.fromMap({
          WidgetState.disabled: buttonStyles.ghost.contentStyle.disabledTextStyle,
          WidgetState.any: buttonStyles.ghost.contentStyle.enabledTextStyle,
        }),
        backgroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.ghost.disabledBoxDecoration.color,
          WidgetState.hovered: buttonStyles.ghost.enabledHoverBoxDecoration.color,
          WidgetState.any: buttonStyles.ghost.enabledBoxDecoration.color,
        }),
        foregroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.ghost.contentStyle.disabledTextStyle.color,
          WidgetState.any: buttonStyles.ghost.contentStyle.enabledTextStyle.color,
        }),
        shape: WidgetStateProperty.all(
          RoundedRectangleBorder(
            borderRadius: buttonStyles.ghost.enabledBoxDecoration.borderRadius ?? style.borderRadius,
          ),
        ),
      ),
    ),
    floatingActionButtonTheme: FloatingActionButtonThemeData(
      backgroundColor: buttonStyles.primary.enabledBoxDecoration.color,
      foregroundColor: buttonStyles.primary.contentStyle.enabledTextStyle.color,
      hoverColor: buttonStyles.primary.enabledHoverBoxDecoration.color,
      disabledElevation: 0,
      shape: RoundedRectangleBorder(
        borderRadius: buttonStyles.primary.enabledBoxDecoration.borderRadius ?? style.borderRadius,
      ),
    ),
    iconButtonTheme: IconButtonThemeData(
      style: ButtonStyle(
        backgroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.ghost.disabledBoxDecoration.color,
          WidgetState.hovered: buttonStyles.ghost.enabledHoverBoxDecoration.color,
          WidgetState.any: buttonStyles.ghost.enabledBoxDecoration.color,
        }),
        foregroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.ghost.contentStyle.disabledTextStyle.color,
          WidgetState.any: buttonStyles.ghost.contentStyle.enabledTextStyle.color,
        }),
        shape: WidgetStateProperty.all(
          RoundedRectangleBorder(
            borderRadius: buttonStyles.ghost.enabledBoxDecoration.borderRadius ?? style.borderRadius,
          ),
        ),
      ),
    ),
    segmentedButtonTheme: SegmentedButtonThemeData(
      style: ButtonStyle(
        textStyle: WidgetStateTextStyle.fromMap({
          WidgetState.disabled: buttonStyles.ghost.contentStyle.disabledTextStyle,
          WidgetState.any: buttonStyles.ghost.contentStyle.enabledTextStyle,
        }),
        backgroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.ghost.disabledBoxDecoration.color,
          WidgetState.hovered: buttonStyles.ghost.enabledHoverBoxDecoration.color,
          WidgetState.any: buttonStyles.ghost.enabledBoxDecoration.color,
        }),
        foregroundColor: WidgetStateMapper({
          WidgetState.disabled: buttonStyles.ghost.contentStyle.disabledTextStyle.color,
          WidgetState.any: buttonStyles.ghost.contentStyle.enabledTextStyle.color,
        }),
        shape: WidgetStateProperty.all(
          RoundedRectangleBorder(
            borderRadius: buttonStyles.ghost.enabledBoxDecoration.borderRadius ?? style.borderRadius,
          ),
        ),
      ),
    ),

    /// Dialog
    dialogTheme: DialogTheme(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,
    ),
  );
}