ux_kit_hooks 0.0.1 copy "ux_kit_hooks: ^0.0.1" to clipboard
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:

0
likes
110
points
22
downloads

Publisher

unverified uploader

Weekly Downloads

Reusable utilities enhancing UX interactions

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_hooks, ux_kit_core

More

Packages that depend on ux_kit_hooks