import 'package:flutter/material.dart'; import '../theme/tokens.dart'; /// A thin-stroked panel with its title label sitting *on* the top border — /// Ratune's signature container ("Album Art", "Queue (127)", "Lyrics", /// "Visualizer"). Recreated with the fieldset trick: draw a full 1px border, /// then overlay the title with a background that occludes the segment behind /// it, so the label appears to break the line. class HairlinePanel extends StatelessWidget { const HairlinePanel({ super.key, required this.title, required this.child, this.trailing, this.active = false, this.padding = const EdgeInsets.all(RatuneSpacing.lg), this.backgroundColor = RatuneColors.background, }); /// Panel label, e.g. "Queue". Rendered uppercase-ish in mono. final String title; /// Optional trailing bit of the label, e.g. "(127)" — dimmed. final String? trailing; /// Panel content. final Widget child; /// When focused, the border and title tint toward the accent. final bool active; final EdgeInsetsGeometry padding; /// Color painted behind the title to "cut" the border. Must match whatever /// sits behind this panel (the canvas by default). final Color backgroundColor; static const double _titleStraddle = 8; @override Widget build(BuildContext context) { final borderColor = active ? RatuneColors.borderActive : RatuneColors.border; final titleColor = active ? Theme.of(context).colorScheme.primary : RatuneColors.dimmed; return Stack( clipBehavior: Clip.none, children: [ Container( margin: const EdgeInsets.only(top: _titleStraddle), decoration: BoxDecoration( border: Border.all(color: borderColor, width: 1), ), child: Padding(padding: padding, child: child), ), Positioned( left: RatuneSpacing.lg, top: 0, child: Container( color: backgroundColor, padding: const EdgeInsets.symmetric( horizontal: RatuneSpacing.md, ), child: Text.rich( TextSpan( children: [ TextSpan( text: title, style: TextStyle( color: titleColor, fontWeight: FontWeight.w700, letterSpacing: 0.5, ), ), if (trailing != null) TextSpan( text: ' $trailing', style: const TextStyle(color: RatuneColors.dimmed), ), ], ), style: Theme.of(context).textTheme.labelMedium, ), ), ), ], ); } }