diff --git a/docs/pages/docs/text-field.mdx b/docs/pages/docs/text-field.mdx
index 5b0f0a7b3..a873c13ab 100644
--- a/docs/pages/docs/text-field.mdx
+++ b/docs/pages/docs/text-field.mdx
@@ -2,7 +2,8 @@ import { Tabs } from 'nextra/components';
import { Widget } from '../../components/widget';
# Text Fields
-A text field lets the user enter text, either with hardware keyboard or with an onscreen keyboard.
+A text field lets the user enter text, either with hardware keyboard or with an onscreen keyboard. It can also be used
+in a form.
@@ -14,7 +15,8 @@ A text field lets the user enter text, either with hardware keyboard or with an
enabled: enabled,
label: 'Email',
hint: 'john@doe.com',
- )
+ footer: Text('Enter your email associated with your Forui account.'),
+ );
```
@@ -26,9 +28,9 @@ A text field lets the user enter text, either with hardware keyboard or with an
```dart
FTextField(
enabled: true,
- label: 'Email',
+ label: Text('Email'),
hint: 'john@doe.com',
- footer: 'Enter your email associated with your Forui account.',
+ footer: Text('Enter your email associated with your Forui account.'),
keyboardType: TextInputType.emailAddress,
textCapitalization: TextCapitalization.none,
);
@@ -38,9 +40,8 @@ FTextField(
```dart
FTextField.email(
- label: 'Email',
hint: 'john@doe.com',
- footer: 'Enter your email associated with your Forui account.',
+ footer: Text('Enter your email associated with your Forui account.'),
);
```
@@ -48,8 +49,7 @@ FTextField.email(
```dart
FTextField.password(
- label: 'Password',
- footer: 'Your password must be at least 8 characters long.',
+ footer: Text('Your password must be at least 8 characters long.'),
);
```
@@ -57,9 +57,9 @@ FTextField.password(
```dart
FTextField.multiline(
- label: 'Description',
+ label: Text('Description'),
hint: 'Enter a description...',
- footer: 'Enter a description of the item.',
+ footer: Text('Enter a description of the item.'),
);
```
@@ -75,7 +75,6 @@ FTextField.multiline(
```dart
FTextField.email(
- label: 'Email',
hint: 'john@doe.com',
);
```
@@ -92,7 +91,7 @@ FTextField.multiline(
```dart
FTextField.email(
- label: 'Email',
+ enabled: false
hint: 'john@doe.com',
);
```
@@ -110,7 +109,6 @@ FTextField.multiline(
```dart
FTextField.password(
controller: TextEditingController(text: 'My password'),
- label: 'Password',
);
```
@@ -125,9 +123,67 @@ FTextField.multiline(
```dart
FTextField.multiline(
- label: 'Leave a review',
+ label: Text('Leave a review'),
+ maxLines: 4,
);
```
+
+### Form
+
+
+
+
+
+
+ ```dart
+ class LoginForm extends StatefulWidget {
+ const LoginForm({super.key});
+
+ @override
+ State createState() => _LoginFormState();
+ }
+
+ class _LoginFormState extends State {
+ final GlobalKey _formKey = GlobalKey();
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) => Form(
+ key: _formKey,
+ child: Column(
+ children: [
+ FTextField.email(
+ hint: 'janedoe@foruslabs.com',
+ help: const Text(''),
+ validator: (value) => (value?.contains('@') ?? false) ? null : 'Please enter a valid email.',
+ ),
+ const SizedBox(height: 4),
+ FTextField.password(
+ hint: '',
+ help: const Text(''),
+ validator: (value) => 8 <= (value?.length ?? 0) ? null : 'Password must be at least 8 characters long.',
+ ),
+ const SizedBox(height: 30),
+ FButton(
+ rawLabel: const Text('Login'),
+ onPress: () {
+ if (!_formKey.currentState!.validate()) {
+ // Handle errors here.
+ return;
+ }
+ },
+ ),
+ ],
+ ),
+ );
+ }
+ ```
+
+
diff --git a/forui/CHANGELOG.md b/forui/CHANGELOG.md
index 68f6bb14c..2798daaec 100644
--- a/forui/CHANGELOG.md
+++ b/forui/CHANGELOG.md
@@ -1,12 +1,14 @@
-## 0.2.0
+## Next
+### Changes
* Add `Header.nested` widget.
-
-### Breaking changes
-
-* `FHeaderStyle` have been nested in `FHeaderStyles.rootStyle`.
-* `FHeaderActionStyle.action` parameter has been renamed to `FRootHeaderStyle.actionStyle`.
-* `FHeaderActionStyle.padding` parameter has been moved to `FRootHeaderStyle.actionSpacing`.
+* Change `FTextField` to be usable in `Form`s.
+* Change `FTextFieldStyle`'s default vertical content padding from `5` to `15`.
+* **Breaking** Move `FHeaderStyle` to `FHeaderStyles.rootStyle`.
+* **Breaking** Move `FHeaderActionStyle.padding` to `FRootHeaderStyle.actionSpacing`.
+* Split exports in `forui.dart` into sub-libraries.
+* **Breaking** Suffix style parameters with `Style`, i.e. `FRootHeaderStyle.action` has been renamed to `FRootHeaderStyle.actionStyle`.
+* Fix missing `key` parameter in `FTextField` constructors.
## 0.1.0
diff --git a/forui/analysis_options.yaml b/forui/analysis_options.yaml
index 81cdeebfb..61f85845a 100644
--- a/forui/analysis_options.yaml
+++ b/forui/analysis_options.yaml
@@ -5,4 +5,5 @@ analyzer:
linter:
rules:
+ - use_key_in_widget_constructors
- require_trailing_commas
diff --git a/forui/build.yaml b/forui/build.yaml
index 824618c6d..479407f30 100644
--- a/forui/build.yaml
+++ b/forui/build.yaml
@@ -3,10 +3,6 @@
targets:
$default:
builders:
- stevia_runner:steviaAssetGenerator:
- generate_for:
- - assets/**
-
mockito:mockBuilder:
generate_for:
- test/**.dart
\ No newline at end of file
diff --git a/forui/lib/assets.dart b/forui/lib/assets.dart
new file mode 100644
index 000000000..b4c2ef18c
--- /dev/null
+++ b/forui/lib/assets.dart
@@ -0,0 +1,6 @@
+/// The bundled assets in [forui_assets](https://github.com/forus-labs/forui/tree/main/forui_assets), exported for
+/// convenience.
+library forui.assets;
+
+export 'package:forui_assets/forui_assets.dart';
+export 'package:forui/src/svg_extension.nitrogen.dart';
diff --git a/forui/lib/forui.dart b/forui/lib/forui.dart
index a9e3d7982..a168a7a17 100644
--- a/forui/lib/forui.dart
+++ b/forui/lib/forui.dart
@@ -1,32 +1,6 @@
/// A Flutter package for building beautiful user interfaces.
library forui;
-// Icons
-export 'package:forui_assets/forui_assets.dart';
-export 'package:forui/src/svg_extension.nitrogen.dart';
-
-// Theme
-export 'src/theme/color_scheme.dart';
-export 'src/theme/style.dart';
-export 'src/theme/theme.dart';
-export 'src/theme/theme_data.dart';
-
-export 'src/theme/typography.dart';
-
-// Themes
-export 'src/theme/themes.dart';
-
-// Foundation
-export 'src/foundation/tappable.dart' hide FTappable;
-
-// Widgets
-export 'src/widgets/badge/badge.dart' hide FBadgeContent, Variant;
-export 'src/widgets/button/button.dart' hide FButtonContent, Variant;
-export 'src/widgets/card/card.dart' hide FCardContent;
-export 'src/widgets/dialog/dialog.dart' hide FDialogContent, FHorizontalDialogContent, FVerticalDialogContent;
-export 'src/widgets/header/header.dart';
-export 'src/widgets/tabs/tabs.dart';
-export 'src/widgets/text_field/text_field.dart';
-export 'src/widgets/scaffold.dart';
-export 'src/widgets/separator.dart';
-export 'src/widgets/switch.dart';
+export 'assets.dart';
+export 'theme.dart';
+export 'widgets.dart';
diff --git a/forui/lib/src/widgets/dialog/dialog_content.dart b/forui/lib/src/widgets/dialog/dialog_content.dart
index b35ce352f..9d057ce1d 100644
--- a/forui/lib/src/widgets/dialog/dialog_content.dart
+++ b/forui/lib/src/widgets/dialog/dialog_content.dart
@@ -102,6 +102,7 @@ class FHorizontalDialogContent extends FDialogContent {
required super.body,
required super.rawBody,
required super.actions,
+ super.key,
}) : super(alignment: CrossAxisAlignment.start, titleTextAlign: TextAlign.start, bodyTextAlign: TextAlign.start);
@override
@@ -125,6 +126,7 @@ class FVerticalDialogContent extends FDialogContent {
required super.body,
required super.rawBody,
required super.actions,
+ super.key,
}) : super(alignment: CrossAxisAlignment.center, titleTextAlign: TextAlign.center, bodyTextAlign: TextAlign.center);
@override
diff --git a/forui/lib/src/widgets/text_field/text_field.dart b/forui/lib/src/widgets/text_field/text_field.dart
index a118eefd5..140243b3e 100644
--- a/forui/lib/src/widgets/text_field/text_field.dart
+++ b/forui/lib/src/widgets/text_field/text_field.dart
@@ -10,74 +10,47 @@ import 'package:meta/meta.dart';
import 'package:forui/forui.dart';
part 'text_field_style.dart';
+part 'text_form_field.dart';
/// A text field.
///
-/// It lets the user enter text, either with hardware keyboard or with an onscreen keyboard.
+/// It lets the user enter text, either with hardware keyboard or with an onscreen keyboard. A [FTextField] is internally
+/// a [FormField], therefore it can be used in a [Form].
///
/// See:
/// * https://forui.dev/docs/text-field for working examples.
/// * [FTextFieldStyle] for customizing a text field's appearance.
+/// * [_Field] for a text field that integrates with a [Form].
/// * [TextField] for more details about working with a text field.
final class FTextField extends StatelessWidget {
- static Widget _defaultContextMenuBuilder(
+ static Widget _contextMenuBuilder(
BuildContext context,
- EditableTextState editableTextState,
+ EditableTextState state,
) =>
- AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState);
+ AdaptiveTextSelectionToolbar.editableText(editableTextState: state);
+
+ static Widget _errorBuilder(BuildContext context, String text) => Text(text);
/// The text field's style. Defaults to [FThemeData.textFieldStyle].
final FTextFieldStyle? style;
/// The label above a text field.
- ///
- /// ## Contract:
- /// Throws [AssertionError] if:
- /// * both [label] and [rawLabel] are not null
- final String? label;
-
- /// The raw label above a text field.
- ///
- /// ## Contract:
- /// Throws [AssertionError] if:
- /// * both [label] and [rawLabel] are not null
- final Widget? rawLabel;
+ final Widget? label;
/// The text to display when the text field is empty.
///
/// See [InputDecoration.hintText] for more information.
final String? hint;
- /// The maximum number of lines the [hint] can occupy. Defaults to the value of [TextField.maxLines] attribute.
- ///
- /// See [InputDecoration.hintMaxLines] for more information.
- final int? hintMaxLines;
-
- /// The help text.
- ///
- /// See [InputDecoration.helperText] for more information.
- final String? help;
-
/// The raw help text.
- final Widget? rawHelp;
-
- /// The maximum number of lines the [help] can occupy. Defaults to the value of [TextField.maxLines] attribute.
- ///
- /// See [InputDecoration.helperMaxLines] for more information.
- final int? helpMaxLines;
-
- /// The error text.
///
- /// See [InputDecoration.errorText] for more information.
- final String? error;
+ /// See [InputDecoration.helper] for more information.
+ final Widget? help;
/// The raw error text.
- final Widget? rawError;
-
- /// The maximum number of lines the [error] can occupy. Defaults to the value of [TextField.maxLines] attribute.
///
- /// See [InputDecoration.errorMaxLines] for more information.
- final int? errorMaxLines;
+ /// See [InputDecoration.error] for more information.
+ final Widget? error;
/// The configuration for the magnifier of this text field.
///
@@ -296,7 +269,7 @@ final class FTextField extends StatelessWidget {
/// Whitespace characters (e.g. newline, space, tab) are included in the character count.
///
/// If [maxLengthEnforcement] is [MaxLengthEnforcement.none], then more than [maxLength] characters may be entered,
- /// but the error counter and divider will switch to the [style]'s [FTextFieldStyle.error] when the limit is exceeded.
+ /// but the error counter and divider will switch to the [style]'s [FTextFieldStyle.errorStyle] when the limit is exceeded.
final int? maxLength;
/// Determines how the [maxLength] limit should be enforced.
@@ -477,27 +450,47 @@ final class FTextField extends StatelessWidget {
/// The suffix icon.
///
/// See [InputDecoration.suffixIcon] for more information.
- final Widget? suffixIcon;
+ final Widget? suffix;
- /// Creates a [FTextField].
+ /// An optional method to call with the final value when the form is saved via [FormState.save].
+ final FormFieldSetter? onSave;
+
+ /// An optional method that validates an input. Returns an error string to
+ /// display if the input is invalid, or null otherwise.
+ ///
+ /// The returned value is exposed by the [FormFieldState.errorText] property. [_Field] transform the text
+ /// using [...] before using the returned widget to override [error].
+ ///
+ /// Alternating between error and normal state can cause the height of the [_Field] to change if no other
+ /// subtext decoration is set on the field. To create a field whose height is fixed regardless of whether or not an
+ /// error is displayed, either wrap the [_Field] in a fixed height parent like [SizedBox], or set the [help]
+ /// parameter to a space.
+ final FormFieldValidator? validator;
+
+ /// An optional value to initialize the form field to, or null otherwise.
+ final String? initialValue;
+
+ /// Used to enable/disable this form field auto validation and update its error text.
+ ///
+ /// Defaults to [AutovalidateMode.disabled].
///
- /// ## Contract:
- /// Throws [AssertionError] if:
- /// * both [label] and [rawLabel] are not null
- /// * both [help] and [rawHelp] are not null
- /// * both [error] and [rawError] are not null
+ /// If [AutovalidateMode.onUserInteraction], this FormField will only auto-validate after its content changes. If
+ /// [AutovalidateMode.always], it will auto-validate even without user interaction. If [AutovalidateMode.disabled],
+ /// auto-validation will be disabled.
+ final AutovalidateMode? autovalidateMode;
+
+ /// A builder that transforms a [FormFieldState.errorText] into a widget. Defaults to a [Text] widget.
+ ///
+ /// The builder is called whenever [validator] returns an error text. It replaces [error] if it was provided.
+ final Widget Function(BuildContext, String) errorBuilder;
+
+ /// Creates a [FTextField].
const FTextField({
this.style,
this.label,
- this.rawLabel,
this.hint,
- this.hintMaxLines,
this.help,
- this.rawHelp,
- this.helpMaxLines,
this.error,
- this.rawError,
- this.errorMaxLines,
this.magnifierConfiguration,
this.controller,
this.focusNode,
@@ -537,28 +530,26 @@ final class FTextField extends StatelessWidget {
this.restorationId,
this.scribbleEnabled = true,
this.enableIMEPersonalizedLearning = true,
- this.contextMenuBuilder = _defaultContextMenuBuilder,
+ this.contextMenuBuilder = _contextMenuBuilder,
this.canRequestFocus = true,
this.undoController,
this.spellCheckConfiguration,
- this.suffixIcon,
- }) : assert(label == null || rawLabel == null, 'Cannot provide both a label and a rawLabel.'),
- assert(help == null || rawHelp == null, 'Cannot provide both a help and a rawHelp.'),
- assert(error == null || rawError == null, 'Cannot provide both an error and a rawError.');
+ this.suffix,
+ this.onSave,
+ this.validator,
+ this.initialValue,
+ this.autovalidateMode,
+ this.errorBuilder = _errorBuilder,
+ super.key,
+ });
/// Creates a [FTextField] configured for emails.
const FTextField.email({
this.style,
- this.label,
- this.rawLabel,
- this.hint = 'Email',
- this.hintMaxLines,
+ this.label = const Text('Email'),
+ this.hint,
this.help,
- this.rawHelp,
- this.helpMaxLines,
this.error,
- this.rawError,
- this.errorMaxLines,
this.magnifierConfiguration,
this.controller,
this.focusNode,
@@ -598,37 +589,29 @@ final class FTextField extends StatelessWidget {
this.restorationId,
this.scribbleEnabled = true,
this.enableIMEPersonalizedLearning = true,
- this.contextMenuBuilder = _defaultContextMenuBuilder,
+ this.contextMenuBuilder = _contextMenuBuilder,
this.canRequestFocus = true,
this.undoController,
this.spellCheckConfiguration,
- this.suffixIcon,
- }) : assert(label == null || rawLabel == null, 'Cannot provide both a label and a rawLabel.'),
- assert(help == null || rawHelp == null, 'Cannot provide both a help and a rawHelp.'),
- assert(error == null || rawError == null, 'Cannot provide both an error and a rawError.');
+ this.suffix,
+ this.onSave,
+ this.validator,
+ this.initialValue,
+ this.autovalidateMode,
+ this.errorBuilder = _errorBuilder,
+ super.key,
+ });
/// Creates a [FTextField] configured for passwords.
///
/// [autofillHints] defaults to [AutofillHints.password]. It should be overridden with [AutofillHints.newPassword]
/// when handling the creation of new passwords.
- ///
- /// ## Contract:
- /// Throws [AssertionError] if:
- /// * both [label] and [rawLabel] are not null
- /// * both [help] and [rawHelp] are not null
- /// * both [error] and [rawError] are not null
const FTextField.password({
this.style,
- this.label,
- this.rawLabel,
- this.hint = 'Password',
- this.hintMaxLines,
+ this.label = const Text('Password'),
+ this.hint,
this.help,
- this.rawHelp,
- this.helpMaxLines,
this.error,
- this.rawError,
- this.errorMaxLines,
this.magnifierConfiguration,
this.controller,
this.focusNode,
@@ -668,36 +651,30 @@ final class FTextField extends StatelessWidget {
this.restorationId,
this.scribbleEnabled = true,
this.enableIMEPersonalizedLearning = true,
- this.contextMenuBuilder = _defaultContextMenuBuilder,
+ this.contextMenuBuilder = _contextMenuBuilder,
this.canRequestFocus = true,
this.undoController,
this.spellCheckConfiguration,
- this.suffixIcon,
- }) : assert(label == null || rawLabel == null, 'Cannot provide both a label and a rawLabel.'),
- assert(help == null || rawHelp == null, 'Cannot provide both a help and a rawHelp.'),
- assert(error == null || rawError == null, 'Cannot provide both an error and a rawError.');
+ this.suffix,
+ this.onSave,
+ this.validator,
+ this.initialValue,
+ this.autovalidateMode,
+ this.errorBuilder = _errorBuilder,
+ super.key,
+ });
/// Creates a [FTextField] configured for multiline inputs.
///
- /// The text field's height can be configured by adjusting [minLines].
- ///
- /// ## Contract:
- /// Throws [AssertionError] if:
- /// * both [label] and [rawLabel] are not null
- /// * both [help] and [rawHelp] are not null
- /// * both [error] and [rawError] are not null
+ /// The text field's height can be configured by adjusting [minLines]. By default, the text field will expand every
+ /// time a new line is added. To limit the maximum height of the text field and make it scrollable, consider setting
+ /// [maxLines].
const FTextField.multiline({
this.style,
this.label,
- this.rawLabel,
this.hint,
- this.hintMaxLines,
this.help,
- this.rawHelp,
- this.helpMaxLines,
this.error,
- this.rawError,
- this.errorMaxLines,
this.magnifierConfiguration,
this.controller,
this.focusNode,
@@ -737,34 +714,30 @@ final class FTextField extends StatelessWidget {
this.restorationId,
this.scribbleEnabled = true,
this.enableIMEPersonalizedLearning = true,
- this.contextMenuBuilder = _defaultContextMenuBuilder,
+ this.contextMenuBuilder = _contextMenuBuilder,
this.canRequestFocus = true,
this.undoController,
this.spellCheckConfiguration,
- this.suffixIcon,
- }) : assert(label == null || rawLabel == null, 'Cannot provide both a label and a rawLabel.'),
- assert(help == null || rawHelp == null, 'Cannot provide both a help and a rawHelp.'),
- assert(error == null || rawError == null, 'Cannot provide both an error and a rawError.');
+ this.suffix,
+ this.onSave,
+ this.validator,
+ this.initialValue,
+ this.autovalidateMode,
+ this.errorBuilder = _errorBuilder,
+ super.key,
+ });
@override
Widget build(BuildContext context) {
final theme = context.theme;
- final typography = theme.typography;
final style = this.style ?? theme.textFieldStyle;
final stateStyle = switch (this) {
- _ when !enabled => style.disabled,
- _ when error != null || rawError != null => style.error,
- _ => style.enabled,
- };
- final materialLocalizations = Localizations.of(context, MaterialLocalizations);
-
- final label = switch ((this.label, rawLabel)) {
- (final String label, _) => Text(label),
- (_, final Widget label) => label,
- _ => null,
+ _ when !enabled => style.disabledStyle,
+ _ when error != null => style.errorStyle,
+ _ => style.enabledStyle,
};
- final textField = MergeSemantics(
+ final textFormField = MergeSemantics(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -773,7 +746,7 @@ final class FTextField extends StatelessWidget {
padding: const EdgeInsets.only(top: 4, bottom: 7),
child: DefaultTextStyle.merge(
style: stateStyle.labelTextStyle,
- child: label,
+ child: label!,
),
),
Material(
@@ -791,13 +764,19 @@ final class FTextField extends StatelessWidget {
primaryColor: style.cursorColor,
),
),
- child: _textField(context, typography, style, stateStyle),
+ child: _Field(
+ parent: this,
+ style: style,
+ stateStyle: stateStyle,
+ key: key,
+ ),
),
),
],
),
);
+ final materialLocalizations = Localizations.of(context, MaterialLocalizations);
return materialLocalizations == null
? Localizations(
locale: Localizations.maybeLocaleOf(context) ?? const Locale('en', 'US'),
@@ -806,131 +785,9 @@ final class FTextField extends StatelessWidget {
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
- child: textField,
+ child: textFormField,
)
- : textField;
- }
-
- Widget _textField(
- BuildContext context,
- FTypography typography,
- FTextFieldStyle style,
- FTextFieldStateStyle current,
- ) {
- final rawError = this.rawError == null
- ? this.rawError
- : DefaultTextStyle.merge(
- style: current.footerTextStyle,
- child: this.rawError!,
- );
-
- final rawHelp = this.rawHelp == null
- ? this.rawHelp
- : DefaultTextStyle.merge(
- style: current.footerTextStyle,
- child: this.rawHelp!,
- );
-
- return TextField(
- controller: controller,
- focusNode: focusNode,
- undoController: undoController,
- cursorErrorColor: style.cursorColor,
- decoration: InputDecoration(
- suffixIcon: suffixIcon,
- // See https://stackoverflow.com/questions/70771410/flutter-how-can-i-remove-the-content-padding-for-error-in-textformfield
- prefix: Padding(padding: EdgeInsets.only(left: style.contentPadding.left)),
- contentPadding: style.contentPadding.copyWith(left: 0),
- hintText: hint,
- hintStyle: current.hintTextStyle,
- hintMaxLines: hintMaxLines,
- helper: rawHelp,
- helperText: help,
- helperStyle: current.footerTextStyle,
- helperMaxLines: helpMaxLines,
- error: rawError,
- errorText: error,
- errorStyle: current.footerTextStyle,
- errorMaxLines: errorMaxLines,
- disabledBorder: OutlineInputBorder(
- borderSide: BorderSide(
- color: style.disabled.unfocused.color,
- width: style.disabled.unfocused.width,
- ),
- borderRadius: style.disabled.unfocused.radius,
- ),
- enabledBorder: OutlineInputBorder(
- borderSide: BorderSide(
- color: style.enabled.unfocused.color,
- width: style.enabled.unfocused.width,
- ),
- borderRadius: style.enabled.unfocused.radius,
- ),
- focusedBorder: OutlineInputBorder(
- borderSide: BorderSide(
- color: style.enabled.focused.color,
- width: style.enabled.focused.width,
- ),
- borderRadius: current.focused.radius,
- ),
- errorBorder: OutlineInputBorder(
- borderSide: BorderSide(
- color: style.error.unfocused.color,
- width: style.error.unfocused.width,
- ),
- borderRadius: style.error.unfocused.radius,
- ),
- focusedErrorBorder: OutlineInputBorder(
- borderSide: BorderSide(
- color: style.error.focused.color,
- width: style.error.focused.width,
- ),
- borderRadius: style.error.focused.radius,
- ),
- ),
- keyboardType: keyboardType,
- textInputAction: textInputAction,
- textCapitalization: textCapitalization,
- style: current.contentTextStyle,
- textAlign: textAlign,
- textAlignVertical: textAlignVertical,
- textDirection: textDirection,
- readOnly: readOnly,
- showCursor: showCursor,
- autofocus: autofocus,
- statesController: statesController,
- obscureText: obscureText,
- autocorrect: autocorrect,
- smartDashesType: smartDashesType,
- smartQuotesType: smartQuotesType,
- enableSuggestions: enableSuggestions,
- maxLines: maxLines,
- minLines: minLines,
- expands: expands,
- maxLength: maxLength,
- maxLengthEnforcement: maxLengthEnforcement,
- onChanged: onChange,
- onEditingComplete: onEditingComplete,
- onSubmitted: onSubmit,
- onAppPrivateCommand: onAppPrivateCommand,
- inputFormatters: inputFormatters,
- enabled: enabled,
- ignorePointers: ignorePointers,
- keyboardAppearance: style.keyboardAppearance,
- scrollPadding: style.scrollPadding,
- dragStartBehavior: dragStartBehavior,
- selectionControls: selectionControls,
- scrollController: scrollController,
- scrollPhysics: scrollPhysics,
- autofillHints: autofillHints,
- restorationId: restorationId,
- scribbleEnabled: scribbleEnabled,
- enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
- contextMenuBuilder: contextMenuBuilder,
- canRequestFocus: canRequestFocus,
- spellCheckConfiguration: spellCheckConfiguration,
- magnifierConfiguration: magnifierConfiguration,
- );
+ : textFormField;
}
@override
@@ -938,13 +795,7 @@ final class FTextField extends StatelessWidget {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('style', style))
- ..add(StringProperty('label', label))
..add(StringProperty('hint', hint))
- ..add(IntProperty('hintMaxLines', hintMaxLines))
- ..add(StringProperty('help', help))
- ..add(IntProperty('helpMaxLines', helpMaxLines))
- ..add(StringProperty('error', error))
- ..add(IntProperty('errorMaxLines', errorMaxLines))
..add(DiagnosticsProperty('magnifierConfiguration', magnifierConfiguration))
..add(DiagnosticsProperty('controller', controller))
..add(DiagnosticsProperty('focusNode', focusNode))
@@ -994,6 +845,11 @@ final class FTextField extends StatelessWidget {
..add(FlagProperty('canRequestFocus', value: canRequestFocus, ifTrue: 'canRequestFocus'))
..add(DiagnosticsProperty('undoController', undoController))
..add(DiagnosticsProperty('spellCheckConfiguration', spellCheckConfiguration))
- ..add(DiagnosticsProperty('suffixIcon', suffixIcon));
+ ..add(DiagnosticsProperty('suffixIcon', suffix))
+ ..add(ObjectFlagProperty.has('onSave', onSave))
+ ..add(ObjectFlagProperty.has('validator', validator))
+ ..add(StringProperty('initialValue', initialValue))
+ ..add(EnumProperty('autovalidateMode', autovalidateMode))
+ ..add(ObjectFlagProperty.has('errorBuilder', errorBuilder));
}
}
diff --git a/forui/lib/src/widgets/text_field/text_field_style.dart b/forui/lib/src/widgets/text_field/text_field_style.dart
index b21d71921..95e12e461 100644
--- a/forui/lib/src/widgets/text_field/text_field_style.dart
+++ b/forui/lib/src/widgets/text_field/text_field_style.dart
@@ -14,7 +14,7 @@ final class FTextFieldStyle with Diagnosticable {
/// The padding surrounding this text field's content.
///
- /// Defaults to `const EdgeInsets.symmetric(horizontal: 15, vertical: 5)`.
+ /// Defaults to `const EdgeInsets.symmetric(horizontal: 15, vertical: 15)`.
final EdgeInsets contentPadding;
/// Configures padding to edges surrounding a [Scrollable] when this text field scrolls into view.
@@ -28,22 +28,22 @@ final class FTextFieldStyle with Diagnosticable {
final EdgeInsets scrollPadding;
/// The style when this text field is enabled.
- final FTextFieldStateStyle enabled;
+ final FTextFieldStateStyle enabledStyle;
/// The style when this text field is enabled.
- final FTextFieldStateStyle disabled;
+ final FTextFieldStateStyle disabledStyle;
/// The style when this text field has an error.
- final FTextFieldStateStyle error;
+ final FTextFieldStateStyle errorStyle;
/// Creates a [FTextFieldStyle].
FTextFieldStyle({
required this.keyboardAppearance,
- required this.enabled,
- required this.disabled,
- required this.error,
+ required this.enabledStyle,
+ required this.disabledStyle,
+ required this.errorStyle,
this.cursorColor = CupertinoColors.activeBlue,
- this.contentPadding = const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
+ this.contentPadding = const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
this.scrollPadding = const EdgeInsets.all(20),
});
@@ -54,9 +54,9 @@ final class FTextFieldStyle with Diagnosticable {
required FStyle style,
}) : keyboardAppearance = colorScheme.brightness,
cursorColor = CupertinoColors.activeBlue,
- contentPadding = const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
+ contentPadding = const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
scrollPadding = const EdgeInsets.all(20.0),
- enabled = FTextFieldStateStyle.inherit(
+ enabledStyle = FTextFieldStateStyle.inherit(
labelColor: colorScheme.primary,
contentColor: colorScheme.primary,
hintColor: colorScheme.mutedForeground,
@@ -66,9 +66,9 @@ final class FTextFieldStyle with Diagnosticable {
typography: typography,
style: style,
),
- disabled = FTextFieldStateStyle.inherit(
- labelColor: colorScheme.primary,
- contentColor: colorScheme.primary,
+ disabledStyle = FTextFieldStateStyle.inherit(
+ labelColor: colorScheme.primary.withOpacity(0.7),
+ contentColor: colorScheme.primary.withOpacity(0.7),
hintColor: colorScheme.border.withOpacity(0.7),
footerColor: colorScheme.border.withOpacity(0.7),
focusedBorderColor: colorScheme.border.withOpacity(0.7),
@@ -76,7 +76,7 @@ final class FTextFieldStyle with Diagnosticable {
typography: typography,
style: style,
),
- error = FTextFieldStateStyle.inherit(
+ errorStyle = FTextFieldStateStyle.inherit(
labelColor: colorScheme.primary,
contentColor: colorScheme.primary,
hintColor: colorScheme.mutedForeground,
@@ -91,17 +91,17 @@ final class FTextFieldStyle with Diagnosticable {
///
/// ```dart
/// final style = FTextFieldStyle(
- /// enabled: ...,
- /// disabled: ...,
+ /// enabledStyle: ...,
+ /// disabledStyle: ...,
/// // Other arguments omitted for brevity
/// );
///
/// final copy = style.copyWith(
- /// disabled: ...,
+ /// disabledStyle: ...,
/// );
///
- /// print(style.enabled == copy.enabled); // true
- /// print(style.disabled == copy.disabled); // false
+ /// print(style.enabledStyle == copy.enabledStyle); // true
+ /// print(style.disabledStyle == copy.disabledStyle); // false
/// ```
@useResult
FTextFieldStyle copyWith({
@@ -109,18 +109,18 @@ final class FTextFieldStyle with Diagnosticable {
Color? cursorColor,
EdgeInsets? contentPadding,
EdgeInsets? scrollPadding,
- FTextFieldStateStyle? enabled,
- FTextFieldStateStyle? disabled,
- FTextFieldStateStyle? error,
+ FTextFieldStateStyle? enabledStyle,
+ FTextFieldStateStyle? disabledStyle,
+ FTextFieldStateStyle? errorStyle,
}) =>
FTextFieldStyle(
keyboardAppearance: keyboardAppearance ?? this.keyboardAppearance,
cursorColor: cursorColor ?? this.cursorColor,
contentPadding: contentPadding ?? this.contentPadding,
scrollPadding: scrollPadding ?? this.scrollPadding,
- enabled: enabled ?? this.enabled,
- disabled: disabled ?? this.disabled,
- error: error ?? this.error,
+ enabledStyle: enabledStyle ?? this.enabledStyle,
+ disabledStyle: disabledStyle ?? this.disabledStyle,
+ errorStyle: errorStyle ?? this.errorStyle,
);
@override
@@ -131,9 +131,9 @@ final class FTextFieldStyle with Diagnosticable {
..add(ColorProperty('cursorColor', cursorColor, defaultValue: CupertinoColors.activeBlue))
..add(DiagnosticsProperty('contentPadding', contentPadding))
..add(DiagnosticsProperty('scrollPadding', scrollPadding))
- ..add(DiagnosticsProperty('enabledBorder', enabled))
- ..add(DiagnosticsProperty('disabledBorder', disabled))
- ..add(DiagnosticsProperty('errorBorder', error));
+ ..add(DiagnosticsProperty('enabledStyle', enabledStyle))
+ ..add(DiagnosticsProperty('disabledStyle', disabledStyle))
+ ..add(DiagnosticsProperty('errorStyle', errorStyle));
}
@override
@@ -145,9 +145,9 @@ final class FTextFieldStyle with Diagnosticable {
cursorColor == other.cursorColor &&
contentPadding == other.contentPadding &&
scrollPadding == other.scrollPadding &&
- enabled == other.enabled &&
- disabled == other.disabled &&
- error == other.error;
+ enabledStyle == other.enabledStyle &&
+ disabledStyle == other.disabledStyle &&
+ errorStyle == other.errorStyle;
@override
int get hashCode =>
@@ -155,9 +155,9 @@ final class FTextFieldStyle with Diagnosticable {
cursorColor.hashCode ^
contentPadding.hashCode ^
scrollPadding.hashCode ^
- enabled.hashCode ^
- disabled.hashCode ^
- error.hashCode;
+ enabledStyle.hashCode ^
+ disabledStyle.hashCode ^
+ errorStyle.hashCode;
}
/// A [FTextField] state's style.
@@ -175,10 +175,10 @@ final class FTextFieldStateStyle with Diagnosticable {
final TextStyle footerTextStyle;
/// The border's color when focused.
- final FTextFieldBorderStyle focused;
+ final FTextFieldBorderStyle focusedStyle;
/// The border's style when unfocused.
- final FTextFieldBorderStyle unfocused;
+ final FTextFieldBorderStyle unfocusedStyle;
/// Creates a [FTextFieldStateStyle].
FTextFieldStateStyle({
@@ -186,8 +186,8 @@ final class FTextFieldStateStyle with Diagnosticable {
required this.contentTextStyle,
required this.hintTextStyle,
required this.footerTextStyle,
- required this.focused,
- required this.unfocused,
+ required this.focusedStyle,
+ required this.unfocusedStyle,
});
/// Creates a [FTextFieldStateStyle] that inherits its properties.
@@ -216,8 +216,8 @@ final class FTextFieldStateStyle with Diagnosticable {
fontFamily: typography.defaultFontFamily,
color: footerColor,
),
- focused = FTextFieldBorderStyle.inherit(color: focusedBorderColor, style: style),
- unfocused = FTextFieldBorderStyle.inherit(color: unfocusedBorderColor, style: style);
+ focusedStyle = FTextFieldBorderStyle.inherit(color: focusedBorderColor, style: style),
+ unfocusedStyle = FTextFieldBorderStyle.inherit(color: unfocusedBorderColor, style: style);
/// Returns a copy of this [FTextFieldStateStyle] with the given properties replaced.
///
@@ -241,16 +241,16 @@ final class FTextFieldStateStyle with Diagnosticable {
TextStyle? contentTextStyle,
TextStyle? hintTextStyle,
TextStyle? footerTextStyle,
- FTextFieldBorderStyle? focused,
- FTextFieldBorderStyle? unfocused,
+ FTextFieldBorderStyle? focusedStyle,
+ FTextFieldBorderStyle? unfocusedStyle,
}) =>
FTextFieldStateStyle(
labelTextStyle: labelTextStyle ?? this.labelTextStyle,
contentTextStyle: contentTextStyle ?? this.contentTextStyle,
hintTextStyle: hintTextStyle ?? this.hintTextStyle,
footerTextStyle: footerTextStyle ?? this.footerTextStyle,
- focused: focused ?? this.focused,
- unfocused: unfocused ?? this.unfocused,
+ focusedStyle: focusedStyle ?? this.focusedStyle,
+ unfocusedStyle: unfocusedStyle ?? this.unfocusedStyle,
);
@override
@@ -261,8 +261,8 @@ final class FTextFieldStateStyle with Diagnosticable {
..add(DiagnosticsProperty('contentTextStyle', contentTextStyle))
..add(DiagnosticsProperty('hintTextStyle', hintTextStyle))
..add(DiagnosticsProperty('footerTextStyle', footerTextStyle))
- ..add(DiagnosticsProperty('focused', focused))
- ..add(DiagnosticsProperty('unfocused', unfocused));
+ ..add(DiagnosticsProperty('focusedStyle', focusedStyle))
+ ..add(DiagnosticsProperty('unfocusedStyle', unfocusedStyle));
}
@override
@@ -273,16 +273,16 @@ final class FTextFieldStateStyle with Diagnosticable {
labelTextStyle == other.labelTextStyle &&
contentTextStyle == other.contentTextStyle &&
hintTextStyle == other.hintTextStyle &&
- focused == other.focused &&
- unfocused == other.unfocused;
+ focusedStyle == other.focusedStyle &&
+ unfocusedStyle == other.unfocusedStyle;
@override
int get hashCode =>
labelTextStyle.hashCode ^
contentTextStyle.hashCode ^
hintTextStyle.hashCode ^
- focused.hashCode ^
- unfocused.hashCode;
+ focusedStyle.hashCode ^
+ unfocusedStyle.hashCode;
}
/// A [FTextField] border's style.
diff --git a/forui/lib/src/widgets/text_field/text_form_field.dart b/forui/lib/src/widgets/text_field/text_form_field.dart
new file mode 100644
index 000000000..ffe232b77
--- /dev/null
+++ b/forui/lib/src/widgets/text_field/text_form_field.dart
@@ -0,0 +1,244 @@
+part of 'text_field.dart';
+
+class _Field extends FormField {
+ final FTextField parent;
+
+ _Field({
+ required FTextField parent,
+ required FTextFieldStyle style,
+ required FTextFieldStateStyle stateStyle,
+ required Key? key,
+ }) : this._(
+ parent: parent,
+ style: style,
+ stateStyle: stateStyle,
+ decoration: InputDecoration(
+ suffixIcon: parent.suffix,
+ // See https://stackoverflow.com/questions/70771410/flutter-how-can-i-remove-the-content-padding-for-error-in-textformfield
+ prefix: Padding(padding: EdgeInsets.only(left: style.contentPadding.left)),
+ contentPadding: style.contentPadding.copyWith(left: 0),
+ hintText: parent.hint,
+ hintStyle: stateStyle.hintTextStyle,
+ helper: parent.help == null
+ ? null
+ : DefaultTextStyle.merge(style: stateStyle.footerTextStyle, child: parent.help!),
+ helperStyle: stateStyle.footerTextStyle,
+ error: parent.error == null
+ ? null
+ : DefaultTextStyle.merge(style: stateStyle.footerTextStyle, child: parent.error!),
+ errorStyle: stateStyle.footerTextStyle,
+ disabledBorder: OutlineInputBorder(
+ borderSide: BorderSide(
+ color: style.disabledStyle.unfocusedStyle.color,
+ width: style.disabledStyle.unfocusedStyle.width,
+ ),
+ borderRadius: style.disabledStyle.unfocusedStyle.radius,
+ ),
+ enabledBorder: OutlineInputBorder(
+ borderSide: BorderSide(
+ color: style.enabledStyle.unfocusedStyle.color,
+ width: style.enabledStyle.unfocusedStyle.width,
+ ),
+ borderRadius: style.enabledStyle.unfocusedStyle.radius,
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderSide: BorderSide(
+ color: style.enabledStyle.focusedStyle.color,
+ width: style.enabledStyle.focusedStyle.width,
+ ),
+ borderRadius: stateStyle.focusedStyle.radius,
+ ),
+ errorBorder: OutlineInputBorder(
+ borderSide: BorderSide(
+ color: style.errorStyle.unfocusedStyle.color,
+ width: style.errorStyle.unfocusedStyle.width,
+ ),
+ borderRadius: style.errorStyle.unfocusedStyle.radius,
+ ),
+ focusedErrorBorder: OutlineInputBorder(
+ borderSide: BorderSide(
+ color: style.errorStyle.focusedStyle.color,
+ width: style.errorStyle.focusedStyle.width,
+ ),
+ borderRadius: style.errorStyle.focusedStyle.radius,
+ ),
+ ),
+ key: key,
+ );
+
+ _Field._({
+ required this.parent,
+ required FTextFieldStyle style,
+ required FTextFieldStateStyle stateStyle,
+ required InputDecoration decoration,
+ super.key,
+ }) : super(
+ onSaved: parent.onSave,
+ validator: parent.validator,
+ initialValue: parent.initialValue,
+ enabled: parent.enabled,
+ autovalidateMode: parent.autovalidateMode,
+ restorationId: parent.restorationId,
+ builder: (field) {
+ final state = field as _State;
+ return UnmanagedRestorationScope(
+ bucket: state.bucket,
+ child: TextField(
+ controller: state._effectiveController,
+ decoration: decoration.copyWith(
+ error: state.errorText == null ? null : parent.errorBuilder(state.context, state.errorText!),
+ ),
+ focusNode: parent.focusNode,
+ undoController: parent.undoController,
+ cursorErrorColor: style.cursorColor,
+ keyboardType: parent.keyboardType,
+ textInputAction: parent.textInputAction,
+ textCapitalization: parent.textCapitalization,
+ style: stateStyle.contentTextStyle,
+ textAlign: parent.textAlign,
+ textAlignVertical: parent.textAlignVertical,
+ textDirection: parent.textDirection,
+ readOnly: parent.readOnly,
+ showCursor: parent.showCursor,
+ autofocus: parent.autofocus,
+ statesController: parent.statesController,
+ obscureText: parent.obscureText,
+ autocorrect: parent.autocorrect,
+ smartDashesType: parent.smartDashesType,
+ smartQuotesType: parent.smartQuotesType,
+ enableSuggestions: parent.enableSuggestions,
+ maxLines: parent.maxLines,
+ minLines: parent.minLines,
+ expands: parent.expands,
+ maxLength: parent.maxLength,
+ maxLengthEnforcement: parent.maxLengthEnforcement,
+ onChanged: (value) {
+ field.didChange(value);
+ parent.onChange?.call(value);
+ },
+ onEditingComplete: parent.onEditingComplete,
+ onSubmitted: parent.onSubmit,
+ onAppPrivateCommand: parent.onAppPrivateCommand,
+ inputFormatters: parent.inputFormatters,
+ enabled: parent.enabled,
+ ignorePointers: parent.ignorePointers,
+ keyboardAppearance: style.keyboardAppearance,
+ scrollPadding: style.scrollPadding,
+ dragStartBehavior: parent.dragStartBehavior,
+ selectionControls: parent.selectionControls,
+ scrollController: parent.scrollController,
+ scrollPhysics: parent.scrollPhysics,
+ autofillHints: parent.autofillHints,
+ restorationId: parent.restorationId,
+ scribbleEnabled: parent.scribbleEnabled,
+ enableIMEPersonalizedLearning: parent.enableIMEPersonalizedLearning,
+ contextMenuBuilder: parent.contextMenuBuilder,
+ canRequestFocus: parent.canRequestFocus,
+ spellCheckConfiguration: parent.spellCheckConfiguration,
+ magnifierConfiguration: parent.magnifierConfiguration,
+ ),
+ );
+ },
+ );
+
+ @override
+ FormFieldState createState() => _State();
+}
+
+// This class is based on Material's _TextFormFieldState implementation.
+class _State extends FormFieldState {
+ RestorableTextEditingController? _controller;
+
+ @override
+ void initState() {
+ super.initState();
+
+ if (widget.parent.controller case final controller?) {
+ controller.addListener(_handleControllerChanged);
+ } else {
+ _registerController(RestorableTextEditingController(text: widget.initialValue));
+ }
+ }
+
+ @override
+ void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+ super.restoreState(oldBucket, initialRestore);
+ if (_controller case final controller?) {
+ registerForRestoration(controller, 'controller');
+ }
+
+ // Make sure to update the internal [FormFieldState] value to sync up with text editing controller value.
+ setValue(_effectiveController.text);
+ }
+
+ void _registerController(RestorableTextEditingController controller) {
+ assert(_controller == null, '_controller is already initialized.');
+ _controller = controller;
+ if (!restorePending) {
+ registerForRestoration(controller, 'controller');
+ }
+ }
+
+ @override
+ void didUpdateWidget(_Field old) {
+ super.didUpdateWidget(old);
+ if (widget.parent.controller == old.parent.controller) {
+ return;
+ }
+
+ widget.parent.controller?.addListener(_handleControllerChanged);
+ old.parent.controller?.removeListener(_handleControllerChanged);
+
+ switch ((widget.parent.controller, old.parent.controller)) {
+ case (final current?, _):
+ setValue(current.text);
+ if (_controller != null) {
+ unregisterFromRestoration(_controller!);
+ _controller?.dispose();
+ _controller = null;
+ }
+
+ case (null, final old?):
+ _registerController(RestorableTextEditingController.fromValue(old.value));
+ }
+ }
+
+ @override
+ void dispose() {
+ widget.parent.controller?.removeListener(_handleControllerChanged);
+ _controller?.dispose();
+ super.dispose();
+ }
+
+ @override
+ void didChange(String? value) {
+ super.didChange(value);
+ if (_effectiveController.text != value) {
+ _effectiveController.text = value ?? '';
+ }
+ }
+
+ @override
+ void reset() {
+ // Set the controller value before calling super.reset() to let _handleControllerChanged suppress the change.
+ _effectiveController.text = widget.initialValue ?? '';
+ super.reset();
+ widget.parent.onChange?.call(_effectiveController.text);
+ }
+
+ void _handleControllerChanged() {
+ // Suppress changes that originated from within this class.
+ //
+ // In the case where a controller has been passed in to this widget, we register this change listener. In these
+ // cases, we'll also receive change notifications for changes originating from within this class -- for example, the
+ // reset() method. In such cases, the FormField value will already have been set.
+ if (_effectiveController.text != value) {
+ didChange(_effectiveController.text);
+ }
+ }
+
+ @override
+ _Field get widget => super.widget as _Field;
+
+ TextEditingController get _effectiveController => widget.parent.controller ?? _controller!.value;
+}
diff --git a/forui/lib/theme.dart b/forui/lib/theme.dart
new file mode 100644
index 000000000..b0799fc43
--- /dev/null
+++ b/forui/lib/theme.dart
@@ -0,0 +1,10 @@
+/// Classes and functions for configuring the Forui widgets' theme. A theme configures the colors and typographic
+/// choices of Forui widgets.
+library forui.theme;
+
+export 'src/theme/color_scheme.dart';
+export 'src/theme/style.dart';
+export 'src/theme/theme.dart';
+export 'src/theme/theme_data.dart';
+export 'src/theme/themes.dart';
+export 'src/theme/typography.dart';
diff --git a/forui/lib/widgets.dart b/forui/lib/widgets.dart
new file mode 100644
index 000000000..7e2a630ef
--- /dev/null
+++ b/forui/lib/widgets.dart
@@ -0,0 +1,13 @@
+/// The Forui widgets and their corresponding styles.
+library forui.widgets;
+
+export 'src/widgets/badge/badge.dart' hide FBadgeContent, Variant;
+export 'src/widgets/button/button.dart' hide FButtonContent, Variant;
+export 'src/widgets/card/card.dart' hide FCardContent;
+export 'src/widgets/dialog/dialog.dart' hide FDialogContent, FHorizontalDialogContent, FVerticalDialogContent;
+export 'src/widgets/header/header.dart';
+export 'src/widgets/tabs/tabs.dart';
+export 'src/widgets/text_field/text_field.dart';
+export 'src/widgets/scaffold.dart';
+export 'src/widgets/separator.dart';
+export 'src/widgets/switch.dart';
diff --git a/forui/test/golden/text_field/default-zinc-dark-focused-no-text.png b/forui/test/golden/text_field/default-zinc-dark-focused-no-text.png
index 2ac12d5ec..187bdd0dd 100644
Binary files a/forui/test/golden/text_field/default-zinc-dark-focused-no-text.png and b/forui/test/golden/text_field/default-zinc-dark-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/default-zinc-dark-focused-raw-text.png b/forui/test/golden/text_field/default-zinc-dark-focused-raw-text.png
deleted file mode 100644
index 5dcd6ba28..000000000
Binary files a/forui/test/golden/text_field/default-zinc-dark-focused-raw-text.png and /dev/null differ
diff --git a/forui/test/golden/text_field/default-zinc-dark-focused-text.png b/forui/test/golden/text_field/default-zinc-dark-focused-text.png
index d8a675b6f..e1e23653e 100644
Binary files a/forui/test/golden/text_field/default-zinc-dark-focused-text.png and b/forui/test/golden/text_field/default-zinc-dark-focused-text.png differ
diff --git a/forui/test/golden/text_field/default-zinc-dark-unfocused-no-text.png b/forui/test/golden/text_field/default-zinc-dark-unfocused-no-text.png
index b786cc36c..d952843fd 100644
Binary files a/forui/test/golden/text_field/default-zinc-dark-unfocused-no-text.png and b/forui/test/golden/text_field/default-zinc-dark-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/default-zinc-dark-unfocused-raw-text.png b/forui/test/golden/text_field/default-zinc-dark-unfocused-raw-text.png
deleted file mode 100644
index c6a26c934..000000000
Binary files a/forui/test/golden/text_field/default-zinc-dark-unfocused-raw-text.png and /dev/null differ
diff --git a/forui/test/golden/text_field/default-zinc-dark-unfocused-text.png b/forui/test/golden/text_field/default-zinc-dark-unfocused-text.png
index 676f7ac82..3c5610f57 100644
Binary files a/forui/test/golden/text_field/default-zinc-dark-unfocused-text.png and b/forui/test/golden/text_field/default-zinc-dark-unfocused-text.png differ
diff --git a/forui/test/golden/text_field/default-zinc-light-focused-no-text.png b/forui/test/golden/text_field/default-zinc-light-focused-no-text.png
index 94487f96d..dc20b5878 100644
Binary files a/forui/test/golden/text_field/default-zinc-light-focused-no-text.png and b/forui/test/golden/text_field/default-zinc-light-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/default-zinc-light-focused-raw-text.png b/forui/test/golden/text_field/default-zinc-light-focused-raw-text.png
deleted file mode 100644
index 4ec24fee7..000000000
Binary files a/forui/test/golden/text_field/default-zinc-light-focused-raw-text.png and /dev/null differ
diff --git a/forui/test/golden/text_field/default-zinc-light-focused-text.png b/forui/test/golden/text_field/default-zinc-light-focused-text.png
index 8702a2549..53d6e6956 100644
Binary files a/forui/test/golden/text_field/default-zinc-light-focused-text.png and b/forui/test/golden/text_field/default-zinc-light-focused-text.png differ
diff --git a/forui/test/golden/text_field/default-zinc-light-unfocused-no-text.png b/forui/test/golden/text_field/default-zinc-light-unfocused-no-text.png
index bfbbb5088..d01bb0c05 100644
Binary files a/forui/test/golden/text_field/default-zinc-light-unfocused-no-text.png and b/forui/test/golden/text_field/default-zinc-light-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/default-zinc-light-unfocused-raw-text.png b/forui/test/golden/text_field/default-zinc-light-unfocused-raw-text.png
deleted file mode 100644
index f352346ad..000000000
Binary files a/forui/test/golden/text_field/default-zinc-light-unfocused-raw-text.png and /dev/null differ
diff --git a/forui/test/golden/text_field/default-zinc-light-unfocused-text.png b/forui/test/golden/text_field/default-zinc-light-unfocused-text.png
index e2d96e606..055e84d17 100644
Binary files a/forui/test/golden/text_field/default-zinc-light-unfocused-text.png and b/forui/test/golden/text_field/default-zinc-light-unfocused-text.png differ
diff --git a/forui/test/golden/text_field/email-zinc-dark-focused-no-text.png b/forui/test/golden/text_field/email-zinc-dark-focused-no-text.png
index 1de20344d..a8766fa44 100644
Binary files a/forui/test/golden/text_field/email-zinc-dark-focused-no-text.png and b/forui/test/golden/text_field/email-zinc-dark-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/email-zinc-dark-focused-text.png b/forui/test/golden/text_field/email-zinc-dark-focused-text.png
index eab4d0a32..fe940ee26 100644
Binary files a/forui/test/golden/text_field/email-zinc-dark-focused-text.png and b/forui/test/golden/text_field/email-zinc-dark-focused-text.png differ
diff --git a/forui/test/golden/text_field/email-zinc-dark-unfocused-no-text.png b/forui/test/golden/text_field/email-zinc-dark-unfocused-no-text.png
index 6944859cf..bc90b17fb 100644
Binary files a/forui/test/golden/text_field/email-zinc-dark-unfocused-no-text.png and b/forui/test/golden/text_field/email-zinc-dark-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/email-zinc-dark-unfocused-text.png b/forui/test/golden/text_field/email-zinc-dark-unfocused-text.png
index 440ce1b89..e534fcd91 100644
Binary files a/forui/test/golden/text_field/email-zinc-dark-unfocused-text.png and b/forui/test/golden/text_field/email-zinc-dark-unfocused-text.png differ
diff --git a/forui/test/golden/text_field/email-zinc-light-focused-no-text.png b/forui/test/golden/text_field/email-zinc-light-focused-no-text.png
index c9c21e4b6..a5326f8c4 100644
Binary files a/forui/test/golden/text_field/email-zinc-light-focused-no-text.png and b/forui/test/golden/text_field/email-zinc-light-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/email-zinc-light-focused-text.png b/forui/test/golden/text_field/email-zinc-light-focused-text.png
index 4260ac49d..2d7206399 100644
Binary files a/forui/test/golden/text_field/email-zinc-light-focused-text.png and b/forui/test/golden/text_field/email-zinc-light-focused-text.png differ
diff --git a/forui/test/golden/text_field/email-zinc-light-unfocused-no-text.png b/forui/test/golden/text_field/email-zinc-light-unfocused-no-text.png
index 428b51ae5..158e091a3 100644
Binary files a/forui/test/golden/text_field/email-zinc-light-unfocused-no-text.png and b/forui/test/golden/text_field/email-zinc-light-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/email-zinc-light-unfocused-text.png b/forui/test/golden/text_field/email-zinc-light-unfocused-text.png
index fd0e3a50f..cf22a7ed7 100644
Binary files a/forui/test/golden/text_field/email-zinc-light-unfocused-text.png and b/forui/test/golden/text_field/email-zinc-light-unfocused-text.png differ
diff --git a/forui/test/golden/text_field/error-zinc-dark-focused-no-text.png b/forui/test/golden/text_field/error-zinc-dark-focused-no-text.png
index e0872467b..13a5c8c2f 100644
Binary files a/forui/test/golden/text_field/error-zinc-dark-focused-no-text.png and b/forui/test/golden/text_field/error-zinc-dark-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/error-zinc-dark-focused-raw-text.png b/forui/test/golden/text_field/error-zinc-dark-focused-raw-text.png
deleted file mode 100644
index 86f2f090b..000000000
Binary files a/forui/test/golden/text_field/error-zinc-dark-focused-raw-text.png and /dev/null differ
diff --git a/forui/test/golden/text_field/error-zinc-dark-focused-text.png b/forui/test/golden/text_field/error-zinc-dark-focused-text.png
index b9306d66f..89e13da48 100644
Binary files a/forui/test/golden/text_field/error-zinc-dark-focused-text.png and b/forui/test/golden/text_field/error-zinc-dark-focused-text.png differ
diff --git a/forui/test/golden/text_field/error-zinc-dark-unfocused-no-text.png b/forui/test/golden/text_field/error-zinc-dark-unfocused-no-text.png
index 5e677fe03..08b964b39 100644
Binary files a/forui/test/golden/text_field/error-zinc-dark-unfocused-no-text.png and b/forui/test/golden/text_field/error-zinc-dark-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/error-zinc-dark-unfocused-raw-text.png b/forui/test/golden/text_field/error-zinc-dark-unfocused-raw-text.png
deleted file mode 100644
index 5aa877a3b..000000000
Binary files a/forui/test/golden/text_field/error-zinc-dark-unfocused-raw-text.png and /dev/null differ
diff --git a/forui/test/golden/text_field/error-zinc-dark-unfocused-text.png b/forui/test/golden/text_field/error-zinc-dark-unfocused-text.png
index 37256ed17..6d30d592e 100644
Binary files a/forui/test/golden/text_field/error-zinc-dark-unfocused-text.png and b/forui/test/golden/text_field/error-zinc-dark-unfocused-text.png differ
diff --git a/forui/test/golden/text_field/error-zinc-light-focused-no-text.png b/forui/test/golden/text_field/error-zinc-light-focused-no-text.png
index 291af3254..a22488944 100644
Binary files a/forui/test/golden/text_field/error-zinc-light-focused-no-text.png and b/forui/test/golden/text_field/error-zinc-light-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/error-zinc-light-focused-raw-text.png b/forui/test/golden/text_field/error-zinc-light-focused-raw-text.png
deleted file mode 100644
index 1aff3e426..000000000
Binary files a/forui/test/golden/text_field/error-zinc-light-focused-raw-text.png and /dev/null differ
diff --git a/forui/test/golden/text_field/error-zinc-light-focused-text.png b/forui/test/golden/text_field/error-zinc-light-focused-text.png
index 1acdc7b55..55d0d672c 100644
Binary files a/forui/test/golden/text_field/error-zinc-light-focused-text.png and b/forui/test/golden/text_field/error-zinc-light-focused-text.png differ
diff --git a/forui/test/golden/text_field/error-zinc-light-unfocused-no-text.png b/forui/test/golden/text_field/error-zinc-light-unfocused-no-text.png
index 9a398d3ae..80587f9de 100644
Binary files a/forui/test/golden/text_field/error-zinc-light-unfocused-no-text.png and b/forui/test/golden/text_field/error-zinc-light-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/error-zinc-light-unfocused-raw-text.png b/forui/test/golden/text_field/error-zinc-light-unfocused-raw-text.png
deleted file mode 100644
index aa6567c2f..000000000
Binary files a/forui/test/golden/text_field/error-zinc-light-unfocused-raw-text.png and /dev/null differ
diff --git a/forui/test/golden/text_field/error-zinc-light-unfocused-text.png b/forui/test/golden/text_field/error-zinc-light-unfocused-text.png
index 261380801..77ad0e4bc 100644
Binary files a/forui/test/golden/text_field/error-zinc-light-unfocused-text.png and b/forui/test/golden/text_field/error-zinc-light-unfocused-text.png differ
diff --git a/forui/test/golden/text_field/multiline-zinc-dark-focused-no-text.png b/forui/test/golden/text_field/multiline-zinc-dark-focused-no-text.png
index e7bd1720f..b0066e461 100644
Binary files a/forui/test/golden/text_field/multiline-zinc-dark-focused-no-text.png and b/forui/test/golden/text_field/multiline-zinc-dark-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/multiline-zinc-dark-focused-text.png b/forui/test/golden/text_field/multiline-zinc-dark-focused-text.png
index 923d6cfda..79d11255d 100644
Binary files a/forui/test/golden/text_field/multiline-zinc-dark-focused-text.png and b/forui/test/golden/text_field/multiline-zinc-dark-focused-text.png differ
diff --git a/forui/test/golden/text_field/multiline-zinc-dark-unfocused-no-text.png b/forui/test/golden/text_field/multiline-zinc-dark-unfocused-no-text.png
index b4132b382..b20beb27c 100644
Binary files a/forui/test/golden/text_field/multiline-zinc-dark-unfocused-no-text.png and b/forui/test/golden/text_field/multiline-zinc-dark-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/multiline-zinc-dark-unfocused-text.png b/forui/test/golden/text_field/multiline-zinc-dark-unfocused-text.png
index 095e4bb4b..5f3675dc9 100644
Binary files a/forui/test/golden/text_field/multiline-zinc-dark-unfocused-text.png and b/forui/test/golden/text_field/multiline-zinc-dark-unfocused-text.png differ
diff --git a/forui/test/golden/text_field/multiline-zinc-light-focused-no-text.png b/forui/test/golden/text_field/multiline-zinc-light-focused-no-text.png
index 8d0eddbf7..325e25fa3 100644
Binary files a/forui/test/golden/text_field/multiline-zinc-light-focused-no-text.png and b/forui/test/golden/text_field/multiline-zinc-light-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/multiline-zinc-light-focused-text.png b/forui/test/golden/text_field/multiline-zinc-light-focused-text.png
index 713a92806..633c0a281 100644
Binary files a/forui/test/golden/text_field/multiline-zinc-light-focused-text.png and b/forui/test/golden/text_field/multiline-zinc-light-focused-text.png differ
diff --git a/forui/test/golden/text_field/multiline-zinc-light-unfocused-no-text.png b/forui/test/golden/text_field/multiline-zinc-light-unfocused-no-text.png
index 619950776..af7249c78 100644
Binary files a/forui/test/golden/text_field/multiline-zinc-light-unfocused-no-text.png and b/forui/test/golden/text_field/multiline-zinc-light-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/multiline-zinc-light-unfocused-text.png b/forui/test/golden/text_field/multiline-zinc-light-unfocused-text.png
index 4762316b7..07346bef0 100644
Binary files a/forui/test/golden/text_field/multiline-zinc-light-unfocused-text.png and b/forui/test/golden/text_field/multiline-zinc-light-unfocused-text.png differ
diff --git a/forui/test/golden/text_field/password-zinc-dark-focused-no-text.png b/forui/test/golden/text_field/password-zinc-dark-focused-no-text.png
index ab655427f..a57679ba5 100644
Binary files a/forui/test/golden/text_field/password-zinc-dark-focused-no-text.png and b/forui/test/golden/text_field/password-zinc-dark-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/password-zinc-dark-focused-text.png b/forui/test/golden/text_field/password-zinc-dark-focused-text.png
index bb921559a..c20569ebd 100644
Binary files a/forui/test/golden/text_field/password-zinc-dark-focused-text.png and b/forui/test/golden/text_field/password-zinc-dark-focused-text.png differ
diff --git a/forui/test/golden/text_field/password-zinc-dark-unfocused-no-text.png b/forui/test/golden/text_field/password-zinc-dark-unfocused-no-text.png
index 3c99da536..edab691ec 100644
Binary files a/forui/test/golden/text_field/password-zinc-dark-unfocused-no-text.png and b/forui/test/golden/text_field/password-zinc-dark-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/password-zinc-dark-unfocused-text.png b/forui/test/golden/text_field/password-zinc-dark-unfocused-text.png
index 0b2fbc0ca..145614a3f 100644
Binary files a/forui/test/golden/text_field/password-zinc-dark-unfocused-text.png and b/forui/test/golden/text_field/password-zinc-dark-unfocused-text.png differ
diff --git a/forui/test/golden/text_field/password-zinc-light-focused-no-text.png b/forui/test/golden/text_field/password-zinc-light-focused-no-text.png
index cfd05c29b..948c48fb2 100644
Binary files a/forui/test/golden/text_field/password-zinc-light-focused-no-text.png and b/forui/test/golden/text_field/password-zinc-light-focused-no-text.png differ
diff --git a/forui/test/golden/text_field/password-zinc-light-focused-text.png b/forui/test/golden/text_field/password-zinc-light-focused-text.png
index 88057fffc..c1a8c6093 100644
Binary files a/forui/test/golden/text_field/password-zinc-light-focused-text.png and b/forui/test/golden/text_field/password-zinc-light-focused-text.png differ
diff --git a/forui/test/golden/text_field/password-zinc-light-unfocused-no-text.png b/forui/test/golden/text_field/password-zinc-light-unfocused-no-text.png
index e2a8f0c34..b12453454 100644
Binary files a/forui/test/golden/text_field/password-zinc-light-unfocused-no-text.png and b/forui/test/golden/text_field/password-zinc-light-unfocused-no-text.png differ
diff --git a/forui/test/golden/text_field/password-zinc-light-unfocused-text.png b/forui/test/golden/text_field/password-zinc-light-unfocused-text.png
index f42adf7b2..37b7b43a8 100644
Binary files a/forui/test/golden/text_field/password-zinc-light-unfocused-text.png and b/forui/test/golden/text_field/password-zinc-light-unfocused-text.png differ
diff --git a/forui/test/src/widgets/text_field/text_field_golden_test.dart b/forui/test/src/widgets/text_field/text_field_golden_test.dart
index 5986e5858..850b64248 100644
--- a/forui/test/src/widgets/text_field/text_field_golden_test.dart
+++ b/forui/test/src/widgets/text_field/text_field_golden_test.dart
@@ -18,60 +18,6 @@ void main() {
group('FTextField', () {
for (final (theme, theme_, _) in TestScaffold.themes) {
for (final (focused, focused_) in [('focused', true), ('unfocused', false)]) {
- testWidgets('default - $theme - $focused - raw text', (tester) async {
- await tester.pumpWidget(
- MaterialApp(
- home: TestScaffold(
- data: theme_,
- child: Padding(
- padding: const EdgeInsets.all(20),
- child: FTextField(
- controller: TextEditingController(text: 'short text'),
- autofocus: focused_,
- rawLabel: const Text('My Label'),
- hint: 'hint',
- rawHelp: const Text('Some help text.'),
- ),
- ),
- ),
- ),
- );
-
- await tester.pumpAndSettle();
-
- await expectLater(
- find.byType(TestScaffold),
- matchesGoldenFile('text_field/default-$theme-$focused-raw-text.png'),
- );
- });
-
- testWidgets('error - $theme - $focused - raw text', (tester) async {
- await tester.pumpWidget(
- MaterialApp(
- home: TestScaffold(
- data: theme_,
- child: Padding(
- padding: const EdgeInsets.all(20),
- child: FTextField(
- controller: TextEditingController(text: 'short text'),
- autofocus: focused_,
- rawLabel: const Text('My Label'),
- hint: 'hint',
- rawError: const Text('An error has occurred.'),
- ),
- ),
- ),
- ),
- );
-
- await tester.pumpAndSettle();
-
- await expectLater(
- find.byType(TestScaffold),
- matchesGoldenFile('text_field/error-$theme-$focused-raw-text.png'),
- );
- });
-
for (final (text, text_) in [('text', 'short text'), ('no-text', null)]) {
testWidgets('default - $theme - $focused - $text', (tester) async {
final controller = text_ == null ? null : TextEditingController(text: text_);
@@ -84,9 +30,9 @@ void main() {
child: FTextField(
controller: controller,
autofocus: focused_,
- label: 'My Label',
+ label: const Text('My Label'),
hint: 'hint',
- help: 'Some help text.',
+ help: const Text('Some help text.'),
),
),
),
@@ -112,9 +58,9 @@ void main() {
child: FTextField(
controller: controller,
autofocus: focused_,
- label: 'My Label',
+ label: const Text('My Label'),
hint: 'hint',
- error: 'An error has occurred.',
+ error: const Text('An error has occurred.'),
),
),
),
@@ -140,7 +86,6 @@ void main() {
child: FTextField.email(
controller: controller,
autofocus: focused_,
- label: 'Email',
hint: 'janedoe@foruslabs.com',
),
),
@@ -167,7 +112,6 @@ void main() {
child: FTextField.password(
controller: controller,
autofocus: focused_,
- label: 'Password',
hint: 'password',
),
),
@@ -196,7 +140,7 @@ void main() {
child: FTextField.multiline(
controller: controller,
autofocus: focused_,
- label: 'My Label',
+ label: const Text('My Label'),
hint: 'hint',
),
),
diff --git a/forui/test/src/widgets/text_field/text_field_test.dart b/forui/test/src/widgets/text_field/text_field_test.dart
index 6f41015fc..d29c77530 100644
--- a/forui/test/src/widgets/text_field/text_field_test.dart
+++ b/forui/test/src/widgets/text_field/text_field_test.dart
@@ -63,34 +63,5 @@ void main() {
expect(tester.takeException(), null);
});
-
- for (final constructor in [
- (string, raw) => FTextField(label: string, rawLabel: raw),
- (string, raw) => FTextField(help: string, rawHelp: raw),
- (string, raw) => FTextField(error: string, rawError: raw),
- (string, raw) => FTextField.email(label: string, rawLabel: raw),
- (string, raw) => FTextField.email(help: string, rawHelp: raw),
- (string, raw) => FTextField.email(error: string, rawError: raw),
- (string, raw) => FTextField.password(label: string, rawLabel: raw),
- (string, raw) => FTextField.password(help: string, rawHelp: raw),
- (string, raw) => FTextField.password(error: string, rawError: raw),
- (string, raw) => FTextField.multiline(label: string, rawLabel: raw),
- (string, raw) => FTextField.multiline(help: string, rawHelp: raw),
- (string, raw) => FTextField.multiline(error: string, rawError: raw),
- ]) {
- for (final (string, raw) in [
- (null, null),
- ('', null),
- (null, const SizedBox()),
- ]) {
- testWidgets('constructor does not throw error', (tester) async {
- expect(() => constructor(string, raw), returnsNormally);
- });
- }
-
- testWidgets('constructor title throws error', (tester) async {
- expect(() => constructor('', const SizedBox()), throwsAssertionError);
- });
- }
});
}
diff --git a/samples/README.md b/samples/README.md
index 996da9fee..2ec4b8a60 100644
--- a/samples/README.md
+++ b/samples/README.md
@@ -1,16 +1,4 @@
-# web
+# Samples
-Samples for the Forui website
-
-## Getting Started
-
-This project is a starting point for a Flutter application.
-
-A few resources to get you started if this is your first Flutter project:
-
-- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
-- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
-
-For help getting started with Flutter development, view the
-[online documentation](https://docs.flutter.dev/), which offers tutorials,
-samples, guidance on mobile development, and a full API reference.
+This project contains the samples used in [forui.dev](https://forui.dev). It is not expected to be consumed directly by
+users.
diff --git a/samples/analysis_options.yaml b/samples/analysis_options.yaml
index 0b4dd70f0..bcd4fecce 100644
--- a/samples/analysis_options.yaml
+++ b/samples/analysis_options.yaml
@@ -3,4 +3,8 @@ analyzer:
errors:
diagnostic_describe_all_properties: ignore
public_member_api_docs: ignore
- unused_result: ignore
\ No newline at end of file
+ unused_result: ignore
+
+linter:
+ rules:
+ - require_trailing_commas
\ No newline at end of file
diff --git a/samples/lib/main.dart b/samples/lib/main.dart
index acb2aac51..4c682b298 100644
--- a/samples/lib/main.dart
+++ b/samples/lib/main.dart
@@ -86,6 +86,10 @@ class _AppRouter extends $_AppRouter {
path: '/text-field/multiline',
page: MultilineTextFieldRoute.page,
),
+ AutoRoute(
+ path: '/text-field/form',
+ page: FormTextFieldRoute.page,
+ ),
AutoRoute(
path: '/scaffold/default',
page: ScaffoldRoute.page,
@@ -97,6 +101,6 @@ class _AppRouter extends $_AppRouter {
AutoRoute(
path: '/switch/default',
page: SwitchRoute.page,
- )
+ ),
];
}
diff --git a/samples/lib/widgets/scaffold.dart b/samples/lib/widgets/scaffold.dart
index 15b496541..76a1ae041 100644
--- a/samples/lib/widgets/scaffold.dart
+++ b/samples/lib/widgets/scaffold.dart
@@ -39,12 +39,12 @@ class ScaffoldPage extends SampleScaffold {
child: Column(
children: [
const FTextField(
- label: 'Name',
+ label: Text('Name'),
hint: 'John Renalo',
),
const SizedBox(height: 10),
const FTextField(
- label: 'Email',
+ label: Text('Email'),
hint: 'john@doe.com',
),
Padding(
diff --git a/samples/lib/widgets/tabs.dart b/samples/lib/widgets/tabs.dart
index 1d3ff1c3d..b3102eff7 100644
--- a/samples/lib/widgets/tabs.dart
+++ b/samples/lib/widgets/tabs.dart
@@ -29,12 +29,12 @@ class TabsPage extends SampleScaffold {
child: Column(
children: [
const FTextField(
- label: 'Name',
+ label: Text('Name'),
hint: 'John Renalo',
),
const SizedBox(height: 10),
const FTextField(
- label: 'Email',
+ label: Text('Email'),
hint: 'john@doe.com',
),
Padding(
@@ -58,9 +58,9 @@ class TabsPage extends SampleScaffold {
padding: const EdgeInsets.only(top: 10),
child: Column(
children: [
- const FTextField(label: 'Current password'),
+ const FTextField(label: Text('Current password')),
const SizedBox(height: 10),
- const FTextField(label: 'New password'),
+ const FTextField(label: Text('New password')),
Padding(
padding: const EdgeInsets.only(top: 24, bottom: 16),
child: FButton(
diff --git a/samples/lib/widgets/text_field.dart b/samples/lib/widgets/text_field.dart
index 340ff0e33..26d6a4065 100644
--- a/samples/lib/widgets/text_field.dart
+++ b/samples/lib/widgets/text_field.dart
@@ -22,7 +22,7 @@ class TextFieldPage extends SampleScaffold {
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
child: FTextField(
enabled: enabled,
- label: 'Email',
+ label: const Text('Email'),
hint: 'john@doe.com',
),
),
@@ -44,7 +44,6 @@ class PasswordTextFieldPage extends SampleScaffold {
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
child: FTextField.password(
controller: TextEditingController(text: 'My password'),
- label: 'Password',
),
),
],
@@ -64,9 +63,64 @@ class MultilineTextFieldPage extends SampleScaffold {
Padding(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 30),
child: FTextField.multiline(
- label: 'Leave a review',
+ label: Text('Leave a review'),
+ maxLines: 4,
),
),
],
);
}
+
+@RoutePage()
+class FormTextFieldPage extends SampleScaffold {
+ FormTextFieldPage({
+ @queryParam super.theme,
+ });
+
+ @override
+ Widget child(BuildContext context) => const Padding(
+ padding: EdgeInsets.all(15.0),
+ child: LoginForm(),
+ );
+}
+
+class LoginForm extends StatefulWidget {
+ const LoginForm({super.key});
+
+ @override
+ State createState() => _LoginFormState();
+}
+
+class _LoginFormState extends State {
+ final GlobalKey _formKey = GlobalKey();
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) => Form(
+ key: _formKey,
+ child: Column(
+ children: [
+ FTextField.email(
+ hint: 'janedoe@foruslabs.com',
+ help: const Text(''),
+ validator: (value) => (value?.contains('@') ?? false) ? null : 'Please enter a valid email.',
+ ),
+ const SizedBox(height: 4),
+ FTextField.password(
+ hint: '',
+ help: const Text(''),
+ validator: (value) => 8 <= (value?.length ?? 0) ? null : 'Password must be at least 8 characters long.',
+ ),
+ const SizedBox(height: 30),
+ FButton(
+ rawLabel: const Text('Login'),
+ onPress: () => _formKey.currentState!.validate(),
+ ),
+ ],
+ ),
+ );
+}