Files
blog/frontend/app/components/PaginationNav.vue
T
davide 1247a2ca07 Add numbered pagination and redesign the IT/EN language switch
Blog and category archives now show a numbered page list (first, last,
current +/-1, collapsing gaps into an ellipsis) instead of only
previous/next — see shared/utils/pagination.ts's paginationRange().

Renders page links via NuxtLink's custom/v-slot API rather than letting
it render its own <a>: RouterLink's built-in active-class/aria-current
detection compares only the route's path, not its query string, so
every ?page=N link was incorrectly marked as the current page.

The language switch now shows both "Italiano / English" at all times,
with the current one as plain non-interactive text and the other as a
link, moved to the header's top-right corner instead of stacked under
the logo.
2026-09-11 14:31:58 +02:00

132 lines
3.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type { Locale } from '#shared/utils/locale'
import { STRINGS, interpolate } from '#shared/utils/i18n'
import { paginationRange } from '#shared/utils/pagination'
const props = defineProps<{ page: number; pageCount: number; locale: Locale }>()
const strings = computed(() => STRINGS[props.locale])
const items = computed(() => paginationRange(props.page, props.pageCount))
</script>
<template>
<nav v-if="pageCount > 1" class="pagination" :aria-label="strings.blog.paginationAriaLabel">
<!-- `custom` bypasses NuxtLink/RouterLink's own active-class and aria-current
computation, which compares only the path, not the query string every
page link would otherwise be (wrongly) marked as the current page. -->
<NuxtLink
v-if="page > 1"
v-slot="{ href, navigate }"
:to="{ query: { page: page - 1 } }"
custom
>
<a :href="href ?? undefined" class="pagination-arrow" rel="prev" :aria-label="strings.blog.prevPage" @click="navigate">
<span aria-hidden="true"></span>
</a>
</NuxtLink>
<span v-else class="pagination-arrow is-disabled" aria-hidden="true"></span>
<ul class="pagination-list">
<li v-for="(item, index) in items" :key="index">
<span v-if="item === 'ellipsis'" class="pagination-ellipsis" aria-hidden="true"></span>
<span v-else-if="item === page" class="pagination-link is-current" aria-current="page">
{{ item }}
</span>
<NuxtLink v-else v-slot="{ href, navigate }" :to="{ query: { page: item } }" custom>
<a
:href="href ?? undefined"
class="pagination-link"
:aria-label="interpolate(strings.blog.goToPage, { page: item })"
@click="navigate"
>
{{ item }}
</a>
</NuxtLink>
</li>
</ul>
<NuxtLink
v-if="page < pageCount"
v-slot="{ href, navigate }"
:to="{ query: { page: page + 1 } }"
custom
>
<a :href="href ?? undefined" class="pagination-arrow" rel="next" :aria-label="strings.blog.nextPage" @click="navigate">
<span aria-hidden="true"></span>
</a>
</NuxtLink>
<span v-else class="pagination-arrow is-disabled" aria-hidden="true"></span>
<p class="sr-only" role="status">
{{ interpolate(strings.blog.pageOf, { current: page, total: pageCount }) }}
</p>
</nav>
</template>
<style scoped>
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin-top: var(--gap-section);
padding-top: 2rem;
border-top: 1px solid var(--color-border);
}
.pagination-list {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0;
margin: 0;
list-style: none;
}
.pagination-arrow,
.pagination-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2rem;
height: 2rem;
padding-inline: 0.4rem;
border-radius: var(--radius);
font-size: 0.85rem;
text-decoration: none;
color: var(--color-body);
}
.pagination-arrow {
font-size: 1.1rem;
}
.pagination-arrow.is-disabled {
color: var(--color-border);
}
.pagination-link:hover {
background: var(--color-surface);
color: var(--color-ink);
}
.pagination-arrow:not(.is-disabled):hover {
background: var(--color-surface);
color: var(--color-ink);
}
.pagination-link.is-current {
background: var(--color-ink);
color: var(--color-bg);
font-weight: 600;
}
.pagination-ellipsis {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.5rem;
color: var(--color-muted);
}
</style>