diff --git a/example/lib/pages/components/search_bar_example.dart b/example/lib/pages/components/search_bar_example.dart index b44ee768..5395fa47 100644 --- a/example/lib/pages/components/search_bar_example.dart +++ b/example/lib/pages/components/search_bar_example.dart @@ -27,6 +27,7 @@ class _SearchBarExampleState extends State { padding: const EdgeInsets.all(20), child: ZetaSearchBar( onChange: (value) {}, + showSpeechToText: false, textInputAction: TextInputAction.search, onFieldSubmitted: (text) { print(text); diff --git a/example/widgetbook/pages/components/search_bar_widgetbook.dart b/example/widgetbook/pages/components/search_bar_widgetbook.dart index 9072142b..fc767a8d 100644 --- a/example/widgetbook/pages/components/search_bar_widgetbook.dart +++ b/example/widgetbook/pages/components/search_bar_widgetbook.dart @@ -49,7 +49,7 @@ Widget searchBarUseCase(BuildContext context) { size: size, shape: shape, disabled: disabled, - hintText: hint, + placeholder: hint, showSpeechToText: showSpeechToText, onChange: (value) { if (value == null) return; diff --git a/lib/src/components/date_input/date_input.dart b/lib/src/components/date_input/date_input.dart index 20905696..88f45768 100644 --- a/lib/src/components/date_input/date_input.dart +++ b/lib/src/components/date_input/date_input.dart @@ -47,6 +47,7 @@ class ZetaDateInput extends ZetaFormField { return InternalTextInput( label: label, + constrained: true, hintText: hintText, errorText: field.errorText ?? errorText, size: size, diff --git a/lib/src/components/password/password_input.dart b/lib/src/components/password/password_input.dart index cb3c01d8..152de9f8 100644 --- a/lib/src/components/password/password_input.dart +++ b/lib/src/components/password/password_input.dart @@ -50,6 +50,7 @@ class ZetaPasswordInput extends ZetaTextFormField { requirementLevel: requirementLevel, errorText: field.errorText ?? errorText, onSubmit: onSubmit, + constrained: true, disabled: disabled, obscureText: state._obscureText, semanticLabel: semanticLabel, diff --git a/lib/src/components/search_bar/search_bar.dart b/lib/src/components/search_bar/search_bar.dart index 57b1a9e2..fc536ede 100644 --- a/lib/src/components/search_bar/search_bar.dart +++ b/lib/src/components/search_bar/search_bar.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../../zeta_flutter.dart'; import '../../interfaces/form_field.dart'; import '../buttons/input_icon_button.dart'; +import '../text_input/internal_text_input.dart'; /// ZetaSearchBar provides input field for searching. /// {@category Components} @@ -25,8 +26,9 @@ class ZetaSearchBar extends ZetaTextFormField { super.initialValue, this.size = ZetaWidgetSize.medium, this.shape = ZetaWidgetBorder.rounded, - @Deprecated('Use hintText instead. ' 'deprecated as of 0.15.0') String? hint, - this.hintText, + @Deprecated('hint has been removed. ' 'deprecated as of 0.15.0') String? hint, + @Deprecated('Use placeholder instead. ' 'deprecated as of 0.16.0') String? hintText, + this.placeholder, this.onSpeechToText, this.showSpeechToText = true, @Deprecated('Use disabled instead. ' 'enabled is deprecated as of 0.11.0') bool enabled = true, @@ -49,130 +51,74 @@ class ZetaSearchBar extends ZetaTextFormField { _ => zeta.radius.none, }; - final defaultInputBorder = OutlineInputBorder( - borderRadius: borderRadius, - borderSide: BorderSide(color: zeta.colors.cool.shade40), - ); - - final focusedBorder = defaultInputBorder.copyWith( - borderSide: BorderSide( - color: zeta.colors.blue.shade50, - width: zeta.spacing.minimum, - ), - ); - - final disabledborder = defaultInputBorder.copyWith( - borderSide: BorderSide(color: zeta.colors.borderDisabled), - ); - late final double iconSize; - late final double padding; switch (size) { case ZetaWidgetSize.large: iconSize = zeta.spacing.xl_2; - padding = zeta.spacing.medium; case ZetaWidgetSize.medium: iconSize = zeta.spacing.xl; - padding = zeta.spacing.small; case ZetaWidgetSize.small: iconSize = zeta.spacing.large; - padding = zeta.spacing.minimum; } return ZetaRoundedScope( rounded: shape != ZetaWidgetBorder.sharp, child: Semantics( excludeSemantics: disabled, - label: disabled ? hintText ?? 'Search' : null, // TODO(UX-1003): Localize + label: disabled ? placeholder ?? 'Search' : null, // TODO(UX-1003): Localize enabled: disabled ? false : null, - child: TextFormField( + child: InternalTextInput( focusNode: focusNode, - enabled: !disabled, + size: size, + disabled: disabled, + constrained: true, + borderRadius: borderRadius, controller: state.effectiveController, keyboardType: TextInputType.text, textInputAction: textInputAction, - onFieldSubmitted: onFieldSubmitted, - onChanged: state.onChange, - style: ZetaTextStyles.bodyMedium, - decoration: InputDecoration( - isDense: true, - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - vertical: padding, - ), - hintText: hintText ?? 'Search', // TODO(UX-1003): Localize - hintStyle: ZetaTextStyles.bodyMedium.copyWith( - color: !disabled ? zeta.colors.textSubtle : zeta.colors.textDisabled, - ), - prefixIcon: Padding( - padding: EdgeInsets.only(left: zeta.spacing.medium, right: zeta.spacing.small), - child: ZetaIcon( - ZetaIcons.search, - color: !disabled ? zeta.colors.cool.shade70 : zeta.colors.cool.shade50, - size: iconSize, - ), - ), - prefixIconConstraints: BoxConstraints( - minHeight: zeta.spacing.xl_2, - minWidth: zeta.spacing.xl_2, - ), - suffixIcon: IntrinsicHeight( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (state.effectiveController.text.isNotEmpty && !disabled) ...[ - Semantics( - container: true, - button: true, - excludeSemantics: true, - label: clearSemanticLabel, - child: InputIconButton( - icon: ZetaIcons.cancel, - onTap: () => state.onChange(''), - disabled: disabled, - size: size, - color: zeta.colors.iconSubtle, - key: const ValueKey('search-clear-btn'), - ), - ), - if (showSpeechToText) - SizedBox( - height: iconSize, - child: VerticalDivider( - color: zeta.colors.cool.shade40, - width: 5, - thickness: 1, - ), - ), - ], - if (showSpeechToText) - Semantics( - label: microphoneSemanticLabel, - container: true, - button: true, - excludeSemantics: true, - child: InputIconButton( - icon: ZetaIcons.microphone, - onTap: state.onSpeechToText, - key: const ValueKey('speech-to-text-btn'), - disabled: disabled, - size: size, - color: zeta.colors.iconDefault, - ), + placeholder: placeholder ?? 'Search', // TODO(UX-1003): Localize + onSubmit: onFieldSubmitted, + onChange: state.onChange, + prefix: ZetaIcon( + ZetaIcons.search, + color: !disabled ? zeta.colors.iconSubtle : zeta.colors.iconDisabled, + size: iconSize, + ), + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (state.effectiveController.text.isNotEmpty && !disabled) ...[ + InputIconButton( + icon: ZetaIcons.cancel, + onTap: () => state.onChange(''), + disabled: disabled, + size: size, + semanticLabel: clearSemanticLabel, + color: zeta.colors.iconSubtle, + key: const ValueKey('search-clear-btn'), + ), + if (showSpeechToText) + SizedBox( + height: iconSize, + child: VerticalDivider( + color: zeta.colors.cool.shade40, + width: 5, + thickness: 1, ), - ], - ), - ), - suffixIconConstraints: BoxConstraints( - minHeight: zeta.spacing.xl_2, - minWidth: zeta.spacing.xl_2, - ), - filled: !disabled ? null : true, - fillColor: !disabled ? null : zeta.colors.cool.shade30, - enabledBorder: defaultInputBorder, - focusedBorder: focusedBorder, - disabledBorder: disabledborder, + ), + ], + if (showSpeechToText) + InputIconButton( + icon: ZetaIcons.microphone, + onTap: state.onSpeechToText, + key: const ValueKey('speech-to-text-btn'), + disabled: disabled, + semanticLabel: microphoneSemanticLabel, + size: size, + color: zeta.colors.iconDefault, + ), + ], ), ), ), @@ -184,14 +130,13 @@ class ZetaSearchBar extends ZetaTextFormField { /// Default is [ZetaWidgetSize.medium] final ZetaWidgetSize size; + /// Placeholder text for the search field. + final String? placeholder; + /// Determines the shape of the input field. /// Default is [ZetaWidgetBorder.rounded] final ZetaWidgetBorder shape; - /// If provided, displays a hint inside the input field. - /// Default is `Search`. - final String? hintText; - /// The type of action button to use for the keyboard. final TextInputAction? textInputAction; @@ -224,14 +169,14 @@ class ZetaSearchBar extends ZetaTextFormField { properties ..add(EnumProperty('size', size)) ..add(EnumProperty('shape', shape)) - ..add(StringProperty('hintText', hintText)) ..add(StringProperty('initialValue', initialValue)) ..add(ObjectFlagProperty.has('onSpeechToText', onSpeechToText)) ..add(DiagnosticsProperty('showSpeechToText', showSpeechToText)) ..add(DiagnosticsProperty('focusNode', focusNode)) ..add(EnumProperty('textInputAction', textInputAction)) ..add(StringProperty('microphoneSemanticLabel', microphoneSemanticLabel)) - ..add(StringProperty('clearSemanticLabel', clearSemanticLabel)); + ..add(StringProperty('clearSemanticLabel', clearSemanticLabel)) + ..add(StringProperty('placeholder', placeholder)); } } diff --git a/lib/src/components/select_input/select_input.dart b/lib/src/components/select_input/select_input.dart index d530feb2..f2c18f3c 100644 --- a/lib/src/components/select_input/select_input.dart +++ b/lib/src/components/select_input/select_input.dart @@ -54,6 +54,7 @@ class ZetaSelectInput extends ZetaFormField { builder: (context, _, controller) { return InternalTextInput( size: size, + constrained: true, requirementLevel: requirementLevel, disabled: disabled, controller: state.inputController, diff --git a/lib/src/components/text_input/internal_text_input.dart b/lib/src/components/text_input/internal_text_input.dart index 05d7a9c1..2c20bba0 100644 --- a/lib/src/components/text_input/internal_text_input.dart +++ b/lib/src/components/text_input/internal_text_input.dart @@ -36,6 +36,8 @@ class InternalTextInput extends ZetaStatefulWidget { this.externalPrefix, this.semanticLabel, this.borderRadius, + this.textInputAction, + this.constrained = false, }) : requirementLevel = requirementLevel ?? ZetaFormFieldRequirement.none, assert(prefix == null || prefixText == null, 'Only one of prefix or prefixText can be accepted.'), assert(suffix == null || suffixText == null, 'Only one of suffix or suffixText can be accepted.'); @@ -130,6 +132,12 @@ class InternalTextInput extends ZetaStatefulWidget { /// The widget displayed before the input. final Widget? externalPrefix; + /// The action to take when the user submits the input. + final TextInputAction? textInputAction; + + /// Determines if the prefix and suffix should be constrained. + final bool constrained; + @override State createState() => InternalTextInputState(); @override @@ -156,7 +164,9 @@ class InternalTextInput extends ZetaStatefulWidget { ..add(DiagnosticsProperty('keyboardType', keyboardType)) ..add(DiagnosticsProperty('focusNode', focusNode)) ..add(DiagnosticsProperty('borderRadius', borderRadius)) - ..add(StringProperty('semanticLabel', semanticLabel)); + ..add(StringProperty('semanticLabel', semanticLabel)) + ..add(EnumProperty('textInputAction', textInputAction)) + ..add(DiagnosticsProperty('constrained', constrained)); } } @@ -197,7 +207,7 @@ class InternalTextInputState extends State { case ZetaWidgetSize.medium: return EdgeInsets.symmetric( horizontal: Zeta.of(context).spacing.medium, - vertical: Zeta.of(context).spacing.small, + vertical: Zeta.of(context).spacing.medium, ); } } @@ -221,7 +231,7 @@ class InternalTextInputState extends State { width = Zeta.of(context).spacing.xl_6; height = Zeta.of(context).spacing.xl_6; case ZetaWidgetSize.small: - width = Zeta.of(context).spacing.xl_6; + width = Zeta.of(context).spacing.xl_4; height = Zeta.of(context).spacing.xl_4; } return BoxConstraints( @@ -277,11 +287,11 @@ class InternalTextInputState extends State { ); OutlineInputBorder _focusedBorder(bool rounded) => _baseBorder(rounded).copyWith( - borderSide: BorderSide(color: _colors.primary.shade50, width: ZetaBorders.medium), - ); // TODO(mikecoomber): change to colors.borderPrimary when added + borderSide: BorderSide(color: _colors.borderPrimary, width: ZetaBorders.medium), + ); OutlineInputBorder _errorBorder(bool rounded) => _baseBorder(rounded).copyWith( - borderSide: BorderSide(color: _colors.error, width: ZetaBorders.medium), + borderSide: BorderSide(color: _colors.borderNegative, width: ZetaBorders.medium), ); @override @@ -337,6 +347,7 @@ class InternalTextInputState extends State { onChanged: widget.onChange, onSubmitted: widget.onSubmit, style: _baseTextStyle, + textInputAction: widget.textInputAction, cursorErrorColor: _colors.error, obscureText: widget.obscureText, focusNode: widget.focusNode, @@ -345,9 +356,11 @@ class InternalTextInputState extends State { contentPadding: _contentPadding, filled: true, prefixIcon: _prefix, - prefixIconConstraints: widget.prefixText != null ? _affixConstraints : null, + prefixIconConstraints: + widget.prefixText != null || widget.constrained ? _affixConstraints : null, suffixIcon: _suffix, - suffixIconConstraints: widget.suffixText != null ? _affixConstraints : null, + suffixIconConstraints: + widget.suffixText != null || widget.constrained ? _affixConstraints : null, focusColor: _backgroundColor, hoverColor: _backgroundColor, fillColor: _backgroundColor, @@ -358,7 +371,9 @@ class InternalTextInputState extends State { errorBorder: widget.disabled ? _baseBorder(rounded) : _errorBorder(rounded), hintText: widget.placeholder, errorText: widget.errorText, - hintStyle: _baseTextStyle, + hintStyle: _baseTextStyle.copyWith( + color: widget.disabled ? _colors.textDisabled : _colors.textSubtle, + ), errorStyle: const TextStyle(height: 0.001, color: Colors.transparent), ), ), diff --git a/lib/src/components/time_input/time_input.dart b/lib/src/components/time_input/time_input.dart index 3d3d0467..ee7e49df 100644 --- a/lib/src/components/time_input/time_input.dart +++ b/lib/src/components/time_input/time_input.dart @@ -49,6 +49,7 @@ class ZetaTimeInput extends ZetaFormField { return InternalTextInput( label: label, hintText: hintText, + constrained: true, errorText: field.errorText ?? errorText, size: size, placeholder: state.timeFormat, diff --git a/test/src/components/search_bar/golden/search_bar_default.png b/test/src/components/search_bar/golden/search_bar_default.png index 3926f597..614622db 100644 Binary files a/test/src/components/search_bar/golden/search_bar_default.png and b/test/src/components/search_bar/golden/search_bar_default.png differ diff --git a/test/src/components/search_bar/golden/search_bar_full.png b/test/src/components/search_bar/golden/search_bar_full.png index e3ffbdaf..a0146091 100644 Binary files a/test/src/components/search_bar/golden/search_bar_full.png and b/test/src/components/search_bar/golden/search_bar_full.png differ diff --git a/test/src/components/search_bar/golden/search_bar_medium.png b/test/src/components/search_bar/golden/search_bar_medium.png index 3926f597..614622db 100644 Binary files a/test/src/components/search_bar/golden/search_bar_medium.png and b/test/src/components/search_bar/golden/search_bar_medium.png differ diff --git a/test/src/components/search_bar/golden/search_bar_sharp.png b/test/src/components/search_bar/golden/search_bar_sharp.png index 27a2b4e3..c3c1ab55 100644 Binary files a/test/src/components/search_bar/golden/search_bar_sharp.png and b/test/src/components/search_bar/golden/search_bar_sharp.png differ diff --git a/test/src/components/search_bar/golden/search_bar_small.png b/test/src/components/search_bar/golden/search_bar_small.png index 835f47d2..0f23bf32 100644 Binary files a/test/src/components/search_bar/golden/search_bar_small.png and b/test/src/components/search_bar/golden/search_bar_small.png differ diff --git a/test/src/components/search_bar/search_bar_test.dart b/test/src/components/search_bar/search_bar_test.dart index 9cfaef8b..120b326a 100644 --- a/test/src/components/search_bar/search_bar_test.dart +++ b/test/src/components/search_bar/search_bar_test.dart @@ -47,7 +47,7 @@ void main() { ); await tester.pumpAndSettle(); - expect(find.byType(TextFormField), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); }); testWidgets('golden: renders initializes correctly', (WidgetTester tester) async { @@ -178,7 +178,7 @@ void main() { ); await tester.pumpAndSettle(); - await tester.enterText(find.byType(TextFormField), 'New text'); + await tester.enterText(find.byType(TextField), 'New text'); await tester.pump(); verify(callbacks.onChange.call('New text')).called(1); @@ -194,7 +194,7 @@ void main() { ); await tester.pumpAndSettle(); - await tester.enterText(find.byType(TextFormField), 'Submit text'); + await tester.enterText(find.byType(TextField), 'Submit text'); await tester.testTextInput.receiveAction(TextInputAction.done); await tester.pump(); @@ -231,7 +231,7 @@ void main() { ); await tester.pumpAndSettle(); - await tester.enterText(find.byType(TextFormField), 'Disabled input'); + await tester.enterText(find.byType(TextField), 'Disabled input'); await tester.pump(); expect(find.text('Disabled input'), findsNothing); @@ -263,7 +263,7 @@ void main() { ); await tester.pumpAndSettle(); - await tester.enterText(find.byType(TextFormField), 'New text'); + await tester.enterText(find.byType(TextField), 'New text'); await tester.pump(); verify(callbacks.onChange.call('New text')).called(1); @@ -280,7 +280,7 @@ void main() { expect(diagnostics.finder('size'), 'medium'); expect(diagnostics.finder('shape'), 'rounded'); - expect(diagnostics.finder('hintText'), 'null'); + expect(diagnostics.finder('placeholder'), 'null'); expect(diagnostics.finder('textInputAction'), 'null'); expect(diagnostics.finder('onSpeechToText'), 'null'); expect(diagnostics.finder('showSpeechToText'), 'true');