feat: add PaginationNav component

This commit is contained in:
Eric Liu 2020-07-22 18:14:08 -07:00
commit 8473589e3a
7 changed files with 244 additions and 0 deletions

View file

@ -0,0 +1,18 @@
<script>
export let page = 0;
export let active = false;
</script>
<li class:bx--pagination-nav__list-item={true}>
<button
data-page={page}
aria-current={active ? 'page' : undefined}
class:bx--pagination-nav__page={true}
class:bx--pagination-nav__page--active={active}
on:click>
<span class:bx--pagination-nav__accessibility-label={true}>
<slot />
</span>
{page}
</button>
</li>

View file

@ -0,0 +1,31 @@
<script>
export let page = 0;
import PaginationNav from "./PaginationNav.svelte";
import { Button } from "../Button";
</script>
<div style="width: 800px;">
<PaginationNav
{...$$props}
bind:page
on:change={({ detail }) => {
console.log('on:change', detail);
}}
on:click:button--previous={({ detail }) => {
console.log('button--previous', detail);
}}
on:click:button--next={({ detail }) => {
console.log('button--next', detail);
}} />
<div style="margin-top: 1.5rem">Bound page index: {page}</div>
<Button
kind="tertiary"
size="small"
style="margin-top: 1rem;"
on:click={() => {
page = 3;
}}>
Set page to index 3
</Button>
</div>

View file

@ -0,0 +1,17 @@
import { withKnobs, number, boolean } from "@storybook/addon-knobs";
import Component from "./PaginationNav.Story.svelte";
export default { title: "PaginationNav", decorators: [withKnobs] };
export const Default = () => ({
Component,
props: {
page: number("Current page index (page)", 0),
total: number("Total number of items (total)", 10),
shown: number("Number of items to be shown (minimum 4) (shown)", 10),
loop: boolean(
"Allow user to loop through the items when reaching first / last (loop)",
false
),
},
});

View file

@ -0,0 +1,129 @@
<script>
export let page = 0;
export let total = 10;
export let shown = 10;
export let loop = false;
export let forwardText = "Next page";
export let backwardText = "Previous page";
import { afterUpdate, createEventDispatcher } from "svelte";
import CaretLeft16 from "carbon-icons-svelte/lib/CaretLeft16";
import CaretRight16 from "carbon-icons-svelte/lib/CaretRight16";
import PaginationItem from "./PaginationItem.svelte";
import PaginationOverflow from "./PaginationOverflow.svelte";
import { Button } from "../Button";
const dispatch = createEventDispatcher();
const MIN = 4; // minimum items shown
afterUpdate(() => {
dispatch("change", { page });
});
let front = 0;
let back = 0;
$: fit = shown >= MIN ? shown : MIN;
$: startOffset = fit <= MIN && page > 1 ? 0 : 1;
$: if (fit >= total) {
front = 0;
back = 0;
}
$: if (fit < total) {
const split = Math.ceil(fit / 2) - 1;
front = page - split + 1;
back = total - page - (fit - split) + 1;
if (front <= 1) {
back -= front <= 0 ? Math.abs(front) + 1 : 0;
front = 0;
}
if (back <= 1) {
front -= back <= 0 ? Math.abs(back) + 1 : 0;
back = 0;
}
}
$: items = Array.from({ length: total })
.map((e, i) => i)
.slice(startOffset + front, (back + 1) * -1);
</script>
<nav aria-label="pagination" class:bx--pagination-nav={true} {...$$restProps}>
<ul class:bx--pagination-nav__list={true}>
<li class:bx--pagination-nav__list-item={true}>
<Button
hasIconOnly
kind="ghost"
tooltipAlignment="center"
tooltipPosition="bottom"
iconDescription={backwardText}
disabled={!loop && page === 0}
icon={CaretLeft16}
on:click={() => {
if (page - 1 < 0) {
if (loop) page = total - 1;
} else {
page--;
}
dispatch('click:button--previous', { page });
}} />
</li>
{#if fit > MIN || (fit <= MIN && page <= 1)}
<PaginationItem page={1} active={page === 0} on:click={() => (page = 0)}>
{page === 0 ? 'Active, Page' : 'Page'}
</PaginationItem>
{/if}
<PaginationOverflow
fromIndex={startOffset}
count={front}
on:select={({ detail }) => (page = detail.index)} />
{#each items as item}
<PaginationItem
page={item + 1}
active={page === item}
on:click={() => (page = item)}>
{page === item ? 'Active, Page' : 'Page'}
</PaginationItem>
{/each}
<PaginationOverflow
fromIndex={total - back - 1}
count={back}
on:select={({ detail }) => {
page = detail.index;
}} />
{#if total > 1}
<PaginationItem
page={total}
active={page === total - 1}
on:click={() => (page = total - 1)}>
{page === total - 1 ? 'Active, Page' : 'Page'}
</PaginationItem>
{/if}
<li class:bx--pagination-nav__list-item={true}>
<Button
hasIconOnly
kind="ghost"
tooltipAlignment="center"
tooltipPosition="bottom"
iconDescription={forwardText}
disabled={!loop && page === total - 1}
icon={CaretRight16}
on:click={() => {
if (page + 1 >= total) {
if (loop) page = 0;
} else {
page++;
}
dispatch('click:button--next', { page });
}} />
</li>
</ul>
<div
aria-live="polite"
aria-atomic="true"
class:bx--pagination-nav__accessibility-label={true}>
Page {page + 1} of {total}
</div>
</nav>

View file

@ -0,0 +1,47 @@
<script>
export let fromIndex = 0;
export let count = 0;
import { createEventDispatcher } from "svelte";
import OverflowMenuHorizontal16 from "carbon-icons-svelte/lib/OverflowMenuHorizontal16";
import PaginationItem from "./PaginationItem.svelte";
const dispatch = createEventDispatcher();
let value = "";
</script>
{#if count > 1}
<li class:bx--pagination-nav__list-item={true}>
<div class:bx--pagination-nav__select={true}>
<!-- svelte-ignore a11y-no-onchange -->
<select
aria-label="Select Page number"
{value}
class:bx--pagination-nav__page={true}
class:bx--pagination-nav__page--select={true}
on:change={({ target }) => {
value = '';
dispatch('select', { index: Number(target.value) });
}}>
<option value="" hidden />
{#each Array.from({ length: count }, (_, i) => i) as i}
<option value={fromIndex + i} data-page={fromIndex + i + 1}>
{fromIndex + i + 1}
</option>
{/each}
</select>
<div class:bx--pagination-nav__select-icon-wrapper={true}>
<OverflowMenuHorizontal16 class="bx--pagination-nav__select-icon" />
</div>
</div>
</li>
{:else if count === 1}
<PaginationItem
page={fromIndex + 1}
on:click={() => {
dispatch('select', { index: fromIndex });
}}>
Page
</PaginationItem>
{/if}

View file

@ -0,0 +1 @@
export { default as PaginationNav } from "./PaginationNav.svelte";