diff --git a/optimus/lib/src/common/field_error.dart b/optimus/lib/src/common/field_error.dart index 23b44b50..9f425bbf 100644 --- a/optimus/lib/src/common/field_error.dart +++ b/optimus/lib/src/common/field_error.dart @@ -6,30 +6,27 @@ class OptimusFieldError extends StatelessWidget { const OptimusFieldError({ super.key, required this.error, + this.isEnabled = true, }); final String error; + final bool isEnabled; @override - Widget build(BuildContext context) => Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(right: spacing150), - child: Icon( - OptimusIcons.error_circle, - size: 16, - color: context.tokens.textAlertDanger, - ), - ), - OptimusCaption( - child: Text( - error, - style: TextStyle( - color: context.tokens.textAlertDanger, - ), - ), - ), - ], - ); + Widget build(BuildContext context) { + final color = isEnabled + ? context.tokens.textAlertDanger + : context.tokens.textDisabled; + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(right: spacing150), + child: Icon(OptimusIcons.error_circle, size: 16, color: color), + ), + OptimusCaption(child: Text(error, style: TextStyle(color: color))), + ], + ); + } } diff --git a/optimus/lib/src/common/field_label.dart b/optimus/lib/src/common/field_label.dart index 3f8e2670..e18daf18 100644 --- a/optimus/lib/src/common/field_label.dart +++ b/optimus/lib/src/common/field_label.dart @@ -7,20 +7,27 @@ class OptimusFieldLabel extends StatelessWidget { super.key, required this.label, this.isRequired = false, + this.isEnabled = true, }); final String label; final bool isRequired; + final bool isEnabled; @override - Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.only(bottom: 2), - child: OptimusLabel( - variation: Variation.variationSecondary, - child: Text( - isRequired ? '$label *' : label, - style: TextStyle(color: context.tokens.textStaticPrimary), - ), + Widget build(BuildContext context) { + final tokens = OptimusTheme.of(context).tokens; + final color = isEnabled ? tokens.textStaticPrimary : tokens.textDisabled; + + return Padding( + padding: const EdgeInsets.only(bottom: 2), + child: OptimusLabel( + variation: Variation.variationSecondary, + child: Text( + isRequired ? '$label *' : label, + style: TextStyle(color: color), ), - ); + ), + ); + } } diff --git a/optimus/lib/src/common/field_wrapper.dart b/optimus/lib/src/common/field_wrapper.dart index 400aea05..baec3b81 100644 --- a/optimus/lib/src/common/field_wrapper.dart +++ b/optimus/lib/src/common/field_wrapper.dart @@ -4,7 +4,6 @@ import 'package:flutter/widgets.dart'; import 'package:optimus/optimus.dart'; import 'package:optimus/src/common/field_error.dart'; import 'package:optimus/src/common/field_label.dart'; -import 'package:optimus/src/constants.dart'; import 'package:optimus/src/typography/presets.dart'; class FieldWrapper extends StatefulWidget { @@ -85,15 +84,13 @@ class _FieldWrapper extends State with ThemeGetter { bool get _isFocused => widget.isFocused ?? widget.focusNode.hasFocus; - Color get _background => widget.isEnabled - ? theme.tokens.backgroundStaticFlat - : theme.tokens.backgroundDisabled; + Color? get _background => widget.isEnabled ? null : tokens.backgroundDisabled; Color get _borderColor { - if (!widget.isEnabled) return theme.tokens.borderDisabled; - if (widget.hasError) return theme.tokens.borderAlertDanger; - if (_isFocused) return theme.tokens.borderInteractiveFocus; - if (_isHovered) return theme.tokens.borderInteractiveSecondaryHover; + if (!widget.isEnabled) return tokens.borderDisabled; + if (widget.hasError) return tokens.borderAlertDanger; + if (_isFocused) return tokens.borderInteractiveFocus; + if (_isHovered) return tokens.borderInteractiveSecondaryHover; return theme.tokens.borderInteractiveSecondaryDefault; } @@ -106,100 +103,101 @@ class _FieldWrapper extends State with ThemeGetter { final prefix = widget.prefix; final suffix = widget.suffix; + final captionColor = + widget.isEnabled ? tokens.textStaticSecondary : tokens.textDisabled; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, - children: [ + children: [ Padding( padding: widget.size.labelPadding, child: Row( children: [ if (label != null) - OptimusFieldLabel(label: label, isRequired: widget.isRequired), + OptimusFieldLabel( + label: label, + isRequired: widget.isRequired, + isEnabled: widget.isEnabled, + ), const Spacer(), if (caption != null) _InputCaption( caption: caption, captionIcon: widget.captionIcon, + isEnabled: widget.isEnabled, ), ], ), ), - Opacity( - opacity: - widget.isEnabled ? OpacityValue.enabled : OpacityValue.disabled, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - IgnorePointer( - ignoring: !widget.isEnabled, - child: - // Decoration is nullable, cannot use DecoratedBox - // ignore: use_decorated_box - Container( - key: widget.fieldBoxKey, - decoration: widget.hasBorders - ? BoxDecoration( - color: _background, - borderRadius: const BorderRadius.all(borderRadius100), - border: Border.all(color: _borderColor, width: 1), - ) - : null, - child: MouseRegion( - onEnter: (_) => setState(() => _isHovered = true), - onExit: (_) => setState(() => _isHovered = false), - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: widget.size.contentPadding, + IgnorePointer( + ignoring: !widget.isEnabled, + child: + // Decoration is nullable, cannot use DecoratedBox + // ignore: use_decorated_box + Container( + key: widget.fieldBoxKey, + decoration: widget.hasBorders + ? BoxDecoration( + color: _background, + borderRadius: const BorderRadius.all(borderRadius100), + border: Border.all(color: _borderColor, width: 1.5), + ) + : null, + child: MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: widget.size.height, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: widget.size.contentPadding, + ), + child: Row( + children: [ + if (prefix != null) + Padding( + padding: const EdgeInsets.only(right: spacing50), + child: _Styled( + isEnabled: widget.isEnabled, + child: prefix, + ), ), - child: Row( - children: [ - if (prefix != null) - Padding( - padding: - const EdgeInsets.only(right: spacing50), - child: _Styled( - isEnabled: widget.isEnabled, - child: prefix, - ), - ), - ...widget.children, - if (suffix != null) - Padding( - padding: const EdgeInsets.only(left: spacing50), - child: _Styled( - isEnabled: widget.isEnabled, - child: suffix, - ), - ), - ], + ...widget.children, + if (suffix != null) + Padding( + padding: const EdgeInsets.only(left: spacing50), + child: _Styled( + isEnabled: widget.isEnabled, + child: suffix, + ), ), - ), - ), + ], ), ), ), - if (helperMessage != null) - Padding( - padding: widget.size.helperPadding, - child: OptimusCaption( - child: DefaultTextStyle.merge( - style: TextStyle( - color: context.tokens.textStaticSecondary, - ), - child: helperMessage, - ), - ), - ), - if (_isUsingBottomHint && _normalizedError.isNotEmpty) - Padding( - padding: widget.size.errorPadding, - child: OptimusFieldError(error: _normalizedError), - ), - ], + ), ), ), + if (helperMessage != null) + Padding( + padding: widget.size.helperPadding, + child: OptimusCaption( + child: DefaultTextStyle.merge( + style: TextStyle(color: captionColor), + child: helperMessage, + ), + ), + ), + if (_isUsingBottomHint && _normalizedError.isNotEmpty) + Padding( + padding: widget.size.errorPadding, + child: OptimusFieldError( + error: _normalizedError, + isEnabled: widget.isEnabled, + ), + ), ], ); } @@ -209,65 +207,61 @@ class _InputCaption extends StatelessWidget { const _InputCaption({ required this.caption, this.captionIcon, + this.isEnabled = true, }); final Widget caption; final IconData? captionIcon; - - @override - Widget build(BuildContext context) => Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: spacing50), - child: OptimusCaption( - variation: Variation.variationSecondary, - child: DefaultTextStyle.merge( - style: TextStyle(color: context.tokens.textStaticSecondary), - child: caption, - ), - ), - ), - if (captionIcon != null) - Icon( - captionIcon, - color: OptimusTheme.of(context).tokens.textStaticPrimary, - size: _captionIconSize, - ), - ], - ); -} - -class _Icon extends StatelessWidget { - const _Icon({required this.child, required this.isEnabled}); - - final Widget child; final bool isEnabled; @override Widget build(BuildContext context) { - final theme = OptimusTheme.of(context); - final color = - isEnabled ? theme.tokens.textStaticPrimary : theme.tokens.textDisabled; + final tokens = OptimusTheme.of(context).tokens; + final iconColor = + isEnabled ? tokens.textStaticPrimary : tokens.textDisabled; + final captionColor = + isEnabled ? tokens.textStaticSecondary : tokens.textDisabled; - return IconTheme(data: IconThemeData(color: color, size: 24), child: child); + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: spacing50), + child: OptimusCaption( + variation: Variation.variationSecondary, + child: DefaultTextStyle.merge( + style: TextStyle(color: captionColor), + child: caption, + ), + ), + ), + if (captionIcon != null) + Icon(captionIcon, color: iconColor, size: _captionIconSize), + ], + ); } } class _Styled extends StatelessWidget { - const _Styled({required this.child, required this.isEnabled}); + const _Styled({required this.child, this.isEnabled = true}); final Widget child; final bool isEnabled; @override Widget build(BuildContext context) { - final theme = OptimusTheme.of(context); - final color = - isEnabled ? theme.tokens.textStaticTertiary : theme.tokens.textDisabled; + final tokens = OptimusTheme.of(context).tokens; + final textColor = + isEnabled ? tokens.textStaticTertiary : tokens.textDisabled; + final iconColor = + !isEnabled ? tokens.textDisabled : tokens.textStaticPrimary; return DefaultTextStyle.merge( - style: preset100s.copyWith(color: color), - child: _Icon(isEnabled: isEnabled, child: child), + style: preset200r.copyWith(color: textColor), + child: IconTheme( + data: IconThemeData(color: iconColor, size: _iconSize), + child: child, + ), ); } } @@ -292,6 +286,13 @@ extension on OptimusWidgetSize { OptimusWidgetSize.medium => spacing200, OptimusWidgetSize.large => spacing250, }; + + double get height => switch (this) { + OptimusWidgetSize.small => spacing400, + OptimusWidgetSize.medium => spacing500, + OptimusWidgetSize.large => spacing600, + }; } const _captionIconSize = 16.0; +const _iconSize = 16.0; diff --git a/optimus/lib/src/common/item_state.dart b/optimus/lib/src/common/item_state.dart deleted file mode 100644 index 19259211..00000000 --- a/optimus/lib/src/common/item_state.dart +++ /dev/null @@ -1 +0,0 @@ -enum InteractableState { normal, hovered, pressed, disabled } diff --git a/optimus/lib/src/form/date_input_field.dart b/optimus/lib/src/form/date_input_field.dart index 42431a52..2f1303c7 100644 --- a/optimus/lib/src/form/date_input_field.dart +++ b/optimus/lib/src/form/date_input_field.dart @@ -77,9 +77,11 @@ class _OptimusDateInputFieldState extends State placeholderStyle: _placeholderStyle, ); - TextStyle get _placeholderStyle => theme.getPlaceholderStyle(widget.size); + TextStyle get _placeholderStyle => + theme.getPlaceholderStyle(isEnabled: widget.isEnabled); - TextStyle get _inputStyle => theme.getTextInputStyle(widget.size); + TextStyle get _inputStyle => + theme.getTextInputStyle(isEnabled: widget.isEnabled); @override void didUpdateWidget(covariant OptimusDateInputField oldWidget) { diff --git a/optimus/lib/src/form/form_style.dart b/optimus/lib/src/form/form_style.dart index 7860254d..65e5bf57 100644 --- a/optimus/lib/src/form/form_style.dart +++ b/optimus/lib/src/form/form_style.dart @@ -3,22 +3,11 @@ import 'package:optimus/optimus.dart'; import 'package:optimus/src/typography/presets.dart'; extension TextInputStyle on OptimusThemeData { - TextStyle getTextInputStyle(OptimusWidgetSize size) => switch (size) { - OptimusWidgetSize.small => - preset200s.copyWith(color: colors.defaultTextColor), - OptimusWidgetSize.medium || - OptimusWidgetSize.large => - preset300s.copyWith(color: colors.defaultTextColor), - }; + TextStyle getTextInputStyle({bool isEnabled = true}) => preset200r.copyWith( + color: isEnabled ? tokens.textStaticPrimary : tokens.textDisabled, + ); - TextStyle getPlaceholderStyle(OptimusWidgetSize size) { - final color = isDark ? colors.neutral0t64 : colors.neutral1000t64; - - return switch (size) { - OptimusWidgetSize.small => preset200s.copyWith(color: color), - OptimusWidgetSize.medium || - OptimusWidgetSize.large => - preset300s.copyWith(color: color), - }; - } + TextStyle getPlaceholderStyle({bool isEnabled = true}) => preset200r.copyWith( + color: isEnabled ? tokens.textStaticTertiary : tokens.textDisabled, + ); } diff --git a/optimus/lib/src/form/input_field.dart b/optimus/lib/src/form/input_field.dart index c61f2bd5..c2621618 100644 --- a/optimus/lib/src/form/input_field.dart +++ b/optimus/lib/src/form/input_field.dart @@ -273,11 +273,11 @@ class _OptimusInputFieldState extends State textInputAction: widget.textInputAction, placeholder: widget.placeholder, placeholderStyle: widget.placeholderStyle ?? - theme.getPlaceholderStyle(widget.size), + theme.getPlaceholderStyle(isEnabled: widget.isEnabled), focusNode: _effectiveFocusNode, enabled: widget.isEnabled, - padding: widget.size.contentPadding, - style: theme.getTextInputStyle(widget.size), + padding: EdgeInsets.zero, + style: theme.getTextInputStyle(isEnabled: widget.isEnabled), // [CupertinoTextField] will try to resolve colors to its own theme, // this will ensure the visible [BoxDecoration] is from the // [FieldWrapper] above. @@ -332,15 +332,4 @@ class _ClearAllButton extends StatelessWidget { ); } -extension on OptimusWidgetSize { - EdgeInsets get contentPadding => switch (this) { - OptimusWidgetSize.small => - const EdgeInsets.symmetric(vertical: spacing50), - OptimusWidgetSize.medium => - const EdgeInsets.symmetric(vertical: spacing100), - OptimusWidgetSize.large => - const EdgeInsets.symmetric(vertical: spacing150), - }; -} - const double _iconSize = 24; diff --git a/optimus/lib/src/spacing.dart b/optimus/lib/src/spacing.dart index 341da90b..dae7ec09 100644 --- a/optimus/lib/src/spacing.dart +++ b/optimus/lib/src/spacing.dart @@ -8,6 +8,7 @@ const double spacing250 = 20; const double spacing300 = 24; const double spacing400 = 32; const double spacing500 = 40; +const double spacing600 = 48; const double spacing700 = 56; const double spacing900 = 72; const double spacing1200 = 96;