Skip to content

Commit

Permalink
feat: Spinner component (#1232)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgreif committed May 17, 2021
1 parent 2c3fc9e commit 6800c60
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/metal-days-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/components": minor
---

New `Spinner` Component
38 changes: 38 additions & 0 deletions docs/content/Spinner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: Spinner
status: alpha
---

Use Spinner to let users know that content is being loaded.

## Examples

### Default (Medium)

```jsx live
<Spinner />
```

### Small

```jsx live
<Spinner size="small"/>
```

### Large

```jsx live
<Spinner size="large"/>
```

## System props

Spinner components get `COMMON` system props. Read our [System Props](/system-props) doc page for a full list of available props.

## Component props

StyledOcticon passes all of its props except the common system props down to the [Octicon component](https://github.com/primer/octicons/tree/master/lib/octicons_react#usage), including:

| Name | Type | Default | Description |
| :--- | :------------------------------------- | :------: | :------------------------------------------------------- |
| size | 'small' &#124; 'medium' &#124; 'large' | 'medium' | Sets the uniform `width` and `height` of the SVG element |
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 @@ -103,6 +103,8 @@
url: /SelectMenu
- title: SideNav
url: /SideNav
- title: Spinner
url: /Spinner
- title: StateLabel
url: /StateLabel
- title: StyledOcticon
Expand Down
58 changes: 58 additions & 0 deletions src/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react'
import styled from 'styled-components'
import {COMMON, SystemCommonProps} from './constants'
import sx, {SxProp} from './sx'
import {ComponentProps} from './utils/types'

const sizeMap = {
small: '16px',
medium: '32px',
large: '64px'
}

export interface SpinnerInternalProps {
size?: keyof typeof sizeMap
}

function Spinner({size: sizeKey, ...props}: SpinnerInternalProps) {
const size = (sizeKey && sizeMap[sizeKey]) ?? sizeMap.medium

return (
<svg height={size} width={size} viewBox="0 0 16 16" fill="none" {...props}>
<circle
cx="8"
cy="8"
r="7"
stroke="currentColor"
strokeOpacity="0.25"
strokeWidth="2"
vectorEffect="non-scaling-stroke"
/>
<path
d="M15 8a7.002 7.002 0 00-7-7"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
/>
</svg>
)
}

const StyledSpinner = styled(Spinner)<SystemCommonProps & SxProp>`
@keyframes rotate-keyframes {
100% {
transform: rotate(360deg);
}
}
animation: rotate-keyframes 1s linear infinite;
${COMMON}
${sx}
`

StyledSpinner.displayName = 'Spinner'

export type SpinnerProps = ComponentProps<typeof StyledSpinner>
export default StyledSpinner
44 changes: 44 additions & 0 deletions src/__tests__/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'
import {Spinner, SpinnerProps} from '..'
import {behavesAsComponent, checkExports} 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)

describe('Spinner', () => {
afterEach(() => {
cleanup()
})

behavesAsComponent({
Component: Spinner,
systemPropArray: [COMMON]
})

checkExports('Spinner', {
default: Spinner
})

it('should have no axe violations', async () => {
const {container} = HTMLRender(<Spinner />)
const results = await axe(container)
expect(results).toHaveNoViolations()
})

it('should respect size arguments', () => {
const expectSize = (input: SpinnerProps['size'] | undefined, expectedSize: string) => {
const {container} = HTMLRender(<Spinner size={input} />)
const svg = container.querySelector('svg')!
expect(svg.getAttribute('height')).toEqual(expectedSize)
expect(svg.getAttribute('width')).toEqual(expectedSize)
}

// default: medium
expectSize(undefined, '32px')
expectSize('small', '16px')
expectSize('medium', '32px')
expectSize('large', '64px')
})
})
33 changes: 33 additions & 0 deletions src/__tests__/__snapshots__/Spinner.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Spinner renders consistently 1`] = `
.c0 {
-webkit-animation: rotate-keyframes 1s linear infinite;
animation: rotate-keyframes 1s linear infinite;
}
<svg
className="c0"
fill="none"
height="32px"
viewBox="0 0 16 16"
width="32px"
>
<circle
cx="8"
cy="8"
r="7"
stroke="currentColor"
strokeOpacity="0.25"
strokeWidth="2"
vectorEffect="non-scaling-stroke"
/>
<path
d="M15 8a7.002 7.002 0 00-7-7"
stroke="currentColor"
strokeLinecap="round"
strokeWidth="2"
vectorEffect="non-scaling-stroke"
/>
</svg>
`;
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ export type {
} from './SelectMenu'
export {default as SideNav} from './SideNav'
export type {SideNavProps, SideNavLinkProps} from './SideNav'
export {default as Spinner} from './Spinner'
export type {SpinnerProps} from './Spinner'
export {default as StateLabel} from './StateLabel'
export type {StateLabelProps} from './StateLabel'
export {default as StyledOcticon} from './StyledOcticon'
Expand Down

1 comment on commit 6800c60

@vercel
Copy link

@vercel vercel bot commented on 6800c60 May 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.