mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 14:47:13 -04:00
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:
parent
2e295e6891
commit
64a2d2de32
9 changed files with 113 additions and 37 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue