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
4 changes: 2 additions & 2 deletions src/components/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ReactNode } from 'react';
import { Spinner } from '../spinner/Spinner';
import Loader from '../loader/Loader';

interface ButtonProps {
id?: string;
Expand Down Expand Up @@ -73,7 +73,7 @@ export const Button = ({
ring-offset-2 ring-offset-transparent transition-all duration-100 ease-in-out
focus-visible:ring-primary/50 ${styles} ${className}`}
>
{loading && <Spinner size={18} />}
{loading && <Loader size={18} />}
<div className="flex items-center justify-center space-x-2" data-cy={buttonChildrenDataCy}>
{children}
</div>
Expand Down
86 changes: 46 additions & 40 deletions src/components/button/__test__/__snapshots__/Button.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -541,10 +541,53 @@ exports[`Button component > Primary loading button should render correctly 1`] =
disabled=""
type="button"
>
<div>
<svg
class="animate-spin undefined"
fill="none"
height="18"
role="img"
viewBox="0 0 24 24"
width="18"
xmlns="http://www.w3.org/2000/svg"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824
3 7.938l3-2.647z"
fill="currentColor"
/>
</svg>
</div>
<div
class="flex items-center justify-center space-x-2"
/>
</button>
</div>
</body>,
"container": <div>
<button
class="h-10 px-5 relative flex shrink-0 select-none flex-row items-center justify-center space-x-2
whitespace-nowrap rounded-lg text-base font-medium outline-none ring-2 ring-primary/0
ring-offset-2 ring-offset-transparent transition-all duration-100 ease-in-out
focus-visible:ring-primary/50 bg-primary-dark active:bg-primary-dark text-white shadow-sm "
disabled=""
type="button"
>
<div>
<svg
class="animate-spin "
class="animate-spin undefined"
fill="none"
height="18"
role="img"
viewBox="0 0 24 24"
width="18"
xmlns="http://www.w3.org/2000/svg"
Expand All @@ -560,48 +603,11 @@ exports[`Button component > Primary loading button should render correctly 1`] =
<path
class="opacity-75"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824
3 7.938l3-2.647z"
3 7.938l3-2.647z"
fill="currentColor"
/>
</svg>
<div
class="flex items-center justify-center space-x-2"
/>
</button>
</div>
</body>,
"container": <div>
<button
class="h-10 px-5 relative flex shrink-0 select-none flex-row items-center justify-center space-x-2
whitespace-nowrap rounded-lg text-base font-medium outline-none ring-2 ring-primary/0
ring-offset-2 ring-offset-transparent transition-all duration-100 ease-in-out
focus-visible:ring-primary/50 bg-primary-dark active:bg-primary-dark text-white shadow-sm "
disabled=""
type="button"
>
<svg
class="animate-spin "
fill="none"
height="18"
viewBox="0 0 24 24"
width="18"
xmlns="http://www.w3.org/2000/svg"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824
3 7.938l3-2.647z"
fill="currentColor"
/>
</svg>
</div>
<div
class="flex items-center justify-center space-x-2"
/>
Expand Down
11 changes: 7 additions & 4 deletions src/components/contextMenu/__test__/ContextMenu.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { render, fireEvent, screen } from '@testing-library/react';
import ContextMenu, { ContextMenuProps, MenuItemType } from '../ContextMenu';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import ContextMenu, { ContextMenuProps } from '../ContextMenu';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { MenuItemType } from '../../menu/Menu';

interface TestItem {
name: string;
Expand Down Expand Up @@ -29,7 +30,9 @@ describe('ContextMenu Component', () => {
return render(<ContextMenu {...props} />);
};

beforeEach(() => {});
afterEach(() => {
vi.clearAllMocks();
});

it('should match snapshot', () => {
const contextMenu = renderContextMenu();
Expand Down
2 changes: 1 addition & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ export * from './checkbox/Checkbox';
export * from './input/Input';
export * from './textArea/TextArea';
export * from './button/Button';
export * from './loader/Loader';
export * from './switch/Switch';
export * from './radio-button/RadioButton';
export * from './avatar/Avatar';
export * from './spinner/Spinner';
export * from './slider/RangeSlider';
export * from './dialog/Dialog';
export * from './modal/Modal';
Expand Down
85 changes: 85 additions & 0 deletions src/components/loader/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import '../../styles/Loader.css';

interface LoaderProps {
classNameContainer?: string;
classNameLoader?: string;
classNameText?: string;
type?: 'spinner' | 'pulse';
text?: string;
size?: number;
}

/**
* Loader component.
*
* @property {string} [classNameContainer]
* - Optional class name for the container wrapping the loader.
* Useful for applying custom styles to the outermost container.
*
* @property {string} [classNameLoader]
* - Optional class name for the loader element itself (spinner or pulse).
* Allows custom styling of the loading animation.
*
* @property {string} [classNameText]
* - Optional class name for the text displayed below the loader.
* Allows style or adjust the appearance of the text.
*
* @property {'spinner' | 'pulse'} [type='spinner']
* - Determines the type of loader to render.
* Can be `'spinner'` for a rotating animation or `'pulse'` for a pulsing effect.
* Defaults to `'spinner'`.
*
* @property {string} [text]
* - Optional text to display below the loader.
*
* @property {number} [size=32]
* - Size of the spinner loader in pixels.
* Applies to the width and height of the SVG element for the `'spinner'` type.
* Defaults to `32`.
*/

const Loader: React.FC<LoaderProps> = ({
classNameContainer,
classNameLoader,
classNameText,
type = 'spinner',
text,
size = 32,
}) => {
const isSpinner = type === 'spinner';

return (
<div className={classNameContainer}>
{isSpinner ? (
<>
<svg
className={`animate-spin ${classNameLoader}`}
width={size}
height={size}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
role="img"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824
3 7.938l3-2.647z"
></path>
</svg>
{text && <p className={classNameText}>{text}</p>}
</>
) : (
<div className={`loader-container ${classNameLoader}`}>
<div className="loader06"></div>
{text && <p className={`loader-text ${classNameText}`}>{text}</p>}
</div>
)}
</div>
);
};

export default Loader;
75 changes: 75 additions & 0 deletions src/components/loader/__test__/Loader.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import { render } from '@testing-library/react';
import { expect } from 'chai';
import { afterEach, describe, it, vi } from 'vitest';
import Loader from '../Loader';

describe('Loader Component', () => {
const renderLoader = (props = {}) => {
return render(<Loader {...props} />);
};

afterEach(() => {
vi.clearAllMocks();
});

it('should match snapshot', () => {
const loader = renderLoader();
expect(loader).toMatchSnapshot();
});

it('renders a spinner loader by default', () => {
const { getByRole } = renderLoader();
const spinner = getByRole('img', { hidden: true });
expect(spinner).toBeInTheDocument();
expect(spinner).toHaveClass('animate-spin');
});

it('renders a pulse loader when type is "pulse"', () => {
const { getByText } = renderLoader({ type: 'pulse' });
const pulseLoader = getByText('', { selector: '.loader06' });
expect(pulseLoader).toBeInTheDocument();
});

it('renders the provided text below the spinner loader', () => {
const text = 'Loading data...';
const { getByText } = renderLoader({ text });
const textElement = getByText(text);
expect(textElement).toBeInTheDocument();
});

it('renders the provided text below the pulse loader', () => {
const text = 'Loading data...';
const { getByText } = renderLoader({ type: 'pulse', text });
const textElement = getByText(text);
expect(textElement).toBeInTheDocument();
expect(textElement).toHaveClass('loader-text');
});

it('applies custom class to the container', () => {
const customClass = 'custom-container';
const { container } = renderLoader({ classNameContainer: customClass });
expect(container.firstChild).toHaveClass(customClass);
});

it('applies custom class to the loader', () => {
const customClass = 'custom-loader';
const { container } = renderLoader({ classNameLoader: customClass });
const loader = container.firstChild?.firstChild;
expect(loader).toHaveClass(customClass);
});

it('renders with the correct size for the spinner', () => {
const size = 64;
const { getByRole } = renderLoader({ size });
const spinner = getByRole('img', { hidden: true });
expect(spinner).toHaveAttribute('width', `${size}`);
expect(spinner).toHaveAttribute('height', `${size}`);
});

it('does not render text if none is provided', () => {
const { queryByText } = renderLoader();
const textElement = queryByText(/./);
expect(textElement).toBeNull();
});
});
Loading