From d0bfcc5a71757dc7b4c3184586c19e2c0d3f99e4 Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Fri, 1 Mar 2024 15:30:23 +0000 Subject: [PATCH 1/5] created navigation bar --- example/lib/home.dart | 2 + .../components/navigation_bar_example.dart | 70 +++++++ example/lib/widgets.dart | 3 + example/windows/runner/flutter_window.cpp | 5 + .../navigation bar/navigation_bar.dart | 182 ++++++++++++++++++ lib/zeta_flutter.dart | 1 + 6 files changed, 263 insertions(+) create mode 100644 example/lib/pages/components/navigation_bar_example.dart create mode 100644 lib/src/components/navigation bar/navigation_bar.dart diff --git a/example/lib/home.dart b/example/lib/home.dart index d79a1916..3fb83363 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -9,6 +9,7 @@ import 'package:zeta_example/pages/components/button_example.dart'; import 'package:zeta_example/pages/components/checkbox_example.dart'; import 'package:zeta_example/pages/components/chip_example.dart'; import 'package:zeta_example/pages/components/dialpad_example.dart'; +import 'package:zeta_example/pages/components/navigation_bar_example.dart'; import 'package:zeta_example/pages/theme/color_example.dart'; import 'package:zeta_example/pages/components/password_input_example.dart'; import 'package:zeta_example/pages/components/progress_example.dart'; @@ -33,6 +34,7 @@ final List components = [ Component(ButtonExample.name, (context) => const ButtonExample()), Component(CheckBoxExample.name, (context) => const CheckBoxExample()), Component(ChipExample.name, (context) => const ChipExample()), + Component(NavigationBarExample.name, (context) => const NavigationBarExample()), Component(PasswordInputExample.name, (context) => const PasswordInputExample()), Component(ProgressExample.name, (context) => const ProgressExample()), Component(DialPadExample.name, (context) => const DialPadExample()), diff --git a/example/lib/pages/components/navigation_bar_example.dart b/example/lib/pages/components/navigation_bar_example.dart new file mode 100644 index 00000000..0132bac8 --- /dev/null +++ b/example/lib/pages/components/navigation_bar_example.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class NavigationBarExample extends StatefulWidget { + static const String name = 'NavigationBar'; + + const NavigationBarExample({super.key}); + + @override + State createState() => _NavigationBarExampleState(); +} + +class _NavigationBarExampleState extends State { + int selectedIndex = 0; + + @override + Widget build(BuildContext context) { + final items = [ + ZetaNavigationBarItem( + icon: ZetaIcons.star_round, + label: 'Label', + showBadge: true, + badgeValue: 2, + ), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ]; + + return ExampleScaffold( + name: 'Navigation Bar', + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ZetaNavigationBar(items: items, dividerIndex: 3), + const SizedBox(height: 16), + ZetaNavigationBar(items: items, splitItems: true), + const SizedBox(height: 16), + ZetaNavigationBar( + items: items, + action: ZetaButton.primary( + label: 'Button', + onPressed: () {}, + ), + ), + ], + ), + ), + bottomNavigationBar: ZetaNavigationBar( + items: items, + currentIndex: selectedIndex, + onTap: (val) => setState(() { + selectedIndex = val; + }), + ), + // bottomNavigationBar: BottomNavigationBar(items: [ + // BottomNavigationBarItem( + // icon: Icon(ZetaIcons.star_round), + // label: 'Label', + // ), + // BottomNavigationBarItem( + // icon: Icon(ZetaIcons.star_round), + // label: 'Label', + // ) + // ]), + ); + } +} diff --git a/example/lib/widgets.dart b/example/lib/widgets.dart index ae57919f..afbe3023 100644 --- a/example/lib/widgets.dart +++ b/example/lib/widgets.dart @@ -66,12 +66,14 @@ class ExampleScaffold extends StatelessWidget { final Widget child; final List actions; final Widget? floatingActionButton; + final Widget? bottomNavigationBar; const ExampleScaffold({ required this.name, required this.child, this.actions = const [], this.floatingActionButton, + this.bottomNavigationBar, super.key, }); @@ -98,6 +100,7 @@ class ExampleScaffold extends StatelessWidget { ], ), backgroundColor: colors.surface, + bottomNavigationBar: bottomNavigationBar, body: SelectionArea( child: child, ), diff --git a/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp index b25e363e..955ee303 100644 --- a/example/windows/runner/flutter_window.cpp +++ b/example/windows/runner/flutter_window.cpp @@ -31,6 +31,11 @@ bool FlutterWindow::OnCreate() { this->Show(); }); + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + return true; } diff --git a/lib/src/components/navigation bar/navigation_bar.dart b/lib/src/components/navigation bar/navigation_bar.dart new file mode 100644 index 00000000..cb189900 --- /dev/null +++ b/lib/src/components/navigation bar/navigation_bar.dart @@ -0,0 +1,182 @@ +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +class ZetaNavigationBarItem { + const ZetaNavigationBarItem({ + required this.icon, + this.label, + this.showBadge = false, + this.badgeValue, + }); + + final IconData icon; + final String? label; + final bool showBadge; + final int? badgeValue; +} + +class ZetaNavigationBar extends StatelessWidget { + const ZetaNavigationBar({ + required this.items, + this.currentIndex, + this.onTap, + super.key, + this.splitItems = false, + this.dividerIndex, + this.action, + }) : assert( + items.length >= 2 && items.length <= 6, + 'The number of items should be between 2 and 6', + ); + + final List items; + final int? currentIndex; + final void Function(int value)? onTap; + final bool splitItems; + final int? dividerIndex; + final Widget? action; + + Row _generateNavigationItemRow(List items) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: items.map((navItem) { + final index = items.indexOf(navItem); + return _NavigationItem( + selected: index == currentIndex, + item: navItem, + onTap: () => onTap?.call(index), + ); + }).toList()); + } + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + + late Widget child; + + if (splitItems || dividerIndex != null) { + final List leftItems = []; + final List rightItems = []; + final splitPoint = dividerIndex ?? (items.length / 2).floor(); + for (int i = 0; i < items.length; i++) { + if (i < splitPoint) { + leftItems.add(items[i]); + } else { + rightItems.add(items[i]); + } + } + child = Row( + mainAxisAlignment: splitItems ? MainAxisAlignment.spaceBetween : MainAxisAlignment.spaceAround, + children: [ + _generateNavigationItemRow(leftItems), + if (dividerIndex != null) Container(color: colors.borderSubtle, width: 1, height: 44), + _generateNavigationItemRow(rightItems), + ], + ); + } else if (action != null) { + child = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _generateNavigationItemRow(items), + action!, + ], + ); + } else { + child = _generateNavigationItemRow(items); + } + + return Container( + padding: const EdgeInsets.only( + left: ZetaSpacing.s, + right: ZetaSpacing.s, + ), + decoration: BoxDecoration( + color: colors.surfacePrimary, + border: Border( + top: BorderSide( + color: colors.borderSubtle, + width: 2, + ), + ), + ), + child: child, + ); + } +} + +class _NavigationItem extends StatelessWidget { + const _NavigationItem({ + required this.selected, + required this.item, + required this.onTap, + }); + + final bool selected; + final ZetaNavigationBarItem item; + final VoidCallback onTap; + + Widget _getBadge(BuildContext context) { + final colors = Zeta.of(context).colors; + final size = item.badgeValue == null && item.showBadge ? ZetaWidgetSize.small : ZetaWidgetSize.medium; + + return Positioned( + right: -2, + top: -2, + child: Container( + padding: const EdgeInsets.all(0.25), + decoration: BoxDecoration( + color: colors.surfacePrimary, + borderRadius: ZetaRadius.full, + ), + child: ZetaIndicator.notification( + size: size, + value: item.badgeValue, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + + final elementColor = selected ? colors.primary : colors.textDisabled; + + return Material( + color: colors.surfacePrimary, + borderRadius: ZetaRadius.wide, + child: InkWell( + borderRadius: ZetaRadius.wide, + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: ZetaSpacing.x4, + vertical: ZetaSpacing.x2, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + children: [ + Icon( + item.icon, + color: elementColor, + ), + if (item.showBadge || item.badgeValue != null) _getBadge(context), + ], + ), + const SizedBox(height: ZetaSpacing.x2), + if (item.label != null) + Text( + item.label!, + style: Theme.of(context).textTheme.labelSmall?.copyWith(color: elementColor), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/zeta_flutter.dart b/lib/zeta_flutter.dart index 8ee35e06..e4f60cf4 100644 --- a/lib/zeta_flutter.dart +++ b/lib/zeta_flutter.dart @@ -21,6 +21,7 @@ export 'src/components/buttons/icon_button.dart'; export 'src/components/checkbox/checkbox.dart'; export 'src/components/chips/chip.dart'; export 'src/components/dial_pad/dial_pad.dart'; +export 'src/components/navigation bar/navigation_bar.dart'; export 'src/components/password/password_input.dart'; export 'src/components/progress/progress_bar.dart'; export 'src/theme/color_extensions.dart'; From 4fd7a2289e505e32a8ebf9fc8816cbcdbbea4be4 Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Fri, 1 Mar 2024 15:45:16 +0000 Subject: [PATCH 2/5] named constructors --- .../components/navigation_bar_example.dart | 6 +-- .../navigation bar/navigation_bar.dart | 44 ++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/example/lib/pages/components/navigation_bar_example.dart b/example/lib/pages/components/navigation_bar_example.dart index 0132bac8..21c0cb6a 100644 --- a/example/lib/pages/components/navigation_bar_example.dart +++ b/example/lib/pages/components/navigation_bar_example.dart @@ -34,11 +34,11 @@ class _NavigationBarExampleState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - ZetaNavigationBar(items: items, dividerIndex: 3), + ZetaNavigationBar.divided(items: items, dividerIndex: 3), const SizedBox(height: 16), - ZetaNavigationBar(items: items, splitItems: true), + ZetaNavigationBar.split(items: items), const SizedBox(height: 16), - ZetaNavigationBar( + ZetaNavigationBar.action( items: items, action: ZetaButton.primary( label: 'Button', diff --git a/lib/src/components/navigation bar/navigation_bar.dart b/lib/src/components/navigation bar/navigation_bar.dart index cb189900..9ec5fb33 100644 --- a/lib/src/components/navigation bar/navigation_bar.dart +++ b/lib/src/components/navigation bar/navigation_bar.dart @@ -21,15 +21,57 @@ class ZetaNavigationBar extends StatelessWidget { required this.items, this.currentIndex, this.onTap, - super.key, this.splitItems = false, this.dividerIndex, this.action, + super.key, }) : assert( items.length >= 2 && items.length <= 6, 'The number of items should be between 2 and 6', ); + const ZetaNavigationBar.divided({ + required List items, + required int? dividerIndex, + int? currentIndex, + void Function(int value)? onTap, + Key? key, + }) : this( + items: items, + currentIndex: currentIndex, + onTap: onTap, + splitItems: true, + dividerIndex: dividerIndex, + key: key, + ); + + const ZetaNavigationBar.split({ + required List items, + int? currentIndex, + void Function(int value)? onTap, + Key? key, + }) : this( + items: items, + currentIndex: currentIndex, + onTap: onTap, + splitItems: true, + key: key, + ); + + const ZetaNavigationBar.action({ + required List items, + required Widget action, + int? currentIndex, + void Function(int value)? onTap, + Key? key, + }) : this( + items: items, + currentIndex: currentIndex, + onTap: onTap, + action: action, + key: key, + ); + final List items; final int? currentIndex; final void Function(int value)? onTap; From 7cb0242b49901cc337e13f655d9f727ba8ac1454 Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Fri, 1 Mar 2024 15:59:11 +0000 Subject: [PATCH 3/5] docs --- .../navigation bar/navigation_bar.dart | 67 ++++++++++++++++--- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/lib/src/components/navigation bar/navigation_bar.dart b/lib/src/components/navigation bar/navigation_bar.dart index 9ec5fb33..7d77caf3 100644 --- a/lib/src/components/navigation bar/navigation_bar.dart +++ b/lib/src/components/navigation bar/navigation_bar.dart @@ -1,22 +1,34 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../../zeta_flutter.dart'; +/// An item to be used in a [ZetaNavigationBar] class ZetaNavigationBarItem { + /// Creates a new [ZetaNavigationBarItem] const ZetaNavigationBarItem({ required this.icon, - this.label, + required this.label, this.showBadge = false, this.badgeValue, }); + /// The icon shown on the item. final IconData icon; + + /// The label shown on the item. final String? label; + + /// Displays a small [ZetaIndicator] on the item. final bool showBadge; + + /// Displays a medium [ZetaIndicator] on the item with the given value. final int? badgeValue; } +/// Navigation Bars (Bottom navigation) allow movement between primary destinations in an app. class ZetaNavigationBar extends StatelessWidget { + /// Creates a new [ZetaNavigationBar]. const ZetaNavigationBar({ required this.items, this.currentIndex, @@ -30,6 +42,7 @@ class ZetaNavigationBar extends StatelessWidget { 'The number of items should be between 2 and 6', ); + /// Creates a [ZetaNavigationBar] with a divider after the item at the given index. const ZetaNavigationBar.divided({ required List items, required int? dividerIndex, @@ -45,6 +58,7 @@ class ZetaNavigationBar extends StatelessWidget { key: key, ); + /// Creates a [ZetaNavigationBar] and splits the items in half. const ZetaNavigationBar.split({ required List items, int? currentIndex, @@ -58,6 +72,7 @@ class ZetaNavigationBar extends StatelessWidget { key: key, ); + /// Creates a [ZetaNavigationBar] with an action. const ZetaNavigationBar.action({ required List items, required Widget action, @@ -72,24 +87,36 @@ class ZetaNavigationBar extends StatelessWidget { key: key, ); + /// The items displayed on the navigation bar. final List items; + + /// The index of the currently active item. final int? currentIndex; + + /// Called when an item is tapped with the index of the tapped item. final void Function(int value)? onTap; + + /// Divides the navigation items in half. final bool splitItems; + + /// The index of the item the divider should be displayed after. final int? dividerIndex; + + /// The action shown on the navigation bar. final Widget? action; Row _generateNavigationItemRow(List items) { return Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: items.map((navItem) { - final index = items.indexOf(navItem); - return _NavigationItem( - selected: index == currentIndex, - item: navItem, - onTap: () => onTap?.call(index), - ); - }).toList()); + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: items.map((navItem) { + final index = items.indexOf(navItem); + return _NavigationItem( + selected: index == currentIndex, + item: navItem, + onTap: () => onTap?.call(index), + ); + }).toList(), + ); } @override @@ -146,6 +173,17 @@ class ZetaNavigationBar extends StatelessWidget { child: child, ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(IterableProperty('items', items)) + ..add(IntProperty('currentIndex', currentIndex)) + ..add(ObjectFlagProperty.has('onTap', onTap)) + ..add(DiagnosticsProperty('splitItems', splitItems)) + ..add(IntProperty('dividerIndex', dividerIndex)); + } } class _NavigationItem extends StatelessWidget { @@ -221,4 +259,13 @@ class _NavigationItem extends StatelessWidget { ), ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('selected', selected)) + ..add(DiagnosticsProperty('item', item)) + ..add(ObjectFlagProperty.has('onTap', onTap)); + } } From 88f70c916bab2fb5ea829cde6804b69604df97da Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Fri, 1 Mar 2024 16:38:01 +0000 Subject: [PATCH 4/5] widget book --- .../components/navigation_bar_widgetbook.dart | 99 +++++++++++++++++++ example/widgetbook/widgetbook.dart | 2 + .../navigation bar/navigation_bar.dart | 10 +- 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 example/widgetbook/pages/components/navigation_bar_widgetbook.dart diff --git a/example/widgetbook/pages/components/navigation_bar_widgetbook.dart b/example/widgetbook/pages/components/navigation_bar_widgetbook.dart new file mode 100644 index 00000000..c7e20dbd --- /dev/null +++ b/example/widgetbook/pages/components/navigation_bar_widgetbook.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent navigationBarWidgetbook() { + return WidgetbookComponent( + name: 'Navigation Bar', + isInitiallyExpanded: false, + useCases: [ + WidgetbookUseCase( + name: 'Navigation bar', + builder: (context) { + final items = [ + ZetaNavigationBarItem( + icon: ZetaIcons.star_round, + label: 'Label', + showBadge: true, + badgeValue: 2, + ), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ]; + return WidgetbookTestWidget( + widget: Center( + child: ZetaNavigationBar( + items: items, + currentIndex: context.knobs.intOrNull.input( + label: 'Selected index', + initialValue: 0, + ), + ), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Divided navigation bar', + builder: (context) { + final items = [ + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ]; + return WidgetbookTestWidget( + widget: Center( + child: ZetaNavigationBar.divided( + items: items, + dividerIndex: context.knobs.intOrNull.input( + label: 'Divider index', + initialValue: 3, + ), + ), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Split navigation bar', + builder: (context) { + final items = [ + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ]; + return WidgetbookTestWidget( + widget: Center( + child: ZetaNavigationBar.split(items: items), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Navigation bar with action', + builder: (context) { + final items = [ + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), + ]; + return WidgetbookTestWidget( + widget: Center( + child: ZetaNavigationBar.action( + items: items, + action: ZetaButton.primary(label: 'Button'), + ), + ), + ); + }, + ), + ], + ); +} diff --git a/example/widgetbook/widgetbook.dart b/example/widgetbook/widgetbook.dart index 2179ec35..b80f99da 100644 --- a/example/widgetbook/widgetbook.dart +++ b/example/widgetbook/widgetbook.dart @@ -10,6 +10,7 @@ import 'pages/components/bottom_sheet_widgetbook.dart'; import 'pages/components/button_widgetbook.dart'; import 'pages/components/checkbox_widgetbook.dart'; import 'pages/components/dial_pad_widgetbook.dart'; +import 'pages/components/navigation_bar_widgetbook.dart'; import 'pages/theme/color_widgetbook.dart'; import 'pages/components/banner_widgetbook.dart'; import 'pages/components/chip_widgetbook.dart'; @@ -41,6 +42,7 @@ class HotReload extends StatelessWidget { passwordInputWidgetBook(), bottomSheetWidgetBook(), dialPadWidgetbook(), + navigationBarWidgetbook(), ]..sort((a, b) => a.name.compareTo(b.name)), ), WidgetbookCategory( diff --git a/lib/src/components/navigation bar/navigation_bar.dart b/lib/src/components/navigation bar/navigation_bar.dart index 7d77caf3..e0fb05cc 100644 --- a/lib/src/components/navigation bar/navigation_bar.dart +++ b/lib/src/components/navigation bar/navigation_bar.dart @@ -53,7 +53,7 @@ class ZetaNavigationBar extends StatelessWidget { items: items, currentIndex: currentIndex, onTap: onTap, - splitItems: true, + splitItems: false, dividerIndex: dividerIndex, key: key, ); @@ -136,11 +136,17 @@ class ZetaNavigationBar extends StatelessWidget { rightItems.add(items[i]); } } + child = Row( mainAxisAlignment: splitItems ? MainAxisAlignment.spaceBetween : MainAxisAlignment.spaceAround, children: [ _generateNavigationItemRow(leftItems), - if (dividerIndex != null) Container(color: colors.borderSubtle, width: 1, height: 44), + if (dividerIndex != null) + Container( + color: colors.borderSubtle, + width: 1, + height: 44, + ), _generateNavigationItemRow(rightItems), ], ); From 0986af49a61c597de0329f09bf522e59885fac72 Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 4 Mar 2024 12:31:59 +0000 Subject: [PATCH 5/5] Fix spacing, change badge logic, update widgetbook and example --- .../components/navigation_bar_example.dart | 17 +-- .../components/navigation_bar_widgetbook.dart | 121 +++++------------- example/widgetbook/test/test_components.dart | 14 +- example/widgetbook/widgetbook.dart | 2 +- lib/src/components/badges/indicator.dart | 6 +- .../navigation bar/navigation_bar.dart | 81 ++++++------ 6 files changed, 82 insertions(+), 159 deletions(-) diff --git a/example/lib/pages/components/navigation_bar_example.dart b/example/lib/pages/components/navigation_bar_example.dart index 21c0cb6a..87731510 100644 --- a/example/lib/pages/components/navigation_bar_example.dart +++ b/example/lib/pages/components/navigation_bar_example.dart @@ -17,12 +17,7 @@ class _NavigationBarExampleState extends State { @override Widget build(BuildContext context) { final items = [ - ZetaNavigationBarItem( - icon: ZetaIcons.star_round, - label: 'Label', - showBadge: true, - badgeValue: 2, - ), + ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label', badge: ZetaIndicator(value: 2)), ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), @@ -55,16 +50,6 @@ class _NavigationBarExampleState extends State { selectedIndex = val; }), ), - // bottomNavigationBar: BottomNavigationBar(items: [ - // BottomNavigationBarItem( - // icon: Icon(ZetaIcons.star_round), - // label: 'Label', - // ), - // BottomNavigationBarItem( - // icon: Icon(ZetaIcons.star_round), - // label: 'Label', - // ) - // ]), ); } } diff --git a/example/widgetbook/pages/components/navigation_bar_widgetbook.dart b/example/widgetbook/pages/components/navigation_bar_widgetbook.dart index c7e20dbd..ce03347b 100644 --- a/example/widgetbook/pages/components/navigation_bar_widgetbook.dart +++ b/example/widgetbook/pages/components/navigation_bar_widgetbook.dart @@ -1,99 +1,38 @@ import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:widgetbook/widgetbook.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; import '../../test/test_components.dart'; -WidgetbookComponent navigationBarWidgetbook() { - return WidgetbookComponent( - name: 'Navigation Bar', - isInitiallyExpanded: false, - useCases: [ - WidgetbookUseCase( - name: 'Navigation bar', - builder: (context) { - final items = [ - ZetaNavigationBarItem( - icon: ZetaIcons.star_round, - label: 'Label', - showBadge: true, - badgeValue: 2, - ), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ]; - return WidgetbookTestWidget( - widget: Center( - child: ZetaNavigationBar( - items: items, - currentIndex: context.knobs.intOrNull.input( - label: 'Selected index', - initialValue: 0, - ), - ), - ), - ); - }, - ), - WidgetbookUseCase( - name: 'Divided navigation bar', - builder: (context) { - final items = [ - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ]; - return WidgetbookTestWidget( - widget: Center( - child: ZetaNavigationBar.divided( - items: items, - dividerIndex: context.knobs.intOrNull.input( - label: 'Divider index', - initialValue: 3, - ), - ), - ), - ); - }, - ), - WidgetbookUseCase( - name: 'Split navigation bar', - builder: (context) { - final items = [ - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ]; - return WidgetbookTestWidget( - widget: Center( - child: ZetaNavigationBar.split(items: items), - ), - ); - }, - ), - WidgetbookUseCase( - name: 'Navigation bar with action', - builder: (context) { - final items = [ - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label'), - ]; - return WidgetbookTestWidget( - widget: Center( - child: ZetaNavigationBar.action( - items: items, - action: ZetaButton.primary(label: 'Button'), - ), - ), - ); - }, - ), - ], +Widget navigationBarUseCase(BuildContext context) { + List items = List.generate( + context.knobs.int.slider(label: 'Items', min: 2, max: 6, initialValue: 2), + (index) => ZetaNavigationBarItem(icon: ZetaIcons.star_round, label: 'Label $index'), ); + int currIndex = 0; + bool showButton = context.knobs.boolean(label: 'Button'); + int? dividerIndex = context.knobs.intOrNull.slider(label: 'Divider', min: 0, max: 6, initialValue: null); + bool showSplit = context.knobs.boolean(label: 'Split Items'); + return StatefulBuilder(builder: (context, setState) { + double width = (items.length * 90) + (showSplit ? 90 : 0) + (dividerIndex != null ? 90 : 0) + (showButton ? 90 : 0); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: WidgetbookTestWidget( + screenSize: Size(width, 260), + widget: ZetaNavigationBar( + items: items, + action: showButton ? ZetaButton.primary(label: 'Button', onPressed: () {}) : null, + onTap: (i) => setState(() => currIndex = i), + currentIndex: currIndex, + splitItems: showSplit, + dividerIndex: dividerIndex, + ), + ), + ), + ], + ); + }); } diff --git a/example/widgetbook/test/test_components.dart b/example/widgetbook/test/test_components.dart index c6d62693..532d222d 100644 --- a/example/widgetbook/test/test_components.dart +++ b/example/widgetbook/test/test_components.dart @@ -20,12 +20,14 @@ class WidgetbookTestWidget extends StatelessWidget { backgroundColor: Colors.transparent, body: removeBody ? widget - : SizedBox( - width: size.width, - height: size.height, - child: MediaQuery( - data: MediaQueryData(size: Size(size.width, size.height)), - child: SingleChildScrollView(child: widget), + : Center( + child: SizedBox( + width: size.width, + height: size.height, + child: MediaQuery( + data: MediaQueryData(size: Size(size.width, size.height)), + child: SingleChildScrollView(child: widget), + ), ), ), ); diff --git a/example/widgetbook/widgetbook.dart b/example/widgetbook/widgetbook.dart index b80f99da..6fc36259 100644 --- a/example/widgetbook/widgetbook.dart +++ b/example/widgetbook/widgetbook.dart @@ -42,7 +42,7 @@ class HotReload extends StatelessWidget { passwordInputWidgetBook(), bottomSheetWidgetBook(), dialPadWidgetbook(), - navigationBarWidgetbook(), + WidgetbookUseCase(name: 'Navigation Bar', builder: (context) => navigationBarUseCase(context)) ]..sort((a, b) => a.name.compareTo(b.name)), ), WidgetbookCategory( diff --git a/lib/src/components/badges/indicator.dart b/lib/src/components/badges/indicator.dart index 52785236..baf8f4d0 100644 --- a/lib/src/components/badges/indicator.dart +++ b/lib/src/components/badges/indicator.dart @@ -18,7 +18,7 @@ class ZetaIndicator extends StatelessWidget { /// Constructor for [ZetaIndicator]. const ZetaIndicator({ super.key, - required this.type, + this.type = ZetaIndicatorType.notification, this.size = ZetaWidgetSize.large, this.icon, this.value, @@ -44,12 +44,16 @@ class ZetaIndicator extends StatelessWidget { }) : type = ZetaIndicatorType.notification; /// The type of the [ZetaIndicator] - icon or notification. + /// + /// Defaults to [ZetaIndicatorType.notification]. final ZetaIndicatorType type; /// The size of the [ZetaIndicator]. Default is [ZetaWidgetSize.large] final ZetaWidgetSize size; /// Inverse the border color. + /// + /// Defaults to false. final bool inverse; /// Indicator icon, default: `ZetaIcons.star_round`. diff --git a/lib/src/components/navigation bar/navigation_bar.dart b/lib/src/components/navigation bar/navigation_bar.dart index e0fb05cc..c865e2ed 100644 --- a/lib/src/components/navigation bar/navigation_bar.dart +++ b/lib/src/components/navigation bar/navigation_bar.dart @@ -3,14 +3,15 @@ import 'package:flutter/material.dart'; import '../../../zeta_flutter.dart'; -/// An item to be used in a [ZetaNavigationBar] +const double _navigationItemBorderWidth = 1; + +/// An item to be used in a [ZetaNavigationBar]. class ZetaNavigationBarItem { /// Creates a new [ZetaNavigationBarItem] const ZetaNavigationBarItem({ required this.icon, required this.label, - this.showBadge = false, - this.badgeValue, + this.badge, }); /// The icon shown on the item. @@ -19,11 +20,8 @@ class ZetaNavigationBarItem { /// The label shown on the item. final String? label; - /// Displays a small [ZetaIndicator] on the item. - final bool showBadge; - - /// Displays a medium [ZetaIndicator] on the item with the given value. - final int? badgeValue; + /// [ZetaIndicator] badge to show on navigation item. + final ZetaIndicator? badge; } /// Navigation Bars (Bottom navigation) allow movement between primary destinations in an app. @@ -123,7 +121,7 @@ class ZetaNavigationBar extends StatelessWidget { Widget build(BuildContext context) { final colors = Zeta.of(context).colors; - late Widget child; + final Widget child; if (splitItems || dividerIndex != null) { final List leftItems = []; @@ -144,8 +142,8 @@ class ZetaNavigationBar extends StatelessWidget { if (dividerIndex != null) Container( color: colors.borderSubtle, - width: 1, - height: 44, + width: _navigationItemBorderWidth, + height: ZetaSpacing.x11, ), _generateNavigationItemRow(rightItems), ], @@ -169,12 +167,7 @@ class ZetaNavigationBar extends StatelessWidget { ), decoration: BoxDecoration( color: colors.surfacePrimary, - border: Border( - top: BorderSide( - color: colors.borderSubtle, - width: 2, - ), - ), + border: Border(top: BorderSide(color: colors.borderSubtle)), ), child: child, ); @@ -203,22 +196,21 @@ class _NavigationItem extends StatelessWidget { final ZetaNavigationBarItem item; final VoidCallback onTap; - Widget _getBadge(BuildContext context) { - final colors = Zeta.of(context).colors; - final size = item.badgeValue == null && item.showBadge ? ZetaWidgetSize.small : ZetaWidgetSize.medium; - + Widget _getBadge(ZetaColors colors) { return Positioned( - right: -2, - top: -2, - child: Container( - padding: const EdgeInsets.all(0.25), + right: ZetaSpacing.xxs, + child: DecoratedBox( decoration: BoxDecoration( color: colors.surfacePrimary, borderRadius: ZetaRadius.full, ), - child: ZetaIndicator.notification( - size: size, - value: item.badgeValue, + child: item.badge?.copyWith( + size: item.badge?.value == null + ? ZetaWidgetSize.small + : item.badge?.size == ZetaWidgetSize.large + ? ZetaWidgetSize.medium + : null, + type: ZetaIndicatorType.notification, ), ), ); @@ -227,33 +219,34 @@ class _NavigationItem extends StatelessWidget { @override Widget build(BuildContext context) { final colors = Zeta.of(context).colors; - final elementColor = selected ? colors.primary : colors.textDisabled; return Material( color: colors.surfacePrimary, - borderRadius: ZetaRadius.wide, child: InkWell( - borderRadius: ZetaRadius.wide, + borderRadius: ZetaRadius.rounded, onTap: onTap, child: Container( - padding: const EdgeInsets.symmetric( - horizontal: ZetaSpacing.x4, - vertical: ZetaSpacing.x2, - ), + padding: const EdgeInsets.only(left: ZetaSpacing.xs, right: ZetaSpacing.xs, bottom: ZetaSpacing.xs), child: Column( mainAxisSize: MainAxisSize.min, children: [ - Stack( - children: [ - Icon( - item.icon, - color: elementColor, - ), - if (item.showBadge || item.badgeValue != null) _getBadge(context), - ], + SizedBox( + width: ZetaSpacing.x11, + height: ZetaSpacing.x8 - _navigationItemBorderWidth, + child: Stack( + children: [ + Positioned( + left: ZetaSpacing.x2_5, + top: ZetaSpacing.xs - _navigationItemBorderWidth, + right: ZetaSpacing.x2_5, + child: Icon(item.icon, color: elementColor, size: ZetaSpacing.x6), + ), + if (item.badge != null) _getBadge(colors), + ], + ), ), - const SizedBox(height: ZetaSpacing.x2), + const SizedBox(height: ZetaSpacing.xs), if (item.label != null) Text( item.label!,