@charcoal-ui/react 5.5.0-beta.1 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcoal-ui/react",
3
- "version": "5.5.0-beta.1",
3
+ "version": "5.5.0-beta.2",
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.1",
58
- "@charcoal-ui/theme": "5.5.0-beta.1",
59
- "@charcoal-ui/icons": "5.5.0-beta.1",
60
- "@charcoal-ui/utils": "5.5.0-beta.1"
57
+ "@charcoal-ui/foundation": "5.5.0-beta.2",
58
+ "@charcoal-ui/theme": "5.5.0-beta.2",
59
+ "@charcoal-ui/utils": "5.5.0-beta.2",
60
+ "@charcoal-ui/icons": "5.5.0-beta.2"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "react": ">=17.0.0"
@@ -1,12 +1,11 @@
1
1
  import { useEffect, useState } from 'react'
2
- import { Meta, StoryObj } from '@storybook/react-webpack5'
3
- import Pagination from '.'
2
+ import { Meta, StoryObj } from '@storybook/react-vite'
3
+ import Pagination, { type PaginationProps } from '.'
4
4
 
5
- type PaginationStoryArgs = {
6
- page: number
7
- pageCount: number
8
- pageRangeDisplayed?: number
9
- }
5
+ type PaginationStoryArgs = Pick<
6
+ PaginationProps,
7
+ 'page' | 'pageCount' | 'pageRangeDisplayed' | 'size'
8
+ >
10
9
 
11
10
  function PaginationWithState(args: PaginationStoryArgs) {
12
11
  const [page, setPage] = useState(args.page)
@@ -15,6 +14,7 @@ function PaginationWithState(args: PaginationStoryArgs) {
15
14
  page={page}
16
15
  pageCount={args.pageCount}
17
16
  pageRangeDisplayed={args.pageRangeDisplayed}
17
+ size={args.size}
18
18
  onChange={setPage}
19
19
  />
20
20
  )
@@ -43,6 +43,7 @@ function LinkPaginationWithState(args: PaginationStoryArgs) {
43
43
  page={page}
44
44
  pageCount={args.pageCount}
45
45
  pageRangeDisplayed={args.pageRangeDisplayed}
46
+ size={args.size}
46
47
  makeUrl={(p) => `#page-${p}`}
47
48
  />
48
49
  </div>
@@ -55,6 +56,7 @@ export default {
55
56
  parameters: {
56
57
  layout: 'centered',
57
58
  },
59
+ render: (args) => <PaginationWithState {...args} />,
58
60
  } satisfies Meta<typeof Pagination>
59
61
 
60
62
  export const Default: StoryObj<typeof Pagination> = {
@@ -62,7 +64,6 @@ export const Default: StoryObj<typeof Pagination> = {
62
64
  page: 5,
63
65
  pageCount: 10,
64
66
  },
65
- render: (args) => <PaginationWithState {...args} />,
66
67
  }
67
68
 
68
69
  export const FirstPage: StoryObj<typeof Pagination> = {
@@ -70,7 +71,6 @@ export const FirstPage: StoryObj<typeof Pagination> = {
70
71
  page: 1,
71
72
  pageCount: 10,
72
73
  },
73
- render: (args) => <PaginationWithState {...args} />,
74
74
  }
75
75
 
76
76
  export const LastPage: StoryObj<typeof Pagination> = {
@@ -78,7 +78,6 @@ export const LastPage: StoryObj<typeof Pagination> = {
78
78
  page: 10,
79
79
  pageCount: 10,
80
80
  },
81
- render: (args) => <PaginationWithState {...args} />,
82
81
  }
83
82
 
84
83
  export const ManyPages: StoryObj<typeof Pagination> = {
@@ -86,7 +85,23 @@ export const ManyPages: StoryObj<typeof Pagination> = {
86
85
  page: 50,
87
86
  pageCount: 103,
88
87
  },
89
- render: (args) => <PaginationWithState {...args} />,
88
+ }
89
+
90
+ export const SizeS: StoryObj<typeof Pagination> = {
91
+ args: {
92
+ page: 5,
93
+ pageCount: 10,
94
+ size: 'S',
95
+ },
96
+ }
97
+
98
+ export const PageRange5: StoryObj<typeof Pagination> = {
99
+ args: {
100
+ page: 5,
101
+ pageCount: 10,
102
+ pageRangeDisplayed: 5,
103
+ size: 'S',
104
+ },
90
105
  }
91
106
 
92
107
  export const LinkPaginationStory: StoryObj<typeof Pagination> = {
@@ -0,0 +1,35 @@
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
+ type PaginationContextValue = {
14
+ page: number
15
+ pageCount: number
16
+ size: Size
17
+ isLinkMode: boolean
18
+ makeUrl?: (page: number) => string
19
+ LinkComponent: React.ElementType<LinkComponentProps>
20
+ makeClickHandler: (value: number) => () => void
21
+ }
22
+
23
+ export const PaginationContext = createContext<PaginationContextValue | null>(
24
+ null,
25
+ )
26
+
27
+ export function usePaginationContext(): PaginationContextValue {
28
+ const context = useContext(PaginationContext)
29
+ if (context == null) {
30
+ throw new Error(
31
+ 'Pagination components must be used within a Pagination component',
32
+ )
33
+ }
34
+ return context
35
+ }
@@ -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
- (pageRangeDisplayed | 0) === pageRangeDisplayed,
34
- `\`pageRangeDisplayed\` must be integer (${pageRangeDisplayed})`,
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
- font-size: 1rem;
9
- line-height: calc(1em + 8px);
10
- text-decoration: none;
11
- border: none;
8
+ cursor: pointer;
9
+ appearance: none;
10
+ padding: 0;
11
+ border-style: none;
12
12
  outline: none;
13
- touch-action: manipulation;
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
- box-sizing: content-box;
25
- min-width: 24px;
26
- min-height: 24px;
27
- padding: 8px;
28
- cursor: pointer;
29
- font-weight: bold;
30
- /* HACK: Safari repaint fix */
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
- border-radius: 48px;
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
- background: var(--charcoal-surface3);
46
- color: var(--charcoal-text2);
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]:hover {
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-spacer {
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,109 @@ 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 PageRangeDisplayed,
11
+ type Size,
12
+ } from './PaginationContext'
13
+
14
+ type NavButtonProps = {
15
+ direction: 'prev' | 'next'
16
+ }
17
+
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()
29
+
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
35
+
36
+ return (
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 (
63
+ <IconButton
64
+ icon="24/Dot"
65
+ size={size}
66
+ disabled
67
+ className="charcoal-pagination-spacer"
68
+ aria-hidden
69
+ />
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
+ className="charcoal-pagination-button"
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>
101
+ )
102
+ }
6
103
 
7
104
  interface CommonProps {
8
105
  page: number
9
106
  pageCount: number
10
- pageRangeDisplayed?: number
11
- }
12
-
13
- type LinkComponentProps = {
14
- href: string
15
- className?: string
16
- children?: React.ReactNode
107
+ pageRangeDisplayed?: PageRangeDisplayed
108
+ size?: Size
17
109
  }
18
110
 
19
111
  type NavProps = Omit<React.ComponentPropsWithoutRef<'nav'>, 'onChange'>
@@ -52,6 +144,7 @@ export default function Pagination({
52
144
  page,
53
145
  pageCount,
54
146
  pageRangeDisplayed,
147
+ size = 'M',
55
148
  onChange,
56
149
  makeUrl,
57
150
  component: LinkComponent = 'a',
@@ -61,93 +154,33 @@ export default function Pagination({
61
154
  'use memo'
62
155
  const window = usePaginationWindow(page, pageCount, pageRangeDisplayed)
63
156
  const isLinkMode = makeUrl !== undefined
64
-
65
- // 'use memo' により React Compiler が自動でメモ化するため useCallback は不要
66
157
  const makeClickHandler = (value: number) => () => onChange?.(value)
67
-
68
158
  const classNames = useClassNames('charcoal-pagination', className)
69
159
 
70
- const NavButton = ({ direction }: { direction: 'prev' | 'next' }) => {
71
- const isPrev = direction === 'prev'
72
- const targetPage = isPrev
73
- ? Math.max(1, page - 1)
74
- : Math.min(pageCount, page + 1)
75
- const disabled = isPrev ? page <= 1 : page >= pageCount
76
-
77
- return (
78
- <IconButton
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
- )
95
- }
96
-
97
- const PageItem = ({ value }: { value: number | string }) => {
98
- // 省略記号
99
- if (value === '...') {
100
- return (
101
- <IconButton
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"
127
- >
128
- {value}
129
- </LinkComponent>
130
- )
131
- }
132
- // ボタンモード: クリックでページ遷移
133
- return (
134
- <button
135
- type="button"
136
- className="charcoal-pagination-button"
137
- onClick={makeClickHandler(value)}
138
- >
139
- {value}
140
- </button>
141
- )
160
+ const contextValue = {
161
+ page,
162
+ pageCount,
163
+ size,
164
+ isLinkMode,
165
+ makeUrl,
166
+ LinkComponent,
167
+ makeClickHandler,
142
168
  }
143
169
 
144
170
  return (
145
- <nav {...navProps} className={classNames} aria-label="Pagination">
146
- <NavButton direction="prev" />
147
- {window.map((p) => (
148
- <PageItem key={p} value={p} />
149
- ))}
150
- <NavButton direction="next" />
151
- </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>
152
185
  )
153
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'