Skip to content

Commit

Permalink
Component Zeta Radio Button (#9)
Browse files Browse the repository at this point in the history
* create component Zeta Radio Button

* remove hover color

* fix label line height
  • Loading branch information
atanasyordanov21 authored and thelukewalton committed Apr 12, 2024
1 parent 9c57528 commit a86f0e3
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 0 deletions.
2 changes: 2 additions & 0 deletions example/lib/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,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/list_item_example.dart';
import 'package:zeta_example/pages/components/navigation_bar_example.dart';
import 'package:zeta_example/pages/components/radio_example.dart';
import 'package:zeta_example/pages/components/switch_example.dart';
import 'package:zeta_example/pages/theme/color_example.dart';
import 'package:zeta_example/pages/components/password_input_example.dart';
Expand Down Expand Up @@ -47,6 +48,7 @@ final List<Component> components = [
Component(DropdownExample.name, (context) => const DropdownExample()),
Component(ProgressExample.name, (context) => const ProgressExample()),
Component(DialPadExample.name, (context) => const DialPadExample()),
Component(RadioButtonExample.name, (context) => const RadioButtonExample()),
Component(SwitchExample.name, (context) => const SwitchExample()),
];

Expand Down
48 changes: 48 additions & 0 deletions example/lib/pages/components/radio_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:zeta_example/widgets.dart';
import 'package:zeta_flutter/zeta_flutter.dart';

class RadioButtonExample extends StatefulWidget {
static const String name = 'RadioButton';

const RadioButtonExample({Key? key}) : super(key: key);

@override
State<RadioButtonExample> createState() => _RadioButtonExampleState();
}

class _RadioButtonExampleState extends State<RadioButtonExample> {
String option1 = 'Label 1';
String option2 = 'Label 2';
String? groupValue;
bool isEnabled = true;

@override
Widget build(BuildContext context) {
return ExampleScaffold(
name: 'Radio Button',
child: Center(
child: Column(
children: [
ZetaRadio<String>(
value: option1,
groupValue: groupValue,
onChanged: isEnabled ? (value) => setState(() => groupValue = value) : null,
label: Text(option1),
),
ZetaRadio<String>(
value: option2,
groupValue: groupValue,
onChanged: isEnabled ? (value) => setState(() => groupValue = value) : null,
label: Text(option2),
),
ZetaButton(
label: isEnabled ? 'Disable' : 'Enable',
onPressed: () => setState(() => isEnabled = !isEnabled),
),
],
),
),
);
}
}
2 changes: 2 additions & 0 deletions example/widgetbook/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import 'pages/components/list_item_widgetbook.dart';
import 'pages/components/navigation_bar_widgetbook.dart';
import 'pages/components/password_input_widgetbook.dart';
import 'pages/components/progress_widgetbook.dart';
import 'pages/components/radio_widgetbook.dart';
import 'pages/components/switch_widgetbook.dart';
import 'pages/theme/color_widgetbook.dart';
import 'pages/theme/radius_widgetbook.dart';
Expand Down Expand Up @@ -89,6 +90,7 @@ class HotReload extends StatelessWidget {
WidgetbookUseCase(name: 'Circle', builder: (context) => progressCircleUseCase(context))
],
),
WidgetbookUseCase(name: 'Radio Button', builder: (context) => radioButtonUseCase(context)),
WidgetbookUseCase(name: 'Switch', builder: (context) => switchUseCase(context)),
]..sort((a, b) => a.name.compareTo(b.name)),
),
Expand Down
44 changes: 44 additions & 0 deletions example/widgetbook/pages/components/radio_widgetbook.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:zeta_flutter/zeta_flutter.dart';

import '../../test/test_components.dart';

Widget radioButtonUseCase(BuildContext context) {
String option1 = 'Label 1';
String option2 = 'Label 2';
String? groupValue;

return WidgetbookTestWidget(
widget: StatefulBuilder(
builder: (context, setState) {
ValueChanged<String?>? onChanged = context.knobs.boolean(label: 'Enabled', initialValue: true)
? (value) => setState(() => groupValue = value)
: null;
return Padding(
padding: const EdgeInsets.all(ZetaSpacing.x5),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: ZetaSpacing.x5),
child: Text('Radio Button'),
),
ZetaRadio<String>(
value: option1,
groupValue: groupValue,
onChanged: onChanged,
label: Text(option1),
),
ZetaRadio<String>(
value: option2,
groupValue: groupValue,
onChanged: onChanged,
label: Text(option2),
),
],
),
);
},
),
);
}
157 changes: 157 additions & 0 deletions lib/src/components/radio/radio.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../../../zeta_flutter.dart';

/// Zeta Radio Button
///
/// Radio Button can select one single option from a goup of different options.
class ZetaRadio<T> extends StatefulWidget {
/// Constructor for [ZetaRadio].
const ZetaRadio({
super.key,
required this.value,
this.groupValue,
this.onChanged,
this.label,
});

/// The value of the option, which can be selected by this Radio Button.
final T value;

/// The selected value among all possible options.
final T? groupValue;

/// Callback function to call when the Radio Button is tapped.
final ValueChanged<T?>? onChanged;

/// The label which appears next to the Radio Button, on the right side.
final Widget? label;

bool get _selected => value == groupValue;

@override
State<ZetaRadio<T>> createState() => _ZetaRadioState<T>();

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<T>('value', value))
..add(DiagnosticsProperty<T?>('groupValue', groupValue))
..add(ObjectFlagProperty<ValueChanged<T?>?>('onChanged', onChanged, ifNull: 'disabled'));
}
}

class _ZetaRadioState<T> extends State<ZetaRadio<T>> with TickerProviderStateMixin, ToggleableStateMixin {
final ToggleablePainter _painter = _RadioPainter();
@override
Widget build(BuildContext context) {
final zetaColors = Zeta.of(context).colors;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Semantics(
inMutuallyExclusiveGroup: true,
checked: widget._selected,
selected: value,
child: buildToggleable(
size: const Size(36, 36),
painter: _painter
..position = position
..reaction = reaction
..reactionFocusFade = reactionFocusFade
..reactionHoverFade = reactionHoverFade
..inactiveReactionColor = Colors.transparent
..reactionColor = Colors.transparent
..hoverColor = Colors.transparent
..focusColor = zetaColors.blue.shade50
..splashRadius = 12
..downPosition = downPosition
..isFocused = states.contains(MaterialState.focused)
..isHovered = states.contains(MaterialState.hovered)
..activeColor =
states.contains(MaterialState.disabled) ? zetaColors.cool.shade30 : zetaColors.blue.shade60
..inactiveColor =
states.contains(MaterialState.disabled) ? zetaColors.cool.shade30 : zetaColors.cool.shade70,
mouseCursor: MaterialStateProperty.all(
MaterialStateProperty.resolveAs<MouseCursor>(
MaterialStateMouseCursor.clickable,
states,
),
),
),
),
if (widget.label != null)
GestureDetector(
onTap: () => onChanged?.call(true),
child: DefaultTextStyle(
style: ZetaTextStyles.bodyLarge.copyWith(
color: states.contains(MaterialState.disabled) ? zetaColors.textDisabled : zetaColors.textDefault,
height: 1.33,
),
child: widget.label!,
),
),
],
);
}

void _handleChanged(bool? selected) {
if (selected == null) {
widget.onChanged!(null);
return;
}
if (selected) {
widget.onChanged!(widget.value);
}
}

@override
ValueChanged<bool?>? get onChanged => widget.onChanged != null ? _handleChanged : null;

@override
bool get tristate => false;

@override
bool get value => widget._selected;

@override
void didUpdateWidget(ZetaRadio<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget._selected != oldWidget._selected) {
animateToValue();
}
}

@override
void dispose() {
_painter.dispose();
super.dispose();
}
}

const double _kOuterRadius = 10;
const double _kInnerRadius = 5;

class _RadioPainter extends ToggleablePainter {
@override
void paint(Canvas canvas, Size size) {
paintRadialReaction(canvas: canvas, origin: size.center(Offset.zero));

final Offset center = (Offset.zero & size).center;

// Outer circle
final Paint paint = Paint()
..color = Color.lerp(inactiveColor, activeColor, position.value)!
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawCircle(center, _kOuterRadius, paint);

// Inner circle
if (!position.isDismissed) {
paint.style = PaintingStyle.fill;
canvas.drawCircle(center, _kInnerRadius * position.value, paint);
}
}
}
1 change: 1 addition & 0 deletions lib/zeta_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export 'src/components/navigation bar/navigation_bar.dart';
export 'src/components/password/password_input.dart';
export 'src/components/progress/progress_bar.dart';
export 'src/components/progress/progress_circle.dart';
export 'src/components/radio/radio.dart';
export 'src/components/switch/zeta_switch.dart';
export 'src/theme/color_extensions.dart';
export 'src/theme/color_scheme.dart';
Expand Down

0 comments on commit a86f0e3

Please sign in to comment.