diff --git a/example/lib/pages/components/checkbox_example.dart b/example/lib/pages/components/checkbox_example.dart index be48113d..4249cd27 100644 --- a/example/lib/pages/components/checkbox_example.dart +++ b/example/lib/pages/components/checkbox_example.dart @@ -12,7 +12,7 @@ class CheckBoxExample extends StatefulWidget { } class _CheckBoxExampleState extends State { - bool? isChecked = true; + bool isChecked = true; bool isEnabled = true; bool useIndeterminate = false; @@ -76,9 +76,9 @@ Row getCheckBoxRow({required bool isEnabled, bool isSharp = true}) { onChanged: isEnabled ? (value) => {} : null, ), ZetaCheckbox( - value: null, rounded: !isSharp, onChanged: isEnabled ? (value) => {} : null, + value: false, ) ]); } diff --git a/example/widgetbook/pages/components/checkbox_widgetbook.dart b/example/widgetbook/pages/components/checkbox_widgetbook.dart index 63ea3365..af313f17 100644 --- a/example/widgetbook/pages/components/checkbox_widgetbook.dart +++ b/example/widgetbook/pages/components/checkbox_widgetbook.dart @@ -6,13 +6,17 @@ import '../../test/test_components.dart'; import '../../utils/utils.dart'; Widget checkboxUseCase(BuildContext context) { - bool? b = true; + bool b = true; return WidgetbookTestWidget( widget: StatefulBuilder( builder: (context, setState) { - dynamic onChanged = - context.knobs.boolean(label: 'Enabled', initialValue: true) ? (b2) => setState(() => b = b2) : null; + ValueChanged? onChanged = context.knobs.boolean( + label: 'Enabled', + initialValue: true, + ) + ? (b2) => setState(() => b = b2) + : null; return ZetaCheckbox( value: b, onChanged: onChanged, diff --git a/lib/src/components/checkbox/checkbox.dart b/lib/src/components/checkbox/checkbox.dart index 36d40b8c..145f632a 100644 --- a/lib/src/components/checkbox/checkbox.dart +++ b/lib/src/components/checkbox/checkbox.dart @@ -5,11 +5,16 @@ import '../../../zeta_flutter.dart'; /// Zeta Checkbox. /// /// Checkboxes allow users to select one or more items from a set. Checkboxes can turn an option on or off. +/// +/// The checkbox itself does not maintain any state. Instead, when the state of +/// the checkbox changes, the widget calls the [onChanged] callback. +/// Widgets that use a checkbox should listen for the [onChanged] callback and +/// rebuild the checkbox with a new [value] to update the visual appearance of +/// the checkbox. class ZetaCheckbox extends FormField { /// Constructs a [ZetaCheckbox]. ZetaCheckbox({ - super.key, - this.value = false, + required this.value, this.label, this.onChanged, this.rounded = true, @@ -17,6 +22,7 @@ class ZetaCheckbox extends FormField { super.validator, super.autovalidateMode, super.restorationId, + super.key, }) : super( initialValue: value, enabled: onChanged != null, @@ -25,13 +31,13 @@ class ZetaCheckbox extends FormField { label: label, onChanged: (changedValue) { field.didChange(changedValue); - onChanged?.call(changedValue); + onChanged?.call(changedValue!); }, rounded: rounded, useIndeterminate: useIndeterminate, value: value, error: !field.isValid, - enabled: onChanged != null, + disabled: onChanged == null, ); }, ); @@ -45,10 +51,10 @@ class ZetaCheckbox extends FormField { final bool useIndeterminate; /// Whether the checkbox is selected, unselected or null (indeterminate) - final bool? value; + final bool value; /// Called when the value of the checkbox should change. - final ValueChanged? onChanged; + final ValueChanged? onChanged; /// The label displayed next to the checkbox final String? label; @@ -63,7 +69,7 @@ class ZetaCheckbox extends FormField { ..add(StringProperty('label', label)) ..add(DiagnosticsProperty('useIndeterminate', useIndeterminate)) ..add(DiagnosticsProperty('rounded', rounded)) - ..add(ObjectFlagProperty?>.has('onChanged', onChanged)); + ..add(ObjectFlagProperty?>.has('onChanged', onChanged)); } } @@ -75,17 +81,17 @@ class ZetaCheckboxFormFieldState extends FormFieldState { class _Checkbox extends StatefulWidget { const _Checkbox({ - this.value = false, required this.onChanged, + this.disabled = false, + this.value = false, this.label, this.rounded = true, this.useIndeterminate = false, this.error = false, - required this.enabled, }); /// Whether the checkbox is selected, unselected or null (indeterminate) - final bool? value; + final bool value; /// Called when the value of the checkbox should change. final ValueChanged onChanged; @@ -103,7 +109,7 @@ class _Checkbox extends StatefulWidget { final bool error; - final bool enabled; + final bool disabled; @override State<_Checkbox> createState() => _CheckboxState(); @@ -116,45 +122,39 @@ class _Checkbox extends StatefulWidget { ..add(DiagnosticsProperty('rounded', rounded)) ..add(DiagnosticsProperty('useIndeterminate', useIndeterminate)) ..add(DiagnosticsProperty('error', error)) - ..add(DiagnosticsProperty('enabled', enabled)) + ..add(DiagnosticsProperty('enabled', disabled)) ..add(ObjectFlagProperty>.has('onChanged', onChanged)); } } class _CheckboxState extends State<_Checkbox> { - bool? get _value => widget.useIndeterminate ? widget.value : (widget.value ?? false); - - bool? get _updatedValue { - if (widget.useIndeterminate) { - if (widget.value == null) { - return false; - } else if (widget.value!) { - return null; - } else { - return true; - } - } else { - return !_value!; - } - } + bool get _checked => widget.value; bool _isFocused = false; bool _isHovered = false; + void _setHovered(bool isHovered) { + if (!widget.disabled) { + setState(() { + _isHovered = isHovered; + }); + } + } + @override Widget build(BuildContext context) { return Semantics( mixed: widget.useIndeterminate, - enabled: widget.enabled, + enabled: !widget.disabled, child: MouseRegion( - cursor: widget.enabled ? SystemMouseCursors.click : SystemMouseCursors.forbidden, - onEnter: (event) => setState(() => _isHovered = true), - onExit: (event) => setState(() => _isHovered = false), - child: widget.enabled + 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.enabled ? () => widget.onChanged.call(_updatedValue) : null, + onTap: !widget.disabled ? () => widget.onChanged.call(!_checked) : null, child: _buildContent(context), ), ) @@ -166,17 +166,17 @@ class _CheckboxState extends State<_Checkbox> { Flex _buildContent(BuildContext context) { final theme = Zeta.of(context); - final icon = _value != null && !_value! + final icon = !_checked ? const SizedBox.shrink() : Icon( - _value != null + !widget.useIndeterminate ? widget.rounded ? ZetaIcons.check_mark_round : ZetaIcons.check_mark_sharp : widget.rounded ? ZetaIcons.remove_round : ZetaIcons.remove_sharp, - color: widget.enabled ? theme.colors.white : theme.colors.textDisabled, + color: !widget.disabled ? theme.colors.white : theme.colors.iconDisabled, size: ZetaSpacing.x3_5, ); @@ -188,7 +188,7 @@ class _CheckboxState extends State<_Checkbox> { duration: const Duration(milliseconds: 200), decoration: BoxDecoration( boxShadow: [ - if (_isFocused && widget.enabled) + if (_isFocused && !widget.disabled) BoxShadow( spreadRadius: 2, blurStyle: BlurStyle.solid, @@ -196,7 +196,7 @@ class _CheckboxState extends State<_Checkbox> { ), ], color: _getBackground(theme), - border: widget.enabled ? Border.all(color: _getBorderColor(theme), width: ZetaSpacing.x0_5) : null, + border: Border.all(color: _getBorderColor(theme), width: ZetaSpacing.x0_5), borderRadius: widget.rounded ? ZetaRadius.minimal : ZetaRadius.none, ), width: ZetaSpacing.x5, @@ -217,19 +217,22 @@ class _CheckboxState extends State<_Checkbox> { Color _getBackground(Zeta theme) { final ZetaColorSwatch color = widget.error ? theme.colors.error : theme.colors.primary; - if (!widget.enabled) return theme.colors.surfaceDisabled; - if (_value != null && !_value!) return theme.colors.surfacePrimary; - if (_isHovered) return color.hover; + if (widget.disabled) return theme.colors.surfaceDisabled; + if (!_checked) return theme.colors.surfacePrimary; + if (_isHovered) return theme.colors.borderHover; return color; } Color _getBorderColor(Zeta theme) { - final ZetaColorSwatch color = widget.error ? theme.colors.error : theme.colors.cool; - - if (_isHovered) return color.shade90; + if (_checked || widget.error || widget.disabled) { + return _getBackground(theme); + } + if (_isHovered) { + return theme.colors.cool.shade90; + } - return color; + return theme.colors.cool.shade70; } @override