From 81b1e0e05919eb368a66aef47adcf7738af76f29 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 29 Sep 2023 11:47:06 +0200 Subject: refactor(components): rewrite Spinner component * Message should be set as children * Default message is no longer available (depending on use case, the consumer might prefer aria-label instead) * It is now possible to define the message position --- .../svg-paths/icons-paths/arrow-icon-paths.tsx | 3 +- src/components/atoms/loaders/spinner.module.scss | 48 --------------- src/components/atoms/loaders/spinner.stories.tsx | 42 ------------- src/components/atoms/loaders/spinner.test.tsx | 15 ----- src/components/atoms/loaders/spinner.tsx | 35 ----------- src/components/atoms/loaders/spinner/index.ts | 1 + .../atoms/loaders/spinner/spinner.module.scss | 69 ++++++++++++++++++++++ .../atoms/loaders/spinner/spinner.stories.tsx | 37 ++++++++++++ .../atoms/loaders/spinner/spinner.test.tsx | 53 +++++++++++++++++ src/components/atoms/loaders/spinner/spinner.tsx | 42 +++++++++++++ 10 files changed, 204 insertions(+), 141 deletions(-) delete mode 100644 src/components/atoms/loaders/spinner.module.scss delete mode 100644 src/components/atoms/loaders/spinner.stories.tsx delete mode 100644 src/components/atoms/loaders/spinner.test.tsx delete mode 100644 src/components/atoms/loaders/spinner.tsx create mode 100644 src/components/atoms/loaders/spinner/index.ts create mode 100644 src/components/atoms/loaders/spinner/spinner.module.scss create mode 100644 src/components/atoms/loaders/spinner/spinner.stories.tsx create mode 100644 src/components/atoms/loaders/spinner/spinner.test.tsx create mode 100644 src/components/atoms/loaders/spinner/spinner.tsx (limited to 'src/components/atoms') diff --git a/src/components/atoms/images/icons/svg-paths/icons-paths/arrow-icon-paths.tsx b/src/components/atoms/images/icons/svg-paths/icons-paths/arrow-icon-paths.tsx index ee29d5d..0dc701a 100644 --- a/src/components/atoms/images/icons/svg-paths/icons-paths/arrow-icon-paths.tsx +++ b/src/components/atoms/images/icons/svg-paths/icons-paths/arrow-icon-paths.tsx @@ -1,7 +1,8 @@ /* eslint-disable react/jsx-no-literals */ import type { FC } from 'react'; +import type { Position } from '../../../../../types'; -export type ArrowOrientation = 'top' | 'right' | 'bottom' | 'left'; +export type ArrowOrientation = Exclude; const getArrowBarPathFrom = (orientation: ArrowOrientation) => { switch (orientation) { diff --git a/src/components/atoms/loaders/spinner.module.scss b/src/components/atoms/loaders/spinner.module.scss deleted file mode 100644 index 3e05cb3..0000000 --- a/src/components/atoms/loaders/spinner.module.scss +++ /dev/null @@ -1,48 +0,0 @@ -@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 deleted file mode 100644 index 197d06c..0000000 --- a/src/components/atoms/loaders/spinner.stories.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { Spinner as SpinnerComponent } from './spinner'; - -/** - * Spinner - Storybook Meta - */ -export default { - title: 'Atoms/Loaders/Spinner', - component: SpinnerComponent, - argTypes: { - message: { - control: { - type: 'text', - }, - description: 'Loading message.', - table: { - category: 'Options', - }, - type: { - name: 'string', - required: false, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ( - -); - -/** - * Loaders Stories - Default Spinner - */ -export const Spinner = Template.bind({}); - -/** - * Loaders Stories - Spinner with custom message - */ -export const SpinnerCustomMessage = Template.bind({}); -SpinnerCustomMessage.args = { - message: 'Submitting...', -}; diff --git a/src/components/atoms/loaders/spinner.test.tsx b/src/components/atoms/loaders/spinner.test.tsx deleted file mode 100644 index 553c3ef..0000000 --- a/src/components/atoms/loaders/spinner.test.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; -import { Spinner } from './spinner'; - -describe('Spinner', () => { - it('renders a spinner loader', () => { - render(); - expect(screen.getByText('Loading...')).toBeInTheDocument(); - }); - - it('renders a spinner loader with a custom message', () => { - render(); - expect(screen.getByText('Submitting')).toBeInTheDocument(); - }); -}); diff --git a/src/components/atoms/loaders/spinner.tsx b/src/components/atoms/loaders/spinner.tsx deleted file mode 100644 index 968290b..0000000 --- a/src/components/atoms/loaders/spinner.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { FC } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './spinner.module.scss'; - -export type SpinnerProps = { - /** - * The loading message. Default: "Loading...". - */ - message?: string; -}; - -/** - * Spinner component - * - * Render a loading message with animation. - */ -export const Spinner: FC = ({ message }) => { - const intl = useIntl(); - - return ( -
-
-
-
-
- {message ?? - intl.formatMessage({ - defaultMessage: 'Loading...', - description: 'Spinner: loading text', - id: 'q9cJQe', - })} -
-
- ); -}; diff --git a/src/components/atoms/loaders/spinner/index.ts b/src/components/atoms/loaders/spinner/index.ts new file mode 100644 index 0000000..cd17217 --- /dev/null +++ b/src/components/atoms/loaders/spinner/index.ts @@ -0,0 +1 @@ +export * from './spinner'; diff --git a/src/components/atoms/loaders/spinner/spinner.module.scss b/src/components/atoms/loaders/spinner/spinner.module.scss new file mode 100644 index 0000000..97882a4 --- /dev/null +++ b/src/components/atoms/loaders/spinner/spinner.module.scss @@ -0,0 +1,69 @@ +@use "../../../../styles/abstracts/functions" as fun; + +.wrapper { + display: flex; + align-items: center; + gap: var(--spacing-xs); + width: fit-content; + + &--left { + flex-flow: row-reverse wrap; + } + + &--right { + flex-flow: row wrap; + } + + &--bottom { + flex-flow: column nowrap; + } + + &--top { + flex-flow: column-reverse nowrap; + } +} + +.icon { + --ball-size: #{fun.convert-px(8)}; + + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + width: calc((var(--ball-size) * 3) + var(--spacing-xs)); + + &__ball { + width: var(--ball-size); + height: var(--ball-size); + 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; + } + } +} + +.body { + color: var(--color-primary-darker); +} + +@keyframes spinner { + 0%, + 80%, + 100% { + transform: scale(0); + } + + 40% { + transform: scale(1); + } +} diff --git a/src/components/atoms/loaders/spinner/spinner.stories.tsx b/src/components/atoms/loaders/spinner/spinner.stories.tsx new file mode 100644 index 0000000..e9dfae4 --- /dev/null +++ b/src/components/atoms/loaders/spinner/spinner.stories.tsx @@ -0,0 +1,37 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Spinner as SpinnerComponent } from './spinner'; + +/** + * Spinner - Storybook Meta + */ +export default { + title: 'Atoms/Loaders', + component: SpinnerComponent, + argTypes: { + children: { + control: { + type: 'text', + }, + description: 'Loading message.', + table: { + category: 'Options', + }, + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +/** + * Loaders Stories - Spinner + */ +export const Spinner = Template.bind({}); +Spinner.args = { + children: 'Submitting...', +}; diff --git a/src/components/atoms/loaders/spinner/spinner.test.tsx b/src/components/atoms/loaders/spinner/spinner.test.tsx new file mode 100644 index 0000000..733648b --- /dev/null +++ b/src/components/atoms/loaders/spinner/spinner.test.tsx @@ -0,0 +1,53 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import { Spinner } from './spinner'; + +describe('Spinner', () => { + it('renders a spinner', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); + }); + + it('can render a spinner with a custom message', () => { + const customMsg = 'Submitting'; + + render({customMsg}); + expect(rtlScreen.getByText(customMsg)).toBeInTheDocument(); + }); + + it('can render a spinner with a custom message at the bottom', () => { + const customMsg = 'necessitatibus'; + + render({customMsg}); + expect(rtlScreen.getByText(customMsg).parentElement).toHaveClass( + 'wrapper--bottom' + ); + }); + + it('can render a spinner with a custom message on the left', () => { + const customMsg = 'eos'; + + render({customMsg}); + expect(rtlScreen.getByText(customMsg).parentElement).toHaveClass( + 'wrapper--left' + ); + }); + + it('can render a spinner with a custom message on the right', () => { + const customMsg = 'neque'; + + render({customMsg}); + expect(rtlScreen.getByText(customMsg).parentElement).toHaveClass( + 'wrapper--right' + ); + }); + + it('can render a spinner with a custom message on the top', () => { + const customMsg = 'vero'; + + render({customMsg}); + expect(rtlScreen.getByText(customMsg).parentElement).toHaveClass( + 'wrapper--top' + ); + }); +}); diff --git a/src/components/atoms/loaders/spinner/spinner.tsx b/src/components/atoms/loaders/spinner/spinner.tsx new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/src/components/atoms/loaders/spinner/spinner.tsx @@ -0,0 +1,42 @@ +import type { FC, HTMLAttributes, ReactNode } from 'react'; +import type { Position } from '../../../../types'; +import styles from './spinner.module.scss'; + +export type SpinnerProps = Omit, 'children'> & { + /** + * The loading message. + */ + children?: ReactNode; + /** + * Define the position of the loading message if any. + * + * @default 'right' + */ + position?: Exclude; +}; + +/** + * Spinner component + * + * Render a loading message with animation. + */ +export const Spinner: FC = ({ + children, + className = '', + position = 'right', + ...props +}) => { + const positionClass = styles[`wrapper--${position}`]; + const wrapperClass = `${styles.wrapper} ${positionClass} ${className}`; + + return ( +
+
+
+
+
+
+
{children}
+
+ ); +}; -- cgit v1.2.3