diff --git a/example/lib/home.dart b/example/lib/home.dart index c6bb826b..4f43ba7b 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -17,6 +17,7 @@ import 'package:zeta_example/pages/components/dialpad_example.dart'; import 'package:zeta_example/pages/components/dropdown_example.dart'; import 'package:zeta_example/pages/components/global_header_example.dart'; import 'package:zeta_example/pages/components/filter_selection_example.dart'; +import 'package:zeta_example/pages/components/list_example.dart'; import 'package:zeta_example/pages/components/list_item_example.dart'; import 'package:zeta_example/pages/components/navigation_bar_example.dart'; import 'package:zeta_example/pages/components/navigation_rail_example.dart'; @@ -67,6 +68,7 @@ final List components = [ Component(CheckBoxExample.name, (context) => const CheckBoxExample()), Component(ChipExample.name, (context) => const ChipExample()), Component(ContactItemExample.name, (context) => const ContactItemExample()), + Component(ListExample.name, (context) => const ListExample()), Component(ListItemExample.name, (context) => const ListItemExample()), Component(NavigationBarExample.name, (context) => const NavigationBarExample()), Component(NotificationListItemExample.name, (context) => const NotificationListItemExample()), diff --git a/example/lib/pages/components/list_example.dart b/example/lib/pages/components/list_example.dart new file mode 100644 index 00000000..34133ea2 --- /dev/null +++ b/example/lib/pages/components/list_example.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class ListExample extends StatelessWidget { + static const name = 'List'; + + const ListExample({super.key}); + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: 'List', + child: ZetaList( + showDivider: false, + items: [ + ZetaListItem(primaryText: 'Item 1'), + ZetaListItem(primaryText: 'Item 2'), + ZetaListItem(primaryText: 'Item 3', showDivider: true), + ZetaListItem(primaryText: 'Item 4', showDivider: true), + ZetaListItem(primaryText: 'Item 5'), + ZetaListItem(primaryText: 'Item 6'), + ], + ), + ); + } +} diff --git a/example/lib/pages/components/list_item_example.dart b/example/lib/pages/components/list_item_example.dart index 54831b0d..72b2ebd9 100644 --- a/example/lib/pages/components/list_item_example.dart +++ b/example/lib/pages/components/list_item_example.dart @@ -12,12 +12,12 @@ class ListItemExample extends StatefulWidget { } class _ListItemExampleState extends State { - bool _isCheckBoxEnabled = false; - bool _isSelected = true; + bool _switchChecked = false; + bool _checkboxChecked = false; - _onDefaultListItemTap() { - setState(() => _isCheckBoxEnabled = !_isCheckBoxEnabled); - } + String radioOption1 = 'Label 1'; + String radioOption2 = 'Label 2'; + String? radioGroupValue; @override Widget build(BuildContext context) { @@ -28,88 +28,88 @@ class _ListItemExampleState extends State { child: Container( color: zetaColors.surfaceSecondary, child: SingleChildScrollView( - child: Column( - children: [ - // List Item with descriptor - Padding( - padding: const EdgeInsets.only(top: ZetaSpacing.large), - child: ZetaListItem( - dense: true, - leading: Container( - width: 48, - height: 48, - decoration: BoxDecoration(borderRadius: ZetaRadius.rounded), - child: Placeholder(), - ), - subtitle: Text("Descriptor"), - title: Text("List Item"), - trailing: ZetaCheckbox( - value: _isCheckBoxEnabled, - onChanged: (_) => _onDefaultListItemTap(), - ), - onTap: _onDefaultListItemTap, - ), - ), - - // Enabled - Padding( - padding: const EdgeInsets.only(top: ZetaSpacing.xl_4), - child: Text( - "Enabled", - style: ZetaTextStyles.titleLarge, - ), - ), - Padding( - padding: const EdgeInsets.only(top: ZetaSpacing.xl_2), - child: ZetaListItem(title: Text("List Item")), - ), - - // Selected - Padding( - padding: const EdgeInsets.only(top: ZetaSpacing.xl_4), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "Selected", - style: ZetaTextStyles.titleLarge, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: ZetaSpacing.xl_2), - child: ZetaListItem( - title: Text("List Item"), - selected: _isSelected, - trailing: _isSelected - ? Icon( - ZetaIcons.check_mark_sharp, - color: zetaColors.primary, - ) - : null, - onTap: () => setState(() => _isSelected = !_isSelected), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + _buildListItem( + 'No Icon', + ZetaListItem( + primaryText: 'List Item', + secondaryText: 'Descriptor', + )), + _buildListItem( + 'Icon Left', + ZetaListItem(primaryText: 'List Item', leading: Icon(ZetaIcons.star_round)), ), - ), - - // Disabled - Padding( - padding: const EdgeInsets.only(top: ZetaSpacing.xl_4), - child: Text( - "Disabled", - style: ZetaTextStyles.titleLarge, - ), - ), - Padding( - padding: const EdgeInsets.only(top: ZetaSpacing.xl_2), - child: ZetaListItem( - title: Text("List Item"), - enabled: false, - onTap: () {}, - ), - ), - ], + _buildListItem( + 'Toggle Right', + ZetaListItem.toggle( + primaryText: 'List Item', + value: _switchChecked, + onChanged: (value) { + setState(() { + _switchChecked = value!; + }); + }, + )), + _buildListItem( + 'Checkbox Right', + ZetaListItem.checkbox( + primaryText: 'List Item', + value: _checkboxChecked, + onChanged: (value) { + print(value); + setState(() { + _checkboxChecked = value; + }); + }, + )), + _buildListItem( + 'Radio Right', + Column( + children: [ + ZetaListItem.radio( + primaryText: 'Radio option 1', + value: radioOption1, + groupValue: radioGroupValue, + onChanged: (value) { + setState(() { + radioGroupValue = value; + }); + }, + ), + ZetaListItem.radio( + primaryText: 'Radio option 2', + value: radioOption2, + groupValue: radioGroupValue, + onChanged: (value) { + setState(() { + radioGroupValue = value; + }); + }, + ), + ], + )), + ].divide(const SizedBox(height: 16)).toList(), + ), ), ), ), ); } } + +Widget _buildListItem(String name, Widget listItem) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: ZetaTextStyles.bodyLarge, + ), + const SizedBox(height: 8), + listItem, + ], + ); +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3e3314fb..d0960103 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -30,4 +30,3 @@ flutter: uses-material-design: true assets: - assets/ - - ../README.md diff --git a/example/widgetbook/main.dart b/example/widgetbook/main.dart index ef12e236..1bc81326 100644 --- a/example/widgetbook/main.dart +++ b/example/widgetbook/main.dart @@ -1,7 +1,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:widgetbook/widgetbook.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -56,9 +55,9 @@ import 'utils/zebra.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - String readme = await rootBundle.loadString('../README.md'); + // String readme = await rootBundle.loadString('../README.md'); - runApp(HotReload(readme: readme)); + runApp(HotReload(readme: 'TODO: cannot import readme on windows')); } class HotReload extends StatefulWidget { @@ -276,7 +275,6 @@ class _HotReloadState extends State { ); }, ), - AccessibilityAddon(), InspectorAddon(enabled: false), ZoomAddon(initialZoom: 1.0), TextScaleAddon(scales: [1.0, 1.2, 1.4, 1.6, 1.8, 2.0], initialScale: 1), diff --git a/example/widgetbook/pages/components/list_item_widgetbook.dart b/example/widgetbook/pages/components/list_item_widgetbook.dart index 55086b2d..2acbbbee 100644 --- a/example/widgetbook/pages/components/list_item_widgetbook.dart +++ b/example/widgetbook/pages/components/list_item_widgetbook.dart @@ -3,42 +3,72 @@ import 'package:widgetbook/widgetbook.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; import '../../test/test_components.dart'; +import '../../utils/utils.dart'; Widget listItemUseCase(BuildContext context) { + bool checkedValue = false; + return WidgetbookTestWidget( widget: StatefulBuilder( builder: (context, setState) { - final subtitle = context.knobs.stringOrNull(label: 'Descriptor', initialValue: null); - - final trailing = context.knobs.boolean(label: 'Trailing', initialValue: false) - ? Checkbox(value: false, onChanged: (_) {}) - : null; - - final leading = context.knobs.boolean(label: 'Leading', initialValue: false) - ? Container( - width: ZetaSpacing.xl_8, - height: ZetaSpacing.xl_8, - decoration: BoxDecoration(borderRadius: ZetaRadius.rounded), - child: Placeholder(), - ) - : null; - - return ZetaListItem( - dense: context.knobs.boolean(label: 'Dense', initialValue: false), - enabled: context.knobs.boolean(label: 'Enabled', initialValue: true), - enabledDivider: context.knobs.boolean( - label: 'Enabled Divider', - initialValue: true, - ), - selected: context.knobs.boolean(label: 'Selected', initialValue: true), - leading: leading, - title: Text( - context.knobs.string(label: 'Title', initialValue: 'List Item'), - ), - subtitle: subtitle != null ? Text(subtitle) : null, - trailing: trailing, - onTap: () {}, + final type = context.knobs.list( + label: 'Type', + options: ['Default', 'Checkbox', 'Radio button', 'Switch'], ); + + final primaryText = context.knobs.string(label: 'Primary text', initialValue: 'Label'); + + final secondaryText = context.knobs.string(label: 'Secondary text', initialValue: 'Descriptor'); + + final showIcon = context.knobs.boolean(label: 'Show icon'); + + final showDivider = context.knobs.boolean(label: 'Show divider'); + + final rounded = roundedKnob(context); + + final leading = showIcon ? Icon(ZetaIcons.star_round) : null; + + final onChanged = (bool? value) => setState(() { + checkedValue = value!; + }); + + if (type == 'Checkbox') { + return ZetaListItem.checkbox( + primaryText: primaryText, + secondaryText: secondaryText, + leading: leading, + showDivider: showDivider, + onChanged: onChanged, + value: checkedValue, + rounded: rounded, + ); + } else if (type == 'Switch') { + return ZetaListItem.toggle( + primaryText: primaryText, + secondaryText: secondaryText, + leading: leading, + showDivider: showDivider, + onChanged: onChanged, + value: checkedValue, + ); + } else if (type == 'Radio button') { + return ZetaListItem.radio( + primaryText: primaryText, + secondaryText: secondaryText, + leading: leading, + showDivider: showDivider, + onChanged: (_) {}, + value: 'value', + groupValue: 'value', + ); + } else { + return ZetaListItem( + primaryText: primaryText, + secondaryText: secondaryText, + leading: leading, + showDivider: showDivider, + ); + } }, ), ); diff --git a/lib/src/components/checkbox/checkbox.dart b/lib/src/components/checkbox/checkbox.dart index 794b3241..dcbca898 100644 --- a/lib/src/components/checkbox/checkbox.dart +++ b/lib/src/components/checkbox/checkbox.dart @@ -145,22 +145,29 @@ class _CheckboxState extends State<_Checkbox> { @override Widget build(BuildContext context) { - return Semantics( - mixed: widget.useIndeterminate, - enabled: !widget.disabled, - child: MouseRegion( - cursor: !widget.disabled ? SystemMouseCursors.click : SystemMouseCursors.forbidden, - onEnter: (event) => _setHovered(true), - onExit: (event) => _setHovered(false), - child: !widget.disabled - ? FocusableActionDetector( - onFocusChange: (bool focus) => setState(() => _isFocused = focus), - child: GestureDetector( - onTap: !widget.disabled ? () => widget.onChanged.call(!_checked) : null, - child: _buildContent(context), - ), - ) - : _buildContent(context), + return Material( + color: Colors.transparent, + child: InkWell( + onTap: !widget.disabled ? () => widget.onChanged.call(!_checked) : null, + borderRadius: ZetaRadius.full, + child: Padding( + padding: const EdgeInsets.all(ZetaSpacing.small), + child: Semantics( + mixed: widget.useIndeterminate, + enabled: !widget.disabled, + child: MouseRegion( + cursor: !widget.disabled ? SystemMouseCursors.click : SystemMouseCursors.forbidden, + onEnter: (event) => _setHovered(true), + onExit: (event) => _setHovered(false), + child: !widget.disabled + ? FocusableActionDetector( + onFocusChange: (bool focus) => setState(() => _isFocused = focus), + child: _buildContent(context), + ) + : _buildContent(context), + ), + ), + ), ), ); } @@ -205,14 +212,13 @@ class _CheckboxState extends State<_Checkbox> { height: ZetaSpacing.xl_1, child: icon, ), - if (widget.label != null) ...[ + if (widget.label != null) Flexible( child: Padding( padding: const EdgeInsets.only(left: ZetaSpacing.medium), child: Text(widget.label!, style: ZetaTextStyles.bodyMedium), ), ), - ], ], ); } @@ -220,7 +226,7 @@ class _CheckboxState extends State<_Checkbox> { Color _getBackground(Zeta theme) { final ZetaColorSwatch color = widget.error ? theme.colors.error : theme.colors.primary; if (widget.disabled) return theme.colors.surfaceDisabled; - if (!_checked) return theme.colors.surfacePrimary; + if (!_checked) return Colors.transparent; if (_isHovered) return theme.colors.borderHover; return color; diff --git a/lib/src/components/list_item/list_item.dart b/lib/src/components/list_item/list_item.dart index a7d705b0..f47e7975 100644 --- a/lib/src/components/list_item/list_item.dart +++ b/lib/src/components/list_item/list_item.dart @@ -2,31 +2,128 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../../zeta_flutter.dart'; +class _DivderScope extends InheritedWidget { + const _DivderScope({required super.child, required this.showDivider}); + + final bool showDivider; + + static _DivderScope? of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType<_DivderScope>(); + } + + @override + bool updateShouldNotify(_DivderScope oldWidget) => oldWidget.showDivider != showDivider; + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('showDivider', showDivider)); + } +} + +/// Used to apply dividers to a group of [ZetaListItem]s. +/// +/// Dividers on individual list items can be hidden or shown by setting their [showDivider] property. +/// +/// This wraps [ListView.builder] so it needs to be used in a widget with a constrained height. +class ZetaList extends StatelessWidget { + /// Creates a new [ZetaList]. + const ZetaList({ + required this.items, + this.showDivider = true, + super.key, + }); + + /// The list of [ZetaListItem]s to be shown in the list. + final List items; + + /// Shows/hides the divider between lists. + /// Defaults to true. + final bool showDivider; + + @override + Widget build(BuildContext context) { + return _DivderScope( + showDivider: showDivider, + child: ListView.builder( + itemBuilder: (context, i) => items[i], + itemCount: items.length, + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('showDivider', showDivider)); + } +} + /// A single row that typically contains some text as well as a leading or trailing widgets. +/// +/// To create list items with a [ZetaSwitch], [ZetaCheckbox], or [ZetaRadio], use the [ZetaListItem.toggle], [ZetaListItem.checkbox] or the [ZetaListItem.radio] named constructors respectively. class ZetaListItem extends StatelessWidget { /// Creates a [ZetaListItem]. const ZetaListItem({ - required this.title, - this.dense = false, - this.enabled = true, - this.enabledDivider = true, + required this.primaryText, + this.secondaryText, this.leading, this.onTap, - this.selected = false, - this.subtitle, + this.showDivider, this.trailing, super.key, }); - /// Dense list items have less space between widgets and use smaller [TextStyle] - final bool dense; + /// Creates a [ZetaListItem] with a [ZetaSwitch] in the trailing widget space. + ZetaListItem.toggle({ + super.key, + required this.primaryText, + this.secondaryText, + this.showDivider, + this.leading, + bool value = false, + ValueChanged? onChanged, + }) : trailing = ZetaSwitch( + value: value, + onChanged: onChanged, + variant: ZetaSwitchType.android, + ), + onTap = (() => onChanged?.call(!value)); - /// Whether this [ZetaListItem] is interactive. - /// If false the [onTap] callback is inoperative. - final bool enabled; + /// Creates a [ZetaListItem] with a [ZetaCheckbox] in the trailing widget space. + ZetaListItem.checkbox({ + super.key, + required this.primaryText, + this.secondaryText, + this.leading, + this.showDivider, + bool value = false, + bool rounded = true, + ValueChanged? onChanged, + bool useIndeterminate = false, + }) : trailing = ZetaCheckbox( + value: value, + onChanged: onChanged, + useIndeterminate: useIndeterminate, + rounded: rounded, + ), + onTap = (() => onChanged?.call(!value)); - /// Whether to apply divider. Normally at the bottom of the [ZetaListItem]. - final bool enabledDivider; + /// Creates a [ZetaListItem] with a [ZetaRadio] in the trailing widget space. + ZetaListItem.radio({ + required this.primaryText, + required dynamic value, + this.secondaryText, + this.leading, + this.showDivider, + dynamic groupValue, + super.key, + ValueChanged? onChanged, + }) : trailing = ZetaRadio( + value: value, + groupValue: groupValue, + onChanged: onChanged, + ), + onTap = (() => onChanged?.call(value)); /// A Widget to display before the title; final Widget? leading; @@ -34,173 +131,96 @@ class ZetaListItem extends StatelessWidget { /// Called when user taps on the [ZetaListItem]. final VoidCallback? onTap; - /// Applies selected styles. If selected is true trailing mu - final bool selected; - - /// Additional content displayed over the title. - /// Typically a [Text] widget. - final Widget? subtitle; + /// The primary text of the [ZetaListItem]. + final String primaryText; - /// The primary content of the [ZetaListItem]. - final Widget title; + /// The primary text of the [ZetaListItem]. + final String? secondaryText; /// A widget to display after the title. final Widget? trailing; + /// Adds a border to the bottom of the list item. + /// If this isn't provided and the item is used in a [ZetaList], the value is fetched from the [showDivider] prop on the [ZetaList]. + final bool? showDivider; + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(DiagnosticsProperty('dense', dense)) - ..add(DiagnosticsProperty('enabled', enabled)) - ..add(DiagnosticsProperty('enabledDivider', enabledDivider)) ..add(DiagnosticsProperty('leading', leading)) ..add(ObjectFlagProperty.has('onTap', onTap)) - ..add(DiagnosticsProperty('selected', selected)) - ..add(DiagnosticsProperty('subtitle', subtitle)) - ..add(DiagnosticsProperty('title', title)) - ..add(DiagnosticsProperty('trailing', trailing)); + ..add(DiagnosticsProperty('trailing', trailing)) + ..add(StringProperty('label', primaryText)) + ..add(StringProperty('secondaryText', secondaryText)) + ..add(DiagnosticsProperty('showDivider', showDivider)); } - TextStyle get _titleTextStyle => dense ? ZetaTextStyles.titleSmall : ZetaTextStyles.titleMedium; - @override Widget build(BuildContext context) { - final zetaColors = Zeta.of(context).colors; - - return _ListItemContainer( - enabled: enabled, - selected: selected, - onTap: onTap, - dense: dense, - enabledDivider: enabledDivider, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( + final colors = Zeta.of(context).colors; + final divide = showDivider ?? _DivderScope.of(context)?.showDivider ?? false; + + return Container( + constraints: const BoxConstraints(minHeight: ZetaSpacing.xl_9), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: divide ? colors.borderDefault : Colors.transparent, + ), + ), + ), + child: Material( + color: Zeta.of(context).colors.surfaceDefault, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: ZetaSpacing.large), child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (leading != null) - Padding( - padding: EdgeInsets.only( - right: dense ? ZetaSpacing.small : ZetaSpacing.large, - ), - child: leading, - ), Flexible( - child: Padding( - padding: EdgeInsets.symmetric( - vertical: dense ? 0.0 : ZetaSpacing.large, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (subtitle != null) - DefaultTextStyle( - style: ZetaTextStyles.titleSmall.copyWith( - color: enabled ? zetaColors.textSubtle : zetaColors.textDisabled, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - child: subtitle ?? const SizedBox(), - ), - DefaultTextStyle( - style: _titleTextStyle.copyWith( - color: enabled ? zetaColors.textDefault : zetaColors.textDisabled, + child: Row( + children: [ + if (leading != null) + Padding( + padding: const EdgeInsets.only( + right: ZetaSpacing.small, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - child: title, + child: leading, ), - ], - ), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + primaryText, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (secondaryText != null && secondaryText!.isNotEmpty) + Text( + secondaryText!, + style: ZetaTextStyles.bodySmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], ), ), + if (trailing != null) + Padding( + padding: const EdgeInsets.only( + left: ZetaSpacing.large, + ), + child: trailing, + ), ], ), ), - if (trailing != null) - Padding( - padding: EdgeInsets.only( - left: dense ? ZetaSpacing.small : ZetaSpacing.large, - ), - child: trailing, - ), - if (trailing == null && selected && enabled) - Padding( - padding: EdgeInsets.only( - left: dense ? ZetaSpacing.small : ZetaSpacing.large, - ), - child: Icon( - ZetaIcons.check_mark_round, - color: zetaColors.blue.shade60, - ), - ), - ], - ), - ); - } -} - -class _ListItemContainer extends StatelessWidget { - const _ListItemContainer({ - required this.child, - required this.dense, - required this.enabled, - required this.enabledDivider, - required this.onTap, - required this.selected, - }); - - final Widget child; - final bool dense; - final bool enabled; - final bool enabledDivider; - final VoidCallback? onTap; - final bool selected; - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('child', child)) - ..add(DiagnosticsProperty('enabled', enabled)) - ..add(DiagnosticsProperty('selected', selected)) - ..add(ObjectFlagProperty.has('onTap', onTap)) - ..add(DiagnosticsProperty('dense', dense)) - ..add(DiagnosticsProperty('enabledDivider', enabledDivider)); - } - - @override - Widget build(BuildContext context) { - final zetaColors = Zeta.of(context).colors; - - return AbsorbPointer( - absorbing: !enabled, - child: Material( - color: enabled - ? selected - ? zetaColors.blue.shade10 - : zetaColors.white - : zetaColors.surfaceDisabled, - child: InkWell( - onTap: enabled ? onTap : null, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: dense ? ZetaSpacing.large : ZetaSpacing.xl_4, - vertical: dense ? ZetaSpacing.small : ZetaSpacing.large, - ), - decoration: BoxDecoration( - border: enabled && enabledDivider - ? Border( - bottom: BorderSide( - color: selected ? zetaColors.blue.shade40 : zetaColors.borderDefault, - ), - ) - : null, - ), - child: child, - ), ), ), ); diff --git a/lib/src/components/radio/radio.dart b/lib/src/components/radio/radio.dart index 0e2ecc3f..a0458337 100644 --- a/lib/src/components/radio/radio.dart +++ b/lib/src/components/radio/radio.dart @@ -47,67 +47,70 @@ class ZetaRadio extends StatefulWidget { class _ZetaRadioState extends State> with TickerProviderStateMixin, ToggleableStateMixin { ToggleablePainter? _painter; - bool _isHovered = false; + @override Widget build(BuildContext context) { final zetaColors = Zeta.of(context).colors; _painter ??= _RadioPainter(colors: zetaColors); - return Semantics( - inMutuallyExclusiveGroup: true, - checked: widget._selected, - selected: value, - excludeSemantics: true, - child: MouseRegion( - onEnter: (_) => setState(() => _isHovered = true), - onExit: (_) => setState(() => _isHovered = false), - cursor: states.contains(WidgetState.disabled) ? SystemMouseCursors.forbidden : SystemMouseCursors.click, - child: SelectionContainer.disabled( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - buildToggleable( - size: const Size(ZetaSpacing.xl_5, ZetaSpacing.xl_5), - painter: _painter! - ..position = position - ..reaction = reaction - ..reactionFocusFade = reactionFocusFade - ..reactionHoverFade = reactionHoverFade - ..inactiveReactionColor = Colors.transparent - ..reactionColor = Colors.transparent - ..hoverColor = Colors.transparent - ..focusColor = zetaColors.blue.shade50 - ..splashRadius = ZetaSpacing.medium - ..downPosition = downPosition - ..isFocused = states.contains(WidgetState.focused) - ..isHovered = states.contains(WidgetState.hovered) - ..activeColor = _isHovered - ? zetaColors.cool.shade90 - : states.contains(WidgetState.disabled) - ? zetaColors.cool.shade30 - : zetaColors.blue.shade60 - ..inactiveColor = _isHovered - ? zetaColors.cool.shade90 - : states.contains(WidgetState.disabled) - ? zetaColors.cool.shade30 - : states.contains(WidgetState.focused) - ? zetaColors.blue.shade50 - : zetaColors.cool.shade70, - mouseCursor: WidgetStateProperty.all( - states.contains(WidgetState.disabled) ? SystemMouseCursors.forbidden : SystemMouseCursors.click, - ), - ), - if (widget.label != null) - GestureDetector( - onTap: () => onChanged?.call(true), - child: DefaultTextStyle( - style: ZetaTextStyles.bodyMedium.copyWith( - color: states.contains(WidgetState.disabled) ? zetaColors.textDisabled : zetaColors.textDefault, - height: 1.33, + + return Material( + color: Colors.transparent, + child: InkWell( + onTap: !states.contains(WidgetState.disabled) ? () => onChanged?.call(true) : null, + borderRadius: ZetaRadius.full, + child: Semantics( + inMutuallyExclusiveGroup: true, + checked: widget._selected, + selected: value, + excludeSemantics: true, + child: MouseRegion( + cursor: states.contains(WidgetState.disabled) ? SystemMouseCursors.forbidden : SystemMouseCursors.click, + child: SelectionContainer.disabled( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + buildToggleable( + size: const Size(ZetaSpacing.xl_5, ZetaSpacing.xl_5), + painter: _painter! + ..position = position + ..reaction = reaction + ..reactionFocusFade = reactionFocusFade + ..reactionHoverFade = reactionHoverFade + ..inactiveReactionColor = Colors.transparent + ..reactionColor = Colors.transparent + ..hoverColor = Colors.transparent + ..focusColor = zetaColors.blue.shade50 + ..splashRadius = ZetaSpacing.medium + ..downPosition = downPosition + ..isFocused = states.contains(WidgetState.focused) + ..isHovered = states.contains(WidgetState.hovered) + ..activeColor = states.contains(WidgetState.hovered) + ? zetaColors.cool.shade90 + : states.contains(WidgetState.disabled) + ? zetaColors.cool.shade30 + : zetaColors.blue.shade60 + ..inactiveColor = states.contains(WidgetState.hovered) + ? zetaColors.cool.shade90 + : states.contains(WidgetState.disabled) + ? zetaColors.cool.shade30 + : states.contains(WidgetState.focused) + ? zetaColors.blue.shade50 + : zetaColors.cool.shade70, + mouseCursor: WidgetStateProperty.all( + states.contains(WidgetState.disabled) ? SystemMouseCursors.forbidden : SystemMouseCursors.click, ), - child: widget.label!, ), - ), - ], + if (widget.label != null) + DefaultTextStyle( + style: ZetaTextStyles.bodyMedium.copyWith( + color: states.contains(WidgetState.disabled) ? zetaColors.textDisabled : zetaColors.textDefault, + height: 1.33, + ), + child: widget.label!, + ).paddingEnd(ZetaSpacing.minimum), + ], + ), + ), ), ), ),