sweep ClideTappable across 15 files, remove 267 lines
23 StatefulWidget+State hover pairs converted to StatelessWidget using ClideTappable(builder: (ctx, hovered, _) => ...). Covers app.dart (5), welcome (2), claude session host (2), column hat (2), spine (1), pane chrome (1), tab bar (1), decisions (1), tickets (1), graph (1), pql panel (1), backlinks (1), file tree (2), git panel (1), git status (1). Only EditorDragHandle and ClidePalette excluded (drag/keyboard behavior beyond hover+tap). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:clide/kernel/src/theme/controller.dart';
|
||||
import 'package:clide/kernel/src/theme/tokens.dart';
|
||||
import 'package:clide/kernel/src/window_controls.dart';
|
||||
import 'package:clide/widgets/src/clide_icon.dart';
|
||||
import 'package:clide/widgets/src/clide_tappable.dart';
|
||||
import 'package:clide/widgets/src/clide_text.dart';
|
||||
import 'package:clide/widgets/src/icons/phosphor.dart';
|
||||
import 'package:clide/widgets/src/typography.dart';
|
||||
@@ -113,69 +114,45 @@ class _RightContent extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _TrafficDot extends StatefulWidget {
|
||||
class _TrafficDot extends StatelessWidget {
|
||||
const _TrafficDot({required this.color, required this.onTap});
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
State<_TrafficDot> createState() => _TrafficDotState();
|
||||
}
|
||||
|
||||
class _TrafficDotState extends State<_TrafficDot> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _hover = true),
|
||||
onExit: (_) => setState(() => _hover = false),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.onTap,
|
||||
child: Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: _hover ? widget.color : widget.color.withAlpha(0xCC),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
return ClideTappable(
|
||||
onTap: onTap,
|
||||
builder: (context, hovered, _) => Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: hovered ? color : color.withAlpha(0xCC),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WinButton extends StatefulWidget {
|
||||
class _WinButton extends StatelessWidget {
|
||||
const _WinButton({required this.icon, required this.onTap, required this.tokens, this.isClose = false});
|
||||
final ClideIconPainter icon;
|
||||
final VoidCallback onTap;
|
||||
final SurfaceTokens tokens;
|
||||
final bool isClose;
|
||||
|
||||
@override
|
||||
State<_WinButton> createState() => _WinButtonState();
|
||||
}
|
||||
|
||||
class _WinButtonState extends State<_WinButton> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hoverBg = widget.isClose ? const Color(0xFFE81123) : widget.tokens.listItemHoverBackground;
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _hover = true),
|
||||
onExit: (_) => setState(() => _hover = false),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.onTap,
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: hatHeight,
|
||||
color: _hover ? hoverBg : null,
|
||||
alignment: Alignment.center,
|
||||
child: ClideIcon(widget.icon, size: 14, color: _hover && widget.isClose ? const Color(0xFFFFFFFF) : widget.tokens.globalTextMuted),
|
||||
),
|
||||
final hoverBg = isClose ? const Color(0xFFE81123) : tokens.listItemHoverBackground;
|
||||
return ClideTappable(
|
||||
onTap: onTap,
|
||||
builder: (context, hovered, _) => Container(
|
||||
width: 36,
|
||||
height: hatHeight,
|
||||
color: hovered ? hoverBg : null,
|
||||
alignment: Alignment.center,
|
||||
child: ClideIcon(icon, size: 14, color: hovered && isClose ? const Color(0xFFFFFFFF) : tokens.globalTextMuted),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'clide_divider.dart';
|
||||
import 'clide_icon.dart';
|
||||
import 'clide_tappable.dart';
|
||||
import 'clide_text.dart';
|
||||
import 'icons/x.dart';
|
||||
import 'typography.dart';
|
||||
@@ -67,43 +68,31 @@ class ClidePaneChrome extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _CloseButton extends StatefulWidget {
|
||||
class _CloseButton extends StatelessWidget {
|
||||
const _CloseButton({required this.onPressed});
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
State<_CloseButton> createState() => _CloseButtonState();
|
||||
}
|
||||
|
||||
class _CloseButtonState extends State<_CloseButton> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: 'Close pane',
|
||||
onTap: widget.onPressed,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (_) => setState(() => _hover = true),
|
||||
onExit: (_) => setState(() => _hover = false),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.onPressed,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: _hover ? tokens.tabCloseHover : null,
|
||||
),
|
||||
child: ClideIcon(
|
||||
const CloseIcon(),
|
||||
size: 10,
|
||||
color: tokens.panelHeaderForeground,
|
||||
),
|
||||
onTap: onPressed,
|
||||
child: ClideTappable(
|
||||
onTap: onPressed,
|
||||
tooltip: 'Close pane',
|
||||
builder: (context, hovered, _) => Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: hovered ? tokens.tabCloseHover : null,
|
||||
),
|
||||
child: ClideIcon(
|
||||
const CloseIcon(),
|
||||
size: 10,
|
||||
color: tokens.panelHeaderForeground,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:clide/kernel/src/theme/controller.dart';
|
||||
import 'package:clide/widgets/src/clide_tappable.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ClideSpine extends StatefulWidget {
|
||||
class ClideSpine extends StatelessWidget {
|
||||
const ClideSpine({
|
||||
super.key,
|
||||
required this.label,
|
||||
@@ -19,15 +20,6 @@ class ClideSpine extends StatefulWidget {
|
||||
|
||||
static const double width = 12;
|
||||
|
||||
@override
|
||||
State<ClideSpine> createState() => _ClideSpineState();
|
||||
}
|
||||
|
||||
enum SpineSide { left, right }
|
||||
|
||||
class _ClideSpineState extends State<ClideSpine> {
|
||||
bool _hovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
@@ -35,60 +27,57 @@ class _ClideSpineState extends State<ClideSpine> {
|
||||
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: '${widget.label} — click to expand',
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (_) => setState(() => _hovered = true),
|
||||
onExit: (_) => setState(() => _hovered = false),
|
||||
child: GestureDetector(
|
||||
onTap: widget.onExpand,
|
||||
child: Container(
|
||||
width: ClideSpine.width,
|
||||
decoration: BoxDecoration(
|
||||
color: _hovered ? tokens.sidebarItemHover : tokens.chromeBackground,
|
||||
border: Border(
|
||||
left: widget.side == SpineSide.right ? borderSide : BorderSide.none,
|
||||
right: widget.side == SpineSide.left ? borderSide : BorderSide.none,
|
||||
),
|
||||
label: '$label — click to expand',
|
||||
child: ClideTappable(
|
||||
onTap: onExpand,
|
||||
builder: (context, hovered, _) => Container(
|
||||
width: ClideSpine.width,
|
||||
decoration: BoxDecoration(
|
||||
color: hovered ? tokens.sidebarItemHover : tokens.chromeBackground,
|
||||
border: Border(
|
||||
left: side == SpineSide.right ? borderSide : BorderSide.none,
|
||||
right: side == SpineSide.left ? borderSide : BorderSide.none,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: Transform.rotate(
|
||||
angle: widget.side == SpineSide.left ? -math.pi / 2 : math.pi / 2,
|
||||
child: Text(
|
||||
widget.label,
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color: tokens.globalTextMuted,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: Transform.rotate(
|
||||
angle: side == SpineSide.left ? -math.pi / 2 : math.pi / 2,
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color: tokens.globalTextMuted,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (badgeCount > 0)
|
||||
Positioned(
|
||||
top: 4,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: tokens.statusInfo,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.badgeCount > 0)
|
||||
Positioned(
|
||||
top: 4,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: tokens.statusInfo,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum SpineSide { left, right }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:clide/kernel/src/theme/controller.dart';
|
||||
import 'package:clide/widgets/src/clide_icon.dart';
|
||||
import 'package:clide/widgets/src/clide_tappable.dart';
|
||||
import 'package:clide/widgets/src/clide_text.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@@ -59,51 +60,35 @@ class ClideTabBar extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _Tab extends StatefulWidget {
|
||||
class _Tab extends StatelessWidget {
|
||||
const _Tab({required this.item, required this.active, required this.onTap});
|
||||
|
||||
final ClideTabItem item;
|
||||
final bool active;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
State<_Tab> createState() => _TabState();
|
||||
}
|
||||
|
||||
class _TabState extends State<_Tab> {
|
||||
bool _hovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
final bg = widget.active
|
||||
? tokens.tabActive
|
||||
: (_hovered ? tokens.tabInactive : tokens.tabInactive);
|
||||
final fg = widget.active
|
||||
? tokens.tabActiveForeground
|
||||
: tokens.tabInactiveForeground;
|
||||
final fg = active ? tokens.tabActiveForeground : tokens.tabInactiveForeground;
|
||||
|
||||
return Semantics(
|
||||
button: true,
|
||||
selected: widget.active,
|
||||
label: widget.item.title,
|
||||
onTap: widget.onTap,
|
||||
selected: active,
|
||||
label: item.title,
|
||||
onTap: onTap,
|
||||
excludeSemantics: true,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (_) => setState(() => _hovered = true),
|
||||
onExit: (_) => setState(() => _hovered = false),
|
||||
child: GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: Container(
|
||||
child: ClideTappable(
|
||||
onTap: onTap,
|
||||
builder: (context, hovered, _) {
|
||||
final bg = active ? tokens.tabActive : tokens.tabInactive;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: widget.active
|
||||
? tokens.tabActiveBorder
|
||||
: const Color(0x00000000),
|
||||
color: active ? tokens.tabActiveBorder : const Color(0x00000000),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
@@ -111,15 +96,15 @@ class _TabState extends State<_Tab> {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.item.icon != null) ...[
|
||||
ClideIcon(widget.item.icon!, size: 12, color: fg),
|
||||
if (item.icon != null) ...[
|
||||
ClideIcon(item.icon!, size: 12, color: fg),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
ClideText(widget.item.title, color: fg),
|
||||
ClideText(item.title, color: fg),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user