* feat: add jump-to-page dialog on pagination ellipsis - Replace pagination windowing algorithm with a tighter 5-item window (curr-1 / curr / curr+1, clamped at edges) and hide-on-mobile for pages more than 1 step away from the current page - Ellipsis spans now carry the pagination-jump-trigger class and open a native <dialog> on click instead of being static decorations - Add <dialog id="pagination-jump-dialog"> with number input, Go button, and Enter-key hint; data-total / data-first-url / data-format-url attributes on <nav> give the JS all it needs to build the target URL - Add assets/ts/pagination.ts: setupPaginationJump() with Fisher-Yates backdrop-click and ESC handling, animated open/close via .closing class - Wire setupPaginationJump() into Stack.init() in main.ts - Extend pagination.scss with .hide-on-mobile, .pagination-jump-trigger hover style, and the full #pagination-jump-dialog styling + keyframe scale-up/scale-down animations - Add [pagination] i18n keys (jumpToPage / jump / pressEnter) to en / zh / zh-hant-tw / zh-hant-hk / ja * style: align pagination code with upstream conventions - pagination.html: collapse multi-line span/class/aria attrs to single lines; use HTML5 void element syntax; fix hint div indentation - pagination.scss: 2-space indent → 4-space indent - pagination.ts: tab indent → 4-space indent; export const arrow fn → export function declaration * chore: update layouts/_partials/pagination.html Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: add aria-labelledby attrributes for better accesibility Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: use { once: true } for the listener and guard against re-entry when .closing is already set. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * style: replace hardcoded values with design tokens and improve dialog UX - Replace all hardcoded px spacing in .page-link with --spacing-* tokens - Use @include card-style in #pagination-jump-dialog; override with --shadow-l2 - Replace padding: 24px with var(--card-padding) for responsive dialog padding - Align header title/subtitle to space-between for cleaner layout - Replace hardcoded gaps/padding in form with spacing tokens - Fix input/button border-radius from 8px to 6px (better proportion for 44px height) - Fix focus ring: use color-mix(accent-color 20%) so it's visible in dark mode - Add :focus-visible + outline:none to fully suppress native browser outline - Remove translateY from button hover for more restrained interaction - Scope transition to specific properties (border-color, box-shadow, background-color) * chore: add pagination test articles for easier debugging * style: enhance backdrop animations for pagination jump dialog * style: replace pagination ellipsis spans with buttons for improved accessibility * fix: check if browser supports dialog API before continue with pagination jump setup * fix: retain focus on last active element when closing pagination jump dialog * fix: style for page-link button (pagination-jump) * style: adjust width of pagination jump dialog, add container padding * style: simplify * refactor: simplify pagination link logic --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jimmy Cai <jimmy@cai.im>
260 lines
6.6 KiB
SCSS
260 lines
6.6 KiB
SCSS
.pagination {
|
|
display: flex;
|
|
@include card-style;
|
|
overflow: hidden;
|
|
|
|
.hide-on-mobile,
|
|
.pagination-link {
|
|
display: none;
|
|
|
|
@include respond(md) {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.page-link {
|
|
padding: var(--spacing-sm) 0;
|
|
margin: 0;
|
|
border: 0;
|
|
background: none;
|
|
font: inherit;
|
|
border-radius: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: 1 1 auto;
|
|
min-width: 36px;
|
|
color: var(--card-text-color-secondary);
|
|
text-decoration: none;
|
|
|
|
@include respond(md) {
|
|
min-width: 48px;
|
|
padding: var(--spacing-md) 0;
|
|
}
|
|
|
|
&:first-child,
|
|
&:last-child {
|
|
flex: 0 0 var(--spacing-3xl);
|
|
padding: var(--spacing-sm);
|
|
|
|
@include respond(md) {
|
|
flex: 0 0 48px;
|
|
padding: var(--spacing-md);
|
|
}
|
|
}
|
|
|
|
&:hover:not(.current):not(.disabled) {
|
|
background-color: var(--card-background-selected);
|
|
opacity: 0.8;
|
|
}
|
|
|
|
&.current {
|
|
background-color: var(--card-background-selected);
|
|
color: var(--card-text-color-main);
|
|
font-weight: bold;
|
|
cursor: default;
|
|
}
|
|
|
|
&.disabled {
|
|
opacity: 0.3;
|
|
cursor: not-allowed;
|
|
pointer-events: none;
|
|
color: var(--card-text-color-secondary);
|
|
}
|
|
|
|
svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
&.pagination-jump-trigger {
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
}
|
|
|
|
#pagination-jump-dialog {
|
|
border: none;
|
|
@include card-style;
|
|
box-shadow: var(--shadow-l2);
|
|
color: var(--body-text-color);
|
|
margin: auto; // Center dialog
|
|
width: calc(100% - (var(--container-padding) * 2));
|
|
max-width: 320px;
|
|
padding: var(--card-padding);
|
|
|
|
&::backdrop {
|
|
background-color: rgba(0, 0, 0, 0.4);
|
|
opacity: 0;
|
|
animation: backdrop-fade-in 0.3s ease forwards;
|
|
}
|
|
|
|
&[open] {
|
|
animation: scale-up 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
|
|
}
|
|
|
|
&.closing {
|
|
animation: scale-down 0.2s ease forwards;
|
|
|
|
&::backdrop {
|
|
animation: backdrop-fade-out 0.2s ease forwards;
|
|
}
|
|
}
|
|
|
|
.pagination-jump-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-md);
|
|
|
|
header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: var(--spacing-xs);
|
|
|
|
label {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.title {
|
|
font-size: 1.6rem;
|
|
font-weight: 600;
|
|
color: var(--card-text-color-main);
|
|
margin: 0;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 1.3rem;
|
|
color: var(--card-text-color-tertiary);
|
|
}
|
|
}
|
|
|
|
.pagination-jump-input-group {
|
|
display: flex;
|
|
gap: var(--spacing-sm);
|
|
align-items: stretch;
|
|
|
|
input[type="number"] {
|
|
flex: 1;
|
|
width: 100%;
|
|
min-width: 0;
|
|
padding: 0 var(--spacing-md);
|
|
height: 44px; // Explicit height for perfect alignment
|
|
border: 1.5px solid var(--card-separator-color);
|
|
border-radius: 6px;
|
|
background-color: var(--body-background);
|
|
color: var(--card-text-color-main);
|
|
font-size: 1.6rem;
|
|
outline: none;
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
|
|
/* Hide spinner */
|
|
-moz-appearance: textfield;
|
|
&::-webkit-outer-spin-button,
|
|
&::-webkit-inner-spin-button {
|
|
-webkit-appearance: none;
|
|
margin: 0;
|
|
}
|
|
|
|
&:focus,
|
|
&:focus-visible {
|
|
outline: none;
|
|
border-color: var(--accent-color);
|
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-color) 20%, transparent);
|
|
}
|
|
}
|
|
|
|
.btn-primary {
|
|
flex-shrink: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0 var(--spacing-xl);
|
|
height: 44px; // Match input height
|
|
background-color: var(--accent-color);
|
|
color: var(--accent-color-text);
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 1.5rem;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
|
|
|
&:hover {
|
|
background-color: var(--accent-color-darker);
|
|
box-shadow: var(--shadow-l1);
|
|
}
|
|
|
|
&:active {
|
|
box-shadow: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.pagination-jump-hint {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
gap: var(--spacing-sm);
|
|
font-size: 1.3rem;
|
|
color: var(--card-text-color-tertiary);
|
|
margin-top: calc(var(--spacing-xs) * -1);
|
|
|
|
kbd {
|
|
background-color: var(--body-background);
|
|
border: 1px solid var(--card-separator-color);
|
|
border-radius: 4px;
|
|
padding: 3px 6px;
|
|
font-family: var(--base-font-family);
|
|
font-size: 1.1rem;
|
|
box-shadow: 0 2px 0 var(--card-separator-color);
|
|
color: var(--card-text-color-main);
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes scale-up {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.9);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes scale-down {
|
|
from {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
transform: scale(0.95);
|
|
}
|
|
}
|
|
|
|
@keyframes backdrop-fade-in {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@keyframes backdrop-fade-out {
|
|
from {
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
}
|