diff --git a/docs/pages/docs/checkbox.mdx b/docs/pages/docs/checkbox.mdx new file mode 100644 index 000000000..e9f719bec --- /dev/null +++ b/docs/pages/docs/checkbox.mdx @@ -0,0 +1,107 @@ +import { Tabs } from 'nextra/components'; +import { Widget } from "../../components/widget"; + +# Checkbox +A control that allows the user to toggle between checked and not checked. It can also be used in a form. + +On touch devices, it is recommended to use a [switch](/docs/switch) instead in most cases. + + + + + + + ```dart + FCheckBox(); + ``` + + + +## Usage + +### `FCheckbox(...)` + +```dart +FCheckbox( + enabled: true, + initialValue: true, +); +``` + +## Examples + +### Disabled + + + + + + + ```dart + FCheckbox( + enabled: false, + ); + ``` + + + +### 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( + crossAxisAlignment: CrossAxisAlignment.start, + 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: 4), + Row( + children: [ + const FCheckbox(), + const SizedBox(width: 7), + Text('Remember password?', style: context.theme.typography.sm), + ], + ), + const SizedBox(height: 30), + FButton( + label: const Text('Login'), + onPress: () => _formKey.currentState!.validate(), + ), + ], + ), + ); + } + ``` + + \ No newline at end of file diff --git a/docs/pages/docs/text-field.mdx b/docs/pages/docs/text-field.mdx index 23d7e871b..c2960d4f8 100644 --- a/docs/pages/docs/text-field.mdx +++ b/docs/pages/docs/text-field.mdx @@ -1,7 +1,7 @@ import { Tabs } from 'nextra/components'; import { Widget } from '../../components/widget'; -# Text Fields +# Text Field 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. diff --git a/forui/CHANGELOG.md b/forui/CHANGELOG.md index e24a950ce..773b4dcec 100644 --- a/forui/CHANGELOG.md +++ b/forui/CHANGELOG.md @@ -1,17 +1,24 @@ ## Next -### Changes -* Add `FHeader.nested` widget. -* Add `FProgress` widget. +### Additions +* Add `FCheckbox`. +* Add `FHeader.nested`. +* Add `FProgress`. + +### Enhancements * **Breaking** Move `FHeaderStyle` to `FHeaderStyles.rootStyle`. * **Breaking** Move `FHeaderActionStyle.padding` to `FRootHeaderStyle.actionSpacing`. -* **Breaking** Suffix style parameters with `Style`, i.e. `FRootHeaderStyle.action` has been renamed to `FRootHeaderStyle.actionStyle`. -* **Breaking** Raw fields have been removed, wrap strings with the Text() widget. Eg. `Button(label: 'Hello')` or `Button(rawLabel: 'Hello')` should be replaced with `Button(label: Text('Hello'))`. +* **Breaking** Suffix style parameters with `Style`, i.e. `FRootHeaderStyle.action` has been renamed to + `FRootHeaderStyle.actionStyle`. +* **Breaking** Raw fields have been removed, wrap strings with the Text() widget. E.g. `FButton(label: 'Hello')` or + `FButton(rawLabel: 'Hello')` should be replaced with `FButton(label: Text('Hello'))`. * Change `FTextField` to be usable in `Form`s. * Change `FTextFieldStyle`'s default vertical content padding from `5` to `15`. +* Split exports in `forui.dart` into sub-libraries. + +### Fixes * Fix missing `key` parameter in `FTextField` constructors. * **Breaking** `FButton.prefixIcon` and `FButton.suffixIcon` have been renamed to `FButton.prefix` and `FButton.suffix`. -* Split exports in `forui.dart` into sub-libraries. * Fix padding inconsistencies in `FCard` and `FDialog`. ## 0.1.0 diff --git a/forui/lib/src/theme/theme_data.dart b/forui/lib/src/theme/theme_data.dart index 9a24dbea4..c00815fb9 100644 --- a/forui/lib/src/theme/theme_data.dart +++ b/forui/lib/src/theme/theme_data.dart @@ -34,6 +34,9 @@ final class FThemeData with Diagnosticable { /// The card style. final FCardStyle cardStyle; + /// The checkbox style. + final FCheckboxStyle checkBoxStyle; + /// The dialog style. final FDialogStyle dialogStyle; @@ -69,6 +72,7 @@ final class FThemeData with Diagnosticable { required this.badgeStyles, required this.buttonStyles, required this.cardStyle, + required this.checkBoxStyle, required this.dialogStyle, required this.headerStyle, required this.progressStyle, @@ -96,6 +100,7 @@ final class FThemeData with Diagnosticable { badgeStyles: FBadgeStyles.inherit(colorScheme: colorScheme, typography: typography, style: style), buttonStyles: FButtonStyles.inherit(colorScheme: colorScheme, typography: typography, style: style), cardStyle: FCardStyle.inherit(colorScheme: colorScheme, typography: typography, style: style), + checkBoxStyle: FCheckboxStyle.inherit(colorScheme: colorScheme), dialogStyle: FDialogStyle.inherit(colorScheme: colorScheme, typography: typography, style: style), headerStyle: FHeaderStyles.inherit(colorScheme: colorScheme, typography: typography, style: style), progressStyle: FProgressStyle.inherit(colorScheme: colorScheme, style: style), @@ -131,6 +136,7 @@ final class FThemeData with Diagnosticable { FBadgeStyles? badgeStyles, FButtonStyles? buttonStyles, FCardStyle? cardStyle, + FCheckboxStyle? checkBoxStyle, FDialogStyle? dialogStyle, FHeaderStyles? headerStyle, FProgressStyle? progressStyle, @@ -147,6 +153,7 @@ final class FThemeData with Diagnosticable { badgeStyles: badgeStyles ?? this.badgeStyles, buttonStyles: buttonStyles ?? this.buttonStyles, cardStyle: cardStyle ?? this.cardStyle, + checkBoxStyle: checkBoxStyle ?? this.checkBoxStyle, dialogStyle: dialogStyle ?? this.dialogStyle, headerStyle: headerStyle ?? this.headerStyle, progressStyle: progressStyle ?? this.progressStyle, @@ -167,6 +174,7 @@ final class FThemeData with Diagnosticable { ..add(DiagnosticsProperty('badgeStyles', badgeStyles, level: DiagnosticLevel.debug)) ..add(DiagnosticsProperty('buttonStyles', buttonStyles, level: DiagnosticLevel.debug)) ..add(DiagnosticsProperty('cardStyle', cardStyle, level: DiagnosticLevel.debug)) + ..add(DiagnosticsProperty('checkBoxStyle', checkBoxStyle, level: DiagnosticLevel.debug)) ..add(DiagnosticsProperty('dialogStyle', dialogStyle, level: DiagnosticLevel.debug)) ..add(DiagnosticsProperty('headerStyle', headerStyle, level: DiagnosticLevel.debug)) ..add(DiagnosticsProperty('progressStyle', progressStyle)) @@ -188,6 +196,7 @@ final class FThemeData with Diagnosticable { badgeStyles == other.badgeStyles && buttonStyles == other.buttonStyles && cardStyle == other.cardStyle && + checkBoxStyle == other.checkBoxStyle && dialogStyle == other.dialogStyle && headerStyle == other.headerStyle && progressStyle == other.progressStyle && @@ -205,6 +214,7 @@ final class FThemeData with Diagnosticable { badgeStyles.hashCode ^ buttonStyles.hashCode ^ cardStyle.hashCode ^ + checkBoxStyle.hashCode ^ dialogStyle.hashCode ^ headerStyle.hashCode ^ progressStyle.hashCode ^ diff --git a/forui/lib/src/widgets/checkbox.dart b/forui/lib/src/widgets/checkbox.dart new file mode 100644 index 000000000..953c2815f --- /dev/null +++ b/forui/lib/src/widgets/checkbox.dart @@ -0,0 +1,347 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/semantics.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import 'package:forui/forui.dart'; + +/// A checkbox control that allows the user to toggle between checked and not checked. +/// +/// On touch devices, it is recommended to use a [FSwitch] instead in most cases. A [FCheckbox] is internally a +/// [FormField], therefore it can be used in a form. +/// +/// See: +/// * https://forui.dev/docs/checkbox for working examples. +/// * [FCheckboxStyle] for customizing a checkbox's appearance. +class FCheckbox extends StatelessWidget { + /// The semantic label of the dialog used by accessibility frameworks to announce screen transitions when the dialog + /// is opened and closed. + /// + /// See also: + /// * [SemanticsConfiguration.namesRoute], for a description of how this value is used. + final String? semanticLabel; + + /// Called when the user initiates a change to the FCheckBox's value: when they have checked or unchecked this box. + final ValueChanged? onChange; + + /// Whether this checkbox should focus itself if nothing else is already focused. Defaults to false. + final bool autofocus; + + /// Defines the [FocusNode] for this checkbox. + final FocusNode? focusNode; + + /// Handler called when the focus changes. + /// + /// Called with true if this widget's node gains focus, and false if it loses focus. + final ValueChanged? onFocusChange; + + /// 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. + final FormFieldValidator? validator; + + /// An optional value to initialize the form field to, or null otherwise. + final bool initialValue; + + /// Whether the form is able to receive user input. + /// + /// Defaults to true. If [autovalidateMode] is not [AutovalidateMode.disabled], the field will be auto validated. + /// Likewise, if this field is false, the widget will not be validated regardless of [autovalidateMode]. + final bool enabled; + + /// Used to enable/disable this form field auto validation and update its error text. + /// + /// Defaults to [AutovalidateMode.disabled]. + /// + /// 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; + + /// Restoration ID to save and restore the state of the form field. + /// + /// Setting the restoration ID to a non-null value results in whether or not the form field validation persists. + /// + /// The state of this widget is persisted in a [RestorationBucket] claimed from the surrounding [RestorationScope] + /// using the provided restoration ID. + /// + /// See also: + /// * [RestorationManager], which explains how state restoration works in Flutter. + final String? restorationId; + + /// Creates a [FCheckbox]. + const FCheckbox({ + this.semanticLabel, + this.onChange, + this.autofocus = false, + this.focusNode, + this.onFocusChange, + this.onSave, + this.validator, + this.initialValue = false, + this.enabled = true, + this.autovalidateMode, + this.restorationId, + super.key, + }); + + @override + Widget build(BuildContext context) { + final style = context.theme.checkBoxStyle; + final stateStyle = enabled ? style.enabledStyle : style.disabledStyle; + + return FocusableActionDetector( + autofocus: autofocus, + focusNode: focusNode, + onFocusChange: onFocusChange, + child: MouseRegion( + cursor: enabled ? SystemMouseCursors.click : MouseCursor.defer, + child: FormField( + onSaved: onSave, + validator: validator, + initialValue: initialValue, + enabled: enabled, + autovalidateMode: autovalidateMode, + restorationId: restorationId, + builder: (state) { + final value = state.value ?? initialValue; + return Semantics( + label: semanticLabel, + enabled: enabled, + checked: value, + child: GestureDetector( + onTap: enabled + ? () { + state.didChange(!value); + onChange?.call(!value); + } + : null, + child: AnimatedSwitcher( + duration: style.animationDuration, + switchInCurve: style.curve, + child: SizedBox.square( + key: ValueKey(value), + dimension: 20, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: stateStyle.borderColor, + width: 0.6, + ), + color: value ? stateStyle.checkedBackgroundColor : stateStyle.uncheckedBackgroundColor, + ), + child: value + ? FAssets.icons.check( + height: 15, + width: 15, + colorFilter: ColorFilter.mode( + stateStyle.iconColor, + BlendMode.srcIn, + ), + ) + : const SizedBox(), + ), + ), + ), + ), + ); + }, + ), + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(StringProperty('semanticLabel', semanticLabel)) + ..add(ObjectFlagProperty.has('onChange', onChange)) + ..add(DiagnosticsProperty('autofocus', autofocus)) + ..add(DiagnosticsProperty('focusNode', focusNode)) + ..add(ObjectFlagProperty.has('onFocusChange', onFocusChange)) + ..add(ObjectFlagProperty.has('onSave', onSave)) + ..add(ObjectFlagProperty.has('validator', validator)) + ..add(DiagnosticsProperty('initialValue', initialValue)) + ..add(DiagnosticsProperty('enabled', enabled)) + ..add(EnumProperty('autovalidateMode', autovalidateMode)) + ..add(StringProperty('restorationId', restorationId)); + } +} + +/// A [FCheckbox]'s style. +final class FCheckboxStyle with Diagnosticable { + /// The duration of the animation when the checkbox's switches between checked and unchecked. + /// + /// Defaults to `const Duration(milliseconds: 100)`. + final Duration animationDuration; + + /// The curve of the animation when the checkbox's switches between checked and unchecked. + /// + /// Defaults to [Curves.linear]. + final Curve curve; + + /// The checkbox's style when it's enabled. + final FCheckboxStateStyle enabledStyle; + + /// The checkbox's style when it's disabled. + final FCheckboxStateStyle disabledStyle; + + /// Creates a [FCheckboxStyle]. + FCheckboxStyle({ + required this.enabledStyle, + required this.disabledStyle, + this.animationDuration = const Duration(milliseconds: 100), + this.curve = Curves.linear, + }); + + /// Creates a [FCheckboxStyle] that inherits its properties from the given [FColorScheme]. + FCheckboxStyle.inherit({required FColorScheme colorScheme}) + : animationDuration = const Duration(milliseconds: 100), + curve = Curves.linear, + enabledStyle = FCheckboxStateStyle( + borderColor: colorScheme.primary, + iconColor: colorScheme.background, + checkedBackgroundColor: colorScheme.primary, + uncheckedBackgroundColor: colorScheme.background, + ), + disabledStyle = FCheckboxStateStyle( + borderColor: colorScheme.primary.withOpacity(0.5), + iconColor: colorScheme.background.withOpacity(0.5), + checkedBackgroundColor: colorScheme.primary.withOpacity(0.5), + uncheckedBackgroundColor: colorScheme.background.withOpacity(0.5), + ); + + /// Returns a copy of this [FCheckboxStyle] with the given properties replaced. + /// + /// ```dart + /// final style = FCheckboxStyle( + /// animationDuration: const Duration(minutes: 1), + /// curve: Curves.linear, + /// // Other arguments omitted for brevity. + /// ); + /// + /// final copy = style.copyWith( + /// curve: Curves.bounceIn, + /// ); + /// + /// print(style.animationDuration); // const Duration(minutes: 1) + /// print(copy.curve); // Curves.bounceIn + /// ``` + FCheckboxStyle copyWith({ + Duration? animationDuration, + Curve? curve, + FCheckboxStateStyle? enabledStyle, + FCheckboxStateStyle? disabledStyle, + }) => + FCheckboxStyle( + animationDuration: animationDuration ?? this.animationDuration, + curve: curve ?? this.curve, + enabledStyle: enabledStyle ?? this.enabledStyle, + disabledStyle: disabledStyle ?? this.disabledStyle, + ); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('animationDuration', animationDuration)) + ..add(DiagnosticsProperty('curve', curve)) + ..add(DiagnosticsProperty('enabledStyle', enabledStyle)) + ..add(DiagnosticsProperty('disabledStyle', disabledStyle)); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FCheckboxStyle && + runtimeType == other.runtimeType && + animationDuration == other.animationDuration && + curve == other.curve && + enabledStyle == other.enabledStyle && + disabledStyle == other.disabledStyle; + + @override + int get hashCode => animationDuration.hashCode ^ curve.hashCode ^ enabledStyle.hashCode ^ disabledStyle.hashCode; +} + +/// A checkbox state's style. +final class FCheckboxStateStyle with Diagnosticable { + /// The checkbox's border color. + final Color borderColor; + + /// The checked checkbox's icon's color. + final Color iconColor; + + /// The checked checkbox's background color. + final Color checkedBackgroundColor; + + /// The unchecked checkbox's background color. + final Color uncheckedBackgroundColor; + + /// Creates a [FCheckboxStateStyle]. + const FCheckboxStateStyle({ + required this.borderColor, + required this.iconColor, + required this.checkedBackgroundColor, + required this.uncheckedBackgroundColor, + }); + + /// Returns a copy of this [FCheckboxStateStyle] with the given properties replaced. + /// + /// ```dart + /// final style = FCheckBoxStateStyle( + /// iconColor: ..., + /// checkedBackgroundColor: ..., + /// // Other arguments omitted for brevity. + /// ); + /// + /// final copy = style.copyWith( + /// checkedBackgroundColor: ..., + /// ); + /// + /// print(style.iconColor == copy.iconColor); // true + /// print(style.checkedBackgroundColor == copy.checkedBackgroundColor); // false + /// ``` + FCheckboxStateStyle copyWith({ + Color? borderColor, + Color? iconColor, + Color? checkedBackgroundColor, + Color? uncheckedBackgroundColor, + }) => + FCheckboxStateStyle( + borderColor: borderColor ?? this.borderColor, + iconColor: iconColor ?? this.iconColor, + checkedBackgroundColor: checkedBackgroundColor ?? this.checkedBackgroundColor, + uncheckedBackgroundColor: uncheckedBackgroundColor ?? this.uncheckedBackgroundColor, + ); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ColorProperty('borderColor', borderColor)) + ..add(ColorProperty('checkedIconColor', iconColor)) + ..add(ColorProperty('checkedBackgroundColor', checkedBackgroundColor)) + ..add(ColorProperty('uncheckedBackgroundColor', uncheckedBackgroundColor)); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FCheckboxStateStyle && + runtimeType == other.runtimeType && + borderColor == other.borderColor && + iconColor == other.iconColor && + checkedBackgroundColor == other.checkedBackgroundColor && + uncheckedBackgroundColor == other.uncheckedBackgroundColor; + + @override + int get hashCode => + borderColor.hashCode ^ iconColor.hashCode ^ checkedBackgroundColor.hashCode ^ uncheckedBackgroundColor.hashCode; +} diff --git a/forui/lib/src/widgets/text_field/text_field.dart b/forui/lib/src/widgets/text_field/text_field.dart index 31c1c04b1..927ac7f82 100644 --- a/forui/lib/src/widgets/text_field/text_field.dart +++ b/forui/lib/src/widgets/text_field/text_field.dart @@ -61,7 +61,7 @@ final class FTextField extends StatelessWidget { /// Controls the text being edited. If null, this widget will create its own [TextEditingController]. final TextEditingController? controller; - /// Defines the keyboard focus for this + /// Defines the keyboard focus for this [FTextField]. /// /// See [TextField.focusNode] for more information. final FocusNode? focusNode; diff --git a/forui/lib/widgets.dart b/forui/lib/widgets.dart index 2334cd26d..25e657a06 100644 --- a/forui/lib/widgets.dart +++ b/forui/lib/widgets.dart @@ -4,6 +4,7 @@ library forui.widgets; export 'src/widgets/badge/badge.dart' hide Variant; export 'src/widgets/button/button.dart' hide Variant; export 'src/widgets/card/card.dart'; +export 'src/widgets/checkbox.dart'; export 'src/widgets/dialog/dialog.dart'; export 'src/widgets/header/header.dart'; export 'src/widgets/progress.dart'; diff --git a/forui/test/golden/check-box/zinc-dark-disabled-value.png b/forui/test/golden/check-box/zinc-dark-disabled-value.png new file mode 100644 index 000000000..964d8fee6 Binary files /dev/null and b/forui/test/golden/check-box/zinc-dark-disabled-value.png differ diff --git a/forui/test/golden/check-box/zinc-dark-enabled-no-value.png b/forui/test/golden/check-box/zinc-dark-enabled-no-value.png new file mode 100644 index 000000000..5e552fb69 Binary files /dev/null and b/forui/test/golden/check-box/zinc-dark-enabled-no-value.png differ diff --git a/forui/test/golden/check-box/zinc-dark-enabled-value.png b/forui/test/golden/check-box/zinc-dark-enabled-value.png new file mode 100644 index 000000000..46e4f5510 Binary files /dev/null and b/forui/test/golden/check-box/zinc-dark-enabled-value.png differ diff --git a/forui/test/golden/check-box/zinc-light-disabled-value.png b/forui/test/golden/check-box/zinc-light-disabled-value.png new file mode 100644 index 000000000..266d5ea38 Binary files /dev/null and b/forui/test/golden/check-box/zinc-light-disabled-value.png differ diff --git a/forui/test/golden/check-box/zinc-light-enabled-no-value.png b/forui/test/golden/check-box/zinc-light-enabled-no-value.png new file mode 100644 index 000000000..5e477dcc5 Binary files /dev/null and b/forui/test/golden/check-box/zinc-light-enabled-no-value.png differ diff --git a/forui/test/golden/check-box/zinc-light-enabled-value.png b/forui/test/golden/check-box/zinc-light-enabled-value.png new file mode 100644 index 000000000..82a23e217 Binary files /dev/null and b/forui/test/golden/check-box/zinc-light-enabled-value.png differ diff --git a/forui/test/src/widgets/checkbox_golden_test.dart b/forui/test/src/widgets/checkbox_golden_test.dart new file mode 100644 index 000000000..522fe0678 --- /dev/null +++ b/forui/test/src/widgets/checkbox_golden_test.dart @@ -0,0 +1,41 @@ +@Tags(['golden']) +library; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:forui/forui.dart'; +import '../test_scaffold.dart'; + +void main() { + group('FCheckBox', () { + for (final (name, theme, background) in TestScaffold.themes) { + for (final (enabled, initialValue) in [ + (true, true), + (true, false), + (false, true), + (true, true), + ]) { + testWidgets('$name with ${enabled ? 'enabled' : 'disabled'} & ${initialValue ? 'value' : 'no-value'}', + (tester) async { + await tester.pumpWidget( + TestScaffold( + data: theme, + background: background, + child: FCheckbox( + enabled: enabled, + initialValue: initialValue, + ), + ), + ); + + await expectLater( + find.byType(TestScaffold), + matchesGoldenFile( + 'check-box/$name-${enabled ? 'enabled' : 'disabled'}-${initialValue ? 'value' : 'no-value'}.png', + ), + ); + }); + } + } + }); +} diff --git a/samples/lib/main.dart b/samples/lib/main.dart index 90305cc4c..8896f9170 100644 --- a/samples/lib/main.dart +++ b/samples/lib/main.dart @@ -55,6 +55,14 @@ class _AppRouter extends $_AppRouter { path: '/card/default', page: CardRoute.page, ), + AutoRoute( + path: '/checkbox/default', + page: CheckboxRoute.page, + ), + AutoRoute( + path: '/checkbox/form', + page: FormCheckboxRoute.page, + ), AutoRoute( path: '/dialog/default', page: DialogRoute.page, diff --git a/samples/lib/widgets/checkbox.dart b/samples/lib/widgets/checkbox.dart new file mode 100644 index 000000000..b3a46b631 --- /dev/null +++ b/samples/lib/widgets/checkbox.dart @@ -0,0 +1,92 @@ +import 'package:flutter/widgets.dart'; + +import 'package:auto_route/auto_route.dart'; +import 'package:forui/forui.dart'; + +import 'package:forui_samples/sample_scaffold.dart'; + +@RoutePage() +class CheckboxPage extends SampleScaffold { + final bool enabled; + + CheckboxPage({ + @queryParam super.theme, + @queryParam this.enabled = false, + }); + + @override + Widget child(BuildContext context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30), + child: FCheckbox( + enabled: enabled, + ), + ), + ], + ); +} + +@RoutePage() +class FormCheckboxPage extends SampleScaffold { + FormCheckboxPage({ + @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( + crossAxisAlignment: CrossAxisAlignment.start, + 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: 4), + Row( + children: [ + const FCheckbox(), + const SizedBox(width: 7), + Text('Remember password?', style: context.theme.typography.sm), + ], + ), + const SizedBox(height: 30), + FButton( + label: const Text('Login'), + onPress: () => _formKey.currentState!.validate(), + ), + ], + ), + ); +}