Files
react/components/pagination/pagination.tsx
2020-06-20 13:25:12 +08:00

143 lines
3.6 KiB
TypeScript

import React, { useEffect, useMemo } from 'react'
import PaginationPrevious from './pagination-previous'
import PaginationNext from './pagination-next'
import PaginationPages from './pagination-pages'
import { PaginationContext, PaginationConfig, PaginationUpdateType } from './pagination-context'
import useCurrentState from '../utils/use-current-state'
import { pickChild } from '../utils/collections'
import { NormalSizes } from '../utils/prop-types'
interface Props {
size?: NormalSizes
page?: number
initialPage?: number
count?: number
limit?: number
onChange?: (val: number) => void
}
const defaultProps = {
size: 'medium' as NormalSizes,
initialPage: 1,
count: 1,
limit: 7,
}
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type PaginationProps = Props & typeof defaultProps & NativeAttrs
type PaginationSize = {
font: string
width: string
}
const getPaginationSizes = (size: NormalSizes) => {
const sizes: { [key in NormalSizes]: PaginationSize } = {
mini: {
font: '.75rem',
width: '1.25rem',
},
small: {
font: '.75rem',
width: '1.65rem',
},
medium: {
font: '.875rem',
width: '2rem',
},
large: {
font: '1rem',
width: '2.4rem',
},
}
return sizes[size]
}
const Pagination: React.FC<React.PropsWithChildren<PaginationProps>> = ({
page: customPage,
initialPage,
count,
limit,
size,
children,
onChange,
}) => {
const [page, setPage, pageRef] = useCurrentState(initialPage)
const [, prevChildren] = pickChild(children, PaginationPrevious)
const [, nextChildren] = pickChild(children, PaginationNext)
const [prevItem, nextItem] = useMemo(() => {
const hasChildren = (c: any) => React.Children.count(c) > 0
const prevDefault = <PaginationPrevious>prev</PaginationPrevious>
const nextDefault = <PaginationNext>next</PaginationNext>
return [
hasChildren(prevChildren) ? prevChildren : prevDefault,
hasChildren(nextChildren) ? nextChildren : nextDefault,
]
}, [prevChildren, nextChildren])
const { font, width } = useMemo(() => getPaginationSizes(size), [size])
const update = (type: PaginationUpdateType) => {
if (type === 'prev' && pageRef.current > 1) {
setPage(last => last - 1)
}
if (type === 'next' && pageRef.current < count) {
setPage(last => last + 1)
}
}
const values = useMemo<PaginationConfig>(
() => ({
isFirst: page <= 1,
isLast: page >= count,
update,
}),
[page],
)
useEffect(() => {
onChange && onChange(page)
}, [page])
useEffect(() => {
if (customPage !== undefined) {
setPage(customPage)
}
}, [customPage])
return (
<PaginationContext.Provider value={values}>
<nav>
{prevItem}
<PaginationPages count={count} current={page} limit={limit} setPage={setPage} />
{nextItem}
</nav>
<style jsx>{`
nav {
margin: 0;
padding: 0;
font-variant: tabular-nums;
font-feature-settings: 'tnum';
font-size: ${font};
--pagination-size: ${width};
}
nav :global(button:last-of-type) {
margin-right: 0;
}
`}</style>
</PaginationContext.Provider>
)
}
type MemoPaginationComponent<P = {}> = React.NamedExoticComponent<P> & {
Previous: typeof PaginationPrevious
Next: typeof PaginationNext
}
type ComponentProps = Partial<typeof defaultProps> &
Omit<Props, keyof typeof defaultProps> &
NativeAttrs
Pagination.defaultProps = defaultProps
export default React.memo(Pagination) as MemoPaginationComponent<ComponentProps>