[Pagination] Rework the pagination styles (#886)

Now with mobile layouts!
This commit is contained in:
Cinder 2025-02-03 21:42:12 -08:00 committed by GitHub
parent 09b46b0766
commit af6ab947d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 168 additions and 64 deletions

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
module IconHelper
PATHS = {
chevron_left: %(<path d="m15 18-6-6 6-6"/>),
chevron_right: %(<path d="m9 18 6-6-6-6"/>),
ellipsis: %(<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>),
}.freeze
def svg_icon(name, *args)
options = args.extract_options!
width = options[:width] || 24
height = options[:height] || 24
tag.svg(
"xmlns": "http://www.w3.org/2000/svg",
"width": width,
"height": height,
"viewbox": "0 0 24 24",
"fill": "none",
"stroke": "currentColor",
"stroke-width": 2,
"stroke-linecap": "round",
"stroke-linejoin": "round",
) do
raw(PATHS[name]) # rubocop:disable Rails/OutputSafety
end
end
end

View File

@ -2,17 +2,23 @@
module PaginationHelper
def sequential_paginator(records)
with_paginator_wrapper do
tag.div(class: "paginator") do
return "" if records.try(:none?)
html = "".html_safe
unless records.is_first_page?
html << tag.li(link_to("< Previous", nav_params_for("a#{records[0].id}"), rel: "prev", id: "paginator-prev", data: { shortcut: "a left" }))
# Previous
html << link_to(records.is_first_page? ? "#" : nav_params_for("a#{records[0].id}"), class: "prev", id: "paginator-prev", rel: "prev", data: { shortcut: "a left", disabled: records.is_first_page? }) do
concat svg_icon(:chevron_left)
concat tag.span("Prev")
end
unless records.is_last_page?
html << tag.li(link_to("Next >", nav_params_for("b#{records[-1].id}"), rel: "next", id: "paginator-next", data: { shortcut: "d right" }))
# Next
html << link_to(records.is_last_page? ? "#" : nav_params_for("b#{records[-1].id}"), class: "next", id: "paginator-next", rel: "next", data: { shortcut: "d right", disabled: records.is_last_page? }) do
concat tag.span("Next")
concat svg_icon(:chevron_right)
end
html
end
end
@ -22,64 +28,73 @@ module PaginationHelper
return sequential_paginator(records)
end
with_paginator_wrapper do
tag.div(class: "paginator", data: { total: [records.total_pages, records.max_numbered_pages].min, current: records.current_page }) do
html = "".html_safe
icon_left = tag.i(class: "fa-solid fa-chevron-left")
if records.current_page >= 2
html << tag.li(class: "arrow") { link_to(icon_left, nav_params_for(records.current_page - 1), rel: "prev", id: "paginator-prev", data: { shortcut: "a left" }) }
else
html << tag.li(class: "arrow") { tag.span(icon_left) }
# Previous
has_prev = records.current_page < 2
html << link_to(has_prev ? "#" : nav_params_for(records.current_page - 1), class: "prev", id: "paginator-prev", rel: "prev", data: { shortcut: "a left", disabled: has_prev }) do
concat svg_icon(:chevron_left)
concat tag.span("Prev")
end
paginator_pages(records).each do |page|
html << numbered_paginator_item(page, records)
# Break
html << tag.div(class: "break")
# Numbered
paginator_pages(records).each do |page, klass|
html << numbered_paginator_item(page, klass, records)
end
icon_right = tag.i(class: "fa-solid fa-chevron-right")
if records.current_page < records.total_pages
html << tag.li(class: "arrow") { link_to(icon_right, nav_params_for(records.current_page + 1), rel: "next", id: "paginator-next", data: { shortcut: "d right" }) }
else
html << tag.li(class: "arrow") { tag.span(icon_right) }
# Next
has_next = records.current_page >= records.total_pages
html << link_to(has_next ? "#" : nav_params_for(records.current_page + 1), class: "next", id: "paginator-next", rel: "next", data: { shortcut: "d right", disabled: has_next }) do
concat tag.span("Next")
concat svg_icon(:chevron_right)
end
html
end
end
private
def with_paginator_wrapper(&)
tag.div(class: "paginator") do
tag.menu(&)
end
end
def paginator_pages(records)
window = 4
small_window = 2
large_window = 4
last_page = [records.total_pages, records.max_numbered_pages].min
left = [2, records.current_page - window].max
right = [records.current_page + window, last_page - 1].min
left_sm = [2, records.current_page - small_window].max
left_lg = [2, records.current_page - large_window].max
right_sm = [records.current_page + small_window, last_page - 1].min
right_lg = [records.current_page + large_window, last_page - 1].min
small_range = left_sm..right_sm
[
1,
("..." unless left == 2),
(left..right).to_a,
("..." unless right == last_page - 1),
(last_page unless last_page <= 1),
].flatten.compact
result = [
[1, "first"],
]
result.push([0, "spacer"]) unless left_lg == 2
(left_lg..right_lg).each do |page|
result.push([page, small_range.member?(page) ? "sm" : "lg"])
end
result.push([0, "spacer"]) unless right_lg == last_page - 1
result.push([last_page, "last"]) unless last_page <= 1
result
end
def numbered_paginator_item(page, records)
def numbered_paginator_item(page, klass, records)
return "" if page.to_i > records.max_numbered_pages
html = "".html_safe
if page == "..."
html << tag.li(class: "more") { link_to(tag.i(class: "fa-solid fa-ellipsis"), nav_params_for(0)) }
if page == 0
html << link_to(svg_icon(:ellipsis), nav_params_for(0), class: "spacer")
elsif page == records.current_page
html << tag.li(class: "current-page") { tag.span(page) }
html << tag.span(page, class: "page current")
else
html << tag.li(class: "numbered-page") { link_to(page, nav_params_for(page)) }
html << link_to(page, nav_params_for(page), class: "page #{klass}")
end
html
end

View File

@ -12,7 +12,7 @@ Paginator.init_fasttravel = function (button) {
};
$(() => {
for (const one of $(".paginator li.more a").get())
for (const one of $(".paginator a.spacer").get())
Paginator.init_fasttravel($(one));
});

View File

@ -28,6 +28,7 @@ Shortcuts.initialize_data_shortcuts = function () {
Shortcuts.keydown(keys, namespace, event => {
const e = $(`[data-shortcut="${keys}"]`).get(0);
if ($e.data("disabled")) return;
if ($e.is("input, textarea")) {
$e.trigger("focus").selectEnd();
} else {

View File

@ -1,35 +1,98 @@
div.paginator {
display: block;
padding: 2em 0 1em 0;
text-align: center;
clear: both;
menu {
.paginator {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
background-color: themed("color-foreground");
border-radius: 0.25rem;
& > a, & > span {
display: flex;
box-sizing: border-box;
justify-content: center;
align-items: center;
min-width: 2.15rem;
font-size: 1rem;
line-height: 1rem;
padding: 0.75rem 0.3rem; // otherwise large page numbers wrap
border-radius: 0.25rem;
&:hover {
background: themed("color-section");
}
}
li {
a {
margin: 0 0.25em;
padding: 0.25em 0.75em;
& > a[data-disabled="true"] {
color: var(--color-text);
pointer-events: none;
}
a:hover {
background: $paginator-hover-background;
color: $paginator-hover-color;
// Ordering
// Oh boy
.page {
order: 20;
&.lg { display: none; }
&.current { cursor: default; }
}
.prev {
order: 1;
margin-right: auto;
}
.spacer {
order: 20;
padding: 0;
&:last-child { display: none; }
svg {
height: 1rem;
transform: rotate(90deg);
}
}
.next {
order: 9;
margin-left: auto;
}
.break {
order: 10;
width: 100%;
}
&.more {
color: $paginator-more-color;
// Tablet
@include window-larger-than(35rem) {
justify-content: center;
gap: 0.125rem;
a, span {
order: 0 !important;
min-width: 2.25rem;
font-size: 0.9rem;
}
span {
margin: 0 0.25em;
padding: 0.25em 0.75em;
font-weight: bold;
.break { display: none; }
.spacer {
padding: inherit;
svg { transform: unset; }
}
.prev { margin-right: 1rem; }
.next { margin-left: 1rem; }
}
@include window-larger-than(50rem) {
a, span {
padding: 0.75rem 0.5rem;
font-size: 0.95rem;
}
.prev, .next {
span { display: none; }
}
}
@include window-larger-than(65rem) {
a.page.lg { display: flex; }
}
}

View File

@ -33,10 +33,6 @@
display: flex;
flex-flow: column;
gap: 1em;
.paginator {
padding: 1em 0;
}
}
}