diff --git a/design_system/lib/src/fit_grid.dart b/design_system/lib/src/fit_grid.dart index 6fe1388..c28e9ee 100644 --- a/design_system/lib/src/fit_grid.dart +++ b/design_system/lib/src/fit_grid.dart @@ -8,9 +8,10 @@ import 'package:flutter/material.dart'; /// /// Rows per page come from the height the parent gives it; the overflow moves /// to further pages in a horizontal [PageView], marked by dots once there is -/// more than one. [itemBuilder] gets the global item index and must fill the -/// cell it is given: a square of up to column width plus [labelHeight], the -/// rows stretching to use up the leftover height. +/// more than one (a "n / N" counter past 12 pages). [itemBuilder] gets the +/// global item index and must fill the cell it is given: a square of up to +/// column width plus [labelHeight], the rows stretching to use up the +/// leftover height. class FitGrid extends StatefulWidget { /// Creates the grid. const FitGrid({ @@ -54,6 +55,10 @@ class FitGrid extends StatefulWidget { const double _dotSize = AppSpacing.sm; const double _dotsHeight = AppSpacing.md + AppSpacing.xs; +// Above this many pages a row of dots outgrows a phone width, so the +// indicator becomes a "n / N" counter instead. +const int _maxDots = 12; + class _FitGridState extends State { final _controller = PageController(); var _page = 0; @@ -159,22 +164,35 @@ class _Dots extends StatelessWidget { return ExcludeSemantics( child: SizedBox( height: _dotsHeight, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - for (var i = 0; i < count; i++) - Container( - key: ValueKey('fit-grid-dot-$i'), - width: _dotSize, - height: _dotSize, - margin: const EdgeInsets.symmetric(horizontal: AppSpacing.xs), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: i == current ? scheme.primary : scheme.outlineVariant, + child: count > _maxDots + ? Center( + child: Text( + '${current + 1} / $count', + key: const ValueKey('fit-grid-page-counter'), + style: Theme.of(context).textTheme.labelSmall, + textScaler: TextScaler.noScaling, ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + for (var i = 0; i < count; i++) + Container( + key: ValueKey('fit-grid-dot-$i'), + width: _dotSize, + height: _dotSize, + margin: const EdgeInsets.symmetric( + horizontal: AppSpacing.xs, + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: i == current + ? scheme.primary + : scheme.outlineVariant, + ), + ), + ], ), - ], - ), ), ); } diff --git a/design_system/pubspec.yaml b/design_system/pubspec.yaml index 952cf96..74d788f 100644 --- a/design_system/pubspec.yaml +++ b/design_system/pubspec.yaml @@ -5,7 +5,7 @@ description: >- helpers that were each reimplemented in three or more repos. Owns the tokens -- consumers import them rather than transcribing the token table by hand. publish_to: 'none' -version: 0.3.0 +version: 0.3.1 environment: sdk: ^3.12.2 diff --git a/design_system/test/fit_grid_test.dart b/design_system/test/fit_grid_test.dart index e7093df..931c220 100644 --- a/design_system/test/fit_grid_test.dart +++ b/design_system/test/fit_grid_test.dart @@ -126,6 +126,30 @@ void main() { expect(find.text('item $perPage').hitTestable(), findsNothing); }); + testWidgets('more than 12 pages: "n / N" counter instead of dots', ( + tester, + ) async { + await _pump(tester, count: 30); + final perPage = _onScreen(tester); + final count = perPage * 13; + await _pump(tester, count: count); + expect(_dot, findsNothing); + final counter = find.byKey(const ValueKey('fit-grid-page-counter')); + expect(tester.widget(counter).data, '1 / 13'); + await tester.fling(find.byType(PageView), const Offset(-250, 0), 1000); + await tester.pumpAndSettle(); + expect(tester.widget(counter).data, '2 / 13'); + expect(tester.takeException(), isNull); + }); + + testWidgets('exactly 12 pages still shows dots', (tester) async { + await _pump(tester, count: 30); + final perPage = _onScreen(tester); + await _pump(tester, count: perPage * 12); + expect(_dot, findsNWidgets(12)); + expect(tester.takeException(), isNull); + }); + testWidgets('swiping left shows the next page and moves the dot', ( tester, ) async {