@charcoal-ui/react 5.5.0-beta.1 → 5.5.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Pagination/PaginationContext.d.ts +20 -0
- package/dist/components/Pagination/PaginationContext.d.ts.map +1 -0
- package/dist/components/Pagination/helper.d.ts +2 -1
- package/dist/components/Pagination/helper.d.ts.map +1 -1
- package/dist/components/Pagination/index.d.ts +15 -9
- package/dist/components/Pagination/index.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +126 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/layered.css +126 -0
- package/dist/layered.css.map +1 -1
- package/package.json +5 -5
- package/src/components/Pagination/Pagination.story.tsx +47 -12
- package/src/components/Pagination/PaginationContext.ts +38 -0
- package/src/components/Pagination/helper.ts +4 -4
- package/src/components/Pagination/index.css +83 -25
- package/src/components/Pagination/index.tsx +169 -94
- package/src/index.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@charcoal-ui/react",
|
|
3
|
-
"version": "5.5.0-beta.
|
|
3
|
+
"version": "5.5.0-beta.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"react-compiler-runtime": "1.0.0",
|
|
55
55
|
"react-stately": "^3.26.0",
|
|
56
56
|
"warning": "^4.0.3",
|
|
57
|
-
"@charcoal-ui/foundation": "5.5.0-beta.
|
|
58
|
-
"@charcoal-ui/
|
|
59
|
-
"@charcoal-ui/
|
|
60
|
-
"@charcoal-ui/utils": "5.5.0-beta.
|
|
57
|
+
"@charcoal-ui/foundation": "5.5.0-beta.3",
|
|
58
|
+
"@charcoal-ui/icons": "5.5.0-beta.3",
|
|
59
|
+
"@charcoal-ui/theme": "5.5.0-beta.3",
|
|
60
|
+
"@charcoal-ui/utils": "5.5.0-beta.3"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"react": ">=17.0.0"
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
3
|
-
import Pagination from '.'
|
|
2
|
+
import { Meta, StoryObj } from '@storybook/react-vite'
|
|
3
|
+
import Pagination, { type PaginationProps } from '.'
|
|
4
4
|
|
|
5
|
-
type PaginationStoryArgs =
|
|
6
|
-
|
|
7
|
-
pageCount
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
type PaginationStoryArgs = Pick<
|
|
6
|
+
PaginationProps,
|
|
7
|
+
'page' | 'pageCount' | 'pageRangeDisplayed' | 'size'
|
|
8
|
+
>
|
|
9
|
+
|
|
10
|
+
type LinkPaginationStoryArgs = PaginationStoryArgs &
|
|
11
|
+
Pick<PaginationProps, 'linkProps'>
|
|
10
12
|
|
|
11
13
|
function PaginationWithState(args: PaginationStoryArgs) {
|
|
12
14
|
const [page, setPage] = useState(args.page)
|
|
@@ -15,6 +17,7 @@ function PaginationWithState(args: PaginationStoryArgs) {
|
|
|
15
17
|
page={page}
|
|
16
18
|
pageCount={args.pageCount}
|
|
17
19
|
pageRangeDisplayed={args.pageRangeDisplayed}
|
|
20
|
+
size={args.size}
|
|
18
21
|
onChange={setPage}
|
|
19
22
|
/>
|
|
20
23
|
)
|
|
@@ -25,7 +28,7 @@ function parsePageFromHash(fallback: number): number {
|
|
|
25
28
|
return match ? parseInt(match[1], 10) : fallback
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
function LinkPaginationWithState(args:
|
|
31
|
+
function LinkPaginationWithState(args: LinkPaginationStoryArgs) {
|
|
29
32
|
const [page, setPage] = useState(() => parsePageFromHash(args.page))
|
|
30
33
|
|
|
31
34
|
useEffect(() => {
|
|
@@ -43,7 +46,9 @@ function LinkPaginationWithState(args: PaginationStoryArgs) {
|
|
|
43
46
|
page={page}
|
|
44
47
|
pageCount={args.pageCount}
|
|
45
48
|
pageRangeDisplayed={args.pageRangeDisplayed}
|
|
49
|
+
size={args.size}
|
|
46
50
|
makeUrl={(p) => `#page-${p}`}
|
|
51
|
+
linkProps={args.linkProps}
|
|
47
52
|
/>
|
|
48
53
|
</div>
|
|
49
54
|
)
|
|
@@ -55,6 +60,7 @@ export default {
|
|
|
55
60
|
parameters: {
|
|
56
61
|
layout: 'centered',
|
|
57
62
|
},
|
|
63
|
+
render: (args) => <PaginationWithState {...args} />,
|
|
58
64
|
} satisfies Meta<typeof Pagination>
|
|
59
65
|
|
|
60
66
|
export const Default: StoryObj<typeof Pagination> = {
|
|
@@ -62,7 +68,6 @@ export const Default: StoryObj<typeof Pagination> = {
|
|
|
62
68
|
page: 5,
|
|
63
69
|
pageCount: 10,
|
|
64
70
|
},
|
|
65
|
-
render: (args) => <PaginationWithState {...args} />,
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
export const FirstPage: StoryObj<typeof Pagination> = {
|
|
@@ -70,7 +75,6 @@ export const FirstPage: StoryObj<typeof Pagination> = {
|
|
|
70
75
|
page: 1,
|
|
71
76
|
pageCount: 10,
|
|
72
77
|
},
|
|
73
|
-
render: (args) => <PaginationWithState {...args} />,
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
export const LastPage: StoryObj<typeof Pagination> = {
|
|
@@ -78,7 +82,6 @@ export const LastPage: StoryObj<typeof Pagination> = {
|
|
|
78
82
|
page: 10,
|
|
79
83
|
pageCount: 10,
|
|
80
84
|
},
|
|
81
|
-
render: (args) => <PaginationWithState {...args} />,
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
export const ManyPages: StoryObj<typeof Pagination> = {
|
|
@@ -86,7 +89,23 @@ export const ManyPages: StoryObj<typeof Pagination> = {
|
|
|
86
89
|
page: 50,
|
|
87
90
|
pageCount: 103,
|
|
88
91
|
},
|
|
89
|
-
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const SizeS: StoryObj<typeof Pagination> = {
|
|
95
|
+
args: {
|
|
96
|
+
page: 5,
|
|
97
|
+
pageCount: 10,
|
|
98
|
+
size: 'S',
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const PageRange5: StoryObj<typeof Pagination> = {
|
|
103
|
+
args: {
|
|
104
|
+
page: 5,
|
|
105
|
+
pageCount: 10,
|
|
106
|
+
pageRangeDisplayed: 5,
|
|
107
|
+
size: 'S',
|
|
108
|
+
},
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
export const LinkPaginationStory: StoryObj<typeof Pagination> = {
|
|
@@ -96,3 +115,19 @@ export const LinkPaginationStory: StoryObj<typeof Pagination> = {
|
|
|
96
115
|
},
|
|
97
116
|
render: (args) => <LinkPaginationWithState {...args} />,
|
|
98
117
|
}
|
|
118
|
+
|
|
119
|
+
export const LinkPaginationWithLinkProps: StoryObj<typeof Pagination> = {
|
|
120
|
+
args: {
|
|
121
|
+
page: 5,
|
|
122
|
+
pageCount: 10,
|
|
123
|
+
linkProps: { scroll: 'marker' },
|
|
124
|
+
},
|
|
125
|
+
render: (args) => (
|
|
126
|
+
<div>
|
|
127
|
+
<p style={{ marginBottom: 8, fontSize: 14, color: '#666' }}>
|
|
128
|
+
linkProps を渡した例(scroll: 'marker' は Next.js Link 用)
|
|
129
|
+
</p>
|
|
130
|
+
<LinkPaginationWithState {...args} />
|
|
131
|
+
</div>
|
|
132
|
+
),
|
|
133
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
export type Size = 'S' | 'M'
|
|
4
|
+
|
|
5
|
+
export type PageRangeDisplayed = 5 | 7
|
|
6
|
+
|
|
7
|
+
export type LinkComponentProps = {
|
|
8
|
+
href: string
|
|
9
|
+
className?: string
|
|
10
|
+
children?: React.ReactNode
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type PaginationContextValue<T extends React.ElementType = 'a'> = {
|
|
14
|
+
page: number
|
|
15
|
+
pageCount: number
|
|
16
|
+
size: Size
|
|
17
|
+
isLinkMode: boolean
|
|
18
|
+
makeUrl?: (page: number) => string
|
|
19
|
+
LinkComponent: T
|
|
20
|
+
makeClickHandler: (value: number) => () => void
|
|
21
|
+
linkProps?: Omit<React.ComponentPropsWithoutRef<T>, 'href' | 'children'>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const PaginationContext = createContext<PaginationContextValue<
|
|
25
|
+
React.ElementType<LinkComponentProps>
|
|
26
|
+
> | null>(null)
|
|
27
|
+
|
|
28
|
+
export function usePaginationContext<
|
|
29
|
+
T extends React.ElementType = 'a',
|
|
30
|
+
>(): PaginationContextValue<T> {
|
|
31
|
+
const context = useContext(PaginationContext)
|
|
32
|
+
if (context == null) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
'Pagination components must be used within a Pagination component',
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
return context as PaginationContextValue<T>
|
|
38
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useDebugValue } from 'react'
|
|
2
2
|
import warning from 'warning'
|
|
3
|
+
import type { PageRangeDisplayed } from './PaginationContext'
|
|
3
4
|
|
|
4
5
|
export function usePaginationWindow(
|
|
5
6
|
page: number,
|
|
6
7
|
pageCount: number,
|
|
7
|
-
pageRangeDisplayed = 7,
|
|
8
|
+
pageRangeDisplayed: PageRangeDisplayed = 7,
|
|
8
9
|
) {
|
|
9
10
|
'use memo'
|
|
10
11
|
// ページャーのリンク生成例:
|
|
@@ -30,10 +31,9 @@ export function usePaginationWindow(
|
|
|
30
31
|
`\`pageCount\` must be integer (${pageCount})`,
|
|
31
32
|
)
|
|
32
33
|
warning(
|
|
33
|
-
|
|
34
|
-
`\`pageRangeDisplayed\` must be
|
|
34
|
+
pageRangeDisplayed === 5 || pageRangeDisplayed === 7,
|
|
35
|
+
`\`pageRangeDisplayed\` must be 5 or 7 (${pageRangeDisplayed})`,
|
|
35
36
|
)
|
|
36
|
-
warning(pageRangeDisplayed > 2, `\`windowSize\` must be greater than 2`)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const visibleFirstPage = 1
|
|
@@ -5,35 +5,55 @@
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
.charcoal-pagination-button {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
border: none;
|
|
8
|
+
cursor: pointer;
|
|
9
|
+
appearance: none;
|
|
10
|
+
padding: 0;
|
|
11
|
+
border-style: none;
|
|
12
12
|
outline: none;
|
|
13
|
-
|
|
13
|
+
text-decoration: none;
|
|
14
|
+
font: inherit;
|
|
15
|
+
margin: 0;
|
|
14
16
|
user-select: none;
|
|
15
|
-
transition:
|
|
16
|
-
box-shadow 0.2s ease 0s,
|
|
17
|
-
color 0.2s ease 0s,
|
|
18
|
-
background 0.2s ease 0s,
|
|
19
|
-
opacity 0.2s ease 0s;
|
|
20
17
|
|
|
21
18
|
display: flex;
|
|
22
|
-
justify-content: center;
|
|
23
19
|
align-items: center;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
justify-content: center;
|
|
21
|
+
font-size: 14px;
|
|
22
|
+
font-weight: 700;
|
|
23
|
+
line-height: 22px;
|
|
24
|
+
|
|
25
|
+
/* HACK:
|
|
26
|
+
* Safari doesn't correctly repaint the elements when they're reordered in response to interaction.
|
|
27
|
+
* This forces it to repaint them. This doesn't work if put on the parents either, has to be here.
|
|
28
|
+
*/
|
|
31
29
|
/* stylelint-disable-next-line property-no-vendor-prefix */
|
|
32
30
|
-webkit-transform: translateZ(0);
|
|
33
31
|
|
|
34
|
-
background: transparent;
|
|
35
32
|
color: var(--charcoal-text3);
|
|
36
|
-
|
|
33
|
+
background-color: var(--charcoal-transparent);
|
|
34
|
+
border-radius: 20px;
|
|
35
|
+
transition:
|
|
36
|
+
0.2s background-color,
|
|
37
|
+
0.2s box-shadow;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.charcoal-pagination-button:focus {
|
|
41
|
+
outline: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.charcoal-pagination-button::-moz-focus-inner {
|
|
45
|
+
border-style: none;
|
|
46
|
+
padding: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.charcoal-pagination[data-size='S'] .charcoal-pagination-button {
|
|
50
|
+
min-width: 32px;
|
|
51
|
+
min-height: 32px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.charcoal-pagination[data-size='M'] .charcoal-pagination-button {
|
|
55
|
+
min-width: 40px;
|
|
56
|
+
min-height: 40px;
|
|
37
57
|
}
|
|
38
58
|
|
|
39
59
|
.charcoal-pagination-button[hidden] {
|
|
@@ -41,22 +61,60 @@
|
|
|
41
61
|
display: block;
|
|
42
62
|
}
|
|
43
63
|
|
|
44
|
-
.charcoal-pagination-button:hover
|
|
45
|
-
|
|
46
|
-
color: var(--charcoal-
|
|
64
|
+
.charcoal-pagination-button:not(:disabled):not([aria-disabled]):hover,
|
|
65
|
+
.charcoal-pagination-button[aria-disabled='false']:hover {
|
|
66
|
+
color: var(--charcoal-text3);
|
|
67
|
+
background-color: var(--charcoal-surface3);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.charcoal-pagination-button:not(:disabled):not([aria-disabled]):active,
|
|
71
|
+
.charcoal-pagination-button[aria-disabled='false']:active {
|
|
72
|
+
color: var(--charcoal-text3);
|
|
73
|
+
background-color: var(--charcoal-surface10);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.charcoal-pagination-button:not(:disabled):not([aria-disabled]):focus,
|
|
77
|
+
.charcoal-pagination-button[aria-disabled='false']:focus {
|
|
78
|
+
outline: none;
|
|
79
|
+
box-shadow: 0 0 0 4px rgba(0, 150, 250, 0.32);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.charcoal-pagination-button:not(:disabled):not([aria-disabled]):focus-visible,
|
|
83
|
+
.charcoal-pagination-button[aria-disabled='false']:focus-visible {
|
|
84
|
+
box-shadow: 0 0 0 4px rgba(0, 150, 250, 0.32);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.charcoal-pagination-button:not(:disabled):not([aria-disabled]):focus:not(
|
|
88
|
+
:focus-visible
|
|
89
|
+
),
|
|
90
|
+
.charcoal-pagination-button[aria-disabled='false']:focus:not(:focus-visible) {
|
|
91
|
+
box-shadow: none;
|
|
47
92
|
}
|
|
48
93
|
|
|
49
94
|
.charcoal-pagination-button[aria-current] {
|
|
95
|
+
cursor: default;
|
|
50
96
|
background-color: var(--charcoal-surface6);
|
|
51
97
|
color: var(--charcoal-text5);
|
|
52
98
|
}
|
|
53
99
|
|
|
54
|
-
.charcoal-pagination-button[aria-current]:
|
|
100
|
+
.charcoal-pagination-button[aria-current]:not(:disabled):not(
|
|
101
|
+
[aria-disabled]
|
|
102
|
+
):hover,
|
|
103
|
+
.charcoal-pagination-button[aria-current]:not(:disabled):not(
|
|
104
|
+
[aria-disabled]
|
|
105
|
+
):active {
|
|
55
106
|
background-color: var(--charcoal-surface6);
|
|
56
107
|
color: var(--charcoal-text5);
|
|
57
108
|
}
|
|
58
109
|
|
|
59
|
-
.charcoal-pagination-
|
|
110
|
+
.charcoal-pagination-nav-button[hidden] {
|
|
111
|
+
visibility: hidden;
|
|
112
|
+
display: block;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.charcoal-pagination-spacer,
|
|
116
|
+
.charcoal-pagination-spacer:hover,
|
|
117
|
+
.charcoal-pagination-spacer:active {
|
|
60
118
|
cursor: default;
|
|
61
119
|
color: var(--charcoal-text3);
|
|
62
120
|
background: none;
|
|
@@ -3,17 +3,130 @@ import './index.css'
|
|
|
3
3
|
import { usePaginationWindow } from './helper'
|
|
4
4
|
import { useClassNames } from '../../_lib/useClassNames'
|
|
5
5
|
import IconButton from '../IconButton'
|
|
6
|
+
import {
|
|
7
|
+
PaginationContext,
|
|
8
|
+
usePaginationContext,
|
|
9
|
+
type LinkComponentProps,
|
|
10
|
+
type PaginationContextValue,
|
|
11
|
+
type PageRangeDisplayed,
|
|
12
|
+
type Size,
|
|
13
|
+
} from './PaginationContext'
|
|
14
|
+
|
|
15
|
+
type NavButtonProps = {
|
|
16
|
+
direction: 'prev' | 'next'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function NavButton({ direction }: NavButtonProps) {
|
|
20
|
+
'use memo'
|
|
21
|
+
const {
|
|
22
|
+
page,
|
|
23
|
+
pageCount,
|
|
24
|
+
size,
|
|
25
|
+
isLinkMode,
|
|
26
|
+
makeUrl,
|
|
27
|
+
LinkComponent,
|
|
28
|
+
makeClickHandler,
|
|
29
|
+
linkProps,
|
|
30
|
+
} = usePaginationContext()
|
|
31
|
+
|
|
32
|
+
const isPrev = direction === 'prev'
|
|
33
|
+
const targetPage = isPrev
|
|
34
|
+
? Math.max(1, page - 1)
|
|
35
|
+
: Math.min(pageCount, page + 1)
|
|
36
|
+
const disabled = isPrev ? page <= 1 : page >= pageCount
|
|
37
|
+
const navButtonClassName = useClassNames(
|
|
38
|
+
'charcoal-pagination-nav-button',
|
|
39
|
+
linkProps?.className,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<IconButton
|
|
44
|
+
icon={isPrev ? '24/Prev' : '24/Next'}
|
|
45
|
+
size={size}
|
|
46
|
+
hidden={disabled}
|
|
47
|
+
{...(isLinkMode && makeUrl
|
|
48
|
+
? {
|
|
49
|
+
component: LinkComponent as 'a',
|
|
50
|
+
href: makeUrl(targetPage),
|
|
51
|
+
'aria-disabled': disabled,
|
|
52
|
+
...linkProps,
|
|
53
|
+
className: navButtonClassName,
|
|
54
|
+
}
|
|
55
|
+
: {
|
|
56
|
+
disabled,
|
|
57
|
+
onClick: makeClickHandler(targetPage),
|
|
58
|
+
className: navButtonClassName,
|
|
59
|
+
})}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function PageItem({ value }: { value: number | string }) {
|
|
65
|
+
'use memo'
|
|
66
|
+
const {
|
|
67
|
+
page,
|
|
68
|
+
size,
|
|
69
|
+
isLinkMode,
|
|
70
|
+
makeUrl,
|
|
71
|
+
LinkComponent,
|
|
72
|
+
makeClickHandler,
|
|
73
|
+
linkProps,
|
|
74
|
+
} = usePaginationContext()
|
|
75
|
+
const pageItemClassName = useClassNames(
|
|
76
|
+
'charcoal-pagination-button',
|
|
77
|
+
linkProps?.className,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
// 省略記号
|
|
81
|
+
if (value === '...') {
|
|
82
|
+
return (
|
|
83
|
+
<IconButton
|
|
84
|
+
icon="24/Dot"
|
|
85
|
+
size={size}
|
|
86
|
+
disabled
|
|
87
|
+
className="charcoal-pagination-spacer"
|
|
88
|
+
aria-hidden
|
|
89
|
+
/>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
// 現在ページ(クリック不可)
|
|
93
|
+
if (value === page) {
|
|
94
|
+
return (
|
|
95
|
+
<span className="charcoal-pagination-button" aria-current="page">
|
|
96
|
+
{value}
|
|
97
|
+
</span>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
if (typeof value !== 'number') return null
|
|
101
|
+
// リンクモード: ページへのリンク
|
|
102
|
+
if (isLinkMode && makeUrl) {
|
|
103
|
+
return (
|
|
104
|
+
<LinkComponent
|
|
105
|
+
href={makeUrl(value)}
|
|
106
|
+
{...linkProps}
|
|
107
|
+
className={pageItemClassName}
|
|
108
|
+
>
|
|
109
|
+
{value}
|
|
110
|
+
</LinkComponent>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
// ボタンモード: クリックでページ遷移
|
|
114
|
+
return (
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
className="charcoal-pagination-button"
|
|
118
|
+
onClick={makeClickHandler(value)}
|
|
119
|
+
>
|
|
120
|
+
{value}
|
|
121
|
+
</button>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
6
124
|
|
|
7
125
|
interface CommonProps {
|
|
8
126
|
page: number
|
|
9
127
|
pageCount: number
|
|
10
|
-
pageRangeDisplayed?:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
type LinkComponentProps = {
|
|
14
|
-
href: string
|
|
15
|
-
className?: string
|
|
16
|
-
children?: React.ReactNode
|
|
128
|
+
pageRangeDisplayed?: PageRangeDisplayed
|
|
129
|
+
size?: Size
|
|
17
130
|
}
|
|
18
131
|
|
|
19
132
|
type NavProps = Omit<React.ComponentPropsWithoutRef<'nav'>, 'onChange'>
|
|
@@ -32,11 +145,20 @@ type NavProps = Omit<React.ComponentPropsWithoutRef<'nav'>, 'onChange'>
|
|
|
32
145
|
* @example
|
|
33
146
|
* // Link mode with custom component (e.g. Next.js Link)
|
|
34
147
|
* <Pagination page={1} pageCount={10} makeUrl={(p) => `?page=${p}`} component={Link} />
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* // Link mode with linkProps (e.g. Next.js scroll)
|
|
151
|
+
* <Pagination page={1} pageCount={10} makeUrl={(p) => `?page=${p}`} component={Link} linkProps={{ scroll: 'marker' }} />
|
|
35
152
|
*/
|
|
36
|
-
export type PaginationProps = CommonProps &
|
|
153
|
+
export type PaginationProps<T extends React.ElementType = 'a'> = CommonProps &
|
|
37
154
|
NavProps &
|
|
38
155
|
(
|
|
39
|
-
| {
|
|
156
|
+
| {
|
|
157
|
+
onChange(newPage: number): void
|
|
158
|
+
makeUrl?: never
|
|
159
|
+
component?: never
|
|
160
|
+
linkProps?: undefined
|
|
161
|
+
}
|
|
40
162
|
| {
|
|
41
163
|
makeUrl(page: number): string
|
|
42
164
|
onChange?: never
|
|
@@ -44,110 +166,63 @@ export type PaginationProps = CommonProps &
|
|
|
44
166
|
* The component used for link elements. Receives `href`, `className`, and `children`.
|
|
45
167
|
* @default 'a'
|
|
46
168
|
*/
|
|
47
|
-
component?:
|
|
169
|
+
component?: T
|
|
170
|
+
/**
|
|
171
|
+
* Additional props passed to all link elements (e.g. Next.js Link's scroll, prefetch).
|
|
172
|
+
*/
|
|
173
|
+
linkProps?: Omit<React.ComponentPropsWithoutRef<T>, 'href' | 'children'>
|
|
48
174
|
}
|
|
49
175
|
)
|
|
50
176
|
|
|
51
|
-
export default function Pagination({
|
|
177
|
+
export default function Pagination<T extends React.ElementType = 'a'>({
|
|
52
178
|
page,
|
|
53
179
|
pageCount,
|
|
54
180
|
pageRangeDisplayed,
|
|
181
|
+
size = 'M',
|
|
55
182
|
onChange,
|
|
56
183
|
makeUrl,
|
|
57
|
-
component: LinkComponent = 'a',
|
|
184
|
+
component: LinkComponent = 'a' as T,
|
|
185
|
+
linkProps,
|
|
58
186
|
className,
|
|
59
187
|
...navProps
|
|
60
|
-
}: PaginationProps) {
|
|
188
|
+
}: PaginationProps<T>) {
|
|
61
189
|
'use memo'
|
|
62
190
|
const window = usePaginationWindow(page, pageCount, pageRangeDisplayed)
|
|
63
191
|
const isLinkMode = makeUrl !== undefined
|
|
64
|
-
|
|
65
|
-
// 'use memo' により React Compiler が自動でメモ化するため useCallback は不要
|
|
66
192
|
const makeClickHandler = (value: number) => () => onChange?.(value)
|
|
67
|
-
|
|
68
193
|
const classNames = useClassNames('charcoal-pagination', className)
|
|
69
194
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
icon={isPrev ? '24/Prev' : '24/Next'}
|
|
80
|
-
size="M"
|
|
81
|
-
className="charcoal-pagination-button"
|
|
82
|
-
hidden={disabled}
|
|
83
|
-
{...(isLinkMode && makeUrl
|
|
84
|
-
? {
|
|
85
|
-
component: LinkComponent as 'a',
|
|
86
|
-
href: makeUrl(targetPage),
|
|
87
|
-
'aria-disabled': disabled,
|
|
88
|
-
}
|
|
89
|
-
: {
|
|
90
|
-
disabled,
|
|
91
|
-
onClick: makeClickHandler(targetPage),
|
|
92
|
-
})}
|
|
93
|
-
/>
|
|
94
|
-
)
|
|
195
|
+
const contextValue = {
|
|
196
|
+
page,
|
|
197
|
+
pageCount,
|
|
198
|
+
size,
|
|
199
|
+
isLinkMode,
|
|
200
|
+
makeUrl,
|
|
201
|
+
LinkComponent,
|
|
202
|
+
makeClickHandler,
|
|
203
|
+
linkProps,
|
|
95
204
|
}
|
|
96
205
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
icon="24/Dot"
|
|
103
|
-
size="M"
|
|
104
|
-
disabled
|
|
105
|
-
className="charcoal-pagination-button charcoal-pagination-spacer"
|
|
106
|
-
aria-hidden
|
|
107
|
-
/>
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
// 現在ページ(クリック不可)
|
|
111
|
-
if (value === page) {
|
|
112
|
-
return (
|
|
113
|
-
<span className="charcoal-pagination-button" aria-current="page">
|
|
114
|
-
{value}
|
|
115
|
-
</span>
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (typeof value !== 'number') return null
|
|
120
|
-
|
|
121
|
-
// リンクモード: ページへのリンク
|
|
122
|
-
if (isLinkMode && makeUrl) {
|
|
123
|
-
return (
|
|
124
|
-
<LinkComponent
|
|
125
|
-
href={makeUrl(value)}
|
|
126
|
-
className="charcoal-pagination-button"
|
|
206
|
+
return (
|
|
207
|
+
<PaginationContext.Provider
|
|
208
|
+
value={
|
|
209
|
+
contextValue as PaginationContextValue<
|
|
210
|
+
React.ElementType<LinkComponentProps>
|
|
127
211
|
>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
type="button"
|
|
136
|
-
className="charcoal-pagination-button"
|
|
137
|
-
onClick={makeClickHandler(value)}
|
|
212
|
+
}
|
|
213
|
+
>
|
|
214
|
+
<nav
|
|
215
|
+
data-size={size}
|
|
216
|
+
aria-label="Pagination"
|
|
217
|
+
{...navProps}
|
|
218
|
+
className={classNames}
|
|
138
219
|
>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<NavButton direction="prev" />
|
|
147
|
-
{window.map((p) => (
|
|
148
|
-
<PageItem key={p} value={p} />
|
|
149
|
-
))}
|
|
150
|
-
<NavButton direction="next" />
|
|
151
|
-
</nav>
|
|
220
|
+
<NavButton direction="prev" />
|
|
221
|
+
{window.map((p) => (
|
|
222
|
+
<PageItem key={p} value={p} />
|
|
223
|
+
))}
|
|
224
|
+
<NavButton direction="next" />
|
|
225
|
+
</nav>
|
|
226
|
+
</PaginationContext.Provider>
|
|
152
227
|
)
|
|
153
228
|
}
|