leo_easy_ui_kit 0.5.1 copy "leo_easy_ui_kit: ^0.5.1" to clipboard
leo_easy_ui_kit: ^0.5.1 copied to clipboard

Leo Easy UI Kit: effortless yet powerful Flutter UI components.

leo_easy_ui_kit #

Effortless APIs. Production‑ready Flutter UI primitives for forms, selectors, and tables.

leo_easy_ui_kit is a focused UI toolkit that gives you high‑level, type‑safe widgets for most input and selection patterns you build in Flutter apps:

  • one‑line easy*Of<T> helpers for the common cases, and
  • fully customizable widgets when you need to go deeper.

Every widget is generic (<T>), null‑safe, and designed to integrate cleanly with your existing state management (ValueNotifier, BLoC, Provider, etc.).

Supported platforms: Android, iOS, Web, macOS, Windows, and Linux (no platform channels – built on standard Flutter).


⚡ Quick start in 60 seconds #

import 'package:flutter/material.dart';
import 'package:leo_easy_ui_kit/leo_easy_ui_kit.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Leo Easy UI Kit demo',
      theme: ThemeData(useMaterial3: true),
      home: const Scaffold(
        body: Center(child: QuickStartDemo()),
      ),
    );
  }
}

class QuickStartDemo extends StatefulWidget {
  const QuickStartDemo({super.key});

  @override
  State<QuickStartDemo> createState() => _QuickStartDemoState();
}

class _QuickStartDemoState extends State<QuickStartDemo> {
  String? role = 'Developer';
  List<String> skills = const ['Flutter'];

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          easyDropdownOf<String>(
            items: const ['Developer', 'Designer', 'Manager'],
            value: role,
            onChanged: (value) => setState(() => role = value),
            label: (v) => v,
          ),
          const SizedBox(height: 16),
          easyChipsOf<String>(
            items: const ['Flutter', 'Dart', 'Firebase', 'UI/UX'],
            values: skills,
            onChangedMany: (values) => setState(() => skills = values),
            multiSelect: true,
          ),
          const SizedBox(height: 16),
          Text('Role: ${role ?? 'none'} | Skills: ${skills.join(', ')}'),
        ],
      ),
    );
  }
}
  • Add the dependency, paste this into main.dart, and run flutter run.
  • You now have a dropdown + chip selector wired up with just a few lines of glue code.

⚡ Quick start in 60 seconds #

import 'package:flutter/material.dart';
import 'package:leo_easy_ui_kit/leo_easy_ui_kit.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Leo Easy UI Kit demo',
      theme: ThemeData(useMaterial3: true),
      home: const Scaffold(
        body: Center(child: QuickStartDemo()),
      ),
    );
  }
}

class QuickStartDemo extends StatefulWidget {
  const QuickStartDemo({super.key});

  @override
  State<QuickStartDemo> createState() => _QuickStartDemoState();
}

class _QuickStartDemoState extends State<QuickStartDemo> {
  String? role = 'Developer';
  List<String> skills = const ['Flutter'];

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          easyDropdownOf<String>(
            items: const ['Developer', 'Designer', 'Manager'],
            value: role,
            onChanged: (value) => setState(() => role = value),
            label: (v) => v,
          ),
          const SizedBox(height: 16),
          easyChipsOf<String>(
            items: const ['Flutter', 'Dart', 'Firebase', 'UI/UX'],
            values: skills,
            onChangedMany: (values) => setState(() => skills = values),
            multiSelect: true,
          ),
          const SizedBox(height: 16),
          Text('Role: \\${role ?? 'none'} | Skills: \\${skills.join(', ')}'),
        ],
      ),
    );
  }
}
  • Add the dependency, paste this into main.dart, and run flutter run.
  • You now have a dropdown + chip selector wired up with just a few lines of glue code.

✨ Highlights #

  • Unified selection model – dropdowns, checkboxes, radios, switches, chips, segmented controls, lists, grids, menus, and data tables all share the same mental model: items + value/values + onChanged/onChangedMany.
  • Multi‑select built‑in – enable multi‑select with a single multiSelect: true flag on widgets that support it.
  • Powerful shortcutseasyDropdownOf, easyCheckboxOf, easyRadioOf, easySwitchOf, easyButtonGroupOf, easyChipsOf, easySegmentedOf, easyPopupSelectOf, easyBottomSheetSelectOf, easyTextFieldOf, easyIntFieldOf, easyDoubleFieldOf, easyTableOf, easyListOf, easySearchableListOf, easyGridOf, easySearchableGridOf, easyMenuOf, easySearchOf, easyTagInputOf, easyFilterBarOf, easyResponsiveScaffoldOf, easyStepperOf, easyPaginationOf, easyTextFormFieldOf.},{
  • Rich data & content widgets – generic data table, selectable lists, grids, popup menus, searchable lists/grids, and HTML rendering (from strings or URLs).
  • File & image pickers – well‑designed primitives that delegate to your own file_picker / image_picker integration via callbacks.
  • Composable base layerEasySelectionController, EasyValueNotifier, EasyFormController, and EasyBloc give you predictable, testable state primitives under the hood.

🧱 Widget catalogue at a glance #

Instead of a single huge widget, leo_easy_ui_kit gives you a small number of consistent families:

  • Inputs & formseasyTextFieldOf, easyIntFieldOf, easyDoubleFieldOf, easyDateFieldOf, easyTextFormFieldOf, EasyFormController.

  • SelectorseasyDropdownOf, easyCheckboxOf, easyRadioOf, easySwitchOf, easyButtonGroupOf, easyChipsOf, easySegmentedOf, easyTagInputOf.

  • Modal selectorseasyPopupSelectOf (dialog), easyBottomSheetSelectOf (bottom sheet).

  • SearcheasySearchOf, easySearchableListOf, easySearchableGridOf.

  • Lists, grids & menuseasyListOf, easyGridOf, easyMenuOf, easyTableOf.

  • Layout & chromeeasyFilterBarOf, easyResponsiveScaffoldOf, easyStepperOf, easyPaginationOf.

  • Files & imagesEasyFilePicker, EasyFilePickerAdvanced, EasyImagePicker.

Every helper follows the same pattern:

// All helpers share this mental model
Widget easySomethingOf<T>({
  required List<T> items,          // or a single value
  T? value,
  List<T>? values,
  ValueChanged<T?>? onChanged,
  ValueChanged<List<T>>? onChangedMany,
  bool multiSelect = false,
  String Function(T)? label,
});

You can see all of them in action in the demo app at example/lib/main.dart.


🚀 Getting started #

  1. Add the dependency:
dependencies:
  leo_easy_ui_kit: ^0.2.0
  1. Import the library:
import 'package:leo_easy_ui_kit/leo_easy_ui_kit.dart';
  1. Start with the easy*Of helpers for simple cases. Drop down to the underlying widgets (EasyDropdown, EasyCheckbox, EasyTable, etc.) when you need more control.

🧭 Example: full demo app #

This package ships with a full example app at example/lib/main.dart. It demonstrates four real‑world areas:

  • Forms – profile form with dropdowns, button groups, checkboxes, radio buttons, switches, numeric inputs, date picker, file picker, image picker, onboarding stepper, and tag input.
  • Selectors – chips, segmented controls, bottom‑sheet and dialog selectors, autocomplete search, and responsive layout shell.
  • Data & menus – selectable data table, filter bar, searchable lists, grids, and popup menu actions.

To run it locally:

flutter run example/lib/main.dart

Below is a tour of the key pieces you will see in the example.


1. Forms – build a profile form in minutes #

1.1 Core inputs with easy*Of #

The Forms tab uses the one‑line helpers to wire up a complete profile form:

easyDropdownOf<String>(
  items: const ['Developer', 'Designer', 'Manager', 'Student'],
  value: role,
  onChanged: (value) => setState(() => role = value),
  label: (v) => v,
);

easyButtonGroupOf<String>(
  items: const ['Beginner', 'Intermediate', 'Advanced', 'Expert'],
  value: experienceLevel,
  onChanged: (value) => setState(() => experienceLevel = value),
);

easyCheckboxOf<String>(
  items: const ['Flutter', 'UI/UX', 'Backend', 'DevOps', 'Mobile', 'Web'],
  values: selectedInterests,
  onChangedMany: (values) => setState(() => selectedInterests = values),
  multiSelect: true,
);

easyRadioOf<String>(
  items: const ['Employed', 'Freelance', 'Student', 'Seeking'],
  value: employmentStatus,
  onChanged: (value) => setState(() => employmentStatus = value),
  label: (v) => v,
);

easySwitchOf<String>(
  items: const ['Enable Notifications'],
  value: receiveNotifications ? 'Enable Notifications' : null,
  onChanged: (value) =>
      setState(() => receiveNotifications = value != null),
  label: (v) => v,
);

// Type‑safe text fields

easyIntFieldOf(
  value: yearsExperience,
  onChanged: (value) => setState(() => yearsExperience = value),
  hintText: 'Years of experience',
);

easyDateFieldOf(
  value: startDate,
  onChanged: (value) => setState(() => startDate = value),
  label: (date) => '${date.year}-${date.month}-${date.day}',
  firstDate: DateTime(2000),
  lastDate: DateTime(2030),
);

What this gives you

  • Minimal boilerplate: no DropdownButton, FormField, or DataTable plumbing.
  • Strong typing: all helpers are generic (easyDropdownOf<String>, easyIntFieldOf, etc.).
  • Multi‑select behavior that is consistent across components.

1.2 File & image pickers #

The same form also demonstrates integrating file and image pickers. leo_easy_ui_kit does not force a particular plugin; instead, you provide the async callback that calls file_picker, image_picker, or any custom API:

EasyFilePicker<String>(
  label: 'Attach files',
  allowMultiple: true,
  values: uploadedFiles,
  onChangedMany: (files) => setState(() => uploadedFiles = files),
  onPick: (category, allowMultiple) async {
    // Plug in your own implementation here (file_picker, etc.).
    // The example app returns demo values to focus on UI wiring.
    return [
      '${category.label} sample ${uploadedFiles.length + 1}.txt',
    ];
  },
);

EasyImagePicker<String>(
  allowMultiple: true,
  values: profileImages,
  onChangedMany: (images) => setState(() => profileImages = images),
  onPick: (source, allowMultiple) async {
    // Plug in image_picker or any camera/gallery implementation.
    return [
      '${source.label} image ${profileImages.length + 1}',
    ];
  },
);

At the bottom of the tab, a summary card (_buildSummaryCard) aggregates the current selections into a compact profile overview so you can see the state flowing through all widgets.

1.3 Tag input #

The Forms/Selectors experience also showcases a tag input for free‑form values:

easyTagInputOf(
  tags: technologyTags,
  onChanged: (tags) => setState(() => technologyTags = tags),
);

Under the hood this uses EasyTagInput<T>, which exposes a parser and label builder so you can map arbitrary strings to strongly‑typed values (IDs, enums, etc.).


The Selectors tab highlights richer selection patterns.

2.1 Chips & segmented controls #

easyChipsOf<String>(
  items: const ['TypeScript', 'Dart', 'Python', 'Rust', 'Go', 'Swift'],
  values: selectedSkills,
  onChangedMany: (values) => setState(() => selectedSkills = values),
  multiSelect: true,
);

// Material 3 segmented control
easySegmentedOf<String>(
  items: const ['Day', 'Week', 'Month', 'Year'],
  value: 'Week',
  onChanged: (value) {
    // update state here
  },
  label: (v) => v,
);

2.2 Bottom sheet & dialog selectors #

// Bottom sheet selector for large lists
easyBottomSheetSelectOf<String>(
  items: const ['United States', 'Canada', 'United Kingdom', 'Germany', 'France'],
  value: country,
  onChanged: (value) => setState(() => country = value),
  label: (v) => v,
  sheetTitle: const Text('Select Country'),
);

// Dialog‑based selector
easyPopupSelectOf<String>(
  items: const ['Light Theme', 'Dark Theme', 'System Default', 'High Contrast'],
  value: 'System Default',
  onChanged: (value) {
    // apply theme
  },
  label: (v) => v,
  title: const Text('Select Theme'),
);

2.3 Search with autocomplete & remote results #

easySearchOf<String>(
  items: const [
    'Alice Johnson',
    'Bob Smith',
    'Charlie Brown',
    'Diana Prince',
    'Eve Davis',
  ],
  label: (v) => v,
  onSelected: (value) {
    // handle selection
  },
  hintText: 'Search for a person...',
  // Optional: integrate remote search
  // remoteSearch: (query) async => fetchUsersFromServer(query),
);

This gives you a ready‑made search bar with suggestion list, multi‑select support, and an escape‑hatch for server‑side filtering.


3. Data & menus – tables, lists, grids, and menus #

The Data tab shows how to present and select structured data.

3.1 Tables, lists and menus #

// Selectable data table
easyTableOf<Project>(
  items: projects,
  columns: const [
    DataColumn(label: Text('Project')),
    DataColumn(label: Text('Status')),
    DataColumn(label: Text('Progress')),
  ],
  cellBuilder: (context, project, colIndex, selected) {
    switch (colIndex) {
      case 0:
        return Text(project.name);
      case 1:
        return _StatusBadge(project.status);
      case 2:
        return Text('${project.progress}%');
      default:
        return const SizedBox.shrink();
    }
  },
  selectedValues: selectedProjects,
  onChangedMany: (values) => setState(() => selectedProjects = values),
  multiSelect: true,
);

// Simple one‑dimensional selectable list
easyListOf<String>(
  items: const ['Inbox', 'Starred', 'Sent', 'Drafts', 'Trash'],
  value: 'Inbox',
  onChanged: (value) {
    // navigate to mailbox
  },
  label: (v) => v,
  divided: true,
);

// Popup menu for actions
easyMenuOf<String>(
  items: const ['Profile', 'Settings', 'Help', 'Logout'],
  value: null,
  onChanged: (value) {
    // handle menu action
  },
  label: (v) => v,
  tooltip: 'Open menu',
);

3.2 Filter bar, searchable list & searchable grid #

// Filter bar wrapping status filters
easyFilterBarOf(
  leading: const Icon(Icons.filter_list),
  filters: [
    easyButtonGroupOf<String>(
      items: const ['All', 'Completed', 'In Progress', 'Planned'],
      value: projectStatusFilter,
      onChanged: (value) => setState(() {
        projectStatusFilter = value ?? 'All';
        tablePageIndex = 0;
      }),
    ),
  ],
);

// Searchable list
easySearchableListOf<String>(
  items: const ['Inbox', 'Starred', 'Sent', 'Drafts', 'Trash', 'Spam'],
  label: (v) => v,
  hintText: 'Search mailboxes...',
);

// Grid view
easyGridOf<Project>(
  items: _sampleProjects,
  values: selectedProjects,
  onChangedMany: (values) => setState(() => selectedProjects = values),
  multiSelect: true,
  label: (p) => p.name,
  crossAxisCount: 2,
  childAspectRatio: 4 / 3,
);

// Searchable grid with custom tiles
easySearchableGridOf<Project>(
  items: _sampleProjects,
  label: (p) => p.name,
  itemBuilder: (context, project, selected) => Card(
    elevation: selected ? 4 : 1,
    child: Padding(
      padding: const EdgeInsets.all(12),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(project.name),
          _StatusBadge(project.status),
          Text('${project.progress}% complete'),
        ],
      ),
    ),
  ),
  values: selectedProjects,
  onChangedMany: (values) => setState(() => selectedProjects = values),
  multiSelect: true,
  hintText: 'Search projects...',
  crossAxisCount: 2,
  childAspectRatio: 4 / 3,
);

🔍 Why this package vs rolling your own / other UI kits? #

This project is early (0.x) and still building adoption, but it is opinionated in a useful way:

  • Consistency across all inputs – once you learn items + value/values + onChanged/onChangedMany, you can use 90% of the API surface.
  • Type‑safety by default – all components are generic (<T>), so you can work with enums, model objects, IDs, etc. instead of just strings.
  • Architecture‑agnostic – even though it ships helpers for flutter_bloc, provider, and flutter_hooks, the core widgets do not force any state management. You can use simple setState, Riverpod, MobX, or anything else.
  • Good defaults, easy escape hatches – for each family you get both a one‑liner (easyDropdownOf) and an advanced widget (EasyDropdown, EasyDropdownNotifier, EasyDropdownBloc, etc.).
  • Cross‑platform with no plugins – everything is built on standard Flutter, so it works on Android, iOS, Web, macOS, Windows, and Linux without additional setup.

If you already have a design system, you can treat these widgets as behavioral primitives and wrap them with your own theming.


⚖️ Current status & trade‑offs #

  • Low usage / adoption – the package is new and not yet widely adopted. To compensate, the repo includes:
    • A full, multi‑tab demo app (example/lib/main.dart) that uses almost all helpers.
    • Strong static analysis (flutter_lints) with zero analyzer issues.
    • Extensive in‑code documentation and this README.
  • Early 0.x version (0.2.0) – the API is still evolving. Breaking changes will be grouped into minor releases (0.3.x, 0.4.x) and called out clearly in the changelog.
  • Limited documentation – this README now contains:
    • A widget catalogue (see above).
    • End‑to‑end demos for forms, selectors, data views, and HTML.
    • Examples for complex flows like pagination, filter bars, and responsive layouts.
  • Dependencies bloat (flutter_bloc, provider, flutter_hooks) – these are used for optional integration helpers only:
    • You can use the core widgets with just flutter + leo_easy_ui_kit; you are not required to adopt BLoC or Provider.
    • If you already use them, you get drop‑in ...Bloc, ...Notifier, ...Provider, and ...Hook variants for free.

If you prefer a smaller dependency surface, you can still consume only the base widgets and shortcuts and ignore the integration layers.


🔌 State management & architecture #

Under the hood, most widgets are powered by a small set of primitives:

  • EasySelectionController<T> – centralizes single/multi‑select state.
  • EasyValueNotifier<T> – a ValueNotifier<T?> that stays in sync with an EasySelectionController.
  • EasyBloc<T> – a ready‑made bloc with EasyEvent/EasyState for when you prefer flutter_bloc.

You can:

  • Use the value / values + onChanged / onChangedMany pattern directly, or
  • Wrap widgets with your own BLoC/Provider/Riverpod and feed them via these base types.

The public surface is intentionally small and predictable; the example app keeps everything in a single StatefulWidget for clarity, but the same widgets work equally well in layered architectures.


🛠 Contributing #

Contributions are welcome:

  1. Fork & clone the repository.
  2. Run flutter pub get.
  3. Run flutter analyze and add or adjust tests.
  4. Open a pull request describing your changes.

Bug reports and feature requests are also appreciated via the issue tracker.

8
likes
160
points
0
downloads

Publisher

unverified uploader

Weekly Downloads

Leo Easy UI Kit: effortless yet powerful Flutter UI components.

Repository (GitHub)
View/report issues

Topics

#flutter #ui-kit #widget #form

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

equatable, flutter, flutter_bloc, flutter_hooks, provider

More

Packages that depend on leo_easy_ui_kit