From 2436836384b6f49a5eaad0d593cca9cfe973266c Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Tue, 31 Mar 2020 15:51:07 -0700 Subject: [PATCH 01/22] Model WIP --- src/Pagination/model.js | 148 ++++++++++++++++++++ src/__tests__/Pagination/PaginationModel.js | 109 ++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 src/Pagination/model.js create mode 100644 src/__tests__/Pagination/PaginationModel.js diff --git a/src/Pagination/model.js b/src/Pagination/model.js new file mode 100644 index 00000000000..e4992f2b223 --- /dev/null +++ b/src/Pagination/model.js @@ -0,0 +1,148 @@ +const DEBUG = false + +const debug = (...args) => { + if (DEBUG) console.log(...args) +} + +class Range { + constructor(start, end) { + if (start > end) { + throw new Error('Range start must be less than or equal to end') + } + + this.start = start + this.end = end + } + + includes(n) { + return n >= this.start && n <= this.end + } + + count() { + return this.end - this.start + 1 + } + + toArray() { + const arr = [] + for (let i = this.start; i <= this.end; i++) { + arr.push(i) + } + return arr + } +} + +export function buildPaginationModel(pageCount, currentPage, showPages, marginPageCount, surroundingPageCount) { + const pages = [] + const pageRange = new Range(1, pageCount) + + const prev = {type: 'PREV', num: currentPage - 1, disabled: currentPage === 1} + + if (showPages) { + const pageNums = new Set() + const addPage = n => (pageRange.includes(n) ? pageNums.add(n) : null) + // const marginLeft = new Range(0, marginPageCount) + // const marginRight = new Range(pageCount + 1 - marginPageCount, pageCount + 1) + for (let i = 1; i <= marginPageCount; i++) { + addPage(i) + addPage(pageCount - (i - 1)) + } + debug([...pageNums.values()]) + + const centerLeft = currentPage - surroundingPageCount + const centerRight = currentPage + surroundingPageCount + debug('left & right', centerLeft, centerRight) + let overflow = 0 + for (let i = currentPage - 1; i >= centerLeft; i--) { + console.log('checking left', i) + if (!pageRange.includes(i)) { + console.log('trying to add page', i, 'but it is alrady there') + // the page to the left of center is already shown or out of bounds + // make up for it to the right + const diff = currentPage - i + overflow++ + console.log('adding', centerRight + overflow, ' instead:', centerRight, overflow) + addPage(centerRight + overflow) + } else { + addPage(i) + } + } + overflow = 0 + for (let i = currentPage + 1; i <= centerRight; i++) { + debug('checking right', i) + if (!pageRange.includes(i)) { + // the page to the right of center is already shown or out of bounds + // make up for it to the left + overflow++ + const diff = i - centerRight + addPage(centerLeft - overflow) + } else { + addPage(i) + } + } + addPage(currentPage) + + // // If we only skipped one page between the margin and center, + // // then just render that page instead of rendering a break + // const leftOfCenter = centerLeft - 1 + // const rightOfCenter = centerRight + 1 + // if (pageNums.has(leftOfCenter - 1)) { + // addPage(leftOfCenter) + // } + // if (pageNums.has(rightOfCenter + 1)) { + // addPage(rightOfCenter) + // } + + const sorted = [...pageNums.values()].sort((a, b) => a - b) + debug('sorted', sorted) + for (const [idx, num] of sorted.entries()) { + const selected = num === currentPage + if (idx === 0) { + pages.push({ + type: 'NUM', + num, + selected + }) + } else { + const last = sorted[idx - 1] + const delta = num - last + if (delta === 1) { + pages.push({ + type: 'NUM', + num, + selected + }) + // } else if (delta === 2) { + // // We skipped one number; + // // render it instead of a break + // pages.push({ + // type: 'NUM', + // num: num - 1, + // selected + // }) + // pages.push({ + // type: 'NUM', + // num, + // selected + // }) + } else { + // We skipped more than one; + // render a break + pages.push({ + type: 'BREAK', + num: num - 1 + }) + pages.push({ + type: 'NUM', + num, + selected + }) + } + } + } + } + + debug(pages) + const next = {type: 'NEXT', num: currentPage + 1, disabled: currentPage === pageCount} + + return [prev, ...pages, next] +} diff --git a/src/__tests__/Pagination/PaginationModel.js b/src/__tests__/Pagination/PaginationModel.js new file mode 100644 index 00000000000..2563ac3cdb8 --- /dev/null +++ b/src/__tests__/Pagination/PaginationModel.js @@ -0,0 +1,109 @@ +import 'babel-polyfill' +import {buildPaginationModel} from '../../Pagination/model' + +function first(array, count = 1) { + const slice = array.slice(0, count) + return count === 1 ? slice[0] : slice +} + +function last(array, count = 1) { + const len = array.length + const slice = array.slice(len - count, len) + return count === 1 ? slice[0] : slice +} + +describe('Pagination model', () => { + it('sets disabled on prev links', () => { + const model1 = buildPaginationModel(10, 1, true, 1, 2) + expect(first(model1).type).toEqual('PREV') + expect(first(model1).disabled).toBe(true) + + const model2 = buildPaginationModel(10, 2, true, 1, 2) + expect(first(model2).type).toEqual('PREV') + expect(first(model2).disabled).toBe(false) + }) + + it('sets disabled on next links', () => { + const model1 = buildPaginationModel(10, 10, true, 1, 2) + expect(last(model1).type).toEqual('NEXT') + expect(last(model1).disabled).toBe(true) + + const model2 = buildPaginationModel(10, 9, true, 1, 2) + expect(last(model2).type).toEqual('NEXT') + expect(last(model2).disabled).toBe(false) + }) + + it('sets the page number for prev and next links', () => { + const model = buildPaginationModel(10, 5, true, 1, 2) + expect(first(model).num).toEqual(4) + expect(last(model).num).toEqual(6) + }) + + it('ensures margin pages on the left', () => { + const model = buildPaginationModel(10, 10, true, 2, 0) + const slice = first(model, 5) + + const expected = [ + {type: 'PREV', num: 9}, + {type: 'NUM', num: 1}, + {type: 'NUM', num: 2}, + {type: 'BREAK'}, + {type: 'NUM'} + ] + + expect(slice).toMatchObject(expected) + }) + + it('ensures margin pages on the right', () => { + const model = buildPaginationModel(10, 1, true, 2, 0) + const slice = last(model, 5) + + const expected = [ + {type: 'NUM'}, + {type: 'BREAK'}, + {type: 'NUM', num: 9}, + {type: 'NUM', num: 10}, + {type: 'NEXT', num: 2} + ] + + expect(slice).toMatchObject(expected) + }) + + it('ensures that the current page is surrounded by the right number of pages', () => { + const model = buildPaginationModel(10, 5, true, 1, 1) + const expected = [ + {type: 'PREV', num: 4}, + {type: 'NUM', num: 1}, + {type: 'BREAK'}, + {type: 'NUM', num: 4}, + {type: 'NUM', num: 5, selected: true}, + {type: 'NUM', num: 6}, + {type: 'BREAK'}, + {type: 'NUM', num: 10}, + {type: 'NEXT', num: 6} + ] + expect(model).toMatchObject(expected) + }) + + it("doesn't show a break if only one page is skipped", () => { + const model = buildPaginationModel(9, 5, true, 2, 1) + const expected = [ + {type: 'PREV', num: 4}, + {type: 'NUM', num: 1}, + {type: 'NUM', num: 2}, + {type: 'NUM', num: 3}, + {type: 'NUM', num: 4}, + {type: 'NUM', num: 5, selected: true}, + {type: 'NUM', num: 6}, + {type: 'NUM', num: 7}, + {type: 'NUM', num: 8}, + {type: 'NUM', num: 9}, + {type: 'NEXT', num: 6} + ] + expect(model).toMatchObject(expected) + }) + + it('adds items to the right if it hits bounds to the left', () => { + // + }) +}) From b260b42403b1f2e960e2bf56c7c560f2ef5b4ad8 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Tue, 31 Mar 2020 16:18:44 -0700 Subject: [PATCH 02/22] Tweak Pagination model --- src/Pagination/model.js | 132 +++++++------------- src/__tests__/Pagination/PaginationModel.js | 52 ++++++-- 2 files changed, 81 insertions(+), 103 deletions(-) diff --git a/src/Pagination/model.js b/src/Pagination/model.js index e4992f2b223..5900b56528b 100644 --- a/src/Pagination/model.js +++ b/src/Pagination/model.js @@ -4,93 +4,59 @@ const debug = (...args) => { if (DEBUG) console.log(...args) } -class Range { - constructor(start, end) { - if (start > end) { - throw new Error('Range start must be less than or equal to end') - } - - this.start = start - this.end = end - } - - includes(n) { - return n >= this.start && n <= this.end - } - - count() { - return this.end - this.start + 1 - } - - toArray() { - const arr = [] - for (let i = this.start; i <= this.end; i++) { - arr.push(i) - } - return arr - } -} - export function buildPaginationModel(pageCount, currentPage, showPages, marginPageCount, surroundingPageCount) { const pages = [] - const pageRange = new Range(1, pageCount) - - const prev = {type: 'PREV', num: currentPage - 1, disabled: currentPage === 1} if (showPages) { const pageNums = new Set() - const addPage = n => (pageRange.includes(n) ? pageNums.add(n) : null) - // const marginLeft = new Range(0, marginPageCount) - // const marginRight = new Range(pageCount + 1 - marginPageCount, pageCount + 1) - for (let i = 1; i <= marginPageCount; i++) { - addPage(i) - addPage(pageCount - (i - 1)) + const addPage = n => { + if (n >= 1 && n <= pageCount) { + pageNums.add(n) + } } - debug([...pageNums.values()]) - const centerLeft = currentPage - surroundingPageCount - const centerRight = currentPage + surroundingPageCount - debug('left & right', centerLeft, centerRight) - let overflow = 0 - for (let i = currentPage - 1; i >= centerLeft; i--) { - console.log('checking left', i) - if (!pageRange.includes(i)) { - console.log('trying to add page', i, 'but it is alrady there') - // the page to the left of center is already shown or out of bounds - // make up for it to the right - const diff = currentPage - i - overflow++ - console.log('adding', centerRight + overflow, ' instead:', centerRight, overflow) - addPage(centerRight + overflow) - } else { - addPage(i) + // Start by defining the window of pages to show around the current page. + // If the window goes off either edge, shift it until it fits. + let extentLeft = currentPage - surroundingPageCount + let extentRight = currentPage + surroundingPageCount + if (extentLeft < 1 && extentRight > pageCount) { + // Our window is larger than the entire range, + // so simply display every page. + extentLeft = 1 + extentRight = pageCount + } else if (extentLeft < 1) { + while (extentLeft < 1) { + extentLeft++ + extentRight++ + } + } else if (extentRight > pageCount) { + while (extentRight > pageCount) { + extentLeft-- + extentRight-- } } - overflow = 0 - for (let i = currentPage + 1; i <= centerRight; i++) { - debug('checking right', i) - if (!pageRange.includes(i)) { - // the page to the right of center is already shown or out of bounds - // make up for it to the left - overflow++ - const diff = i - centerRight - addPage(centerLeft - overflow) + + // Next, include the pages in the margins. + // If a margin page is already covered in the window, + // extend the window to the other direction. + for (let i = 1; i <= marginPageCount; i++) { + const leftPage = i + const rightPage = pageCount - (i - 1) + if (leftPage >= extentLeft) { + extentRight++ } else { - addPage(i) + addPage(leftPage) + } + if (rightPage <= extentRight) { + extentLeft-- + } else { + addPage(rightPage) } } - addPage(currentPage) - // // If we only skipped one page between the margin and center, - // // then just render that page instead of rendering a break - // const leftOfCenter = centerLeft - 1 - // const rightOfCenter = centerRight + 1 - // if (pageNums.has(leftOfCenter - 1)) { - // addPage(leftOfCenter) - // } - // if (pageNums.has(rightOfCenter + 1)) { - // addPage(rightOfCenter) - // } + for (let i = extentLeft; i <= extentRight; i++) { + addPage(i) + } const sorted = [...pageNums.values()].sort((a, b) => a - b) debug('sorted', sorted) @@ -111,22 +77,8 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa num, selected }) - // } else if (delta === 2) { - // // We skipped one number; - // // render it instead of a break - // pages.push({ - // type: 'NUM', - // num: num - 1, - // selected - // }) - // pages.push({ - // type: 'NUM', - // num, - // selected - // }) } else { - // We skipped more than one; - // render a break + // We skipped some, so add a break pages.push({ type: 'BREAK', num: num - 1 @@ -142,7 +94,7 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa } debug(pages) + const prev = {type: 'PREV', num: currentPage - 1, disabled: currentPage === 1} const next = {type: 'NEXT', num: currentPage + 1, disabled: currentPage === pageCount} - return [prev, ...pages, next] } diff --git a/src/__tests__/Pagination/PaginationModel.js b/src/__tests__/Pagination/PaginationModel.js index 2563ac3cdb8..a54f4f3ac4e 100644 --- a/src/__tests__/Pagination/PaginationModel.js +++ b/src/__tests__/Pagination/PaginationModel.js @@ -85,25 +85,51 @@ describe('Pagination model', () => { expect(model).toMatchObject(expected) }) - it("doesn't show a break if only one page is skipped", () => { - const model = buildPaginationModel(9, 5, true, 2, 1) + // it("doesn't show a break if only one page is skipped", () => { + // const model = buildPaginationModel(9, 5, true, 2, 1) + // const expected = [ + // {type: 'PREV', num: 4}, + // {type: 'NUM', num: 1}, + // {type: 'NUM', num: 2}, + // {type: 'NUM', num: 3}, + // {type: 'NUM', num: 4}, + // {type: 'NUM', num: 5, selected: true}, + // {type: 'NUM', num: 6}, + // {type: 'NUM', num: 7}, + // {type: 'NUM', num: 8}, + // {type: 'NUM', num: 9}, + // {type: 'NEXT', num: 6} + // ] + // expect(model).toMatchObject(expected) + // }) + + it('adds items to the right if it hits bounds to the left', () => { + const model = buildPaginationModel(15, 2, true, 1, 1) const expected = [ - {type: 'PREV', num: 4}, + {type: 'PREV', num: 1}, {type: 'NUM', num: 1}, - {type: 'NUM', num: 2}, + {type: 'NUM', num: 2, selected: true}, {type: 'NUM', num: 3}, + // normally with a surround of 1, only 1 and 3 would be shown + // however, since 1 was already shown, we extend to 4 {type: 'NUM', num: 4}, - {type: 'NUM', num: 5, selected: true}, - {type: 'NUM', num: 6}, - {type: 'NUM', num: 7}, - {type: 'NUM', num: 8}, - {type: 'NUM', num: 9}, - {type: 'NEXT', num: 6} + {type: 'BREAK'} ] - expect(model).toMatchObject(expected) + expect(first(model, 6)).toMatchObject(expected) }) - it('adds items to the right if it hits bounds to the left', () => { - // + it('adds items to the left if it hits bounds to the right', () => { + const model = buildPaginationModel(15, 14, true, 1, 1) + const expected = [ + // normally with a surround of 1, only 13 and 15 would be shown + // however, since 15 was already shown, we extend to 12 + {type: 'BREAK'}, + {type: 'NUM', num: 12}, + {type: 'NUM', num: 13}, + {type: 'NUM', num: 14, selected: true}, + {type: 'NUM', num: 15}, + {type: 'NEXT', num: 15} + ] + expect(last(model, 6)).toMatchObject(expected) }) }) From 93450ac3c43a0f0c30e3a222afcf43e62285f2a3 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Tue, 31 Mar 2020 16:20:33 -0700 Subject: [PATCH 03/22] Add WIP Pagination component --- docs/content/Pagination.md | 67 +++++++ .../src/@primer/gatsby-theme-doctocat/nav.yml | 2 + src/Pagination/Pagination.js | 177 ++++++++++++++++++ src/Pagination/index.js | 3 + src/index.js | 1 + 5 files changed, 250 insertions(+) create mode 100644 docs/content/Pagination.md create mode 100644 src/Pagination/Pagination.js create mode 100644 src/Pagination/index.js diff --git a/docs/content/Pagination.md b/docs/content/Pagination.md new file mode 100644 index 00000000000..428b71aa79b --- /dev/null +++ b/docs/content/Pagination.md @@ -0,0 +1,67 @@ +--- +title: Pagination +--- +import State from '../components/State' + +Use the pagination component to create a connected set of links that go to related pages (for example, previous, next, or page numbers). + +## Basic example + +The pagination component only requires two properties to function: `pages`, which is the total number of pages, and `currentPage`, which is the currently selected page (which should be managed by the consuming application). 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, like in the `onPageChange` function defined in this example: + +```jsx live + + {([page, setPage]) => { + const totalPages = 15 + const onPageChange = (evt, page) => { + evt.preventDefault() + setPage(page) + } + + return ( + + Current page: {page} / {totalPages} + + + ) + }} + +``` + +## Previous/next pagination + +You can make a simple pagination container with just the Previous and Next buttons by setting `showPages` to `false`. You still need to define the total number of pages and the current page so that the component calculates the correct styles. + +```jsx live + + {([page, setPage]) => { + const totalPages = 10 + const onPageChange = (evt, page) => { + evt.preventDefault() + setPage(page) + } + + return ( + + Current page: {page} / {totalPages} + + + ) + }} + +``` diff --git a/docs/src/@primer/gatsby-theme-doctocat/nav.yml b/docs/src/@primer/gatsby-theme-doctocat/nav.yml index 7f9f8399039..432a3aadf78 100644 --- a/docs/src/@primer/gatsby-theme-doctocat/nav.yml +++ b/docs/src/@primer/gatsby-theme-doctocat/nav.yml @@ -60,6 +60,8 @@ url: /LabelGroup - title: Link url: /Link + - title: Pagination + url: /Pagination - title: PointerBox url: /PointerBox - title: Popover diff --git a/src/Pagination/Pagination.js b/src/Pagination/Pagination.js new file mode 100644 index 00000000000..8bf84f9fdca --- /dev/null +++ b/src/Pagination/Pagination.js @@ -0,0 +1,177 @@ +import React from 'react' +import PropTypes from 'prop-types' +import styled from 'styled-components' +// import classnames from 'classnames' +import {get} from '../constants' +// import theme from '../theme' +// import elementType from './utils/elementType' +// import Link from './Link' +import Box from '../Box' +import {buildPaginationModel} from './model' + +const Page = styled.a` + position: relative; + float: left; + padding: 7px 12px; + margin-left: -1px; + font-size: 13px; + font-style: normal; + font-weight: ${get('fontWeights.bold')}; + color: ${get('blue.5')}; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + user-select: none; + background: ${get('white')}; + border: ${get('borders.1')} ${get('colors.border.gray')}; +` + +function buildPaginationModelComponent(page, hrefBuilder, ariaLabelBuilder, onClick) { + const props = {} + let content = '' + let key = '' + + switch (page.type) { + case 'PREV': { + key = 'page-prev' + content = 'Previous' + if (page.disabled) { + Object.assign(props, {as: 'span', 'aria-disabled': 'true'}) + } else { + Object.assign(props, { + rel: 'prev', + href: hrefBuilder(page.num), + 'aria-label': 'Previous Page', + onClick + }) + } + break + } + case 'NEXT': { + key = 'page-next' + content = 'Next' + if (page.disabled) { + Object.assign(props, {as: 'span', 'aria-disabled': 'true'}) + } else { + Object.assign(props, { + rel: 'next', + href: hrefBuilder(page.num), + 'aria-label': 'Next Page', + onClick + }) + } + break + } + case 'NUM': { + key = `page-${page.num}` + content = page.num + if (page.selected) { + Object.assign(props, {as: 'em', 'aria-current': 'page'}) + } else { + Object.assign(props, {href: hrefBuilder(page.num), 'aria-label': ariaLabelBuilder(page.num), onClick}) + } + break + } + case 'BREAK': { + key = `page-${page.num}-break` + content = '…' + Object.assign(props, {as: 'span'}) + } + } + + return ( + + {content} + + ) +} + +function usePaginationPages({ + pages, + currentPage, + onPageChange, + hrefBuilder, + ariaLabelBuilder, + marginPageCount, + showPages, + surroundingPageCount +}) { + const pageChange = React.useCallback(n => e => onPageChange(e, n), [onPageChange]) + + const model = React.useMemo(() => { + return buildPaginationModel(pages, currentPage, showPages, marginPageCount, surroundingPageCount) + }, [pages, currentPage, showPages, marginPageCount, surroundingPageCount]) + + const children = React.useMemo(() => { + return model.map(page => buildPaginationModelComponent(page, hrefBuilder, ariaLabelBuilder, pageChange(page.num))) + }, [model, hrefBuilder, ariaLabelBuilder, pageChange]) + + return children +} + +const PaginationContainer = styled.nav` + margin-top: 20px; + margin-bottom: 15px; + text-align: center; +` + +function Pagination({ + theme, + pages, + currentPage, + onPageChange, + hrefBuilder, + ariaLabelBuilder, + marginPageCount, + showPages, + surroundingPageCount, + ...rest +}) { + const pageElements = usePaginationPages({ + pages, + currentPage, + onPageChange, + hrefBuilder, + ariaLabelBuilder, + marginPageCount, + showPages, + surroundingPageCount + }) + return ( + + {pageElements} + + ) +} + +function defaultAriaLabelBuilder(pageNum) { + return `Page ${pageNum}` +} + +function defaultHrefBuilder(pageNum) { + return `#${pageNum}` +} + +function noop() {} + +Pagination.propTypes = { + ariaLabelBuilder: PropTypes.func, + currentPage: PropTypes.number.isRequired, + hrefBuilder: PropTypes.func, + marginPageCount: PropTypes.number, + onPageChange: PropTypes.func, + pages: PropTypes.number.isRequired, + showPages: PropTypes.bool, + surroundingPageCount: PropTypes.number +} + +Pagination.defaultProps = { + ariaLabelBuilder: defaultAriaLabelBuilder, + hrefBuilder: defaultHrefBuilder, + marginPageCount: 1, + onPageChange: noop, + showPages: true, + surroundingPageCount: 2 +} + +export default Pagination diff --git a/src/Pagination/index.js b/src/Pagination/index.js new file mode 100644 index 00000000000..1466412190c --- /dev/null +++ b/src/Pagination/index.js @@ -0,0 +1,3 @@ +import Pagination from './Pagination' + +export default Pagination diff --git a/src/index.js b/src/index.js index c94c997caad..1481979d276 100644 --- a/src/index.js +++ b/src/index.js @@ -34,6 +34,7 @@ export {default as Heading} from './Heading' export {default as Label} from './Label' export {default as LabelGroup} from './LabelGroup' export {default as Link} from './Link' +export {default as Pagination} from './Pagination' export {default as PointerBox} from './PointerBox' export {default as Popover} from './Popover' export {default as ProgressBar} from './ProgressBar' From d24e7440d2f8621a15a22d770f4c2e1e44e2753c Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 10:50:12 -0700 Subject: [PATCH 04/22] Finish up base functionalty and styling --- src/Pagination/Pagination.js | 136 +++++++++++++++-------------------- src/Pagination/model.js | 56 +++++++++++++++ src/theme.js | 19 +++++ 3 files changed, 132 insertions(+), 79 deletions(-) diff --git a/src/Pagination/Pagination.js b/src/Pagination/Pagination.js index 8bf84f9fdca..898e56147a7 100644 --- a/src/Pagination/Pagination.js +++ b/src/Pagination/Pagination.js @@ -1,13 +1,13 @@ import React from 'react' import PropTypes from 'prop-types' -import styled from 'styled-components' +import styled, {css} from 'styled-components' // import classnames from 'classnames' import {get} from '../constants' // import theme from '../theme' // import elementType from './utils/elementType' // import Link from './Link' import Box from '../Box' -import {buildPaginationModel} from './model' +import {buildPaginationModel, buildComponentData} from './model' const Page = styled.a` position: relative; @@ -17,81 +17,60 @@ const Page = styled.a` font-size: 13px; font-style: normal; font-weight: ${get('fontWeights.bold')}; - color: ${get('blue.5')}; + color: ${get('pagination.colors.link')}; white-space: nowrap; vertical-align: middle; cursor: pointer; user-select: none; background: ${get('white')}; - border: ${get('borders.1')} ${get('colors.border.gray')}; -` + border: ${get('borders.1')} ${get('pagination.colors.border')}; + text-decoration: none; -function buildPaginationModelComponent(page, hrefBuilder, ariaLabelBuilder, onClick) { - const props = {} - let content = '' - let key = '' - - switch (page.type) { - case 'PREV': { - key = 'page-prev' - content = 'Previous' - if (page.disabled) { - Object.assign(props, {as: 'span', 'aria-disabled': 'true'}) - } else { - Object.assign(props, { - rel: 'prev', - href: hrefBuilder(page.num), - 'aria-label': 'Previous Page', - onClick - }) - } - break - } - case 'NEXT': { - key = 'page-next' - content = 'Next' - if (page.disabled) { - Object.assign(props, {as: 'span', 'aria-disabled': 'true'}) - } else { - Object.assign(props, { - rel: 'next', - href: hrefBuilder(page.num), - 'aria-label': 'Next Page', - onClick - }) - } - break - } - case 'NUM': { - key = `page-${page.num}` - content = page.num - if (page.selected) { - Object.assign(props, {as: 'em', 'aria-current': 'page'}) - } else { - Object.assign(props, {href: hrefBuilder(page.num), 'aria-label': ariaLabelBuilder(page.num), onClick}) - } - break - } - case 'BREAK': { - key = `page-${page.num}-break` - content = '…' - Object.assign(props, {as: 'span'}) - } + &:first-child { + margin-left: 0; + border-top-left-radius: ${get('radii.1')}; + border-bottom-left-radius: ${get('radii.1')}; } - return ( - - {content} - - ) -} + &:last-child { + border-top-right-radius: ${get('radii.1')}; + border-bottom-right-radius: ${get('radii.1')}; + } + + ${props => + !props.selected && + css` + &:hover, + &:focus { + z-index: 2; + background-color: ${get('pagination.colors.hover.bg')}; + border-color: ${get('pagination.colors.border')}; + } + `} + + ${props => + props.selected && + css` + z-index: 3; + color: ${get('colors.white')}; + background-color: ${get('pagination.colors.selected.bg')}; + border-color: ${get('pagination.colors.selected.border')}; + `} + + ${props => + props.disabled && + css` + color: ${get('pagination.colors.disabled.fg')}; + cursor: default; + background-color: ${get('pagination.colors.disabled.bg')}; + `} +` function usePaginationPages({ - pages, + pageCount, currentPage, onPageChange, hrefBuilder, - ariaLabelBuilder, marginPageCount, showPages, surroundingPageCount @@ -99,12 +78,19 @@ function usePaginationPages({ const pageChange = React.useCallback(n => e => onPageChange(e, n), [onPageChange]) const model = React.useMemo(() => { - return buildPaginationModel(pages, currentPage, showPages, marginPageCount, surroundingPageCount) - }, [pages, currentPage, showPages, marginPageCount, surroundingPageCount]) + return buildPaginationModel(pageCount, currentPage, showPages, marginPageCount, surroundingPageCount) + }, [pageCount, currentPage, showPages, marginPageCount, surroundingPageCount]) const children = React.useMemo(() => { - return model.map(page => buildPaginationModelComponent(page, hrefBuilder, ariaLabelBuilder, pageChange(page.num))) - }, [model, hrefBuilder, ariaLabelBuilder, pageChange]) + return model.map(page => { + const {props, key, content} = buildComponentData(page, hrefBuilder, pageChange(page.num)) + return ( + + {content} + + ) + }) + }, [model, hrefBuilder, pageChange]) return children } @@ -117,22 +103,20 @@ const PaginationContainer = styled.nav` function Pagination({ theme, - pages, + pageCount, currentPage, onPageChange, hrefBuilder, - ariaLabelBuilder, marginPageCount, showPages, surroundingPageCount, ...rest }) { const pageElements = usePaginationPages({ - pages, + pageCount, currentPage, onPageChange, hrefBuilder, - ariaLabelBuilder, marginPageCount, showPages, surroundingPageCount @@ -144,10 +128,6 @@ function Pagination({ ) } -function defaultAriaLabelBuilder(pageNum) { - return `Page ${pageNum}` -} - function defaultHrefBuilder(pageNum) { return `#${pageNum}` } @@ -155,18 +135,16 @@ function defaultHrefBuilder(pageNum) { function noop() {} Pagination.propTypes = { - ariaLabelBuilder: PropTypes.func, currentPage: PropTypes.number.isRequired, hrefBuilder: PropTypes.func, marginPageCount: PropTypes.number, onPageChange: PropTypes.func, - pages: PropTypes.number.isRequired, + pageCount: PropTypes.number.isRequired, showPages: PropTypes.bool, surroundingPageCount: PropTypes.number } Pagination.defaultProps = { - ariaLabelBuilder: defaultAriaLabelBuilder, hrefBuilder: defaultHrefBuilder, marginPageCount: 1, onPageChange: noop, diff --git a/src/Pagination/model.js b/src/Pagination/model.js index 5900b56528b..76202bf0f36 100644 --- a/src/Pagination/model.js +++ b/src/Pagination/model.js @@ -98,3 +98,59 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa const next = {type: 'NEXT', num: currentPage + 1, disabled: currentPage === pageCount} return [prev, ...pages, next] } + +export function buildComponentData(page, hrefBuilder, onClick) { + const props = {} + let content = '' + let key = '' + + switch (page.type) { + case 'PREV': { + key = 'page-prev' + content = 'Previous' + if (page.disabled) { + Object.assign(props, {as: 'span', 'aria-disabled': 'true', disabled: true}) + } else { + Object.assign(props, { + rel: 'prev', + href: hrefBuilder(page.num), + 'aria-label': 'Previous Page', + onClick + }) + } + break + } + case 'NEXT': { + key = 'page-next' + content = 'Next' + if (page.disabled) { + Object.assign(props, {as: 'span', 'aria-disabled': 'true', disabled: true}) + } else { + Object.assign(props, { + rel: 'next', + href: hrefBuilder(page.num), + 'aria-label': 'Next Page', + onClick + }) + } + break + } + case 'NUM': { + key = `page-${page.num}` + content = page.num + if (page.selected) { + Object.assign(props, {as: 'em', 'aria-current': 'page', selected: true}) + } else { + Object.assign(props, {href: hrefBuilder(page.num), 'aria-label': `Page ${page.num}`, onClick}) + } + break + } + case 'BREAK': { + key = `page-${page.num}-break` + content = '…' + Object.assign(props, {as: 'span', disabled: true}) + } + } + + return {props, key, content} +} diff --git a/src/theme.js b/src/theme.js index 79bbd79b2ac..5e16ef6a510 100644 --- a/src/theme.js +++ b/src/theme.js @@ -158,6 +158,24 @@ const popovers = { } } +const pagination = { + colors: { + border: colors.border.gray, + link: colors.blue[5], + disabled: { + fg: colors.gray[3], + bg: colors.gray[0] + }, + hover: { + bg: colors.gray[1] + }, + selected: { + border: colors.blue[5], + bg: colors.blue[5] + } + } +} + const theme = { breakpoints: ['544px', '768px', '1012px', '1280px'], maxWidths: { @@ -204,6 +222,7 @@ const theme = { }, space: ['0', '4px', '8px', '16px', '24px', '32px', '40px', '48px', '64px', '80px', '96px', '112px', '128px'], buttons, + pagination, popovers } From cd141f76536cf8b961acb048db346264a005e637 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 10:53:36 -0700 Subject: [PATCH 05/22] Ensure breaks when margin is 0 --- src/Pagination/model.js | 16 ++++++++++++++++ src/__tests__/Pagination/PaginationModel.js | 14 ++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/Pagination/model.js b/src/Pagination/model.js index 76202bf0f36..ae0f7bce999 100644 --- a/src/Pagination/model.js +++ b/src/Pagination/model.js @@ -63,6 +63,14 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa for (const [idx, num] of sorted.entries()) { const selected = num === currentPage if (idx === 0) { + if (num !== 1) { + // If the first page isn't page one, + // we need to add a break + pages.push({ + type: 'BREAK', + num: 1 + }) + } pages.push({ type: 'NUM', num, @@ -77,6 +85,14 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa num, selected }) + if (idx === sorted.length - 1 && num !== pageCount) { + // If the last page we're rendering + // isn't the final page, we need a break + pages.push({ + type: 'BREAK', + num: pageCount + }) + } } else { // We skipped some, so add a break pages.push({ diff --git a/src/__tests__/Pagination/PaginationModel.js b/src/__tests__/Pagination/PaginationModel.js index a54f4f3ac4e..53f7ec4b88f 100644 --- a/src/__tests__/Pagination/PaginationModel.js +++ b/src/__tests__/Pagination/PaginationModel.js @@ -132,4 +132,18 @@ describe('Pagination model', () => { ] expect(last(model, 6)).toMatchObject(expected) }) + + it('correctly creates breaks next to the next/prev links with a margin of 0', () => { + const model = buildPaginationModel(10, 5, true, 0, 1) + const expected = [ + {type: 'PREV'}, + {type: 'BREAK', num: 1}, + {type: 'NUM', num: 4}, + {type: 'NUM', num: 5, selected: true}, + {type: 'NUM', num: 6}, + {type: 'BREAK', num: 10}, + {type: 'NEXT'} + ] + expect(model).toMatchObject(expected) + }) }) From c800b515a25049b2f4e02bacaa9efc4b14c42c85 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 10:57:30 -0700 Subject: [PATCH 06/22] Fix when last page isn't final page --- src/Pagination/model.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Pagination/model.js b/src/Pagination/model.js index ae0f7bce999..570015d5a4d 100644 --- a/src/Pagination/model.js +++ b/src/Pagination/model.js @@ -85,14 +85,6 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa num, selected }) - if (idx === sorted.length - 1 && num !== pageCount) { - // If the last page we're rendering - // isn't the final page, we need a break - pages.push({ - type: 'BREAK', - num: pageCount - }) - } } else { // We skipped some, so add a break pages.push({ @@ -107,6 +99,16 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa } } } + + const lastPage = pages[pages.length - 1] + if (lastPage.type === 'NUM' && lastPage.num !== pageCount) { + // The last page we rendered wasn't the actual last page, + // so we need an additional break + pages.push({ + type: 'BREAK', + num: pageCount + }) + } } debug(pages) From 152ae8180a374b47e4d851527458aa0fa124b3fe Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 10:59:40 -0700 Subject: [PATCH 07/22] :keyboard: --- src/__tests__/Pagination/PaginationModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/Pagination/PaginationModel.js b/src/__tests__/Pagination/PaginationModel.js index 53f7ec4b88f..94fe9665daa 100644 --- a/src/__tests__/Pagination/PaginationModel.js +++ b/src/__tests__/Pagination/PaginationModel.js @@ -133,7 +133,7 @@ describe('Pagination model', () => { expect(last(model, 6)).toMatchObject(expected) }) - it('correctly creates breaks next to the next/prev links with a margin of 0', () => { + it('correctly creates breaks next to the next/prev links when margin is 0', () => { const model = buildPaginationModel(10, 5, true, 0, 1) const expected = [ {type: 'PREV'}, From 361a999e64f2463bb0309fc9f78c56d2d262551a Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 10:59:55 -0700 Subject: [PATCH 08/22] Finish primary Pagination docs --- docs/content/Pagination.md | 88 +++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/docs/content/Pagination.md b/docs/content/Pagination.md index 428b71aa79b..9b148d265b9 100644 --- a/docs/content/Pagination.md +++ b/docs/content/Pagination.md @@ -7,16 +7,26 @@ Use the pagination component to create a connected set of links that go to relat ## Basic example -The pagination component only requires two properties to function: `pages`, which is the total number of pages, and `currentPage`, which is the currently selected page (which should be managed by the consuming application). 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: +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 + 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, like in the `onPageChange` function defined in this example: +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 - + {([page, setPage]) => { const totalPages = 15 const onPageChange = (evt, page) => { @@ -28,7 +38,7 @@ By default, clicking a link in the pagination component will cause the browser t Current page: {page} / {totalPages} @@ -38,9 +48,73 @@ By default, clicking a link in the pagination component will cause the browser t ``` -## Previous/next pagination +## 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 + + {([lastUrl, setLastUrl]) => { + const onPageChange = (evt, page) => { + evt.preventDefault() + setLastUrl(evt.target.href) + } + const hrefBuilder = (page) => { + return `https://example.com/pages/${page}` + } + + return ( + + The last URL clicked was: {lastUrl} + + + ) + }} + +``` + +## 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 + 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 to the right. 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 + + {[1, 2, 3, 4, 5].map(page => ( + e.preventDefault()} + /> + ))} + +``` + +### Previous/next pagination -You can make a simple pagination container with just the Previous and Next buttons by setting `showPages` to `false`. You still need to define the total number of pages and the current page so that the component calculates the correct styles. +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 @@ -55,7 +129,7 @@ You can make a simple pagination container with just the Previous and Next butto Current page: {page} / {totalPages} Date: Wed, 1 Apr 2020 11:00:27 -0700 Subject: [PATCH 09/22] Remove test for unwanted behavior --- src/__tests__/Pagination/PaginationModel.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/__tests__/Pagination/PaginationModel.js b/src/__tests__/Pagination/PaginationModel.js index 94fe9665daa..3dec12c539b 100644 --- a/src/__tests__/Pagination/PaginationModel.js +++ b/src/__tests__/Pagination/PaginationModel.js @@ -85,24 +85,6 @@ describe('Pagination model', () => { expect(model).toMatchObject(expected) }) - // it("doesn't show a break if only one page is skipped", () => { - // const model = buildPaginationModel(9, 5, true, 2, 1) - // const expected = [ - // {type: 'PREV', num: 4}, - // {type: 'NUM', num: 1}, - // {type: 'NUM', num: 2}, - // {type: 'NUM', num: 3}, - // {type: 'NUM', num: 4}, - // {type: 'NUM', num: 5, selected: true}, - // {type: 'NUM', num: 6}, - // {type: 'NUM', num: 7}, - // {type: 'NUM', num: 8}, - // {type: 'NUM', num: 9}, - // {type: 'NEXT', num: 6} - // ] - // expect(model).toMatchObject(expected) - // }) - it('adds items to the right if it hits bounds to the left', () => { const model = buildPaginationModel(15, 2, true, 1, 1) const expected = [ From d05bee6d6bbea5d98f817a5a445ff37e67a60678 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 12:04:15 -0700 Subject: [PATCH 10/22] Refactor theme --- src/Pagination/Pagination.js | 37 ++++++---- src/theme.js | 129 ++++++++++++++++++++++------------- 2 files changed, 104 insertions(+), 62 deletions(-) diff --git a/src/Pagination/Pagination.js b/src/Pagination/Pagination.js index 898e56147a7..394638ddeb4 100644 --- a/src/Pagination/Pagination.js +++ b/src/Pagination/Pagination.js @@ -2,7 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import styled, {css} from 'styled-components' // import classnames from 'classnames' -import {get} from '../constants' +import {get, COMMON} from '../constants' // import theme from '../theme' // import elementType from './utils/elementType' // import Link from './Link' @@ -16,35 +16,37 @@ const Page = styled.a` margin-left: -1px; font-size: 13px; font-style: normal; - font-weight: ${get('fontWeights.bold')}; - color: ${get('pagination.colors.link')}; + font-weight: ${get('pagination.fontWeight')}; + color: ${get('pagination.colors.normal.fg')}; white-space: nowrap; vertical-align: middle; cursor: pointer; user-select: none; - background: ${get('white')}; - border: ${get('borders.1')} ${get('pagination.colors.border')}; + background: ${get('pagination.colors.normal.bg')}; + border: ${get('borders.1')} ${get('pagination.colors.normal.border')}; text-decoration: none; &:first-child { margin-left: 0; - border-top-left-radius: ${get('radii.1')}; - border-bottom-left-radius: ${get('radii.1')}; + border-top-left-radius: ${get('pagination.borderRadius')}; + border-bottom-left-radius: ${get('pagination.borderRadius')}; } &:last-child { - border-top-right-radius: ${get('radii.1')}; - border-bottom-right-radius: ${get('radii.1')}; + border-top-right-radius: ${get('pagination.borderRadius')}; + border-bottom-right-radius: ${get('pagination.borderRadius')}; } ${props => !props.selected && + !props.disabled && css` &:hover, &:focus { z-index: 2; + color: ${get('pagination.colors.hover.fg')}; background-color: ${get('pagination.colors.hover.bg')}; - border-color: ${get('pagination.colors.border')}; + border-color: ${get('pagination.colors.hover.border')}; } `} @@ -52,7 +54,7 @@ const Page = styled.a` props.selected && css` z-index: 3; - color: ${get('colors.white')}; + color: ${get('pagination.colors.selected.fg')}; background-color: ${get('pagination.colors.selected.bg')}; border-color: ${get('pagination.colors.selected.border')}; `} @@ -63,10 +65,14 @@ const Page = styled.a` color: ${get('pagination.colors.disabled.fg')}; cursor: default; background-color: ${get('pagination.colors.disabled.bg')}; + border-color: ${get('pagination.colors.disabled.border')}; `} + + ${COMMON}; ` function usePaginationPages({ + theme, pageCount, currentPage, onPageChange, @@ -85,7 +91,7 @@ function usePaginationPages({ return model.map(page => { const {props, key, content} = buildComponentData(page, hrefBuilder, pageChange(page.num)) return ( - + {content} ) @@ -113,6 +119,7 @@ function Pagination({ ...rest }) { const pageElements = usePaginationPages({ + theme, pageCount, currentPage, onPageChange, @@ -122,8 +129,10 @@ function Pagination({ surroundingPageCount }) return ( - - {pageElements} + + + {pageElements} + ) } diff --git a/src/theme.js b/src/theme.js index 5e16ef6a510..a869d864172 100644 --- a/src/theme.js +++ b/src/theme.js @@ -1,6 +1,7 @@ import {black, white, gray, blue, green, orange, purple, red, yellow} from 'primer-colors' import {lineHeights} from 'primer-typography' +// General const colors = { bodytext: gray[9], black, @@ -52,6 +53,59 @@ const colors = { accent: orange[5] } +const breakpoints = ['544px', '768px', '1012px', '1280px'] + +const maxWidths = { + small: '544px', + medium: '768px', + large: '1012px', + xlarge: '1280px' +} + +const fonts = { + normal: fontStack([ + '-apple-system', + 'BlinkMacSystemFont', + 'Segoe UI', + 'Helvetica', + 'Arial', + 'sans-serif', + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol' + ]), + mono: fontStack(['SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo', 'Courier', 'monospace']) +} + +const fontWeights = { + light: 300, + normal: 400, + semibold: 500, + bold: 600 +} + +const borders = [0, '1px solid'] + +const radii = ['0', '3px', '6px', '100px'] + +const shadows = { + small: '0 1px 1px rgba(27, 31, 35, 0.1)', + medium: '0 1px 5px rgba(27, 31, 35, 0.15)', + large: '0 1px 15px rgba(27, 31, 35, 0.15)', + 'extra-large': '0 10px 50px rgba(27, 31, 35, 0.07)', + formControl: 'inset 0px 2px 0px rgba(225, 228, 232, 0.2)', + formControlDisabled: 'inset 0px 2px 0px rgba(220, 227, 237, 0.3)', + formControlFocus: 'rgba(3, 102, 214, 0.3) 0px 0px 0px 0.2em', + primaryShadow: '0px 1px 0px rgba(20, 70, 32, 0.1), inset 0px 2px 0px rgba(255, 255, 255, 0.03)', + primaryActiveShadow: 'inset 0px 1px 0px rgba(20, 70, 32, 0.2)' +} + +const fontSizes = ['12px', '14px', '16px', '20px', '24px', '32px', '40px', '48px'] + +const space = ['0', '4px', '8px', '16px', '24px', '32px', '40px', '48px', '64px', '80px', '96px', '112px', '128px'] + +// Components + const buttons = { default: { color: { @@ -159,68 +213,47 @@ const popovers = { } const pagination = { + fontWeight: fontWeights.bold, + borderRadius: radii[1], colors: { - border: colors.border.gray, - link: colors.blue[5], + normal: { + fg: colors.blue[5], + bg: colors.white, + border: colors.border.gray + }, disabled: { fg: colors.gray[3], - bg: colors.gray[0] + bg: colors.gray[0], + border: colors.border.gray }, hover: { - bg: colors.gray[1] + fg: colors.blue[5], + bg: colors.gray[1], + border: colors.border.gray }, selected: { - border: colors.blue[5], - bg: colors.blue[5] + fg: colors.white, + bg: colors.blue[5], + border: colors.blue[5] } } } const theme = { - breakpoints: ['544px', '768px', '1012px', '1280px'], - maxWidths: { - small: '544px', - medium: '768px', - large: '1012px', - xlarge: '1280px' - }, - fonts: { - normal: fontStack([ - '-apple-system', - 'BlinkMacSystemFont', - 'Segoe UI', - 'Helvetica', - 'Arial', - 'sans-serif', - 'Apple Color Emoji', - 'Segoe UI Emoji', - 'Segoe UI Symbol' - ]), - mono: fontStack(['SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo', 'Courier', 'monospace']) - }, - fontWeights: { - light: 300, - normal: 400, - semibold: 500, - bold: 600 - }, + // General + borders, + breakpoints, colors, - borders: [0, '1px solid'], - fontSizes: ['12px', '14px', '16px', '20px', '24px', '32px', '40px', '48px'], + fonts, + fontSizes, + fontWeights, lineHeights, - radii: ['0', '3px', '6px', '100px'], - shadows: { - small: '0 1px 1px rgba(27, 31, 35, 0.1)', - medium: '0 1px 5px rgba(27, 31, 35, 0.15)', - large: '0 1px 15px rgba(27, 31, 35, 0.15)', - 'extra-large': '0 10px 50px rgba(27, 31, 35, 0.07)', - formControl: 'inset 0px 2px 0px rgba(225, 228, 232, 0.2)', - formControlDisabled: 'inset 0px 2px 0px rgba(220, 227, 237, 0.3)', - formControlFocus: 'rgba(3, 102, 214, 0.3) 0px 0px 0px 0.2em', - primaryShadow: '0px 1px 0px rgba(20, 70, 32, 0.1), inset 0px 2px 0px rgba(255, 255, 255, 0.03)', - primaryActiveShadow: 'inset 0px 1px 0px rgba(20, 70, 32, 0.2)' - }, - space: ['0', '4px', '8px', '16px', '24px', '32px', '40px', '48px', '64px', '80px', '96px', '112px', '128px'], + maxWidths, + radii, + shadows, + space, + + // Components buttons, pagination, popovers From 42c31eb935d3c521cf6da292162f0f85fe58d82d Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 12:04:36 -0700 Subject: [PATCH 11/22] Add props and theme info for Pagination --- docs/content/Pagination.md | 56 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/docs/content/Pagination.md b/docs/content/Pagination.md index 9b148d265b9..434aa99acd0 100644 --- a/docs/content/Pagination.md +++ b/docs/content/Pagination.md @@ -11,7 +11,7 @@ The pagination component only requires two properties to render: `pageCount`, wh ```jsx live e.preventDefault()} /> @@ -71,7 +71,7 @@ type HrefBuilder = (page: number) => string The last URL clicked was: {lastUrl} ``` + +## 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 | | 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: { + fontWeight, + borderRadius, + colors: { + normal: { + fg, + bg, + border + }, + disabled: { + fg, + bg, + border + }, + hover: { + fg, + bg, + border + }, + selected: { + fg, + bg, + border + } + } + } +} +``` From c0dc5fb2c61cb26f3119d6e142b73a6a8e04f89a Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 12:08:59 -0700 Subject: [PATCH 12/22] Fill out types and props --- docs/content/Pagination.md | 2 +- index.d.ts | 20 ++++++++++++++++++++ src/Pagination/Pagination.js | 8 +++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/content/Pagination.md b/docs/content/Pagination.md index 434aa99acd0..aa98eb7c1ab 100644 --- a/docs/content/Pagination.md +++ b/docs/content/Pagination.md @@ -151,7 +151,7 @@ Pagination components get `COMMON` system props. Read our [System Props](/system | 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 | | Called with event and page number when a page is clicked. | +| 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. | diff --git a/index.d.ts b/index.d.ts index e37386e440a..56b6c2c792f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -217,6 +217,22 @@ declare module '@primer/components' { export const Link: React.FunctionComponent + export type PaginationHrefBuilder = (page: number) => string + + export type PaginationPageChangeCallback = (e: React.MouseEvent, page: number) => void + + export interface PaginationProps extends CommonProps { + currentPage: number + hrefBuilder?: PaginationHrefBuilder + marginPageCount?: number + onPageChange?: PaginationPageChangeCallback + pageCount: number + showPages?: boolean + surroundingPageCount?: number + } + + export const Pagination: React.FunctionComponent + export interface PointerBoxProps extends CommonProps, LayoutProps, BorderBoxProps { caret?: string } @@ -531,6 +547,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 diff --git a/src/Pagination/Pagination.js b/src/Pagination/Pagination.js index 394638ddeb4..5c2ff2c7408 100644 --- a/src/Pagination/Pagination.js +++ b/src/Pagination/Pagination.js @@ -1,11 +1,8 @@ import React from 'react' import PropTypes from 'prop-types' import styled, {css} from 'styled-components' -// import classnames from 'classnames' import {get, COMMON} from '../constants' -// import theme from '../theme' -// import elementType from './utils/elementType' -// import Link from './Link' +import theme from '../theme' import Box from '../Box' import {buildPaginationModel, buildComponentData} from './model' @@ -158,7 +155,8 @@ Pagination.defaultProps = { marginPageCount: 1, onPageChange: noop, showPages: true, - surroundingPageCount: 2 + surroundingPageCount: 2, + theme } export default Pagination From e712849cff25c8a22c480e58a1c2474834723db7 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 12:23:38 -0700 Subject: [PATCH 13/22] Update tests and lint --- src/Pagination/Pagination.js | 3 +- src/Pagination/model.js | 8 - src/__tests__/Pagination/Pagination.js | 41 ++++ .../__snapshots__/Pagination.js.snap | 216 ++++++++++++++++++ src/utils/test-matchers.js | 8 +- 5 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 src/__tests__/Pagination/Pagination.js create mode 100644 src/__tests__/Pagination/__snapshots__/Pagination.js.snap diff --git a/src/Pagination/Pagination.js b/src/Pagination/Pagination.js index 5c2ff2c7408..6d9a33ca5a3 100644 --- a/src/Pagination/Pagination.js +++ b/src/Pagination/Pagination.js @@ -147,7 +147,8 @@ Pagination.propTypes = { onPageChange: PropTypes.func, pageCount: PropTypes.number.isRequired, showPages: PropTypes.bool, - surroundingPageCount: PropTypes.number + surroundingPageCount: PropTypes.number, + ...COMMON.propTypes } Pagination.defaultProps = { diff --git a/src/Pagination/model.js b/src/Pagination/model.js index 570015d5a4d..5f5ba68f06e 100644 --- a/src/Pagination/model.js +++ b/src/Pagination/model.js @@ -1,9 +1,3 @@ -const DEBUG = false - -const debug = (...args) => { - if (DEBUG) console.log(...args) -} - export function buildPaginationModel(pageCount, currentPage, showPages, marginPageCount, surroundingPageCount) { const pages = [] @@ -59,7 +53,6 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa } const sorted = [...pageNums.values()].sort((a, b) => a - b) - debug('sorted', sorted) for (const [idx, num] of sorted.entries()) { const selected = num === currentPage if (idx === 0) { @@ -111,7 +104,6 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa } } - debug(pages) const prev = {type: 'PREV', num: currentPage - 1, disabled: currentPage === 1} const next = {type: 'NEXT', num: currentPage + 1, disabled: currentPage === pageCount} return [prev, ...pages, next] diff --git a/src/__tests__/Pagination/Pagination.js b/src/__tests__/Pagination/Pagination.js new file mode 100644 index 00000000000..0be0c01aa4d --- /dev/null +++ b/src/__tests__/Pagination/Pagination.js @@ -0,0 +1,41 @@ +import React from 'react' +import Pagination from '../../Pagination' +import {render} from '../../utils/testing' +import {COMMON} from '../../constants' +import {render as HTMLRender, cleanup} from '@testing-library/react' +import {axe, toHaveNoViolations} from 'jest-axe' +import 'babel-polyfill' +expect.extend(toHaveNoViolations) + +const reqProps = {pageCount: 10, currentPage: 1} +const comp = + +describe('Pagination', () => { + it('implements common system props', () => { + expect(Pagination).toImplementSystemProps(COMMON) + }) + + it('should have no axe violations', async () => { + const {container} = HTMLRender(comp) + const results = await axe(container, { + rules: { + // The skip-link rule has to do with entire documents + // and is not relevant to this component. + // See https://dequeuniversity.com/rules/axe/3.3/skip-link?application=axeAPI + 'skip-link': { + enabled: false + } + } + }) + expect(results).toHaveNoViolations() + cleanup() + }) + + it('has default theme', () => { + expect(comp).toSetDefaultTheme() + }) + + it('matches snapshot', () => { + expect(render(comp)).toMatchSnapshot() + }) +}) diff --git a/src/__tests__/Pagination/__snapshots__/Pagination.js.snap b/src/__tests__/Pagination/__snapshots__/Pagination.js.snap new file mode 100644 index 00000000000..04172d2115d --- /dev/null +++ b/src/__tests__/Pagination/__snapshots__/Pagination.js.snap @@ -0,0 +1,216 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pagination matches snapshot 1`] = ` +.c1 { + display: inline-block; +} + +.c2 { + position: relative; + float: left; + padding: 7px 12px; + margin-left: -1px; + font-size: 13px; + font-style: normal; + font-weight: 600; + color: #0366d6; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background: #fff; + border: 1px solid #e1e4e8; + -webkit-text-decoration: none; + text-decoration: none; + color: #d1d5da; + cursor: default; + background-color: #fafbfc; + border-color: #e1e4e8; +} + +.c2:first-child { + margin-left: 0; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.c2:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.c3 { + position: relative; + float: left; + padding: 7px 12px; + margin-left: -1px; + font-size: 13px; + font-style: normal; + font-weight: 600; + color: #0366d6; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background: #fff; + border: 1px solid #e1e4e8; + -webkit-text-decoration: none; + text-decoration: none; + z-index: 3; + color: #fff; + background-color: #0366d6; + border-color: #0366d6; +} + +.c3:first-child { + margin-left: 0; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.c3:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.c4 { + position: relative; + float: left; + padding: 7px 12px; + margin-left: -1px; + font-size: 13px; + font-style: normal; + font-weight: 600; + color: #0366d6; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background: #fff; + border: 1px solid #e1e4e8; + -webkit-text-decoration: none; + text-decoration: none; +} + +.c4:first-child { + margin-left: 0; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.c4:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.c4:hover, +.c4:focus { + z-index: 2; + color: #0366d6; + background-color: #f6f8fa; + border-color: #e1e4e8; +} + +.c0 { + margin-top: 20px; + margin-bottom: 15px; + text-align: center; +} + + +`; diff --git a/src/utils/test-matchers.js b/src/utils/test-matchers.js index 0aea935954a..ff70965ae10 100644 --- a/src/utils/test-matchers.js +++ b/src/utils/test-matchers.js @@ -52,7 +52,13 @@ expect.extend({ }, toSetDefaultTheme(Component) { - const wrapper = mount() + let comp + if (Component.type) { + comp = Component + } else { + comp = + } + const wrapper = mount(comp) const pass = this.equals(wrapper.prop('theme'), theme) return { pass, From 94a6d312ed5eae6b2d0c3ba2550c506b8eb7fad5 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 12:27:13 -0700 Subject: [PATCH 14/22] Update snapshot from theme changes --- .../__snapshots__/BreadcrumbItem.js.snap | 26 +++++++++++++++++++ .../__snapshots__/FilterListItem.js.snap | 26 +++++++++++++++++++ .../__snapshots__/SubNavLink.js.snap | 26 +++++++++++++++++++ .../__snapshots__/UnderlineNavLink.js.snap | 26 +++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/src/__tests__/__snapshots__/BreadcrumbItem.js.snap b/src/__tests__/__snapshots__/BreadcrumbItem.js.snap index 1d89748a790..09d1337d8c8 100644 --- a/src/__tests__/__snapshots__/BreadcrumbItem.js.snap +++ b/src/__tests__/__snapshots__/BreadcrumbItem.js.snap @@ -292,6 +292,32 @@ exports[`Breadcrumb.Item renders the given "as" prop 1`] = ` "small": "544px", "xlarge": "1280px", }, + "pagination": Object { + "borderRadius": "3px", + "colors": Object { + "disabled": Object { + "bg": "#fafbfc", + "border": "#e1e4e8", + "fg": "#d1d5da", + }, + "hover": Object { + "bg": "#f6f8fa", + "border": "#e1e4e8", + "fg": "#0366d6", + }, + "normal": Object { + "bg": "#fff", + "border": "#e1e4e8", + "fg": "#0366d6", + }, + "selected": Object { + "bg": "#0366d6", + "border": "#0366d6", + "fg": "#fff", + }, + }, + "fontWeight": 600, + }, "popovers": Object { "colors": Object { "caret": "rgba(27, 31, 35, 0.15)", diff --git a/src/__tests__/__snapshots__/FilterListItem.js.snap b/src/__tests__/__snapshots__/FilterListItem.js.snap index 6fb22ed9084..6a4efe3a29a 100644 --- a/src/__tests__/__snapshots__/FilterListItem.js.snap +++ b/src/__tests__/__snapshots__/FilterListItem.js.snap @@ -305,6 +305,32 @@ exports[`FilterList.Item renders the given "as" prop 1`] = ` "small": "544px", "xlarge": "1280px", }, + "pagination": Object { + "borderRadius": "3px", + "colors": Object { + "disabled": Object { + "bg": "#fafbfc", + "border": "#e1e4e8", + "fg": "#d1d5da", + }, + "hover": Object { + "bg": "#f6f8fa", + "border": "#e1e4e8", + "fg": "#0366d6", + }, + "normal": Object { + "bg": "#fff", + "border": "#e1e4e8", + "fg": "#0366d6", + }, + "selected": Object { + "bg": "#0366d6", + "border": "#0366d6", + "fg": "#fff", + }, + }, + "fontWeight": 600, + }, "popovers": Object { "colors": Object { "caret": "rgba(27, 31, 35, 0.15)", diff --git a/src/__tests__/__snapshots__/SubNavLink.js.snap b/src/__tests__/__snapshots__/SubNavLink.js.snap index c18f1a79131..f85ecc653ee 100644 --- a/src/__tests__/__snapshots__/SubNavLink.js.snap +++ b/src/__tests__/__snapshots__/SubNavLink.js.snap @@ -332,6 +332,32 @@ exports[`SubNav.Link renders the given "as" prop 1`] = ` "small": "544px", "xlarge": "1280px", }, + "pagination": Object { + "borderRadius": "3px", + "colors": Object { + "disabled": Object { + "bg": "#fafbfc", + "border": "#e1e4e8", + "fg": "#d1d5da", + }, + "hover": Object { + "bg": "#f6f8fa", + "border": "#e1e4e8", + "fg": "#0366d6", + }, + "normal": Object { + "bg": "#fff", + "border": "#e1e4e8", + "fg": "#0366d6", + }, + "selected": Object { + "bg": "#0366d6", + "border": "#0366d6", + "fg": "#fff", + }, + }, + "fontWeight": 600, + }, "popovers": Object { "colors": Object { "caret": "rgba(27, 31, 35, 0.15)", diff --git a/src/__tests__/__snapshots__/UnderlineNavLink.js.snap b/src/__tests__/__snapshots__/UnderlineNavLink.js.snap index 7060a25b054..daf4462a201 100644 --- a/src/__tests__/__snapshots__/UnderlineNavLink.js.snap +++ b/src/__tests__/__snapshots__/UnderlineNavLink.js.snap @@ -309,6 +309,32 @@ exports[`UnderlineNav.Link renders the given "as" prop 1`] = ` "small": "544px", "xlarge": "1280px", }, + "pagination": Object { + "borderRadius": "3px", + "colors": Object { + "disabled": Object { + "bg": "#fafbfc", + "border": "#e1e4e8", + "fg": "#d1d5da", + }, + "hover": Object { + "bg": "#f6f8fa", + "border": "#e1e4e8", + "fg": "#0366d6", + }, + "normal": Object { + "bg": "#fff", + "border": "#e1e4e8", + "fg": "#0366d6", + }, + "selected": Object { + "bg": "#0366d6", + "border": "#0366d6", + "fg": "#fff", + }, + }, + "fontWeight": 600, + }, "popovers": Object { "colors": Object { "caret": "rgba(27, 31, 35, 0.15)", From 33eb7d3d61fb5efd4f69468b6610d332b26c6c8d Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 13:24:23 -0700 Subject: [PATCH 15/22] Remove Set usage Thanks IE --- src/Pagination/model.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Pagination/model.js b/src/Pagination/model.js index 5f5ba68f06e..25a3efcdaa4 100644 --- a/src/Pagination/model.js +++ b/src/Pagination/model.js @@ -2,10 +2,10 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa const pages = [] if (showPages) { - const pageNums = new Set() + const pageNums = [] const addPage = n => { if (n >= 1 && n <= pageCount) { - pageNums.add(n) + pageNums.push(n) } } @@ -52,7 +52,10 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa addPage(i) } - const sorted = [...pageNums.values()].sort((a, b) => a - b) + const sorted = pageNums + .slice() + .sort((a, b) => a - b) + .filter((item, idx, ary) => !idx || item !== ary[idx - 1]) for (const [idx, num] of sorted.entries()) { const selected = num === currentPage if (idx === 0) { From 7eb1592fd6d91a011e0825e3a1599eccbc7a16fe Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 13:28:08 -0700 Subject: [PATCH 16/22] Add TS TypeDoc comments for some props --- index.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/index.d.ts b/index.d.ts index 56b6c2c792f..3d6bcdce6f1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -224,10 +224,16 @@ declare module '@primer/components' { export interface PaginationProps extends CommonProps { currentPage: number hrefBuilder?: PaginationHrefBuilder + /** + * How many pages to show on the left and right of the component + */ 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 } From 2b32cf6251f86489d26ae1951eb7688a0e7e896a Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 14:25:07 -0700 Subject: [PATCH 17/22] No Array.prototype.entries in IE --- src/Pagination/model.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Pagination/model.js b/src/Pagination/model.js index 25a3efcdaa4..9a9dda8a166 100644 --- a/src/Pagination/model.js +++ b/src/Pagination/model.js @@ -56,7 +56,8 @@ export function buildPaginationModel(pageCount, currentPage, showPages, marginPa .slice() .sort((a, b) => a - b) .filter((item, idx, ary) => !idx || item !== ary[idx - 1]) - for (const [idx, num] of sorted.entries()) { + for (let idx = 0; idx < sorted.length; idx++) { + const num = sorted[idx] const selected = num === currentPage if (idx === 0) { if (num !== 1) { From d3ded898469d5377184b110231e417750fa0d481 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 14:26:54 -0700 Subject: [PATCH 18/22] Base Pagination font size on theme --- docs/content/Pagination.md | 1 + src/Pagination/Pagination.js | 2 +- src/theme.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/content/Pagination.md b/docs/content/Pagination.md index aa98eb7c1ab..62aeae010ae 100644 --- a/docs/content/Pagination.md +++ b/docs/content/Pagination.md @@ -164,6 +164,7 @@ The following snippet shows the properties in the theme that control the styling { // ... rest of theme ... pagination: { + fontSize, fontWeight, borderRadius, colors: { diff --git a/src/Pagination/Pagination.js b/src/Pagination/Pagination.js index 6d9a33ca5a3..ff9b56a5b7f 100644 --- a/src/Pagination/Pagination.js +++ b/src/Pagination/Pagination.js @@ -11,7 +11,7 @@ const Page = styled.a` float: left; padding: 7px 12px; margin-left: -1px; - font-size: 13px; + font-size: ${get('pagination.fontSize')}; font-style: normal; font-weight: ${get('pagination.fontWeight')}; color: ${get('pagination.colors.normal.fg')}; diff --git a/src/theme.js b/src/theme.js index a869d864172..a845644f864 100644 --- a/src/theme.js +++ b/src/theme.js @@ -213,6 +213,7 @@ const popovers = { } const pagination = { + fontSize: '13px', fontWeight: fontWeights.bold, borderRadius: radii[1], colors: { From 5e0fa02467560096bb8feee1e2c09ebf6fdade12 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 14:29:20 -0700 Subject: [PATCH 19/22] wordsing --- docs/content/Pagination.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/Pagination.md b/docs/content/Pagination.md index 62aeae010ae..2dbe002064d 100644 --- a/docs/content/Pagination.md +++ b/docs/content/Pagination.md @@ -3,7 +3,7 @@ title: Pagination --- import State from '../components/State' -Use the pagination component to create a connected set of links that go to related pages (for example, previous, next, or page numbers). +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 @@ -96,7 +96,7 @@ Two props control how many links are displayed in the pagination container at an /> ``` -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 to the right. 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. +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 From eadc341fd54b668d854e7e0c3aae7142a0690429 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 14:32:00 -0700 Subject: [PATCH 20/22] Update snapshots from theme change --- src/__tests__/__snapshots__/BreadcrumbItem.js.snap | 1 + src/__tests__/__snapshots__/FilterListItem.js.snap | 1 + src/__tests__/__snapshots__/SubNavLink.js.snap | 1 + src/__tests__/__snapshots__/UnderlineNavLink.js.snap | 1 + 4 files changed, 4 insertions(+) diff --git a/src/__tests__/__snapshots__/BreadcrumbItem.js.snap b/src/__tests__/__snapshots__/BreadcrumbItem.js.snap index 09d1337d8c8..8cbdfdc4c54 100644 --- a/src/__tests__/__snapshots__/BreadcrumbItem.js.snap +++ b/src/__tests__/__snapshots__/BreadcrumbItem.js.snap @@ -316,6 +316,7 @@ exports[`Breadcrumb.Item renders the given "as" prop 1`] = ` "fg": "#fff", }, }, + "fontSize": "13px", "fontWeight": 600, }, "popovers": Object { diff --git a/src/__tests__/__snapshots__/FilterListItem.js.snap b/src/__tests__/__snapshots__/FilterListItem.js.snap index 6a4efe3a29a..1bcbcca165a 100644 --- a/src/__tests__/__snapshots__/FilterListItem.js.snap +++ b/src/__tests__/__snapshots__/FilterListItem.js.snap @@ -329,6 +329,7 @@ exports[`FilterList.Item renders the given "as" prop 1`] = ` "fg": "#fff", }, }, + "fontSize": "13px", "fontWeight": 600, }, "popovers": Object { diff --git a/src/__tests__/__snapshots__/SubNavLink.js.snap b/src/__tests__/__snapshots__/SubNavLink.js.snap index f85ecc653ee..792bb254410 100644 --- a/src/__tests__/__snapshots__/SubNavLink.js.snap +++ b/src/__tests__/__snapshots__/SubNavLink.js.snap @@ -356,6 +356,7 @@ exports[`SubNav.Link renders the given "as" prop 1`] = ` "fg": "#fff", }, }, + "fontSize": "13px", "fontWeight": 600, }, "popovers": Object { diff --git a/src/__tests__/__snapshots__/UnderlineNavLink.js.snap b/src/__tests__/__snapshots__/UnderlineNavLink.js.snap index daf4462a201..da0456fdab0 100644 --- a/src/__tests__/__snapshots__/UnderlineNavLink.js.snap +++ b/src/__tests__/__snapshots__/UnderlineNavLink.js.snap @@ -333,6 +333,7 @@ exports[`UnderlineNav.Link renders the given "as" prop 1`] = ` "fg": "#fff", }, }, + "fontSize": "13px", "fontWeight": 600, }, "popovers": Object { From f58e2d759896882a223e2c402fda21e37b797224 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 15:04:23 -0700 Subject: [PATCH 21/22] Fix tests and snapshots --- src/__tests__/BorderBox.js | 2 +- .../__snapshots__/BreadcrumbItem.js.snap | 20 +++++++++---------- .../__snapshots__/CircleBadge.js.snap | 8 ++++---- .../__snapshots__/FilterListItem.js.snap | 20 +++++++++---------- .../__snapshots__/SubNavLink.js.snap | 20 +++++++++---------- .../__snapshots__/UnderlineNavLink.js.snap | 20 +++++++++---------- 6 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/__tests__/BorderBox.js b/src/__tests__/BorderBox.js index 67a2c3becc8..9304f6ab166 100644 --- a/src/__tests__/BorderBox.js +++ b/src/__tests__/BorderBox.js @@ -40,6 +40,6 @@ describe('BorderBox', () => { // the test returns the box shadow value without spaces, so had to manually provide the expected string here it('renders box shadow', () => { - expect(render()).toHaveStyleRule('box-shadow', '0 1px 0 rgba(149,157,165,0.1)') + expect(render()).toHaveStyleRule('box-shadow', '0 1px 1px rgba(27,31,35,0.1)') }) }) diff --git a/src/__tests__/__snapshots__/BreadcrumbItem.js.snap b/src/__tests__/__snapshots__/BreadcrumbItem.js.snap index 04bbdd2a3f6..4d31c8bffb4 100644 --- a/src/__tests__/__snapshots__/BreadcrumbItem.js.snap +++ b/src/__tests__/__snapshots__/BreadcrumbItem.js.snap @@ -286,12 +286,6 @@ exports[`Breadcrumb.Item renders the given "as" prop 1`] = ` "condensedUltra": 1, "default": 1.5, }, - "maxWidths": Object { - "large": "1012px", - "medium": "768px", - "small": "544px", - "xlarge": "1280px", - }, "pagination": Object { "borderRadius": "3px", "colors": Object { @@ -331,15 +325,21 @@ exports[`Breadcrumb.Item renders the given "as" prop 1`] = ` "100px", ], "shadows": Object { - "extra-large": "0 12px 48px rgba(149, 157, 165, 0.3)", + "extra-large": "0 10px 50px rgba(27, 31, 35, 0.07)", "formControl": "inset 0px 2px 0px rgba(225, 228, 232, 0.2)", "formControlDisabled": "inset 0px 2px 0px rgba(220, 227, 237, 0.3)", "formControlFocus": "rgba(3, 102, 214, 0.3) 0px 0px 0px 0.2em", - "large": "0 8px 24px rgba(149, 157, 165, 0.2)", - "medium": "0 3px 6px rgba(149, 157, 165, 0.15)", + "large": "0 1px 15px rgba(27, 31, 35, 0.15)", + "medium": "0 1px 5px rgba(27, 31, 35, 0.15)", "primaryActiveShadow": "inset 0px 1px 0px rgba(20, 70, 32, 0.2)", "primaryShadow": "0px 1px 0px rgba(20, 70, 32, 0.1), inset 0px 2px 0px rgba(255, 255, 255, 0.03)", - "small": "0 1px 0 rgba(149, 157, 165, 0.1)", + "small": "0 1px 1px rgba(27, 31, 35, 0.1)", + }, + "sizes": Object { + "large": "1012px", + "medium": "768px", + "small": "544px", + "xlarge": "1280px", }, "space": Array [ "0", diff --git a/src/__tests__/__snapshots__/CircleBadge.js.snap b/src/__tests__/__snapshots__/CircleBadge.js.snap index ccce0cf64b7..e35e2e949b2 100644 --- a/src/__tests__/__snapshots__/CircleBadge.js.snap +++ b/src/__tests__/__snapshots__/CircleBadge.js.snap @@ -16,7 +16,7 @@ exports[`CircleBadge respects "as" prop 1`] = ` justify-content: center; background-color: #fff; border-radius: 50%; - box-shadow: 0 3px 6px rgba(149,157,165,0.15); + box-shadow: 0 1px 5px rgba(27,31,35,0.15); width: 96px; height: 96px; } @@ -42,7 +42,7 @@ exports[`CircleBadge respects the inline prop 1`] = ` justify-content: center; background-color: #fff; border-radius: 50%; - box-shadow: 0 3px 6px rgba(149,157,165,0.15); + box-shadow: 0 1px 5px rgba(27,31,35,0.15); width: 96px; height: 96px; } @@ -68,7 +68,7 @@ exports[`CircleBadge respects the variant prop 1`] = ` justify-content: center; background-color: #fff; border-radius: 50%; - box-shadow: 0 3px 6px rgba(149,157,165,0.15); + box-shadow: 0 1px 5px rgba(27,31,35,0.15); width: 128px; height: 128px; } @@ -94,7 +94,7 @@ exports[`CircleBadge uses the size prop to override the variant prop 1`] = ` justify-content: center; background-color: #fff; border-radius: 50%; - box-shadow: 0 3px 6px rgba(149,157,165,0.15); + box-shadow: 0 1px 5px rgba(27,31,35,0.15); width: 20px; height: 20px; } diff --git a/src/__tests__/__snapshots__/FilterListItem.js.snap b/src/__tests__/__snapshots__/FilterListItem.js.snap index 0ea54d6d40d..46b6de7b6c7 100644 --- a/src/__tests__/__snapshots__/FilterListItem.js.snap +++ b/src/__tests__/__snapshots__/FilterListItem.js.snap @@ -299,12 +299,6 @@ exports[`FilterList.Item renders the given "as" prop 1`] = ` "condensedUltra": 1, "default": 1.5, }, - "maxWidths": Object { - "large": "1012px", - "medium": "768px", - "small": "544px", - "xlarge": "1280px", - }, "pagination": Object { "borderRadius": "3px", "colors": Object { @@ -344,15 +338,21 @@ exports[`FilterList.Item renders the given "as" prop 1`] = ` "100px", ], "shadows": Object { - "extra-large": "0 12px 48px rgba(149, 157, 165, 0.3)", + "extra-large": "0 10px 50px rgba(27, 31, 35, 0.07)", "formControl": "inset 0px 2px 0px rgba(225, 228, 232, 0.2)", "formControlDisabled": "inset 0px 2px 0px rgba(220, 227, 237, 0.3)", "formControlFocus": "rgba(3, 102, 214, 0.3) 0px 0px 0px 0.2em", - "large": "0 8px 24px rgba(149, 157, 165, 0.2)", - "medium": "0 3px 6px rgba(149, 157, 165, 0.15)", + "large": "0 1px 15px rgba(27, 31, 35, 0.15)", + "medium": "0 1px 5px rgba(27, 31, 35, 0.15)", "primaryActiveShadow": "inset 0px 1px 0px rgba(20, 70, 32, 0.2)", "primaryShadow": "0px 1px 0px rgba(20, 70, 32, 0.1), inset 0px 2px 0px rgba(255, 255, 255, 0.03)", - "small": "0 1px 0 rgba(149, 157, 165, 0.1)", + "small": "0 1px 1px rgba(27, 31, 35, 0.1)", + }, + "sizes": Object { + "large": "1012px", + "medium": "768px", + "small": "544px", + "xlarge": "1280px", }, "space": Array [ "0", diff --git a/src/__tests__/__snapshots__/SubNavLink.js.snap b/src/__tests__/__snapshots__/SubNavLink.js.snap index 1a58b25c02b..a743fc0410d 100644 --- a/src/__tests__/__snapshots__/SubNavLink.js.snap +++ b/src/__tests__/__snapshots__/SubNavLink.js.snap @@ -326,12 +326,6 @@ exports[`SubNav.Link renders the given "as" prop 1`] = ` "condensedUltra": 1, "default": 1.5, }, - "maxWidths": Object { - "large": "1012px", - "medium": "768px", - "small": "544px", - "xlarge": "1280px", - }, "pagination": Object { "borderRadius": "3px", "colors": Object { @@ -371,15 +365,21 @@ exports[`SubNav.Link renders the given "as" prop 1`] = ` "100px", ], "shadows": Object { - "extra-large": "0 12px 48px rgba(149, 157, 165, 0.3)", + "extra-large": "0 10px 50px rgba(27, 31, 35, 0.07)", "formControl": "inset 0px 2px 0px rgba(225, 228, 232, 0.2)", "formControlDisabled": "inset 0px 2px 0px rgba(220, 227, 237, 0.3)", "formControlFocus": "rgba(3, 102, 214, 0.3) 0px 0px 0px 0.2em", - "large": "0 8px 24px rgba(149, 157, 165, 0.2)", - "medium": "0 3px 6px rgba(149, 157, 165, 0.15)", + "large": "0 1px 15px rgba(27, 31, 35, 0.15)", + "medium": "0 1px 5px rgba(27, 31, 35, 0.15)", "primaryActiveShadow": "inset 0px 1px 0px rgba(20, 70, 32, 0.2)", "primaryShadow": "0px 1px 0px rgba(20, 70, 32, 0.1), inset 0px 2px 0px rgba(255, 255, 255, 0.03)", - "small": "0 1px 0 rgba(149, 157, 165, 0.1)", + "small": "0 1px 1px rgba(27, 31, 35, 0.1)", + }, + "sizes": Object { + "large": "1012px", + "medium": "768px", + "small": "544px", + "xlarge": "1280px", }, "space": Array [ "0", diff --git a/src/__tests__/__snapshots__/UnderlineNavLink.js.snap b/src/__tests__/__snapshots__/UnderlineNavLink.js.snap index 65e72e10f4c..a0c09bb9395 100644 --- a/src/__tests__/__snapshots__/UnderlineNavLink.js.snap +++ b/src/__tests__/__snapshots__/UnderlineNavLink.js.snap @@ -303,12 +303,6 @@ exports[`UnderlineNav.Link renders the given "as" prop 1`] = ` "condensedUltra": 1, "default": 1.5, }, - "maxWidths": Object { - "large": "1012px", - "medium": "768px", - "small": "544px", - "xlarge": "1280px", - }, "pagination": Object { "borderRadius": "3px", "colors": Object { @@ -348,15 +342,21 @@ exports[`UnderlineNav.Link renders the given "as" prop 1`] = ` "100px", ], "shadows": Object { - "extra-large": "0 12px 48px rgba(149, 157, 165, 0.3)", + "extra-large": "0 10px 50px rgba(27, 31, 35, 0.07)", "formControl": "inset 0px 2px 0px rgba(225, 228, 232, 0.2)", "formControlDisabled": "inset 0px 2px 0px rgba(220, 227, 237, 0.3)", "formControlFocus": "rgba(3, 102, 214, 0.3) 0px 0px 0px 0.2em", - "large": "0 8px 24px rgba(149, 157, 165, 0.2)", - "medium": "0 3px 6px rgba(149, 157, 165, 0.15)", + "large": "0 1px 15px rgba(27, 31, 35, 0.15)", + "medium": "0 1px 5px rgba(27, 31, 35, 0.15)", "primaryActiveShadow": "inset 0px 1px 0px rgba(20, 70, 32, 0.2)", "primaryShadow": "0px 1px 0px rgba(20, 70, 32, 0.1), inset 0px 2px 0px rgba(255, 255, 255, 0.03)", - "small": "0 1px 0 rgba(149, 157, 165, 0.1)", + "small": "0 1px 1px rgba(27, 31, 35, 0.1)", + }, + "sizes": Object { + "large": "1012px", + "medium": "768px", + "small": "544px", + "xlarge": "1280px", }, "space": Array [ "0", From bb9c5ede21bfa75e4e97a6d91324568c34d2e6c2 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 1 Apr 2020 15:10:10 -0700 Subject: [PATCH 22/22] Remove unused variable --- src/theme.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/theme.js b/src/theme.js index a663a9e3647..cccbade424e 100644 --- a/src/theme.js +++ b/src/theme.js @@ -55,13 +55,6 @@ const colors = { const breakpoints = ['544px', '768px', '1012px', '1280px'] -const maxWidths = { - small: '544px', - medium: '768px', - large: '1012px', - xlarge: '1280px' -} - const fonts = { normal: fontStack([ '-apple-system',