fin_ui 0.1.1
fin_ui: ^0.1.1 copied to clipboard
A reusable Flutter UI foundation with adaptive navigation, theme tokens, split panes, and polished app controls.
FinUI #
FinUI is a reusable Flutter UI foundation for small and medium app clients. It provides theme tokens, app-level controls, adaptive navigation, split-pane layouts, and a compact page transition policy extracted from GXNU Class Tool.
The package is intentionally conservative: it uses Flutter ThemeData, MaterialPageRoute, IndexedStack, and small custom controls instead of large Material navigation components. The goal is to keep app pages fast, stable, and visually consistent across mobile and desktop.
Features #
- Theme system:
FuiTheme.light(),FuiTheme.dark(),FuiAccent, dynamic system accent support, palette extension, typography defaults, scroll behavior, and page transition policy. - Design tokens: spacing, radius, icon sizes, animation durations, page/card/tile insets.
- Controls: buttons, icon buttons, text fields, combo boxes, tags, tiles, sections, surfaces, progress bars, refresh indicators, empty states, bottom tabs, navigation rail, and toast.
- Adaptive navigation:
FuiNavigationShellswitches between bottom tabs, navigation rail, and split layout from screen width. - Detail navigation:
FuiNavigation.openDetail()keeps one API for compact full-page pushes, bottom sheets, dialogs, and wide-screen secondary panes. - Split layout:
FuiAdaptiveSplitScaffoldandFuiSplitNavigationScaffoldcover common master-detail and optional supplementary-pane screens. - Original route feel:
FuiPageRoutepreserves the old Class Tool route semantics by inheriting fromMaterialPageRouteand delegating transitions back tosuper.buildTransitions().
Installation #
Use the package from pub.dev after it is published:
dependencies:
fin_ui: ^0.1.1
For local development before publishing:
dependencies:
fin_ui:
path: ../fin_ui
Import the package entrypoint:
import 'package:fin_ui/fin_ui.dart';
package:fin_ui/fui.dart is kept as a compatibility alias and exports the same public API.
Quick start #
Wrap your app with FinUI themes and scroll behavior:
import 'package:fin_ui/fin_ui.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FinUI Example',
theme: FuiTheme.light(),
darkTheme: FuiTheme.dark(),
scrollBehavior: FuiTheme.scrollBehavior,
home: const ExampleShell(),
);
}
}
Use FuiNavigationShell as the app-level shell:
class ExampleShell extends StatefulWidget {
const ExampleShell({super.key});
@override
State<ExampleShell> createState() => _ExampleShellState();
}
class _ExampleShellState extends State<ExampleShell> {
int selectedIndex = 0;
@override
Widget build(BuildContext context) {
return FuiNavigationShell(
selectedIndex: selectedIndex,
onSelected: (index) => setState(() => selectedIndex = index),
autoExtendRail: true,
splitPlan: const FuiSplitNavigationPlan(
placeholderData: FuiSecondaryPlaceholderData(
icon: FUIIcons.apps,
title: 'Select a page',
subtitle: 'Details will appear here on wide screens.',
),
),
items: const [
FuiNavigationItem(
destination: FuiNavigationDestination(
icon: FUIIcons.home,
selectedIcon: FUIIcons.homeFilled,
label: 'Home',
),
page: HomePage(),
),
FuiNavigationItem(
destination: FuiNavigationDestination(
icon: FUIIcons.calendar,
selectedIcon: FUIIcons.calendarFilled,
label: 'Schedule',
),
page: SchedulePage(),
),
FuiNavigationItem(
destination: FuiNavigationDestination(
icon: FUIIcons.settings,
selectedIcon: FUIIcons.settingsFilled,
label: 'Settings',
),
page: SettingsPage(),
),
],
);
}
}
Theme and tokens #
FuiTheme registers FUIPalette as a ThemeExtension, so controls can read semantic colors from the build context:
final colors = context.colors;
Use FUITokens and FUIInsets instead of hard-coded spacing for app UI:
Padding(
padding: FUIInsets.page,
child: FUISurface(
child: Column(
children: const [
FUITag(label: 'Ready', variant: FUITagVariant.success),
SizedBox(height: FUITokens.gap12),
FUIButton(label: 'Continue'),
],
),
),
);
Dynamic system accent colors are available through FuiDynamicThemeBuilder:
FuiDynamicThemeBuilder(
builder: (context, lightTheme, darkTheme) {
return MaterialApp(
theme: lightTheme,
darkTheme: darkTheme,
scrollBehavior: FuiTheme.scrollBehavior,
home: const ExampleShell(),
);
},
);
Navigation model #
FinUI navigation has two layers.
The app shell layer uses FuiNavigationShell. On compact widths it renders the custom bottom tab bar. On wider layouts it renders the custom rail. On large landscape layouts it can use split panes.
The detail layer uses FuiNavigation.openDetail():
FUITile(
title: 'Open detail',
subtitle: 'Compact screens push a page; split screens use the right pane.',
onTap: () {
FuiNavigation.openDetail(
context,
builder: (context) => const DetailPage(),
);
},
);
Do not replace this with ad-hoc PageRouteBuilder animations. FuiNavigation.openDetail() uses FuiPageRoute for compact page pushes and uses the secondary pane navigator in split mode. This keeps navigation behavior consistent across apps.
Breakpoints #
Default navigation breakpoints are:
| Layout | Default rule |
|---|---|
| Bottom tabs | width < 720 |
| Navigation rail | width >= 720 |
| Split layout | width >= 900, height >= 600, aspect ratio >= 1.2 |
| Extended rail | enabled by autoExtendRail, width >= 1100 |
Override them when an app needs different behavior:
const breakpoints = FuiNavigationBreakpoints(
railWidth: 760,
extendedRailWidth: 1180,
splitWidth: 980,
);
Split-pane pages #
Use FuiAdaptiveSplitScaffold for list-detail pages:
FuiAdaptiveSplitScaffold<String>(
masterTitle: 'Items',
items: const [
FuiSplitItem(value: 'a', title: 'Item A', subtitle: 'Primary detail'),
FuiSplitItem(value: 'b', title: 'Item B', subtitle: 'Secondary detail'),
],
selectedValue: selectedId,
onSelected: (value) => setState(() => selectedId = value),
plan: const FuiSplitNavigationPlan(
placeholderData: FuiSecondaryPlaceholderData(
title: 'Select an item',
subtitle: 'The item detail appears here.',
),
),
detailBuilder: (context, id) => DetailPane(id: id),
supplementaryBuilder: (context, id) => InspectorPane(id: id),
);
When a detail page uses FuiPageHead, FinUI automatically shows a close button for the first right-pane detail and a back button for deeper pages in the local detail stack.
Controls #
Common controls exported by the package:
| Area | API |
|---|---|
| Layout | FUIPage, FUISurface, FUISection, FUITile |
| Input | FUITextField, FUIComboBox, FUISegmentedControl, FUISelectionTile, FUISlider |
| Actions | FUIButton, FUIIconButton, FUIFloatingActionButton |
| Feedback | FUIToast, FUIProgressBar, FUIRefresh, FUIEmptyState, FUITag |
| Navigation | FUIAppBar, FUIButtonGroupTabBar, FUIBottomTabBar, FUINavRail, FuiNavigationShell, FuiNavigation.openDetail() |
| Overlays | FUIDialog, FUIToast |
Toast can be shown with an explicit context:
FUIToast.success(context, message: 'Saved');
Global toast helpers use GetX context lookup and require the host app to provide a GetX overlay context, for example by using GetMaterialApp:
FUIToast.successGlobal(message: 'Saved');
Prefer context-based toast in package examples and reusable widgets.
Route transition policy #
The original Class Tool used normal MaterialPageRoute pushes and controlled the animation through ThemeData.pageTransitionsTheme. FinUI preserves that behavior.
Current policy:
- Android, Windows, Linux, Fuchsia:
PredictiveBackPageTransitionsBuilder. - iOS, macOS:
CupertinoPageTransitionsBuilder. - Compact detail pushes:
FuiPageRoute, which extendsMaterialPageRoute. FuiPageRoute.buildTransitions()forcesFuiTheme.pageTransitionsTheme, then delegates tosuper.buildTransitions().
Do not hand-write route animations unless a component has a separate, documented reason. In particular, do not replace FuiPageRoute with PageRouteBuilder plus guessed SlideTransition/FadeTransition timings.
Repository workflow #
Local checks:
flutter pub get
dart format --set-exit-if-changed lib test example
flutter analyze
flutter test
flutter pub publish --dry-run
The GitHub Actions workflow runs the same checks on pull requests and pushes.
Publishing #
Before publishing:
- Update
versioninpubspec.yaml. - Update
CHANGELOG.md. - Run the local checks above.
- Verify
flutter pub publish --dry-runoutput and file list. - Publish with
flutter pub publish.
Keep public APIs stable after 1.0.0. Before that, prefer 0.x.y releases or prerelease versions such as 0.2.0-dev.1 while the design system is still moving.
License #
FinUI is licensed under the MIT License. See LICENSE.