extend ClideTappable with press/longpress, retrofit button + rail
ClideTappable builder now receives (context, hovered, pressed). Optional onLongPress and onPressChanged callbacks. ClideButton is now a StatelessWidget wrapping ClideTappable. ClideIconRail refactored — _RailButton uses ClideTappable with tooltip. ClideTooltip positions smart (above when near screen bottom). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import 'package:clide/kernel/src/theme/controller.dart';
|
||||
import 'package:clide/widgets/src/clide_tappable.dart';
|
||||
import 'package:clide/widgets/src/clide_text.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum ClideButtonVariant { normal, primary, subtle }
|
||||
|
||||
class ClideButton extends StatefulWidget {
|
||||
class ClideButton extends StatelessWidget {
|
||||
const ClideButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
@@ -13,92 +14,66 @@ class ClideButton extends StatefulWidget {
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
this.semanticLabel,
|
||||
this.semanticHint,
|
||||
this.tooltip,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final VoidCallback? onPressed;
|
||||
final ClideButtonVariant variant;
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
/// Overrides [label] for screen readers (use when the visible label is
|
||||
/// an icon-only glyph or a noun that reads oddly when announced).
|
||||
final String? semanticLabel;
|
||||
|
||||
/// Screen-reader hint describing the button's effect. Optional.
|
||||
final String? semanticHint;
|
||||
|
||||
@override
|
||||
State<ClideButton> createState() => _ClideButtonState();
|
||||
}
|
||||
|
||||
class _ClideButtonState extends State<ClideButton> {
|
||||
bool _hovered = false;
|
||||
bool _pressed = false;
|
||||
final String? tooltip;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
final enabled = widget.onPressed != null;
|
||||
|
||||
Color bg;
|
||||
Color fg;
|
||||
switch (widget.variant) {
|
||||
case ClideButtonVariant.normal:
|
||||
bg = _pressed
|
||||
? tokens.buttonActiveBackground
|
||||
: _hovered
|
||||
? tokens.buttonHoverBackground
|
||||
: tokens.buttonBackground;
|
||||
fg = tokens.buttonForeground;
|
||||
case ClideButtonVariant.primary:
|
||||
bg = _pressed
|
||||
? tokens.panelActiveBorder
|
||||
: _hovered
|
||||
? tokens.buttonActiveBackground
|
||||
: tokens.buttonActiveBackground;
|
||||
fg = tokens.globalBackground;
|
||||
case ClideButtonVariant.subtle:
|
||||
bg = _hovered
|
||||
? tokens.listItemHoverBackground
|
||||
: tokens.listItemBackground;
|
||||
fg = tokens.listItemForeground;
|
||||
}
|
||||
final enabled = onPressed != null;
|
||||
|
||||
return Semantics(
|
||||
button: true,
|
||||
enabled: enabled,
|
||||
label: widget.semanticLabel ?? widget.label,
|
||||
hint: widget.semanticHint,
|
||||
onTap: enabled ? widget.onPressed : null,
|
||||
label: semanticLabel ?? label,
|
||||
hint: semanticHint,
|
||||
onTap: enabled ? onPressed : null,
|
||||
excludeSemantics: true,
|
||||
child: MouseRegion(
|
||||
cursor:
|
||||
enabled ? SystemMouseCursors.click : SystemMouseCursors.forbidden,
|
||||
onEnter: (_) => setState(() => _hovered = true),
|
||||
onExit: (_) => setState(() => _hovered = false),
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _pressed = true),
|
||||
onTapCancel: () => setState(() => _pressed = false),
|
||||
onTapUp: (_) {
|
||||
setState(() => _pressed = false);
|
||||
widget.onPressed?.call();
|
||||
},
|
||||
child: Container(
|
||||
padding: widget.padding,
|
||||
child: ClideTappable(
|
||||
cursor: enabled ? SystemMouseCursors.click : SystemMouseCursors.forbidden,
|
||||
tooltip: tooltip,
|
||||
onTap: onPressed,
|
||||
builder: (ctx, hovered, pressed) {
|
||||
Color bg;
|
||||
Color fg;
|
||||
switch (variant) {
|
||||
case ClideButtonVariant.normal:
|
||||
bg = pressed
|
||||
? tokens.buttonActiveBackground
|
||||
: hovered
|
||||
? tokens.buttonHoverBackground
|
||||
: tokens.buttonBackground;
|
||||
fg = tokens.buttonForeground;
|
||||
case ClideButtonVariant.primary:
|
||||
bg = pressed ? tokens.panelActiveBorder : tokens.buttonActiveBackground;
|
||||
fg = tokens.globalBackground;
|
||||
case ClideButtonVariant.subtle:
|
||||
bg = hovered ? tokens.listItemHoverBackground : tokens.listItemBackground;
|
||||
fg = tokens.listItemForeground;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
border: Border.all(color: tokens.buttonBorder, width: 1),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: ClideText(
|
||||
widget.label,
|
||||
label,
|
||||
color: fg,
|
||||
fontWeight: widget.variant == ClideButtonVariant.primary
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
fontWeight: variant == ClideButtonVariant.primary ? FontWeight.w600 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user