Viewer: round icon buttons with backgrounds; plain text follows Edit -> Save too

Edit/Preview and Save are now compact (36px) icon-only NooButtons - primary when
alone, Edit/Preview secondary beside a primary Save, a spinner circle while
saving. Plain text opens read-only like Markdown and gets the same toggle.
NooButton gains an opt-in iconOnly mode.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Ayushya Amitabh
2026-10-06 20:50:26 -04:00
co-authored by Claude Sonnet 5.5
parent 5fa20ff3e9
commit 1634d34341
5 changed files with 169 additions and 40 deletions
+27 -6
View File
@@ -1,7 +1,15 @@
import 'package:flutter/material.dart';
import '../../../theme/design_tokens.dart';
enum NooButtonVariant { primary, tonal, secondary, danger, outline, text, textDanger }
enum NooButtonVariant {
primary,
tonal,
secondary,
danger,
outline,
text,
textDanger,
}
enum NooButtonSize { cta, card, field, toolbar, compact, xs }
@@ -37,6 +45,11 @@ class NooButton extends StatefulWidget {
final bool disabled;
final VoidCallback? onTap;
/// A round, icon-only button (no label) - [icon] centred in a circle as
/// wide as the size's height, instead of the pill's side padding. Callers
/// are responsible for a tooltip/semantic label, since there's no text.
final bool iconOnly;
const NooButton({
super.key,
this.variant = NooButtonVariant.primary,
@@ -46,6 +59,7 @@ class NooButton extends StatefulWidget {
this.fullWidth = false,
this.disabled = false,
this.onTap,
this.iconOnly = false,
});
@override
@@ -87,7 +101,8 @@ class _NooButtonState extends State<NooButton> {
final colors = context.nooColors;
final s = _sizes[widget.size]!;
final (bg, fg, border) = _colors(colors);
final hasLeadingIcon = widget.icon != null && widget.child != null && !_isTextVariant;
final hasLeadingIcon =
widget.icon != null && widget.child != null && !_isTextVariant;
final content = Row(
mainAxisSize: MainAxisSize.min,
@@ -107,7 +122,9 @@ class _NooButtonState extends State<NooButton> {
child: GestureDetector(
onTapDown: widget.disabled ? null : (_) => setState(() => _down = true),
onTapUp: widget.disabled ? null : (_) => setState(() => _down = false),
onTapCancel: widget.disabled ? null : () => setState(() => _down = false),
onTapCancel: widget.disabled
? null
: () => setState(() => _down = false),
onTap: widget.disabled ? null : widget.onTap,
child: AnimatedScale(
scale: _down && !widget.disabled ? NooMotion.pressScale : 1,
@@ -115,8 +132,10 @@ class _NooButtonState extends State<NooButton> {
curve: NooMotion.ease,
child: Container(
height: s.height,
width: widget.fullWidth ? double.infinity : null,
padding: _isTextVariant
width: widget.iconOnly
? s.height
: (widget.fullWidth ? double.infinity : null),
padding: _isTextVariant || widget.iconOnly
? EdgeInsets.zero
: EdgeInsets.only(
left: hasLeadingIcon ? s.px - 4 : s.px,
@@ -128,7 +147,9 @@ class _NooButtonState extends State<NooButton> {
// of shrink-wrapping to it, which is what stretched this button
// edge-to-edge instead of sizing to its content (see NooFab's
// doc comment for the same bug).
alignment: widget.fullWidth ? Alignment.center : null,
alignment: widget.fullWidth || widget.iconOnly
? Alignment.center
: null,
decoration: BoxDecoration(
color: bg,
borderRadius: BorderRadius.circular(NooRadii.pill),