Skip to content

Commit 64a2d2d

Browse files
committed
feat: implement keyboard triggering on buttons and add focus state (#7724)
* feat: implement keyboard triggering on buttons and add focus state * chore: pass isFocused to builders
1 parent 2e295e6 commit 64a2d2d

File tree

9 files changed

+113
-37
lines changed

9 files changed

+113
-37
lines changed

frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base_button.dart

Lines changed: 96 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ typedef AFBaseButtonColorBuilder = Color Function(
77
bool disabled,
88
);
99

10+
typedef AFBaseButtonBorderColorBuilder = Color Function(
11+
BuildContext context,
12+
bool isHovering,
13+
bool disabled,
14+
bool isFocused,
15+
);
16+
1017
class AFBaseButton extends StatefulWidget {
1118
const AFBaseButton({
1219
super.key,
@@ -16,49 +23,99 @@ class AFBaseButton extends StatefulWidget {
1623
required this.borderRadius,
1724
this.borderColor,
1825
this.backgroundColor,
26+
this.ringColor,
1927
this.disabled = false,
2028
});
2129

2230
final VoidCallback? onTap;
2331

24-
final AFBaseButtonColorBuilder? borderColor;
32+
final AFBaseButtonBorderColorBuilder? borderColor;
33+
final AFBaseButtonBorderColorBuilder? ringColor;
2534
final AFBaseButtonColorBuilder? backgroundColor;
2635

2736
final EdgeInsetsGeometry padding;
2837
final double borderRadius;
2938
final bool disabled;
3039

31-
final Widget Function(BuildContext context, bool isHovering, bool disabled)
32-
builder;
40+
final Widget Function(
41+
BuildContext context,
42+
bool isHovering,
43+
bool disabled,
44+
) builder;
3345

3446
@override
3547
State<AFBaseButton> createState() => _AFBaseButtonState();
3648
}
3749

3850
class _AFBaseButtonState extends State<AFBaseButton> {
51+
final FocusNode focusNode = FocusNode();
52+
3953
bool isHovering = false;
54+
bool isFocused = false;
55+
56+
@override
57+
void dispose() {
58+
focusNode.dispose();
59+
super.dispose();
60+
}
4061

4162
@override
4263
Widget build(BuildContext context) {
4364
final Color borderColor = _buildBorderColor(context);
4465
final Color backgroundColor = _buildBackgroundColor(context);
66+
final Color ringColor = _buildRingColor(context);
4567

46-
return MouseRegion(
47-
cursor:
48-
widget.disabled ? SystemMouseCursors.basic : SystemMouseCursors.click,
49-
onEnter: (_) => setState(() => isHovering = true),
50-
onExit: (_) => setState(() => isHovering = false),
51-
child: GestureDetector(
52-
onTap: widget.disabled ? null : widget.onTap,
53-
child: DecoratedBox(
54-
decoration: BoxDecoration(
55-
color: backgroundColor,
56-
border: Border.all(color: borderColor),
57-
borderRadius: BorderRadius.circular(widget.borderRadius),
58-
),
59-
child: Padding(
60-
padding: widget.padding,
61-
child: widget.builder(context, isHovering, widget.disabled),
68+
return Actions(
69+
actions: {
70+
ActivateIntent: CallbackAction<ActivateIntent>(
71+
onInvoke: (_) {
72+
if (!widget.disabled) {
73+
widget.onTap?.call();
74+
}
75+
return;
76+
},
77+
),
78+
},
79+
child: Focus(
80+
focusNode: focusNode,
81+
onFocusChange: (isFocused) {
82+
setState(() => this.isFocused = isFocused);
83+
},
84+
child: MouseRegion(
85+
cursor: widget.disabled
86+
? SystemMouseCursors.basic
87+
: SystemMouseCursors.click,
88+
onEnter: (_) => setState(() => isHovering = true),
89+
onExit: (_) => setState(() => isHovering = false),
90+
child: GestureDetector(
91+
onTap: widget.disabled ? null : widget.onTap,
92+
child: DecoratedBox(
93+
decoration: BoxDecoration(
94+
borderRadius: BorderRadius.circular(widget.borderRadius),
95+
border: isFocused
96+
? Border.all(
97+
color: ringColor,
98+
width: 2,
99+
strokeAlign: BorderSide.strokeAlignOutside,
100+
)
101+
: null,
102+
),
103+
child: DecoratedBox(
104+
decoration: BoxDecoration(
105+
color: backgroundColor,
106+
border: Border.all(color: borderColor),
107+
borderRadius: BorderRadius.circular(widget.borderRadius),
108+
),
109+
child: Padding(
110+
padding: widget.padding,
111+
child: widget.builder(
112+
context,
113+
isHovering,
114+
widget.disabled,
115+
),
116+
),
117+
),
118+
),
62119
),
63120
),
64121
),
@@ -67,7 +124,8 @@ class _AFBaseButtonState extends State<AFBaseButton> {
67124

68125
Color _buildBorderColor(BuildContext context) {
69126
final theme = AppFlowyTheme.of(context);
70-
return widget.borderColor?.call(context, isHovering, widget.disabled) ??
127+
return widget.borderColor
128+
?.call(context, isHovering, widget.disabled, isFocused) ??
71129
theme.borderColorScheme.greyTertiary;
72130
}
73131

@@ -76,4 +134,22 @@ class _AFBaseButtonState extends State<AFBaseButton> {
76134
return widget.backgroundColor?.call(context, isHovering, widget.disabled) ??
77135
theme.fillColorScheme.transparent;
78136
}
137+
138+
Color _buildRingColor(BuildContext context) {
139+
final theme = AppFlowyTheme.of(context);
140+
141+
if (widget.ringColor != null) {
142+
return widget.ringColor!
143+
.call(context, isHovering, widget.disabled, isFocused);
144+
}
145+
146+
if (isFocused) {
147+
return AppFlowyTheme.of(context)
148+
.borderColorScheme
149+
.themeThick
150+
.withAlpha(128);
151+
}
152+
153+
return theme.borderColorScheme.transparent;
154+
}
79155
}

frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_button.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class AFFilledButton extends StatelessWidget {
115115
return AFBaseButton(
116116
disabled: disabled,
117117
backgroundColor: backgroundColor,
118-
borderColor: (_, __, ___) => Colors.transparent,
118+
borderColor: (_, __, ___, ____) => Colors.transparent,
119119
padding: padding ?? size.buildPadding(context),
120120
borderRadius: borderRadius ?? size.buildBorderRadius(context),
121121
onTap: onTap,

frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_text_button.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class AFFilledTextButton extends AFBaseTextButton {
114114
return AFBaseButton(
115115
disabled: disabled,
116116
backgroundColor: backgroundColor,
117-
borderColor: (_, __, ___) => Colors.transparent,
117+
borderColor: (_, __, ___, ____) => Colors.transparent,
118118
padding: padding ?? size.buildPadding(context),
119119
borderRadius: borderRadius ?? size.buildBorderRadius(context),
120120
onTap: onTap,

frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_button.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class AFGhostButton extends StatelessWidget {
8686
return AFBaseButton(
8787
disabled: disabled,
8888
backgroundColor: backgroundColor,
89-
borderColor: (_, __, ___) => Colors.transparent,
89+
borderColor: (_, __, ___, ____) => Colors.transparent,
9090
padding: padding ?? size.buildPadding(context),
9191
borderRadius: borderRadius ?? size.buildBorderRadius(context),
9292
onTap: onTap,

frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_icon_text_button.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class AFGhostIconTextButton extends StatelessWidget {
109109
return AFBaseButton(
110110
disabled: disabled,
111111
backgroundColor: backgroundColor,
112-
borderColor: (context, isHovering, disabled) {
112+
borderColor: (context, isHovering, disabled, isFocused) {
113113
return Colors.transparent;
114114
},
115115
padding: padding ?? size.buildPadding(context),

frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_text_button.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class AFGhostTextButton extends AFBaseTextButton {
8888
return AFBaseButton(
8989
disabled: disabled,
9090
backgroundColor: backgroundColor,
91-
borderColor: (_, __, ___) => Colors.transparent,
91+
borderColor: (_, __, ___, ____) => Colors.transparent,
9292
padding: padding ?? size.buildPadding(context),
9393
borderRadius: borderRadius ?? size.buildBorderRadius(context),
9494
onTap: onTap,

frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_button.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class AFOutlinedButton extends StatelessWidget {
3838
padding: padding,
3939
borderRadius: borderRadius,
4040
disabled: disabled,
41-
borderColor: (context, isHovering, disabled) {
41+
borderColor: (context, isHovering, disabled, isFocused) {
4242
final theme = AppFlowyTheme.of(context);
4343
if (disabled) {
4444
return theme.borderColorScheme.greyTertiary;
@@ -79,7 +79,7 @@ class AFOutlinedButton extends StatelessWidget {
7979
padding: padding,
8080
borderRadius: borderRadius,
8181
disabled: disabled,
82-
borderColor: (context, isHovering, disabled) {
82+
borderColor: (context, isHovering, disabled, isFocused) {
8383
final theme = AppFlowyTheme.of(context);
8484
if (disabled) {
8585
return theme.fillColorScheme.errorThick;
@@ -118,7 +118,7 @@ class AFOutlinedButton extends StatelessWidget {
118118
padding: padding,
119119
borderRadius: borderRadius,
120120
disabled: true,
121-
borderColor: (context, isHovering, disabled) {
121+
borderColor: (context, isHovering, disabled, isFocused) {
122122
final theme = AppFlowyTheme.of(context);
123123
if (disabled) {
124124
return theme.borderColorScheme.greyTertiary;
@@ -148,7 +148,7 @@ class AFOutlinedButton extends StatelessWidget {
148148
final EdgeInsetsGeometry? padding;
149149
final double? borderRadius;
150150

151-
final AFBaseButtonColorBuilder? borderColor;
151+
final AFBaseButtonBorderColorBuilder? borderColor;
152152
final AFBaseButtonColorBuilder? backgroundColor;
153153

154154
final AFOutlinedButtonWidgetBuilder builder;

frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_icon_text_button.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class AFOutlinedIconTextButton extends StatelessWidget {
4646
borderRadius: borderRadius,
4747
disabled: disabled,
4848
alignment: alignment,
49-
borderColor: (context, isHovering, disabled) {
49+
borderColor: (context, isHovering, disabled, isFocused) {
5050
final theme = AppFlowyTheme.of(context);
5151
if (disabled) {
5252
return theme.borderColorScheme.greyTertiary;
@@ -101,7 +101,7 @@ class AFOutlinedIconTextButton extends StatelessWidget {
101101
borderRadius: borderRadius,
102102
disabled: disabled,
103103
alignment: alignment,
104-
borderColor: (context, isHovering, disabled) {
104+
borderColor: (context, isHovering, disabled, isFocused) {
105105
final theme = AppFlowyTheme.of(context);
106106
if (disabled) {
107107
return theme.fillColorScheme.errorThick;
@@ -156,7 +156,7 @@ class AFOutlinedIconTextButton extends StatelessWidget {
156156
? theme.textColorScheme.tertiary
157157
: theme.textColorScheme.primary;
158158
},
159-
borderColor: (context, isHovering, disabled) {
159+
borderColor: (context, isHovering, disabled, isFocused) {
160160
final theme = AppFlowyTheme.of(context);
161161
if (disabled) {
162162
return theme.borderColorScheme.greyTertiary;
@@ -190,7 +190,7 @@ class AFOutlinedIconTextButton extends StatelessWidget {
190190
final AFOutlinedIconBuilder iconBuilder;
191191

192192
final AFBaseButtonColorBuilder? textColor;
193-
final AFBaseButtonColorBuilder? borderColor;
193+
final AFBaseButtonBorderColorBuilder? borderColor;
194194
final AFBaseButtonColorBuilder? backgroundColor;
195195

196196
@override

frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_text_button.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class AFOutlinedTextButton extends AFBaseTextButton {
3737
borderRadius: borderRadius,
3838
disabled: disabled,
3939
alignment: alignment,
40-
borderColor: (context, isHovering, disabled) {
40+
borderColor: (context, isHovering, disabled, isFocused) {
4141
final theme = AppFlowyTheme.of(context);
4242
if (disabled) {
4343
return theme.borderColorScheme.greyTertiary;
@@ -90,7 +90,7 @@ class AFOutlinedTextButton extends AFBaseTextButton {
9090
borderRadius: borderRadius,
9191
disabled: disabled,
9292
alignment: alignment,
93-
borderColor: (context, isHovering, disabled) {
93+
borderColor: (context, isHovering, disabled, isFocused) {
9494
final theme = AppFlowyTheme.of(context);
9595
if (disabled) {
9696
return theme.fillColorScheme.errorThick;
@@ -143,7 +143,7 @@ class AFOutlinedTextButton extends AFBaseTextButton {
143143
? theme.textColorScheme.tertiary
144144
: theme.textColorScheme.primary;
145145
},
146-
borderColor: (context, isHovering, disabled) {
146+
borderColor: (context, isHovering, disabled, isFocused) {
147147
final theme = AppFlowyTheme.of(context);
148148
if (disabled) {
149149
return theme.borderColorScheme.greyTertiary;
@@ -166,7 +166,7 @@ class AFOutlinedTextButton extends AFBaseTextButton {
166166
);
167167
}
168168

169-
final AFBaseButtonColorBuilder? borderColor;
169+
final AFBaseButtonBorderColorBuilder? borderColor;
170170

171171
@override
172172
Widget build(BuildContext context) {

0 commit comments

Comments
 (0)