Files
clide/lib/builtin/shared/reader_chrome.dart
T
jpmschweitzerandClaude Opus 4.8 f83e52b818 reader pin UX: push-pin icon, toggle, left/right split
Per user feedback on the reader action bar: use the push-pin glyph (not
the chain/link), make the pin button toggle the pinned state (tap to pin
current, tap again to unpin) via ReaderNav.togglePin, and split the
layout so the pin/unpin toggle sits on the left while jump-to-pin joins
the navigator (back/forward) on the right — left toggles, right
navigates. The action button gains an active (accent) state for the
pinned indicator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:31:51 +02:00

168 lines
5.0 KiB
Dart

/// Shared action-bar chrome for sidebar reader widgets (T-189, T-190, T-191).
///
/// Provides [ReaderActionBar] — the visible action bar: back, forward, pin,
/// edit pencil. Plugs into a [ClidePaneChrome] via its `trailing:` slot. The
/// retained back/forward history that drives it is the kernel `ReaderNav`
/// (one per right-pane reader); this file is just the buttons.
library;
import 'package:clide/kernel/kernel.dart';
import 'package:clide/widgets/widgets.dart';
import 'package:flutter/widgets.dart';
// ---------------------------------------------------------------------------
// Action bar widget
// ---------------------------------------------------------------------------
/// A row of reader-chrome action buttons. Layout (T-196 UX): a pin/unpin
/// **toggle on the left**, then the **navigator** on the right — back,
/// forward, jump-to-pin (only while pinned) — and the edit pencil last.
/// The left toggles pinned state; the right navigates.
///
/// Designed to plug into [ClidePaneChrome.trailing]. All callbacks are
/// optional — pass null to hide/disable the corresponding button.
class ReaderActionBar extends StatelessWidget {
const ReaderActionBar({
super.key,
required this.canGoBack,
required this.canGoForward,
required this.hasPinned,
required this.onBack,
required this.onForward,
required this.onPin,
required this.onJumpToPin,
required this.onEdit,
});
final bool canGoBack;
final bool canGoForward;
final bool hasPinned;
final VoidCallback? onBack;
final VoidCallback? onForward;
/// Called when the pin toggle is tapped — pins the current entry when
/// nothing is pinned, else clears the pin.
final VoidCallback? onPin;
/// Called when the jump-to-pin button (in the navigator) is tapped.
final VoidCallback? onJumpToPin;
/// Called when the edit pencil is tapped. Pass null to hide the button.
final VoidCallback? onEdit;
@override
Widget build(BuildContext context) {
final tokens = ClideTheme.of(context).surface;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
// Left — toggle the pinned state.
_ActionButton(
painter: PhosphorIcons.pushPin,
tooltip: hasPinned ? 'Unpin' : 'Pin',
enabled: onPin != null,
active: hasPinned,
onTap: onPin,
tokens: tokens,
),
const SizedBox(width: 8),
// Right — the navigator.
_ActionButton(
painter: PhosphorIcons.caretLeft,
tooltip: 'Back',
enabled: canGoBack,
onTap: canGoBack ? onBack : null,
tokens: tokens,
),
const SizedBox(width: 2),
_ActionButton(
painter: PhosphorIcons.caretRight,
tooltip: 'Forward',
enabled: canGoForward,
onTap: canGoForward ? onForward : null,
tokens: tokens,
),
if (hasPinned) ...[
const SizedBox(width: 2),
_ActionButton(
painter: PhosphorIcons.arrowUUpLeft,
tooltip: 'Jump to pin',
enabled: true,
onTap: onJumpToPin,
tokens: tokens,
),
],
if (onEdit != null) ...[
const SizedBox(width: 4),
_ActionButton(
painter: PhosphorIcons.pencilSimple,
tooltip: 'Edit in editor',
enabled: true,
onTap: onEdit,
tokens: tokens,
),
],
],
);
}
}
// ---------------------------------------------------------------------------
// Private button widget
// ---------------------------------------------------------------------------
class _ActionButton extends StatelessWidget {
const _ActionButton({
required this.painter,
required this.tooltip,
required this.enabled,
required this.onTap,
required this.tokens,
this.active = false,
});
final ClideIconPainter painter;
final String tooltip;
final bool enabled;
/// Renders the glyph in the focus/accent colour to signal an on state
/// (used by the pin toggle when something is pinned).
final bool active;
final VoidCallback? onTap;
final SurfaceTokens tokens;
@override
Widget build(BuildContext context) {
final Color color;
if (!enabled) {
color = tokens.globalTextMuted;
} else if (active) {
color = tokens.globalFocus;
} else {
color = tokens.panelHeaderForeground;
}
return Semantics(
button: true,
label: tooltip,
enabled: enabled,
toggled: active,
onTap: onTap,
excludeSemantics: true,
child: ClideTappable(
onTap: onTap,
tooltip: tooltip,
builder: (ctx, hovered, _) => Container(
width: 20,
height: 20,
alignment: Alignment.center,
decoration: BoxDecoration(
color: hovered && enabled ? tokens.sidebarItemHover : null,
borderRadius: BorderRadius.circular(3),
),
child: ClideIcon(painter, size: 11, color: color),
),
),
);
}
}