ux_kit_hooks 0.0.1
ux_kit_hooks: ^0.0.1 copied to clipboard
Reusable utilities enhancing UX interactions
ux_kit_hooks #
ux_kit_hooks
builds on top of ux_kit_core and provides Flutter Hooks support for managing form state, validation, and inputs.
It makes working with forms declarative, reactive, and simple, while keeping behavior separate from styling.
Getting started #
Prerequisites:
- Flutter 3.0 or higher
- Dart 3.8 or higher
Usage #
class SignUpForm {
const SignUpForm({
this.firstName = '',
this.lastName = '',
this.email = '',
this.password = '',
});
final String firstName;
final String lastName;
final String email;
final String password;
factory SignUpForm.formatter(List values) {
return SignUpForm(
firstName: values[0],
lastName: values[1],
email: values[2],
password: values[3],
);
}
static const FormConfig<SignUpForm> config = FormConfig(
list: [
TextConfig(
label: 'First Name',
hint: 'first name',
validation: Validation(
validator: Validator.required(),
message: 'First name is required',
),
),
TextConfig(
label: 'Last Name',
hint: 'last name',
validation: Validation(
validator: Validator.required(),
message: 'Last name is required',
),
),
EmailConfig(
label: 'Email',
hint: 'email',
validation: Validation.queue([
Validation(
validator: Validator.required(),
message: 'Email is required',
),
Validation(
validator: Validator.email,
message: 'Email is invalid',
),
]),
),
PasswordConfig(
label: 'Password',
hint: 'password',
validation: Validation.queue([
Validation(
key: 'password',
validator: Validator.required(),
message: 'Password is required',
),
Validation(
key: 'password',
validator: Validator.minLength(8),
message: 'Password must be at least 8 characters long',
),
]),
),
PasswordConfig(
label: 'Confirm Password',
hint: 'confirm password',
validation: Validation.relatedTo('password',
validatorBuilder: IsSameAsValidator.new,
message: 'Passwords do not match',
),
),
],
formatter: SignUpForm.formatter,
);
}
class SignUpFormWidget extends StatelessWidget {
const SignUpFormWidget({super.key});
@override
Widget build(BuildContext context) {
return MyForm(
config: SignUpForm.config,
onSubmit: (SignUpForm form) {
// TODO: submit form
},
);
}
}
class MyForm<T> extends HookWidget {
const MyForm({
super.key,
required this.config,
required this.onSubmit,
this.value,
});
final FormConfig<T> config;
final void Function(T formattedValue) onSubmit;
final FutureOr<List>? value;
@override
Widget build(BuildContext context) {
final form = useInput.form(
config: config,
value: value,
);
return ListView(
children: [
for (final input in form.list)
if (input case FieldInput())
MyTextField(input: input),
ElevatedButton(
onPressed: () {
if (form.validate()) {
onSubmit(form.formatted);
}
},
child: const Text('Submit'),
),
],
);
}
}
class MyTextField extends HookWidget {
const MyTextField({super.key, required this.input});
final FieldInput input;
@override
Widget build(BuildContext context) {
String? errorText;
final validationResult = useListenable(input.validationResult).value;
if (validationResult case HasMessage(isValid: final isValid, message: final message) when !isValid && message is String) {
errorText = message;
}
bool obscureText = false;
if (input case PasswordInput(obscure: final obscure)) {
obscureText = useListenable(obscure).value;
}
return TextField(
controller: input.textEditing,
focusNode: input.focusNode,
autocorrect: input is! PasswordInput,
obscureText: obscureText,
keyboardType: input.keyboardType,
inputFormatters: input.inputFormatters,
autofillHints: input.autofillHints,
decoration: InputDecoration(
labelText: input.config.label,
hintText: 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,
};
}
}
Dependencies #
This package depends on: