11import 'package:flutter/material.dart' ;
22import 'package:flutter/services.dart' ;
33import 'package:flutter_screenutil/flutter_screenutil.dart' ;
4+ import 'package:flutter_svg/svg.dart' ;
5+ import 'package:whitenoise/ui/core/themes/assets.dart' ;
46import 'package:whitenoise/ui/core/themes/src/extensions.dart' ;
57import 'package:whitenoise/ui/core/ui/wn_validation_notification.dart' ;
68
@@ -9,6 +11,11 @@ enum FieldType {
911 password,
1012}
1113
14+ enum FieldSize {
15+ regular, // 56.h
16+ small, // 44.h
17+ }
18+
1219class WnTextFormField extends StatefulWidget {
1320 const WnTextFormField ({
1421 super .key,
@@ -42,6 +49,7 @@ class WnTextFormField extends StatefulWidget {
4249 this .textCapitalization = TextCapitalization .none,
4350 this .autovalidateMode = AutovalidateMode .onUserInteraction,
4451 this .inputFormatters,
52+ this .size = FieldSize .regular,
4553 });
4654
4755 final Key ? formKey;
@@ -75,6 +83,7 @@ class WnTextFormField extends StatefulWidget {
7583 final TextEditingController ? controller;
7684 final FormFieldValidator <String ?>? validator;
7785 final InputDecoration ? decoration;
86+ final FieldSize size;
7887
7988 @override
8089 State <WnTextFormField > createState () => _WnTextFormFieldState ();
@@ -100,21 +109,14 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
100109 });
101110 }
102111
103- Widget ? get suffixIcon {
104- final resolvedIcon = ValueListenableBuilder (
105- valueListenable: hasError,
106- builder: (context, hasError, _) {
107- final errorIcon = ! hasError ? const SizedBox () : const Icon (Icons .error);
108-
109- return switch (widget.type) {
110- FieldType .password => errorIcon,
111- FieldType .standard || null => errorIcon,
112- };
113- },
114- );
115-
116- return widget.decoration? .suffixIcon ?? resolvedIcon;
117- }
112+ Widget get suffixIcon => ValueListenableBuilder <bool >(
113+ valueListenable: hasError,
114+ builder:
115+ (_, hasError, _) =>
116+ hasError
117+ ? SvgPicture .asset (AssetsPaths .icErrorFilled)
118+ : (widget.decoration? .suffixIcon ?? const SizedBox .shrink ()),
119+ );
118120
119121 String ? validator (dynamic value) {
120122 final result = widget.validator? .call (value);
@@ -135,7 +137,13 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
135137 fontWeight: FontWeight .w600,
136138 );
137139
140+ final isSmall = widget.size == FieldSize .small;
141+ final targetHeight = isSmall ? 44. h : 56. h;
142+ final bool isMultiline =
143+ ((widget.maxLines ?? 1 ) > 1 ) || ((widget.minLines ?? 1 ) > 1 ) || widget.expands;
144+
138145 final decoration = (widget.decoration ?? const InputDecoration ()).copyWith (
146+ constraints: isMultiline ? null : BoxConstraints .tightFor (height: targetHeight),
139147 suffixIcon: suffixIcon,
140148 labelText: widget.labelText,
141149 hintText: widget.hintText,
@@ -146,10 +154,9 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
146154 ),
147155 suffixIconColor: context.colors.primary,
148156 fillColor: context.colors.avatarSurface,
149- contentPadding: EdgeInsets .symmetric (
150- horizontal: 12. w,
151- vertical: 16. h,
152- ),
157+ contentPadding: EdgeInsets .symmetric (horizontal: 12. w, vertical: isSmall ? 13.5 .h : 19.5 .h),
158+ prefixIconConstraints: BoxConstraints .tightFor (height: targetHeight),
159+ suffixIconConstraints: BoxConstraints .tightFor (height: targetHeight),
153160 border: OutlineInputBorder (
154161 borderRadius: BorderRadius .zero,
155162 borderSide: BorderSide (
@@ -165,7 +172,7 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
165172 filled: true ,
166173 );
167174
168- return TextFormField (
175+ final field = TextFormField (
169176 key: widget.formKey,
170177 validator: validator,
171178 enabled: widget.enabled,
@@ -195,5 +202,19 @@ class _WnTextFormFieldState extends State<WnTextFormField> {
195202 keyboardType: widget.keyboardType,
196203 inputFormatters: widget.inputFormatters,
197204 );
205+
206+ // If maxLines or minLines is specified, return the field as is
207+ // Without using ConstainedBox to enforce the target height.
208+ // Same rule applied in InputDecoration above.
209+ if (isMultiline) {
210+ return field;
211+ }
212+ // Also enforce the target height at the parent layout level so surrounding
213+ // widgets measure consistently. The decoration.constraints above ensures
214+ // the border matches this height (no extra whitespace around it).
215+ return ConstrainedBox (
216+ constraints: BoxConstraints .tightFor (height: targetHeight),
217+ child: field,
218+ );
198219 }
199220}
0 commit comments