aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms/loaders
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-04-04 11:53:44 +0200
committerArmand Philippot <git@armandphilippot.com>2022-04-04 11:53:44 +0200
commite29e145b053a5b1f338cef8a0721e185e955cdc2 (patch)
tree3391d241d53440caf910e67b105cab8c7496897e /src/components/atoms/loaders
parent21eb67ef5e59d36b996392f60b5045f152a64604 (diff)
chore: add a Spinner component
Diffstat (limited to 'src/components/atoms/loaders')
-rw-r--r--src/components/atoms/loaders/spinner.module.scss48
-rw-r--r--src/components/atoms/loaders/spinner.stories.tsx28
-rw-r--r--src/components/atoms/loaders/spinner.test.tsx14
-rw-r--r--src/components/atoms/loaders/spinner.tsx37
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;