@windstream/react-shared-components 0.2.2 → 0.2.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.
@@ -4,16 +4,11 @@ import { Pagination } from "./index";
4
4
  import { fireEvent, render, screen } from "@testing-library/react";
5
5
 
6
6
  // Mock dependencies
7
- jest.mock("@shared/components/button", () => ({
8
- Button: ({ children, onClick, disabled, className, ...rest }: any) => (
9
- <button
10
- onClick={onClick}
11
- disabled={disabled}
12
- className={className}
13
- {...rest}
14
- >
7
+ jest.mock("@shared/components/link", () => ({
8
+ Link: ({ children, href, onClick, className, ...rest }: any) => (
9
+ <a href={href} onClick={onClick} className={className} {...rest}>
15
10
  {children}
16
- </button>
11
+ </a>
17
12
  ),
18
13
  }));
19
14
 
@@ -27,6 +22,7 @@ jest.mock("@shared/components/material-icon", () => ({
27
22
 
28
23
  describe("Pagination", () => {
29
24
  const mockOnPageChange = jest.fn();
25
+ const mockGetPageHref = (page: number) => `/blog?page=${page}`;
30
26
 
31
27
  beforeEach(() => {
32
28
  mockOnPageChange.mockClear();
@@ -39,6 +35,7 @@ describe("Pagination", () => {
39
35
  currentPage={1}
40
36
  totalPages={5}
41
37
  onPageChange={mockOnPageChange}
38
+ getPageHref={mockGetPageHref}
42
39
  />
43
40
  );
44
41
  expect(screen.getByRole("navigation")).toHaveAttribute(
@@ -53,6 +50,7 @@ describe("Pagination", () => {
53
50
  currentPage={1}
54
51
  totalPages={5}
55
52
  onPageChange={mockOnPageChange}
53
+ getPageHref={mockGetPageHref}
56
54
  ariaLabel="Blog pages"
57
55
  />
58
56
  );
@@ -62,28 +60,30 @@ describe("Pagination", () => {
62
60
  );
63
61
  });
64
62
 
65
- it("renders previous and next navigation buttons", () => {
63
+ it("renders previous and next navigation links", () => {
66
64
  render(
67
65
  <Pagination
68
66
  currentPage={3}
69
67
  totalPages={5}
70
68
  onPageChange={mockOnPageChange}
69
+ getPageHref={mockGetPageHref}
71
70
  />
72
71
  );
73
72
  expect(
74
- screen.getByRole("button", { name: "Go to previous page" })
73
+ screen.getByRole("link", { name: "Go to previous page" })
75
74
  ).toBeInTheDocument();
76
75
  expect(
77
- screen.getByRole("button", { name: "Go to next page" })
76
+ screen.getByRole("link", { name: "Go to next page" })
78
77
  ).toBeInTheDocument();
79
78
  });
80
79
 
81
- it("renders chevron icons in nav buttons", () => {
80
+ it("renders chevron icons in nav links", () => {
82
81
  render(
83
82
  <Pagination
84
83
  currentPage={1}
85
84
  totalPages={5}
86
85
  onPageChange={mockOnPageChange}
86
+ getPageHref={mockGetPageHref}
87
87
  />
88
88
  );
89
89
  expect(screen.getByTestId("icon-chevron_left")).toBeInTheDocument();
@@ -91,57 +91,59 @@ describe("Pagination", () => {
91
91
  });
92
92
  });
93
93
 
94
- describe("Button states", () => {
95
- it("disables previous button on first page", () => {
94
+ describe("Link states", () => {
95
+ it("disables previous link on first page", () => {
96
96
  render(
97
97
  <Pagination
98
98
  currentPage={1}
99
99
  totalPages={5}
100
100
  onPageChange={mockOnPageChange}
101
+ getPageHref={mockGetPageHref}
101
102
  />
102
103
  );
103
- expect(
104
- screen.getByRole("button", { name: "Go to previous page" })
105
- ).toBeDisabled();
104
+ const prevLink = screen.getByRole("link", { name: "Go to previous page" });
105
+ expect(prevLink).toHaveAttribute("aria-disabled", "true");
106
+ expect(prevLink).toHaveAttribute("tabindex", "-1");
106
107
  });
107
108
 
108
- it("enables previous button when not on first page", () => {
109
+ it("enables previous link when not on first page", () => {
109
110
  render(
110
111
  <Pagination
111
112
  currentPage={3}
112
113
  totalPages={5}
113
114
  onPageChange={mockOnPageChange}
115
+ getPageHref={mockGetPageHref}
114
116
  />
115
117
  );
116
- expect(
117
- screen.getByRole("button", { name: "Go to previous page" })
118
- ).not.toBeDisabled();
118
+ const prevLink = screen.getByRole("link", { name: "Go to previous page" });
119
+ expect(prevLink).not.toHaveAttribute("aria-disabled", "true");
119
120
  });
120
121
 
121
- it("disables next button on last page", () => {
122
+ it("disables next link on last page", () => {
122
123
  render(
123
124
  <Pagination
124
125
  currentPage={5}
125
126
  totalPages={5}
126
127
  onPageChange={mockOnPageChange}
128
+ getPageHref={mockGetPageHref}
127
129
  />
128
130
  );
129
- expect(
130
- screen.getByRole("button", { name: "Go to next page" })
131
- ).toBeDisabled();
131
+ const nextLink = screen.getByRole("link", { name: "Go to next page" });
132
+ expect(nextLink).toHaveAttribute("aria-disabled", "true");
133
+ expect(nextLink).toHaveAttribute("tabindex", "-1");
132
134
  });
133
135
 
134
- it("enables next button when not on last page", () => {
136
+ it("enables next link when not on last page", () => {
135
137
  render(
136
138
  <Pagination
137
139
  currentPage={3}
138
140
  totalPages={5}
139
141
  onPageChange={mockOnPageChange}
142
+ getPageHref={mockGetPageHref}
140
143
  />
141
144
  );
142
- expect(
143
- screen.getByRole("button", { name: "Go to next page" })
144
- ).not.toBeDisabled();
145
+ const nextLink = screen.getByRole("link", { name: "Go to next page" });
146
+ expect(nextLink).not.toHaveAttribute("aria-disabled", "true");
145
147
  });
146
148
 
147
149
  it("marks current page with aria-current='page'", () => {
@@ -150,10 +152,11 @@ describe("Pagination", () => {
150
152
  currentPage={3}
151
153
  totalPages={5}
152
154
  onPageChange={mockOnPageChange}
155
+ getPageHref={mockGetPageHref}
153
156
  />
154
157
  );
155
158
  expect(
156
- screen.getByRole("button", { name: "Go to page 3" })
159
+ screen.getByRole("link", { name: "Go to page 3" })
157
160
  ).toHaveAttribute("aria-current", "page");
158
161
  });
159
162
 
@@ -163,65 +166,84 @@ describe("Pagination", () => {
163
166
  currentPage={3}
164
167
  totalPages={5}
165
168
  onPageChange={mockOnPageChange}
169
+ getPageHref={mockGetPageHref}
166
170
  />
167
171
  );
168
172
  expect(
169
- screen.getByRole("button", { name: "Go to page 1" })
173
+ screen.getByRole("link", { name: "Go to page 1" })
170
174
  ).not.toHaveAttribute("aria-current");
171
175
  });
172
176
 
173
- it("applies active class to current page button", () => {
177
+ it("applies active class to current page link", () => {
174
178
  render(
175
179
  <Pagination
176
180
  currentPage={2}
177
181
  totalPages={5}
178
182
  onPageChange={mockOnPageChange}
183
+ getPageHref={mockGetPageHref}
179
184
  />
180
185
  );
181
- const activeBtn = screen.getByRole("button", { name: "Go to page 2" });
182
- expect(activeBtn.className).toContain("bg-bg-surface-active");
183
- expect(activeBtn.className).toContain("font-bold");
186
+ const activeLink = screen.getByRole("link", { name: "Go to page 2" });
187
+ expect(activeLink.className).toContain("bg-bg-surface-active");
188
+ expect(activeLink.className).toContain("font-bold");
184
189
  });
185
190
 
186
- it("does not apply active class to non-current page buttons", () => {
191
+ it("does not apply active class to non-current page links", () => {
187
192
  render(
188
193
  <Pagination
189
194
  currentPage={2}
190
195
  totalPages={5}
191
196
  onPageChange={mockOnPageChange}
197
+ getPageHref={mockGetPageHref}
192
198
  />
193
199
  );
194
- const inactiveBtn = screen.getByRole("button", {
200
+ const inactiveLink = screen.getByRole("link", {
195
201
  name: "Go to page 1",
196
202
  });
197
- expect(inactiveBtn.className).not.toContain("bg-bg-surface-active");
203
+ expect(inactiveLink.className).not.toContain("bg-bg-surface-active");
204
+ });
205
+
206
+ it("renders correct href for page links", () => {
207
+ render(
208
+ <Pagination
209
+ currentPage={1}
210
+ totalPages={5}
211
+ onPageChange={mockOnPageChange}
212
+ getPageHref={mockGetPageHref}
213
+ />
214
+ );
215
+ expect(
216
+ screen.getByRole("link", { name: "Go to page 3" })
217
+ ).toHaveAttribute("href", "/blog?page=3");
198
218
  });
199
219
  });
200
220
 
201
221
  describe("Page navigation clicks", () => {
202
- it("calls onPageChange with previous page when previous button clicked", () => {
222
+ it("calls onPageChange with previous page when previous link clicked", () => {
203
223
  render(
204
224
  <Pagination
205
225
  currentPage={3}
206
226
  totalPages={5}
207
227
  onPageChange={mockOnPageChange}
228
+ getPageHref={mockGetPageHref}
208
229
  />
209
230
  );
210
231
  fireEvent.click(
211
- screen.getByRole("button", { name: "Go to previous page" })
232
+ screen.getByRole("link", { name: "Go to previous page" })
212
233
  );
213
234
  expect(mockOnPageChange).toHaveBeenCalledWith(2);
214
235
  });
215
236
 
216
- it("calls onPageChange with next page when next button clicked", () => {
237
+ it("calls onPageChange with next page when next link clicked", () => {
217
238
  render(
218
239
  <Pagination
219
240
  currentPage={3}
220
241
  totalPages={5}
221
242
  onPageChange={mockOnPageChange}
243
+ getPageHref={mockGetPageHref}
222
244
  />
223
245
  );
224
- fireEvent.click(screen.getByRole("button", { name: "Go to next page" }));
246
+ fireEvent.click(screen.getByRole("link", { name: "Go to next page" }));
225
247
  expect(mockOnPageChange).toHaveBeenCalledWith(4);
226
248
  });
227
249
 
@@ -231,35 +253,38 @@ describe("Pagination", () => {
231
253
  currentPage={1}
232
254
  totalPages={5}
233
255
  onPageChange={mockOnPageChange}
256
+ getPageHref={mockGetPageHref}
234
257
  />
235
258
  );
236
- fireEvent.click(screen.getByRole("button", { name: "Go to page 4" }));
259
+ fireEvent.click(screen.getByRole("link", { name: "Go to page 4" }));
237
260
  expect(mockOnPageChange).toHaveBeenCalledWith(4);
238
261
  });
239
262
 
240
- it("does not call onPageChange when disabled previous button is clicked", () => {
263
+ it("does not call onPageChange when disabled previous link is clicked", () => {
241
264
  render(
242
265
  <Pagination
243
266
  currentPage={1}
244
267
  totalPages={5}
245
268
  onPageChange={mockOnPageChange}
269
+ getPageHref={mockGetPageHref}
246
270
  />
247
271
  );
248
272
  fireEvent.click(
249
- screen.getByRole("button", { name: "Go to previous page" })
273
+ screen.getByRole("link", { name: "Go to previous page" })
250
274
  );
251
275
  expect(mockOnPageChange).not.toHaveBeenCalled();
252
276
  });
253
277
 
254
- it("does not call onPageChange when disabled next button is clicked", () => {
278
+ it("does not call onPageChange when disabled next link is clicked", () => {
255
279
  render(
256
280
  <Pagination
257
281
  currentPage={5}
258
282
  totalPages={5}
259
283
  onPageChange={mockOnPageChange}
284
+ getPageHref={mockGetPageHref}
260
285
  />
261
286
  );
262
- fireEvent.click(screen.getByRole("button", { name: "Go to next page" }));
287
+ fireEvent.click(screen.getByRole("link", { name: "Go to next page" }));
263
288
  expect(mockOnPageChange).not.toHaveBeenCalled();
264
289
  });
265
290
  });
@@ -271,10 +296,11 @@ describe("Pagination", () => {
271
296
  currentPage={1}
272
297
  totalPages={1}
273
298
  onPageChange={mockOnPageChange}
299
+ getPageHref={mockGetPageHref}
274
300
  />
275
301
  );
276
302
  expect(
277
- screen.getByRole("button", { name: "Go to page 1" })
303
+ screen.getByRole("link", { name: "Go to page 1" })
278
304
  ).toBeInTheDocument();
279
305
  expect(screen.queryByText("…")).not.toBeInTheDocument();
280
306
  });
@@ -285,11 +311,12 @@ describe("Pagination", () => {
285
311
  currentPage={1}
286
312
  totalPages={5}
287
313
  onPageChange={mockOnPageChange}
314
+ getPageHref={mockGetPageHref}
288
315
  />
289
316
  );
290
317
  for (let i = 1; i <= 5; i++) {
291
318
  expect(
292
- screen.getByRole("button", { name: `Go to page ${i}` })
319
+ screen.getByRole("link", { name: `Go to page ${i}` })
293
320
  ).toBeInTheDocument();
294
321
  }
295
322
  expect(screen.queryByText("…")).not.toBeInTheDocument();
@@ -301,11 +328,12 @@ describe("Pagination", () => {
301
328
  currentPage={4}
302
329
  totalPages={7}
303
330
  onPageChange={mockOnPageChange}
331
+ getPageHref={mockGetPageHref}
304
332
  />
305
333
  );
306
334
  for (let i = 1; i <= 7; i++) {
307
335
  expect(
308
- screen.getByRole("button", { name: `Go to page ${i}` })
336
+ screen.getByRole("link", { name: `Go to page ${i}` })
309
337
  ).toBeInTheDocument();
310
338
  }
311
339
  expect(screen.queryByText("…")).not.toBeInTheDocument();
@@ -319,19 +347,20 @@ describe("Pagination", () => {
319
347
  currentPage={1}
320
348
  totalPages={10}
321
349
  onPageChange={mockOnPageChange}
350
+ getPageHref={mockGetPageHref}
322
351
  />
323
352
  );
324
353
  for (let i = 1; i <= 5; i++) {
325
354
  expect(
326
- screen.getByRole("button", { name: `Go to page ${i}` })
355
+ screen.getByRole("link", { name: `Go to page ${i}` })
327
356
  ).toBeInTheDocument();
328
357
  }
329
358
  expect(screen.getByText("…")).toBeInTheDocument();
330
359
  expect(
331
- screen.getByRole("button", { name: "Go to page 10" })
360
+ screen.getByRole("link", { name: "Go to page 10" })
332
361
  ).toBeInTheDocument();
333
362
  expect(
334
- screen.queryByRole("button", { name: "Go to page 6" })
363
+ screen.queryByRole("link", { name: "Go to page 6" })
335
364
  ).not.toBeInTheDocument();
336
365
  });
337
366
 
@@ -341,16 +370,17 @@ describe("Pagination", () => {
341
370
  currentPage={4}
342
371
  totalPages={10}
343
372
  onPageChange={mockOnPageChange}
373
+ getPageHref={mockGetPageHref}
344
374
  />
345
375
  );
346
376
  for (let i = 1; i <= 5; i++) {
347
377
  expect(
348
- screen.getByRole("button", { name: `Go to page ${i}` })
378
+ screen.getByRole("link", { name: `Go to page ${i}` })
349
379
  ).toBeInTheDocument();
350
380
  }
351
381
  expect(screen.getByText("…")).toBeInTheDocument();
352
382
  expect(
353
- screen.getByRole("button", { name: "Go to page 10" })
383
+ screen.getByRole("link", { name: "Go to page 10" })
354
384
  ).toBeInTheDocument();
355
385
  });
356
386
  });
@@ -362,15 +392,16 @@ describe("Pagination", () => {
362
392
  currentPage={10}
363
393
  totalPages={10}
364
394
  onPageChange={mockOnPageChange}
395
+ getPageHref={mockGetPageHref}
365
396
  />
366
397
  );
367
398
  expect(
368
- screen.getByRole("button", { name: "Go to page 1" })
399
+ screen.getByRole("link", { name: "Go to page 1" })
369
400
  ).toBeInTheDocument();
370
401
  expect(screen.getByText("…")).toBeInTheDocument();
371
402
  for (let i = 6; i <= 10; i++) {
372
403
  expect(
373
- screen.getByRole("button", { name: `Go to page ${i}` })
404
+ screen.getByRole("link", { name: `Go to page ${i}` })
374
405
  ).toBeInTheDocument();
375
406
  }
376
407
  });
@@ -381,15 +412,16 @@ describe("Pagination", () => {
381
412
  currentPage={7}
382
413
  totalPages={10}
383
414
  onPageChange={mockOnPageChange}
415
+ getPageHref={mockGetPageHref}
384
416
  />
385
417
  );
386
418
  expect(
387
- screen.getByRole("button", { name: "Go to page 1" })
419
+ screen.getByRole("link", { name: "Go to page 1" })
388
420
  ).toBeInTheDocument();
389
421
  expect(screen.getByText("…")).toBeInTheDocument();
390
422
  for (let i = 6; i <= 10; i++) {
391
423
  expect(
392
- screen.getByRole("button", { name: `Go to page ${i}` })
424
+ screen.getByRole("link", { name: `Go to page ${i}` })
393
425
  ).toBeInTheDocument();
394
426
  }
395
427
  });
@@ -402,22 +434,23 @@ describe("Pagination", () => {
402
434
  currentPage={5}
403
435
  totalPages={10}
404
436
  onPageChange={mockOnPageChange}
437
+ getPageHref={mockGetPageHref}
405
438
  />
406
439
  );
407
440
  expect(
408
- screen.getByRole("button", { name: "Go to page 1" })
441
+ screen.getByRole("link", { name: "Go to page 1" })
409
442
  ).toBeInTheDocument();
410
443
  expect(
411
- screen.getByRole("button", { name: "Go to page 4" })
444
+ screen.getByRole("link", { name: "Go to page 4" })
412
445
  ).toBeInTheDocument();
413
446
  expect(
414
- screen.getByRole("button", { name: "Go to page 5" })
447
+ screen.getByRole("link", { name: "Go to page 5" })
415
448
  ).toBeInTheDocument();
416
449
  expect(
417
- screen.getByRole("button", { name: "Go to page 6" })
450
+ screen.getByRole("link", { name: "Go to page 6" })
418
451
  ).toBeInTheDocument();
419
452
  expect(
420
- screen.getByRole("button", { name: "Go to page 10" })
453
+ screen.getByRole("link", { name: "Go to page 10" })
421
454
  ).toBeInTheDocument();
422
455
  const ellipses = screen.getAllByText("…");
423
456
  expect(ellipses).toHaveLength(2);
@@ -429,6 +462,7 @@ describe("Pagination", () => {
429
462
  currentPage={5}
430
463
  totalPages={10}
431
464
  onPageChange={mockOnPageChange}
465
+ getPageHref={mockGetPageHref}
432
466
  />
433
467
  );
434
468
  const ellipses = screen.getAllByText("…");
@@ -444,42 +478,44 @@ describe("Pagination", () => {
444
478
  currentPage={6}
445
479
  totalPages={10}
446
480
  onPageChange={mockOnPageChange}
481
+ getPageHref={mockGetPageHref}
447
482
  />
448
483
  );
449
484
  expect(
450
- screen.getByRole("button", { name: "Go to page 1" })
485
+ screen.getByRole("link", { name: "Go to page 1" })
451
486
  ).toBeInTheDocument();
452
487
  expect(
453
- screen.getByRole("button", { name: "Go to page 5" })
488
+ screen.getByRole("link", { name: "Go to page 5" })
454
489
  ).toBeInTheDocument();
455
490
  expect(
456
- screen.getByRole("button", { name: "Go to page 6" })
491
+ screen.getByRole("link", { name: "Go to page 6" })
457
492
  ).toBeInTheDocument();
458
493
  expect(
459
- screen.getByRole("button", { name: "Go to page 7" })
494
+ screen.getByRole("link", { name: "Go to page 7" })
460
495
  ).toBeInTheDocument();
461
496
  expect(
462
- screen.getByRole("button", { name: "Go to page 10" })
497
+ screen.getByRole("link", { name: "Go to page 10" })
463
498
  ).toBeInTheDocument();
464
499
  expect(screen.getAllByText("…")).toHaveLength(2);
465
500
  });
466
501
  });
467
502
 
468
503
  describe("Edge cases", () => {
469
- it("handles single page (both nav buttons disabled)", () => {
504
+ it("handles single page (both nav links disabled)", () => {
470
505
  render(
471
506
  <Pagination
472
507
  currentPage={1}
473
508
  totalPages={1}
474
509
  onPageChange={mockOnPageChange}
510
+ getPageHref={mockGetPageHref}
475
511
  />
476
512
  );
477
513
  expect(
478
- screen.getByRole("button", { name: "Go to previous page" })
479
- ).toBeDisabled();
514
+ screen.getByRole("link", { name: "Go to previous page" })
515
+ ).toHaveAttribute("aria-disabled", "true");
480
516
  expect(
481
- screen.getByRole("button", { name: "Go to next page" })
482
- ).toBeDisabled();
517
+ screen.getByRole("link", { name: "Go to next page" })
518
+ ).toHaveAttribute("aria-disabled", "true");
483
519
  });
484
520
 
485
521
  it("handles totalPages=8 with current=4 (near-start boundary)", () => {
@@ -488,15 +524,16 @@ describe("Pagination", () => {
488
524
  currentPage={4}
489
525
  totalPages={8}
490
526
  onPageChange={mockOnPageChange}
527
+ getPageHref={mockGetPageHref}
491
528
  />
492
529
  );
493
530
  for (let i = 1; i <= 5; i++) {
494
531
  expect(
495
- screen.getByRole("button", { name: `Go to page ${i}` })
532
+ screen.getByRole("link", { name: `Go to page ${i}` })
496
533
  ).toBeInTheDocument();
497
534
  }
498
535
  expect(
499
- screen.getByRole("button", { name: "Go to page 8" })
536
+ screen.getByRole("link", { name: "Go to page 8" })
500
537
  ).toBeInTheDocument();
501
538
  });
502
539
 
@@ -506,14 +543,15 @@ describe("Pagination", () => {
506
543
  currentPage={5}
507
544
  totalPages={8}
508
545
  onPageChange={mockOnPageChange}
546
+ getPageHref={mockGetPageHref}
509
547
  />
510
548
  );
511
549
  expect(
512
- screen.getByRole("button", { name: "Go to page 1" })
550
+ screen.getByRole("link", { name: "Go to page 1" })
513
551
  ).toBeInTheDocument();
514
552
  for (let i = 4; i <= 8; i++) {
515
553
  expect(
516
- screen.getByRole("button", { name: `Go to page ${i}` })
554
+ screen.getByRole("link", { name: `Go to page ${i}` })
517
555
  ).toBeInTheDocument();
518
556
  }
519
557
  });
@@ -3,24 +3,29 @@
3
3
  import React from "react";
4
4
  import { PaginationProps } from "./types";
5
5
 
6
- import { Button } from "@shared/components/button";
6
+ import { Link } from "@shared/components/link";
7
7
  import { MaterialIcon } from "@shared/components/material-icon";
8
8
 
9
+ const defaultGetPageHref = (page: number) => `?page=${page}`;
10
+
9
11
  export const Pagination: React.FC<PaginationProps> = ({
10
12
  currentPage,
11
13
  totalPages,
12
14
  onPageChange,
15
+ getPageHref = defaultGetPageHref,
13
16
  ariaLabel = "Pagination",
14
17
  }: PaginationProps) => {
15
18
  const pageItems = buildPageItems(currentPage, totalPages);
16
19
 
17
- const navBtnBase =
18
- "inline-flex items-center justify-center w-9 h-9 rounded-full bg-transparent cursor-pointer transition-colors duration-150 hover:border-text-brand hover:text-text-brand disabled:opacity-40 disabled:cursor-not-allowed";
20
+ const navLinkBase =
21
+ "inline-flex items-center justify-center w-9 h-9 rounded-full bg-transparent cursor-pointer transition-colors duration-150 hover:border-text-brand hover:text-text-brand";
22
+
23
+ const navLinkDisabled = "opacity-40 pointer-events-none";
19
24
 
20
- const pageBtnBase =
25
+ const pageLinkBase =
21
26
  "inline-flex items-center justify-center w-9 h-9 rounded-lg text-body2 text-gray-700 bg-transparent border-none cursor-pointer transition-colors duration-150 hover:bg-gray-100 hover:text-gray-900";
22
27
 
23
- const pageBtnActive = "bg-bg-surface-active font-bold hover:bg-bg-gray-300";
28
+ const pageLinkActive = "bg-bg-surface-active font-bold hover:bg-bg-gray-300";
24
29
 
25
30
  return (
26
31
  <nav
@@ -28,14 +33,22 @@ export const Pagination: React.FC<PaginationProps> = ({
28
33
  aria-label={ariaLabel}
29
34
  >
30
35
  {/* Previous */}
31
- <Button
32
- className={navBtnBase}
33
- onClick={() => onPageChange(currentPage - 1)}
34
- disabled={currentPage === 1}
36
+ <Link
37
+ href={currentPage === 1 ? "#" : getPageHref(currentPage - 1)}
38
+ className={`${navLinkBase} ${currentPage === 1 ? navLinkDisabled : ""}`}
35
39
  aria-label="Go to previous page"
40
+ aria-disabled={currentPage === 1}
41
+ tabIndex={currentPage === 1 ? -1 : undefined}
42
+ onClick={e => {
43
+ if (currentPage === 1) {
44
+ e.preventDefault();
45
+ return;
46
+ }
47
+ onPageChange?.(currentPage - 1);
48
+ }}
36
49
  >
37
50
  <MaterialIcon name="chevron_left" size={32} aria-hidden="true" />
38
- </Button>
51
+ </Link>
39
52
 
40
53
  {/* Page numbers */}
41
54
  {pageItems.map((item, idx) =>
@@ -48,27 +61,36 @@ export const Pagination: React.FC<PaginationProps> = ({
48
61
 
49
62
  </span>
50
63
  ) : (
51
- <Button
64
+ <Link
52
65
  key={item}
53
- className={`${pageBtnBase} ${item === currentPage ? pageBtnActive : ""}`}
54
- onClick={() => onPageChange(item)}
66
+ href={getPageHref(item)}
67
+ className={`${pageLinkBase} ${item === currentPage ? pageLinkActive : ""}`}
55
68
  aria-label={`Go to page ${item}`}
56
69
  aria-current={item === currentPage ? "page" : undefined}
70
+ onClick={() => onPageChange?.(item)}
57
71
  >
58
72
  {item}
59
- </Button>
73
+ </Link>
60
74
  )
61
75
  )}
62
76
 
63
77
  {/* Next */}
64
- <Button
65
- className={navBtnBase}
66
- onClick={() => onPageChange(currentPage + 1)}
67
- disabled={currentPage === totalPages}
78
+ <Link
79
+ href={currentPage === totalPages ? "#" : getPageHref(currentPage + 1)}
80
+ className={`${navLinkBase} ${currentPage === totalPages ? navLinkDisabled : ""}`}
68
81
  aria-label="Go to next page"
82
+ aria-disabled={currentPage === totalPages}
83
+ tabIndex={currentPage === totalPages ? -1 : undefined}
84
+ onClick={e => {
85
+ if (currentPage === totalPages) {
86
+ e.preventDefault();
87
+ return;
88
+ }
89
+ onPageChange?.(currentPage + 1);
90
+ }}
69
91
  >
70
92
  <MaterialIcon name="chevron_right" size={32} aria-hidden="true" />
71
- </Button>
93
+ </Link>
72
94
  </nav>
73
95
  );
74
96
  };
@@ -1,6 +1,7 @@
1
1
  export interface PaginationProps {
2
2
  currentPage: number;
3
3
  totalPages: number;
4
- onPageChange: (page: number) => void;
4
+ onPageChange?: (page: number) => void;
5
+ getPageHref?: (page: number) => string;
5
6
  ariaLabel?: string;
6
7
  }