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
This commit is contained in:
Richard Shiue 2025-04-11 11:10:17 +08:00 committed by GitHub
parent 351c891a5a
commit cc3f537932
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 113 additions and 37 deletions

View file

@ -7,6 +7,13 @@ typedef AFBaseButtonColorBuilder = Color Function(
bool disabled,
);
typedef AFBaseButtonBorderColorBuilder = Color Function(
BuildContext context,
bool isHovering,
bool disabled,
bool isFocused,
);
class AFBaseButton extends StatefulWidget {
const AFBaseButton({
super.key,
@ -16,49 +23,99 @@ class AFBaseButton extends StatefulWidget {
required this.borderRadius,
this.borderColor,
this.backgroundColor,
this.ringColor,
this.disabled = false,
});
final VoidCallback? onTap;
final AFBaseButtonColorBuilder? borderColor;
final AFBaseButtonBorderColorBuilder? borderColor;
final AFBaseButtonBorderColorBuilder? ringColor;
final AFBaseButtonColorBuilder? backgroundColor;
final EdgeInsetsGeometry padding;
final double borderRadius;
final bool disabled;
final Widget Function(BuildContext context, bool isHovering, bool disabled)
builder;
final Widget Function(
BuildContext context,
bool isHovering,
bool disabled,
) builder;
@override
State<AFBaseButton> createState() => _AFBaseButtonState();
}
class _AFBaseButtonState extends State<AFBaseButton> {
final FocusNode focusNode = FocusNode();
bool isHovering = false;
bool isFocused = false;
@override
void dispose() {
focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final Color borderColor = _buildBorderColor(context);
final Color backgroundColor = _buildBackgroundColor(context);
final Color ringColor = _buildRingColor(context);
return MouseRegion(
cursor:
widget.disabled ? SystemMouseCursors.basic : SystemMouseCursors.click,
onEnter: (_) => setState(() => isHovering = true),
onExit: (_) => setState(() => isHovering = false),
child: GestureDetector(
onTap: widget.disabled ? null : widget.onTap,
child: DecoratedBox(
decoration: BoxDecoration(
color: backgroundColor,
border: Border.all(color: borderColor),
borderRadius: BorderRadius.circular(widget.borderRadius),
),
child: Padding(
padding: widget.padding,
child: widget.builder(context, isHovering, widget.disabled),
return Actions(
actions: {
ActivateIntent: CallbackAction<ActivateIntent>(
onInvoke: (_) {
if (!widget.disabled) {
widget.onTap?.call();
}
return;
},
),
},
child: Focus(
focusNode: focusNode,
onFocusChange: (isFocused) {
setState(() => this.isFocused = isFocused);
},
child: MouseRegion(
cursor: widget.disabled
? SystemMouseCursors.basic
: SystemMouseCursors.click,
onEnter: (_) => setState(() => isHovering = true),
onExit: (_) => setState(() => isHovering = false),
child: GestureDetector(
onTap: widget.disabled ? null : widget.onTap,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(widget.borderRadius),
border: isFocused
? Border.all(
color: ringColor,
width: 2,
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
),
child: DecoratedBox(
decoration: BoxDecoration(
color: backgroundColor,
border: Border.all(color: borderColor),
borderRadius: BorderRadius.circular(widget.borderRadius),
),
child: Padding(
padding: widget.padding,
child: widget.builder(
context,
isHovering,
widget.disabled,
),
),
),
),
),
),
),
@ -67,7 +124,8 @@ class _AFBaseButtonState extends State<AFBaseButton> {
Color _buildBorderColor(BuildContext context) {
final theme = AppFlowyTheme.of(context);
return widget.borderColor?.call(context, isHovering, widget.disabled) ??
return widget.borderColor
?.call(context, isHovering, widget.disabled, isFocused) ??
theme.borderColorScheme.greyTertiary;
}
@ -76,4 +134,22 @@ class _AFBaseButtonState extends State<AFBaseButton> {
return widget.backgroundColor?.call(context, isHovering, widget.disabled) ??
theme.fillColorScheme.transparent;
}
Color _buildRingColor(BuildContext context) {
final theme = AppFlowyTheme.of(context);
if (widget.ringColor != null) {
return widget.ringColor!
.call(context, isHovering, widget.disabled, isFocused);
}
if (isFocused) {
return AppFlowyTheme.of(context)
.borderColorScheme
.themeThick
.withAlpha(128);
}
return theme.borderColorScheme.transparent;
}
}

View file

@ -115,7 +115,7 @@ class AFFilledButton extends StatelessWidget {
return AFBaseButton(
disabled: disabled,
backgroundColor: backgroundColor,
borderColor: (_, __, ___) => Colors.transparent,
borderColor: (_, __, ___, ____) => Colors.transparent,
padding: padding ?? size.buildPadding(context),
borderRadius: borderRadius ?? size.buildBorderRadius(context),
onTap: onTap,

View file

@ -114,7 +114,7 @@ class AFFilledTextButton extends AFBaseTextButton {
return AFBaseButton(
disabled: disabled,
backgroundColor: backgroundColor,
borderColor: (_, __, ___) => Colors.transparent,
borderColor: (_, __, ___, ____) => Colors.transparent,
padding: padding ?? size.buildPadding(context),
borderRadius: borderRadius ?? size.buildBorderRadius(context),
onTap: onTap,

View file

@ -86,7 +86,7 @@ class AFGhostButton extends StatelessWidget {
return AFBaseButton(
disabled: disabled,
backgroundColor: backgroundColor,
borderColor: (_, __, ___) => Colors.transparent,
borderColor: (_, __, ___, ____) => Colors.transparent,
padding: padding ?? size.buildPadding(context),
borderRadius: borderRadius ?? size.buildBorderRadius(context),
onTap: onTap,

View file

@ -109,7 +109,7 @@ class AFGhostIconTextButton extends StatelessWidget {
return AFBaseButton(
disabled: disabled,
backgroundColor: backgroundColor,
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
return Colors.transparent;
},
padding: padding ?? size.buildPadding(context),

View file

@ -88,7 +88,7 @@ class AFGhostTextButton extends AFBaseTextButton {
return AFBaseButton(
disabled: disabled,
backgroundColor: backgroundColor,
borderColor: (_, __, ___) => Colors.transparent,
borderColor: (_, __, ___, ____) => Colors.transparent,
padding: padding ?? size.buildPadding(context),
borderRadius: borderRadius ?? size.buildBorderRadius(context),
onTap: onTap,

View file

@ -38,7 +38,7 @@ class AFOutlinedButton extends StatelessWidget {
padding: padding,
borderRadius: borderRadius,
disabled: disabled,
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
final theme = AppFlowyTheme.of(context);
if (disabled) {
return theme.borderColorScheme.greyTertiary;
@ -79,7 +79,7 @@ class AFOutlinedButton extends StatelessWidget {
padding: padding,
borderRadius: borderRadius,
disabled: disabled,
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
final theme = AppFlowyTheme.of(context);
if (disabled) {
return theme.fillColorScheme.errorThick;
@ -118,7 +118,7 @@ class AFOutlinedButton extends StatelessWidget {
padding: padding,
borderRadius: borderRadius,
disabled: true,
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
final theme = AppFlowyTheme.of(context);
if (disabled) {
return theme.borderColorScheme.greyTertiary;
@ -148,7 +148,7 @@ class AFOutlinedButton extends StatelessWidget {
final EdgeInsetsGeometry? padding;
final double? borderRadius;
final AFBaseButtonColorBuilder? borderColor;
final AFBaseButtonBorderColorBuilder? borderColor;
final AFBaseButtonColorBuilder? backgroundColor;
final AFOutlinedButtonWidgetBuilder builder;

View file

@ -46,7 +46,7 @@ class AFOutlinedIconTextButton extends StatelessWidget {
borderRadius: borderRadius,
disabled: disabled,
alignment: alignment,
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
final theme = AppFlowyTheme.of(context);
if (disabled) {
return theme.borderColorScheme.greyTertiary;
@ -101,7 +101,7 @@ class AFOutlinedIconTextButton extends StatelessWidget {
borderRadius: borderRadius,
disabled: disabled,
alignment: alignment,
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
final theme = AppFlowyTheme.of(context);
if (disabled) {
return theme.fillColorScheme.errorThick;
@ -156,7 +156,7 @@ class AFOutlinedIconTextButton extends StatelessWidget {
? theme.textColorScheme.tertiary
: theme.textColorScheme.primary;
},
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
final theme = AppFlowyTheme.of(context);
if (disabled) {
return theme.borderColorScheme.greyTertiary;
@ -190,7 +190,7 @@ class AFOutlinedIconTextButton extends StatelessWidget {
final AFOutlinedIconBuilder iconBuilder;
final AFBaseButtonColorBuilder? textColor;
final AFBaseButtonColorBuilder? borderColor;
final AFBaseButtonBorderColorBuilder? borderColor;
final AFBaseButtonColorBuilder? backgroundColor;
@override

View file

@ -37,7 +37,7 @@ class AFOutlinedTextButton extends AFBaseTextButton {
borderRadius: borderRadius,
disabled: disabled,
alignment: alignment,
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
final theme = AppFlowyTheme.of(context);
if (disabled) {
return theme.borderColorScheme.greyTertiary;
@ -90,7 +90,7 @@ class AFOutlinedTextButton extends AFBaseTextButton {
borderRadius: borderRadius,
disabled: disabled,
alignment: alignment,
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
final theme = AppFlowyTheme.of(context);
if (disabled) {
return theme.fillColorScheme.errorThick;
@ -143,7 +143,7 @@ class AFOutlinedTextButton extends AFBaseTextButton {
? theme.textColorScheme.tertiary
: theme.textColorScheme.primary;
},
borderColor: (context, isHovering, disabled) {
borderColor: (context, isHovering, disabled, isFocused) {
final theme = AppFlowyTheme.of(context);
if (disabled) {
return theme.borderColorScheme.greyTertiary;
@ -166,7 +166,7 @@ class AFOutlinedTextButton extends AFBaseTextButton {
);
}
final AFBaseButtonColorBuilder? borderColor;
final AFBaseButtonBorderColorBuilder? borderColor;
@override
Widget build(BuildContext context) {