implement collapse spine, focus mode, keyboard shortcuts, context rail
Collapsed side panels render as a 12px vertical spine with rotated label, hover highlight, and badge dot (D-051). Focus mode snapshots and restores the full layout arrangement (D-052). Fourteen new commands in the default-layout extension: collapse toggles (Ctrl+Shift+1/3), panel focus (Ctrl+1/2/3), focus mode (Ctrl+.), sidebar section switching (Alt+1-5), Escape dismiss (D-054). Right panel gets a bottom icon rail matching the sidebar pattern (D-047). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:clide_app/kernel/src/theme/controller.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ClideSpine extends StatefulWidget {
|
||||
const ClideSpine({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.onExpand,
|
||||
this.side = SpineSide.left,
|
||||
this.badgeCount = 0,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final VoidCallback onExpand;
|
||||
final SpineSide side;
|
||||
final int badgeCount;
|
||||
|
||||
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;
|
||||
final borderSide = BorderSide(color: tokens.dividerColor);
|
||||
|
||||
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.sidebarBackground,
|
||||
border: Border(
|
||||
left: widget.side == SpineSide.right ? borderSide : BorderSide.none,
|
||||
right: widget.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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user