Skip to content

Commit

Permalink
feat: add support for Spinners (#3541)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Tristan Davey authored and jquense committed Mar 14, 2019
1 parent e479acd commit 2adb7c6
Show file tree
Hide file tree
Showing 15 changed files with 293 additions and 2 deletions.
86 changes: 86 additions & 0 deletions 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 (
<Component
{...props}
className={classNames(
className,
bsSpinnerPrefix,
size && `${bsSpinnerPrefix}-${size}`,
variant && `text-${variant}`,
)}
>
{children}
</Component>
);
}
}

export default createBootstrapComponent(Spinner, 'spinner');
1 change: 1 addition & 0 deletions src/index.js
Expand Up @@ -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';
Expand Down
30 changes: 30 additions & 0 deletions test/SpinnerSpec.js
@@ -0,0 +1,30 @@
import React from 'react';
import { mount } from 'enzyme';

import Spinner from '../src/Spinner';

describe('<Spinner>', () => {
it('Should render a basic spinner correctly', () => {
mount(<Spinner animation="border" />).assertSingle('div.spinner-border');
});

it('Should render a spinner with a custom element, variant and size ', () => {
mount(
<Spinner as="span" animation="grow" variant="primary" size="sm" />,
).assertSingle('span.spinner-grow.spinner-grow-sm.text-primary');
});

it('Should render a spinner with other properties', () => {
mount(<Spinner animation="grow" role="status" />).assertSingle(
'div.spinner-grow[role="status"]',
);
});

it('Should render child elements', () => {
mount(
<Spinner animation="grow">
<span id="testChild" />
</Spinner>,
).assertSingle('div.spinner-grow span#testChild');
});
});
25 changes: 25 additions & 0 deletions 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<As, SpinnerProps> {}

export default Spinner;
1 change: 1 addition & 0 deletions types/components/index.d.ts
Expand Up @@ -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';
Expand Down
13 changes: 13 additions & 0 deletions types/simple.test.tsx
Expand Up @@ -27,6 +27,7 @@ import {
Tooltip,
Pagination,
ProgressBar,
Spinner,
Tabs,
Tab,
ToggleButtonGroup,
Expand Down Expand Up @@ -287,6 +288,18 @@ import {
<Button variant="secondary">Tooltip on left</Button>
</OverlayTrigger>;

<Spinner
as="span"
animation="border"
variant="primary"
size="sm"
role="state"
/>;

<Spinner animation="grow">
<span>Something Inside</span>
</Spinner>;

<Pagination>
<Pagination.First />
<Pagination.Prev />
Expand Down
4 changes: 2 additions & 2 deletions www/gatsby-ssr.js
Expand Up @@ -4,8 +4,8 @@ exports.onRenderBody = ({ setHeadComponents, setPostBodyComponents }) => {
setHeadComponents([
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"
integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS"
crossOrigin="anonymous"
/>,
<link
Expand Down
1 change: 1 addition & 0 deletions www/src/components/SideNav.js
Expand Up @@ -127,6 +127,7 @@ const components = [
'pagination',
'popovers',
'progress',
'spinners',
'table',
'tabs',
'tooltips',
Expand Down
3 changes: 3 additions & 0 deletions www/src/examples/Spinner/Basic.js
@@ -0,0 +1,3 @@
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>;
1 change: 1 addition & 0 deletions www/src/examples/Spinner/Border.js
@@ -0,0 +1 @@
<Spinner animation="border" />;
22 changes: 22 additions & 0 deletions www/src/examples/Spinner/Buttons.js
@@ -0,0 +1,22 @@
<ButtonToolbar>
<Button variant="primary" disabled>
<Spinner
as="span"
animation="border"
size="sm"
role="status"
aria-hidden="true"
/>
<span className="sr-only">Loading...</span>
</Button>
<Button variant="primary" disabled>
<Spinner
as="span"
animation="grow"
size="sm"
role="status"
aria-hidden="true"
/>
Loading...
</Button>
</ButtonToolbar>;
1 change: 1 addition & 0 deletions www/src/examples/Spinner/Grow.js
@@ -0,0 +1 @@
<Spinner animation="grow" />;
6 changes: 6 additions & 0 deletions www/src/examples/Spinner/Sizes.js
@@ -0,0 +1,6 @@
<>
<Spinner animation="border" size="sm" />
<Spinner animation="border" />
<Spinner animation="grow" size="sm" />
<Spinner animation="grow" />
</>;
18 changes: 18 additions & 0 deletions www/src/examples/Spinner/Variants.js
@@ -0,0 +1,18 @@
<>
<Spinner animation="border" variant="primary" />
<Spinner animation="border" variant="secondary" />
<Spinner animation="border" variant="success" />
<Spinner animation="border" variant="danger" />
<Spinner animation="border" variant="warning" />
<Spinner animation="border" variant="info" />
<Spinner animation="border" variant="light" />
<Spinner animation="border" variant="dark" />
<Spinner animation="grow" variant="primary" />
<Spinner animation="grow" variant="secondary" />
<Spinner animation="grow" variant="success" />
<Spinner animation="grow" variant="danger" />
<Spinner animation="grow" variant="warning" />
<Spinner animation="grow" variant="info" />
<Spinner animation="grow" variant="light" />
<Spinner animation="grow" variant="dark" />
</>;
83 changes: 83 additions & 0 deletions 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

<ReactPlayground codeText={SpinnerBasic} />

## 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`**

<ReactPlayground codeText={SpinnerBorder} />

** Grow Spinner - `grow` **

<ReactPlayground codeText={SpinnerGrow} />

## 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.

<ReactPlayground codeText={SpinnerVariants} />

## Sizing

In addition to the standard size, a smaller additional preconfigured
size is available by configuring the `size` property to `sm`.

<ReactPlayground codeText={SpinnerSizes} />

## 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.

<ReactPlayground codeText={SpinnerButtons} />

## 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.

<ReactPlayground codeText={SpinnerBasic} />

## API

<ComponentApi metadata={props.data.metadata} />


export const query = graphql`
query Spinner {
metadata: componentMetadata(displayName: { eq: "Spinner" }) {
displayName
...ComponentApi_metadata
}
}
`;

0 comments on commit 2adb7c6

Please sign in to comment.