Skip to content

Commit 13cf23c

Browse files
committed
✨ Add Pagination component
1 parent ff69141 commit 13cf23c

18 files changed

+791
-9
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ import { Accordion } from 'webcoreui/react'
209209
- [List](https://github.com/Frontendland/webcoreui/tree/main/src/components/List)
210210
- [Menu](https://github.com/Frontendland/webcoreui/tree/main/src/components/Menu)
211211
- [Modal](https://github.com/Frontendland/webcoreui/tree/main/src/components/Modal)
212+
- [Pagination](https://github.com/Frontendland/webcoreui/tree/main/src/components/Pagination)
212213
- [Popover](https://github.com/Frontendland/webcoreui/tree/main/src/components/Popover)
213214
- [Progress](https://github.com/Frontendland/webcoreui/tree/main/src/components/Progress)
214215
- [Radio](https://github.com/Frontendland/webcoreui/tree/main/src/components/Radio)

scripts/buildTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const buildTypes = type => {
2929
'Checkbox',
3030
'Input',
3131
'List',
32+
'Pagination',
3233
'Radio',
3334
'Select',
3435
'Slider',

src/components/Button/button.module.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
.button {
44
@include layout(inline-flex, center, xs);
5-
@include typography(default, primary-50, none);
5+
@include typography(default, primary-50, none, normal);
66
@include spacing(py-sm, px-md);
77
@include border-radius(xs);
88
@include border(0);
@@ -38,6 +38,11 @@
3838
@include typography(primary);
3939
box-shadow: inset 0px 0px 0px 1px var(--w-color-primary);
4040
}
41+
42+
&[disabled] {
43+
@include typography(primary-30);
44+
box-shadow: inset 0px 0px 0px 1px var(--w-color-primary-30);
45+
}
4146
}
4247

4348
&.flat {

src/components/Button/button.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ export type ButtonProps = {
1313
}
1414

1515
export type SvelteButtonProps = {
16-
onClick?: () => any
16+
onClick?: (() => any) | null
1717
} & ButtonProps
1818

1919
export type ReactButtonProps = {
20-
onClick?: () => any
20+
onClick?: (() => any) | null
2121
children: React.ReactNode
2222
} & ButtonProps

src/components/Icon/map.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import Alert from '../../icons/alert.svg?raw'
22
import ArrowDown from '../../icons/arrow-down.svg?raw'
3+
import ArrowLeft from '../../icons/arrow-left.svg?raw'
4+
import ArrowRight from '../../icons/arrow-right.svg?raw'
35
import Check from '../../icons/check.svg?raw'
46
import CircleCheck from '../../icons/circle-check.svg?raw'
57
import Close from '../../icons/close.svg?raw'
@@ -13,6 +15,8 @@ import Warning from '../../icons/warning.svg?raw'
1315
const iconMap = {
1416
'alert': Alert,
1517
'arrow-down': ArrowDown,
18+
'arrow-left': ArrowLeft,
19+
'arrow-right': ArrowRight,
1620
'check': Check,
1721
'circle-check': CircleCheck,
1822
'close': Close,
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
---
2+
import type { PaginationProps } from './pagination'
3+
4+
import Button from '../Button/Button.astro'
5+
6+
import ArrowLeft from '../../icons/arrow-left.svg?raw'
7+
import ArrowRight from '../../icons/arrow-right.svg?raw'
8+
9+
import styles from './pagination.module.scss'
10+
11+
interface Props extends PaginationProps {}
12+
13+
const {
14+
type,
15+
showChevrons,
16+
showDots,
17+
disablePrevious,
18+
disableNext,
19+
previousLink,
20+
nextLink,
21+
previousPageLabel = 'Previous',
22+
nextPageLabel = 'Next',
23+
pages,
24+
theme = 'outline',
25+
totalPages,
26+
currentPage,
27+
className
28+
} = Astro.props
29+
30+
const classes = [
31+
styles.pagination,
32+
theme !== 'outline' && (theme === null ? styles.primary : styles[theme]),
33+
type === 'dots' && styles.dots,
34+
className
35+
]
36+
37+
const calculatedCurrentPage = currentPage
38+
|| (pages?.findIndex(page => page.active) || -1) + 1
39+
|| 1
40+
41+
const calculatedTotalPages = totalPages
42+
|| pages?.length
43+
|| 0
44+
---
45+
46+
<ul
47+
class:list={classes}
48+
data-id="w-pagination"
49+
data-total-pages={calculatedTotalPages}
50+
data-current-page={calculatedCurrentPage}
51+
>
52+
{type === 'dots' ? (
53+
<Fragment>
54+
{pages?.map(page => (
55+
<li>
56+
<button
57+
data-active={page.active ? 'true' : undefined}
58+
data-page={page.label}
59+
/>
60+
</li>
61+
))}
62+
</Fragment>
63+
) : (
64+
<li>
65+
<Button
66+
disabled={disablePrevious || (calculatedCurrentPage === 1 && !previousLink)}
67+
href={previousLink}
68+
theme={theme}
69+
data-page="prev"
70+
>
71+
{(showChevrons || type === 'arrows') && <Fragment set:html={ArrowLeft} />}
72+
{type !== 'arrows' && previousPageLabel}
73+
</Button>
74+
</li>
75+
<Fragment>
76+
{type !== 'arrows' && pages?.slice(0, calculatedTotalPages)?.map((page, index) =>
77+
<li>
78+
<Button
79+
href={page.link}
80+
data-active={calculatedCurrentPage === index + 1 ? 'true' : null}
81+
theme={theme}
82+
data-page={page.label}
83+
>
84+
{page.label}
85+
</Button>
86+
</li>
87+
)}
88+
{showDots && (
89+
<li>
90+
<Button theme={theme} className={styles.inactive}>
91+
...
92+
</Button>
93+
</li>
94+
)}
95+
</Fragment>
96+
<li>
97+
<Button
98+
disabled={disableNext || (calculatedCurrentPage === calculatedTotalPages && !nextLink)}
99+
href={nextLink}
100+
theme={theme}
101+
data-page="next"
102+
>
103+
{type !== 'arrows' && nextPageLabel}
104+
{(showChevrons || type === 'arrows') && <Fragment set:html={ArrowRight} />}
105+
</Button>
106+
</li>
107+
)}
108+
</ul>
109+
110+
<script>
111+
import { dispatch } from '../../utils/event'
112+
113+
const getCurrentPage = (pageElements: Element[], current: number, next: string) => {
114+
if (next === 'prev') {
115+
return current - 1
116+
}
117+
118+
if (next === 'next') {
119+
return current + 1
120+
}
121+
122+
return pageElements?.findIndex(child => {
123+
const button = child.children[0] as HTMLButtonElement
124+
125+
return button.dataset.page === next
126+
}) + 1
127+
}
128+
129+
const paginations = document.querySelectorAll('[data-id="w-pagination"]')
130+
131+
Array.from(paginations).forEach(element => {
132+
const pagination = element as HTMLUListElement
133+
const totalPages = Number(pagination.dataset.totalPages)
134+
let currentPage = Number(pagination.dataset.currentPage)
135+
136+
element.addEventListener('click', event => {
137+
const target = event.target as HTMLButtonElement
138+
const navigated = target.nodeName === 'BUTTON'
139+
&& !target.dataset.active
140+
&& !target.disabled
141+
142+
if (navigated) {
143+
const prevPageButton = element.querySelector('[data-page="prev"]') as HTMLButtonElement
144+
const nextPageButton = element.querySelector('[data-page="next"]') as HTMLButtonElement
145+
const currentPageButton = element.querySelector('[data-active]') as HTMLButtonElement
146+
147+
const pageElements = Array
148+
.from(pagination.children)
149+
.filter(child => {
150+
const button = child.children[0] as HTMLButtonElement
151+
152+
return button.dataset.page && !['prev', 'next'].includes(button.dataset.page)
153+
})
154+
155+
currentPage = getCurrentPage(pageElements, currentPage, target.dataset.page || '')
156+
currentPageButton?.removeAttribute('data-active')
157+
158+
const activeButton = pageElements
159+
.find((_, index) => index + 1 === currentPage)
160+
?.children[0] as HTMLButtonElement
161+
162+
if (activeButton) {
163+
activeButton.dataset.active = 'true'
164+
}
165+
166+
if (prevPageButton && nextPageButton) {
167+
prevPageButton.disabled = currentPage === 1
168+
nextPageButton.disabled = currentPage === totalPages
169+
}
170+
171+
dispatch('paginate', {
172+
page: currentPage,
173+
...(activeButton?.dataset.page && { label: activeButton?.dataset.page }),
174+
element
175+
})
176+
}
177+
})
178+
})
179+
</script>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<script lang="ts">
2+
import type { SveltePaginationProps } from './pagination'
3+
4+
import Button from '../Button/Button.svelte'
5+
6+
import { classNames } from '../../utils/classNames'
7+
8+
import ArrowLeft from '../../icons/arrow-left.svg?raw'
9+
import ArrowRight from '../../icons/arrow-right.svg?raw'
10+
11+
import styles from './pagination.module.scss'
12+
13+
export let type: SveltePaginationProps['type'] = null
14+
export let showChevrons: SveltePaginationProps['showChevrons'] = false
15+
export let showDots: SveltePaginationProps['showDots'] = false
16+
export let disablePrevious: SveltePaginationProps['disablePrevious'] = false
17+
export let disableNext: SveltePaginationProps['disableNext'] = false
18+
export let previousLink: SveltePaginationProps['previousLink'] = ''
19+
export let nextLink: SveltePaginationProps['nextLink'] = ''
20+
export let previousPageLabel: SveltePaginationProps['previousPageLabel'] = 'Previous'
21+
export let nextPageLabel: SveltePaginationProps['nextPageLabel'] = 'Next'
22+
export let pages: SveltePaginationProps['pages'] = []
23+
export let theme: SveltePaginationProps['theme'] = 'outline'
24+
export let totalPages: SveltePaginationProps['totalPages'] = null
25+
export let currentPage: SveltePaginationProps['currentPage'] = null
26+
export let className: SveltePaginationProps['className'] = ''
27+
export let onChange: SveltePaginationProps['onChange'] = () => {}
28+
29+
const classes = classNames([
30+
styles.pagination,
31+
theme !== 'outline' && (theme === null || theme === undefined ? styles.primary : styles[theme]),
32+
type === 'dots' && styles.dots,
33+
className
34+
])
35+
36+
const calculatedTotalPages = totalPages
37+
|| pages?.length
38+
|| 0
39+
40+
const paginate = (to: string | number) => {
41+
if (to === 'prev') {
42+
calculatedCurrentPage = calculatedCurrentPage - 1
43+
} else if (to === 'next') {
44+
calculatedCurrentPage = calculatedCurrentPage + 1
45+
} else {
46+
calculatedCurrentPage = to as number
47+
}
48+
49+
const label = pages?.[calculatedCurrentPage - 1]?.label
50+
51+
onChange?.({
52+
page: calculatedCurrentPage,
53+
...(label && { label })
54+
})
55+
}
56+
57+
let calculatedCurrentPage = currentPage
58+
|| (pages?.findIndex(page => page.active) || -1) + 1
59+
|| 1
60+
</script>
61+
62+
<ul class={classes}>
63+
{#if type === 'dots' && pages?.length}
64+
{#each pages as _, index}
65+
<li>
66+
<button
67+
data-active={calculatedCurrentPage === index + 1 ? 'true' : null}
68+
on:click={calculatedCurrentPage !== index + 1
69+
? () => paginate(index + 1)
70+
: null
71+
}
72+
/>
73+
</li>
74+
{/each}
75+
{:else}
76+
<li>
77+
<Button
78+
disabled={disablePrevious || (calculatedCurrentPage === 1 && !previousLink)}
79+
href={previousLink}
80+
theme={theme}
81+
onClick={!(disablePrevious || (calculatedCurrentPage === 1 && !previousLink))
82+
? () => paginate('prev')
83+
: null
84+
}
85+
>
86+
{#if showChevrons || type === 'arrows'}
87+
{@html ArrowLeft}
88+
{/if}
89+
{#if type !== 'arrows'}
90+
{previousPageLabel}
91+
{/if}
92+
</Button>
93+
</li>
94+
{#if type !== 'arrows' && pages?.length}
95+
{#each pages?.slice(0, calculatedTotalPages) as page, index}
96+
<li>
97+
<Button
98+
href={page.link}
99+
data-active={calculatedCurrentPage === index + 1 ? 'true' : null}
100+
theme={theme}
101+
onClick={calculatedCurrentPage !== index + 1
102+
? () => paginate(index + 1)
103+
: null
104+
}
105+
>
106+
{page.label}
107+
</Button>
108+
</li>
109+
{/each}
110+
{/if}
111+
{#if showDots}
112+
<li>
113+
<Button theme={theme} className={styles.inactive}>
114+
...
115+
</Button>
116+
</li>
117+
{/if}
118+
<li>
119+
<Button
120+
disabled={disableNext || calculatedCurrentPage === calculatedTotalPages}
121+
href={nextLink}
122+
theme={theme}
123+
onClick={!disableNext
124+
? () => paginate('next')
125+
: null
126+
}
127+
>
128+
{#if type !== 'arrows'}
129+
{nextPageLabel}
130+
{/if}
131+
{#if showChevrons || type === 'arrows'}
132+
{@html ArrowRight}
133+
{/if}
134+
</Button>
135+
</li>
136+
{/if}
137+
</ul>

0 commit comments

Comments
 (0)