From fc5cd2a7514097b688ffe3fe088447f4eb8156b9 Mon Sep 17 00:00:00 2001 From: Harry Sild <46851868+Kypsis@users.noreply.github.com> Date: Fri, 5 May 2023 14:41:55 +0300 Subject: [PATCH] feat: [MDS-503] Create Input widget (#144) --- example/lib/src/storybook/stories/tag.dart | 5 +- .../stories/{textarea.dart => text_area.dart} | 0 .../lib/src/storybook/stories/text_input.dart | 177 ++++++ example/lib/src/storybook/storybook.dart | 6 +- lib/moon_design.dart | 6 +- lib/src/theme/borders.dart | 23 +- .../text_area_colors.dart} | 10 + .../text_area_properties.dart} | 26 +- .../text_area_theme.dart} | 4 +- .../theme/text_input/text_input_colors.dart | 108 ++++ .../text_input/text_input_properties.dart | 81 +++ .../text_input_size_properties.dart | 120 ++++ .../theme/text_input/text_input_sizes.dart | 71 +++ .../theme/text_input/text_input_theme.dart | 70 +++ lib/src/theme/theme.dart | 13 +- lib/src/widgets/alert/alert.dart | 2 +- lib/src/widgets/authcode/authcode.dart | 3 +- lib/src/widgets/buttons/button.dart | 2 +- lib/src/widgets/chips/chip.dart | 2 +- lib/src/widgets/common/icons/moon_icon.dart | 4 +- .../text_area.dart} | 285 ++++++--- lib/src/widgets/text_input/text_input.dart | 584 ++++++++++++++++++ 22 files changed, 1468 insertions(+), 134 deletions(-) rename example/lib/src/storybook/stories/{textarea.dart => text_area.dart} (100%) create mode 100644 example/lib/src/storybook/stories/text_input.dart rename lib/src/theme/{textarea/textarea_colors.dart => text_area/text_area_colors.dart} (87%) rename lib/src/theme/{textarea/textarea_properties.dart => text_area/text_area_properties.dart} (72%) rename lib/src/theme/{textarea/textarea_theme.dart => text_area/text_area_theme.dart} (91%) create mode 100644 lib/src/theme/text_input/text_input_colors.dart create mode 100644 lib/src/theme/text_input/text_input_properties.dart create mode 100644 lib/src/theme/text_input/text_input_size_properties.dart create mode 100644 lib/src/theme/text_input/text_input_sizes.dart create mode 100644 lib/src/theme/text_input/text_input_theme.dart rename lib/src/widgets/{textarea/textarea.dart => text_area/text_area.dart} (59%) create mode 100644 lib/src/widgets/text_input/text_input.dart diff --git a/example/lib/src/storybook/stories/tag.dart b/example/lib/src/storybook/stories/tag.dart index 7b2114f2..0c1ccd0d 100644 --- a/example/lib/src/storybook/stories/tag.dart +++ b/example/lib/src/storybook/stories/tag.dart @@ -88,10 +88,7 @@ class TagStory extends Story { backgroundColor: backgroundColor, leading: showLeadingKnob ? const MoonIcon(MoonIcons.close_small_16) : null, label: showLabelKnob - ? Padding( - padding: EdgeInsets.only(top: setUpperCase ? 1.5 : 0), - child: Text(setUpperCase ? customLabelTextKnob.toUpperCase() : customLabelTextKnob), - ) + ? Text(setUpperCase ? customLabelTextKnob.toUpperCase() : customLabelTextKnob) : null, trailing: showTrailingKnob ? const MoonIcon(MoonIcons.close_small_16) : null, ), diff --git a/example/lib/src/storybook/stories/textarea.dart b/example/lib/src/storybook/stories/text_area.dart similarity index 100% rename from example/lib/src/storybook/stories/textarea.dart rename to example/lib/src/storybook/stories/text_area.dart diff --git a/example/lib/src/storybook/stories/text_input.dart b/example/lib/src/storybook/stories/text_input.dart new file mode 100644 index 00000000..ae72b2b2 --- /dev/null +++ b/example/lib/src/storybook/stories/text_input.dart @@ -0,0 +1,177 @@ +import 'package:example/src/storybook/common/color_options.dart'; +import 'package:flutter/material.dart'; +import 'package:moon_design/moon_design.dart'; +import 'package:storybook_flutter/storybook_flutter.dart'; + +TextEditingController _textEditingController = TextEditingController(); + +class TextInputStory extends Story { + TextInputStory() + : super( + name: "TextInput", + builder: (context) { + final textInputSizesKnob = context.knobs.nullable.options( + label: "textInputSize", + description: "Size variants for MoonTextInput.", + enabled: false, + initial: MoonTextInputSize.md, + options: const [ + Option(label: "sm", value: MoonTextInputSize.sm), + Option(label: "md", value: MoonTextInputSize.md), + Option(label: "lg", value: MoonTextInputSize.lg), + Option(label: "xl", value: MoonTextInputSize.xl) + ], + ); + + final textColorsKnob = context.knobs.nullable.options( + label: "textColor", + description: "MoonColors variants for MoonTextInput text.", + enabled: false, + initial: 0, // piccolo + options: colorOptions, + ); + + final textColor = colorTable(context)[textColorsKnob ?? 40]; + + final hintTextColorsKnob = context.knobs.nullable.options( + label: "hintTextColor", + description: "MoonColors variants for MoonTextInput hint text.", + enabled: false, + initial: 0, // piccolo + options: colorOptions, + ); + + final hintTextColor = colorTable(context)[hintTextColorsKnob ?? 40]; + + final backgroundColorsKnob = context.knobs.nullable.options( + label: "backgroundColor", + description: "MoonColors variants for MoonTextInput background.", + enabled: false, + initial: 0, // piccolo + options: colorOptions, + ); + + final backgroundColor = colorTable(context)[backgroundColorsKnob ?? 40]; + + final activeBorderColorsKnob = context.knobs.nullable.options( + label: "activeBorderColor", + description: "MoonColors variants for MoonTextInput active border.", + enabled: false, + initial: 0, // piccolo + options: colorOptions, + ); + + final activeBorderColor = colorTable(context)[activeBorderColorsKnob ?? 40]; + + final inactiveBorderColorsKnob = context.knobs.nullable.options( + label: "inactiveBorderColor", + description: "MoonColors variants for MoonTextInput inactive border.", + enabled: false, + initial: 0, // piccolo + options: colorOptions, + ); + + final inactiveBorderColor = colorTable(context)[inactiveBorderColorsKnob ?? 40]; + + final errorBorderColorsKnob = context.knobs.nullable.options( + label: "errorBorderColor", + description: "MoonColors variants for MoonTextInput error border.", + enabled: false, + initial: 0, // piccolo + options: colorOptions, + ); + + final errorBorderColor = colorTable(context)[errorBorderColorsKnob ?? 40]; + + final borderRadiusKnob = context.knobs.nullable.sliderInt( + label: "borderRadius", + description: "Border radius for MoonTextInput.", + enabled: false, + initial: 8, + max: 32, + ); + + final enabledKnob = context.knobs.boolean( + label: "enabled", + description: "Switch between enabled and disabled states.", + initial: true, + ); + + final showLeadingKnob = context.knobs.boolean( + label: "leading", + description: "Show widget in MoonTextInput leading slot.", + initial: true, + ); + + final showTrailingKnob = context.knobs.boolean( + label: "trailing", + description: "Show widget in MoonTextInput trailing slot.", + initial: true, + ); + + final showSupportingKnob = context.knobs.boolean( + label: "supporting", + description: "Show widget in MoonTextInput supporting slot.", + ); + + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 64), + Form( + child: Builder( + builder: (context) { + return Column( + children: [ + SizedBox( + height: 86, + child: MoonTextInput( + controller: _textEditingController, + textInputSize: textInputSizesKnob, + enabled: enabledKnob, + textColor: textColor, + hintTextColor: hintTextColor, + backgroundColor: backgroundColor, + activeBorderColor: activeBorderColor, + inactiveBorderColor: inactiveBorderColor, + errorBorderColor: errorBorderColor, + borderRadius: borderRadiusKnob != null + ? BorderRadius.circular(borderRadiusKnob.toDouble()) + : null, + hintText: "Enter your text here...", + validator: (value) => value?.length != null && value!.length < 10 + ? "The text should be longer than 10 characters." + : null, + leading: showLeadingKnob ? const MoonIcon(MoonIcons.search_24) : null, + trailing: showTrailingKnob + ? MoonButton.icon( + icon: MoonIcon( + MoonIcons.close_24, + color: DefaultTextStyle.of(context).style.color, + ), + buttonSize: MoonButtonSize.xs, + onTap: () => _textEditingController.clear(), + ) + : null, + supporting: showSupportingKnob ? const Text("Supporting text") : null, + errorBuilder: (context, errorText) => Text(errorText!), + ), + ), + const SizedBox(height: 8), + MoonFilledButton( + label: const Text("Submit"), + onTap: () => Form.of(context).validate(), + ) + ], + ); + }, + ), + ), + const SizedBox(height: 64), + ], + ), + ); + }, + ); +} diff --git a/example/lib/src/storybook/storybook.dart b/example/lib/src/storybook/storybook.dart index f7b97f56..dc31c227 100644 --- a/example/lib/src/storybook/storybook.dart +++ b/example/lib/src/storybook/storybook.dart @@ -16,7 +16,8 @@ import 'package:example/src/storybook/stories/popover.dart'; import 'package:example/src/storybook/stories/radio.dart'; import 'package:example/src/storybook/stories/switch.dart'; import 'package:example/src/storybook/stories/tag.dart'; -import 'package:example/src/storybook/stories/textarea.dart'; +import 'package:example/src/storybook/stories/text_area.dart'; +import 'package:example/src/storybook/stories/text_input.dart'; import 'package:example/src/storybook/stories/toast.dart'; import 'package:example/src/storybook/stories/tooltip.dart'; import 'package:flutter/material.dart'; @@ -43,7 +44,7 @@ class StorybookPage extends StatelessWidget { return Stack( children: [ Storybook( - initialStory: "Accordion", + initialStory: "TextInput", plugins: _plugins, brandingWidget: const MoonVersionWidget(), wrapperBuilder: (context, child) => MaterialApp( @@ -93,6 +94,7 @@ class StorybookPage extends StatelessWidget { SwitchStory(), TagStory(), TextAreaStory(), + TextInputStory(), ToastStory(), TooltipStory(), ], diff --git a/lib/moon_design.dart b/lib/moon_design.dart index 9c589e83..340d8282 100644 --- a/lib/moon_design.dart +++ b/lib/moon_design.dart @@ -21,7 +21,8 @@ export 'package:moon_design/src/theme/radio/radio_theme.dart'; export 'package:moon_design/src/theme/shadows.dart'; export 'package:moon_design/src/theme/sizes.dart'; export 'package:moon_design/src/theme/switch/switch_theme.dart'; -export 'package:moon_design/src/theme/textarea/textarea_theme.dart'; +export 'package:moon_design/src/theme/text_area/text_area_theme.dart'; +export 'package:moon_design/src/theme/text_input/text_input_theme.dart'; export 'package:moon_design/src/theme/theme.dart'; export 'package:moon_design/src/theme/toast/toast_theme.dart'; export 'package:moon_design/src/theme/tooltip/tooltip_theme.dart'; @@ -63,6 +64,7 @@ export 'package:moon_design/src/widgets/progress/linear_progress.dart'; export 'package:moon_design/src/widgets/radio/radio.dart'; export 'package:moon_design/src/widgets/switch/switch.dart'; export 'package:moon_design/src/widgets/tag/tag.dart'; -export 'package:moon_design/src/widgets/textarea/textarea.dart'; +export 'package:moon_design/src/widgets/text_area/text_area.dart'; +export 'package:moon_design/src/widgets/text_input/text_input.dart'; export 'package:moon_design/src/widgets/toast/toast.dart'; export 'package:moon_design/src/widgets/tooltip/tooltip.dart'; diff --git a/lib/src/theme/borders.dart b/lib/src/theme/borders.dart index cddd2d13..06a06264 100644 --- a/lib/src/theme/borders.dart +++ b/lib/src/theme/borders.dart @@ -13,7 +13,8 @@ class MoonBorders extends ThemeExtension with DiagnosticableTreeMix surfaceSm: BorderRadius.all(Radius.circular(8)), surfaceMd: BorderRadius.all(Radius.circular(12)), surfaceLg: BorderRadius.all(Radius.circular(16)), - borderWidth: 1, + defaultBorderWidth: 1, + activeBorderWidth: 1.5, ); /// Interactive radius XS. @@ -38,7 +39,10 @@ class MoonBorders extends ThemeExtension with DiagnosticableTreeMix final BorderRadiusGeometry surfaceLg; /// Default border width. - final double borderWidth; + final double defaultBorderWidth; + + /// Active border width. + final double activeBorderWidth; const MoonBorders({ required this.interactiveXs, @@ -48,7 +52,8 @@ class MoonBorders extends ThemeExtension with DiagnosticableTreeMix required this.surfaceSm, required this.surfaceMd, required this.surfaceLg, - required this.borderWidth, + required this.defaultBorderWidth, + required this.activeBorderWidth, }); @override @@ -60,7 +65,8 @@ class MoonBorders extends ThemeExtension with DiagnosticableTreeMix BorderRadiusGeometry? surfaceSm, BorderRadiusGeometry? surfaceMd, BorderRadiusGeometry? surfaceLg, - double? borderWidth, + double? defaultBorderWidth, + double? activeBorderWidth, }) { return MoonBorders( interactiveXs: interactiveXs ?? this.interactiveXs, @@ -70,7 +76,8 @@ class MoonBorders extends ThemeExtension with DiagnosticableTreeMix surfaceSm: surfaceSm ?? this.surfaceSm, surfaceMd: surfaceMd ?? this.surfaceMd, surfaceLg: surfaceLg ?? this.surfaceLg, - borderWidth: borderWidth ?? this.borderWidth, + defaultBorderWidth: defaultBorderWidth ?? this.defaultBorderWidth, + activeBorderWidth: activeBorderWidth ?? this.activeBorderWidth, ); } @@ -86,7 +93,8 @@ class MoonBorders extends ThemeExtension with DiagnosticableTreeMix surfaceSm: BorderRadiusGeometry.lerp(surfaceSm, other.surfaceSm, t)!, surfaceMd: BorderRadiusGeometry.lerp(surfaceMd, other.surfaceMd, t)!, surfaceLg: BorderRadiusGeometry.lerp(surfaceLg, other.surfaceLg, t)!, - borderWidth: lerpDouble(borderWidth, other.borderWidth, t)!, + defaultBorderWidth: lerpDouble(defaultBorderWidth, other.defaultBorderWidth, t)!, + activeBorderWidth: lerpDouble(activeBorderWidth, other.activeBorderWidth, t)!, ); } @@ -102,6 +110,7 @@ class MoonBorders extends ThemeExtension with DiagnosticableTreeMix ..add(DiagnosticsProperty("surfaceSm", surfaceSm)) ..add(DiagnosticsProperty("surfaceMd", surfaceMd)) ..add(DiagnosticsProperty("surfaceLg", surfaceLg)) - ..add(DoubleProperty("borderWidth", borderWidth)); + ..add(DoubleProperty("defaultBorderWidth", defaultBorderWidth)) + ..add(DoubleProperty("activeBorderWidth", activeBorderWidth)); } } diff --git a/lib/src/theme/textarea/textarea_colors.dart b/lib/src/theme/text_area/text_area_colors.dart similarity index 87% rename from lib/src/theme/textarea/textarea_colors.dart rename to lib/src/theme/text_area/text_area_colors.dart index 394ba34c..21aa2363 100644 --- a/lib/src/theme/textarea/textarea_colors.dart +++ b/lib/src/theme/text_area/text_area_colors.dart @@ -10,6 +10,7 @@ class MoonTextAreaColors extends ThemeExtension with Diagnos activeBorderColor: MoonColors.light.piccolo, inactiveBorderColor: MoonColors.light.beerus, errorBorderColor: MoonColors.light.chiChi100, + hoverBorderColor: MoonColors.light.beerus, hintTextColor: MoonColors.light.trunks, ); @@ -18,6 +19,7 @@ class MoonTextAreaColors extends ThemeExtension with Diagnos activeBorderColor: MoonColors.dark.piccolo, inactiveBorderColor: MoonColors.dark.beerus, errorBorderColor: MoonColors.dark.chiChi100, + hoverBorderColor: MoonColors.dark.beerus, hintTextColor: MoonColors.dark.trunks, ); @@ -33,6 +35,9 @@ class MoonTextAreaColors extends ThemeExtension with Diagnos /// TextArea error border color. final Color errorBorderColor; + /// TextArea error border color. + final Color hoverBorderColor; + /// TextArea hint text color. final Color hintTextColor; @@ -41,6 +46,7 @@ class MoonTextAreaColors extends ThemeExtension with Diagnos required this.activeBorderColor, required this.inactiveBorderColor, required this.errorBorderColor, + required this.hoverBorderColor, required this.hintTextColor, }); @@ -50,6 +56,7 @@ class MoonTextAreaColors extends ThemeExtension with Diagnos Color? activeBorderColor, Color? inactiveBorderColor, Color? errorBorderColor, + Color? hoverBorderColor, Color? hintTextColor, }) { return MoonTextAreaColors( @@ -57,6 +64,7 @@ class MoonTextAreaColors extends ThemeExtension with Diagnos activeBorderColor: activeBorderColor ?? this.activeBorderColor, inactiveBorderColor: inactiveBorderColor ?? this.inactiveBorderColor, errorBorderColor: errorBorderColor ?? this.errorBorderColor, + hoverBorderColor: hoverBorderColor ?? this.hoverBorderColor, hintTextColor: hintTextColor ?? this.hintTextColor, ); } @@ -70,6 +78,7 @@ class MoonTextAreaColors extends ThemeExtension with Diagnos activeBorderColor: Color.lerp(activeBorderColor, other.activeBorderColor, t)!, inactiveBorderColor: Color.lerp(inactiveBorderColor, other.inactiveBorderColor, t)!, errorBorderColor: Color.lerp(errorBorderColor, other.errorBorderColor, t)!, + hoverBorderColor: Color.lerp(hoverBorderColor, other.hoverBorderColor, t)!, hintTextColor: Color.lerp(hintTextColor, other.hintTextColor, t)!, ); } @@ -83,6 +92,7 @@ class MoonTextAreaColors extends ThemeExtension with Diagnos ..add(ColorProperty("activeBorderColor", activeBorderColor)) ..add(ColorProperty("inactiveBorderColor", inactiveBorderColor)) ..add(ColorProperty("errorBorderColor", errorBorderColor)) + ..add(ColorProperty("hoverBorderColor", hoverBorderColor)) ..add(ColorProperty("hintTextColor", hintTextColor)); } } diff --git a/lib/src/theme/textarea/textarea_properties.dart b/lib/src/theme/text_area/text_area_properties.dart similarity index 72% rename from lib/src/theme/textarea/textarea_properties.dart rename to lib/src/theme/text_area/text_area_properties.dart index af85016e..a706c734 100644 --- a/lib/src/theme/textarea/textarea_properties.dart +++ b/lib/src/theme/text_area/text_area_properties.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:moon_design/src/theme/borders.dart'; +import 'package:moon_design/src/theme/sizes.dart'; import 'package:moon_design/src/theme/typography/text_styles.dart'; @immutable @@ -10,9 +11,10 @@ class MoonTextAreaProperties extends ThemeExtension with borderRadius: MoonBorders.borders.interactiveSm, transitionDuration: const Duration(milliseconds: 200), transitionCurve: Curves.easeInOutCubic, + supportingPadding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x3s, vertical: MoonSizes.sizes.x4s), textPadding: const EdgeInsets.all(16), textStyle: MoonTextStyles.body.text16, - errorTextStyle: MoonTextStyles.body.text12, + supportingTextStyle: MoonTextStyles.body.text12, ); /// TextArea border radius. @@ -24,22 +26,26 @@ class MoonTextAreaProperties extends ThemeExtension with /// TextArea transition curve. final Curve transitionCurve; + /// The padding around TextArea supporting widget or error builder. + final EdgeInsetsGeometry supportingPadding; + /// TextArea text padding. final EdgeInsetsGeometry textPadding; /// TextArea text style. final TextStyle textStyle; - /// TextArea error text style. - final TextStyle errorTextStyle; + /// TextArea supporting or error text style. + final TextStyle supportingTextStyle; const MoonTextAreaProperties({ required this.borderRadius, required this.transitionDuration, required this.transitionCurve, + required this.supportingPadding, required this.textPadding, required this.textStyle, - required this.errorTextStyle, + required this.supportingTextStyle, }); @override @@ -47,17 +53,19 @@ class MoonTextAreaProperties extends ThemeExtension with BorderRadiusGeometry? borderRadius, Duration? transitionDuration, Curve? transitionCurve, + EdgeInsetsGeometry? supportingPadding, EdgeInsetsGeometry? textPadding, TextStyle? textStyle, - TextStyle? errorTextStyle, + TextStyle? supportingTextStyle, }) { return MoonTextAreaProperties( borderRadius: borderRadius ?? this.borderRadius, transitionDuration: transitionDuration ?? this.transitionDuration, transitionCurve: transitionCurve ?? this.transitionCurve, + supportingPadding: supportingPadding ?? this.supportingPadding, textPadding: textPadding ?? this.textPadding, textStyle: textStyle ?? this.textStyle, - errorTextStyle: errorTextStyle ?? this.errorTextStyle, + supportingTextStyle: supportingTextStyle ?? this.supportingTextStyle, ); } @@ -69,9 +77,10 @@ class MoonTextAreaProperties extends ThemeExtension with borderRadius: BorderRadiusGeometry.lerp(borderRadius, other.borderRadius, t)!, transitionDuration: lerpDuration(transitionDuration, other.transitionDuration, t), transitionCurve: other.transitionCurve, + supportingPadding: EdgeInsetsGeometry.lerp(supportingPadding, other.supportingPadding, t)!, textPadding: EdgeInsetsGeometry.lerp(textPadding, other.textPadding, t)!, textStyle: TextStyle.lerp(textStyle, other.textStyle, t)!, - errorTextStyle: TextStyle.lerp(errorTextStyle, other.errorTextStyle, t)!, + supportingTextStyle: TextStyle.lerp(supportingTextStyle, other.supportingTextStyle, t)!, ); } @@ -83,8 +92,9 @@ class MoonTextAreaProperties extends ThemeExtension with ..add(DiagnosticsProperty("borderRadius", borderRadius)) ..add(DiagnosticsProperty("transitionDuration", transitionDuration)) ..add(DiagnosticsProperty("transitionCurve", transitionCurve)) + ..add(DiagnosticsProperty("supportingPadding", supportingPadding)) ..add(DiagnosticsProperty("textPadding", textPadding)) ..add(DiagnosticsProperty("textStyle", textStyle)) - ..add(DiagnosticsProperty("errorTextStyle", errorTextStyle)); + ..add(DiagnosticsProperty("supportingTextStyle", supportingTextStyle)); } } diff --git a/lib/src/theme/textarea/textarea_theme.dart b/lib/src/theme/text_area/text_area_theme.dart similarity index 91% rename from lib/src/theme/textarea/textarea_theme.dart rename to lib/src/theme/text_area/text_area_theme.dart index f7467a24..7d65d6b9 100644 --- a/lib/src/theme/textarea/textarea_theme.dart +++ b/lib/src/theme/text_area/text_area_theme.dart @@ -1,8 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:moon_design/src/theme/textarea/textarea_colors.dart'; -import 'package:moon_design/src/theme/textarea/textarea_properties.dart'; +import 'package:moon_design/src/theme/text_area/text_area_colors.dart'; +import 'package:moon_design/src/theme/text_area/text_area_properties.dart'; @immutable class MoonTextAreaTheme extends ThemeExtension with DiagnosticableTreeMixin { diff --git a/lib/src/theme/text_input/text_input_colors.dart b/lib/src/theme/text_input/text_input_colors.dart new file mode 100644 index 00000000..c366faec --- /dev/null +++ b/lib/src/theme/text_input/text_input_colors.dart @@ -0,0 +1,108 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/colors.dart'; + +@immutable +class MoonTextInputColors extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonTextInputColors( + backgroundColor: MoonColors.light.gohan, + activeBorderColor: MoonColors.light.piccolo, + inactiveBorderColor: MoonColors.light.beerus, + errorBorderColor: MoonColors.light.chiChi100, + hoverBorderColor: MoonColors.light.beerus, + labelTextColor: MoonColors.light.trunks, + supportingTextColor: MoonColors.light.trunks, + ); + + static final dark = MoonTextInputColors( + backgroundColor: MoonColors.dark.gohan, + activeBorderColor: MoonColors.dark.piccolo, + inactiveBorderColor: MoonColors.dark.beerus, + errorBorderColor: MoonColors.dark.chiChi100, + hoverBorderColor: MoonColors.dark.beerus, + labelTextColor: MoonColors.dark.trunks, + supportingTextColor: MoonColors.dark.trunks, + ); + + /// TextInput background color. + final Color backgroundColor; + + /// TextInput active border color. + final Color activeBorderColor; + + /// TextInput inactive border color. + final Color inactiveBorderColor; + + /// TextInput error border color. + final Color errorBorderColor; + + /// TextInput hover border color. + final Color hoverBorderColor; + + /// TextInput hint text color. + final Color labelTextColor; + + /// TextInput hint text color. + final Color supportingTextColor; + + const MoonTextInputColors({ + required this.backgroundColor, + required this.activeBorderColor, + required this.inactiveBorderColor, + required this.errorBorderColor, + required this.hoverBorderColor, + required this.labelTextColor, + required this.supportingTextColor, + }); + + @override + MoonTextInputColors copyWith({ + Color? backgroundColor, + Color? activeBorderColor, + Color? inactiveBorderColor, + Color? errorBorderColor, + Color? hoverBorderColor, + Color? labelTextColor, + Color? supportingTextColor, + }) { + return MoonTextInputColors( + backgroundColor: backgroundColor ?? this.backgroundColor, + activeBorderColor: activeBorderColor ?? this.activeBorderColor, + inactiveBorderColor: inactiveBorderColor ?? this.inactiveBorderColor, + errorBorderColor: errorBorderColor ?? this.errorBorderColor, + hoverBorderColor: hoverBorderColor ?? this.hoverBorderColor, + labelTextColor: labelTextColor ?? this.labelTextColor, + supportingTextColor: supportingTextColor ?? this.supportingTextColor, + ); + } + + @override + MoonTextInputColors lerp(ThemeExtension? other, double t) { + if (other is! MoonTextInputColors) return this; + + return MoonTextInputColors( + backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t)!, + activeBorderColor: Color.lerp(activeBorderColor, other.activeBorderColor, t)!, + inactiveBorderColor: Color.lerp(inactiveBorderColor, other.inactiveBorderColor, t)!, + errorBorderColor: Color.lerp(errorBorderColor, other.errorBorderColor, t)!, + hoverBorderColor: Color.lerp(hoverBorderColor, other.hoverBorderColor, t)!, + labelTextColor: Color.lerp(labelTextColor, other.labelTextColor, t)!, + supportingTextColor: Color.lerp(supportingTextColor, other.supportingTextColor, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonTextInputColors")) + ..add(ColorProperty("backgroundColor", backgroundColor)) + ..add(ColorProperty("activeBorderColor", activeBorderColor)) + ..add(ColorProperty("inactiveBorderColor", inactiveBorderColor)) + ..add(ColorProperty("errorBorderColor", errorBorderColor)) + ..add(ColorProperty("hoverBorderColor", hoverBorderColor)) + ..add(ColorProperty("labelTextColor", labelTextColor)) + ..add(ColorProperty("supportingTextColor", supportingTextColor)); + } +} diff --git a/lib/src/theme/text_input/text_input_properties.dart b/lib/src/theme/text_input/text_input_properties.dart new file mode 100644 index 00000000..a0a19ad0 --- /dev/null +++ b/lib/src/theme/text_input/text_input_properties.dart @@ -0,0 +1,81 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/sizes.dart'; +import 'package:moon_design/src/theme/typography/text_styles.dart'; + +@immutable +class MoonTextInputProperties extends ThemeExtension with DiagnosticableTreeMixin { + static final properties = MoonTextInputProperties( + transitionDuration: const Duration(milliseconds: 200), + transitionCurve: Curves.easeInOutCubic, + supportingPadding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x3s, vertical: MoonSizes.sizes.x4s), + labelTextStyle: MoonTextStyles.body.text12, + supportingTextStyle: MoonTextStyles.body.text12, + ); + + /// TextInput transition duration. + final Duration transitionDuration; + + /// TextInput transition curve. + final Curve transitionCurve; + + /// The padding around TextInput supporting widget or error builder. + final EdgeInsetsGeometry supportingPadding; + + /// TextInput label text style. + final TextStyle labelTextStyle; + + /// TextInput supporting or error text style. + final TextStyle supportingTextStyle; + + const MoonTextInputProperties({ + required this.transitionDuration, + required this.transitionCurve, + required this.supportingPadding, + required this.labelTextStyle, + required this.supportingTextStyle, + }); + + @override + MoonTextInputProperties copyWith({ + Duration? transitionDuration, + Curve? transitionCurve, + EdgeInsetsGeometry? supportingPadding, + TextStyle? labelTextStyle, + TextStyle? supportingTextStyle, + }) { + return MoonTextInputProperties( + transitionDuration: transitionDuration ?? this.transitionDuration, + transitionCurve: transitionCurve ?? this.transitionCurve, + supportingPadding: supportingPadding ?? this.supportingPadding, + labelTextStyle: labelTextStyle ?? this.labelTextStyle, + supportingTextStyle: supportingTextStyle ?? this.supportingTextStyle, + ); + } + + @override + MoonTextInputProperties lerp(ThemeExtension? other, double t) { + if (other is! MoonTextInputProperties) return this; + + return MoonTextInputProperties( + transitionDuration: lerpDuration(transitionDuration, other.transitionDuration, t), + transitionCurve: other.transitionCurve, + supportingPadding: EdgeInsetsGeometry.lerp(supportingPadding, other.supportingPadding, t)!, + labelTextStyle: TextStyle.lerp(labelTextStyle, other.labelTextStyle, t)!, + supportingTextStyle: TextStyle.lerp(supportingTextStyle, other.supportingTextStyle, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonTextInputProperties")) + ..add(DiagnosticsProperty("transitionDuration", transitionDuration)) + ..add(DiagnosticsProperty("transitionCurve", transitionCurve)) + ..add(DiagnosticsProperty("supportingPadding", supportingPadding)) + ..add(DiagnosticsProperty("labelTextStyle", labelTextStyle)) + ..add(DiagnosticsProperty("supportingTextStyle", supportingTextStyle)); + } +} diff --git a/lib/src/theme/text_input/text_input_size_properties.dart b/lib/src/theme/text_input/text_input_size_properties.dart new file mode 100644 index 00000000..09bc697f --- /dev/null +++ b/lib/src/theme/text_input/text_input_size_properties.dart @@ -0,0 +1,120 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/borders.dart'; +import 'package:moon_design/src/theme/sizes.dart'; +import 'package:moon_design/src/theme/typography/text_styles.dart'; + +@immutable +class MoonTextInputSizeProperties extends ThemeExtension with DiagnosticableTreeMixin { + static final sm = MoonTextInputSizeProperties( + borderRadius: MoonBorders.borders.interactiveXs, + height: MoonSizes.sizes.sm, + gap: MoonSizes.sizes.x4s, + iconSizeValue: MoonSizes.sizes.xs, + padding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x4s), + textStyle: MoonTextStyles.heading.text14, + ); + + static final md = MoonTextInputSizeProperties( + borderRadius: MoonBorders.borders.interactiveSm, + height: MoonSizes.sizes.md, + gap: MoonSizes.sizes.x4s, + iconSizeValue: MoonSizes.sizes.xs, + padding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x3s), + textStyle: MoonTextStyles.heading.text14, + ); + + static final lg = MoonTextInputSizeProperties( + borderRadius: MoonBorders.borders.interactiveSm, + height: MoonSizes.sizes.lg, + gap: MoonSizes.sizes.x4s, + iconSizeValue: MoonSizes.sizes.xs, + padding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x3s), + textStyle: MoonTextStyles.heading.text16, + ); + + static final xl = MoonTextInputSizeProperties( + borderRadius: MoonBorders.borders.interactiveSm, + height: MoonSizes.sizes.xl, + gap: MoonSizes.sizes.x2s, + iconSizeValue: MoonSizes.sizes.xs, + padding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x2s), + textStyle: MoonTextStyles.heading.text16, + ); + + /// TextInput border radius. + final BorderRadiusGeometry borderRadius; + + /// TextInput height. + final double height; + + /// Space between TextInput children. + final double gap; + + /// TextInput icon size value. + final double iconSizeValue; + + /// Padding around TextInput children. + final EdgeInsetsGeometry padding; + + /// TextInput text style. + final TextStyle textStyle; + + const MoonTextInputSizeProperties({ + required this.borderRadius, + required this.height, + required this.gap, + required this.iconSizeValue, + required this.padding, + required this.textStyle, + }); + + @override + MoonTextInputSizeProperties copyWith({ + BorderRadiusGeometry? borderRadius, + double? height, + double? gap, + double? iconSizeValue, + EdgeInsetsGeometry? padding, + TextStyle? textStyle, + }) { + return MoonTextInputSizeProperties( + borderRadius: borderRadius ?? this.borderRadius, + height: height ?? this.height, + gap: gap ?? this.gap, + iconSizeValue: iconSizeValue ?? this.iconSizeValue, + padding: padding ?? this.padding, + textStyle: textStyle ?? this.textStyle, + ); + } + + @override + MoonTextInputSizeProperties lerp(ThemeExtension? other, double t) { + if (other is! MoonTextInputSizeProperties) return this; + + return MoonTextInputSizeProperties( + borderRadius: BorderRadiusGeometry.lerp(borderRadius, other.borderRadius, t)!, + height: lerpDouble(height, other.height, t)!, + gap: lerpDouble(gap, other.gap, t)!, + iconSizeValue: lerpDouble(iconSizeValue, other.iconSizeValue, t)!, + padding: EdgeInsetsGeometry.lerp(padding, other.padding, t)!, + textStyle: TextStyle.lerp(textStyle, other.textStyle, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonTextInputSizeProperties")) + ..add(DiagnosticsProperty("borderRadius", borderRadius)) + ..add(DoubleProperty("height", height)) + ..add(DoubleProperty("gap", gap)) + ..add(DoubleProperty("iconSizeValue", iconSizeValue)) + ..add(DiagnosticsProperty("padding", padding)) + ..add(DiagnosticsProperty("textStyle", textStyle)); + } +} diff --git a/lib/src/theme/text_input/text_input_sizes.dart b/lib/src/theme/text_input/text_input_sizes.dart new file mode 100644 index 00000000..c96e8d2e --- /dev/null +++ b/lib/src/theme/text_input/text_input_sizes.dart @@ -0,0 +1,71 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/text_input/text_input_size_properties.dart'; + +@immutable +class MoonTextInputSizes extends ThemeExtension with DiagnosticableTreeMixin { + static final sizes = MoonTextInputSizes( + sm: MoonTextInputSizeProperties.sm, + md: MoonTextInputSizeProperties.md, + lg: MoonTextInputSizeProperties.lg, + xl: MoonTextInputSizeProperties.xl, + ); + + /// Small TextInput properties. + final MoonTextInputSizeProperties sm; + + /// Medium TextInput properties. + final MoonTextInputSizeProperties md; + + /// Large TextInput properties. + final MoonTextInputSizeProperties lg; + + /// Extra large (floating) TextInput properties. + final MoonTextInputSizeProperties xl; + + const MoonTextInputSizes({ + required this.sm, + required this.md, + required this.lg, + required this.xl, + }); + + @override + MoonTextInputSizes copyWith({ + MoonTextInputSizeProperties? sm, + MoonTextInputSizeProperties? md, + MoonTextInputSizeProperties? lg, + MoonTextInputSizeProperties? xl, + }) { + return MoonTextInputSizes( + sm: sm ?? this.sm, + md: md ?? this.md, + lg: lg ?? this.lg, + xl: xl ?? this.xl, + ); + } + + @override + MoonTextInputSizes lerp(ThemeExtension? other, double t) { + if (other is! MoonTextInputSizes) return this; + + return MoonTextInputSizes( + sm: sm.lerp(other.sm, t), + md: md.lerp(other.md, t), + lg: lg.lerp(other.lg, t), + xl: xl.lerp(other.xl, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonTextInputSizes")) + ..add(DiagnosticsProperty("sm", sm)) + ..add(DiagnosticsProperty("md", md)) + ..add(DiagnosticsProperty("lg", lg)) + ..add(DiagnosticsProperty("xl", xl)); + } +} diff --git a/lib/src/theme/text_input/text_input_theme.dart b/lib/src/theme/text_input/text_input_theme.dart new file mode 100644 index 00000000..6fb1c74c --- /dev/null +++ b/lib/src/theme/text_input/text_input_theme.dart @@ -0,0 +1,70 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/text_input/text_input_colors.dart'; +import 'package:moon_design/src/theme/text_input/text_input_properties.dart'; +import 'package:moon_design/src/theme/text_input/text_input_sizes.dart'; + +@immutable +class MoonTextInputTheme extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonTextInputTheme( + colors: MoonTextInputColors.light, + properties: MoonTextInputProperties.properties, + sizes: MoonTextInputSizes.sizes, + ); + + static final dark = MoonTextInputTheme( + colors: MoonTextInputColors.dark, + properties: MoonTextInputProperties.properties, + sizes: MoonTextInputSizes.sizes, + ); + + /// TextInput colors. + final MoonTextInputColors colors; + + /// TextInput properties. + final MoonTextInputProperties properties; + + /// TextInput sizes. + final MoonTextInputSizes sizes; + + const MoonTextInputTheme({ + required this.colors, + required this.properties, + required this.sizes, + }); + + @override + MoonTextInputTheme copyWith({ + MoonTextInputColors? colors, + MoonTextInputProperties? properties, + MoonTextInputSizes? sizes, + }) { + return MoonTextInputTheme( + colors: colors ?? this.colors, + properties: properties ?? this.properties, + sizes: sizes ?? this.sizes, + ); + } + + @override + MoonTextInputTheme lerp(ThemeExtension? other, double t) { + if (other is! MoonTextInputTheme) return this; + + return MoonTextInputTheme( + colors: colors.lerp(other.colors, t), + properties: properties.lerp(other.properties, t), + sizes: sizes.lerp(other.sizes, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder diagnosticProperties) { + super.debugFillProperties(diagnosticProperties); + diagnosticProperties + ..add(DiagnosticsProperty("type", "MoonTextInputTheme")) + ..add(DiagnosticsProperty("colors", colors)) + ..add(DiagnosticsProperty("properties", properties)) + ..add(DiagnosticsProperty("sizes", sizes)); + } +} diff --git a/lib/src/theme/theme.dart b/lib/src/theme/theme.dart index 6f300ef3..af4ed77b 100644 --- a/lib/src/theme/theme.dart +++ b/lib/src/theme/theme.dart @@ -23,7 +23,8 @@ import 'package:moon_design/src/theme/shadows.dart'; import 'package:moon_design/src/theme/sizes.dart'; import 'package:moon_design/src/theme/switch/switch_theme.dart'; import 'package:moon_design/src/theme/tag/tag_theme.dart'; -import 'package:moon_design/src/theme/textarea/textarea_theme.dart'; +import 'package:moon_design/src/theme/text_area/text_area_theme.dart'; +import 'package:moon_design/src/theme/text_input/text_input_theme.dart'; import 'package:moon_design/src/theme/toast/toast_theme.dart'; import 'package:moon_design/src/theme/tooltip/tooltip_theme.dart'; import 'package:moon_design/src/theme/typography/typography.dart'; @@ -54,6 +55,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { switchTheme: MoonSwitchTheme.light, tagTheme: MoonTagTheme.light, textAreaTheme: MoonTextAreaTheme.light, + textInputTheme: MoonTextInputTheme.light, toastTheme: MoonToastTheme.light, tooltipTheme: MoonTooltipTheme.light, typography: MoonTypography.light, @@ -83,6 +85,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { switchTheme: MoonSwitchTheme.dark, tagTheme: MoonTagTheme.dark, textAreaTheme: MoonTextAreaTheme.dark, + textInputTheme: MoonTextInputTheme.dark, toastTheme: MoonToastTheme.dark, tooltipTheme: MoonTooltipTheme.dark, typography: MoonTypography.dark, @@ -157,6 +160,9 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { /// Moon Design System MoonTextArea widget theming. final MoonTextAreaTheme textAreaTheme; + /// Moon Design System MoonTextInput widget theming. + final MoonTextInputTheme textInputTheme; + /// Moon Design System MoonToast widget theming. final MoonToastTheme toastTheme; @@ -190,6 +196,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { required this.switchTheme, required this.tagTheme, required this.textAreaTheme, + required this.textInputTheme, required this.toastTheme, required this.tooltipTheme, required this.typography, @@ -220,6 +227,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { MoonSwitchTheme? switchTheme, MoonTagTheme? tagTheme, MoonTextAreaTheme? textAreaTheme, + MoonTextInputTheme? textInputTheme, MoonToastTheme? toastTheme, MoonTooltipTheme? tooltipTheme, MoonTypography? typography, @@ -248,6 +256,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { switchTheme: switchTheme ?? this.switchTheme, tagTheme: tagTheme ?? this.tagTheme, textAreaTheme: textAreaTheme ?? this.textAreaTheme, + textInputTheme: textInputTheme ?? this.textInputTheme, toastTheme: toastTheme ?? this.toastTheme, tooltipTheme: tooltipTheme ?? this.tooltipTheme, typography: typography ?? this.typography, @@ -282,6 +291,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { switchTheme: switchTheme.lerp(other.switchTheme, t), tagTheme: tagTheme.lerp(other.tagTheme, t), textAreaTheme: textAreaTheme.lerp(other.textAreaTheme, t), + textInputTheme: textInputTheme.lerp(other.textInputTheme, t), toastTheme: toastTheme.lerp(other.toastTheme, t), tooltipTheme: tooltipTheme.lerp(other.tooltipTheme, t), typography: typography.lerp(other.typography, t), @@ -316,6 +326,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { ..add(DiagnosticsProperty("MoonSwitchTheme", switchTheme)) ..add(DiagnosticsProperty("MoonTagTheme", tagTheme)) ..add(DiagnosticsProperty("MoonTextAreaTheme", textAreaTheme)) + ..add(DiagnosticsProperty("MoonTextInputTheme", textInputTheme)) ..add(DiagnosticsProperty("MoonToastTheme", toastTheme)) ..add(DiagnosticsProperty("MoonTooltipTheme", tooltipTheme)) ..add(DiagnosticsProperty("MoonTypography", typography)); diff --git a/lib/src/widgets/alert/alert.dart b/lib/src/widgets/alert/alert.dart index 73183f05..0c0cad84 100644 --- a/lib/src/widgets/alert/alert.dart +++ b/lib/src/widgets/alert/alert.dart @@ -192,7 +192,7 @@ class _MoonAlertState extends State with SingleTickerProviderStateMix MoonBorders.borders.interactiveSm; final double effectiveBorderWidth = - widget.borderWidth ?? context.moonBorders?.borderWidth ?? MoonBorders.borders.borderWidth; + widget.borderWidth ?? context.moonBorders?.defaultBorderWidth ?? MoonBorders.borders.defaultBorderWidth; final double effectiveHorizontalGap = widget.horizontalGap ?? context.moonTheme?.alertTheme.properties.horizontalGap ?? MoonSizes.sizes.x3s; diff --git a/lib/src/widgets/authcode/authcode.dart b/lib/src/widgets/authcode/authcode.dart index 709deaf7..c88132d9 100644 --- a/lib/src/widgets/authcode/authcode.dart +++ b/lib/src/widgets/authcode/authcode.dart @@ -689,7 +689,8 @@ class _MoonAuthCodeState extends State with TickerProviderStateMix context.moonTheme?.authCodeTheme.properties.borderRadius ?? MoonBorders.borders.interactiveSm; - _effectiveBorderWidth = widget.borderWidth ?? context.moonBorders?.borderWidth ?? MoonBorders.borders.borderWidth; + _effectiveBorderWidth = + widget.borderWidth ?? context.moonBorders?.defaultBorderWidth ?? MoonBorders.borders.defaultBorderWidth; _effectiveGap = widget.gap ?? context.moonTheme?.authCodeTheme.properties.gap ?? MoonSizes.sizes.x4s; diff --git a/lib/src/widgets/buttons/button.dart b/lib/src/widgets/buttons/button.dart index 2446d553..416e5d6a 100644 --- a/lib/src/widgets/buttons/button.dart +++ b/lib/src/widgets/buttons/button.dart @@ -339,7 +339,7 @@ class MoonButton extends StatelessWidget { borderColor ?? context.moonTheme?.buttonTheme.colors.borderColor ?? MoonColors.light.trunks; final double effectiveBorderWidth = - borderWidth ?? context.moonBorders?.borderWidth ?? MoonBorders.borders.borderWidth; + borderWidth ?? context.moonBorders?.defaultBorderWidth ?? MoonBorders.borders.defaultBorderWidth; final Color effectiveHoverEffectColor = hoverEffectColor ?? context.moonEffects?.controlHoverEffect.primaryHoverColor ?? diff --git a/lib/src/widgets/chips/chip.dart b/lib/src/widgets/chips/chip.dart index a96cd570..f5601d9c 100644 --- a/lib/src/widgets/chips/chip.dart +++ b/lib/src/widgets/chips/chip.dart @@ -201,7 +201,7 @@ class MoonChip extends StatelessWidget { final BorderRadiusGeometry effectiveBorderRadius = borderRadius ?? effectiveMoonChipSize.borderRadius; final double effectiveBorderWidth = - borderWidth ?? context.moonBorders?.borderWidth ?? MoonBorders.borders.borderWidth; + borderWidth ?? context.moonBorders?.defaultBorderWidth ?? MoonBorders.borders.defaultBorderWidth; final double effectiveHeight = height ?? effectiveMoonChipSize.height; diff --git a/lib/src/widgets/common/icons/moon_icon.dart b/lib/src/widgets/common/icons/moon_icon.dart index dce4fc39..5f905fcd 100644 --- a/lib/src/widgets/common/icons/moon_icon.dart +++ b/lib/src/widgets/common/icons/moon_icon.dart @@ -204,6 +204,7 @@ class MoonIcon extends StatelessWidget { Widget iconWidget = RichText( overflow: TextOverflow.visible, // Never clip. + textAlign: TextAlign.center, textDirection: textDirection, // Since we already fetched it for the assert... text: TextSpan( text: String.fromCharCode(icon!.codePoint), @@ -217,6 +218,7 @@ class MoonIcon extends StatelessWidget { inherit: false, color: iconColor, fontSize: iconSize, + height: 1, fontFamily: icon!.fontFamily, package: icon!.fontPackage, shadows: iconShadows, @@ -242,7 +244,7 @@ class MoonIcon extends StatelessWidget { return Semantics( label: semanticLabel, child: ExcludeSemantics( - child: iconWidget, + child: Center(child: SizedBox.square(dimension: iconSize, child: iconWidget)), ), ); } diff --git a/lib/src/widgets/textarea/textarea.dart b/lib/src/widgets/text_area/text_area.dart similarity index 59% rename from lib/src/widgets/textarea/textarea.dart rename to lib/src/widgets/text_area/text_area.dart index 0306481e..b6d0e3d1 100644 --- a/lib/src/widgets/textarea/textarea.dart +++ b/lib/src/widgets/text_area/text_area.dart @@ -1,11 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:moon_design/src/theme/borders.dart'; import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/effects/focus_effects.dart'; import 'package:moon_design/src/theme/opacity.dart'; +import 'package:moon_design/src/theme/sizes.dart'; import 'package:moon_design/src/theme/theme.dart'; import 'package:moon_design/src/utils/extensions.dart'; import 'package:moon_design/src/widgets/common/animated_icon_theme.dart'; +import 'package:moon_design/src/widgets/common/effects/focus_effect.dart'; typedef MoonTextAreaErrorBuilder = Widget Function(BuildContext context, String? errorText); @@ -31,12 +35,12 @@ class MoonTextArea extends StatefulWidget { /// {@macro flutter.services.TextInputConfiguration.enableSuggestions} final bool enableSuggestions; - /// {@macro flutter.widgets.editableText.scribbleEnabled} - final bool scribbleEnabled; - /// {@macro flutter.widgets.editableText.readOnly} final bool readOnly; + /// {@macro flutter.widgets.editableText.scribbleEnabled} + final bool scribbleEnabled; + /// {@macro flutter.widgets.editableText.showCursor} final bool? showCursor; @@ -67,6 +71,9 @@ class MoonTextArea extends StatefulWidget { /// The border color of the error text area. final Color? errorBorderColor; + /// The border color of the hovered text area. + final Color? hoverBorderColor; + /// The text color of the text area. final Color? textColor; @@ -85,15 +92,15 @@ class MoonTextArea extends StatefulWidget { /// {@macro flutter.widgets.editableText.scrollPadding} final EdgeInsets scrollPadding; + /// The padding around supporting widget or error builder. + final EdgeInsetsGeometry? supportingPadding; + /// The padding around the text content. final EdgeInsetsGeometry? textPadding; /// {@macro flutter.widgets.Focus.focusNode}. final FocusNode? focusNode; - /// Validator for the text area widget. - final FormFieldValidator? validator; - /// The maximum number of characters (Unicode grapheme clusters) to allow in the text area. /// /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength} @@ -117,9 +124,6 @@ class MoonTextArea extends StatefulWidget { /// {@macro flutter.services.textFormatter.maxLengthEnforcement} final MaxLengthEnforcement? maxLengthEnforcement; - /// Builder for the error widget. - final MoonTextAreaErrorBuilder? errorBuilder; - /// {@macro flutter.widgets.editableText.scrollController} final ScrollController? scrollController; @@ -151,12 +155,12 @@ class MoonTextArea extends StatefulWidget { /// {@macro flutter.widgets.editableText.textAlign} final TextAlign textAlign; - /// {@macro flutter.widgets.editableText.textDirection} - final TextDirection? textDirection; - /// {@macro flutter.widgets.editableText.textCapitalization} final TextCapitalization textCapitalization; + /// {@macro flutter.widgets.editableText.textDirection} + final TextDirection? textDirection; + /// Controls the text being edited. final TextEditingController? controller; @@ -166,13 +170,16 @@ class MoonTextArea extends StatefulWidget { /// [TextInputType.multiline] and [TextInputAction.done] otherwise. final TextInputAction? textInputAction; - /// The style to use for the text being edited. + /// The text style to use for the text being edited. /// /// This text style is also used as the base style for the [decoration]. final TextStyle? textStyle; - /// The style to use for the error state text. - final TextStyle? errorTextStyle; + /// The text style to use for the supporting or error state text. + final TextStyle? supportingTextStyle; + + /// Validator for the text area widget. + final FormFieldValidator? validator; /// A callback that is called when the user taps the text area widget. final GestureTapCallback? onTap; @@ -193,7 +200,7 @@ class MoonTextArea extends StatefulWidget { final VoidCallback? onEditingComplete; /// [FormState.save]. - final void Function(String?)? onSaved; + final ValueChanged? onSaved; /// {@macro flutter.widgets.editableText.onSubmitted} /// @@ -203,6 +210,13 @@ class MoonTextArea extends StatefulWidget { /// focusable item when the user is done editing. final ValueChanged? onSubmitted; + /// Builder for the error widget. + final MoonTextAreaErrorBuilder? errorBuilder; + + /// The widget in the supporting slot of the text area. + final Widget? supporting; + + /// MDS TextArea widget const MoonTextArea({ super.key, this.autovalidateMode = AutovalidateMode.disabled, @@ -212,8 +226,8 @@ class MoonTextArea extends StatefulWidget { this.enableIMEPersonalizedLearning = true, this.enableInteractiveSelection, this.enableSuggestions = true, - this.scribbleEnabled = true, this.readOnly = false, + this.scribbleEnabled = true, this.showCursor, this.borderRadius, this.keyboardAppearance, @@ -222,12 +236,14 @@ class MoonTextArea extends StatefulWidget { this.activeBorderColor, this.inactiveBorderColor, this.errorBorderColor, + this.hoverBorderColor, this.textColor, this.hintTextColor, this.height, this.transitionDuration, this.transitionCurve, this.scrollPadding = const EdgeInsets.all(24.0), + this.supportingPadding, this.textPadding, this.focusNode, this.validator, @@ -236,7 +252,6 @@ class MoonTextArea extends StatefulWidget { this.autofillHints, this.inputFormatters, this.maxLengthEnforcement, - this.errorBuilder, this.scrollController, this.scrollPhysics, this.hintText, @@ -245,18 +260,20 @@ class MoonTextArea extends StatefulWidget { this.semanticLabel, this.strutStyle, this.textAlign = TextAlign.start, - this.textDirection, this.textCapitalization = TextCapitalization.none, + this.textDirection, this.controller, this.textInputAction, this.textStyle, - this.errorTextStyle, + this.supportingTextStyle, this.onTap, this.onTapOutside, this.onChanged, this.onEditingComplete, this.onSaved, this.onSubmitted, + this.errorBuilder, + this.supporting, }); @override @@ -264,15 +281,10 @@ class MoonTextArea extends StatefulWidget { } class _MoonTextAreaState extends State { + bool _isFocused = false; + bool _isHovered = false; String? _errorText; - String? _validateInput(String? value) { - final validationResult = widget.validator?.call(value); - _errorText = validationResult; - - return validationResult; - } - Color _getTextColor(BuildContext context, {required Color effectiveBackgroundColor}) { final backgroundLuminance = effectiveBackgroundColor.computeLuminance(); if (backgroundLuminance > 0.5) { @@ -282,6 +294,21 @@ class _MoonTextAreaState extends State { } } + void _setFocusStatus(bool isFocused) { + setState(() => _isFocused = isFocused); + } + + void _setHoverStatus(bool isHovered) { + setState(() => _isHovered = isHovered); + } + + String? _validateInput(String? value) { + final validationResult = widget.validator?.call(value); + _errorText = validationResult; + + return validationResult; + } + @override Widget build(BuildContext context) { final BorderRadiusGeometry effectiveBorderRadius = @@ -302,6 +329,18 @@ class _MoonTextAreaState extends State { context.moonTheme?.textAreaTheme.colors.errorBorderColor ?? MoonColors.light.chiChi100; + final Color effectiveHoverBorderColor = + widget.hoverBorderColor ?? context.moonTheme?.textInputTheme.colors.hoverBorderColor ?? MoonColors.light.beerus; + + final double effectiveFocusEffectExtent = + context.moonEffects?.controlFocusEffect.effectExtent ?? MoonFocusEffects.lightFocusEffect.effectExtent; + + final Color focusEffectColor = + context.isDarkMode ? effectiveActiveBorderColor.withOpacity(0.4) : effectiveActiveBorderColor.withOpacity(0.2); + + final Color errorFocusEffectColor = + context.isDarkMode ? effectiveErrorBorderColor.withOpacity(0.4) : effectiveErrorBorderColor.withOpacity(0.2); + final Color effectiveTextColor = widget.textColor ?? _getTextColor(context, effectiveBackgroundColor: effectiveBackgroundColor); @@ -317,19 +356,42 @@ class _MoonTextAreaState extends State { final Curve effectiveTransitionCurve = widget.transitionCurve ?? context.moonTheme?.textAreaTheme.properties.transitionCurve ?? Curves.easeInOutCubic; + final EdgeInsetsGeometry effectiveSupportingPadding = widget.supportingPadding ?? + context.moonTheme?.textInputTheme.properties.supportingPadding ?? + EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x3s, vertical: MoonSizes.sizes.x4s); + final EdgeInsetsGeometry effectiveTextPadding = widget.textPadding ?? context.moonTheme?.textAreaTheme.properties.textPadding ?? const EdgeInsets.all(16); final TextStyle effectiveTextStyle = widget.textStyle ?? context.moonTheme?.textAreaTheme.properties.textStyle ?? const TextStyle(fontSize: 16); - final TextStyle effectiveErrorTextStyle = widget.errorTextStyle ?? - context.moonTheme?.textAreaTheme.properties.errorTextStyle ?? + final TextStyle effectiveSupportingTextStyle = widget.supportingTextStyle ?? + context.moonTheme?.textAreaTheme.properties.supportingTextStyle ?? const TextStyle(fontSize: 12); final OutlineInputBorder defaultBorder = OutlineInputBorder( borderRadius: effectiveBorderRadius.smoothBorderRadius(context), - borderSide: BorderSide(color: effectiveInactiveBorderColor), + borderSide: BorderSide( + color: _isHovered ? effectiveHoverBorderColor : effectiveInactiveBorderColor, + width: _isHovered ? MoonBorders.borders.activeBorderWidth : MoonBorders.borders.defaultBorderWidth, + ), + ); + + final OutlineInputBorder focusBorder = OutlineInputBorder( + borderRadius: effectiveBorderRadius.smoothBorderRadius(context), + borderSide: BorderSide( + color: effectiveActiveBorderColor, + width: MoonBorders.borders.activeBorderWidth, + ), + ); + + final OutlineInputBorder errorBorder = OutlineInputBorder( + borderRadius: effectiveBorderRadius.smoothBorderRadius(context), + borderSide: BorderSide( + color: effectiveErrorBorderColor, + width: MoonBorders.borders.activeBorderWidth, + ), ); return Semantics( @@ -341,89 +403,106 @@ class _MoonTextAreaState extends State { duration: effectiveTransitionDuration, child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextFormField( - autocorrect: widget.autocorrect, - autofillHints: widget.autofillHints, - autofocus: widget.autofocus, - autovalidateMode: widget.autovalidateMode, - controller: widget.controller, - cursorColor: effectiveTextColor, - enabled: widget.enabled, - enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, - enableInteractiveSelection: widget.enableInteractiveSelection, - enableSuggestions: widget.enableSuggestions, - expands: true, - focusNode: widget.focusNode, - initialValue: widget.initialValue, - inputFormatters: widget.inputFormatters, - keyboardAppearance: widget.keyboardAppearance, - keyboardType: TextInputType.multiline, - maxLength: widget.maxLength, - maxLengthEnforcement: widget.maxLengthEnforcement, - maxLines: null, - minLines: widget.minLines, - onChanged: widget.onChanged, - onEditingComplete: widget.onEditingComplete, - onFieldSubmitted: widget.onSubmitted, - onSaved: widget.onSaved, - onTap: widget.onTap, - onTapOutside: widget.onTapOutside, - readOnly: widget.readOnly, - restorationId: widget.restorationId, - scrollController: widget.scrollController, - scrollPadding: widget.scrollPadding, - scrollPhysics: widget.scrollPhysics, - showCursor: widget.showCursor, - strutStyle: widget.strutStyle, - style: effectiveTextStyle.copyWith(color: effectiveTextColor), - textAlign: widget.textAlign, - textAlignVertical: TextAlignVertical.top, - textCapitalization: widget.textCapitalization, - textDirection: widget.textDirection, - textInputAction: widget.textInputAction, - validator: _validateInput, - decoration: InputDecoration( - contentPadding: effectiveTextPadding, - filled: true, - hintText: widget.hintText, - hintStyle: effectiveTextStyle.copyWith(color: effectiveHintTextColor), - errorStyle: const TextStyle(height: 0, fontSize: 0), - constraints: BoxConstraints( - minHeight: widget.height ?? 0.0, - maxHeight: widget.height ?? double.infinity, - ), - fillColor: effectiveBackgroundColor, - focusColor: effectiveActiveBorderColor, - hoverColor: Colors.transparent, - border: defaultBorder, - enabledBorder: defaultBorder, - disabledBorder: defaultBorder, - errorBorder: OutlineInputBorder( - borderRadius: effectiveBorderRadius.smoothBorderRadius(context), - borderSide: BorderSide( - color: effectiveErrorBorderColor, - width: 2, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: effectiveBorderRadius.smoothBorderRadius(context), - borderSide: BorderSide( - color: effectiveActiveBorderColor, - width: 2, + MoonFocusEffect( + show: _isFocused, + effectExtent: effectiveFocusEffectExtent, + effectColor: _errorText == null ? focusEffectColor : errorFocusEffectColor, + childBorderRadius: effectiveBorderRadius, + effectDuration: effectiveTransitionDuration, + effectCurve: effectiveTransitionCurve, + child: MouseRegion( + onEnter: (_) => _setHoverStatus(true), + onExit: (_) => _setHoverStatus(false), + child: Focus( + canRequestFocus: false, + onFocusChange: _setFocusStatus, + child: TextFormField( + autocorrect: widget.autocorrect, + autofillHints: widget.autofillHints, + autofocus: widget.autofocus, + autovalidateMode: widget.autovalidateMode, + controller: widget.controller, + cursorColor: effectiveTextColor, + enabled: widget.enabled, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + enableInteractiveSelection: widget.enableInteractiveSelection, + enableSuggestions: widget.enableSuggestions, + expands: true, + focusNode: widget.focusNode, + initialValue: widget.initialValue, + inputFormatters: widget.inputFormatters, + keyboardAppearance: widget.keyboardAppearance, + keyboardType: TextInputType.multiline, + maxLength: widget.maxLength, + maxLengthEnforcement: widget.maxLengthEnforcement, + maxLines: null, + minLines: widget.minLines, + onChanged: widget.onChanged, + onEditingComplete: widget.onEditingComplete, + onFieldSubmitted: widget.onSubmitted, + onSaved: widget.onSaved, + onTap: widget.onTap, + onTapOutside: widget.onTapOutside, + readOnly: widget.readOnly, + restorationId: widget.restorationId, + scrollController: widget.scrollController, + scrollPadding: widget.scrollPadding, + scrollPhysics: widget.scrollPhysics, + showCursor: widget.showCursor, + strutStyle: widget.strutStyle, + style: effectiveTextStyle.copyWith(color: effectiveTextColor), + textAlign: widget.textAlign, + textAlignVertical: TextAlignVertical.top, + textCapitalization: widget.textCapitalization, + textDirection: widget.textDirection, + textInputAction: widget.textInputAction, + validator: _validateInput, + decoration: InputDecoration( + filled: true, + fillColor: effectiveBackgroundColor, + focusColor: effectiveActiveBorderColor, + hoverColor: Colors.transparent, + contentPadding: effectiveTextPadding, + border: defaultBorder, + enabledBorder: defaultBorder, + disabledBorder: defaultBorder, + focusedBorder: focusBorder, + errorBorder: errorBorder, + focusedErrorBorder: errorBorder, + hintText: widget.hintText, + hintStyle: effectiveTextStyle.copyWith(color: effectiveHintTextColor), + errorStyle: const TextStyle(height: 0.1, fontSize: 0), + constraints: BoxConstraints( + minHeight: widget.height ?? 24, + maxHeight: widget.height ?? 120, + ), + ), ), ), ), ), - if (_errorText != null && widget.errorBuilder != null) + if (widget.supporting != null || (_errorText != null && widget.errorBuilder != null)) RepaintBoundary( child: AnimatedIconTheme( - color: effectiveErrorBorderColor, + color: _errorText != null && widget.errorBuilder != null + ? effectiveErrorBorderColor + : effectiveHintTextColor, duration: effectiveTransitionDuration, child: AnimatedDefaultTextStyle( - style: effectiveErrorTextStyle.copyWith(color: effectiveErrorBorderColor), + style: effectiveSupportingTextStyle.copyWith( + color: _errorText != null && widget.errorBuilder != null + ? effectiveErrorBorderColor + : effectiveHintTextColor, + ), duration: effectiveTransitionDuration, - child: widget.errorBuilder!(context, _errorText), + child: Padding( + padding: effectiveSupportingPadding, + child: _errorText != null && widget.errorBuilder != null + ? widget.errorBuilder!(context, _errorText) + : widget.supporting, + ), ), ), ), diff --git a/lib/src/widgets/text_input/text_input.dart b/lib/src/widgets/text_input/text_input.dart new file mode 100644 index 00000000..227225cd --- /dev/null +++ b/lib/src/widgets/text_input/text_input.dart @@ -0,0 +1,584 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:moon_design/src/theme/borders.dart'; +import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/effects/focus_effects.dart'; +import 'package:moon_design/src/theme/opacity.dart'; +import 'package:moon_design/src/theme/sizes.dart'; +import 'package:moon_design/src/theme/text_input/text_input_size_properties.dart'; +import 'package:moon_design/src/theme/theme.dart'; +import 'package:moon_design/src/utils/extensions.dart'; +import 'package:moon_design/src/widgets/common/animated_icon_theme.dart'; +import 'package:moon_design/src/widgets/common/effects/focus_effect.dart'; + +enum MoonTextInputSize { + sm, + md, + lg, + xl, +} + +typedef MoonTextInputErrorBuilder = Widget Function(BuildContext context, String? errorText); + +class MoonTextInput extends StatefulWidget { + /// Used to set the auto validation mode. + final AutovalidateMode autovalidateMode; + + /// {@macro flutter.widgets.editableText.autocorrect} + final bool autocorrect; + + /// {@macro flutter.widgets.editableText.autofocus} + final bool autofocus; + + /// If false the widget is "disabled": it ignores taps and it has a reduced opacity. + final bool enabled; + + /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning} + final bool enableIMEPersonalizedLearning; + + /// {@macro flutter.widgets.editableText.enableInteractiveSelection} + final bool? enableInteractiveSelection; + + /// {@macro flutter.services.TextInputConfiguration.enableSuggestions} + final bool enableSuggestions; + + /// {@macro flutter.widgets.editableText.readOnly} + final bool readOnly; + + /// {@macro flutter.widgets.editableText.scribbleEnabled} + final bool scribbleEnabled; + + /// {@macro flutter.widgets.editableText.showCursor} + final bool? showCursor; + + /// The border radius of the input. + final BorderRadiusGeometry? borderRadius; + + /// The appearance of the keyboard. + /// + /// This setting is only honored on iOS devices. + /// + /// If unset, defaults to [ThemeData.brightness]. + final Brightness? keyboardAppearance; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip? clipBehavior; + + /// The background color of the input. + final Color? backgroundColor; + + /// The border color of the active or focused input. + final Color? activeBorderColor; + + /// The border color of the inactive input. + final Color? inactiveBorderColor; + + /// The border color of the error input. + final Color? errorBorderColor; + + /// The border color of the hovered input. + final Color? hoverBorderColor; + + /// The text color of the input. + final Color? textColor; + + /// The text color of the hint in input. + final Color? hintTextColor; + + /// The gap between the leading or trailing and the label widgets. + final double? gap; + + /// The height of the input (this does not include the space taken by [MoonTextInput.errorBuilder]). + final double? height; + + /// The transition duration for disable animation. + final Duration? transitionDuration; + + /// The transition curve for disable animation. + final Curve? transitionCurve; + + /// {@macro flutter.widgets.editableText.scrollPadding} + final EdgeInsets scrollPadding; + + /// The padding of the text input. + final EdgeInsetsGeometry? padding; + + /// The padding around supporting widget or error builder. + final EdgeInsetsGeometry? supportingPadding; + + /// {@macro flutter.widgets.Focus.focusNode}. + final FocusNode? focusNode; + + /// The maximum number of characters (Unicode grapheme clusters) to allow in the input. + /// + /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength} + final int? maxLength; + + /// {@macro flutter.widgets.editableText.minLines} + /// * [expands], which determines whether the field should fill the height of its parent. + final int? minLines; + + /// {@macro flutter.widgets.editableText.autofillHints} + /// {@macro flutter.services.AutofillConfiguration.autofillHints} + final Iterable? autofillHints; + + /// {@macro flutter.widgets.editableText.inputFormatters} + final List? inputFormatters; + + /// Determines how the [maxLength] limit should be enforced. + /// + /// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement} + /// + /// {@macro flutter.services.textFormatter.maxLengthEnforcement} + final MaxLengthEnforcement? maxLengthEnforcement; + + /// The size of the text input. + final MoonTextInputSize? textInputSize; + + /// {@macro flutter.widgets.editableText.scrollController} + final ScrollController? scrollController; + + /// {@macro flutter.widgets.editableText.scrollPhysics} + final ScrollPhysics? scrollPhysics; + + /// The text for the hint. + final String? hintText; + + /// The initial value of the input. + final String? initialValue; + + /// {@template flutter.material.textfield.restorationId} + /// Restoration ID to save and restore the state of the text field. + /// + /// See also: + /// + /// * [RestorationManager], which explains how state restoration works in + /// Flutter. + /// {@endtemplate} + final String? restorationId; + + /// The semantic label for the widget. + final String? semanticLabel; + + /// {@macro flutter.widgets.editableText.strutStyle} + final StrutStyle? strutStyle; + + /// {@macro flutter.widgets.editableText.textAlign} + final TextAlign textAlign; + + /// {@macro flutter.widgets.editableText.textCapitalization} + final TextCapitalization textCapitalization; + + /// {@macro flutter.widgets.editableText.textDirection} + final TextDirection? textDirection; + + /// Controls the text being edited. + final TextEditingController? controller; + + /// The type of action button to use for the keyboard. + /// + /// Defaults to [TextInputAction.newline] if [keyboardType] is + /// [TextInputType.multiline] and [TextInputAction.done] otherwise. + final TextInputAction? textInputAction; + + /// The style to use for the text being edited. + /// + /// This text style is also used as the base style for the [decoration]. + final TextStyle? textStyle; + + /// The style to use for the error state text. + final TextStyle? supportingTextStyle; + + /// Validator for the input widget. + final FormFieldValidator? validator; + + /// A callback that is called when the user taps the input widget. + final GestureTapCallback? onTap; + + /// A callback that is called when the user taps outside the input widget. + final TapRegionCallback? onTapOutside; + + /// {@macro flutter.widgets.editableText.onChanged} + /// + /// See also: + /// + /// * [inputFormatters]: which are called before [onChanged] runs and can validate and change ("format") the input + /// value. + /// * [onEditingComplete], [onSubmitted]: which are more specialized input change notifications. + final ValueChanged? onChanged; + + /// {@macro flutter.widgets.editableText.onEditingComplete} + final VoidCallback? onEditingComplete; + + /// [FormState.save]. + final ValueChanged? onSaved; + + /// {@macro flutter.widgets.editableText.onSubmitted} + /// + /// See also: + /// + /// * [TextInputAction.next] and [TextInputAction.previous], which automatically shift the focus to the next/previous + /// focusable item when the user is done editing. + final ValueChanged? onSubmitted; + + /// Builder for the error widget. + final MoonTextInputErrorBuilder? errorBuilder; + + /// The widget in the leading slot of the text input. + final Widget? leading; + + /// The widget in the trailing slot of the text input. + final Widget? trailing; + + /// The widget in the supporting slot of the text area. + final Widget? supporting; + + const MoonTextInput({ + super.key, + this.autovalidateMode = AutovalidateMode.disabled, + this.autocorrect = true, + this.autofocus = false, + this.enabled = true, + this.enableIMEPersonalizedLearning = true, + this.enableInteractiveSelection, + this.enableSuggestions = true, + this.readOnly = false, + this.scribbleEnabled = true, + this.showCursor, + this.borderRadius, + this.keyboardAppearance, + this.clipBehavior, + this.backgroundColor, + this.activeBorderColor, + this.inactiveBorderColor, + this.errorBorderColor, + this.hoverBorderColor, + this.textColor, + this.hintTextColor, + this.gap, + this.height, + this.transitionDuration, + this.transitionCurve, + this.scrollPadding = const EdgeInsets.all(20.0), + this.padding, + this.supportingPadding, + this.focusNode, + this.maxLength, + this.minLines, + this.autofillHints, + this.inputFormatters, + this.maxLengthEnforcement, + this.textInputSize, + this.scrollController, + this.scrollPhysics, + this.hintText, + this.initialValue, + this.restorationId, + this.semanticLabel, + this.strutStyle, + this.textAlign = TextAlign.start, + this.textCapitalization = TextCapitalization.none, + this.textDirection, + this.controller, + this.textInputAction, + this.textStyle, + this.supportingTextStyle, + this.validator, + this.onTap, + this.onTapOutside, + this.onChanged, + this.onEditingComplete, + this.onSaved, + this.onSubmitted, + this.errorBuilder, + this.leading, + this.trailing, + this.supporting, + }); + + @override + State createState() => _MoonTextInputState(); +} + +class _MoonTextInputState extends State { + bool _isFocused = false; + bool _isHovered = false; + String? _errorText; + + MoonTextInputSizeProperties _getMoonTextInputSize(BuildContext context, MoonTextInputSize? moonTextInputSize) { + switch (moonTextInputSize) { + case MoonTextInputSize.sm: + return context.moonTheme?.textInputTheme.sizes.sm ?? MoonTextInputSizeProperties.sm; + case MoonTextInputSize.md: + return context.moonTheme?.textInputTheme.sizes.md ?? MoonTextInputSizeProperties.md; + case MoonTextInputSize.lg: + return context.moonTheme?.textInputTheme.sizes.lg ?? MoonTextInputSizeProperties.lg; + case MoonTextInputSize.xl: + return context.moonTheme?.textInputTheme.sizes.xl ?? MoonTextInputSizeProperties.xl; + default: + return context.moonTheme?.textInputTheme.sizes.md ?? MoonTextInputSizeProperties.md; + } + } + + Color _getTextColor(BuildContext context, {required Color effectiveBackgroundColor}) { + final backgroundLuminance = effectiveBackgroundColor.computeLuminance(); + if (backgroundLuminance > 0.5) { + return MoonColors.light.bulma; + } else { + return MoonColors.dark.bulma; + } + } + + void _setFocusStatus(bool isFocused) { + setState(() => _isFocused = isFocused); + } + + void _setHoverStatus(bool isHovered) { + setState(() => _isHovered = isHovered); + } + + String? _validateInput(String? value) { + final validationResult = widget.validator?.call(value); + _errorText = validationResult; + + return validationResult; + } + + @override + Widget build(BuildContext context) { + final MoonTextInputSizeProperties effectiveMoonTextInputSize = _getMoonTextInputSize(context, widget.textInputSize); + + final BorderRadiusGeometry effectiveBorderRadius = widget.borderRadius ?? effectiveMoonTextInputSize.borderRadius; + + final Color effectiveBackgroundColor = + widget.backgroundColor ?? context.moonTheme?.textInputTheme.colors.backgroundColor ?? MoonColors.light.gohan; + + final Color effectiveActiveBorderColor = widget.activeBorderColor ?? + context.moonTheme?.textInputTheme.colors.activeBorderColor ?? + MoonColors.light.piccolo; + + final Color effectiveInactiveBorderColor = widget.inactiveBorderColor ?? + context.moonTheme?.textInputTheme.colors.inactiveBorderColor ?? + MoonColors.light.beerus; + + final Color effectiveErrorBorderColor = widget.errorBorderColor ?? + context.moonTheme?.textInputTheme.colors.errorBorderColor ?? + MoonColors.light.chiChi100; + + final Color effectiveHoverBorderColor = + widget.hoverBorderColor ?? context.moonTheme?.textInputTheme.colors.hoverBorderColor ?? MoonColors.light.beerus; + + final Color focusEffectColor = + context.isDarkMode ? effectiveActiveBorderColor.withOpacity(0.4) : effectiveActiveBorderColor.withOpacity(0.2); + + final Color errorFocusEffectColor = + context.isDarkMode ? effectiveErrorBorderColor.withOpacity(0.4) : effectiveErrorBorderColor.withOpacity(0.2); + + final Color effectiveTextColor = + widget.textColor ?? _getTextColor(context, effectiveBackgroundColor: effectiveBackgroundColor); + + final Color effectiveHintTextColor = + widget.hintTextColor ?? context.moonTheme?.textInputTheme.colors.supportingTextColor ?? MoonColors.light.trunks; + + final double effectiveGap = widget.gap ?? effectiveMoonTextInputSize.gap; + + final double effectiveHeight = widget.height ?? effectiveMoonTextInputSize.height; + + final double effectiveDisabledOpacityValue = context.moonTheme?.opacity.disabled ?? MoonOpacity.opacities.disabled; + + final double effectiveFocusEffectExtent = + context.moonEffects?.controlFocusEffect.effectExtent ?? MoonFocusEffects.lightFocusEffect.effectExtent; + + final Duration effectiveTransitionDuration = widget.transitionDuration ?? + context.moonTheme?.textInputTheme.properties.transitionDuration ?? + const Duration(milliseconds: 200); + + final Curve effectiveTransitionCurve = + widget.transitionCurve ?? context.moonTheme?.textInputTheme.properties.transitionCurve ?? Curves.easeInOutCubic; + + final EdgeInsetsGeometry effectivePadding = widget.padding ?? effectiveMoonTextInputSize.padding; + + final EdgeInsetsGeometry effectiveSupportingPadding = widget.supportingPadding ?? + context.moonTheme?.textInputTheme.properties.supportingPadding ?? + EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x3s, vertical: MoonSizes.sizes.x4s); + + final EdgeInsets resolvedDirectionalPadding = effectivePadding.resolve(Directionality.of(context)); + + final TextStyle effectiveTextStyle = widget.textStyle ?? effectiveMoonTextInputSize.textStyle; + + final TextStyle effectiveSupportingTextStyle = widget.supportingTextStyle ?? + context.moonTheme?.textInputTheme.properties.supportingTextStyle ?? + const TextStyle(fontSize: 12); + + final OutlineInputBorder defaultBorder = OutlineInputBorder( + borderRadius: effectiveBorderRadius.smoothBorderRadius(context), + borderSide: BorderSide( + color: _isHovered ? effectiveHoverBorderColor : effectiveInactiveBorderColor, + width: _isHovered ? MoonBorders.borders.activeBorderWidth : MoonBorders.borders.defaultBorderWidth, + ), + ); + + final double resolvedLeadingWidth = widget.leading != null + ? effectiveMoonTextInputSize.iconSizeValue + resolvedDirectionalPadding.left + effectiveGap + : resolvedDirectionalPadding.left; + + final double resolvedTrailingWidth = widget.trailing != null + ? effectiveMoonTextInputSize.iconSizeValue + resolvedDirectionalPadding.right + effectiveGap + : resolvedDirectionalPadding.right; + + final OutlineInputBorder focusBorder = OutlineInputBorder( + borderRadius: effectiveBorderRadius.smoothBorderRadius(context), + borderSide: BorderSide( + color: effectiveActiveBorderColor, + width: MoonBorders.borders.activeBorderWidth, + ), + ); + + final OutlineInputBorder errorBorder = OutlineInputBorder( + borderRadius: effectiveBorderRadius.smoothBorderRadius(context), + borderSide: BorderSide( + color: effectiveErrorBorderColor, + width: MoonBorders.borders.activeBorderWidth, + ), + ); + + return Semantics( + label: widget.semanticLabel, + child: RepaintBoundary( + child: AnimatedOpacity( + opacity: widget.enabled ? 1.0 : effectiveDisabledOpacityValue, + curve: effectiveTransitionCurve, + duration: effectiveTransitionDuration, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MoonFocusEffect( + show: _isFocused, + effectExtent: effectiveFocusEffectExtent, + effectColor: _errorText == null ? focusEffectColor : errorFocusEffectColor, + childBorderRadius: effectiveBorderRadius, + effectDuration: effectiveTransitionDuration, + effectCurve: effectiveTransitionCurve, + child: MouseRegion( + onEnter: (_) => _setHoverStatus(true), + onExit: (_) => _setHoverStatus(false), + child: Focus( + canRequestFocus: false, + onFocusChange: _setFocusStatus, + child: TextFormField( + autocorrect: widget.autocorrect, + autofillHints: widget.autofillHints, + autofocus: widget.autofocus, + autovalidateMode: widget.autovalidateMode, + controller: widget.controller, + cursorColor: effectiveTextColor, + enabled: widget.enabled, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + enableInteractiveSelection: widget.enableInteractiveSelection, + enableSuggestions: widget.enableSuggestions, + focusNode: widget.focusNode, + initialValue: widget.initialValue, + inputFormatters: widget.inputFormatters, + keyboardAppearance: widget.keyboardAppearance, + onChanged: widget.onChanged, + onEditingComplete: widget.onEditingComplete, + onFieldSubmitted: widget.onSubmitted, + onSaved: widget.onSaved, + onTap: widget.onTap, + onTapOutside: widget.onTapOutside, + readOnly: widget.readOnly, + restorationId: widget.restorationId, + scrollController: widget.scrollController, + scrollPadding: widget.scrollPadding, + scrollPhysics: widget.scrollPhysics, + showCursor: widget.showCursor, + strutStyle: widget.strutStyle, + style: effectiveTextStyle.copyWith(color: effectiveTextColor), + textAlign: widget.textAlign, + textAlignVertical: TextAlignVertical.center, + textCapitalization: widget.textCapitalization, + textDirection: widget.textDirection, + textInputAction: widget.textInputAction, + validator: _validateInput, + decoration: InputDecoration( + /* labelText: widget.hintText, + labelStyle: effectiveTextStyle.copyWith(color: effectiveHintTextColor), + alignLabelWithHint: true, */ + filled: true, + isCollapsed: true, + fillColor: effectiveBackgroundColor, + focusColor: effectiveActiveBorderColor, + hoverColor: Colors.transparent, + border: defaultBorder, + enabledBorder: defaultBorder, + disabledBorder: defaultBorder, + focusedBorder: focusBorder, + errorBorder: errorBorder, + focusedErrorBorder: errorBorder, + constraints: BoxConstraints.tightFor(height: effectiveHeight), + hintText: widget.hintText, + hintStyle: effectiveTextStyle.copyWith(color: effectiveHintTextColor), + errorStyle: const TextStyle(height: 0.1, fontSize: 0), + prefixStyle: effectiveTextStyle.copyWith(color: effectiveTextColor), + prefixIconColor: effectiveTextColor, + prefixIconConstraints: BoxConstraints.tightFor(width: resolvedLeadingWidth), + prefixIcon: widget.leading != null + ? Padding( + padding: EdgeInsetsDirectional.only( + start: resolvedDirectionalPadding.left, + end: effectiveGap, + ), + child: widget.leading, + ) + : const SizedBox.expand(), + suffixStyle: effectiveTextStyle.copyWith(color: effectiveTextColor), + suffixIconColor: effectiveTextColor, + suffixIconConstraints: BoxConstraints.tightFor(width: resolvedTrailingWidth), + suffixIcon: widget.trailing != null + ? Padding( + padding: EdgeInsetsDirectional.only( + start: effectiveGap, + end: resolvedDirectionalPadding.right, + ), + child: widget.trailing, + ) + : const SizedBox.expand(), + ), + ), + ), + ), + ), + if (widget.supporting != null || (_errorText != null && widget.errorBuilder != null)) + RepaintBoundary( + child: AnimatedIconTheme( + color: _errorText != null && widget.errorBuilder != null + ? effectiveErrorBorderColor + : effectiveHintTextColor, + duration: effectiveTransitionDuration, + child: AnimatedDefaultTextStyle( + style: effectiveSupportingTextStyle.copyWith( + color: _errorText != null && widget.errorBuilder != null + ? effectiveErrorBorderColor + : effectiveHintTextColor, + ), + duration: effectiveTransitionDuration, + child: Padding( + padding: effectiveSupportingPadding, + child: _errorText != null && widget.errorBuilder != null + ? widget.errorBuilder!(context, _errorText) + : widget.supporting, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +}