ux_kit_core 0.0.1
ux_kit_core: ^0.0.1 copied to clipboard
Essential components powering UX Kit
ux_kit_core lets you manage UI behavior configuration in one place, separate from styling and decoration. For now, it focuses on inputs and forms, using predefined and flexible blocks of configs and controllers to help you handle input lifecycles, validation, and sync and async updates.
Getting started #
Prerequisites:
- Flutter 3.0 or higher
- Dart 3.8 or higher
Usage #
class MyForm extends StatefulWidget {
const MyForm({super.key});
@override
State<MyForm> createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
late EmailInput email;
late PasswordInput password;
@override
void initState() {
super.initState();
email = EmailInput.init(EmailConfig());
email.addDefaultListeners();
password = PasswordInput.init(PasswordConfig());
password.addDefaultListeners();
}
@override
void dispose() {
email.dispose();
password.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
MyTextField(input: email),
MyTextField(input: password),
ElevatedButton(
onPressed: () {
if (email.validate() && password.validate()) {
print('Email: ${email.formatted}');
print('Password: ${password.formatted}');
}
},
child: const Text('Submit'),
),
],
);
}
}
class MyTextField extends StatefulWidget {
const MyTextField({super.key, required this.input});
final FieldInput input;
@override
State<MyTextField> createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
bool obscureText = false;
String? errorText;
@override
void initState() {
super.initState();
widget.input.validationResult.addListener(errorTextListener);
if (widget.input case PasswordInput password) {
password.obscure.addListener(obscurePasswordListener);
}
}
@override
void dispose() {
widget.input.validationResult.removeListener(errorTextListener);
if (widget.input case PasswordInput password) {
password.obscure.removeListener(obscurePasswordListener);
}
super.dispose();
}
void obscurePasswordListener() {
setState(() {
obscureText = (widget.input as PasswordInput).obscure.value;
});
}
void errorTextListener() {
setState(() {
final validationResult = widget.input.validationResult.value;
if (validationResult case HasMessage(isValid: final isValid, message: final message) when !isValid && message is String) {
errorText = message;
}
});
}
@override
Widget build(BuildContext context) {
return TextField(
controller: widget.input.textEditing,
focusNode: widget.input.focusNode,
autocorrect: widget.input is! PasswordInput,
obscureText: obscureText,
keyboardType: widget.input.keyboardType,
inputFormatters: widget.input.inputFormatters,
autofillHints: widget.input.autofillHints,
decoration: InputDecoration(
labelText: widget.input.config.label,
hintText: widget.input.config.hint,
errorText: errorText,
),
);
}
}
extension on FieldInput {
TextInputType get keyboardType {
return switch (this) {
PasswordInput() => TextInputType.visiblePassword,
PhoneInput() => TextInputType.phone,
TextInput() => TextInputType.text,
NumberInput() => TextInputType.number,
EmailInput() => TextInputType.emailAddress,
DateInput() => TextInputType.datetime,
AddressInput() => TextInputType.streetAddress,
};
}
List<TextInputFormatter>? get inputFormatters {
return switch (this) {
PhoneInput() ||
NumberInput() => [FilteringTextInputFormatter.digitsOnly],
_ => null,
};
}
List<String>? get autofillHints {
return switch (this) {
PasswordInput() => [AutofillHints.password],
PhoneInput() => [
AutofillHints.telephoneNumberNational,
AutofillHints.telephoneNumber,
],
EmailInput() => [AutofillHints.email],
DateInput() => [AutofillHints.birthday],
AddressInput() => [AutofillHints.fullStreetAddress],
_ => null,
};
}
}