Skip to content

Commit

Permalink
feat: [DX-1034] Add characters count to the OptimusInputField
Browse files Browse the repository at this point in the history
  • Loading branch information
witwash committed Feb 6, 2024
1 parent e983ca9 commit eb5cf3c
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 7 deletions.
82 changes: 75 additions & 7 deletions optimus/lib/src/common/field_wrapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class FieldWrapper extends StatefulWidget {
super.key,
this.isEnabled = true,
required this.focusNode,
this.controller,
this.isFocused,
this.label,
this.caption,
Expand All @@ -24,10 +25,12 @@ class FieldWrapper extends StatefulWidget {
this.fieldBoxKey,
this.children = const <Widget>[],
this.size = OptimusWidgetSize.large,
this.maxCharacters,
});

final bool isEnabled;
final FocusNode focusNode;
final TextEditingController? controller;
final bool? isFocused;
final String? label;
final Widget? caption;
Expand All @@ -42,6 +45,7 @@ class FieldWrapper extends StatefulWidget {
final List<Widget> children;
final Key? fieldBoxKey;
final OptimusWidgetSize size;
final int? maxCharacters;

bool get hasError {
final error = this.error;
Expand Down Expand Up @@ -105,7 +109,8 @@ class _FieldWrapper extends State<FieldWrapper> with ThemeGetter {
final caption = widget.caption;
final prefix = widget.prefix;
final suffix = widget.suffix;

final controller = widget.controller;
final maxCharacters = widget.maxCharacters;
final captionColor =
widget.isEnabled ? tokens.textStaticSecondary : tokens.textDisabled;

Expand Down Expand Up @@ -187,14 +192,34 @@ class _FieldWrapper extends State<FieldWrapper> with ThemeGetter {
),
),
),
if (widget.isEnabled && helperMessage != null)
if (widget.isEnabled && helperMessage != null || maxCharacters != null)
Padding(
padding: widget.size.getHelperPadding(tokens),
child: OptimusCaption(
child: DefaultTextStyle.merge(
style: TextStyle(color: captionColor),
child: helperMessage,
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.isEnabled && helperMessage != null)
Expanded(
child: OptimusCaption(
child: DefaultTextStyle.merge(
softWrap: true,
maxLines: 2,
overflow: TextOverflow.fade,
style: TextStyle(
color: captionColor,
),
child: helperMessage,
),
),
),
if (helperMessage == null) const Spacer(),
if (controller != null && maxCharacters != null)
_CharacterCounter(
current: controller.text.length,
max: maxCharacters,
),
],
),
),
if (widget.isEnabled &&
Expand Down Expand Up @@ -251,6 +276,49 @@ class _InputCaption extends StatelessWidget {
}
}

class _CharacterCounter extends StatelessWidget {
const _CharacterCounter({
required this.current,
required this.max,
});

final int current;
final int max;

@override
Widget build(BuildContext context) {
final tokens = context.tokens;

final child = Text(
'$current/$max',
style: tokens.bodyMedium.copyWith(
color:
current > max ? tokens.textAlertDanger : tokens.textStaticSecondary,
),
);

return current > max
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(
left: tokens.spacing200,
right: tokens.spacing25,
),
child: const OptimusIcon(
iconData: OptimusIcons.error_circle,
iconSize: OptimusIconSize.small,
colorOption: OptimusIconColorOption.danger,
),
),
child,
],
)
: child;
}
}

class _Styled extends StatelessWidget {
const _Styled({required this.child, this.isEnabled = true});

Expand Down
7 changes: 7 additions & 0 deletions optimus/lib/src/form/input_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class OptimusInputField extends StatefulWidget {
this.helperMessage,
this.maxLines = 1,
this.minLines,
this.maxCharacters,
this.controller,
this.error,
this.errorVariant = OptimusInputErrorVariant.bottomHint,
Expand Down Expand Up @@ -74,6 +75,10 @@ class OptimusInputField extends StatefulWidget {
/// {@macro flutter.widgets.editableText.maxLines}
final int maxLines;

/// The maximum characters for the field. Current character count will be
/// displayed under the field.
final int? maxCharacters;

/// {@macro flutter.widgets.editableText.minLines}
final int? minLines;
final TextEditingController? controller;
Expand Down Expand Up @@ -221,6 +226,7 @@ class _OptimusInputFieldState extends State<OptimusInputField>

return FieldWrapper(
focusNode: _effectiveFocusNode,
controller: _effectiveController,
isFocused: widget.isFocused,
isEnabled: widget.isEnabled,
label: widget.label,
Expand All @@ -231,6 +237,7 @@ class _OptimusInputFieldState extends State<OptimusInputField>
errorVariant: widget.errorVariant,
hasBorders: widget.hasBorders,
isRequired: widget.isRequired,
maxCharacters: widget.maxCharacters,
prefix: _shouldShowPrefix
? Prefix(prefix: widget.prefix, leading: widget.leading)
: null,
Expand Down
2 changes: 2 additions & 0 deletions optimus/lib/src/form/input_form_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class OptimusInputFormField extends FormField<String> {
String? label,
int maxLines = 1,
int? minLines,
int? maxCharacters,
bool enableInteractiveSelection = true,
bool autofocus = false,
bool autocorrect = true,
Expand Down Expand Up @@ -63,6 +64,7 @@ class OptimusInputFormField extends FormField<String> {
label: label,
maxLines: maxLines,
minLines: minLines,
maxCharacters: maxCharacters,
controller: state._effectiveController,
error: field.errorText,
enableInteractiveSelection: enableInteractiveSelection,
Expand Down
10 changes: 10 additions & 0 deletions storybook/lib/stories/input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ final Story inputStory = Story(
initial: OptimusInputErrorVariant.bottomHint,
options: OptimusInputErrorVariant.values.toOptions(),
);
final limitChars = k.boolean(
label: 'Limit characters',
initial: true,
);
int? maxChars;
if (limitChars) {
maxChars =
k.sliderInt(label: 'Max Characters', max: 100, min: 1, initial: 30);
}

return Align(
alignment: Alignment.center,
Expand All @@ -43,6 +52,7 @@ final Story inputStory = Story(
isEnabled: k.boolean(label: 'Enabled', initial: true),
isRequired: k.boolean(label: 'Required'),
isPasswordField: k.boolean(label: 'Password'),
maxCharacters: maxChars,
prefix: prefix.isNotEmpty ? Text(prefix) : null,
suffix: suffix.isNotEmpty ? Text(suffix) : null,
leading: leadingIcon == null ? null : Icon(leadingIcon),
Expand Down

0 comments on commit eb5cf3c

Please sign in to comment.