-
Notifications
You must be signed in to change notification settings - Fork 658
Pagination component #739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Pagination component #739
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
2436836
Model WIP
BinaryMuse b260b42
Tweak Pagination model
BinaryMuse 93450ac
Add WIP Pagination component
BinaryMuse d24e744
Finish up base functionalty and styling
BinaryMuse cd141f7
Ensure breaks when margin is 0
BinaryMuse c800b51
Fix when last page isn't final page
BinaryMuse 152ae81
:keyboard:
BinaryMuse 361a999
Finish primary Pagination docs
BinaryMuse bb0bf78
Remove test for unwanted behavior
BinaryMuse d05bee6
Refactor theme
BinaryMuse 42c31eb
Add props and theme info for Pagination
BinaryMuse c0dc5fb
Fill out types and props
BinaryMuse e712849
Update tests and lint
BinaryMuse 94a6d31
Update snapshot from theme changes
BinaryMuse 33eb7d3
Remove Set usage
BinaryMuse 7eb1592
Add TS TypeDoc comments for some props
BinaryMuse 2b32cf6
No Array.prototype.entries in IE
BinaryMuse d3ded89
Base Pagination font size on theme
BinaryMuse 5e0fa02
wordsing
BinaryMuse eadc341
Update snapshots from theme change
BinaryMuse 1c73597
Merge remote-tracking branch 'origin/major' into mkt/pagination-compo…
BinaryMuse f58e2d7
Fix tests and snapshots
BinaryMuse bb9c5ed
Remove unused variable
BinaryMuse ba41cd4
Merge remote-tracking branch 'origin/major' into mkt/pagination-compo…
BinaryMuse File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| --- | ||
| title: Pagination | ||
| --- | ||
| import State from '../components/State' | ||
|
|
||
| Use the pagination component to create a connected set of links that lead to related pages (for example, previous, next, or page numbers). | ||
|
|
||
| ## Basic example | ||
|
|
||
| The pagination component only requires two properties to render: `pageCount`, which is the total number of pages, and `currentPage`, which is the currently selected page number (which should be managed by the consuming application). | ||
|
|
||
| ```jsx live | ||
| <Pagination | ||
| pageCount={15} | ||
| currentPage={2} | ||
| onPageChange={e => e.preventDefault()} | ||
| /> | ||
| ``` | ||
|
|
||
| However, to handle state changes when the user clicks a page, you also need to pass `onPageChange`, which is a function that takes a click event and page number as an argument: | ||
|
|
||
| ```javascript | ||
| type PageChangeCallback = (evt: React.MouseEvent, page: number) => void | ||
| ``` | ||
|
|
||
| By default, clicking a link in the pagination component will cause the browser to navigate to the URL specified by the page. To cancel navigation and handle state management on your own, you should call `preventDefault` on the event, as in this example: | ||
|
|
||
| ```jsx live | ||
| <State default={1}> | ||
| {([page, setPage]) => { | ||
| const totalPages = 15 | ||
| const onPageChange = (evt, page) => { | ||
| evt.preventDefault() | ||
| setPage(page) | ||
| } | ||
|
|
||
| return ( | ||
| <BorderBox p={2}> | ||
| <Box>Current page: {page} / {totalPages}</Box> | ||
| <Pagination | ||
| pageCount={totalPages} | ||
| currentPage={page} | ||
| onPageChange={onPageChange} | ||
| /> | ||
| </BorderBox> | ||
| ) | ||
| }} | ||
| </State> | ||
| ``` | ||
|
|
||
| ## Customizing link URLs | ||
|
|
||
| To customize the URL generated for each link, you can pass a function to the `hrefBuilder` property. The function should take a page number as an argument and return a URL to use for the link. | ||
|
|
||
| ```javascript | ||
| type HrefBuilder = (page: number) => string | ||
| ``` | ||
|
|
||
| ```jsx live | ||
| <State default={'(nothing clicked yet)'}> | ||
| {([lastUrl, setLastUrl]) => { | ||
| const onPageChange = (evt, page) => { | ||
| evt.preventDefault() | ||
| setLastUrl(evt.target.href) | ||
| } | ||
| const hrefBuilder = (page) => { | ||
| return `https://example.com/pages/${page}` | ||
| } | ||
|
|
||
| return ( | ||
| <BorderBox p={2}> | ||
| <Box>The last URL clicked was: {lastUrl}</Box> | ||
| <Pagination | ||
| pageCount={15} | ||
| currentPage={2} | ||
| onPageChange={onPageChange} | ||
| hrefBuilder={hrefBuilder} | ||
| /> | ||
| </BorderBox> | ||
| ) | ||
| }} | ||
| </State> | ||
| ``` | ||
|
|
||
| ## Customizing which pages are shown | ||
|
|
||
| Two props control how many links are displayed in the pagination container at any given time. `marginPageCount` controls how many pages are guaranteed to be displayed on the left and right of the component; `surroundingPageCount` controls how many pages will be displayed to the left and right of the current page. | ||
|
|
||
| ```jsx live | ||
| <Pagination | ||
| pageCount={20} | ||
| currentPage={10} | ||
| marginPageCount={1} | ||
| surroundingPageCount={2} | ||
| onPageChange={e => e.preventDefault()} | ||
| /> | ||
| ``` | ||
|
|
||
| The algorithm tries to minimize the amount the component shrinks and grows as the user changes pages; for this reason, if any of the pages in the margin (controlled via `marginPageCount`) intersect with pages in the center (controlled by `surroundingPageCount`), the center section will be shifted away from the margin. Consider the following examples, where pages one through six are shown when any of the first four pages are selected. Only when the fifth page is selected and there is a gap between the margin pages and the center pages does a break element appear. | ||
|
|
||
| ```jsx live | ||
| <Box> | ||
| {[1, 2, 3, 4, 5].map(page => ( | ||
| <Pagination | ||
| pageCount={20} | ||
| currentPage={page} | ||
| marginPageCount={1} | ||
| surroundingPageCount={2} | ||
| onPageChange={e => e.preventDefault()} | ||
| /> | ||
| ))} | ||
| </Box> | ||
| ``` | ||
|
|
||
| ### Previous/next pagination | ||
|
|
||
| To hide all the page numbers and create a simple pagination container with just the Previous and Next buttons, set `showPages` to `false`. | ||
|
|
||
| ```jsx live | ||
| <State default={1}> | ||
| {([page, setPage]) => { | ||
| const totalPages = 10 | ||
| const onPageChange = (evt, page) => { | ||
| evt.preventDefault() | ||
| setPage(page) | ||
| } | ||
|
|
||
| return ( | ||
| <BorderBox p={2}> | ||
| <Box>Current page: {page} / {totalPages}</Box> | ||
| <Pagination | ||
| pageCount={totalPages} | ||
| currentPage={page} | ||
| onPageChange={onPageChange} | ||
| showPages={false} | ||
| /> | ||
| </BorderBox> | ||
| ) | ||
| }} | ||
| </State> | ||
| ``` | ||
|
|
||
| ## System props | ||
|
|
||
| Pagination components get `COMMON` system props. Read our [System Props](/system-props) doc page for a full list of available props. | ||
|
|
||
| ## Component props | ||
|
|
||
| | Name | Type | Default | Description | | ||
| | :- | :- | :-: | :- | | ||
| | currentPage | Number | | **Required.** The currently selected page. | | ||
| | hrefBuilder | Function | `#${page}` | A function to generate links based on page number. | | ||
| | marginPageCount | Number | 1 | How many pages to always show at the left and right of the component. | | ||
| | onPageChange | Function | no-op | Called with event and page number when a page is clicked. | | ||
| | pageCount | Number | | **Required.** The total number of pages. | | ||
| | showPages | Boolean | `true` | Whether or not to show the individual page links. | | ||
| | surroundingPageCount | Number | 2 | How many pages to display on each side of the currently selected page. | | ||
|
|
||
| ## Theming | ||
|
|
||
| The following snippet shows the properties in the theme that control the styling of the pagination component: | ||
|
|
||
| ```javascript | ||
| { | ||
| // ... rest of theme ... | ||
| pagination: { | ||
| fontSize, | ||
| fontWeight, | ||
| borderRadius, | ||
| colors: { | ||
| normal: { | ||
| fg, | ||
| bg, | ||
| border | ||
| }, | ||
| disabled: { | ||
| fg, | ||
| bg, | ||
| border | ||
| }, | ||
| hover: { | ||
| fg, | ||
| bg, | ||
| border | ||
| }, | ||
| selected: { | ||
| fg, | ||
| bg, | ||
| border | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,9 +72,7 @@ declare module '@primer/components' { | |
| extends BaseProps, | ||
| CommonProps, | ||
| TypographyProps, | ||
| Omit<React.HTMLAttributes<HTMLHeadingElement>, 'color'> { | ||
| as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | ||
| } | ||
| Omit<React.HTMLAttributes<HTMLHeadingElement>, 'color'> {} | ||
|
|
||
| export const Heading: React.FunctionComponent<HeadingProps> | ||
|
|
||
|
|
@@ -219,6 +217,28 @@ declare module '@primer/components' { | |
|
|
||
| export const Link: React.FunctionComponent<LinkProps> | ||
|
|
||
| export type PaginationHrefBuilder = (page: number) => string | ||
|
|
||
| export type PaginationPageChangeCallback = (e: React.MouseEvent, page: number) => void | ||
|
|
||
| export interface PaginationProps extends CommonProps { | ||
| currentPage: number | ||
| hrefBuilder?: PaginationHrefBuilder | ||
| /** | ||
| * How many pages to show on the left and right of the component | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do these comments show up in VSCode when someone is writing TS? That's pretty sweet
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah they power TS intellisense IIRC |
||
| */ | ||
| marginPageCount?: number | ||
| onPageChange?: PaginationPageChangeCallback | ||
| pageCount: number | ||
| showPages?: boolean | ||
| /** | ||
| * How many pages to show directly to the left and right of the current page | ||
| */ | ||
| surroundingPageCount?: number | ||
BinaryMuse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| export const Pagination: React.FunctionComponent<PaginationProps> | ||
|
|
||
| export interface PointerBoxProps extends CommonProps, LayoutProps, BorderBoxProps { | ||
| caret?: string | ||
| } | ||
|
|
@@ -391,15 +411,12 @@ declare module '@primer/components' { | |
| export const themeGet: (key: any) => any | ||
|
|
||
| export interface DialogProps extends CommonProps { | ||
| title: string | ||
| isOpen: boolean | ||
| onDismiss: () => unknown | ||
| } | ||
|
|
||
| export interface DialogHeaderProps extends FlexProps {} | ||
|
|
||
| export const Dialog: React.FunctionComponent<DialogProps> & { | ||
| Header: React.FunctionComponent<DialogHeaderProps> | ||
| } | ||
| export const Dialog: React.FunctionComponent<DialogProps> | ||
|
|
||
| export interface LabelGroupProps extends CommonProps, Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'> {} | ||
|
|
||
|
|
@@ -536,6 +553,10 @@ declare module '@primer/components/src/Link' { | |
| import {Link} from '@primer/components' | ||
| export default Link | ||
| } | ||
| declare module '@primer/components/src/Pagination' { | ||
| import {Pagination} from '@primer/components' | ||
| export default Pagination | ||
| } | ||
| declare module '@primer/components/src/PointerBox' { | ||
| import {PointerBox} from '@primer/components' | ||
| export default PointerBox | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great idea to start doing! Would love to spend time refactoring the current components and creating more component-related objects in the theme, then start documenting that on each docs page