polish(ux): selected filter chips fill solid for clear contrast
The selected state used a pale tonal container (seedPrimaryContainer) that sat almost on top of the pale canvas, so 'on' barely read. Selected chips now fill solid — seedGreen for the plain attribute/market chips and the swatch's own ink for family/form chips — with white label, icon and checkmark. Idle chips stay light with dark ink, so the on/off jump is unmistakable. White-on-ink AA is locked by a new contrast test.
This commit is contained in:
parent
805f1dc535
commit
4a1e72bc7e
5 changed files with 53 additions and 12 deletions
|
|
@ -164,9 +164,9 @@ class _MonthStripState extends State<_MonthStrip> {
|
|||
),
|
||||
selected: selected,
|
||||
onSelected: (_) => widget.onSelect(m),
|
||||
selectedColor: seedPrimaryContainer,
|
||||
selectedColor: seedGreen,
|
||||
labelStyle: TextStyle(
|
||||
color: selected ? seedOnPrimaryContainer : seedMuted,
|
||||
color: selected ? Colors.white : seedMuted,
|
||||
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ class _FilterBar extends StatelessWidget {
|
|||
avatar: Icon(
|
||||
Icons.calendar_month,
|
||||
size: 18,
|
||||
color: state.sowThisMonthOnly ? null : seedGreen,
|
||||
color: state.sowThisMonthOnly ? Colors.white : seedGreen,
|
||||
),
|
||||
label: Text(t.calendar.filterChip),
|
||||
selected: state.sowThisMonthOnly,
|
||||
|
|
@ -320,7 +320,7 @@ class _FilterBar extends StatelessWidget {
|
|||
avatar: Icon(
|
||||
Icons.volunteer_activism_outlined,
|
||||
size: 18,
|
||||
color: state.sharingOnly ? null : seedGreen,
|
||||
color: state.sharingOnly ? Colors.white : seedGreen,
|
||||
),
|
||||
label: Text(t.share.filterChip),
|
||||
selected: state.sharingOnly,
|
||||
|
|
@ -332,7 +332,7 @@ class _FilterBar extends StatelessWidget {
|
|||
avatar: Icon(
|
||||
Icons.eco,
|
||||
size: 18,
|
||||
color: state.organicOnly ? null : seedGreen,
|
||||
color: state.organicOnly ? Colors.white : seedGreen,
|
||||
),
|
||||
label: Text(t.editVariety.organic),
|
||||
selected: state.organicOnly,
|
||||
|
|
@ -344,7 +344,7 @@ class _FilterBar extends StatelessWidget {
|
|||
avatar: Icon(
|
||||
Icons.autorenew,
|
||||
size: 18,
|
||||
color: state.needsReproductionOnly ? null : seedGreen,
|
||||
color: state.needsReproductionOnly ? Colors.white : seedGreen,
|
||||
),
|
||||
label: Text(t.inventory.needsReproductionFilter),
|
||||
selected: state.needsReproductionOnly,
|
||||
|
|
@ -446,16 +446,23 @@ class _SwatchFilterChip extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Idle: a soft same-hue fill with dark ink. Selected: the ink becomes the
|
||||
// fill and the content turns white, so the chip clearly reads as "on" while
|
||||
// keeping its family/form hue. White on every ink meets WCAG AA — see
|
||||
// test/ui/category_palette_contrast_test.dart.
|
||||
final ink = selected ? Colors.white : swatch.ink;
|
||||
return FilterChip(
|
||||
label: Text(label),
|
||||
avatar: icon == null ? null : Icon(icon, size: 18, color: swatch.ink),
|
||||
avatar: icon == null ? null : Icon(icon, size: 18, color: ink),
|
||||
selected: selected,
|
||||
onSelected: (_) => onSelected(),
|
||||
backgroundColor: swatch.fill,
|
||||
selectedColor: swatch.ink.withValues(alpha: 0.24),
|
||||
checkmarkColor: swatch.ink,
|
||||
side: BorderSide(color: swatch.ink.withValues(alpha: 0.35)),
|
||||
labelStyle: TextStyle(color: swatch.ink, fontWeight: FontWeight.w500),
|
||||
selectedColor: swatch.ink,
|
||||
checkmarkColor: Colors.white,
|
||||
side: BorderSide(
|
||||
color: swatch.ink.withValues(alpha: selected ? 0 : 0.35),
|
||||
),
|
||||
labelStyle: TextStyle(color: ink, fontWeight: FontWeight.w500),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ class _MarketFilterBar extends StatelessWidget {
|
|||
avatar: Icon(
|
||||
Icons.eco,
|
||||
size: 18,
|
||||
color: state.organicOnly ? null : seedGreen,
|
||||
color: state.organicOnly ? Colors.white : seedGreen,
|
||||
),
|
||||
label: Text(t.editVariety.organic),
|
||||
selected: state.organicOnly,
|
||||
|
|
|
|||
|
|
@ -105,5 +105,24 @@ ThemeData buildTaneTheme() {
|
|||
thickness: 1,
|
||||
color: seedDivider,
|
||||
),
|
||||
// Filter/choice chips: idle chips stay light with dark ink, but a *selected*
|
||||
// chip fills solid green with white ink so "on" reads unmistakably against
|
||||
// the pale canvas (the soft tonal default sat too close to it). White on
|
||||
// seedGreen meets WCAG AA — see test/ui/theme_contrast_test.dart. Chips that
|
||||
// carry their own hue (family/form swatches) override this per-swatch.
|
||||
chipTheme: const ChipThemeData(
|
||||
selectedColor: seedGreen,
|
||||
checkmarkColor: Colors.white,
|
||||
labelStyle: TextStyle(
|
||||
color: seedOnSurface,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
secondaryLabelStyle: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,4 +35,19 @@ void main() {
|
|||
reason: '$type ink on fill');
|
||||
}
|
||||
});
|
||||
|
||||
// A *selected* swatch chip fills solid with its ink and draws white content
|
||||
// (label, icon, checkmark). That pairing must clear AA too.
|
||||
test('white content meets AA on every selected (ink-filled) swatch', () {
|
||||
for (var i = 0; i < 60; i++) {
|
||||
final s = categorySwatch('family-$i');
|
||||
expect(contrast(Colors.white, s.ink), greaterThanOrEqualTo(4.5),
|
||||
reason: 'white on category-$i ink');
|
||||
}
|
||||
for (final type in LotType.values) {
|
||||
final s = lotTypeSwatch(type);
|
||||
expect(contrast(Colors.white, s.ink), greaterThanOrEqualTo(4.5),
|
||||
reason: 'white on $type ink');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue