refactor(ui): one canonical segment control (ChainSegments) everywhere
Some checks failed
Security / Security check (push) Failing after 1s
Some checks failed
Security / Security check (push) Failing after 1s
The same single-select choice pattern appeared as four widgets: audit's hover pills, the store's SegmentedButton, the store filter dialog's ChoiceChips, and the approvals TabBar (usertest finding #14 / night-log decision 'pill segment as canon'). The audit pattern is promoted to a shared ChainSegments widget (optional icons, hover, selected border, button+selected semantics) and all four sites use it; approvals switches lists via IndexedStack so both stay alive and switching does not refetch. Guard per the no-bugfix-without-a-guard rule: widget tests for selection + semantics, plus a canon sweep that bans TabBar/TabBarView/TabController/SegmentedButton/ChoiceChip from lib/ (comments exempt). Deliberately out of scope: the flow editor's Graph/Text/Run tabs live in the separate editor package. Studio 0.75.0; guide images regenerated, dark + light verified. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
bfd58baa75
commit
ed680c507a
9 changed files with 311 additions and 147 deletions
|
|
@ -634,86 +634,10 @@ class _FilterChips extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final l = AppLocalizations.of(context)!;
|
||||
final items = _filterItems(l);
|
||||
return Row(
|
||||
children: [
|
||||
for (final (v, label) in items)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: ChainSpace.xs),
|
||||
child: _ChipButton(
|
||||
label: label,
|
||||
selected: value == v,
|
||||
onTap: () => onChanged(v),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChipButton extends StatefulWidget {
|
||||
final String label;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _ChipButton({
|
||||
required this.label,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_ChipButton> createState() => _ChipButtonState();
|
||||
}
|
||||
|
||||
class _ChipButtonState extends State<_ChipButton> {
|
||||
bool _hovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final fg = widget.selected
|
||||
? theme.colorScheme.primary
|
||||
: _hovered
|
||||
? theme.colorScheme.onSurface
|
||||
: theme.colorScheme.onSurfaceVariant;
|
||||
final bg = widget.selected
|
||||
? theme.colorScheme.primary.withValues(alpha: 0.12)
|
||||
: _hovered
|
||||
? theme.colorScheme.surfaceContainerHigh
|
||||
: Colors.transparent;
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _hovered = true),
|
||||
onExit: (_) => setState(() => _hovered = false),
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: ChainMotion.fast,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: ChainSpace.md,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||
border: Border.all(
|
||||
color: widget.selected
|
||||
? theme.colorScheme.primary.withValues(alpha: 0.3)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
widget.label,
|
||||
// UI label, not a value — mono is reserved for
|
||||
// paths/identifiers (usertest finding: the chips
|
||||
// read as foreign bodies in the header).
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight: widget.selected ? FontWeight.w600 : FontWeight.w400,
|
||||
color: fg,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
return ChainSegments<String>(
|
||||
items: [for (final (v, label) in items) ChainSegmentItem(v, label)],
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue