Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions src/common/components/Loader/LoaderSpinner.tsx

This file was deleted.

6 changes: 4 additions & 2 deletions src/common/components/Loader/LoaderSuspense.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PropsWithChildren, Suspense } from 'react';

import LoaderSpinner from './LoaderSpinner';
import Spinner from './Spinner';

/**
* The `LoaderSuspense` component renders an animated spinning loader. Typically used
Expand All @@ -14,7 +14,9 @@ const LoaderSuspense = ({ children }: PropsWithChildren): JSX.Element => {
className="flex h-[50vh] items-center justify-center"
data-testid="loader-suspense-fallback"
>
<LoaderSpinner text="Loading..." testId="loader-suspense-spinner" />
<Spinner testId="loader-suspense-spinner">
<Spinner.Text>Loading...</Spinner.Text>
</Spinner>
</div>
}
>
Expand Down
47 changes: 47 additions & 0 deletions src/common/components/Loader/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { PropsWithChildren } from 'react';

import { BaseComponentProps } from 'common/utils/types';
import { cn } from 'common/utils/css';
import FAIcon, { FAIconProps } from 'common/components/Icon/FAIcon';

/**
* Properties for the `Spinner` component.
* @param {string} [icon] - Optional. A `FAIconProps` object containing properties
* for the icon.
* @see {@link FAIconProps}
*/
export interface SpinnerProps extends BaseComponentProps, PropsWithChildren {
icon?: Omit<FAIconProps, 'icon'> & Partial<Pick<FAIconProps, 'icon'>>;
}

/**
* The `Spinner` component displays an animated spinning loader icon with
* optional accompanying text. Typically used when some foreground or background
* process is occurring, such as an interaction with an API.
*/
const Spinner = ({ children, className, icon, testId = 'spinner' }: SpinnerProps): JSX.Element => {
return (
<div className={cn('flex items-center gap-2', className)} data-testid={testId}>
<FAIcon icon={icon?.icon || 'circleNotch'} spin {...icon} testId={`${testId}-icon`} />
{children}
</div>
);
};

/**
* The Text component displays optional accompanying text for the `Spinner`.
*/
const Text = ({
children,
className,
testId = 'spinner-text',
}: BaseComponentProps & PropsWithChildren): JSX.Element => {
return (
<div className={cn(className)} data-testid={testId}>
{children}
</div>
);
};
Spinner.Text = Text;

export default Spinner;
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import type { Meta, StoryObj } from '@storybook/react';

import LoaderSpinner from '../LoaderSpinner';
import Spinner from '../Spinner';

const meta = {
title: 'Common/Loader/LoaderSpinner',
component: LoaderSpinner,
title: 'Common/Loader/Spinner',
component: Spinner,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
children: { description: 'Optional. The content, e.g. "Spinner.Text".' },
className: { description: 'Additional CSS classes.' },
icon: { description: 'Optional. The icon name.' },
iconClassName: { description: 'Optional. CSS class names for the icon.' },
icon: { description: 'Optional. A "FAIconProps" object containing properties for the icon.' },
testId: { description: 'The test identifier.' },
text: { description: 'Optional. The loader text.' },
textClassName: { description: 'Optional. CSS class names for the text.' },
},
} satisfies Meta<typeof LoaderSpinner>;
} satisfies Meta<typeof Spinner>;

export default meta;

Expand All @@ -29,24 +27,24 @@ export const Simple: Story = {

export const Larger: Story = {
args: {
iconClassName: 'text-2xl',
icon: { size: '2x' },
},
};

export const Colored: Story = {
args: {
iconClassName: 'text-blue-600',
icon: { className: 'text-blue-600' },
},
};

export const WithText: Story = {
args: {
text: 'Engaging warp engines Captain...',
children: <Spinner.Text>Engaging warp engines Captain...</Spinner.Text>,
},
};

export const WithAlternativeIcon: Story = {
args: {
icon: 'circleXmark',
icon: { icon: 'circleXmark' },
},
};
82 changes: 0 additions & 82 deletions src/common/components/Loader/__tests__/LoaderSpinner.test.tsx

This file was deleted.

49 changes: 49 additions & 0 deletions src/common/components/Loader/__tests__/Spinner.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, it } from 'vitest';

import { render, screen, waitFor } from 'test/test-utils';

import Spinner from '../Spinner';

describe('Spinner', () => {
it('should render successfully', async () => {
// ARRANGE
render(<Spinner testId="my-spinner" />);
await screen.findByTestId('my-spinner');

// ASSERT
expect(screen.getByTestId('my-spinner')).toBeDefined();
});

it('should render default icon', async () => {
// ARRANGE
render(<Spinner testId="my-spinner" />);
await screen.findByTestId('my-spinner');

// ASSERT
expect(screen.getByTestId('my-spinner-icon')).toHaveAttribute('data-icon', 'circle-notch');
});

it('should render custom icon', async () => {
// ARRANGE
render(<Spinner testId="my-spinner" icon={{ icon: 'bars' }} />);
await waitFor(() =>
expect(screen.getByTestId('my-spinner-icon')).toHaveAttribute('data-icon', 'bars'),
);

// ASSERT
expect(screen.getByTestId('my-spinner-icon')).toHaveAttribute('data-icon', 'bars');
});

it('should render text', async () => {
// ARRANGE
render(
<Spinner testId="my-spinner">
<Spinner.Text testId="my-spinner-text">loader text</Spinner.Text>
</Spinner>,
);
await screen.findByTestId('my-spinner-text');

// ASSERT
expect(screen.getByTestId('my-spinner-text')).toHaveTextContent(/loader text/);
});
});
5 changes: 5 additions & 0 deletions src/common/components/Router/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const SearchInputComponents = lazy(
);
const SelectComponents = lazy(() => import('pages/Components/components/SelectComponents'));
const SkeletonComponents = lazy(() => import('pages/Components/components/SkeletonComponents'));
const SpinnerComponents = lazy(() => import('pages/Components/components/SpinnerComponents'));
const TabsComponents = lazy(() => import('pages/Components/components/TabsComponents'));
const TextComponents = lazy(() => import('pages/Components/components/TextComponents'));
const TextareaComponents = lazy(() => import('pages/Components/components/TextareaComponents'));
Expand Down Expand Up @@ -175,6 +176,10 @@ export const routes: RouteObject[] = [
path: 'skeleton',
element: withSuspense(<SkeletonComponents />),
},
{
path: 'spinner',
element: withSuspense(<SpinnerComponents />),
},
{
path: 'tabs',
element: withSuspense(<TabsComponents />),
Expand Down
6 changes: 4 additions & 2 deletions src/common/providers/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PropsWithChildren } from 'react';

import { AuthContext, AuthContextValue } from './AuthContext';
import { useGetUserTokens } from 'common/api/useGetUserTokens';
import LoaderSpinner from 'common/components/Loader/LoaderSpinner';
import Spinner from 'common/components/Loader/Spinner';

/**
* The `AuthContextProvider` React component creates, maintains, and provides
Expand Down Expand Up @@ -33,7 +33,9 @@ const AuthContextProvider = ({ children }: PropsWithChildren): JSX.Element => {
{!isReady && (
<div className="h-[50vh]" data-testid="provider-auth">
<div className="flex h-full items-center justify-center text-2xl">
<LoaderSpinner text="Signing in..." />
<Spinner>
<Spinner.Text>Signing in...</Spinner.Text>
</Spinner>
</div>
</div>
)}
Expand Down
6 changes: 4 additions & 2 deletions src/pages/Auth/Signout/SignoutPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

import { useSignout } from './api/useSignout';
import LoaderSpinner from 'common/components/Loader/LoaderSpinner';
import Spinner from 'common/components/Loader/Spinner';
import Page from 'common/components/Content/Page';
import Container from 'common/components/Content/Container';

Expand Down Expand Up @@ -30,7 +30,9 @@ const SignoutPage = (): JSX.Element => {
<Page testId="page-signout">
<Container className="h-[50vh]">
<div className="flex h-full items-center justify-center text-2xl">
<LoaderSpinner text="Signing out..." />
<Spinner>
<Spinner.Text>Signing out...</Spinner.Text>
</Spinner>
</div>
</Container>
</Page>
Expand Down
3 changes: 3 additions & 0 deletions src/pages/Components/ComponentsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ const ComponentsPage = (): JSX.Element => {
<MenuNavLink to="skeleton" styleActive>
Skeleton
</MenuNavLink>
<MenuNavLink to="spinner" styleActive>
Spinner
</MenuNavLink>
<MenuNavLink to="tabs" styleActive>
Tabs
</MenuNavLink>
Expand Down
Loading