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.
import 'package:fin_ui/fin_ui.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const FinUiExampleApp());
}
class FinUiExampleApp extends StatelessWidget {
const FinUiExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FinUI Example',
theme: FuiTheme.light(),
darkTheme: FuiTheme.dark(),
scrollBehavior: FuiTheme.scrollBehavior,
home: const ExampleShell(),
);
}
}
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 content',
subtitle: 'Open a detail page to show it 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(),
),
],
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return FUIPage(
children: [
const _PageTitle(
title: 'FinUI',
subtitle: 'Theme, controls, and adaptive navigation.',
),
const SizedBox(height: FUITokens.gap16),
FUISection(
title: 'Actions',
children: [
FUITile(
icon: FUIIcons.apps,
title: 'Open detail',
subtitle: 'Uses FuiNavigation.openDetail and FuiPageRoute.',
onTap: () => FuiNavigation.openDetail(
context,
builder: (context) => const DetailPage(),
),
),
FUITile(
icon: FUIIcons.info,
title: 'Show toast',
subtitle: 'Context-based overlay toast.',
onTap: () => FUIToast.success(context, message: 'Saved'),
),
],
),
const SizedBox(height: FUITokens.gap16),
const FUISurface(
padding: FUIInsets.card,
child: Wrap(
spacing: FUITokens.gap8,
runSpacing: FUITokens.gap8,
children: [
FUITag(label: 'Primary', variant: FUITagVariant.primary),
FUITag(label: 'Success', variant: FUITagVariant.success),
FUITag(label: 'Warning', variant: FUITagVariant.warning),
FUITag(label: 'Danger', variant: FUITagVariant.danger),
],
),
),
],
);
}
}
class SchedulePage extends StatelessWidget {
const SchedulePage({super.key});
@override
Widget build(BuildContext context) {
return const FUIPage(
children: [
_PageTitle(
title: 'Schedule',
subtitle: 'Example page kept alive by IndexedStack.',
),
SizedBox(height: FUITokens.gap16),
FUISection(
title: 'Today',
children: [
FUITile(
icon: FUIIcons.today,
title: 'Morning session',
subtitle: '08:30 - 10:00',
showChevron: false,
),
FUITile(
icon: FUIIcons.schedule,
title: 'Afternoon session',
subtitle: '14:00 - 15:30',
showChevron: false,
),
],
),
],
);
}
}
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
String mode = 'auto';
@override
Widget build(BuildContext context) {
return FUIPage(
children: [
const _PageTitle(title: 'Settings', subtitle: 'Example controls.'),
const SizedBox(height: FUITokens.gap16),
FUISegmentedControl<String>(
value: mode,
items: const [
FUISegmentedItem(
value: 'light',
label: 'Light',
icon: FUIIcons.lightMode,
),
FUISegmentedItem(
value: 'dark',
label: 'Dark',
icon: FUIIcons.darkMode,
),
FUISegmentedItem(
value: 'auto',
label: 'Auto',
icon: FUIIcons.autoBrightness,
),
],
onChanged: (value) => setState(() => mode = value),
),
const SizedBox(height: FUITokens.gap16),
const FUITextField(
label: 'Search',
hintText: 'Type something',
prefixIcon: FUIIcons.search,
),
],
);
}
}
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return FUIPage(
children: [
const FuiPageHead(
title: 'Detail page',
subtitle: 'Uses the FinUI navigation transition policy.',
),
const SizedBox(height: FUITokens.gap16),
FUISection(
title: 'Nested navigation',
children: [
FUITile(
icon: FUIIcons.add,
title: 'Open nested detail',
subtitle: 'Pushes another detail through the same API.',
onTap: () => FuiNavigation.openDetail(
context,
builder: (context) => const NestedDetailPage(),
),
),
],
),
],
);
}
}
class NestedDetailPage extends StatelessWidget {
const NestedDetailPage({super.key});
@override
Widget build(BuildContext context) {
return const FUIPage(
children: [
FuiPageHead(title: 'Nested detail', subtitle: 'Back closes this page.'),
SizedBox(height: FUITokens.gap16),
FUIEmptyState(
icon: FUIIcons.check,
title: 'Done',
message: 'The nested detail route is working.',
),
],
);
}
}
class _PageTitle extends StatelessWidget {
const _PageTitle({required this.title, required this.subtitle});
final String title;
final String subtitle;
@override
Widget build(BuildContext context) {
final colors = context.colors;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: colors.textPrimary,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: FUITokens.gap4),
Text(
subtitle,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colors.textSecondary,
fontWeight: FontWeight.w500,
),
),
],
);
}
}