mirror of
https://github.com/we-promise/sure.git
synced 2026-09-04 14:21:23 +00:00
* fix(pagination): stop the pager wrapping on narrow screens
The page-number strip put the last page on a second line on a phone. The
container was a plain block, so the inline-flex links flowed as inline
content and wrapped like words. Measured on a 760-page list: the strip needs
~198px, and a 360px viewport leaves ~158px once the chevrons and the
per-page select are taken out.
The failure was not even consistent — at 402px the row wrapped, at 360px the
same markup overflowed its parent instead, because a block box inside a flex
item resolves its width differently at each size.
Make the strip a nowrap flex row, and drop the pages that aren't useful on a
phone. Small screens keep the first page, the last page, the current page and
the gaps ("1 … 5 … 760", ~148px); the full series returns from `sm` up. Short
series are left alone — Pagy only emits those when the collection has that
few pages, so hiding there would render a 3-page pager as "1 3".
`sm:` is a viewport breakpoint and the pager also lives in narrow columns on
wide screens (the account activity feed with both sidebars open), so the row
keeps `overflow-x-auto` as a backstop. At 320px, where even the reduced set
is wider than the space, it scrolls rather than wrapping.
Shared by twelve call sites, so this covers transactions, imports, rules,
account activity, statements, merchants, exports and the debug log.
* fix(pagination): mark the pages the mobile pager drops
Hiding the middle page links without saying so made the survivors read as
adjacent. On a 760-page collection sitting on page 3, Pagy emits
[1, 2, "3", 4, 5, :gap, 760] and a phone rendered "1 3 … 760" — page 2 gone
with nothing to show for it. A gapless 6-page series was worse: "1 6".
Each collapsed run now renders its own mobile-only ellipsis, unless Pagy
already put a gap beside that run, which would otherwise read as "… …".
[1, 2, "3", 4, 5, :gap, 760] -> 1 … 3 … 760
["1", 2, 3, 4, 5, 6] -> 1 … 6
["1", 2, 3, 4, 5, :gap, 760] -> 1 … 760 (Pagy's gap already covers it)
[1, :gap, 3, 4, "5", 6, 7, …] -> 1 … 5 … 760
The helper now returns a per-slot plan rather than a class, since the view
needs to know where a run starts, not just whether a slot is hidden.
Tests carry a property check — no two page numbers may survive side by side
unless they are consecutive — with a guard that fails if every pair happens to
be separated by an ellipsis, which would make the property vacuous. It was, on
the first pass.
73 lines
3.7 KiB
ERB
73 lines
3.7 KiB
ERB
<%# locals: (pagy:) %>
|
|
|
|
<nav class="flex w-full items-center justify-between">
|
|
<%# min-w-0 so the series pill can actually shrink below its content width;
|
|
without it the flex item keeps its intrinsic size and overflows the nav. %>
|
|
<div class="flex min-w-0 items-center gap-1 mr-3">
|
|
<div>
|
|
<% if pagy.prev %>
|
|
<%= link_to pagy_url_for(pagy, pagy.prev),
|
|
data: { turbo_frame: :_top },
|
|
class: "inline-flex items-center p-2 text-sm font-medium text-secondary hover:border-secondary hover:text-secondary" do %>
|
|
<%= icon("chevron-left") %>
|
|
<% end %>
|
|
<% else %>
|
|
<div class="inline-flex items-center p-2 text-sm font-medium hover:border-secondary">
|
|
<%= icon("chevron-left") %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<%# A flex row, not a block: as a block the inline-flex page links flowed as
|
|
inline content and wrapped like words, dropping the last page onto a
|
|
second line. `overflow-x-auto` is the backstop for a container too
|
|
narrow even for the reduced set (see PaginationHelper). %>
|
|
<% series = pagy.series %>
|
|
<% mobile_plan = pagination_mobile_plan(series) %>
|
|
<div class="rounded-xl p-1 bg-container-inset flex items-center flex-nowrap overflow-x-auto max-w-full">
|
|
<% series.each_with_index do |series_item, index| %>
|
|
<% mobile_state = mobile_plan[index] %>
|
|
<%# Stands in for the run of pages hidden from here on a phone, so the
|
|
remaining numbers don't read as adjacent. Mirrors the real gap's
|
|
markup and disappears from `sm` up, where the run is shown. %>
|
|
<% if mobile_state == :collapsed %>
|
|
<span class="inline-flex sm:hidden shrink-0 items-center px-2 py-1 text-sm font-medium text-secondary" aria-hidden="true">...</span>
|
|
<% end %>
|
|
<% if series_item.is_a?(Integer) %>
|
|
<%= link_to pagy_url_for(pagy, series_item),
|
|
data: { turbo_frame: :_top },
|
|
class: class_names("rounded-md px-2 py-1 shrink-0 items-center text-sm font-medium text-secondary hover:border-secondary hover:text-secondary", mobile_state == :visible ? "inline-flex" : "hidden sm:inline-flex") do %>
|
|
<%= series_item %>
|
|
<% end %>
|
|
<% elsif series_item.is_a?(String) %>
|
|
<%= link_to pagy_url_for(pagy, series_item),
|
|
data: { turbo_frame: :_top },
|
|
class: "rounded-md px-2 py-1 bg-container border border-secondary shadow-xs inline-flex shrink-0 items-center text-sm font-medium text-primary" do %>
|
|
<%= series_item %>
|
|
<% end %>
|
|
<% elsif series_item == :gap %>
|
|
<span class="inline-flex shrink-0 items-center px-2 py-1 text-sm font-medium text-secondary">...</span>
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
<div>
|
|
<% if pagy.next %>
|
|
<%= link_to pagy_url_for(pagy, pagy.next),
|
|
data: { turbo_frame: :_top },
|
|
class: "inline-flex items-center p-2 text-sm font-medium text-secondary hover:border-secondary hover:text-secondary" do %>
|
|
<%= icon("chevron-right") %>
|
|
<% end %>
|
|
<% else %>
|
|
<div class="inline-flex items-center p-2 text-sm font-medium hover:border-secondary">
|
|
<%= icon("chevron-right") %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-4">
|
|
<%= select_tag :per_page,
|
|
options_for_select(["10", "20", "30", "50", "100"], pagy.limit),
|
|
data: { controller: "selectable-link" },
|
|
class: "py-1.5 pr-8 text-sm text-primary font-medium bg-container-inset border border-secondary rounded-lg focus:border-secondary focus:ring-secondary focus-visible:ring-secondary" %>
|
|
</div>
|
|
</nav>
|