widgets_box 0.2.0
widgets_box: ^0.2.0 copied to clipboard
A Flutter package for efficient UI state management, handling loading, empty views, content display, caching, and pull-to-refresh with customizable widgets.
widgets_box
One theme-aware component system for every Flutter app you ship.
Screens, cards, fields, buttons, status & app-infrastructure widgets — styled once, consistent everywhere.
Why widgets_box? #
Every app re-implements the same building blocks — a loading/empty/content screen, a status pill, a labelled text field, a themed button — and they drift apart. widgets_box is the single source of those blocks:
- 🎨 Theme-aware — every widget reads your
ThemeDatafirst, so it matches your brand with zero configuration. - ⚙️ Fully configurable — set package-wide defaults once with
WidgetsBoxConfig, override per-instance anywhere. - 🧩 20+ widgets — screens, cards, list rows, status badges, detail rows, section headers, inputs, buttons, images, toasts.
- 🛠️ App infrastructure — a debug-only backend environment switcher, in-process app restart, "Powered by" and app/Shorebird version widgets.
- 🔤
WB-prefixed API — typeWBand autocomplete surfaces the whole library. Old names still work via deprecated aliases. - 🌍 Localized — ships English + Arabic strings out of the box via
smart_localize.
Table of Contents #
- Installation
- Styling precedence
- Screens & state
- Cards & content
- Status & tags
- Inputs
- Buttons
- Images
- App infrastructure
- Functions & extensions
- Migrating to the
WBnames - Contributing
- License
Installation #
dependencies:
widgets_box: ^0.2.0
import 'package:widgets_box/widgets_box.dart';
Optionally wrap your app in a WidgetsBoxConfigProvider (see Styling precedence) to set package-wide defaults. Everything else works straight from your Theme.
Styling precedence #
Every visual value resolves top-down, so an app themes once and overrides only where it needs to. A null at any layer simply falls through to the layer below.
Wrap the app in a WidgetsBoxConfigProvider to set package-wide defaults (card radius/padding, field borders, button sizing, toast colors); anything left unset falls back to your Flutter Theme.
WidgetsBoxConfigProvider(
config: const WidgetsBoxConfig(
cardConfig: WBCardConfig(radius: 16, padding: EdgeInsets.all(16)),
buttonConfig: ButtonConfig(radius: 8, height: 44),
),
child: const MyApp(),
);
Screens & state #
WBScreen wires loading, empty and content states together with pull-to-refresh in one place.
WBScreen(
isLoading: state.isLoading,
isEmpty: state.items.isEmpty,
onRefresh: () => controller.reload(),
emptyWidget: WBEmptyState(
title: 'No orders yet',
subtitle: 'Pull to refresh or try again.',
onRetry: controller.reload, // localized "Retry" (en/ar) by default
),
builder: (context) => OrdersList(items: state.items),
);
const WBLoading(); // themed loading indicator
WBEmptyState(subtitle: 'Nothing here').toSliver(); // drop into a CustomScrollView
Cards & content #
WBCard(
onTap: openInvoice,
child: Column(
children: [
WBSectionHeader(title: 'Invoice #10428', actionLabel: 'View', onAction: openInvoice),
WBDetailRow(label: 'Supplier', value: 'Nile Vet Supplies'),
WBDetailRow.money(label: 'Total', value: 12500, total: true),
],
),
);
// Image-led card
WBCard.media(
image: WBCachedImage(imageUrl: product.image),
child: Text(product.name),
);
Status & tags #
WBStatusBadge resolves its color from an explicit color, a hex string (#2E7D4F), or a semantic name (success, danger, warning, info, primary, gray).
WBStatusBadge(label: 'Active', colorValue: 'success');
WBStatusBadge(label: 'Overdue', colorValue: 'danger', icon: Icons.warning);
WBStatusBadge(label: 'Draft', colorValue: '#2563EB', outlined: true);
WBTag(text: 'New', backgroundColor: Colors.blue, textColor: Colors.white);
Inputs #
WBTextField ships with typed factories that pre-wire keyboard, validation and affordances.
WBTextField(title: 'Username', hintText: 'Enter username', controller: nameCtrl);
WBTextField.email(title: 'Email', controller: emailCtrl);
WBTextField.password(title: 'Password', controller: passCtrl); // built-in show/hide
WBTextField.number(title: 'Quantity', controller: qtyCtrl); // decimal keyboard
WBTextField.phone(title: 'Phone', initialCountryCode: '+20'); // country-code picker
Buttons #
WBButton(label: 'Save changes', onPressed: save); // primary
WBButton(label: 'Cancel', type: WBButtonType.secondary, onPressed: pop);
WBButton(label: 'Skip', type: WBButtonType.tertiary, onPressed: skip);
WBButton.icon(
label: 'Continue',
icon: Icons.arrow_forward,
iconType: IconType.icon,
isIconEnd: true,
onPressed: next,
);
Colors follow Theme.primaryColor with a white foreground by default; pass backgroundColor/labelColor to override per-instance.
Images #
WBCachedImage(imageUrl: user.avatar, height: 120, fallback: const Icon(Icons.person));
WBUserImage(photo: user.avatar, displayName: user.name); // avatar, or initials fallback
WBCachedImage handles network / SVG / asset / file sources, fades in, and degrades to a fallback on error.
App infrastructure #
Debug environment switcher — repoint the API base URL at runtime without a rebuild. Inert in release builds, so a shipped app always boots into production.
// main.dart — before your DI/Dio is constructed
const production = WBAppEnvironment(name: 'prod', label: 'Production', baseUrl: prodUrl);
WBEnvironmentManager.init(
WBEnvironmentConfig(
environments: const [
production,
WBAppEnvironment(name: 'staging', label: 'Staging', baseUrl: stagingUrl),
],
defaultEnvironment: production,
persist: (env) => prefs.setString('env', env.name),
read: () => prefs.getString('env'),
onReinitialize: resetDependencies,
),
);
WBEnvironmentManager.loadPersisted();
runApp(const WBAppRestarter(child: MyApp())); // enables in-process restart
final baseUrl = WBEnvironmentManager.baseUrl; // always production in release builds
WBEnvironmentSwitcher.show(context); // debug-only bottom sheet to pick an environment
Branding & version — drop-in footer widgets:
const WBPoweredBy(); // "Powered by …" mark
const WBAppVersion(); // app version + Shorebird patch number
Functions & extensions #
showToastSuccess(msg: 'Saved');
showToastError(msg: 'Something went wrong');
HexColor('#2563EB'); // safe hex → Color (falls back to opaque white)
context.isArabic; context.isDark; // BuildContext helpers
context.bodySmall; context.titleLarge;
Migrating to the WB names #
The public widgets were rebranded with a consistent WB prefix. The previous names keep working through @Deprecated typedefs, so you can migrate incrementally:
| Old name | New name | Old name | New name |
|---|---|---|---|
MainButton |
WBButton |
SmartScreen |
WBScreen |
MainTextField |
WBTextField |
SmartEmptyWidget |
WBEmptyState |
SmartLoadingWidget |
WBLoading |
SmartCachedImages |
WBCachedImage |
SmartUserImage |
WBUserImage |
SmartTagWidget |
WBTag |
StatusWidget |
WBStatus |
SmartStatusWidget |
WBPositionedStatus |
Contributing #
Feel free to contribute. Found a bug or want a feature? Open an issue. Fixed something? Send a pull request.
License #
Released under the MIT License.
