leo_easy_ui_kit 0.2.0
leo_easy_ui_kit: ^0.2.0 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, tables, and HTML.
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.).
✨ 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,easyHtml,easyHtmlFromUrl,easyTagInputOf,easyFilterBarOf,easyResponsiveScaffoldOf. - Rich data & content widgets – generic data table, selectable lists, grids, popup menus, 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, andEasyBlocgive you predictable, testable state primitives under the hood.
🚀 Getting started #
- Add the dependency:
dependencies:
leo_easy_ui_kit: ^0.1.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,EasyHtml, 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.
- HTML – render inline HTML strings or remote pages with loading and error states.
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,
);
4. HTML – render rich content from strings or URLs #
The HTML tab demonstrates both inline and remote HTML rendering.
// Inline HTML rendering
easyHtml(
'''
<h2 style="color: #6200EE;">Welcome to Leo Easy UI Kit</h2>
<p>This is <strong>rendered HTML</strong> content.</p>
''',
onTapUrl: (url) async {
// Decide how to handle links (launch, track, etc.)
return false; // prevent default behaviour
},
);
// Fetch and render HTML from a URL
SizedBox(
height: 400,
child: easyHtmlFromUrl(
'https://example.com',
loading: const Center(child: CircularProgressIndicator()),
errorBuilder: (context, error) => Text('Failed to load: $error'),
),
);
This is ideal for CMS content, documentation pages, terms & conditions, and any server‑driven HTML experience you want to surface in‑app.
🔌 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.