<div class="js-pagination" data-base-path='"#"' data-current-page='44' data-first-url='"#"' data-last-url='"#"' data-next-url='"#"' data-pages='[44,45,46]' data-page-trigger='"p"' data-prev-url='"#"' data-total-pages='100'></div>

<link media="all" rel="stylesheet" href="/pagination/pagination.css" />
<script src="/pagination/pagination.js"></script>
{% set currentPage = pageInfo.currentPage %}
{% set totalPages = pageInfo.totalPages %}

{% if totalPages < 5 %}
  {% set rangeStart = 1 %}
  {% set rangeEnd = totalPages %}
{% elseif currentPage == 2 %}
  {% set rangeStart = 1 %}
  {% set rangeEnd = min(totalPages, 3) %}
{% elseif currentPage == totalPages - 3 %}
  {% set rangeStart = currentPage %}
  {% set rangeEnd = totalPages %}
{% elseif currentPage >= totalPages - 2 %}
  {% set rangeStart = max(1, totalPages - 2) %}
  {% set rangeEnd = totalPages %}
{% else %}
  {% set rangeStart = currentPage %}
  {% set rangeEnd = min(totalPages, currentPage + 2) %}
{% endif %}
{% set pages = range(rangeStart, rangeEnd) %}

{% if(totalPages > 1) %}

  <div class="js-pagination"
       data-base-path='{{ pageInfo.basePath|json_encode }}'
       data-current-page='{{ pageInfo.currentPage }}'
       data-first-url='{{ pageInfo.firstUrl|json_encode }}'
       data-last-url='{{ pageInfo.lastUrl|json_encode }}'
       data-next-url='{{ pageInfo.nextUrl|json_encode }}'
       data-pages='{{ pages|json_encode }}'
       data-page-trigger='{{ pageInfo.pageTrigger|json_encode }}'
       data-prev-url='{{ pageInfo.prevUrl|json_encode }}'
       data-total-pages='{{ pageInfo.totalPages }}'
  ></div>

{% endif %}

{% import "_macros.twig" as h %}
{{ h.componentAssets('pagination') }}
{
  "pageInfo": {
    "basePath": "#",
    "currentPage": 44,
    "firstUrl": "#",
    "lastUrl": "#",
    "nextUrl": "#",
    "pageTrigger": "p",
    "prevUrl": "#",
    "totalPages": 100
  }
}
  • Content:
    <script setup>
    import { onMounted, onUnmounted, ref, useTemplateRef } from "vue";
    
    const props = defineProps({
      basePath: {
        type: String,
        required: false,
      },
      currentPage: {
        type: Number,
        default: 1,
      },
      firstUrl: {
        type: String,
        required: false,
      },
      lastUrl: {
        type: String,
        required: false,
      },
      nextUrl: {
        type: String,
        required: false,
      },
      pages: {
        type: Array,
        required: true,
      },
      pageTrigger: {
        type: String,
        default: "p",
      },
      prevUrl: {
        type: String,
        required: false,
      },
      totalPages: {
        type: Number,
        required: true,
      },
    });
    const jump = ref(null);
    const paginationNav = useTemplateRef("pagination-nav");
    
    const emit = defineEmits(["goToPage"]);
    
    const resizeObserver = new ResizeObserver((entries) => {
      const entry = entries[0];
      const pagNav = entry.target; //.pagination-nav
      const pag = pagNav.querySelector(".pagination");
    
      //add the expanded class and grab the dimensions
      pagNav.classList.add("paginationNav--expanded");
      const bBox = pag.getBoundingClientRect();
    
      //if the pagination is as wide or wider than the container, remove the expanded class
      if (entry.contentRect.width <= bBox.width) {
        pagNav.classList.remove("paginationNav--expanded");
      }
    });
    
    //lifecycle
    onMounted(() => {
      resizeObserver.observe(paginationNav.value);
    });
    onUnmounted(() => {
      resizeObserver.unobserve(paginationNav.value);
    });
    
    //methods
    function getPageUrl(page) {
      return `/${props.basePath}/${props.pageTrigger}${page}${window.location.search}`;
    }
    function handleFormSubmit() {
      if (props.basePath) {
        _jumpToPage(jump.value);
      } else {
        emit("goToPage", jump.value);
        jump.value = null;
      }
    }
    function throttle(f, delay) {
      let timer = 0;
      return function (...args) {
        clearTimeout(timer);
        timer = setTimeout(() => f.apply(this, args), delay);
      };
    }
    function _jumpToPage(page) {
      window.location.href = getPageUrl(page);
    }
    </script>
    
    <template>
      <nav
        aria-label="Pagination"
        class="u-blockCenter paginationNav"
        ref="pagination-nav"
      >
        <div class="pagination">
          <component
            v-if="currentPage > 1"
            :is="prevUrl ? 'a' : 'button'"
            :href="prevUrl ?? null"
            v-on:click="$emit('goToPage', currentPage - 1, 'prevPage')"
            class="pagination__link pagination__link--prev"
            :title="`Go to page ${currentPage - 1}`"
          >
            <span class="prependedIcon"><i class="fas fa-angle-left"></i></span>Prev
          </component>
          <div class="pagination__pages">
            <template v-if="currentPage > 2 && pages.at(0) > 1">
              <component
                :is="firstUrl ? 'a' : 'button'"
                :href="firstUrl ?? null"
                v-on:click="$emit('goToPage', 1, 'first')"
                class="pagination__link"
                ><span class="u-srOnly">Go to page&nbsp;</span>1</component
              >
              <span class="pagination__sep pagination__sep--first">...</span>
            </template>
            <template v-for="page in pages">
              <span
                v-if="page === currentPage"
                class="pagination__link pagination__link--active"
                ><span class="u-srOnly">Current page&nbsp;</span>{{ page }}</span
              >
              <component
                v-else
                :is="basePath ? 'a' : 'button'"
                :href="basePath ? getPageUrl(page) : null"
                v-on:click="$emit('goToPage', page, 'specific')"
                class="pagination__link"
                ><span class="u-srOnly">Go to page&nbsp;</span>{{ page }}</component
              >
            </template>
            <template v-if="pages.at(-1) < totalPages - 1">
              <span class="pagination__sep">...</span>
              <component
                :is="lastUrl ? 'a' : 'button'"
                :href="lastUrl ?? null"
                v-on:click="$emit('goToPage', totalPages, 'last')"
                class="pagination__link"
                ><span class="u-srOnly">Go to page&nbsp;</span
                >{{ totalPages }}</component
              >
            </template>
          </div>
          <component
            v-if="currentPage < totalPages"
            :is="nextUrl ? 'a' : 'button'"
            :href="nextUrl ?? null"
            v-on:click="$emit('goToPage', currentPage + 1, 'next')"
            class="pagination__link pagination__link--next"
            :title="`Go to page ${currentPage + 1}`"
          >
            Next<span class="appendedIcon"
              >&zwj;<i class="fas fa-angle-right"></i
            ></span>
          </component>
        </div>
        <form
          v-on:submit.prevent="handleFormSubmit"
          class="paginationJump"
          aria-label="Jump to page"
        >
          <label for="jump-to-page">Go to page:</label>
          <input
            v-model="jump"
            type="number"
            class="input input--text paginationJump__input"
            id="jump-to-page"
            size="2"
            min="1"
            :max="totalPages"
          />
          <input
            type="submit"
            value="Go"
            class="button button--primary paginationJump__button"
          />
        </form>
      </nav>
    </template>
    
    <style scoped lang="scss">
    @use "../_layout/mixins";
    
    $border: 2px solid var(--neutral--darker);
    $gap: 0.5em;
    
    .paginationNav {
      container-type: inline-size;
      container-name: pagination;
    }
    .pagination {
      display: inline-grid;
      grid-template-areas:
        "prev  next"
        "pages pages";
      justify-content: center;
      align-items: center;
      row-gap: 0.5em;
      grid-auto-flow: column;
    }
    .pagination__pages {
      grid-area: pages;
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
    }
    .pagination__sep--first:has(+ span) {
      margin-inline-end: $gap;
    }
    .pagination__link {
      @include mixins.blockLink($underline: false);
      padding: 0.2em $gap;
      color: var(--color);
      cursor: pointer;
    }
    .pagination__link--active {
      color: var(--white);
      background-color: var(--blue);
    }
    .pagination__link--prev {
      justify-self: flex-start;
      grid-area: prev;
    }
    .pagination__page {
      justify-self: center;
    }
    .pagination__link--next {
      justify-self: flex-end;
      grid-area: next;
    }
    .paginationNav--expanded {
      .pagination {
        grid-template-areas: "prev pages next";
      }
      .pagination__link--prev {
        justify-self: flex-start;
        border-inline-end: $border;
      }
      .pagination__link--next {
        justify-self: flex-end;
        border-inline-start: $border;
      }
    }
    
    .paginationJump {
      margin-block-start: 1em;
      display: flex;
      flex-wrap: wrap;
      gap: $gap;
      justify-content: center;
      align-items: center;
    }
    .paginationJump__input {
      width: 4em;
    }
    .paginationJump__button {
      --button-padding: 0 0.5em;
      align-self: stretch;
      --button-size: var(--step-0);
    }
    </style>
    
  • URL: /components/raw/pagination/Pagination.vue
  • Filesystem Path: src/components/pagination/Pagination.vue
  • Size: 6.6 KB
  • Content:
    import { createApp } from 'vue';
    import Pagination from "./Pagination.vue";
    
    const els = document.querySelectorAll(".js-pagination");
    els.forEach((el) => {
      const app = createApp(Pagination, {
        basePath: JSON.parse(el.dataset.basePath),
        currentPage: Number(el.dataset.currentPage),
        firstUrl: JSON.parse(el.dataset.firstUrl),
        lastUrl: JSON.parse(el.dataset.lastUrl),
        nextUrl: JSON.parse(el.dataset.nextUrl),
        pages: JSON.parse(el.dataset.pages),
        pageTrigger: JSON.parse(el.dataset.pageTrigger),
        prevUrl: JSON.parse(el.dataset.prevUrl),
        totalPages: Number(el.dataset.totalPages)
      });
      app.mount(el);
    })
  • URL: /components/raw/pagination/pagination.js
  • Filesystem Path: src/components/pagination/pagination.js
  • Size: 637 Bytes