paginationBar function

BloomNode paginationBar({
  1. required String currentPath,
  2. required Map<String, String> currentQuery,
  3. required int total,
  4. required int itemCount,
  5. String? nextCursor,
  6. int pageSize = 24,
  7. String extraClassName = '',
})

Reusable cursor-based pagination bar primitive.

Implementation

BloomNode paginationBar({
  required String currentPath,
  required Map<String, String> currentQuery,
  required int total,
  required int itemCount,
  String? nextCursor,
  int pageSize = 24,
  String extraClassName = '',
}) {
  final backRaw = currentQuery['back'];
  final backList =
      backRaw != null && backRaw.isNotEmpty ? backRaw.split(',') : <String>[];
  final hasPrevious = backList.isNotEmpty;
  final hasNext = nextCursor != null && nextCursor.isNotEmpty;

  final startItem = total == 0 ? 0 : (backList.length * pageSize) + 1;
  final endItem = total == 0 ? 0 : (startItem + itemCount - 1).clamp(0, total);
  final currentPage = backList.length + 1;
  final totalPages = total == 0 ? 1 : ((total - 1) ~/ pageSize) + 1;
  final positionText = total == 0
      ? 'No results'
      : 'Showing $startItem–$endItem of $total';
  final pageText = 'Page $currentPage of $totalPages';

  final ghostBtnClasses = buttonClasses(
    variant: ButtonVariant.ghost,
    size: ButtonSize.sm,
    extraClassName:
        'gap-1 px-3 py-1.5 text-xs sm:text-sm border border-[var(--border)]',
  );

  return Div(
    attrs: const {
      'role': 'navigation',
      'aria-label': 'Pagination',
    },
    className: cn([
      'flex flex-col sm:flex-row items-center justify-between gap-4 pt-6 border-t border-[var(--border)]',
      extraClassName,
    ]),
    children: [
      Div(
        className: 'flex items-center gap-2',
        children: [
          if (hasPrevious)
            Link(
              href: prevPageHref(currentPath, currentQuery),
              className: cn([ghostBtnClasses, 'hover:bg-[var(--bg-muted)]']),
              children: [
                uiIcon('chevron-left', className: 'w-4 h-4'),
                Span(text: 'Previous'),
              ],
            )
          else
            El(
              'button',
              attrs: const {
                'type': 'button',
                'disabled': 'true',
                'aria-disabled': 'true',
              },
              className: cn([ghostBtnClasses, 'opacity-50 pointer-events-none']),
              children: [
                uiIcon('chevron-left', className: 'w-4 h-4'),
                Span(text: 'Previous'),
              ],
            ),
          if (hasNext)
            Link(
              href: nextPageHref(currentPath, currentQuery, nextCursor),
              className: cn([ghostBtnClasses, 'hover:bg-[var(--bg-muted)]']),
              children: [
                Span(text: 'Next'),
                uiIcon('chevron-right', className: 'w-4 h-4'),
              ],
            )
          else
            El(
              'button',
              attrs: const {
                'type': 'button',
                'disabled': 'true',
                'aria-disabled': 'true',
              },
              className: cn([ghostBtnClasses, 'opacity-50 pointer-events-none']),
              children: [
                Span(text: 'Next'),
                uiIcon('chevron-right', className: 'w-4 h-4'),
              ],
            ),
        ],
      ),
      Div(
        className:
            'text-xs sm:text-sm text-[var(--text-muted)] flex items-center gap-2 flex-wrap',
        children: [
          Span(text: positionText),
          Span(className: 'text-[var(--text-faint)]', text: '•'),
          Span(text: pageText),
        ],
      ),
    ],
  );
}