Skip to content

Commit a86f0e3

Browse files
atanasyordanov21thelukewalton
authored andcommitted
Component Zeta Radio Button (#9)
* create component Zeta Radio Button * remove hover color * fix label line height
1 parent 9c57528 commit a86f0e3

File tree

6 files changed

+254
-0
lines changed

6 files changed

+254
-0
lines changed

example/lib/home.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:zeta_example/pages/components/dialpad_example.dart';
1313
import 'package:zeta_example/pages/components/dropdown_example.dart';
1414
import 'package:zeta_example/pages/components/list_item_example.dart';
1515
import 'package:zeta_example/pages/components/navigation_bar_example.dart';
16+
import 'package:zeta_example/pages/components/radio_example.dart';
1617
import 'package:zeta_example/pages/components/switch_example.dart';
1718
import 'package:zeta_example/pages/theme/color_example.dart';
1819
import 'package:zeta_example/pages/components/password_input_example.dart';
@@ -47,6 +48,7 @@ final List<Component> components = [
4748
Component(DropdownExample.name, (context) => const DropdownExample()),
4849
Component(ProgressExample.name, (context) => const ProgressExample()),
4950
Component(DialPadExample.name, (context) => const DialPadExample()),
51+
Component(RadioButtonExample.name, (context) => const RadioButtonExample()),
5052
Component(SwitchExample.name, (context) => const SwitchExample()),
5153
];
5254

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:zeta_example/widgets.dart';
3+
import 'package:zeta_flutter/zeta_flutter.dart';
4+
5+
class RadioButtonExample extends StatefulWidget {
6+
static const String name = 'RadioButton';
7+
8+
const RadioButtonExample({Key? key}) : super(key: key);
9+
10+
@override
11+
State<RadioButtonExample> createState() => _RadioButtonExampleState();
12+
}
13+
14+
class _RadioButtonExampleState extends State<RadioButtonExample> {
15+
String option1 = 'Label 1';
16+
String option2 = 'Label 2';
17+
String? groupValue;
18+
bool isEnabled = true;
19+
20+
@override
21+
Widget build(BuildContext context) {
22+
return ExampleScaffold(
23+
name: 'Radio Button',
24+
child: Center(
25+
child: Column(
26+
children: [
27+
ZetaRadio<String>(
28+
value: option1,
29+
groupValue: groupValue,
30+
onChanged: isEnabled ? (value) => setState(() => groupValue = value) : null,
31+
label: Text(option1),
32+
),
33+
ZetaRadio<String>(
34+
value: option2,
35+
groupValue: groupValue,
36+
onChanged: isEnabled ? (value) => setState(() => groupValue = value) : null,
37+
label: Text(option2),
38+
),
39+
ZetaButton(
40+
label: isEnabled ? 'Disable' : 'Enable',
41+
onPressed: () => setState(() => isEnabled = !isEnabled),
42+
),
43+
],
44+
),
45+
),
46+
);
47+
}
48+
}

example/widgetbook/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import 'pages/components/list_item_widgetbook.dart';
1919
import 'pages/components/navigation_bar_widgetbook.dart';
2020
import 'pages/components/password_input_widgetbook.dart';
2121
import 'pages/components/progress_widgetbook.dart';
22+
import 'pages/components/radio_widgetbook.dart';
2223
import 'pages/components/switch_widgetbook.dart';
2324
import 'pages/theme/color_widgetbook.dart';
2425
import 'pages/theme/radius_widgetbook.dart';
@@ -89,6 +90,7 @@ class HotReload extends StatelessWidget {
8990
WidgetbookUseCase(name: 'Circle', builder: (context) => progressCircleUseCase(context))
9091
],
9192
),
93+
WidgetbookUseCase(name: 'Radio Button', builder: (context) => radioButtonUseCase(context)),
9294
WidgetbookUseCase(name: 'Switch', builder: (context) => switchUseCase(context)),
9395
]..sort((a, b) => a.name.compareTo(b.name)),
9496
),
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:widgetbook/widgetbook.dart';
3+
import 'package:zeta_flutter/zeta_flutter.dart';
4+
5+
import '../../test/test_components.dart';
6+
7+
Widget radioButtonUseCase(BuildContext context) {
8+
String option1 = 'Label 1';
9+
String option2 = 'Label 2';
10+
String? groupValue;
11+
12+
return WidgetbookTestWidget(
13+
widget: StatefulBuilder(
14+
builder: (context, setState) {
15+
ValueChanged<String?>? onChanged = context.knobs.boolean(label: 'Enabled', initialValue: true)
16+
? (value) => setState(() => groupValue = value)
17+
: null;
18+
return Padding(
19+
padding: const EdgeInsets.all(ZetaSpacing.x5),
20+
child: Column(
21+
children: [
22+
Padding(
23+
padding: const EdgeInsets.only(bottom: ZetaSpacing.x5),
24+
child: Text('Radio Button'),
25+
),
26+
ZetaRadio<String>(
27+
value: option1,
28+
groupValue: groupValue,
29+
onChanged: onChanged,
30+
label: Text(option1),
31+
),
32+
ZetaRadio<String>(
33+
value: option2,
34+
groupValue: groupValue,
35+
onChanged: onChanged,
36+
label: Text(option2),
37+
),
38+
],
39+
),
40+
);
41+
},
42+
),
43+
);
44+
}

lib/src/components/radio/radio.dart

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter/material.dart';
3+
4+
import '../../../zeta_flutter.dart';
5+
6+
/// Zeta Radio Button
7+
///
8+
/// Radio Button can select one single option from a goup of different options.
9+
class ZetaRadio<T> extends StatefulWidget {
10+
/// Constructor for [ZetaRadio].
11+
const ZetaRadio({
12+
super.key,
13+
required this.value,
14+
this.groupValue,
15+
this.onChanged,
16+
this.label,
17+
});
18+
19+
/// The value of the option, which can be selected by this Radio Button.
20+
final T value;
21+
22+
/// The selected value among all possible options.
23+
final T? groupValue;
24+
25+
/// Callback function to call when the Radio Button is tapped.
26+
final ValueChanged<T?>? onChanged;
27+
28+
/// The label which appears next to the Radio Button, on the right side.
29+
final Widget? label;
30+
31+
bool get _selected => value == groupValue;
32+
33+
@override
34+
State<ZetaRadio<T>> createState() => _ZetaRadioState<T>();
35+
36+
@override
37+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
38+
super.debugFillProperties(properties);
39+
properties
40+
..add(DiagnosticsProperty<T>('value', value))
41+
..add(DiagnosticsProperty<T?>('groupValue', groupValue))
42+
..add(ObjectFlagProperty<ValueChanged<T?>?>('onChanged', onChanged, ifNull: 'disabled'));
43+
}
44+
}
45+
46+
class _ZetaRadioState<T> extends State<ZetaRadio<T>> with TickerProviderStateMixin, ToggleableStateMixin {
47+
final ToggleablePainter _painter = _RadioPainter();
48+
@override
49+
Widget build(BuildContext context) {
50+
final zetaColors = Zeta.of(context).colors;
51+
return Row(
52+
mainAxisSize: MainAxisSize.min,
53+
children: [
54+
Semantics(
55+
inMutuallyExclusiveGroup: true,
56+
checked: widget._selected,
57+
selected: value,
58+
child: buildToggleable(
59+
size: const Size(36, 36),
60+
painter: _painter
61+
..position = position
62+
..reaction = reaction
63+
..reactionFocusFade = reactionFocusFade
64+
..reactionHoverFade = reactionHoverFade
65+
..inactiveReactionColor = Colors.transparent
66+
..reactionColor = Colors.transparent
67+
..hoverColor = Colors.transparent
68+
..focusColor = zetaColors.blue.shade50
69+
..splashRadius = 12
70+
..downPosition = downPosition
71+
..isFocused = states.contains(MaterialState.focused)
72+
..isHovered = states.contains(MaterialState.hovered)
73+
..activeColor =
74+
states.contains(MaterialState.disabled) ? zetaColors.cool.shade30 : zetaColors.blue.shade60
75+
..inactiveColor =
76+
states.contains(MaterialState.disabled) ? zetaColors.cool.shade30 : zetaColors.cool.shade70,
77+
mouseCursor: MaterialStateProperty.all(
78+
MaterialStateProperty.resolveAs<MouseCursor>(
79+
MaterialStateMouseCursor.clickable,
80+
states,
81+
),
82+
),
83+
),
84+
),
85+
if (widget.label != null)
86+
GestureDetector(
87+
onTap: () => onChanged?.call(true),
88+
child: DefaultTextStyle(
89+
style: ZetaTextStyles.bodyLarge.copyWith(
90+
color: states.contains(MaterialState.disabled) ? zetaColors.textDisabled : zetaColors.textDefault,
91+
height: 1.33,
92+
),
93+
child: widget.label!,
94+
),
95+
),
96+
],
97+
);
98+
}
99+
100+
void _handleChanged(bool? selected) {
101+
if (selected == null) {
102+
widget.onChanged!(null);
103+
return;
104+
}
105+
if (selected) {
106+
widget.onChanged!(widget.value);
107+
}
108+
}
109+
110+
@override
111+
ValueChanged<bool?>? get onChanged => widget.onChanged != null ? _handleChanged : null;
112+
113+
@override
114+
bool get tristate => false;
115+
116+
@override
117+
bool get value => widget._selected;
118+
119+
@override
120+
void didUpdateWidget(ZetaRadio<T> oldWidget) {
121+
super.didUpdateWidget(oldWidget);
122+
if (widget._selected != oldWidget._selected) {
123+
animateToValue();
124+
}
125+
}
126+
127+
@override
128+
void dispose() {
129+
_painter.dispose();
130+
super.dispose();
131+
}
132+
}
133+
134+
const double _kOuterRadius = 10;
135+
const double _kInnerRadius = 5;
136+
137+
class _RadioPainter extends ToggleablePainter {
138+
@override
139+
void paint(Canvas canvas, Size size) {
140+
paintRadialReaction(canvas: canvas, origin: size.center(Offset.zero));
141+
142+
final Offset center = (Offset.zero & size).center;
143+
144+
// Outer circle
145+
final Paint paint = Paint()
146+
..color = Color.lerp(inactiveColor, activeColor, position.value)!
147+
..style = PaintingStyle.stroke
148+
..strokeWidth = 2;
149+
canvas.drawCircle(center, _kOuterRadius, paint);
150+
151+
// Inner circle
152+
if (!position.isDismissed) {
153+
paint.style = PaintingStyle.fill;
154+
canvas.drawCircle(center, _kInnerRadius * position.value, paint);
155+
}
156+
}
157+
}

lib/zeta_flutter.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export 'src/components/navigation bar/navigation_bar.dart';
2929
export 'src/components/password/password_input.dart';
3030
export 'src/components/progress/progress_bar.dart';
3131
export 'src/components/progress/progress_circle.dart';
32+
export 'src/components/radio/radio.dart';
3233
export 'src/components/switch/zeta_switch.dart';
3334
export 'src/theme/color_extensions.dart';
3435
export 'src/theme/color_scheme.dart';

0 commit comments

Comments
 (0)