From 2adb7c62e83543c1ae9e037e013f01f29a9d3725 Mon Sep 17 00:00:00 2001 From: Tristan Davey Date: Fri, 15 Mar 2019 02:42:41 +1100 Subject: [PATCH] feat: add support for Spinners (#3541) Adds support for the [spinners components](https://getbootstrap.com/docs/4.2/components/spinners/) introduced in Bootstrap 4.2. *Changes:* - Adds new `Spinner` component - Add tests for new component - Creates new page in documentation for new component - Upgrades documentation CSS to Bootstrap 4.2 (given the claimed 4.2 support in the documentation itself) *Notes:* - I wasn't sure about the terminology for the property that switches between the two styles of spinner component. I couldn't think of a good term that wouldn't overlap with an existing property. `animation` was chosen at it was the most representative of the changes the property made, but I feel it may still be too ambiguous. I'm open to alternative suggestions. - Whilst the documentation claims compatibility with Bootstrap 4.2 it was using a Bootstrap 4.1 CSS file. I've upgraded the documentation to a Bootstrap 4.2 version of the CSS file. I can remove this change and submit seperately if necessary. --- src/Spinner.js | 86 +++++++++++++++++++++++++++ src/index.js | 1 + test/SpinnerSpec.js | 30 ++++++++++ types/components/Spinner.d.ts | 25 ++++++++ types/components/index.d.ts | 1 + types/simple.test.tsx | 13 ++++ www/gatsby-ssr.js | 4 +- www/src/components/SideNav.js | 1 + www/src/examples/Spinner/Basic.js | 3 + www/src/examples/Spinner/Border.js | 1 + www/src/examples/Spinner/Buttons.js | 22 +++++++ www/src/examples/Spinner/Grow.js | 1 + www/src/examples/Spinner/Sizes.js | 6 ++ www/src/examples/Spinner/Variants.js | 18 ++++++ www/src/pages/components/spinners.mdx | 83 ++++++++++++++++++++++++++ 15 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 src/Spinner.js create mode 100644 test/SpinnerSpec.js create mode 100644 types/components/Spinner.d.ts create mode 100644 www/src/examples/Spinner/Basic.js create mode 100644 www/src/examples/Spinner/Border.js create mode 100644 www/src/examples/Spinner/Buttons.js create mode 100644 www/src/examples/Spinner/Grow.js create mode 100644 www/src/examples/Spinner/Sizes.js create mode 100644 www/src/examples/Spinner/Variants.js create mode 100644 www/src/pages/components/spinners.mdx diff --git a/src/Spinner.js b/src/Spinner.js new file mode 100644 index 0000000000..0e30128a9e --- /dev/null +++ b/src/Spinner.js @@ -0,0 +1,86 @@ +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import React from 'react'; + +import { createBootstrapComponent } from './ThemeProvider'; + +class Spinner extends React.Component { + static propTypes = { + /** + * @default 'spinner' + */ + bsPrefix: PropTypes.string.isRequired, + + /** + * The visual color style of the spinner + * + * @type {('primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark')} + */ + variant: PropTypes.string, + + /** + * Changes the animation style of the spinner. + * + * @type {('border'|'grow')} + * @default true + */ + animation: PropTypes.oneOf(['border', 'grow']).isRequired, + + /** + * Component size variations. + * + * @type {('sm')} + */ + size: PropTypes.string, + + /** + * This component may be used to wrap child elements or components. + */ + children: PropTypes.element, + + /** + * An ARIA accessible role applied to the Menu component. This should generally be set to 'status' + */ + role: PropTypes.string, + + /** + * @default div + */ + as: PropTypes.elementType, + }; + + static defaultProps = { + as: 'div', + }; + + render() { + const { + bsPrefix, + variant, + animation, + size, + children, + as, + className, + ...props + } = this.props; + const Component = as; + const bsSpinnerPrefix = `${bsPrefix}-${animation}`; + + return ( + + {children} + + ); + } +} + +export default createBootstrapComponent(Spinner, 'spinner'); diff --git a/src/index.js b/src/index.js index 7387dd3f52..ae60b0d372 100644 --- a/src/index.js +++ b/src/index.js @@ -53,6 +53,7 @@ export ProgressBar from './ProgressBar'; export ResponsiveEmbed from './ResponsiveEmbed'; export Row from './Row'; export SafeAnchor from './SafeAnchor'; +export Spinner from './Spinner'; export SplitButton from './SplitButton'; export Tab from './Tab'; export TabContainer from './TabContainer'; diff --git a/test/SpinnerSpec.js b/test/SpinnerSpec.js new file mode 100644 index 0000000000..ba6273c13a --- /dev/null +++ b/test/SpinnerSpec.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { mount } from 'enzyme'; + +import Spinner from '../src/Spinner'; + +describe('', () => { + it('Should render a basic spinner correctly', () => { + mount().assertSingle('div.spinner-border'); + }); + + it('Should render a spinner with a custom element, variant and size ', () => { + mount( + , + ).assertSingle('span.spinner-grow.spinner-grow-sm.text-primary'); + }); + + it('Should render a spinner with other properties', () => { + mount().assertSingle( + 'div.spinner-grow[role="status"]', + ); + }); + + it('Should render child elements', () => { + mount( + + + , + ).assertSingle('div.spinner-grow span#testChild'); + }); +}); diff --git a/types/components/Spinner.d.ts b/types/components/Spinner.d.ts new file mode 100644 index 0000000000..92a68da674 --- /dev/null +++ b/types/components/Spinner.d.ts @@ -0,0 +1,25 @@ +import * as React from 'react'; + +import { BsPrefixComponent } from './helpers'; + +export interface SpinnerProps { + animation: 'border' | 'grow'; + role?: string; + size?: 'sm'; + variant?: + | 'primary' + | 'secondary' + | 'success' + | 'danger' + | 'warning' + | 'info' + | 'light' + | 'dark'; + bsPrefix?: string; +} + +declare class Spinner< + As extends React.ReactType = typeof Spinner +> extends BsPrefixComponent {} + +export default Spinner; diff --git a/types/components/index.d.ts b/types/components/index.d.ts index b49d596235..ec4be38c70 100644 --- a/types/components/index.d.ts +++ b/types/components/index.d.ts @@ -76,6 +76,7 @@ export { default as ProgressBar, ProgressBarProps } from './ProgressBar'; export { default as ResponsiveEmbed, ResponsiveEmbedProps } from './ResponsiveEmbed'; export { default as Row, RowProps } from './Row'; export { default as SafeAnchor, SafeAnchorProps } from './SafeAnchor'; +export { default as Spinner, SpinnerProps } from './Spinner'; export { default as SplitButton, SplitButtonProps } from './SplitButton'; export { default as Tab, TabProps } from './Tab'; export { default as TabContainer, TabContainerProps } from './TabContainer'; diff --git a/types/simple.test.tsx b/types/simple.test.tsx index 2c6e3c4965..ebd77ed407 100644 --- a/types/simple.test.tsx +++ b/types/simple.test.tsx @@ -27,6 +27,7 @@ import { Tooltip, Pagination, ProgressBar, + Spinner, Tabs, Tab, ToggleButtonGroup, @@ -287,6 +288,18 @@ import { ; +; + + + Something Inside +; + diff --git a/www/gatsby-ssr.js b/www/gatsby-ssr.js index f45f248785..f21dc9e5b0 100644 --- a/www/gatsby-ssr.js +++ b/www/gatsby-ssr.js @@ -4,8 +4,8 @@ exports.onRenderBody = ({ setHeadComponents, setPostBodyComponents }) => { setHeadComponents([ , + Loading... +; diff --git a/www/src/examples/Spinner/Border.js b/www/src/examples/Spinner/Border.js new file mode 100644 index 0000000000..004619efda --- /dev/null +++ b/www/src/examples/Spinner/Border.js @@ -0,0 +1 @@ +; diff --git a/www/src/examples/Spinner/Buttons.js b/www/src/examples/Spinner/Buttons.js new file mode 100644 index 0000000000..35a329c81e --- /dev/null +++ b/www/src/examples/Spinner/Buttons.js @@ -0,0 +1,22 @@ + + + +; diff --git a/www/src/examples/Spinner/Grow.js b/www/src/examples/Spinner/Grow.js new file mode 100644 index 0000000000..806f0d43ac --- /dev/null +++ b/www/src/examples/Spinner/Grow.js @@ -0,0 +1 @@ +; diff --git a/www/src/examples/Spinner/Sizes.js b/www/src/examples/Spinner/Sizes.js new file mode 100644 index 0000000000..398ca4b13f --- /dev/null +++ b/www/src/examples/Spinner/Sizes.js @@ -0,0 +1,6 @@ +<> + + + + +; diff --git a/www/src/examples/Spinner/Variants.js b/www/src/examples/Spinner/Variants.js new file mode 100644 index 0000000000..156c60167e --- /dev/null +++ b/www/src/examples/Spinner/Variants.js @@ -0,0 +1,18 @@ +<> + + + + + + + + + + + + + + + + +; diff --git a/www/src/pages/components/spinners.mdx b/www/src/pages/components/spinners.mdx new file mode 100644 index 0000000000..579b2fd8fb --- /dev/null +++ b/www/src/pages/components/spinners.mdx @@ -0,0 +1,83 @@ +import { graphql } from 'gatsby'; + +import ComponentApi from '../../components/ComponentApi'; +import ReactPlayground from '../../components/ReactPlayground'; +import SpinnerBasic from '../../examples/Spinner/Basic'; +import SpinnerBorder from '../../examples/Spinner/Border'; +import SpinnerGrow from '../../examples/Spinner/Grow'; +import SpinnerVariants from '../../examples/Spinner/Variants'; +import SpinnerSizes from '../../examples/Spinner/Sizes'; +import SpinnerButtons from '../../examples/Spinner/Buttons'; + + +# Spinners + +Spinners can be used to show the loading state in your projects. +Spinners can be used as a + + + +## Animations + +Bootstrap offers two animation styles for spinners. The animation style +can be configured with the `animation` property. An animation style +must always be provided when creating a spinner. + +** Border Spinner - `border`** + + + +** Grow Spinner - `grow` ** + + + +## Variants + +All standard visual variants are available for both animation styles by +setting the `variant` property. Alternatively spinners can be custom +sized with the `style` property, or custom CSS classes. + + + +## Sizing + +In addition to the standard size, a smaller additional preconfigured +size is available by configuring the `size` property to `sm`. + + + +## Buttons + +Like the original Bootstrap spinners, these can also be used with +buttons. To use this component out-of-the-box it is recommended you +change the element type to `span` by configuring the `as` property when +using spinners inside buttons. + + + +## Accessibility + +To ensure the maximum accessibility for spinner components it is +recommended you provide a relevant ARIA `role` property, +and include screenreader-only readable text representation of the +spinner's meaning inside the component using Bootstrap's `sr-only` +class. + +The example below provides an example of accessible usage of this + component. + + + +## API + + + + +export const query = graphql` + query Spinner { + metadata: componentMetadata(displayName: { eq: "Spinner" }) { + displayName + ...ComponentApi_metadata + } + } +`;