Skip to content

Commit

Permalink
KDS-1878-migrate-pagination-1 (#4244)
Browse files Browse the repository at this point in the history
* add Pagination component

* fix style import

* Fix truncate style import

* Fix truncate styles

* Moved styles to correct location

* Moved styles to correct location

* update stories

* replace snowflake directional and pagination links in Pagination component

* Fix SCSS imports

* shuffle stories around

* add stickersheets

* replace unit tests with storybook

* remove circular dep

* removed Enum

* add changeset

* update docs for subcomponents
  • Loading branch information
gyfchong authored Oct 30, 2023
1 parent da82c6e commit bc94e27
Show file tree
Hide file tree
Showing 28 changed files with 494 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-cheetahs-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kaizen/components": minor
---

Migrate Pagination from `kaizen-legacy`
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@import "../utils/variables";
@import "../Button/Button.module";
@import "../IconButton/IconButton.module";
@import "../PaginationLink/PaginationLink.module";
@import "../DirectionalLink/DirectionalLink.module";
@import "../../Pagination/subcomponents/PaginationLink/PaginationLink.module";
@import "../../Pagination/subcomponents/DirectionalLink/DirectionalLink.module";

.container {
display: inline-block;
Expand Down
2 changes: 0 additions & 2 deletions packages/components/src/Button/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export type { CustomButtonProps } from "./GenericButton"
export * from "./Button"
export * from "./IconButton"
export * from "./PaginationLink"
export * from "./DirectionalLink"
65 changes: 65 additions & 0 deletions packages/components/src/Pagination/Pagination.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
@import "~@kaizen/design-tokens/sass/typography";
@import "~@kaizen/design-tokens/sass/color";
@import "~@kaizen/design-tokens/sass/border";

// Pagination
.container {
display: flex;
align-items: center;
justify-content: center;
}

.pagesIndicatorWrapper {
display: flex;
flex-direction: row;
justify-content: space-around;
}

.arrowIconWrapper {
height: 36px;
width: 36px;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
border: $border-focus-ring-border-width $border-focus-ring-border-style
$color-blue-300;
margin: 0 5px;
background-color: transparent;
color: $color-blue-500;
box-sizing: border-box;

&:disabled {
opacity: 35%;
pointer-events: none;
}

&:hover {
background-color: $color-blue-100;
}

&:focus {
background-color: $color-blue-200;

&:focus-visible {
outline: none;
}

.pageIndicatorFocusRing {
border: $border-focus-ring-border-width $border-focus-ring-border-style
$color-blue-500;
}
}
}

// Truncate indicator
.truncateIndicatorWrapper {
display: flex;
align-items: center;
justify-content: center;
height: 36px;
width: 36px;
background-color: transparent;
color: rgba($color-purple-800-rgb, 0.7);
margin: 0 5px;
}
30 changes: 30 additions & 0 deletions packages/components/src/Pagination/Pagination.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react"
import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { Pagination } from "./Pagination"

const user = userEvent.setup()

const defaultProps = {
currentPage: 1,
pageCount: 10,
ariaLabelNextPage: "Next page",
ariaLabelPreviousPage: "Previous page",
ariaLabelPage: "Page",
onPageChange: jest.fn<void, [number]>(),
}

describe("<Pagination />", () => {
it("calls onPageChange when clicking page number", async () => {
const onPageChange = jest.fn<void, [number]>()

render(<Pagination {...defaultProps} onPageChange={onPageChange} />)

expect(onPageChange).toHaveBeenCalledTimes(0)

await user.click(screen.getByRole("button", { name: "Page 1" }))
await waitFor(() => {
expect(onPageChange).toHaveBeenCalledTimes(1)
})
})
})
163 changes: 163 additions & 0 deletions packages/components/src/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import React, { HTMLAttributes } from "react"
import classnames from "classnames"
import { OverrideClassName } from "~types/OverrideClassName"
import { DirectionalLink } from "./subcomponents/DirectionalLink"
import { PaginationLink } from "./subcomponents/PaginationLink"
import { TruncateIndicator } from "./subcomponents/TruncateIndicator"
import { createRange } from "./utils/createRange"
import styles from "./Pagination.module.scss"

export type PaginationProps = {
currentPage: number
pageCount: number
ariaLabelNextPage: string
ariaLabelPreviousPage: string
ariaLabelPage: string
onPageChange: (newPage: number) => void
} & OverrideClassName<HTMLAttributes<HTMLElement>>

type PageAction = "prev" | "next"

/**
* {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3082092975/Pagination Guidance} |
* {@link https://cultureamp.design/?path=/docs/components-pagination--docs Storybook}
*/
export const Pagination = ({
currentPage = 1,
pageCount,
ariaLabelNextPage,
ariaLabelPreviousPage,
ariaLabelPage,
onPageChange,
classNameOverride,
...restProps
}: PaginationProps): JSX.Element => {
// Click event for all pagination buttons (next, prev, and the actual numbers)
const handleButtonClick = (newPage: number | PageAction): void => {
if (newPage === "prev") {
onPageChange(currentPage - 1)
return
}
if (newPage === "next") {
onPageChange(currentPage + 1)
return
}
onPageChange(newPage)
}

const paginationIndicator = (page: number): JSX.Element => (
<PaginationLink
key={page}
pageNumber={page}
isActive={currentPage === page}
aria-label={`${ariaLabelPage} ${page}`}
onClick={() => handleButtonClick(page)}
/>
)

const pagination = (): JSX.Element[] => {
const items: JSX.Element[] = []

const boundaryPagesRange = 1
const siblingPagesRange = 1

// truncateSize is 1 now but could be 0 if we add the ability to hide it.
const truncateSize = 1

const showAllPages =
1 + 2 * truncateSize + 2 * siblingPagesRange + 2 * boundaryPagesRange >=
pageCount

// Simplify generation of pages if number of available items is equal or greater than total pages to show
if (showAllPages) {
return createRange(1, pageCount).map(paginationIndicator)
} else {
// Calculate group of first pages
const firstPagesStart = 1
const firstPagesEnd = boundaryPagesRange
const firstPages = createRange(firstPagesStart, firstPagesEnd).map(
paginationIndicator
)

// Calculate group of last pages
const lastPagesStart = pageCount + 1 - boundaryPagesRange
const lastPagesEnd = pageCount
const lastPages = createRange(lastPagesStart, lastPagesEnd).map(
paginationIndicator
)

// Calculate group of main pages
const mainPagesStart = Math.min(
Math.max(
currentPage - siblingPagesRange,
firstPagesEnd + truncateSize + 1
),
lastPagesStart - truncateSize - 2 * siblingPagesRange - 1
)
const mainPagesEnd = mainPagesStart + 2 * siblingPagesRange
const mainPages = createRange(mainPagesStart, mainPagesEnd).map(
paginationIndicator
)

// Add group of first pages
items.push(...firstPages)

// Calculate and add truncate before group of main pages
const firstEllipsisPageNumber = mainPagesStart - 1
const showPageInsteadOfFirstEllipsis =
firstEllipsisPageNumber === firstPagesEnd + 1
items.push(
showPageInsteadOfFirstEllipsis ? (
paginationIndicator(firstEllipsisPageNumber)
) : (
<TruncateIndicator key={firstEllipsisPageNumber} />
)
)

// Add group of main pages
items.push(...mainPages)

// Calculate and add truncate after group of main pages
const secondEllipsisPageNumber = mainPagesEnd + 1
const showPageInsteadOfSecondEllipsis =
secondEllipsisPageNumber === lastPagesStart - 1
items.push(
showPageInsteadOfSecondEllipsis ? (
paginationIndicator(secondEllipsisPageNumber)
) : (
<TruncateIndicator key={secondEllipsisPageNumber} />
)
)

// Add group of last pages
items.push(...lastPages)
}
return items
}

const previousPageDisabled = currentPage <= 1
const nextPageDisabled = currentPage >= pageCount

return (
<nav
className={classnames(styles.container, classNameOverride)}
{...restProps}
>
<DirectionalLink
label={ariaLabelPreviousPage}
direction="prev"
disabled={previousPageDisabled}
onClick={(): void => handleButtonClick("prev")}
/>

<div className={styles.pagesIndicatorWrapper}>{pagination()}</div>

<DirectionalLink
label={ariaLabelNextPage}
direction="next"
disabled={nextPageDisabled}
onClick={(): void => handleButtonClick("next")}
/>
</nav>
)
}
28 changes: 28 additions & 0 deletions packages/components/src/Pagination/_docs/Pagination.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Canvas, Controls, Meta } from "@storybook/blocks"
import { ResourceLinks, KaioNotification, Installation } from "~storybook/components"
import * as PaginationStories from "./Pagination.stories"

<Meta of={PaginationStories} />

# Pagination

<ResourceLinks
sourceCode="https://github.com/cultureamp/kaizen-design-system/tree/main/packages/components/src/Pagination"
figma="https://www.figma.com/file/ZRfnoNUXbGZv4eVWLbF4Az/%EF%B8%8F%F0%9F%96%BC%EF%B8%8F-Component-Gallery?node-id=9%3A39913&t=P1w10jr2cpPuaayw-1"
designGuidelines="https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3082092975/Pagination"
className="!mb-8"
/>

<KaioNotification />

<Installation
installCommand="yarn add @kaizen/components"
importStatement='import { Pagination } from "@kaizen/components"'
/>

## Overview

Pagination separates large bodies of content into separate pages. You can access each page via a shared index of links.

<Canvas of={PaginationStories.Playground} />
<Controls of={PaginationStories.Playground} />
Loading

0 comments on commit bc94e27

Please sign in to comment.