// In-place help — the "explain it where it happens" pattern // (usertest: the Add-satellite dialog asked for a name with no hint // of what a satellite is or what the name does). Two pieces: // // • ChainInlineHelp — a calm intro strip at the top of a dialog // or surface: one plain sentence saying what this is and what // will happen, with an optional "Learn more" link into the // full doc sheet. // • ChainFieldHelp — a small "?" affordance to sit next to a // single field's label; tap/hover reveals a one-line // explanation. Use it only where a field genuinely needs it. // // Both are intentionally quiet: help should be present, not loud. import 'package:flutter/material.dart'; import '../theme/tokens.dart'; /// A one-sentence intro strip for the top of a dialog or panel. /// [onLearnMore] wires the "Learn more" link to a doc sheet /// (`showFaiDoc`), shown only when provided. class ChainInlineHelp extends StatelessWidget { final String text; final IconData icon; final VoidCallback? onLearnMore; final String? learnMoreLabel; const ChainInlineHelp({ super.key, required this.text, this.icon = Icons.info_outline, this.onLearnMore, this.learnMoreLabel, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Container( padding: const EdgeInsets.all(ChainSpace.md), decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(ChainRadius.sm), border: Border.all(color: theme.colorScheme.outlineVariant), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 16, color: theme.colorScheme.primary), const SizedBox(width: ChainSpace.sm), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( text, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface, height: 1.4, ), ), if (onLearnMore != null) ...[ const SizedBox(height: ChainSpace.xs), InkWell( onTap: onLearnMore, borderRadius: BorderRadius.circular(ChainRadius.sm), child: Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( learnMoreLabel ?? 'Learn more', style: theme.textTheme.labelSmall?.copyWith( color: theme.colorScheme.primary, fontWeight: FontWeight.w600, ), ), const SizedBox(width: 2), Icon( Icons.arrow_forward, size: 12, color: theme.colorScheme.primary, ), ], ), ), ), ], ], ), ), ], ), ); } } /// A "?" info affordance for a single field. Sit it next to the /// field's label; hover shows the [message] as a tooltip, and a /// tap reveals it too (touch / keyboard users who don't hover). /// Semantics carry [message] for screen readers. class ChainFieldHelp extends StatefulWidget { final String message; const ChainFieldHelp({super.key, required this.message}); @override State createState() => _ChainFieldHelpState(); } class _ChainFieldHelpState extends State { final _tooltipKey = GlobalKey(); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Tooltip( key: _tooltipKey, message: widget.message, waitDuration: const Duration(milliseconds: 300), triggerMode: TooltipTriggerMode.manual, preferBelow: false, child: Semantics( button: true, label: widget.message, child: InkResponse( radius: 14, // Manual trigger so a tap (not just hover) reveals it. onTap: () => _tooltipKey.currentState?.ensureTooltipVisible(), child: Padding( padding: const EdgeInsets.all(2), child: Icon( Icons.help_outline, size: 14, color: theme.colorScheme.onSurfaceVariant, ), ), ), ), ); } } /// A field label with a trailing [ChainFieldHelp]. Convenience for /// the common "label + ?" row above a TextField. class ChainFieldLabel extends StatelessWidget { final String label; final String help; const ChainFieldLabel({super.key, required this.label, required this.help}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Row( children: [ Text( label, style: theme.textTheme.labelMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(width: 4), ChainFieldHelp(message: help), ], ); } }