Broke out pagination into a separate Svelte subcomponent

This commit is contained in:
palewire 2023-04-02 13:32:52 -04:00
parent df1edd38a8
commit 61b0b5c91c
5 changed files with 175 additions and 121 deletions

View file

@ -0,0 +1,132 @@
<script lang="ts">
import { intcomma } from 'journalize';
/**
* The current page number.
* @type {number}
*/
export let pageNumber: number = 1;
/**
* The default page size.
* @type {number}
*/
export let pageSize: number = 25;
/**
* The number of records in the current page.
* @type {number}
*/
export let pageLength: number = null;
/**
* The total number of records in the data set.
* @type {number}
*/
export let n: number = null;
function goToPreviousPage() {
if (pageNumber > 1) {
pageNumber -= 1;
}
}
function goToNextPage() {
if (pageNumber < Math.ceil(n / pageSize)) {
pageNumber += 1;
}
}
</script>
<nav aria-label="pagination" class="pagination">
<button on:click="{goToPreviousPage}" disabled="{pageNumber === 1}"
><div class="icon-wrapper">
<svg
class="icon"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 6 11"
><path
d="m1.76 5.134 3.887-3.887a.71.71 0 0 0 0-1.027.709.709 0 0 0-1.027 0l-4.4 4.4a.71.71 0 0 0 0 1.027l4.4 4.4c.147.147.367.22.513.22a.79.79 0 0 0 .513-.22.71.71 0 0 0 0-1.027L1.76 5.133Z"
></path></svg
> <span class="visually-hidden">Previous page</span>
</div></button
>
<span class="label" aria-label="page {pageNumber}" aria-current="page"
>{pageNumber * pageSize - pageSize + 1}-{pageNumber * pageSize -
pageSize +
pageLength} of {intcomma(n)}</span
>
<button
on:click="{goToNextPage}"
disabled="{pageNumber === Math.ceil(n / pageSize)}"
><div class="icon-wrapper">
<svg
class="icon"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 7 11"
><path
d="m6.013 4.987-4.4-4.4a.71.71 0 0 0-1.027 0 .709.709 0 0 0 0 1.027L4.473 5.5.586 9.387a.71.71 0 0 0 0 1.027c.147.147.293.22.513.22.22 0 .367-.073.514-.22l4.4-4.4a.71.71 0 0 0 0-1.027Z"
></path></svg
> <span class="visually-hidden">Next page</span>
</div></button
>
</nav>
<style lang="scss">
@import '../../scss/colours/thematic/tr';
@import '../../scss/fonts/variables';
nav.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 1rem;
font-size: 1rem;
font-family: $font-family-display;
font-weight: 400;
button {
padding: 5px 10px;
border: 1px solid;
border-color: $tr-contrast-grey;
border-radius: 8px;
background: $white;
color: $tr-medium-grey;
cursor: pointer;
width: 40px;
height: 40px;
&:disabled {
border-color: $tr-light-grey;
color: $tr-light-grey;
cursor: default;
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
transition: all 0.15s ease;
.icon {
height: 1rem;
width: 1rem;
fill: currentColor;
}
}
}
.label {
margin: 0 1rem;
}
}
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
</style>

View file

@ -9,6 +9,8 @@
// @ts-ignore
import truncateDocs from './stories/docs/truncate.md?raw';
// @ts-ignore
import paginateDocs from './stories/docs/paginate.md?raw';
// @ts-ignore
import searchDocs from './stories/docs/search.md?raw';
// @ts-ignore
import filterDocs from './stories/docs/filter.md?raw';
@ -80,6 +82,18 @@
}}"
/>
<Story
name="Paginate"
{...withStoryDocs(paginateDocs)}
args="{{
data: pressFreedom,
title: 'Press Freedom Index',
paginated: true,
source:
"Source: <a href='https://en.wikipedia.org/wiki/Press_Freedom_Index'>Reporters Without Borders</a>",
}}"
/>
<Story
name="Search"
{...withStoryDocs(searchDocs)}
@ -88,7 +102,7 @@
paginated: true,
searchable: true,
title: 'Press Freedom Index',
notes:
source:
"Source: <a href='https://en.wikipedia.org/wiki/Press_Freedom_Index'>Reporters Without Borders</a>",
}}"
/>

View file

@ -132,10 +132,10 @@
/** Import local helpers */
import Block from '../Block/Block.svelte';
import Pagination from './Pagination.svelte';
import {
filterArray,
paginateArray,
numberWithCommas,
uniqueAttr,
isNumeric,
} from './utils.js';
@ -150,15 +150,13 @@
let filterValue = '';
$: filteredData = filterArray(data, searchText, filterField, filterValue);
$: sortedData = sortArray(filteredData, sortField, sortDirection);
$: currentPageData = () => {
if (truncated) {
return showAll ? sortedData : sortedData.slice(0, truncateLength + 1);
} else if (paginated) {
return paginateArray(sortedData, pageSize, pageNumber);
} else {
return sortedData;
}
};
$: currentPageData = truncated
? showAll
? sortedData
: sortedData.slice(0, truncateLength + 1)
: paginated
? paginateArray(sortedData, pageSize, pageNumber)
: sortedData;
// Estimate the text alignment of our fields. Strings go left. Numbers go right.
function getAlignment(value) {
@ -191,18 +189,6 @@
sortDirection = sortDirection === 'ascending' ? 'descending' : 'ascending';
}
function goToPreviousPage() {
if (pageNumber > 1) {
pageNumber -= 1;
}
}
function goToNextPage() {
if (pageNumber < Math.ceil(filteredData.length / pageSize)) {
pageNumber += 1;
}
}
function sortArray(array, column, direction) {
if (!sortable) return array;
return array.sort((a, b) => {
@ -318,7 +304,7 @@
</tr>
</thead>
<tbody class="table--tbody">
{#each currentPageData() as item, idx}
{#each currentPageData as item, idx}
<tr data-row-index="{idx}">
{#each includedFields as field}
<td
@ -358,46 +344,12 @@
</nav>
{/if}
{#if paginated}
<nav aria-label="pagination" class="pagination">
<button on:click="{goToPreviousPage}" disabled="{pageNumber === 1}"
><div class="icon-wrapper">
<svg
class="icon"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 6 11"
><path
d="m1.76 5.134 3.887-3.887a.71.71 0 0 0 0-1.027.709.709 0 0 0-1.027 0l-4.4 4.4a.71.71 0 0 0 0 1.027l4.4 4.4c.147.147.367.22.513.22a.79.79 0 0 0 .513-.22.71.71 0 0 0 0-1.027L1.76 5.133Z"
></path></svg
> <span class="visually-hidden">Previous page</span>
</div></button
>
<span class="label" aria-label="page {pageNumber}" aria-current="page"
>{pageNumber * pageSize - pageSize + 1}-{pageNumber * pageSize -
pageSize +
currentPageData.length} of {numberWithCommas(
filteredData.length
)}</span
>
<button
on:click="{goToNextPage}"
disabled="{pageNumber === Math.ceil(filteredData.length / pageSize)}"
><div class="icon-wrapper">
<svg
class="icon"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 7 11"
><path
d="m6.013 4.987-4.4-4.4a.71.71 0 0 0-1.027 0 .709.709 0 0 0 0 1.027L4.473 5.5.586 9.387a.71.71 0 0 0 0 1.027c.147.147.293.22.513.22.22 0 .367-.073.514-.22l4.4-4.4a.71.71 0 0 0 0-1.027Z"
></path></svg
> <span class="visually-hidden">Next page</span>
</div></button
>
</nav>
{/if}
<Pagination
bind:pageNumber
bind:pageSize
bind:pageLength="{currentPageData.length}"
bind:n="{sortedData.length}"
/>{/if}
</section>
</Block>
@ -554,55 +506,4 @@
cursor: pointer;
}
}
nav.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 1rem;
font-size: 1rem;
font-family: $font-family-display;
font-weight: 400;
button {
padding: 5px 10px;
border: 1px solid;
border-color: $tr-contrast-grey;
border-radius: 8px;
background: $white;
color: $tr-medium-grey;
cursor: pointer;
width: 40px;
height: 40px;
&:disabled {
border-color: $tr-light-grey;
color: $tr-light-grey;
cursor: default;
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
transition: all 0.15s ease;
.icon {
height: 1rem;
width: 1rem;
fill: currentColor;
}
}
}
.label {
margin: 0 1rem;
}
}
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
</style>

View file

@ -0,0 +1,13 @@
When you table has lots of rows you should consider breaking it up into pages. This can be done by setting the `paginated` option.
When it is enabled, readers can leaf through the data using a set of buttons below the table. By default there are 25 records per page. You can change the number by adjusting the `pageSize` option.
This is a good option when publishing large tables for readers to explore. It works well with interactive features like searching and filters.
```svelte
<Table
data="{yourData}"
paginated="{true}"
source="{'<a href='https://en.wikipedia.org/wiki/Press_Freedom_Index'>Reporters Without Borders</a>'}"
/>
```

View file

@ -16,12 +16,6 @@ export function paginateArray(array, pageSize, pageNumber) {
return array.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
}
export function numberWithCommas(n) {
const parts = n.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
}
export function uniqueAttr(array, attr) {
return array.map((e) => e[attr]).filter(unique);
}