Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions docs/content/Dialog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,26 @@ The dialog component is used for all modals. It renders on top of the rest of th

**Note:** You'll need to manage the `isOpen` state in a wrapper component of your own. For documentation purposes only we've created a mock `State` to handle this, but you should handle the state in the component you consume `Dialog` in or in a wrapper component.

```jsx live

<State default={false}>
{([isOpen, setIsOpen]) => (
<>
<Button onClick={() => setIsOpen(true)}>Open</Button>
<Dialog isOpen={isOpen} onDismiss={() => setIsOpen(false)}>
<Dialog.Header>Title</Dialog.Header>
<Dialog title="Title" isOpen={isOpen} onDismiss={() => setIsOpen(false)}>
<Box p={3}>
<Text fontFamily="sans-serif">Some content</Text>
</Box>
</Dialog>
</>
)}
</State>
```

You can also pass any non-text content into the header:

```jsx live
```jsx
<State default={false}>
{([isOpen, setIsOpen]) => (
<>
<Button onClick={() => setIsOpen(true)}>Open</Button>
<Dialog isOpen={isOpen} onDismiss={() => setIsOpen(false)}>
<Dialog.Header>
<Octicon icon={Zap} />
</Dialog.Header>
<Dialog title="Title" isOpen={isOpen} onDismiss={() => setIsOpen(false)}>
<Box p={3}>
<Text fontFamily="sans-serif">Some content</Text>
</Box>
Expand All @@ -44,15 +37,23 @@ You can also pass any non-text content into the header:
</State>
```


You can also pass any React node as the title to override the styling:

```jsx
<Dialog title={<Text fontSize={6}>Title</Text>}>
```

## System props

`Dialog` components get the `COMMON` and `LAYOUT` categories of system props. `Dialog.Header` components get `COMMON`, `LAYOUT`, and `FLEX` system props. Read our [System Props](/system-props) doc page for a full list of available props.
Dialog components get the `COMMON` and `LAYOUT` categories of system props. Read our [System Props](/system-props) doc page for a full list of available props.

## Component props

These props are **all required**.

| Prop name | Type | Description |
| :- | :- | :- |
| isOpen | Boolean | Set whether or not the dialog is shown |
| onDismiss | Function | A user-provided function that should close the dialog |

`Dialog.Header` does not take any non-system props.
| title | String or Node | The title shown in the header |
| isOpen | Boolean | Handles opening and closing the dialog |
| onDismiss | Function | Should close the dialog |
10 changes: 3 additions & 7 deletions docs/content/Heading.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
title: Heading
---

The Heading component will render an html `h2` tag without any default styling. Other heading level elements `h1-h6` are available through use of the `as` prop (see [System Props](/system-props) for additional explanation). Please reference the [w3 recommendations for headings](https://www.w3.org/WAI/tutorials/page-structure/headings/) to ensure your headings provide an accessible experience for screen reader users.

**Attention:** Make sure to include a valid heading element to render a Heading component other than `h2` (`<Heading as="h1">H1 Element</Heading>`).
The Heading component will render an html `h1-6` tag without any default styling. Please reference the [w3 recommendations for headings](https://www.w3.org/WAI/tutorials/page-structure/headings/) to ensure your headings provide an accessible experience for screen reader users.

## Default example
```jsx live
<Heading fontSize={1} mb={2}>H2 heading with fontSize={1}</Heading>
<Heading fontSize={1} mb={2}>With fontSize={1}</Heading>
```

## System props
Expand All @@ -17,6 +15,4 @@ Heading components get `TYPOGRAPHY` and `COMMON` system props. Read our [System

## Component props

| Prop name | Type | Description |
| :-------- | :------ | :----------------------------------------------- |
| as | String | sets the HTML tag for the component |
Heading does not get any additional props other than the system props mentioned above.
194 changes: 194 additions & 0 deletions docs/content/Pagination.md
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
Copy link
Copy Markdown

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


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
}
}
}
}
```
2 changes: 2 additions & 0 deletions docs/src/@primer/gatsby-theme-doctocat/nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
url: /LabelGroup
- title: Link
url: /Link
- title: Pagination
url: /Pagination
- title: PointerBox
url: /PointerBox
- title: Popover
Expand Down
37 changes: 29 additions & 8 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
}

export const Pagination: React.FunctionComponent<PaginationProps>

export interface PointerBoxProps extends CommonProps, LayoutProps, BorderBoxProps {
caret?: string
}
Expand Down Expand Up @@ -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'> {}

Expand Down Expand Up @@ -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
Expand Down
Loading