@charcoal-ui/react 5.5.0-beta.0 → 5.5.0-beta.2

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.
@@ -1,158 +1,186 @@
1
1
  import './index.css'
2
2
 
3
- import { memo, useCallback } from 'react'
4
- import { usePagerWindow } from './helper'
3
+ import { usePaginationWindow } from './helper'
5
4
  import { useClassNames } from '../../_lib/useClassNames'
6
5
  import IconButton from '../IconButton'
6
+ import {
7
+ PaginationContext,
8
+ usePaginationContext,
9
+ type LinkComponentProps,
10
+ type PageRangeDisplayed,
11
+ type Size,
12
+ } from './PaginationContext'
7
13
 
8
- const Text = 'span'
9
-
10
- interface CommonProps {
11
- page: number
12
- pageCount: number
13
- pageRangeDisplayed?: number
14
+ type NavButtonProps = {
15
+ direction: 'prev' | 'next'
14
16
  }
15
17
 
16
- export interface PaginationProps extends CommonProps {
17
- onChange(newPage: number): void
18
- }
19
-
20
- export default memo(function Pagination({
21
- page,
22
- pageCount,
23
- pageRangeDisplayed,
24
- onChange,
25
- className,
26
- ...props
27
- }: PaginationProps & Omit<React.ComponentPropsWithoutRef<'nav'>, 'onChange'>) {
28
- const window = usePagerWindow(page, pageCount, pageRangeDisplayed)
29
- const makeClickHandler = useCallback(
30
- (value: number) => () => {
31
- onChange(value)
32
- },
33
- [onChange],
34
- )
18
+ function NavButton({ direction }: NavButtonProps) {
19
+ 'use memo'
20
+ const {
21
+ page,
22
+ pageCount,
23
+ size,
24
+ isLinkMode,
25
+ makeUrl,
26
+ LinkComponent,
27
+ makeClickHandler,
28
+ } = usePaginationContext()
35
29
 
36
- const hasNext = page < pageCount
37
- const hasPrev = page > 1
38
- const classNames = useClassNames('charcoal-pagination', className)
30
+ const isPrev = direction === 'prev'
31
+ const targetPage = isPrev
32
+ ? Math.max(1, page - 1)
33
+ : Math.min(pageCount, page + 1)
34
+ const disabled = isPrev ? page <= 1 : page >= pageCount
39
35
 
40
36
  return (
41
- <nav {...props} className={classNames}>
37
+ <IconButton
38
+ icon={isPrev ? '24/Prev' : '24/Next'}
39
+ size={size}
40
+ hidden={disabled}
41
+ className="charcoal-pagination-nav-button"
42
+ {...(isLinkMode && makeUrl
43
+ ? {
44
+ component: LinkComponent as 'a',
45
+ href: makeUrl(targetPage),
46
+ 'aria-disabled': disabled,
47
+ }
48
+ : {
49
+ disabled,
50
+ onClick: makeClickHandler(targetPage),
51
+ })}
52
+ />
53
+ )
54
+ }
55
+
56
+ function PageItem({ value }: { value: number | string }) {
57
+ 'use memo'
58
+ const { page, size, isLinkMode, makeUrl, LinkComponent, makeClickHandler } =
59
+ usePaginationContext()
60
+ // 省略記号
61
+ if (value === '...') {
62
+ return (
42
63
  <IconButton
43
- icon="24/Prev"
44
- size="M"
45
- className="charcoal-pagination-button"
46
- data-no-background
47
- hidden={!hasPrev}
48
- disabled={!hasPrev}
49
- onClick={makeClickHandler(Math.max(1, page - 1))}
64
+ icon="24/Dot"
65
+ size={size}
66
+ disabled
67
+ className="charcoal-pagination-spacer"
68
+ aria-hidden
50
69
  />
51
- {window.map((p) =>
52
- p === '...' ? (
53
- <IconButton
54
- key={p}
55
- icon="24/Dot"
56
- size="M"
57
- disabled
58
- className="charcoal-pagination-button charcoal-pagination-spacer"
59
- aria-hidden
60
- />
61
- ) : p === page ? (
62
- // we remove the onClick but don't mark it as disabled to preserve keyboard focus
63
- // not doing so causes the focus ring to flicker in and out of existence
64
- <button
65
- key={p}
66
- type="button"
67
- className="charcoal-pagination-button"
68
- aria-current
69
- >
70
- <Text>{p}</Text>
71
- </button>
72
- ) : (
73
- <button
74
- key={p}
75
- type="button"
76
- className="charcoal-pagination-button"
77
- onClick={makeClickHandler(p)}
78
- >
79
- <Text>{p}</Text>
80
- </button>
81
- ),
82
- )}
83
- <IconButton
84
- icon="24/Next"
85
- size="M"
70
+ )
71
+ }
72
+ // 現在ページ(クリック不可)
73
+ if (value === page) {
74
+ return (
75
+ <span className="charcoal-pagination-button" aria-current="page">
76
+ {value}
77
+ </span>
78
+ )
79
+ }
80
+ if (typeof value !== 'number') return null
81
+ // リンクモード: ページへのリンク
82
+ if (isLinkMode && makeUrl) {
83
+ return (
84
+ <LinkComponent
85
+ href={makeUrl(value)}
86
86
  className="charcoal-pagination-button"
87
- data-no-background
88
- hidden={!hasNext}
89
- disabled={!hasNext}
90
- onClick={makeClickHandler(Math.min(pageCount, page + 1))}
91
- />
92
- </nav>
87
+ >
88
+ {value}
89
+ </LinkComponent>
90
+ )
91
+ }
92
+ // ボタンモード: クリックでページ遷移
93
+ return (
94
+ <button
95
+ type="button"
96
+ className="charcoal-pagination-button"
97
+ onClick={makeClickHandler(value)}
98
+ >
99
+ {value}
100
+ </button>
93
101
  )
94
- })
102
+ }
95
103
 
96
- export interface LinkPaginationProps extends CommonProps {
97
- makeUrl(page: number): string
104
+ interface CommonProps {
105
+ page: number
106
+ pageCount: number
107
+ pageRangeDisplayed?: PageRangeDisplayed
108
+ size?: Size
98
109
  }
99
110
 
100
- export function LinkPagination({
111
+ type NavProps = Omit<React.ComponentPropsWithoutRef<'nav'>, 'onChange'>
112
+
113
+ /**
114
+ * Pagination component. Use either `onChange` (button mode) or `makeUrl` (link mode).
115
+ *
116
+ * @example
117
+ * // Button mode - for client-side state
118
+ * <Pagination page={1} pageCount={10} onChange={setPage} />
119
+ *
120
+ * @example
121
+ * // Link mode - for server routing / static pages
122
+ * <Pagination page={1} pageCount={10} makeUrl={(p) => `?page=${p}`} />
123
+ *
124
+ * @example
125
+ * // Link mode with custom component (e.g. Next.js Link)
126
+ * <Pagination page={1} pageCount={10} makeUrl={(p) => `?page=${p}`} component={Link} />
127
+ */
128
+ export type PaginationProps = CommonProps &
129
+ NavProps &
130
+ (
131
+ | { onChange(newPage: number): void; makeUrl?: never; component?: never }
132
+ | {
133
+ makeUrl(page: number): string
134
+ onChange?: never
135
+ /**
136
+ * The component used for link elements. Receives `href`, `className`, and `children`.
137
+ * @default 'a'
138
+ */
139
+ component?: React.ElementType<LinkComponentProps>
140
+ }
141
+ )
142
+
143
+ export default function Pagination({
101
144
  page,
102
145
  pageCount,
103
146
  pageRangeDisplayed,
147
+ size = 'M',
148
+ onChange,
104
149
  makeUrl,
150
+ component: LinkComponent = 'a',
105
151
  className,
106
- ...props
107
- }: LinkPaginationProps & React.ComponentPropsWithoutRef<'nav'>) {
108
- const window = usePagerWindow(page, pageCount, pageRangeDisplayed)
109
-
110
- const hasNext = page < pageCount
111
- const hasPrev = page > 1
152
+ ...navProps
153
+ }: PaginationProps) {
154
+ 'use memo'
155
+ const window = usePaginationWindow(page, pageCount, pageRangeDisplayed)
156
+ const isLinkMode = makeUrl !== undefined
157
+ const makeClickHandler = (value: number) => () => onChange?.(value)
112
158
  const classNames = useClassNames('charcoal-pagination', className)
113
159
 
160
+ const contextValue = {
161
+ page,
162
+ pageCount,
163
+ size,
164
+ isLinkMode,
165
+ makeUrl,
166
+ LinkComponent,
167
+ makeClickHandler,
168
+ }
169
+
114
170
  return (
115
- <nav {...props} className={classNames}>
116
- <IconButton
117
- icon="24/Prev"
118
- size="M"
119
- component="a"
120
- href={makeUrl(Math.max(1, page - 1))}
121
- className="charcoal-pagination-button"
122
- data-no-background
123
- hidden={!hasPrev}
124
- aria-disabled={!hasPrev}
125
- />
126
- {window.map((p) =>
127
- p === '...' ? (
128
- <IconButton
129
- key={p}
130
- icon="24/Dot"
131
- size="M"
132
- disabled
133
- className="charcoal-pagination-button charcoal-pagination-spacer"
134
- aria-hidden
135
- />
136
- ) : p === page ? (
137
- <span key={p} className="charcoal-pagination-button" aria-current>
138
- <Text>{p}</Text>
139
- </span>
140
- ) : (
141
- <a key={p} href={makeUrl(p)} className="charcoal-pagination-button">
142
- <Text>{p}</Text>
143
- </a>
144
- ),
145
- )}
146
- <IconButton
147
- icon="24/Next"
148
- size="M"
149
- component="a"
150
- href={makeUrl(Math.min(pageCount, page + 1))}
151
- className="charcoal-pagination-button"
152
- data-no-background
153
- hidden={!hasNext}
154
- aria-disabled={!hasNext}
155
- />
156
- </nav>
171
+ <PaginationContext.Provider value={contextValue}>
172
+ <nav
173
+ className={classNames}
174
+ data-size={size}
175
+ aria-label="Pagination"
176
+ {...navProps}
177
+ >
178
+ <NavButton direction="prev" />
179
+ {window.map((p) => (
180
+ <PageItem key={p} value={p} />
181
+ ))}
182
+ <NavButton direction="next" />
183
+ </nav>
184
+ </PaginationContext.Provider>
157
185
  )
158
186
  }
package/src/index.ts CHANGED
@@ -83,3 +83,7 @@ export {
83
83
  default as UnstableTextEllipsis,
84
84
  type TextEllipsisProps,
85
85
  } from './components/TextEllipsis'
86
+ export {
87
+ default as UnstablePagination,
88
+ type PaginationProps,
89
+ } from './components/Pagination'