leo_easy_ui_kit 0.5.1
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 runflutter 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 runflutter 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: trueflag on widgets that support it. - Powerful shortcuts –
easyDropdownOf,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_pickerintegration via callbacks. - Composable base layer –
EasySelectionController,EasyValueNotifier,EasyFormController, andEasyBlocgive 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 & forms –
easyTextFieldOf,easyIntFieldOf,easyDoubleFieldOf,easyDateFieldOf,easyTextFormFieldOf,EasyFormController. -
Selectors –
easyDropdownOf,easyCheckboxOf,easyRadioOf,easySwitchOf,easyButtonGroupOf,easyChipsOf,easySegmentedOf,easyTagInputOf. -
Modal selectors –
easyPopupSelectOf(dialog),easyBottomSheetSelectOf(bottom sheet). -
Search –
easySearchOf,easySearchableListOf,easySearchableGridOf. -
Lists, grids & menus –
easyListOf,easyGridOf,easyMenuOf,easyTableOf. -
Layout & chrome –
easyFilterBarOf,easyResponsiveScaffoldOf,easyStepperOf,easyPaginationOf. -
Files & images –
EasyFilePicker,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 #
- Add the dependency:
dependencies:
leo_easy_ui_kit: ^0.2.0
- Import the library:
import 'package:leo_easy_ui_kit/leo_easy_ui_kit.dart';
- Start with the
easy*Ofhelpers 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, orDataTableplumbing. - 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.).
2. Advanced selectors – chips, bottom sheets, dialogs & search #
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, andflutter_hooks, the core widgets do not force any state management. You can use simplesetState, 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.
- A full, multi‑tab demo app (
- 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...Hookvariants for free.
- You can use the core widgets with just
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>– aValueNotifier<T?>that stays in sync with anEasySelectionController.EasyBloc<T>– a ready‑madeblocwithEasyEvent/EasyStatefor when you preferflutter_bloc.
You can:
- Use the
value/values+onChanged/onChangedManypattern 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:
- Fork & clone the repository.
- Run
flutter pub get. - Run
flutter analyzeand add or adjust tests. - Open a pull request describing your changes.
Bug reports and feature requests are also appreciated via the issue tracker.