Theme topic

Fields take colors and fonts from your ThemeData. MREFieldsTheme adds the values Material does not have: corner radius, padding, breakpoints and texts.

Change it for the whole app

Register the extension on your theme. Every field below it uses these values.

final app = MaterialApp(
  theme: ThemeData(
    extensions: const [
      MREFieldsTheme(
        fieldBorderRadius: 16,
        contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 16),
      ),
    ],
  ),
  home: const HomePage(),
);

Change one value

Start from the defaults and replace only what you need.

final tokens = MREFieldsTheme.defaults.copyWith(fieldBorderRadius: 4);

Change one field

A parameter on the widget beats the theme for that field only.

final field = MRETextField(
  labelText: 'Search',
  borderRadius: 28, // this field only; the theme keeps its own radius
  contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
);

Dark mode

Register the extension on both themes. A theme without it falls back to the defaults.

const fields = MREFieldsTheme(fieldBorderRadius: 16);

final app = MaterialApp(
  theme: ThemeData(extensions: const [fields]),
  darkTheme: ThemeData(
    brightness: Brightness.dark,
    extensions: const [fields],
  ),
  home: const HomePage(),
);

Breakpoints

A field measures the width it really gets and picks a size.

Width Size Content padding
below 600 MREWindowSize.compact contentPadding
600 to 839 MREWindowSize.medium contentPadding
840 and up MREWindowSize.expanded expandedContentPadding

Move the limits:

const tokens = MREFieldsTheme(
  compactBreakpoint: 480,
  expandedBreakpoint: 1024,
);

Use the tokens in your own widget

/// A custom widget that follows the same tokens and breakpoints as the fields.
class AdaptiveBox extends StatelessWidget {
  /// Creates the box.
  const AdaptiveBox({super.key});

  @override
  Widget build(BuildContext context) {
    final tokens = MREFieldsTheme.of(context);

    return LayoutBuilder(
      builder: (context, constraints) {
        final size = tokens.windowSizeFor(constraints.maxWidth);

        return Padding(
          padding: tokens.contentPaddingFor(size),
          child: Text('Window size: ${size.name}'),
        );
      },
    );
  }
}

Translate the texts

Every text a field shows is in MREFieldsStrings, in English by default. Swap it when the app locale changes:

/// Returns the field texts for [locale]. English is the fallback.
MREFieldsStrings stringsFor(Locale locale) {
  return switch (locale.languageCode) {
    'ar' => const MREFieldsStrings(
      clearTooltip: 'مسح',
      countryPickerTitle: 'اختر الدولة',
      countrySearchHint: 'ابحث عن دولة أو رمز',
      noCountriesFound: 'لا توجد دول',
      removeImageTooltip: 'إزالة الصورة',
      replaceImageTooltip: 'استبدال الصورة',
      closeViewerTooltip: 'إغلاق',
    ),
    _ => const MREFieldsStrings(),
  };
}

/// Swaps the strings whenever the app locale changes.
Widget localizedFields(BuildContext context, Widget child) {
  final theme = Theme.of(context);
  final fields = MREFieldsTheme.of(
    context,
  ).copyWith(strings: stringsFor(Localizations.localeOf(context)));

  return Theme(
    data: theme.copyWith(
      extensions: [
        ...theme.extensions.values.where((e) => e is! MREFieldsTheme),
        fields,
      ],
    ),
    child: child,
  );
}

Widget localizedApp() {
  return MaterialApp(
    localizationsDelegates: GlobalMaterialLocalizations.delegates,
    supportedLocales: const [Locale('en'), Locale('ar')],
    builder: (context, child) => localizedFields(context, child!),
    home: const HomePage(),
  );
}

Add localizationsDelegates and supportedLocales as in your app. Without a supported locale, Flutter resolves back to English.

Classes

MREFieldsStrings Theme
Texts shown by the fields. English by default; the package has no translations.
MREFieldsTheme Theme
Field-only tokens: corner radius, padding, breakpoints and texts.

Enums

MREWindowSize Theme
Width class of the space a field has available.