mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-14 18:01:06 +00:00
Merge pull request #215 from IBM/pagination-nav
feat: add PaginationNav component
This commit is contained in:
commit
2ae4c8a008
7 changed files with 244 additions and 0 deletions
18
src/PaginationNav/PaginationItem.svelte
Normal file
18
src/PaginationNav/PaginationItem.svelte
Normal 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>
|
31
src/PaginationNav/PaginationNav.Story.svelte
Normal file
31
src/PaginationNav/PaginationNav.Story.svelte
Normal 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>
|
17
src/PaginationNav/PaginationNav.stories.js
Normal file
17
src/PaginationNav/PaginationNav.stories.js
Normal 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
|
||||
),
|
||||
},
|
||||
});
|
129
src/PaginationNav/PaginationNav.svelte
Normal file
129
src/PaginationNav/PaginationNav.svelte
Normal 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>
|
47
src/PaginationNav/PaginationOverflow.svelte
Normal file
47
src/PaginationNav/PaginationOverflow.svelte
Normal 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}
|
1
src/PaginationNav/index.js
Normal file
1
src/PaginationNav/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as PaginationNav } from "./PaginationNav.svelte";
|
|
@ -65,6 +65,7 @@ export { NumberInput, NumberInputSkeleton } from "./NumberInput";
|
|||
export { OrderedList } from "./OrderedList";
|
||||
export { OverflowMenu, OverflowMenuItem } from "./OverflowMenu";
|
||||
export { Pagination, PaginationSkeleton } from "./Pagination";
|
||||
export { PaginationNav } from "./PaginationNav";
|
||||
export {
|
||||
ProgressIndicator,
|
||||
ProgressIndicatorSkeleton,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue