import './index.css'
import { usePaginationWindow } from './helper'
import { useClassNames } from '../../_lib/useClassNames'
import IconButton, { IconButtonProps } from '../IconButton'
import {
PaginationContext,
usePaginationContext,
type LinkComponentProps,
type PaginationContextValue,
type PageRangeDisplayed,
type Size,
} from './PaginationContext'
type NavButtonProps = {
direction: 'prev' | 'next'
ariaLabel: IconButtonProps['aria-label']
}
function NavButton({ direction, ariaLabel }: NavButtonProps) {
'use memo'
const {
page,
pageCount,
size,
isLinkMode,
makeUrl,
LinkComponent,
makeClickHandler,
linkProps,
} = usePaginationContext()
const isPrev = direction === 'prev'
const targetPage = isPrev
? Math.max(1, page - 1)
: Math.min(pageCount, page + 1)
const disabled = isPrev ? page <= 1 : page >= pageCount
const navButtonClassName = useClassNames(
'charcoal-pagination-nav-button',
linkProps?.className,
)
return (
)
}
function PageItem({ value }: { value: number | string }) {
'use memo'
const {
page,
size,
isLinkMode,
makeUrl,
LinkComponent,
makeClickHandler,
linkProps,
} = usePaginationContext()
const pageItemClassName = useClassNames(
'charcoal-pagination-button',
linkProps?.className,
)
// 省略記号
if (value === '...') {
return (
)
}
// 現在ページ(クリック不可)
if (value === page) {
return (
{value}
)
}
if (typeof value !== 'number') return null
// リンクモード: ページへのリンク
if (isLinkMode && makeUrl) {
return (
{value}
)
}
// ボタンモード: クリックでページ遷移
return (
)
}
interface PaginationCommonProps {
page: number
pageCount: number
pageRangeDisplayed?: PageRangeDisplayed
size?: Size
ariaLabelPrev?: string
ariaLabelNext?: string
}
type NavProps = Omit, 'onChange'>
/**
* Pagination component. Use either `onChange` (button mode) or `makeUrl` (link mode).
*
* @example
* // Button mode - for client-side state
*
*
* @example
* // Link mode - for server routing / static pages
* `?page=${p}`} />
*
* @example
* // Link mode with custom component (e.g. Next.js Link)
* `?page=${p}`} component={Link} />
*
* @example
* // Link mode with linkProps (e.g. Next.js scroll)
* `?page=${p}`} component={Link} linkProps={{ scroll: 'marker' }} />
*/
export type PaginationProps =
PaginationCommonProps &
NavProps &
(
| {
onChange(newPage: number): void
makeUrl?: never
component?: never
linkProps?: undefined
}
| {
makeUrl(page: number): string
onChange?: never
/**
* The component used for link elements. Receives `href`, `className`, and `children`.
* @default 'a'
*/
component?: T
/**
* Additional props passed to all link elements (e.g. Next.js Link's scroll, prefetch).
*/
linkProps?: Omit<
React.ComponentPropsWithoutRef,
'href' | 'children'
>
}
)
export default function Pagination({
page,
pageCount,
pageRangeDisplayed,
size = 'M',
onChange,
makeUrl,
component: LinkComponent = 'a' as T,
linkProps,
className,
ariaLabelNext = 'Next',
ariaLabelPrev = 'Previous',
...navProps
}: PaginationProps) {
'use memo'
const window = usePaginationWindow(page, pageCount, pageRangeDisplayed)
const isLinkMode = makeUrl !== undefined
const makeClickHandler = (value: number) => () => onChange?.(value)
const classNames = useClassNames('charcoal-pagination', className)
const contextValue = {
page,
pageCount,
size,
isLinkMode,
makeUrl,
LinkComponent,
makeClickHandler,
linkProps,
}
return (
>
}
>
)
}