From c8e87150c3ff803b7cee890ae0c7b37589b8326b Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Fri, 12 Apr 2024 15:40:14 +0100 Subject: [PATCH 1/6] started refactoring dropdown --- .../pages/components/dropdown_example.dart | 11 +- .../pages/components/dropdown_widgetbook.dart | 10 +- lib/src/components/dropdown/dropdown.dart | 172 ++++++++---------- 3 files changed, 81 insertions(+), 112 deletions(-) diff --git a/example/lib/pages/components/dropdown_example.dart b/example/lib/pages/components/dropdown_example.dart index 0b37dcc2..0e30b104 100644 --- a/example/lib/pages/components/dropdown_example.dart +++ b/example/lib/pages/components/dropdown_example.dart @@ -11,10 +11,7 @@ class DropdownExample extends StatefulWidget { } class _DropdownExampleState extends State { - ZetaDropdownItem selectedItem = ZetaDropdownItem( - value: "Item 1", - leadingIcon: Icon(ZetaIcons.star_round), - ); + String selectedItem = "Item 1"; @override Widget build(BuildContext context) { @@ -36,18 +33,18 @@ class _DropdownExampleState extends State { items: [ ZetaDropdownItem( value: "Item 1", - leadingIcon: Icon(ZetaIcons.star_round), + icon: Icon(ZetaIcons.star_round), ), ZetaDropdownItem( value: "Item 2", - leadingIcon: Icon(ZetaIcons.star_half_round), + icon: Icon(ZetaIcons.star_half_round), ), ZetaDropdownItem( value: "Item 3", ) ], ), - Text('Selected item : ${selectedItem.value}') + Text('Selected item : ${selectedItem}') ])), ), ), diff --git a/example/widgetbook/pages/components/dropdown_widgetbook.dart b/example/widgetbook/pages/components/dropdown_widgetbook.dart index 8cdc9191..73308f16 100644 --- a/example/widgetbook/pages/components/dropdown_widgetbook.dart +++ b/example/widgetbook/pages/components/dropdown_widgetbook.dart @@ -19,21 +19,21 @@ class DropdownExample extends StatefulWidget { } class _DropdownExampleState extends State { - List _children = [ - ZetaDropdownItem( + List<_DropdownItem> _children = [ + _DropdownItem( value: "Item 1", leadingIcon: Icon(ZetaIcons.star_round), ), - ZetaDropdownItem( + _DropdownItem( value: "Item 2", leadingIcon: Icon(ZetaIcons.star_half_round), ), - ZetaDropdownItem( + _DropdownItem( value: "Item 3", ) ]; - late ZetaDropdownItem selectedItem = ZetaDropdownItem( + late _DropdownItem selectedItem = _DropdownItem( value: "Item 1", leadingIcon: Icon(ZetaIcons.star_round), ); diff --git a/lib/src/components/dropdown/dropdown.dart b/lib/src/components/dropdown/dropdown.dart index 0e761d81..73ff64c9 100644 --- a/lib/src/components/dropdown/dropdown.dart +++ b/lib/src/components/dropdown/dropdown.dart @@ -3,8 +3,22 @@ import 'package:flutter/material.dart'; import '../../../zeta_flutter.dart'; +class ZetaDropdownItem { + ZetaDropdownItem({ + String? label, + required this.value, + this.icon, + }) { + this.label = label ?? this.value.toString(); + } + + late final String label; + final T value; + final Widget? icon; +} + /// Class for [ZetaDropdown] -class ZetaDropdown extends StatefulWidget { +class ZetaDropdown extends StatefulWidget { ///Constructor of [ZetaDropdown] const ZetaDropdown({ super.key, @@ -16,14 +30,14 @@ class ZetaDropdown extends StatefulWidget { this.isMinimized = false, }); - /// Input items as list of [ZetaDropdownItem] - final List items; + /// Input items as list of [_DropdownItem] + final List> items; /// Currently selected item - final ZetaDropdownItem selectedItem; + final T? selectedItem; /// Handles changes of dropdown menu - final ValueSetter onChange; + final ValueSetter onChange; /// {@macro zeta-component-rounded} final bool rounded; @@ -35,28 +49,34 @@ class ZetaDropdown extends StatefulWidget { final bool isMinimized; @override - State createState() => _ZetaDropDownState(); + State> createState() => _ZetaDropDownState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties ..add(EnumProperty('leadingType', leadingType)) ..add(DiagnosticsProperty('rounded', rounded)) - ..add( - ObjectFlagProperty>.has( - 'onChange', - onChange, - ), - ) ..add(DiagnosticsProperty('isMinimized', isMinimized)); } } -class _ZetaDropDownState extends State { +class _ZetaDropDownState extends State> { final OverlayPortalController _tooltipController = OverlayPortalController(); final _link = LayerLink(); final _menuKey = GlobalKey(); // declare a global key + ZetaDropdownItem? _selectedItem; + + @override + void initState() { + super.initState(); + try { + _selectedItem = widget.items.firstWhere((item) => item.value == widget.selectedItem); + } catch (e) { + _selectedItem = null; + } + } + /// Returns if click event position is within the header. bool _isInHeader( Offset headerPosition, @@ -95,15 +115,17 @@ class _ZetaDropDownState extends State { event.position, )) _tooltipController.hide(); }, - child: ZetaDropDownMenu( + child: _ZetaDropDownMenu( items: widget.items, - selected: widget.selectedItem.value, + selected: widget.selectedItem, + rounded: widget.rounded, width: _size, boxType: widget.leadingType, - onPress: (item) { - if (item != null) { - widget.onChange(item); - } + onSelected: (item) { + setState(() { + _selectedItem = item; + }); + widget.onChange(item.value); _tooltipController.hide(); }, ), @@ -111,12 +133,7 @@ class _ZetaDropDownState extends State { ), ); }, - child: widget.selectedItem.copyWith( - round: widget.rounded, - focus: _tooltipController.isShowing, - press: onTap, - inputKey: _menuKey, - ), + child: GestureDetector(onTap: onTap, child: Text(_selectedItem?.label ?? 'Select...')), ), ), ); @@ -152,43 +169,33 @@ enum LeadingStyle { radio } -/// Class for [ZetaDropdownItem] -class ZetaDropdownItem extends StatefulWidget { - ///Public constructor for [ZetaDropdownItem] - const ZetaDropdownItem({ +/// Class for [_DropdownItem] +class _DropdownItem extends StatefulWidget { + ///Public constructor for [_DropdownItem] + const _DropdownItem({ super.key, required this.value, this.leadingIcon, - }) : rounded = true, - selected = false, - leadingType = LeadingStyle.none, - itemKey = null, - onPress = null; - - const ZetaDropdownItem._({ - super.key, - required this.rounded, - required this.selected, - required this.value, - this.leadingIcon, - this.onPress, - this.leadingType, - this.itemKey, + this.rounded = true, + this.selected = false, + this.leadingType = LeadingStyle.none, + this.itemKey = null, + this.onPress = null, }); /// {@macro zeta-component-rounded} final bool rounded; - /// If [ZetaDropdownItem] is selected + /// If [_DropdownItem] is selected final bool selected; - /// Value of [ZetaDropdownItem] - final String value; + /// Value of [_DropdownItem] + final ZetaDropdownItem value; - /// Leading icon for [ZetaDropdownItem] + /// Leading icon for [_DropdownItem] final Icon? leadingIcon; - /// Handles clicking for [ZetaDropdownItem] + /// Handles clicking for [_DropdownItem] final VoidCallback? onPress; /// If checkbox is to be shown, the type of it. @@ -197,35 +204,14 @@ class ZetaDropdownItem extends StatefulWidget { /// Key for item final GlobalKey? itemKey; - /// Returns copy of [ZetaDropdownItem] with those private variables included - ZetaDropdownItem copyWith({ - bool? round, - bool? focus, - LeadingStyle? boxType, - VoidCallback? press, - GlobalKey? inputKey, - }) { - return ZetaDropdownItem._( - rounded: round ?? rounded, - selected: focus ?? selected, - onPress: press ?? onPress, - leadingType: boxType ?? leadingType, - itemKey: inputKey ?? itemKey, - value: value, - leadingIcon: leadingIcon, - key: key, - ); - } - @override - State createState() => _ZetaDropdownMenuItemState(); + State<_DropdownItem> createState() => _ZetaDropdownMenuItemState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties ..add(DiagnosticsProperty('rounded', rounded)) ..add(DiagnosticsProperty('selected', selected)) - ..add(StringProperty('value', value)) ..add(ObjectFlagProperty.has('onPress', onPress)) ..add(EnumProperty('leadingType', leadingType)) ..add( @@ -237,7 +223,7 @@ class ZetaDropdownItem extends StatefulWidget { } } -class _ZetaDropdownMenuItemState extends State { +class _ZetaDropdownMenuItemState extends State<_DropdownItem> { final controller = MaterialStatesController(); @override @@ -266,7 +252,7 @@ class _ZetaDropdownMenuItemState extends State { _getLeadingWidget(), const SizedBox(width: ZetaSpacing.x3), Text( - widget.value, + widget.value.label, ), ], ).paddingVertical(ZetaSpacing.x2_5), @@ -347,13 +333,11 @@ class _ZetaDropdownMenuItemState extends State { } } -///Class for [ZetaDropDownMenu] -class ZetaDropDownMenu extends StatefulWidget { - ///Constructor for [ZetaDropDownMenu] - const ZetaDropDownMenu({ - super.key, +class _ZetaDropDownMenu extends StatefulWidget { + ///Constructor for [_ZetaDropDownMenu] + const _ZetaDropDownMenu({ required this.items, - required this.onPress, + required this.onSelected, required this.selected, this.rounded = false, this.width, @@ -361,13 +345,12 @@ class ZetaDropDownMenu extends StatefulWidget { }); /// Input items for the menu - final List items; + final List> items; ///Handles clicking of item in menu - final ValueSetter onPress; + final ValueSetter> onSelected; - /// If item in menu is the currently selected item - final String selected; + final T? selected; /// {@macro zeta-component-rounded} final bool rounded; @@ -379,25 +362,18 @@ class ZetaDropDownMenu extends StatefulWidget { final LeadingStyle? boxType; @override - State createState() => _ZetaDropDownMenuState(); + State<_ZetaDropDownMenu> createState() => _ZetaDropDownMenuState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add( - ObjectFlagProperty>.has( - 'onPress', - onPress, - ), - ) ..add(DiagnosticsProperty('rounded', rounded)) ..add(DoubleProperty('width', width)) - ..add(EnumProperty('boxType', boxType)) - ..add(StringProperty('selected', selected)); + ..add(EnumProperty('boxType', boxType)); } } -class _ZetaDropDownMenuState extends State { +class _ZetaDropDownMenuState extends State<_ZetaDropDownMenu> { @override Widget build(BuildContext context) { final colors = Zeta.of(context).colors; @@ -424,13 +400,9 @@ class _ZetaDropDownMenuState extends State { return Column( mainAxisSize: MainAxisSize.min, children: [ - item.copyWith( - round: widget.rounded, - focus: widget.selected == item.value, - boxType: widget.boxType, - press: () { - widget.onPress(item); - }, + _DropdownItem( + value: item, + onPress: () => widget.onSelected(item), ), const SizedBox(height: ZetaSpacing.x1), ], From 97d5b5f130836ee3d96c58cd05f0238c994fdea1 Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Tue, 16 Apr 2024 11:20:11 +0100 Subject: [PATCH 2/6] fixing leading --- lib/src/components/dropdown/dropdown.dart | 149 ++++++++++++---------- 1 file changed, 84 insertions(+), 65 deletions(-) diff --git a/lib/src/components/dropdown/dropdown.dart b/lib/src/components/dropdown/dropdown.dart index 73ff64c9..d1a1e8fc 100644 --- a/lib/src/components/dropdown/dropdown.dart +++ b/lib/src/components/dropdown/dropdown.dart @@ -3,6 +3,22 @@ import 'package:flutter/material.dart'; import '../../../zeta_flutter.dart'; +enum ZetaDropdownMenuType { + /// No Leading + none, + + /// Circular checkbox + checkbox, + + /// Square checkbox + radio +} + +enum ZetaDropdownSize { + standard, + mini, +} + class ZetaDropdownItem { ZetaDropdownItem({ String? label, @@ -26,8 +42,8 @@ class ZetaDropdown extends StatefulWidget { required this.onChange, required this.selectedItem, this.rounded = true, - this.leadingType = LeadingStyle.none, - this.isMinimized = false, + this.type = ZetaDropdownMenuType.none, + this.size = ZetaDropdownSize.standard, }); /// Input items as list of [_DropdownItem] @@ -43,10 +59,10 @@ class ZetaDropdown extends StatefulWidget { final bool rounded; /// The style for the leading widget. Can be a checkbox or radio button - final LeadingStyle leadingType; + final ZetaDropdownMenuType type; /// If menu is minimised. - final bool isMinimized; + final ZetaDropdownSize size; @override State> createState() => _ZetaDropDownState(); @@ -54,9 +70,8 @@ class ZetaDropdown extends StatefulWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(EnumProperty('leadingType', leadingType)) - ..add(DiagnosticsProperty('rounded', rounded)) - ..add(DiagnosticsProperty('isMinimized', isMinimized)); + ..add(EnumProperty('leadingType', type)) + ..add(DiagnosticsProperty('rounded', rounded)); } } @@ -64,9 +79,12 @@ class _ZetaDropDownState extends State> { final OverlayPortalController _tooltipController = OverlayPortalController(); final _link = LayerLink(); final _menuKey = GlobalKey(); // declare a global key + final _headerKey = GlobalKey(); ZetaDropdownItem? _selectedItem; + late final bool showLeading; + @override void initState() { super.initState(); @@ -75,6 +93,8 @@ class _ZetaDropDownState extends State> { } catch (e) { _selectedItem = null; } + showLeading = + widget.type != ZetaDropdownMenuType.none || widget.items.map((item) => item.icon != null).contains(true); } /// Returns if click event position is within the header. @@ -105,22 +125,24 @@ class _ZetaDropDownState extends State> { alignment: AlignmentDirectional.topStart, child: TapRegion( onTapOutside: (event) { - final headerBox = _menuKey.currentContext!.findRenderObject()! as RenderBox; + final headerBox = _headerKey.currentContext!.findRenderObject()! as RenderBox; final headerPosition = headerBox.localToGlobal(Offset.zero); - - if (!_isInHeader( + final inHeader = _isInHeader( headerPosition, headerBox.size, event.position, - )) _tooltipController.hide(); + ); + if (!inHeader) _tooltipController.hide(); }, child: _ZetaDropDownMenu( items: widget.items, selected: widget.selectedItem, rounded: widget.rounded, + showLeading: showLeading, width: _size, - boxType: widget.leadingType, + key: _menuKey, + menuType: widget.type, onSelected: (item) { setState(() { _selectedItem = item; @@ -133,13 +155,18 @@ class _ZetaDropDownState extends State> { ), ); }, - child: GestureDetector(onTap: onTap, child: Text(_selectedItem?.label ?? 'Select...')), + child: _DropdownItem( + onPress: onTap, + value: _selectedItem ?? widget.items.first, + showLeading: showLeading, + key: _headerKey, + ), ), ), ); } - double get _size => widget.isMinimized ? 120 : 320; + double get _size => widget.size == ZetaDropdownSize.mini ? 120 : 320; void onTap() { _tooltipController.toggle(); @@ -157,55 +184,42 @@ class _ZetaDropDownState extends State> { } } -/// Checkbox enum for different checkbox types -enum LeadingStyle { - /// No Leading - none, - - /// Circular checkbox - checkbox, - - /// Square checkbox - radio -} - /// Class for [_DropdownItem] class _DropdownItem extends StatefulWidget { ///Public constructor for [_DropdownItem] const _DropdownItem({ super.key, required this.value, - this.leadingIcon, + required this.showLeading, this.rounded = true, this.selected = false, - this.leadingType = LeadingStyle.none, - this.itemKey = null, - this.onPress = null, + this.menuType = ZetaDropdownMenuType.none, + this.itemKey, + this.onPress, }); /// {@macro zeta-component-rounded} final bool rounded; + final bool showLeading; + /// If [_DropdownItem] is selected final bool selected; /// Value of [_DropdownItem] final ZetaDropdownItem value; - /// Leading icon for [_DropdownItem] - final Icon? leadingIcon; - /// Handles clicking for [_DropdownItem] final VoidCallback? onPress; /// If checkbox is to be shown, the type of it. - final LeadingStyle? leadingType; + final ZetaDropdownMenuType menuType; /// Key for item final GlobalKey? itemKey; @override - State<_DropdownItem> createState() => _ZetaDropdownMenuItemState(); + State<_DropdownItem> createState() => _DropdownItemState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -213,7 +227,7 @@ class _DropdownItem extends StatefulWidget { ..add(DiagnosticsProperty('rounded', rounded)) ..add(DiagnosticsProperty('selected', selected)) ..add(ObjectFlagProperty.has('onPress', onPress)) - ..add(EnumProperty('leadingType', leadingType)) + ..add(EnumProperty('leadingType', menuType)) ..add( DiagnosticsProperty>?>( 'itemKey', @@ -223,7 +237,7 @@ class _DropdownItem extends StatefulWidget { } } -class _ZetaDropdownMenuItemState extends State<_DropdownItem> { +class _DropdownItemState extends State<_DropdownItem> { final controller = MaterialStatesController(); @override @@ -261,26 +275,26 @@ class _ZetaDropdownMenuItemState extends State<_DropdownItem> { } Widget _getLeadingWidget() { - switch (widget.leadingType!) { - case LeadingStyle.checkbox: + switch (widget.menuType) { + case ZetaDropdownMenuType.checkbox: return Checkbox( value: widget.selected, onChanged: (val) { widget.onPress!.call(); }, ); - case LeadingStyle.radio: + case ZetaDropdownMenuType.radio: return Radio( value: widget.selected, groupValue: true, onChanged: (val) { - widget.onPress!.call(); + widget.onPress?.call(); }, ); - case LeadingStyle.none: - return widget.leadingIcon ?? - const SizedBox( - width: 24, + case ZetaDropdownMenuType.none: + return widget.value.icon ?? + SizedBox( + width: widget.showLeading ? 24 : 0, ); } } @@ -288,17 +302,19 @@ class _ZetaDropdownMenuItemState extends State<_DropdownItem> { ButtonStyle _getStyle(ZetaColors colors) { return ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.disabled)) { + return colors.surfaceDisabled; + } + if (widget.selected) { + return colors.surfaceSelected; + } if (states.contains(MaterialState.hovered)) { return colors.surfaceHovered; } - if (states.contains(MaterialState.pressed)) { return colors.surfaceSelected; } - if (states.contains(MaterialState.disabled) || widget.onPress == null) { - return colors.surfaceDisabled; - } return colors.surfacePrimary; }), foregroundColor: MaterialStateProperty.resolveWith((states) { @@ -312,9 +328,7 @@ class _ZetaDropdownMenuItemState extends State<_DropdownItem> { borderRadius: widget.rounded ? ZetaRadius.minimal : ZetaRadius.none, ), ), - side: MaterialStatePropertyAll( - widget.selected ? BorderSide(color: colors.primary.shade60) : BorderSide.none, - ), + side: const MaterialStatePropertyAll(BorderSide.none), padding: const MaterialStatePropertyAll(EdgeInsets.zero), elevation: const MaterialStatePropertyAll(0), overlayColor: const MaterialStatePropertyAll(Colors.transparent), @@ -339,9 +353,11 @@ class _ZetaDropDownMenu extends StatefulWidget { required this.items, required this.onSelected, required this.selected, + required this.showLeading, this.rounded = false, this.width, - this.boxType, + this.menuType = ZetaDropdownMenuType.none, + super.key, }); /// Input items for the menu @@ -350,6 +366,8 @@ class _ZetaDropDownMenu extends StatefulWidget { ///Handles clicking of item in menu final ValueSetter> onSelected; + final bool showLeading; + final T? selected; /// {@macro zeta-component-rounded} @@ -359,7 +377,7 @@ class _ZetaDropDownMenu extends StatefulWidget { final double? width; /// If items have checkboxes, the type of that checkbox. - final LeadingStyle? boxType; + final ZetaDropdownMenuType menuType; @override State<_ZetaDropDownMenu> createState() => _ZetaDropDownMenuState(); @@ -369,7 +387,7 @@ class _ZetaDropDownMenu extends StatefulWidget { properties ..add(DiagnosticsProperty('rounded', rounded)) ..add(DoubleProperty('width', width)) - ..add(EnumProperty('boxType', boxType)); + ..add(EnumProperty('boxType', menuType)); } } @@ -378,6 +396,7 @@ class _ZetaDropDownMenuState extends State<_ZetaDropDownMenu> { Widget build(BuildContext context) { final colors = Zeta.of(context).colors; return Container( + padding: const EdgeInsets.all(ZetaSpacing.x3), decoration: BoxDecoration( color: colors.surfacePrimary, borderRadius: widget.rounded ? ZetaRadius.minimal : ZetaRadius.none, @@ -396,18 +415,18 @@ class _ZetaDropDownMenuState extends State<_ZetaDropDownMenu> { builder: (BuildContext bcontext) { return Column( mainAxisSize: MainAxisSize.min, - children: widget.items.map((item) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - _DropdownItem( + children: widget.items + .map((item) { + return _DropdownItem( value: item, onPress: () => widget.onSelected(item), - ), - const SizedBox(height: ZetaSpacing.x1), - ], - ); - }).toList(), + selected: item.value == widget.selected, + showLeading: widget.showLeading, + menuType: widget.menuType, + ); + }) + .divide(const SizedBox(height: ZetaSpacing.x1)) + .toList(), ); }, ), From 4f79b85ca0099d171c0e571b2694195c27a20a84 Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Tue, 16 Apr 2024 13:32:18 +0100 Subject: [PATCH 3/6] docs, leading space --- lib/src/components/dropdown/dropdown.dart | 119 ++++++++++++++-------- 1 file changed, 78 insertions(+), 41 deletions(-) diff --git a/lib/src/components/dropdown/dropdown.dart b/lib/src/components/dropdown/dropdown.dart index d1a1e8fc..9bbf2efa 100644 --- a/lib/src/components/dropdown/dropdown.dart +++ b/lib/src/components/dropdown/dropdown.dart @@ -3,23 +3,30 @@ import 'package:flutter/material.dart'; import '../../../zeta_flutter.dart'; +/// Sets the type of a [ZetaDropdown] enum ZetaDropdownMenuType { - /// No Leading - none, + /// No leading elements before each item unless an icon is given to the [ZetaDropdownItem] + standard, - /// Circular checkbox + /// Displays a [ZetaCheckbox] before each item. checkbox, - /// Square checkbox + /// Displays a [ZetaRadio] before each item. radio } +/// Used to set the size of a [ZetaDropdown] enum ZetaDropdownSize { + /// Initial width of 320dp. standard, + + /// Initial width of 120dp. mini, } +/// An item used in a [ZetaDropdown]. class ZetaDropdownItem { + /// Creates a new [ZetaDropdownItem] ZetaDropdownItem({ String? label, required this.value, @@ -28,40 +35,51 @@ class ZetaDropdownItem { this.label = label ?? this.value.toString(); } + /// The label for the item. late final String label; + + /// The value of the item. final T value; + + /// The icon shown next to the dropdown item. + /// + /// Will not be shown if the type of [ZetaDropdown] is set to anything other than [ZetaDropdownMenuType.standard] final Widget? icon; } /// Class for [ZetaDropdown] class ZetaDropdown extends StatefulWidget { - ///Constructor of [ZetaDropdown] + /// Creates a new [ZetaDropdown]. const ZetaDropdown({ - super.key, required this.items, - required this.onChange, + this.onChange, required this.selectedItem, this.rounded = true, - this.type = ZetaDropdownMenuType.none, + this.type = ZetaDropdownMenuType.standard, this.size = ZetaDropdownSize.standard, + super.key, }); - /// Input items as list of [_DropdownItem] + /// The items displayed in the dropdown. final List> items; - /// Currently selected item + /// The currently selected item. final T? selectedItem; - /// Handles changes of dropdown menu - final ValueSetter onChange; + /// Called with the selected value whenever the dropdown is changed. + final ValueSetter? onChange; /// {@macro zeta-component-rounded} final bool rounded; - /// The style for the leading widget. Can be a checkbox or radio button + /// The type of the dropdown menu. + /// + /// Defaults to [ZetaDropdownMenuType.standard] final ZetaDropdownMenuType type; - /// If menu is minimised. + /// The size of the dropdown menu. + /// + /// Defaults to [ZetaDropdownSize.mini] final ZetaDropdownSize size; @override @@ -71,19 +89,25 @@ class ZetaDropdown extends StatefulWidget { super.debugFillProperties(properties); properties ..add(EnumProperty('leadingType', type)) - ..add(DiagnosticsProperty('rounded', rounded)); + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(IterableProperty>('items', items)) + ..add(DiagnosticsProperty('selectedItem', selectedItem)) + ..add(ObjectFlagProperty?>.has('onChange', onChange)) + ..add(EnumProperty('size', size)); } } class _ZetaDropDownState extends State> { final OverlayPortalController _tooltipController = OverlayPortalController(); final _link = LayerLink(); - final _menuKey = GlobalKey(); // declare a global key + final _menuKey = GlobalKey(); final _headerKey = GlobalKey(); ZetaDropdownItem? _selectedItem; - late final bool showLeading; + bool get _allocateLeadingSpace { + return widget.type != ZetaDropdownMenuType.standard || widget.items.map((item) => item.icon != null).contains(true); + } @override void initState() { @@ -93,11 +117,9 @@ class _ZetaDropDownState extends State> { } catch (e) { _selectedItem = null; } - showLeading = - widget.type != ZetaDropdownMenuType.none || widget.items.map((item) => item.icon != null).contains(true); } - /// Returns if click event position is within the header. + // Returns if click event position is within the header. bool _isInHeader( Offset headerPosition, Size headerSize, @@ -139,7 +161,7 @@ class _ZetaDropDownState extends State> { items: widget.items, selected: widget.selectedItem, rounded: widget.rounded, - showLeading: showLeading, + allocateLeadingSpace: _allocateLeadingSpace, width: _size, key: _menuKey, menuType: widget.type, @@ -147,7 +169,7 @@ class _ZetaDropDownState extends State> { setState(() { _selectedItem = item; }); - widget.onChange(item.value); + widget.onChange?.call(item.value); _tooltipController.hide(); }, ), @@ -158,7 +180,7 @@ class _ZetaDropDownState extends State> { child: _DropdownItem( onPress: onTap, value: _selectedItem ?? widget.items.first, - showLeading: showLeading, + allocateLeadingSpace: widget.type != ZetaDropdownMenuType.standard || _selectedItem?.icon != null, key: _headerKey, ), ), @@ -190,10 +212,10 @@ class _DropdownItem extends StatefulWidget { const _DropdownItem({ super.key, required this.value, - required this.showLeading, + required this.allocateLeadingSpace, this.rounded = true, this.selected = false, - this.menuType = ZetaDropdownMenuType.none, + this.menuType = ZetaDropdownMenuType.standard, this.itemKey, this.onPress, }); @@ -201,7 +223,7 @@ class _DropdownItem extends StatefulWidget { /// {@macro zeta-component-rounded} final bool rounded; - final bool showLeading; + final bool allocateLeadingSpace; /// If [_DropdownItem] is selected final bool selected; @@ -233,7 +255,9 @@ class _DropdownItem extends StatefulWidget { 'itemKey', itemKey, ), - ); + ) + ..add(DiagnosticsProperty('allocateLeadingSpace', allocateLeadingSpace)) + ..add(DiagnosticsProperty>('value', value)); } } @@ -254,6 +278,15 @@ class _DropdownItemState extends State<_DropdownItem> { Widget build(BuildContext context) { final colors = Zeta.of(context).colors; + Widget? leading = _getLeadingWidget(); + + if (leading != null) { + leading = Padding( + padding: const EdgeInsets.only(right: ZetaSpacing.x3), + child: leading, + ); + } + return DefaultTextStyle( style: ZetaTextStyles.bodyMedium, child: OutlinedButton( @@ -263,8 +296,7 @@ class _DropdownItemState extends State<_DropdownItem> { child: Row( children: [ const SizedBox(width: ZetaSpacing.x3), - _getLeadingWidget(), - const SizedBox(width: ZetaSpacing.x3), + if (leading != null) leading, Text( widget.value.label, ), @@ -274,7 +306,8 @@ class _DropdownItemState extends State<_DropdownItem> { ); } - Widget _getLeadingWidget() { + Widget? _getLeadingWidget() { + if (!widget.allocateLeadingSpace) return null; switch (widget.menuType) { case ZetaDropdownMenuType.checkbox: return Checkbox( @@ -291,10 +324,10 @@ class _DropdownItemState extends State<_DropdownItem> { widget.onPress?.call(); }, ); - case ZetaDropdownMenuType.none: + case ZetaDropdownMenuType.standard: return widget.value.icon ?? - SizedBox( - width: widget.showLeading ? 24 : 0, + const SizedBox( + width: 24, ); } } @@ -308,12 +341,12 @@ class _DropdownItemState extends State<_DropdownItem> { if (widget.selected) { return colors.surfaceSelected; } - if (states.contains(MaterialState.hovered)) { - return colors.surfaceHovered; - } if (states.contains(MaterialState.pressed)) { return colors.surfaceSelected; } + if (states.contains(MaterialState.hovered)) { + return colors.surfaceHovered; + } return colors.surfacePrimary; }), @@ -353,10 +386,10 @@ class _ZetaDropDownMenu extends StatefulWidget { required this.items, required this.onSelected, required this.selected, - required this.showLeading, + required this.allocateLeadingSpace, this.rounded = false, this.width, - this.menuType = ZetaDropdownMenuType.none, + this.menuType = ZetaDropdownMenuType.standard, super.key, }); @@ -366,7 +399,7 @@ class _ZetaDropDownMenu extends StatefulWidget { ///Handles clicking of item in menu final ValueSetter> onSelected; - final bool showLeading; + final bool allocateLeadingSpace; final T? selected; @@ -387,7 +420,11 @@ class _ZetaDropDownMenu extends StatefulWidget { properties ..add(DiagnosticsProperty('rounded', rounded)) ..add(DoubleProperty('width', width)) - ..add(EnumProperty('boxType', menuType)); + ..add(EnumProperty('boxType', menuType)) + ..add(IterableProperty>('items', items)) + ..add(ObjectFlagProperty>>.has('onSelected', onSelected)) + ..add(DiagnosticsProperty('allocateLeadingSpace', allocateLeadingSpace)) + ..add(DiagnosticsProperty('selected', selected)); } } @@ -421,7 +458,7 @@ class _ZetaDropDownMenuState extends State<_ZetaDropDownMenu> { value: item, onPress: () => widget.onSelected(item), selected: item.value == widget.selected, - showLeading: widget.showLeading, + allocateLeadingSpace: widget.allocateLeadingSpace, menuType: widget.menuType, ); }) From 083a7e8a10b5b3fd700d288257fe70ebedf7c5c7 Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Tue, 16 Apr 2024 13:58:10 +0100 Subject: [PATCH 4/6] adding disabled --- lib/src/components/dropdown/dropdown.dart | 35 ++++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/src/components/dropdown/dropdown.dart b/lib/src/components/dropdown/dropdown.dart index 9bbf2efa..dd063c07 100644 --- a/lib/src/components/dropdown/dropdown.dart +++ b/lib/src/components/dropdown/dropdown.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -53,8 +55,9 @@ class ZetaDropdown extends StatefulWidget { const ZetaDropdown({ required this.items, this.onChange, - required this.selectedItem, + this.selectedItem, this.rounded = true, + this.disabled = false, this.type = ZetaDropdownMenuType.standard, this.size = ZetaDropdownSize.standard, super.key, @@ -72,6 +75,9 @@ class ZetaDropdown extends StatefulWidget { /// {@macro zeta-component-rounded} final bool rounded; + /// Disables the dropdown. + final bool disabled; + /// The type of the dropdown menu. /// /// Defaults to [ZetaDropdownMenuType.standard] @@ -112,6 +118,25 @@ class _ZetaDropDownState extends State> { @override void initState() { super.initState(); + _setSelectedItem(); + } + + @override + void didUpdateWidget(ZetaDropdown oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.selectedItem != widget.selectedItem) { + setState(_setSelectedItem); + } + if (widget.disabled) { + unawaited( + Future.delayed(Duration.zero).then( + (value) => _tooltipController.hide(), + ), + ); + } + } + + void _setSelectedItem() { try { _selectedItem = widget.items.firstWhere((item) => item.value == widget.selectedItem); } catch (e) { @@ -178,9 +203,10 @@ class _ZetaDropDownState extends State> { ); }, child: _DropdownItem( - onPress: onTap, + onPress: !widget.disabled ? onTap : null, value: _selectedItem ?? widget.items.first, - allocateLeadingSpace: widget.type != ZetaDropdownMenuType.standard || _selectedItem?.icon != null, + allocateLeadingSpace: widget.type == ZetaDropdownMenuType.standard && _selectedItem?.icon != null, + rounded: widget.rounded, key: _headerKey, ), ), @@ -213,7 +239,7 @@ class _DropdownItem extends StatefulWidget { super.key, required this.value, required this.allocateLeadingSpace, - this.rounded = true, + required this.rounded, this.selected = false, this.menuType = ZetaDropdownMenuType.standard, this.itemKey, @@ -460,6 +486,7 @@ class _ZetaDropDownMenuState extends State<_ZetaDropDownMenu> { selected: item.value == widget.selected, allocateLeadingSpace: widget.allocateLeadingSpace, menuType: widget.menuType, + rounded: widget.rounded, ); }) .divide(const SizedBox(height: ZetaSpacing.x1)) From d7b101c4b164b2a6bb0a9f68dc10eacc76df18fb Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Tue, 16 Apr 2024 14:11:43 +0100 Subject: [PATCH 5/6] Fixing text overflow and widgetbook --- .../pages/components/dropdown_example.dart | 62 ++++++++++++------- .../pages/components/dropdown_widgetbook.dart | 58 +++++++---------- lib/src/components/dropdown/dropdown.dart | 49 ++++++++++----- 3 files changed, 95 insertions(+), 74 deletions(-) diff --git a/example/lib/pages/components/dropdown_example.dart b/example/lib/pages/components/dropdown_example.dart index 0e30b104..4a35a131 100644 --- a/example/lib/pages/components/dropdown_example.dart +++ b/example/lib/pages/components/dropdown_example.dart @@ -15,38 +15,58 @@ class _DropdownExampleState extends State { @override Widget build(BuildContext context) { + final items = [ + ZetaDropdownItem( + value: "Item 1", + icon: Icon(ZetaIcons.star_round), + ), + ZetaDropdownItem( + value: "Item 2", + icon: Icon(ZetaIcons.star_half_round), + ), + ZetaDropdownItem( + value: "Item 3", + ) + ]; + return ExampleScaffold( name: "Dropdown", - child: Center( - child: SingleChildScrollView( - child: SizedBox( - width: 320, - child: Column(children: [ + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ ZetaDropdown( - leadingType: LeadingStyle.checkbox, + disabled: true, + type: ZetaDropdownMenuType.standard, onChange: (value) { setState(() { selectedItem = value; }); }, selectedItem: selectedItem, - items: [ - ZetaDropdownItem( - value: "Item 1", - icon: Icon(ZetaIcons.star_round), - ), - ZetaDropdownItem( - value: "Item 2", - icon: Icon(ZetaIcons.star_half_round), - ), - ZetaDropdownItem( - value: "Item 3", - ) - ], + items: items, ), Text('Selected item : ${selectedItem}') - ])), - ), + ], + ), + ), + ZetaDropdown( + items: items, + selectedItem: selectedItem, + type: ZetaDropdownMenuType.checkbox, + ), + ZetaDropdown( + items: items, + selectedItem: selectedItem, + size: ZetaDropdownSize.mini, + type: ZetaDropdownMenuType.radio, + ), + ], ), ); } diff --git a/example/widgetbook/pages/components/dropdown_widgetbook.dart b/example/widgetbook/pages/components/dropdown_widgetbook.dart index 73308f16..053c277a 100644 --- a/example/widgetbook/pages/components/dropdown_widgetbook.dart +++ b/example/widgetbook/pages/components/dropdown_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 dropdownUseCase(BuildContext context) => WidgetbookTestWidget( widget: Center( @@ -19,52 +20,37 @@ class DropdownExample extends StatefulWidget { } class _DropdownExampleState extends State { - List<_DropdownItem> _children = [ - _DropdownItem( + final items = [ + ZetaDropdownItem( value: "Item 1", - leadingIcon: Icon(ZetaIcons.star_round), + icon: Icon(ZetaIcons.star_round), ), - _DropdownItem( + ZetaDropdownItem( value: "Item 2", - leadingIcon: Icon(ZetaIcons.star_half_round), + icon: Icon(ZetaIcons.star_half_round), ), - _DropdownItem( + ZetaDropdownItem( value: "Item 3", ) ]; - late _DropdownItem selectedItem = _DropdownItem( - value: "Item 1", - leadingIcon: Icon(ZetaIcons.star_round), - ); - @override Widget build(BuildContext _) { - return SingleChildScrollView( - child: SizedBox( - width: double.infinity, - child: Column(children: [ - ZetaDropdown( - leadingType: widget.c.knobs.list( - label: "Checkbox type", - options: [ - LeadingStyle.none, - LeadingStyle.checkbox, - LeadingStyle.radio, - ], - ), - onChange: (value) { - setState(() { - selectedItem = value; - }); - }, - selectedItem: selectedItem, - items: _children, - rounded: widget.c.knobs.boolean(label: "Rounded"), - isMinimized: widget.c.knobs.boolean(label: "Minimized"), - ), - Text('Selected item : ${selectedItem.value}') - ])), + return ZetaDropdown( + type: widget.c.knobs.list( + label: "Dropdown type", + options: ZetaDropdownMenuType.values, + labelBuilder: enumLabelBuilder, + ), + onChange: (value) {}, + items: items, + rounded: widget.c.knobs.boolean(label: "Rounded"), + disabled: widget.c.knobs.boolean(label: "Disabled"), + size: widget.c.knobs.list( + label: 'Size', + options: ZetaDropdownSize.values, + labelBuilder: enumLabelBuilder, + ), ); } } diff --git a/lib/src/components/dropdown/dropdown.dart b/lib/src/components/dropdown/dropdown.dart index dd063c07..bc166ce0 100644 --- a/lib/src/components/dropdown/dropdown.dart +++ b/lib/src/components/dropdown/dropdown.dart @@ -99,7 +99,8 @@ class ZetaDropdown extends StatefulWidget { ..add(IterableProperty>('items', items)) ..add(DiagnosticsProperty('selectedItem', selectedItem)) ..add(ObjectFlagProperty?>.has('onChange', onChange)) - ..add(EnumProperty('size', size)); + ..add(EnumProperty('size', size)) + ..add(DiagnosticsProperty('disabled', disabled)); } } @@ -184,7 +185,7 @@ class _ZetaDropDownState extends State> { }, child: _ZetaDropDownMenu( items: widget.items, - selected: widget.selectedItem, + selected: _selectedItem?.value, rounded: widget.rounded, allocateLeadingSpace: _allocateLeadingSpace, width: _size, @@ -313,21 +314,35 @@ class _DropdownItemState extends State<_DropdownItem> { ); } - return DefaultTextStyle( - style: ZetaTextStyles.bodyMedium, - child: OutlinedButton( - key: widget.itemKey, - onPressed: widget.onPress, - style: _getStyle(colors), - child: Row( - children: [ - const SizedBox(width: ZetaSpacing.x3), - if (leading != null) leading, - Text( - widget.value.label, - ), - ], - ).paddingVertical(ZetaSpacing.x2_5), + return ConstrainedBox( + constraints: const BoxConstraints(maxHeight: ZetaSpacing.x10), + child: DefaultTextStyle( + style: ZetaTextStyles.bodyMedium, + child: OutlinedButton( + key: widget.itemKey, + onPressed: widget.onPress, + style: _getStyle(colors), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: ZetaSpacing.x3), + if (leading != null) leading, + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: ZetaSpacing.x2), + child: Align( + alignment: Alignment.centerLeft, + child: FittedBox( + child: Text( + widget.value.label, + ), + ), + ), + ), + ), + ], + ).paddingVertical(ZetaSpacing.x2_5), + ), ), ); } From 25f216c3174f32606f77982136de0fa8e4ad517a Mon Sep 17 00:00:00 2001 From: mikecoomber Date: Tue, 16 Apr 2024 14:14:40 +0100 Subject: [PATCH 6/6] assert --- .../lib/pages/components/dropdown_example.dart | 6 +++--- lib/src/components/dropdown/dropdown.dart | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/example/lib/pages/components/dropdown_example.dart b/example/lib/pages/components/dropdown_example.dart index 4a35a131..2fb0ee93 100644 --- a/example/lib/pages/components/dropdown_example.dart +++ b/example/lib/pages/components/dropdown_example.dart @@ -48,7 +48,7 @@ class _DropdownExampleState extends State { selectedItem = value; }); }, - selectedItem: selectedItem, + value: selectedItem, items: items, ), Text('Selected item : ${selectedItem}') @@ -57,12 +57,12 @@ class _DropdownExampleState extends State { ), ZetaDropdown( items: items, - selectedItem: selectedItem, + value: selectedItem, type: ZetaDropdownMenuType.checkbox, ), ZetaDropdown( items: items, - selectedItem: selectedItem, + value: selectedItem, size: ZetaDropdownSize.mini, type: ZetaDropdownMenuType.radio, ), diff --git a/lib/src/components/dropdown/dropdown.dart b/lib/src/components/dropdown/dropdown.dart index bc166ce0..b88036c5 100644 --- a/lib/src/components/dropdown/dropdown.dart +++ b/lib/src/components/dropdown/dropdown.dart @@ -55,19 +55,21 @@ class ZetaDropdown extends StatefulWidget { const ZetaDropdown({ required this.items, this.onChange, - this.selectedItem, + this.value, this.rounded = true, this.disabled = false, this.type = ZetaDropdownMenuType.standard, this.size = ZetaDropdownSize.standard, super.key, - }); + }) : assert(items.length > 0, 'Items must be greater than 0.'); /// The items displayed in the dropdown. final List> items; - /// The currently selected item. - final T? selectedItem; + /// The value of the selected item. + /// + /// If no [ZetaDropdownItem] in [items] has a matching value, the first item in [items] will be set as the selected item. + final T? value; /// Called with the selected value whenever the dropdown is changed. final ValueSetter? onChange; @@ -97,7 +99,7 @@ class ZetaDropdown extends StatefulWidget { ..add(EnumProperty('leadingType', type)) ..add(DiagnosticsProperty('rounded', rounded)) ..add(IterableProperty>('items', items)) - ..add(DiagnosticsProperty('selectedItem', selectedItem)) + ..add(DiagnosticsProperty('selectedItem', value)) ..add(ObjectFlagProperty?>.has('onChange', onChange)) ..add(EnumProperty('size', size)) ..add(DiagnosticsProperty('disabled', disabled)); @@ -125,7 +127,7 @@ class _ZetaDropDownState extends State> { @override void didUpdateWidget(ZetaDropdown oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.selectedItem != widget.selectedItem) { + if (oldWidget.value != widget.value) { setState(_setSelectedItem); } if (widget.disabled) { @@ -139,9 +141,9 @@ class _ZetaDropDownState extends State> { void _setSelectedItem() { try { - _selectedItem = widget.items.firstWhere((item) => item.value == widget.selectedItem); + _selectedItem = widget.items.firstWhere((item) => item.value == widget.value); } catch (e) { - _selectedItem = null; + _selectedItem = widget.items.first; } }