From 44b0e01028c02723daefd078f17732e12a77361d Mon Sep 17 00:00:00 2001 From: Osman Date: Tue, 12 Mar 2024 15:34:36 +0000 Subject: [PATCH 1/4] Add breadcrumbs --- example/lib/home.dart | 20 +- .../pages/components/breadcrumbs_example.dart | 44 +++ .../pages/components/progress_example.dart | 7 + example/widgetbook/main.dart | 137 ++++++-- .../components/breadcrumbs_widgetbook.dart | 68 ++++ .../components/breadcrumbs/breadcrumbs.dart | 323 ++++++++++++++++++ lib/zeta_flutter.dart | 1 + 7 files changed, 560 insertions(+), 40 deletions(-) create mode 100644 example/lib/pages/components/breadcrumbs_example.dart create mode 100644 example/widgetbook/pages/components/breadcrumbs_widgetbook.dart create mode 100644 lib/src/components/breadcrumbs/breadcrumbs.dart diff --git a/example/lib/home.dart b/example/lib/home.dart index da7d414b..79051b0f 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -5,6 +5,7 @@ import 'package:zeta_example/pages/components/avatar_example.dart'; import 'package:zeta_example/pages/components/badges_example.dart'; import 'package:zeta_example/pages/components/banner_example.dart'; import 'package:zeta_example/pages/components/bottom_sheet_example.dart'; +import 'package:zeta_example/pages/components/breadcrumbs_example.dart'; 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'; @@ -33,11 +34,14 @@ final List components = [ Component(BannerExample.name, (context) => const BannerExample()), Component(BadgesExample.name, (context) => const BadgesExample()), Component(BottomSheetExample.name, (context) => const BottomSheetExample()), + Component(BreadCrumbsExample.name, (context) => const BreadCrumbsExample()), 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( + NavigationBarExample.name, (context) => const NavigationBarExample()), + Component( + PasswordInputExample.name, (context) => const PasswordInputExample()), Component(ProgressExample.name, (context) => const ProgressExample()), Component(DialPadExample.name, (context) => const DialPadExample()), ]; @@ -104,21 +108,27 @@ class _HomeState extends State { title: Text('Widgets'), backgroundColor: Zeta.of(context).colors.warm.shade30, children: _components - .map((item) => ListTile(title: Text(item.name), onTap: () => context.go('/${item.name}'))) + .map((item) => ListTile( + title: Text(item.name), + onTap: () => context.go('/${item.name}'))) .toList(), ), ExpansionTile( title: Text('Theme'), backgroundColor: Zeta.of(context).colors.warm.shade30, children: _theme - .map((item) => ListTile(title: Text(item.name), onTap: () => context.go('/${item.name}'))) + .map((item) => ListTile( + title: Text(item.name), + onTap: () => context.go('/${item.name}'))) .toList(), ), ExpansionTile( title: Text('Assets'), backgroundColor: Zeta.of(context).colors.warm.shade30, children: _assets - .map((item) => ListTile(title: Text(item.name), onTap: () => context.go('/${item.name}'))) + .map((item) => ListTile( + title: Text(item.name), + onTap: () => context.go('/${item.name}'))) .toList(), ), ], diff --git a/example/lib/pages/components/breadcrumbs_example.dart b/example/lib/pages/components/breadcrumbs_example.dart new file mode 100644 index 00000000..a9eaf235 --- /dev/null +++ b/example/lib/pages/components/breadcrumbs_example.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class BreadCrumbsExample extends StatefulWidget { + static const String name = 'Breadcrumbs'; + + const BreadCrumbsExample({super.key}); + + @override + State createState() => _BreadCrumbsExampleState(); +} + +class _BreadCrumbsExampleState extends State { + List _children = [ + 'Icon before with seperator', + ]; + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: 'Breadcrumbs', + child: Center( + child: SingleChildScrollView( + child: SizedBox( + width: double.infinity, + child: Column(children: [ + ZetaBreadCrumbs(children: _children), + SizedBox( + height: 50, + ), + FilledButton( + onPressed: () { + setState(() { + _children.add('Icon before with seperator'); + }); + }, + child: Text("Add Breadcrumb")) + ])), + ), + ), + ); + } +} diff --git a/example/lib/pages/components/progress_example.dart b/example/lib/pages/components/progress_example.dart index 5bfc6642..3fe929b1 100644 --- a/example/lib/pages/components/progress_example.dart +++ b/example/lib/pages/components/progress_example.dart @@ -51,12 +51,19 @@ class ProgressExampleState extends State { isThin: false, label: "UPLOADING ...", ), + SizedBox( + height: 40, + ), Wrapper( stepsCompleted: 0, circleSize: ZetaCircleSizes.xl, rounded: false, isCircle: true, ), + SizedBox( + height: 40, + ), + Row(mainAxisAlignment: MainAxisAlignment.center, children: []) ]), ), ), diff --git a/example/widgetbook/main.dart b/example/widgetbook/main.dart index 8abeaece..25192632 100644 --- a/example/widgetbook/main.dart +++ b/example/widgetbook/main.dart @@ -8,6 +8,7 @@ import 'pages/components/avatar_widgetbook.dart'; import 'pages/components/badges_widgetbook.dart'; import 'pages/components/banner_widgetbook.dart'; import 'pages/components/bottom_sheet_widgetbook.dart'; +import 'pages/components/breadcrumbs_widgetbook.dart'; import 'pages/components/button_widgetbook.dart'; import 'pages/components/checkbox_widgetbook.dart'; import 'pages/components/chip_widgetbook.dart'; @@ -39,47 +40,100 @@ class HotReload extends StatelessWidget { WidgetbookComponent( name: 'Badge', useCases: [ - WidgetbookUseCase(name: 'Status Label', builder: (context) => statusLabelUseCase(context)), - WidgetbookUseCase(name: 'Priority Pill', builder: (context) => priorityPillUseCase(context)), - WidgetbookUseCase(name: 'Badge', builder: (context) => badgeUseCase(context)), - WidgetbookUseCase(name: 'Indicators', builder: (context) => indicatorsUseCase(context)), - WidgetbookUseCase(name: 'Tags', builder: (context) => tagsUseCase(context)), WidgetbookUseCase( - name: 'Workcloud Indicators', builder: (context) => workcloudIndicatorsUseCase(context)), + name: 'Status Label', + builder: (context) => statusLabelUseCase(context)), + WidgetbookUseCase( + name: 'Priority Pill', + builder: (context) => priorityPillUseCase(context)), + WidgetbookUseCase( + name: 'Badge', builder: (context) => badgeUseCase(context)), + WidgetbookUseCase( + name: 'Indicators', + builder: (context) => indicatorsUseCase(context)), + WidgetbookUseCase( + name: 'Tags', builder: (context) => tagsUseCase(context)), + WidgetbookUseCase( + name: 'Workcloud Indicators', + builder: (context) => workcloudIndicatorsUseCase(context)), ], ), - WidgetbookUseCase(name: 'Avatar', builder: (context) => avatarUseCase(context)), - WidgetbookUseCase(name: 'Checkbox', builder: (context) => checkboxUseCase(context)), + WidgetbookUseCase( + name: 'Avatar', builder: (context) => avatarUseCase(context)), + WidgetbookUseCase( + name: 'Checkbox', + builder: (context) => checkboxUseCase(context)), WidgetbookComponent( name: 'Buttons', useCases: [ - WidgetbookUseCase(name: 'Button', builder: (context) => buttonUseCase(context)), - WidgetbookUseCase(name: 'Icon Button', builder: (context) => iconButtonUseCase(context)), WidgetbookUseCase( - name: 'Floating Action Button', builder: (context) => floatingActionButtonUseCase(context)), - WidgetbookUseCase(name: 'Group Button', builder: (context) => buttonGroupUseCase(context)), + name: 'Button', + builder: (context) => buttonUseCase(context)), + WidgetbookUseCase( + name: 'Icon Button', + builder: (context) => iconButtonUseCase(context)), + WidgetbookUseCase( + name: 'Floating Action Button', + builder: (context) => floatingActionButtonUseCase(context)), + WidgetbookUseCase( + name: 'Group Button', + builder: (context) => buttonGroupUseCase(context)), ], ), - WidgetbookUseCase(name: 'Banners', builder: (context) => bannerUseCase(context)), - WidgetbookUseCase(name: 'In Page Banners', builder: (context) => inPageBannerUseCase(context)), - WidgetbookUseCase(name: 'Accordion', builder: (context) => accordionUseCase(context)), + WidgetbookUseCase( + name: 'BreadCrumbs', + builder: (context) => breadCrumbsUseCase(context)), + WidgetbookUseCase( + name: 'Banners', builder: (context) => bannerUseCase(context)), + WidgetbookUseCase( + name: 'In Page Banners', + builder: (context) => inPageBannerUseCase(context)), + WidgetbookUseCase( + name: 'Accordion', + builder: (context) => accordionUseCase(context)), WidgetbookComponent( name: 'Chips', useCases: [ - WidgetbookUseCase(name: 'Filter Chip', builder: (context) => filterChipUseCase(context)), - WidgetbookUseCase(name: 'Input Chip', builder: (context) => inputChipUseCase(context)), - WidgetbookUseCase(name: 'Assist Chip', builder: (context) => assistChipUseCase(context)), + WidgetbookUseCase( + name: 'Filter Chip', + builder: (context) => filterChipUseCase(context)), + WidgetbookUseCase( + name: 'Input Chip', + builder: (context) => inputChipUseCase(context)), + WidgetbookUseCase( + name: 'Assist Chip', + builder: (context) => assistChipUseCase(context)), ], ), - WidgetbookUseCase(name: 'Password Input', builder: (context) => passwordInputUseCase(context)), - WidgetbookUseCase(name: 'Bottom Sheet', builder: (context) => bottomSheetContentUseCase(context)), - WidgetbookUseCase(name: 'Dial Pad', builder: (context) => dialPadUseCase(context)), - WidgetbookUseCase(name: 'Navigation Bar', builder: (context) => navigationBarUseCase(context)), + WidgetbookUseCase( + name: 'Password Input', + builder: (context) => passwordInputUseCase(context)), + WidgetbookComponent( + name: 'Bottom Sheet', + useCases: [ + WidgetbookUseCase( + name: 'Content', + builder: (context) => bottomSheetContentUseCase(context)), + WidgetbookUseCase( + name: 'Live', + builder: (context) => bottomSheetLiveUseCase(context)), + ], + ), + WidgetbookUseCase( + name: 'Dial Pad', + builder: (context) => dialPadUseCase(context)), + WidgetbookUseCase( + name: 'Navigation Bar', + builder: (context) => navigationBarUseCase(context)), WidgetbookComponent( name: 'Progress', useCases: [ - WidgetbookUseCase(name: 'Bar', builder: (context) => progressBarUseCase(context)), - WidgetbookUseCase(name: 'Circle', builder: (context) => progressCircleUseCase(context)) + WidgetbookUseCase( + name: 'Bar', + builder: (context) => progressBarUseCase(context)), + WidgetbookUseCase( + name: 'Circle', + builder: (context) => progressCircleUseCase(context)) ], ), ]..sort((a, b) => a.name.compareTo(b.name)), @@ -88,17 +142,23 @@ class HotReload extends StatelessWidget { name: 'Theme', isInitiallyExpanded: false, children: [ - WidgetbookUseCase(name: 'Typography', builder: (context) => typographyUseCase(context)), - WidgetbookUseCase(name: 'Color', builder: (context) => colorUseCase(context)), - WidgetbookUseCase(name: 'Spacing', builder: (context) => spacingUseCase(context)), - WidgetbookUseCase(name: 'Radius', builder: (context) => radiusUseCase(context)), + WidgetbookUseCase( + name: 'Typography', + builder: (context) => typographyUseCase(context)), + WidgetbookUseCase( + name: 'Color', builder: (context) => colorUseCase(context)), + WidgetbookUseCase( + name: 'Spacing', builder: (context) => spacingUseCase(context)), + WidgetbookUseCase( + name: 'Radius', builder: (context) => radiusUseCase(context)), ]..sort((a, b) => a.name.compareTo(b.name)), ), WidgetbookCategory( name: 'Assets', isInitiallyExpanded: false, children: [ - WidgetbookUseCase(name: 'Icons', builder: (context) => iconsUseCase(context)), + WidgetbookUseCase( + name: 'Icons', builder: (context) => iconsUseCase(context)), ]..sort((a, b) => a.name.compareTo(b.name)), ), ], @@ -115,16 +175,23 @@ class HotReload extends StatelessWidget { ), ThemeAddon( themes: [ - WidgetbookTheme(name: 'Light Mode', data: _Theme(isDark: false, isAAA: false)), - WidgetbookTheme(name: 'Dark Mode', data: _Theme(isDark: true, isAAA: false)), - WidgetbookTheme(name: 'Light Mode AAA', data: _Theme(isDark: false, isAAA: true)), - WidgetbookTheme(name: 'Dark Mode AAA', data: _Theme(isDark: true, isAAA: true)), + WidgetbookTheme( + name: 'Light Mode', data: _Theme(isDark: false, isAAA: false)), + WidgetbookTheme( + name: 'Dark Mode', data: _Theme(isDark: true, isAAA: false)), + WidgetbookTheme( + name: 'Light Mode AAA', + data: _Theme(isDark: false, isAAA: true)), + WidgetbookTheme( + name: 'Dark Mode AAA', data: _Theme(isDark: true, isAAA: true)), ], themeBuilder: (context, theme, child) { _Theme _theme = theme; return ZetaProvider( - initialContrast: _theme.isAAA ? ZetaContrast.aaa : ZetaContrast.aa, - initialThemeMode: _theme.isDark ? ThemeMode.dark : ThemeMode.light, + initialContrast: + _theme.isAAA ? ZetaContrast.aaa : ZetaContrast.aa, + initialThemeMode: + _theme.isDark ? ThemeMode.dark : ThemeMode.light, builder: (context, theme, themeMode) { return Builder( builder: (context) { diff --git a/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart b/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart new file mode 100644 index 00000000..9c867267 --- /dev/null +++ b/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +Widget breadCrumbsUseCase(BuildContext context) => WidgetbookTestWidget( + widget: Center( + child: BreadCrumbExample(context), + )); + +class BreadCrumbExample extends StatefulWidget { + const BreadCrumbExample(this.c); + final BuildContext c; + + @override + State createState() => _BreadCrumbExampleState(); +} + +class _BreadCrumbExampleState extends State { + List _children = [ + 'Icon before with seperator', + ]; + + @override + Widget build(BuildContext _) { + return SingleChildScrollView( + child: SizedBox( + width: double.infinity, + child: Column(children: [ + ZetaBreadCrumbs( + children: _children, + rounded: widget.c.knobs.boolean(label: 'Rounded'), + activeIcon: widget.c.knobs.list( + label: 'ActiveIcon', + options: [ + ZetaIcons.star_round, + ZetaIcons.add_alert_round, + ZetaIcons.add_box_round, + ZetaIcons.barcode_round, + ], + labelBuilder: (value) { + if (value == ZetaIcons.star_round) + return 'ZetaIcons.star_round'; + if (value == ZetaIcons.add_alert_round) + return 'ZetaIcons.add_alert_round'; + if (value == ZetaIcons.add_box_round) + return 'ZetaIcons.add_box_round'; + if (value == ZetaIcons.barcode_round) + return 'ZetaIcons.barcode_round'; + return ''; + }, + ), + ), + SizedBox( + height: 50, + ), + FilledButton( + onPressed: () { + setState(() { + _children.add('Icon before with seperator'); + }); + }, + child: Text("Add Breadcrumb")) + ])), + ); + } +} diff --git a/lib/src/components/breadcrumbs/breadcrumbs.dart b/lib/src/components/breadcrumbs/breadcrumbs.dart new file mode 100644 index 00000000..59fac935 --- /dev/null +++ b/lib/src/components/breadcrumbs/breadcrumbs.dart @@ -0,0 +1,323 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +///Class for [ZetaBreadCrumbs] +class ZetaBreadCrumbs extends StatefulWidget { + ///Constructor for [ZetaBreadCrumbs] + const ZetaBreadCrumbs({ + super.key, + required this.children, + this.rounded = true, + this.activeIcon, + }); + + /// Breadcrumb children + final List children; + + /// {@macro zeta-component-rounded} + final bool rounded; + + /// Active icon for breadcrumb + final IconData? activeIcon; + + @override + State createState() => _ZetaBreadCrumbsState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(IterableProperty('children', children)) + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(DiagnosticsProperty('activeIcon', activeIcon)); + } +} + +class _ZetaBreadCrumbsState extends State { + late int _selectedIndex; + late List _children; + + @override + void initState() { + super.initState(); + _selectedIndex = widget.children.length - 1; + _children = [...widget.children]; + } + + // TODO: Optimize so we don't call set state each time. OldWidget stays the same as current widget + @override + void didUpdateWidget(ZetaBreadCrumbs oldWidget) { + if (widget.children.length != _children.length) { + setState(() { + _selectedIndex = widget.children.length - 1; + _children = [...widget.children]; + }); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: renderedChildren(widget.children) + .divide( + const Row( + children: [ + SizedBox( + width: ZetaSpacing.xs, + ), + Icon( + ZetaIcons.chevron_right_round, + size: ZetaSpacing.x5, + ), + SizedBox( + width: ZetaSpacing.xs, + ), + ], + ), + ) + .toList(), + ), + ); + } + + ///Creates breadcumb widget + BreadCrumb createBreadCrumb(String label, int index) { + return BreadCrumb( + label: label, + isSelected: _selectedIndex == index, + onPressed: () { + setState(() { + _selectedIndex = index; + }); + }, + activeIcon: widget.activeIcon, + ); + } + + List renderedChildren(List children) { + final List returnList = []; + if (children.length > 3) { + returnList.add(createBreadCrumb(children.first, 0)); + + final List truncatedChildren = []; + + for (final (index, element) + in children.sublist(1, children.length - 1).indexed) { + truncatedChildren.add(createBreadCrumb(element, index + 1)); + } + returnList + ..add( + BreadCrumbsTruncated( + rounded: widget.rounded, + children: truncatedChildren, + ), + ) + ..add(createBreadCrumb(children.last, children.length - 1)); + } else { + for (final (index, element) in children.indexed) { + returnList.add(createBreadCrumb(element, index)); + } + } + return returnList; + } +} + +/// Class for untruncated [BreadCrumb]. +class BreadCrumb extends StatelessWidget { + ///Constructor for [BreadCrumb] + const BreadCrumb({ + super.key, + required this.label, + this.icon, + required this.isSelected, + required this.onPressed, + this.activeIcon, + }); + + /// [BreadCrumb] label. + final String label; + + /// Selected icon. + final IconData? icon; + + /// Is [BreadCrumb] selected. + final bool isSelected; + + /// Handles press for [BreadCrumb] + final VoidCallback onPressed; + + /// Active icon for [BreadCrumb] + final IconData? activeIcon; + + @override + Widget build(BuildContext context) { + final controller = MaterialStatesController(); + final colors = Zeta.of(context).colors; + return Material( + color: Colors.transparent, + child: InkWell( + statesController: controller, + onTap: onPressed, + enableFeedback: false, + splashColor: Colors.transparent, + overlayColor: MaterialStateProperty.resolveWith((states) { + return Colors.transparent; + }), + child: Row( + children: [ + if (isSelected) + Icon( + activeIcon ?? ZetaIcons.star_round, + color: getColor(controller.value, colors), + ), + const SizedBox( + width: ZetaSpacing.xs, + ), + Text( + label, + style: TextStyle(color: getColor(controller.value, colors)), + ), + ], + ), + ), + ); + } + + /// Get color of breadcrumb based on state. + Color getColor(Set states, ZetaColors colors) { + if (states.contains(MaterialState.hovered)) { + return colors.blue.shade100; + } + if (isSelected) return colors.black; + return colors.textSubtle; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ObjectFlagProperty.has('onPressed', onPressed)) + ..add(StringProperty('label', label)) + ..add(DiagnosticsProperty('icon', icon)) + ..add(DiagnosticsProperty('isSelected', isSelected)) + ..add(DiagnosticsProperty('activeIcon', activeIcon)); + } +} + +///Class for [BreadCrumbsTruncated] +class BreadCrumbsTruncated extends StatefulWidget { + ///Constructor for [BreadCrumbsTruncated] + const BreadCrumbsTruncated({ + super.key, + required this.children, + required this.rounded, + }); + + ///Breadcrumb children + final List children; + + /// {@macro zeta-component-rounded} + final bool rounded; + + @override + State createState() => _BreadCrumbsTruncatedState(); + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('rounded', rounded)); + } +} + +class _BreadCrumbsTruncatedState extends State { + bool _expanded = false; + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + + return _expanded + ? expandedBreadcrumb() + : FilledButton( + onPressed: () { + setState(() { + _expanded = true; + }); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.hovered)) { + return colors.surfaceHovered; + } + if (states.contains(MaterialState.pressed)) { + return colors.primary.shade10; + } + if (states.contains(MaterialState.disabled)) { + return colors.surfaceDisabled; + } + return colors.warm.shade10; + }), + foregroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.disabled)) { + return colors.textDisabled; + } + return colors.textDefault; + }), + shape: MaterialStatePropertyAll( + RoundedRectangleBorder( + borderRadius: + (widget.rounded ? ZetaRadius.minimal : ZetaRadius.none), + ), + ), + side: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.focused)) { + return BorderSide( + width: ZetaSpacing.x0_5, + color: colors.primary.shade100, + ); + } + if (states.isEmpty) { + return BorderSide(color: colors.borderDefault, width: 0.5); + } + return null; + }), + padding: MaterialStateProperty.all(EdgeInsets.zero), + minimumSize: MaterialStateProperty.all(Size.zero), + elevation: const MaterialStatePropertyAll(0), + ), + child: const Icon( + ZetaIcons.more_horizontal_round, + size: ZetaSpacing.x4, + ) + .paddingHorizontal(ZetaSpacing.xs) + .paddingVertical(ZetaSpacing.xxs), + ); + } + + Widget expandedBreadcrumb() { + return Row( + children: widget.children + .divide( + const Row( + children: [ + SizedBox( + width: ZetaSpacing.xs, + ), + Icon( + ZetaIcons.chevron_right_round, + size: ZetaSpacing.x5, + ), + SizedBox( + width: ZetaSpacing.xs, + ), + ], + ), + ) + .toList(), + ); + } +} diff --git a/lib/zeta_flutter.dart b/lib/zeta_flutter.dart index 9c887252..75d941d8 100644 --- a/lib/zeta_flutter.dart +++ b/lib/zeta_flutter.dart @@ -14,6 +14,7 @@ export 'src/components/banners/in_page_banner.dart'; export 'src/components/banners/system_banner.dart'; export 'src/components/bottom sheets/bottom_sheet.dart'; export 'src/components/bottom sheets/menu_items.dart'; +export 'src/components/breadcrumbs/breadcrumbs.dart'; export 'src/components/buttons/button.dart'; export 'src/components/buttons/button_group.dart'; export 'src/components/buttons/button_style.dart'; From 1ea85b20c63fcb340f9b8767574eac8470687340 Mon Sep 17 00:00:00 2001 From: Osman Date: Tue, 12 Mar 2024 15:41:25 +0000 Subject: [PATCH 2/4] Rebase --- example/widgetbook/main.dart | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/example/widgetbook/main.dart b/example/widgetbook/main.dart index 25192632..b08e0c0c 100644 --- a/example/widgetbook/main.dart +++ b/example/widgetbook/main.dart @@ -108,17 +108,9 @@ class HotReload extends StatelessWidget { WidgetbookUseCase( name: 'Password Input', builder: (context) => passwordInputUseCase(context)), - WidgetbookComponent( - name: 'Bottom Sheet', - useCases: [ - WidgetbookUseCase( - name: 'Content', - builder: (context) => bottomSheetContentUseCase(context)), - WidgetbookUseCase( - name: 'Live', - builder: (context) => bottomSheetLiveUseCase(context)), - ], - ), + WidgetbookUseCase( + name: 'Content', + builder: (context) => bottomSheetContentUseCase(context)), WidgetbookUseCase( name: 'Dial Pad', builder: (context) => dialPadUseCase(context)), From f716ecc279d38ed3d33a40e1a1f1192dee094cdf Mon Sep 17 00:00:00 2001 From: Osman Date: Wed, 13 Mar 2024 13:54:39 +0000 Subject: [PATCH 3/4] change to have breadcrumb inputs --- .../pages/components/breadcrumbs_example.dart | 20 ++++++++++++++++--- .../components/breadcrumbs_widgetbook.dart | 20 ++++++++++++++++--- .../components/breadcrumbs/breadcrumbs.dart | 15 +++++++------- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/example/lib/pages/components/breadcrumbs_example.dart b/example/lib/pages/components/breadcrumbs_example.dart index a9eaf235..12566eda 100644 --- a/example/lib/pages/components/breadcrumbs_example.dart +++ b/example/lib/pages/components/breadcrumbs_example.dart @@ -12,9 +12,15 @@ class BreadCrumbsExample extends StatefulWidget { } class _BreadCrumbsExampleState extends State { - List _children = [ - 'Icon before with seperator', + List _children = [ + BreadCrumb( + label: 'Icon before with seperator', + onPressed: () { + print("Breadcrumb " + 0.toString() + "Clicked"); + }, + ), ]; + int index = 1; @override Widget build(BuildContext context) { @@ -32,7 +38,15 @@ class _BreadCrumbsExampleState extends State { FilledButton( onPressed: () { setState(() { - _children.add('Icon before with seperator'); + _children.add( + BreadCrumb( + label: 'Icon before with seperator', + onPressed: () { + print("Breadcrumb clicked"); + }, + ), + ); + index++; }); }, child: Text("Add Breadcrumb")) diff --git a/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart b/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart index 9c867267..2ecdb9e2 100644 --- a/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart +++ b/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart @@ -18,9 +18,15 @@ class BreadCrumbExample extends StatefulWidget { } class _BreadCrumbExampleState extends State { - List _children = [ - 'Icon before with seperator', + List _children = [ + BreadCrumb( + label: 'Icon before with seperator', + onPressed: () { + print("Breadcrumb " + 0.toString() + "Clicked"); + }, + ), ]; + int index = 1; @override Widget build(BuildContext _) { @@ -58,7 +64,15 @@ class _BreadCrumbExampleState extends State { FilledButton( onPressed: () { setState(() { - _children.add('Icon before with seperator'); + _children.add( + BreadCrumb( + label: 'Icon before with seperator', + onPressed: () { + print("Breadcrumb clicked"); + }, + ), + ); + index++; }); }, child: Text("Add Breadcrumb")) diff --git a/lib/src/components/breadcrumbs/breadcrumbs.dart b/lib/src/components/breadcrumbs/breadcrumbs.dart index 59fac935..2c0dce9f 100644 --- a/lib/src/components/breadcrumbs/breadcrumbs.dart +++ b/lib/src/components/breadcrumbs/breadcrumbs.dart @@ -14,7 +14,7 @@ class ZetaBreadCrumbs extends StatefulWidget { }); /// Breadcrumb children - final List children; + final List children; /// {@macro zeta-component-rounded} final bool rounded; @@ -29,7 +29,7 @@ class ZetaBreadCrumbs extends StatefulWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(IterableProperty('children', children)) + ..add(IterableProperty('children', children)) ..add(DiagnosticsProperty('rounded', rounded)) ..add(DiagnosticsProperty('activeIcon', activeIcon)); } @@ -37,7 +37,7 @@ class ZetaBreadCrumbs extends StatefulWidget { class _ZetaBreadCrumbsState extends State { late int _selectedIndex; - late List _children; + late List _children; @override void initState() { @@ -86,20 +86,21 @@ class _ZetaBreadCrumbsState extends State { } ///Creates breadcumb widget - BreadCrumb createBreadCrumb(String label, int index) { + BreadCrumb createBreadCrumb(BreadCrumb input, int index) { return BreadCrumb( - label: label, + label: input.label, isSelected: _selectedIndex == index, onPressed: () { setState(() { _selectedIndex = index; }); + input.onPressed.call(); }, activeIcon: widget.activeIcon, ); } - List renderedChildren(List children) { + List renderedChildren(List children) { final List returnList = []; if (children.length > 3) { returnList.add(createBreadCrumb(children.first, 0)); @@ -134,7 +135,7 @@ class BreadCrumb extends StatelessWidget { super.key, required this.label, this.icon, - required this.isSelected, + this.isSelected = false, required this.onPressed, this.activeIcon, }); From 497164a4e659e70177060f57c7709683225d18bd Mon Sep 17 00:00:00 2001 From: Osman Date: Fri, 15 Mar 2024 14:28:25 +0000 Subject: [PATCH 4/4] Respond to comments --- .../pages/components/breadcrumbs_example.dart | 6 +- .../components/breadcrumbs_widgetbook.dart | 28 ++---- .../components/breadcrumbs/breadcrumbs.dart | 88 ++++++++++++------- 3 files changed, 66 insertions(+), 56 deletions(-) diff --git a/example/lib/pages/components/breadcrumbs_example.dart b/example/lib/pages/components/breadcrumbs_example.dart index 12566eda..9bc02348 100644 --- a/example/lib/pages/components/breadcrumbs_example.dart +++ b/example/lib/pages/components/breadcrumbs_example.dart @@ -12,8 +12,8 @@ class BreadCrumbsExample extends StatefulWidget { } class _BreadCrumbsExampleState extends State { - List _children = [ - BreadCrumb( + List _children = [ + ZetaBreadCrumb( label: 'Icon before with seperator', onPressed: () { print("Breadcrumb " + 0.toString() + "Clicked"); @@ -39,7 +39,7 @@ class _BreadCrumbsExampleState extends State { onPressed: () { setState(() { _children.add( - BreadCrumb( + ZetaBreadCrumb( label: 'Icon before with seperator', onPressed: () { print("Breadcrumb clicked"); diff --git a/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart b/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart index 2ecdb9e2..e36c7f8b 100644 --- a/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart +++ b/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart @@ -3,6 +3,7 @@ import 'package:widgetbook/widgetbook.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; import '../../test/test_components.dart'; +import '../../utils/utils.dart'; Widget breadCrumbsUseCase(BuildContext context) => WidgetbookTestWidget( widget: Center( @@ -18,8 +19,8 @@ class BreadCrumbExample extends StatefulWidget { } class _BreadCrumbExampleState extends State { - List _children = [ - BreadCrumb( + List _children = [ + ZetaBreadCrumb( label: 'Icon before with seperator', onPressed: () { print("Breadcrumb " + 0.toString() + "Clicked"); @@ -37,26 +38,7 @@ class _BreadCrumbExampleState extends State { ZetaBreadCrumbs( children: _children, rounded: widget.c.knobs.boolean(label: 'Rounded'), - activeIcon: widget.c.knobs.list( - label: 'ActiveIcon', - options: [ - ZetaIcons.star_round, - ZetaIcons.add_alert_round, - ZetaIcons.add_box_round, - ZetaIcons.barcode_round, - ], - labelBuilder: (value) { - if (value == ZetaIcons.star_round) - return 'ZetaIcons.star_round'; - if (value == ZetaIcons.add_alert_round) - return 'ZetaIcons.add_alert_round'; - if (value == ZetaIcons.add_box_round) - return 'ZetaIcons.add_box_round'; - if (value == ZetaIcons.barcode_round) - return 'ZetaIcons.barcode_round'; - return ''; - }, - ), + activeIcon: iconKnob(context), ), SizedBox( height: 50, @@ -65,7 +47,7 @@ class _BreadCrumbExampleState extends State { onPressed: () { setState(() { _children.add( - BreadCrumb( + ZetaBreadCrumb( label: 'Icon before with seperator', onPressed: () { print("Breadcrumb clicked"); diff --git a/lib/src/components/breadcrumbs/breadcrumbs.dart b/lib/src/components/breadcrumbs/breadcrumbs.dart index 2c0dce9f..cb791273 100644 --- a/lib/src/components/breadcrumbs/breadcrumbs.dart +++ b/lib/src/components/breadcrumbs/breadcrumbs.dart @@ -14,7 +14,7 @@ class ZetaBreadCrumbs extends StatefulWidget { }); /// Breadcrumb children - final List children; + final List children; /// {@macro zeta-component-rounded} final bool rounded; @@ -29,7 +29,7 @@ class ZetaBreadCrumbs extends StatefulWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(IterableProperty('children', children)) + ..add(IterableProperty('children', children)) ..add(DiagnosticsProperty('rounded', rounded)) ..add(DiagnosticsProperty('activeIcon', activeIcon)); } @@ -37,7 +37,7 @@ class ZetaBreadCrumbs extends StatefulWidget { class _ZetaBreadCrumbsState extends State { late int _selectedIndex; - late List _children; + late List _children; @override void initState() { @@ -46,7 +46,6 @@ class _ZetaBreadCrumbsState extends State { _children = [...widget.children]; } - // TODO: Optimize so we don't call set state each time. OldWidget stays the same as current widget @override void didUpdateWidget(ZetaBreadCrumbs oldWidget) { if (widget.children.length != _children.length) { @@ -86,8 +85,8 @@ class _ZetaBreadCrumbsState extends State { } ///Creates breadcumb widget - BreadCrumb createBreadCrumb(BreadCrumb input, int index) { - return BreadCrumb( + ZetaBreadCrumb createBreadCrumb(ZetaBreadCrumb input, int index) { + return ZetaBreadCrumb( label: input.label, isSelected: _selectedIndex == index, onPressed: () { @@ -100,7 +99,7 @@ class _ZetaBreadCrumbsState extends State { ); } - List renderedChildren(List children) { + List renderedChildren(List children) { final List returnList = []; if (children.length > 3) { returnList.add(createBreadCrumb(children.first, 0)); @@ -128,10 +127,10 @@ class _ZetaBreadCrumbsState extends State { } } -/// Class for untruncated [BreadCrumb]. -class BreadCrumb extends StatelessWidget { - ///Constructor for [BreadCrumb] - const BreadCrumb({ +/// Class for untruncated [ZetaBreadCrumb]. +class ZetaBreadCrumb extends StatefulWidget { + ///Constructor for [ZetaBreadCrumb] + const ZetaBreadCrumb({ super.key, required this.label, this.icon, @@ -140,30 +139,57 @@ class BreadCrumb extends StatelessWidget { this.activeIcon, }); - /// [BreadCrumb] label. + /// [ZetaBreadCrumb] label. final String label; /// Selected icon. final IconData? icon; - /// Is [BreadCrumb] selected. + /// Is [ZetaBreadCrumb] selected. final bool isSelected; - /// Handles press for [BreadCrumb] + /// Handles press for [ZetaBreadCrumb] final VoidCallback onPressed; - /// Active icon for [BreadCrumb] + /// Active icon for [ZetaBreadCrumb] final IconData? activeIcon; + @override + State createState() => _ZetaBreadCrumbState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ObjectFlagProperty.has('onPressed', onPressed)) + ..add(StringProperty('label', label)) + ..add(DiagnosticsProperty('icon', icon)) + ..add(DiagnosticsProperty('isSelected', isSelected)) + ..add(DiagnosticsProperty('activeIcon', activeIcon)); + } +} + +class _ZetaBreadCrumbState extends State { + final controller = MaterialStatesController(); + + @override + void initState() { + super.initState(); + controller.addListener(() { + if (context.mounted && mounted) { + setState(() {}); + } + }); + } + @override Widget build(BuildContext context) { - final controller = MaterialStatesController(); final colors = Zeta.of(context).colors; return Material( color: Colors.transparent, child: InkWell( statesController: controller, - onTap: onPressed, + onTap: widget.onPressed, enableFeedback: false, splashColor: Colors.transparent, overlayColor: MaterialStateProperty.resolveWith((states) { @@ -171,16 +197,16 @@ class BreadCrumb extends StatelessWidget { }), child: Row( children: [ - if (isSelected) + if (widget.isSelected) Icon( - activeIcon ?? ZetaIcons.star_round, + widget.activeIcon ?? ZetaIcons.star_round, color: getColor(controller.value, colors), ), const SizedBox( width: ZetaSpacing.xs, ), Text( - label, + widget.label, style: TextStyle(color: getColor(controller.value, colors)), ), ], @@ -192,21 +218,21 @@ class BreadCrumb extends StatelessWidget { /// Get color of breadcrumb based on state. Color getColor(Set states, ZetaColors colors) { if (states.contains(MaterialState.hovered)) { - return colors.blue.shade100; + return colors.blue; } - if (isSelected) return colors.black; + if (widget.isSelected) return colors.black; return colors.textSubtle; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - ..add(ObjectFlagProperty.has('onPressed', onPressed)) - ..add(StringProperty('label', label)) - ..add(DiagnosticsProperty('icon', icon)) - ..add(DiagnosticsProperty('isSelected', isSelected)) - ..add(DiagnosticsProperty('activeIcon', activeIcon)); + properties.add( + DiagnosticsProperty( + 'controller', + controller, + ), + ); } } @@ -290,8 +316,10 @@ class _BreadCrumbsTruncatedState extends State { minimumSize: MaterialStateProperty.all(Size.zero), elevation: const MaterialStatePropertyAll(0), ), - child: const Icon( - ZetaIcons.more_horizontal_round, + child: Icon( + widget.rounded + ? ZetaIcons.more_horizontal_round + : ZetaIcons.more_horizontal_sharp, size: ZetaSpacing.x4, ) .paddingHorizontal(ZetaSpacing.xs)