diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-04-04 11:53:44 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-04-04 11:53:44 +0200 |
| commit | e29e145b053a5b1f338cef8a0721e185e955cdc2 (patch) | |
| tree | 3391d241d53440caf910e67b105cab8c7496897e /src/components/atoms/loaders | |
| parent | 21eb67ef5e59d36b996392f60b5045f152a64604 (diff) | |
chore: add a Spinner component
Diffstat (limited to 'src/components/atoms/loaders')
| -rw-r--r-- | src/components/atoms/loaders/spinner.module.scss | 48 | ||||
| -rw-r--r-- | src/components/atoms/loaders/spinner.stories.tsx | 28 | ||||
| -rw-r--r-- | src/components/atoms/loaders/spinner.test.tsx | 14 | ||||
| -rw-r--r-- | src/components/atoms/loaders/spinner.tsx | 37 |
4 files changed, 127 insertions, 0 deletions
diff --git a/src/components/atoms/loaders/spinner.module.scss b/src/components/atoms/loaders/spinner.module.scss new file mode 100644 index 0000000..8d818a2 --- /dev/null +++ b/src/components/atoms/loaders/spinner.module.scss @@ -0,0 +1,48 @@ +@use "@styles/abstracts/functions" as fun; + +.wrapper { + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: center; + gap: var(--spacing-2xs); + margin: var(--spacing-md) 0; +} + +.ball { + width: fun.convert-px(8); + height: fun.convert-px(8); + background: linear-gradient( + to right, + var(--color-primary-light) 0%, + var(--color-primary-lighter) 100% + ); + border-radius: 50%; + animation: spinner 1.4s infinite ease-in-out both; + + &:first-child { + animation-delay: -0.32s; + } + + &:nth-child(2) { + animation-delay: -0.16s; + } +} + +.text { + margin-left: var(--spacing-xs); + color: var(--color-primary-darker); + text-align: center; +} + +@keyframes spinner { + 0%, + 80%, + 100% { + transform: scale(0); + } + + 40% { + transform: scale(1); + } +} diff --git a/src/components/atoms/loaders/spinner.stories.tsx b/src/components/atoms/loaders/spinner.stories.tsx new file mode 100644 index 0000000..86c316e --- /dev/null +++ b/src/components/atoms/loaders/spinner.stories.tsx @@ -0,0 +1,28 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import SpinnerComponent from './spinner'; + +export default { + title: 'Atoms/Loaders', + component: SpinnerComponent, + argTypes: { + message: { + control: { + type: 'text', + }, + description: 'Loading message.', + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta<typeof SpinnerComponent>; + +const Template: ComponentStory<typeof SpinnerComponent> = (args) => ( + <IntlProvider locale="en"> + <SpinnerComponent {...args} /> + </IntlProvider> +); + +export const Spinner = Template.bind({}); diff --git a/src/components/atoms/loaders/spinner.test.tsx b/src/components/atoms/loaders/spinner.test.tsx new file mode 100644 index 0000000..0a6db91 --- /dev/null +++ b/src/components/atoms/loaders/spinner.test.tsx @@ -0,0 +1,14 @@ +import { render, screen } from '@test-utils'; +import Spinner from './spinner'; + +describe('Spinner', () => { + it('renders a spinner loader', () => { + render(<Spinner />); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); + + it('renders a spinner loader with a custom message', () => { + render(<Spinner message="Submitting" />); + expect(screen.getByText('Submitting')).toBeInTheDocument(); + }); +}); diff --git a/src/components/atoms/loaders/spinner.tsx b/src/components/atoms/loaders/spinner.tsx new file mode 100644 index 0000000..57b0a43 --- /dev/null +++ b/src/components/atoms/loaders/spinner.tsx @@ -0,0 +1,37 @@ +import { FC } from 'react'; +import { useIntl } from 'react-intl'; +import styles from './spinner.module.scss'; + +type SpinnerProps = { + /** + * The loading message. Default: "Loading...". + */ + message?: string; +}; + +/** + * Spinner component + * + * Render a loading message with animation. + */ +const Spinner: FC<SpinnerProps> = ({ message }) => { + const intl = useIntl(); + + return ( + <div className={styles.wrapper}> + <div className={styles.ball}></div> + <div className={styles.ball}></div> + <div className={styles.ball}></div> + <div className={styles.text}> + {message || + intl.formatMessage({ + defaultMessage: 'Loading...', + description: 'Spinner: loading text', + id: 'q9cJQe', + })} + </div> + </div> + ); +}; + +export default Spinner; |
