diff --git a/.changeset/metal-days-remain.md b/.changeset/metal-days-remain.md new file mode 100644 index 00000000000..90a1129ef0e --- /dev/null +++ b/.changeset/metal-days-remain.md @@ -0,0 +1,5 @@ +--- +"@primer/components": minor +--- + +New `Spinner` Component diff --git a/docs/content/Spinner.md b/docs/content/Spinner.md new file mode 100644 index 00000000000..8c8a48d7120 --- /dev/null +++ b/docs/content/Spinner.md @@ -0,0 +1,38 @@ +--- +title: Spinner +status: alpha +--- + +Use Spinner to let users know that content is being loaded. + +## Examples + +### Default (Medium) + +```jsx live + +``` + +### Small + +```jsx live + +``` + +### Large + +```jsx live + +``` + +## 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' | 'medium' | 'large' | 'medium' | Sets the uniform `width` and `height` of the SVG element | diff --git a/docs/src/@primer/gatsby-theme-doctocat/nav.yml b/docs/src/@primer/gatsby-theme-doctocat/nav.yml index 9eacf0a11f9..1f3bad0514e 100644 --- a/docs/src/@primer/gatsby-theme-doctocat/nav.yml +++ b/docs/src/@primer/gatsby-theme-doctocat/nav.yml @@ -103,6 +103,8 @@ url: /SelectMenu - title: SideNav url: /SideNav + - title: Spinner + url: /Spinner - title: StateLabel url: /StateLabel - title: StyledOcticon diff --git a/src/Spinner.tsx b/src/Spinner.tsx new file mode 100644 index 00000000000..13ecb3a59c1 --- /dev/null +++ b/src/Spinner.tsx @@ -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 ( + + + + + ) +} + +const StyledSpinner = styled(Spinner)` + @keyframes rotate-keyframes { + 100% { + transform: rotate(360deg); + } + } + + animation: rotate-keyframes 1s linear infinite; + + ${COMMON} + ${sx} +` + +StyledSpinner.displayName = 'Spinner' + +export type SpinnerProps = ComponentProps +export default StyledSpinner diff --git a/src/__tests__/Spinner.tsx b/src/__tests__/Spinner.tsx new file mode 100644 index 00000000000..37074665915 --- /dev/null +++ b/src/__tests__/Spinner.tsx @@ -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() + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + + it('should respect size arguments', () => { + const expectSize = (input: SpinnerProps['size'] | undefined, expectedSize: string) => { + const {container} = HTMLRender() + 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') + }) +}) diff --git a/src/__tests__/__snapshots__/Spinner.tsx.snap b/src/__tests__/__snapshots__/Spinner.tsx.snap new file mode 100644 index 00000000000..5c7f00dce0c --- /dev/null +++ b/src/__tests__/__snapshots__/Spinner.tsx.snap @@ -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; +} + + + + + +`; diff --git a/src/index.ts b/src/index.ts index 8cf553218e6..a631920c65e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'