diff --git a/example/lib/src/storybook/stories/textarea.dart b/example/lib/src/storybook/stories/textarea.dart new file mode 100644 index 00000000..e45b5893 --- /dev/null +++ b/example/lib/src/storybook/stories/textarea.dart @@ -0,0 +1,128 @@ +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'; + +class TextAreaStory extends Story { + TextAreaStory() + : super( + name: "TextArea", + builder: (context) { + final textColorsKnob = context.knobs.options( + label: "textColor", + description: "MoonColors variants for MoonTextArea text.", + initial: 40, // null + options: colorOptions, + ); + + final textColor = colorTable(context)[textColorsKnob]; + + final hintTextColorsKnob = context.knobs.options( + label: "hintTextColor", + description: "MoonColors variants for MoonTextArea hint text.", + initial: 40, // null + options: colorOptions, + ); + + final hintTextColor = colorTable(context)[hintTextColorsKnob]; + + final backgroundColorsKnob = context.knobs.options( + label: "backgroundColor", + description: "MoonColors variants for MoonTextArea background.", + initial: 40, // null + options: colorOptions, + ); + + final backgroundColor = colorTable(context)[backgroundColorsKnob]; + + final activeBorderColorsKnob = context.knobs.options( + label: "activeBorderColor", + description: "MoonColors variants for MoonTextArea active border.", + initial: 40, // null + options: colorOptions, + ); + + final activeBorderColor = colorTable(context)[activeBorderColorsKnob]; + + final inactiveBorderColorsKnob = context.knobs.options( + label: "inactiveBorderColor", + description: "MoonColors variants for MoonTextArea inactive border.", + initial: 40, // null + options: colorOptions, + ); + + final inactiveBorderColor = colorTable(context)[inactiveBorderColorsKnob]; + + final errorBorderColorsKnob = context.knobs.options( + label: "errorBorderColor", + description: "MoonColors variants for MoonTextArea error border.", + initial: 40, // null + options: colorOptions, + ); + + final errorBorderColor = colorTable(context)[errorBorderColorsKnob]; + + final borderRadiusKnob = context.knobs.sliderInt( + max: 32, + initial: 8, + label: "borderRadius", + description: "Border radius for MoonTextArea.", + ); + + final enabledKnob = context.knobs.boolean( + label: "enabled", + description: "Switch between enabled and disabled states.", + initial: true, + ); + + final setRtlModeKnob = context.knobs.boolean( + label: "RTL mode", + description: "Switch between LTR and RTL modes.", + ); + + return Directionality( + textDirection: setRtlModeKnob ? TextDirection.rtl : TextDirection.ltr, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 64), + Form( + child: Builder( + builder: (context) { + return Column( + children: [ + MoonTextArea( + enabled: enabledKnob, + height: 300, + textColor: textColor, + hintTextColor: hintTextColor, + backgroundColor: backgroundColor, + activeBorderColor: activeBorderColor, + inactiveBorderColor: inactiveBorderColor, + errorBorderColor: errorBorderColor, + borderRadius: BorderRadius.circular(borderRadiusKnob.toDouble()), + hintText: "Enter your text here...", + validator: (value) => value?.length != null && value!.length < 10 + ? "The text should be longer than 10 characters." + : null, + errorBuilder: (context, errorText) => Text(errorText!), + ), + const SizedBox(height: 16), + 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 9f89d842..e9d56d8d 100644 --- a/example/lib/src/storybook/storybook.dart +++ b/example/lib/src/storybook/storybook.dart @@ -16,6 +16,7 @@ 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/toast.dart'; import 'package:example/src/storybook/stories/tooltip.dart'; import 'package:flutter/material.dart'; @@ -89,6 +90,7 @@ class StorybookPage extends StatelessWidget { RadioStory(), SwitchStory(), TagStory(), + TextAreaStory(), ToastStory(), TooltipStory(), ], diff --git a/lib/moon_design.dart b/lib/moon_design.dart index 50f918ff..a8558149 100644 --- a/lib/moon_design.dart +++ b/lib/moon_design.dart @@ -21,15 +21,18 @@ 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/theme.dart'; export 'package:moon_design/src/theme/toast/toast_theme.dart'; export 'package:moon_design/src/theme/tooltip/tooltip_theme.dart'; export 'package:moon_design/src/theme/typography/text_colors.dart'; export 'package:moon_design/src/theme/typography/text_styles.dart'; export 'package:moon_design/src/theme/typography/typography.dart'; + export 'package:moon_design/src/utils/extensions.dart'; export 'package:moon_design/src/utils/measure_size.dart'; export 'package:moon_design/src/utils/widget_surveyor.dart'; + export 'package:moon_design/src/widgets/accordion/accordion_item.dart'; export 'package:moon_design/src/widgets/alert/alert.dart'; export 'package:moon_design/src/widgets/alert/filled_alert.dart'; @@ -59,5 +62,6 @@ 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/toast/toast.dart'; export 'package:moon_design/src/widgets/tooltip/tooltip.dart'; diff --git a/lib/src/theme/accordion/accordion_item_colors.dart b/lib/src/theme/accordion/accordion_item_colors.dart index d79796f2..49284e48 100644 --- a/lib/src/theme/accordion/accordion_item_colors.dart +++ b/lib/src/theme/accordion/accordion_item_colors.dart @@ -10,8 +10,8 @@ class MoonAccordionItemColors extends ThemeExtension wi expandedBackgroundColor: MoonColors.light.gohan, borderColor: MoonColors.light.beerus, dividerColor: MoonColors.light.beerus, - expandedIconColor: MoonColors.light.bulma, - iconColor: MoonColors.light.trunks, + trailingIconColor: MoonColors.light.trunks, + expandedTrailingIconColor: MoonColors.light.bulma, ); static final dark = MoonAccordionItemColors( @@ -19,8 +19,8 @@ class MoonAccordionItemColors extends ThemeExtension wi expandedBackgroundColor: MoonColors.dark.gohan, borderColor: MoonColors.dark.beerus, dividerColor: MoonColors.dark.beerus, - iconColor: MoonColors.dark.trunks, - expandedIconColor: MoonColors.dark.bulma, + trailingIconColor: MoonColors.dark.trunks, + expandedTrailingIconColor: MoonColors.dark.bulma, ); /// Accordion item background color. @@ -35,19 +35,19 @@ class MoonAccordionItemColors extends ThemeExtension wi /// Accordion item divider color. final Color dividerColor; - /// Accordion item icon color. - final Color iconColor; + /// Accordion item trailing icon color. + final Color trailingIconColor; - /// Expanded accordion item icon color. - final Color expandedIconColor; + /// Expanded accordion item trailing icon color. + final Color expandedTrailingIconColor; const MoonAccordionItemColors({ required this.backgroundColor, required this.expandedBackgroundColor, required this.borderColor, required this.dividerColor, - required this.expandedIconColor, - required this.iconColor, + required this.trailingIconColor, + required this.expandedTrailingIconColor, }); @override @@ -56,16 +56,16 @@ class MoonAccordionItemColors extends ThemeExtension wi Color? expandedBackgroundColor, Color? borderColor, Color? dividerColor, - Color? expandedIconColor, - Color? iconColor, + Color? trailingIconColor, + Color? expandedTrailingIconColor, }) { return MoonAccordionItemColors( backgroundColor: backgroundColor ?? this.backgroundColor, expandedBackgroundColor: expandedBackgroundColor ?? this.expandedBackgroundColor, borderColor: borderColor ?? this.borderColor, dividerColor: dividerColor ?? this.dividerColor, - expandedIconColor: expandedIconColor ?? this.expandedIconColor, - iconColor: iconColor ?? this.iconColor, + trailingIconColor: trailingIconColor ?? this.trailingIconColor, + expandedTrailingIconColor: expandedTrailingIconColor ?? this.expandedTrailingIconColor, ); } @@ -78,8 +78,8 @@ class MoonAccordionItemColors extends ThemeExtension wi expandedBackgroundColor: Color.lerp(expandedBackgroundColor, other.expandedBackgroundColor, t)!, borderColor: Color.lerp(borderColor, other.borderColor, t)!, dividerColor: Color.lerp(dividerColor, other.dividerColor, t)!, - expandedIconColor: Color.lerp(expandedIconColor, other.expandedIconColor, t)!, - iconColor: Color.lerp(iconColor, other.iconColor, t)!, + trailingIconColor: Color.lerp(trailingIconColor, other.trailingIconColor, t)!, + expandedTrailingIconColor: Color.lerp(expandedTrailingIconColor, other.expandedTrailingIconColor, t)!, ); } @@ -92,7 +92,7 @@ class MoonAccordionItemColors extends ThemeExtension wi ..add(ColorProperty("expandedBackgroundColor", expandedBackgroundColor)) ..add(ColorProperty("borderColor", borderColor)) ..add(ColorProperty("dividerColor", dividerColor)) - ..add(ColorProperty("expandedIconColor", expandedIconColor)) - ..add(ColorProperty("iconColor", iconColor)); + ..add(ColorProperty("trailingIconColor", trailingIconColor)) + ..add(ColorProperty("expandedTrailingIconColor", expandedTrailingIconColor)); } } diff --git a/lib/src/theme/textarea/textarea_colors.dart b/lib/src/theme/textarea/textarea_colors.dart new file mode 100644 index 00000000..a8cc9e79 --- /dev/null +++ b/lib/src/theme/textarea/textarea_colors.dart @@ -0,0 +1,89 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/colors.dart'; + +@immutable +class MoonTextAreaColors extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonTextAreaColors( + backgroundColor: MoonColors.light.gohan, + activeBorderColor: MoonColors.light.piccolo, + inactiveBorderColor: MoonColors.light.beerus, + errorBorderColor: MoonColors.light.chiChi100, + hintTextColor: MoonColors.light.trunks, + ); + + static final dark = MoonTextAreaColors( + backgroundColor: MoonColors.dark.gohan, + activeBorderColor: MoonColors.dark.piccolo, + inactiveBorderColor: MoonColors.dark.beerus, + errorBorderColor: MoonColors.dark.chiChi100, + hintTextColor: MoonColors.dark.trunks, + ); + + /// TextArea background color. + final Color backgroundColor; + + /// TextArea active border color. + final Color activeBorderColor; + + /// TextArea inactive border color. + final Color inactiveBorderColor; + + /// TextArea error border color. + final Color errorBorderColor; + + /// TextArea hint text color. + final Color hintTextColor; + + const MoonTextAreaColors({ + required this.backgroundColor, + required this.activeBorderColor, + required this.inactiveBorderColor, + required this.errorBorderColor, + required this.hintTextColor, + }); + + @override + MoonTextAreaColors copyWith({ + Color? backgroundColor, + Color? activeBorderColor, + Color? inactiveBorderColor, + Color? errorBorderColor, + Color? hoverBorderColor, + Color? hintTextColor, + }) { + return MoonTextAreaColors( + backgroundColor: backgroundColor ?? this.backgroundColor, + activeBorderColor: activeBorderColor ?? this.activeBorderColor, + inactiveBorderColor: inactiveBorderColor ?? this.inactiveBorderColor, + errorBorderColor: errorBorderColor ?? this.errorBorderColor, + hintTextColor: hintTextColor ?? this.hintTextColor, + ); + } + + @override + MoonTextAreaColors lerp(ThemeExtension? other, double t) { + if (other is! MoonTextAreaColors) return this; + + return MoonTextAreaColors( + 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)!, + hintTextColor: Color.lerp(hintTextColor, other.hintTextColor, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonTextAreaColors")) + ..add(ColorProperty("backgroundColor", backgroundColor)) + ..add(ColorProperty("activeBorderColor", activeBorderColor)) + ..add(ColorProperty("inactiveBorderColor", inactiveBorderColor)) + ..add(ColorProperty("errorBorderColor", errorBorderColor)) + ..add(ColorProperty("hintTextColor", hintTextColor)); + } +} diff --git a/lib/src/theme/textarea/textarea_properties.dart b/lib/src/theme/textarea/textarea_properties.dart new file mode 100644 index 00000000..85bad80b --- /dev/null +++ b/lib/src/theme/textarea/textarea_properties.dart @@ -0,0 +1,90 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/borders.dart'; +import 'package:moon_design/src/theme/typography/text_styles.dart'; + +@immutable +class MoonTextAreaProperties extends ThemeExtension with DiagnosticableTreeMixin { + static final properties = MoonTextAreaProperties( + borderRadius: MoonBorders.borders.interactiveSm, + textPadding: const EdgeInsets.all(16), + transitionDuration: const Duration(milliseconds: 200), + transitionCurve: Curves.easeInOutCubic, + textStyle: MoonTextStyles.body.text16, + errorTextStyle: MoonTextStyles.body.text12, + ); + + /// TextArea border radius. + final BorderRadius borderRadius; + + /// TextArea text padding. + final EdgeInsetsGeometry textPadding; + + /// TextArea transition duration. + final Duration transitionDuration; + + /// TextArea transition curve. + final Curve transitionCurve; + + /// TextArea text style. + final TextStyle textStyle; + + /// TextArea error text style. + final TextStyle errorTextStyle; + + const MoonTextAreaProperties({ + required this.borderRadius, + required this.textPadding, + required this.transitionDuration, + required this.transitionCurve, + required this.textStyle, + required this.errorTextStyle, + }); + + @override + MoonTextAreaProperties copyWith({ + BorderRadius? borderRadius, + EdgeInsetsGeometry? textPadding, + Duration? transitionDuration, + Curve? transitionCurve, + TextStyle? textStyle, + TextStyle? errorTextStyle, + }) { + return MoonTextAreaProperties( + borderRadius: borderRadius ?? this.borderRadius, + textPadding: textPadding ?? this.textPadding, + transitionDuration: transitionDuration ?? this.transitionDuration, + transitionCurve: transitionCurve ?? this.transitionCurve, + textStyle: textStyle ?? this.textStyle, + errorTextStyle: errorTextStyle ?? this.errorTextStyle, + ); + } + + @override + MoonTextAreaProperties lerp(ThemeExtension? other, double t) { + if (other is! MoonTextAreaProperties) return this; + + return MoonTextAreaProperties( + borderRadius: BorderRadius.lerp(borderRadius, other.borderRadius, t)!, + textPadding: EdgeInsetsGeometry.lerp(textPadding, other.textPadding, t)!, + transitionDuration: lerpDuration(transitionDuration, other.transitionDuration, t), + transitionCurve: other.transitionCurve, + textStyle: TextStyle.lerp(textStyle, other.textStyle, t)!, + errorTextStyle: TextStyle.lerp(errorTextStyle, other.errorTextStyle, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonTextAreaProperties")) + ..add(DiagnosticsProperty("borderRadius", borderRadius)) + ..add(DiagnosticsProperty("textPadding", textPadding)) + ..add(DiagnosticsProperty("transitionDuration", transitionDuration)) + ..add(DiagnosticsProperty("transitionCurve", transitionCurve)) + ..add(DiagnosticsProperty("textStyle", textStyle)) + ..add(DiagnosticsProperty("errorTextStyle", errorTextStyle)); + } +} diff --git a/lib/src/theme/textarea/textarea_theme.dart b/lib/src/theme/textarea/textarea_theme.dart new file mode 100644 index 00000000..f7467a24 --- /dev/null +++ b/lib/src/theme/textarea/textarea_theme.dart @@ -0,0 +1,59 @@ +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'; + +@immutable +class MoonTextAreaTheme extends ThemeExtension with DiagnosticableTreeMixin { + static final light = MoonTextAreaTheme( + colors: MoonTextAreaColors.light, + properties: MoonTextAreaProperties.properties, + ); + + static final dark = MoonTextAreaTheme( + colors: MoonTextAreaColors.dark, + properties: MoonTextAreaProperties.properties, + ); + + /// TextArea colors. + final MoonTextAreaColors colors; + + /// TextArea properties. + final MoonTextAreaProperties properties; + + const MoonTextAreaTheme({ + required this.colors, + required this.properties, + }); + + @override + MoonTextAreaTheme copyWith({ + MoonTextAreaColors? colors, + MoonTextAreaProperties? properties, + }) { + return MoonTextAreaTheme( + colors: colors ?? this.colors, + properties: properties ?? this.properties, + ); + } + + @override + MoonTextAreaTheme lerp(ThemeExtension? other, double t) { + if (other is! MoonTextAreaTheme) return this; + + return MoonTextAreaTheme( + colors: colors.lerp(other.colors, t), + properties: properties.lerp(other.properties, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder diagnosticProperties) { + super.debugFillProperties(diagnosticProperties); + diagnosticProperties + ..add(DiagnosticsProperty("type", "MoonTextAreaTheme")) + ..add(DiagnosticsProperty("colors", colors)) + ..add(DiagnosticsProperty("properties", properties)); + } +} diff --git a/lib/src/theme/theme.dart b/lib/src/theme/theme.dart index dbc0c706..6f300ef3 100644 --- a/lib/src/theme/theme.dart +++ b/lib/src/theme/theme.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; + import 'package:moon_design/src/theme/accordion/accordion_theme.dart'; import 'package:moon_design/src/theme/alert/alert_theme.dart'; import 'package:moon_design/src/theme/authcode/authcode_theme.dart'; @@ -22,6 +23,7 @@ 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/toast/toast_theme.dart'; import 'package:moon_design/src/theme/tooltip/tooltip_theme.dart'; import 'package:moon_design/src/theme/typography/typography.dart'; @@ -51,6 +53,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { sizes: MoonSizes.sizes, switchTheme: MoonSwitchTheme.light, tagTheme: MoonTagTheme.light, + textAreaTheme: MoonTextAreaTheme.light, toastTheme: MoonToastTheme.light, tooltipTheme: MoonTooltipTheme.light, typography: MoonTypography.light, @@ -79,6 +82,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { sizes: MoonSizes.sizes, switchTheme: MoonSwitchTheme.dark, tagTheme: MoonTagTheme.dark, + textAreaTheme: MoonTextAreaTheme.dark, toastTheme: MoonToastTheme.dark, tooltipTheme: MoonTooltipTheme.dark, typography: MoonTypography.dark, @@ -150,6 +154,9 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { /// Moon Design System MoonTag widget theming. final MoonTagTheme tagTheme; + /// Moon Design System MoonTextArea widget theming. + final MoonTextAreaTheme textAreaTheme; + /// Moon Design System MoonToast widget theming. final MoonToastTheme toastTheme; @@ -182,6 +189,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { required this.sizes, required this.switchTheme, required this.tagTheme, + required this.textAreaTheme, required this.toastTheme, required this.tooltipTheme, required this.typography, @@ -211,6 +219,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { MoonSizes? sizes, MoonSwitchTheme? switchTheme, MoonTagTheme? tagTheme, + MoonTextAreaTheme? textAreaTheme, MoonToastTheme? toastTheme, MoonTooltipTheme? tooltipTheme, MoonTypography? typography, @@ -238,6 +247,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { sizes: sizes ?? this.sizes, switchTheme: switchTheme ?? this.switchTheme, tagTheme: tagTheme ?? this.tagTheme, + textAreaTheme: textAreaTheme ?? this.textAreaTheme, toastTheme: toastTheme ?? this.toastTheme, tooltipTheme: tooltipTheme ?? this.tooltipTheme, typography: typography ?? this.typography, @@ -271,6 +281,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { sizes: sizes.lerp(other.sizes, t), switchTheme: switchTheme.lerp(other.switchTheme, t), tagTheme: tagTheme.lerp(other.tagTheme, t), + textAreaTheme: textAreaTheme.lerp(other.textAreaTheme, t), toastTheme: toastTheme.lerp(other.toastTheme, t), tooltipTheme: tooltipTheme.lerp(other.tooltipTheme, t), typography: typography.lerp(other.typography, t), @@ -304,6 +315,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { ..add(DiagnosticsProperty("MoonSizes", sizes)) ..add(DiagnosticsProperty("MoonSwitchTheme", switchTheme)) ..add(DiagnosticsProperty("MoonTagTheme", tagTheme)) + ..add(DiagnosticsProperty("MoonTextAreaTheme", textAreaTheme)) ..add(DiagnosticsProperty("MoonToastTheme", toastTheme)) ..add(DiagnosticsProperty("MoonTooltipTheme", tooltipTheme)) ..add(DiagnosticsProperty("MoonTypography", typography)); diff --git a/lib/src/widgets/accordion/accordion_item.dart b/lib/src/widgets/accordion/accordion_item.dart index 0780b427..be7f523e 100644 --- a/lib/src/widgets/accordion/accordion_item.dart +++ b/lib/src/widgets/accordion/accordion_item.dart @@ -57,13 +57,13 @@ class MoonAccordionItem extends StatefulWidget { /// The color of the divider between the header and the body. final Color? dividerColor; - /// The icon color of accordion's expansion arrow icon when the accordion is expanded. - final Color? iconColor; + /// The color of accordion's trailing icon (downward caret by default) when the accordion is collapsed. + final Color? trailingIconColor; - /// The icon color of accordion's expansion arrow icon when the accordion is collapsed. - final Color? expandedIconColor; + /// The color of accordion's trailing icon (downward caret by default) when the accordion is expanded. + final Color? expandedTrailingIconColor; - /// The color of the accordion's titles when the accordion is expanded. + /// The color of the accordion's title. final Color? textColor; /// Whether to show a border around the accordion. @@ -176,8 +176,8 @@ class MoonAccordionItem extends StatefulWidget { this.backgroundColor, this.expandedBackgroundColor, this.dividerColor, - this.iconColor, - this.expandedIconColor, + this.trailingIconColor, + this.expandedTrailingIconColor, this.textColor, this.showBorder = false, this.showDivider = true, @@ -353,12 +353,12 @@ class _MoonAccordionItemState extends State> with Single context.moonTheme?.accordionTheme.itemColors.backgroundColor ?? MoonColors.light.gohan; - final Color effectiveIconColor = widget.iconColor ?? - context.moonTheme?.accordionTheme.itemColors.iconColor ?? + final Color effectiveIconColor = widget.trailingIconColor ?? + context.moonTheme?.accordionTheme.itemColors.trailingIconColor ?? _getTextColor(context, effectiveBackgroundColor: effectiveBackgroundColor); - final Color effectiveExpandedIconColor = widget.expandedIconColor ?? - context.moonTheme?.accordionTheme.itemColors.expandedIconColor ?? + final Color effectiveExpandedIconColor = widget.expandedTrailingIconColor ?? + context.moonTheme?.accordionTheme.itemColors.expandedTrailingIconColor ?? effectiveIconColor; _iconColorAnimation = @@ -440,7 +440,7 @@ class _MoonAccordionItemState extends State> with Single ? Color.alphaBlend(effectiveHoverEffectColor, _backgroundColorAnimation!.value!) : _backgroundColorAnimation!.value; - final Color effectiveTextColor = + final Color effectiveTextColor = widget.textColor ?? _getTextColor(context, effectiveBackgroundColor: resolvedBackgroundColor ?? effectiveBackgroundColor); return Semantics( diff --git a/lib/src/widgets/textarea/textarea.dart b/lib/src/widgets/textarea/textarea.dart new file mode 100644 index 00000000..b02e7b75 --- /dev/null +++ b/lib/src/widgets/textarea/textarea.dart @@ -0,0 +1,436 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/opacity.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'; + +typedef MoonTextAreaErrorBuilder = Widget Function(BuildContext context, String? errorText); + +class MoonTextArea extends StatefulWidget { + /// Controls the text being edited. + final TextEditingController? controller; + + /// {@macro flutter.widgets.editableText.scrollController} + final ScrollController? scrollController; + + /// {@macro flutter.widgets.editableText.scrollPhysics} + final ScrollPhysics? scrollPhysics; + + /// The background color of the text area. + final Color? backgroundColor; + + /// The border color of the active or focused text area. + final Color? activeBorderColor; + + /// The border color of the inactive text area. + final Color? inactiveBorderColor; + + /// The border color of the error text area. + final Color? errorBorderColor; + + /// The text color of the text area. + final Color? textColor; + + /// The text color of the hint in text area. + final Color? hintTextColor; + + /// 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; + + /// {@macro flutter.widgets.editableText.textCapitalization} + final TextCapitalization textCapitalization; + + /// {@macro flutter.widgets.editableText.textAlign} + final TextAlign textAlign; + + /// {@macro flutter.widgets.editableText.textDirection} + final TextDirection? textDirection; + + /// 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? errorTextStyle; + + /// {@macro flutter.widgets.editableText.strutStyle} + final StrutStyle? strutStyle; + + /// {@macro flutter.widgets.editableText.autocorrect} + final bool autocorrect; + + /// 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 height of the text area (this does not include the space taken by [MoonTextArea.errorBuilder]). + final double? height; + + /// The text for the hint. + final String? hintText; + + /// The initial value of the text area. + 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 border radius of the text area. + final BorderRadius? borderRadius; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip? clipBehavior; + + /// The padding around the text content. + final EdgeInsetsGeometry? textPadding; + + /// {@macro flutter.widgets.editableText.minLines} + /// * [expands], which determines whether the field should fill the height of its parent. + final int? minLines; + + /// The maximum number of characters (Unicode grapheme clusters) to allow in the text area. + /// + /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength} + final int? maxLength; + + /// Determines how the [maxLength] limit should be enforced. + /// + /// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement} + /// + /// {@macro flutter.services.textFormatter.maxLengthEnforcement} + final MaxLengthEnforcement? maxLengthEnforcement; + + /// {@macro flutter.widgets.editableText.inputFormatters} + final List? inputFormatters; + + /// {@macro flutter.widgets.editableText.autofillHints} + /// {@macro flutter.services.AutofillConfiguration.autofillHints} + final Iterable? autofillHints; + + /// Used to set the auto validation mode. + final AutovalidateMode autovalidateMode; + + /// The appearance of the keyboard. + /// + /// This setting is only honored on iOS devices. + /// + /// If unset, defaults to [ThemeData.brightness]. + final Brightness? keyboardAppearance; + + /// {@macro flutter.widgets.editableText.scrollPadding} + final EdgeInsets scrollPadding; + + /// The transition duration for disable animation. + final Duration? transitionDuration; + + /// The transition curve for disable animation. + final Curve? transitionCurve; + + /// {@macro flutter.widgets.editableText.autofocus} + final bool autofocus; + + /// {@macro flutter.widgets.Focus.focusNode}. + final FocusNode? focusNode; + + /// The semantic label for the widget. + final String? semanticLabel; + + /// {@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 void Function(String?)? 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; + + /// A callback that is called when the user taps the text area widget. + final GestureTapCallback? onTap; + + /// A callback that is called when the user taps outside the text area widget. + final TapRegionCallback? onTapOutside; + + /// Validator for the text area widget. + final FormFieldValidator? validator; + + /// Builder for the error widget. + final MoonTextAreaErrorBuilder? errorBuilder; + + const MoonTextArea({ + super.key, + this.controller, + this.scrollController, + this.scrollPhysics, + this.backgroundColor, + this.activeBorderColor, + this.inactiveBorderColor, + this.errorBorderColor, + this.textColor, + this.hintTextColor, + this.textInputAction, + this.textCapitalization = TextCapitalization.none, + this.textAlign = TextAlign.start, + this.textDirection, + this.textStyle, + this.errorTextStyle, + this.strutStyle, + this.autocorrect = true, + this.enabled = true, + this.enableIMEPersonalizedLearning = true, + this.enableInteractiveSelection, + this.enableSuggestions = true, + this.readOnly = false, + this.scribbleEnabled = true, + this.showCursor, + this.height, + this.hintText, + this.initialValue, + this.restorationId, + this.borderRadius, + this.clipBehavior, + this.textPadding, + this.minLines, + this.maxLength, + this.maxLengthEnforcement, + this.inputFormatters, + this.autofillHints, + this.autovalidateMode = AutovalidateMode.disabled, + this.keyboardAppearance, + this.scrollPadding = const EdgeInsets.all(20.0), + this.transitionDuration, + this.transitionCurve, + this.autofocus = false, + this.focusNode, + this.semanticLabel, + this.onChanged, + this.onEditingComplete, + this.onSaved, + this.onSubmitted, + this.onTap, + this.onTapOutside, + this.validator, + this.errorBuilder, + }); + + @override + State createState() => _MoonTextAreaState(); +} + +class _MoonTextAreaState extends State { + 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) { + return MoonColors.light.bulma; + } else { + return MoonColors.dark.bulma; + } + } + + @override + Widget build(BuildContext context) { + final Color effectiveBackgroundColor = + widget.backgroundColor ?? context.moonTheme?.textAreaTheme.colors.backgroundColor ?? MoonColors.light.gohan; + + final Color effectiveActiveBorderColor = widget.activeBorderColor ?? + context.moonTheme?.textAreaTheme.colors.activeBorderColor ?? + MoonColors.light.piccolo; + + final Color effectiveInactiveBorderColor = widget.inactiveBorderColor ?? + context.moonTheme?.textAreaTheme.colors.inactiveBorderColor ?? + MoonColors.light.beerus; + + final Color effectiveErrorBorderColor = widget.errorBorderColor ?? + context.moonTheme?.textAreaTheme.colors.errorBorderColor ?? + MoonColors.light.chiChi100; + + final Color effectiveTextColor = + widget.textColor ?? _getTextColor(context, effectiveBackgroundColor: effectiveBackgroundColor); + + final Color effectiveHintTextColor = + widget.hintTextColor ?? context.moonTheme?.textAreaTheme.colors.hintTextColor ?? MoonColors.light.trunks; + + final BorderRadius effectiveBorderRadius = + widget.borderRadius ?? context.moonTheme?.textAreaTheme.properties.borderRadius ?? BorderRadius.circular(8); + + final double effectiveDisabledOpacityValue = context.moonTheme?.opacity.disabled ?? MoonOpacity.opacities.disabled; + + final EdgeInsetsGeometry effectiveTextPadding = + widget.textPadding ?? context.moonTheme?.textAreaTheme.properties.textPadding ?? const EdgeInsets.all(16); + + final Duration effectiveTransitionDuration = widget.transitionDuration ?? + context.moonTheme?.textAreaTheme.properties.transitionDuration ?? + const Duration(milliseconds: 200); + + final Curve effectiveTransitionCurve = + widget.transitionCurve ?? context.moonTheme?.textAreaTheme.properties.transitionCurve ?? Curves.easeInOutCubic; + + final TextStyle effectiveTextStyle = + widget.textStyle ?? context.moonTheme?.textAreaTheme.properties.textStyle ?? const TextStyle(fontSize: 16); + + final TextStyle effectiveErrorTextStyle = widget.errorTextStyle ?? + context.moonTheme?.textAreaTheme.properties.errorTextStyle ?? + const TextStyle(fontSize: 12); + + final OutlineInputBorder defaultBorder = OutlineInputBorder( + borderRadius: effectiveBorderRadius.smoothBorderRadius, + borderSide: BorderSide(color: effectiveInactiveBorderColor), + ); + + return Semantics( + label: widget.semanticLabel, + child: RepaintBoundary( + child: AnimatedOpacity( + opacity: widget.enabled ? 1.0 : effectiveDisabledOpacityValue, + curve: effectiveTransitionCurve, + duration: effectiveTransitionDuration, + child: Column( + mainAxisSize: MainAxisSize.min, + 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, + borderSide: BorderSide( + color: effectiveErrorBorderColor, + width: 2, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: effectiveBorderRadius.smoothBorderRadius, + borderSide: BorderSide( + color: effectiveActiveBorderColor, + width: 2, + ), + ), + ), + ), + if (_errorText != null && widget.errorBuilder != null) + RepaintBoundary( + child: AnimatedIconTheme( + color: effectiveErrorBorderColor, + duration: effectiveTransitionDuration, + child: AnimatedDefaultTextStyle( + style: effectiveErrorTextStyle.copyWith(color: effectiveErrorBorderColor), + duration: effectiveTransitionDuration, + child: widget.errorBuilder!(context, _errorText), + ), + ), + ), + ], + ), + ), + ), + ); + } +}