Skip to content

Commit

Permalink
Feat/861n1w96w/loader (#16)
Browse files Browse the repository at this point in the history
* feat: added basic loader

* refactor styles

* removed types

* feat: add tests

* fix tsconfig

* fix: review changes

* fix: fix tsconfig

* fix: fix hardcoded width

* fix tsconfig

* fix: refactor test

* fix: fix exports
  • Loading branch information
mateusz-kleszcz authored Aug 8, 2023
1 parent 91cf31f commit 261715b
Show file tree
Hide file tree
Showing 21 changed files with 479 additions and 13 deletions.
9 changes: 3 additions & 6 deletions src/components/Button/Button.styles.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { SystemProps } from '@xstyled/styled-components';

import { ButtonVariant } from './types/ButtonType.type';
import { VariantConfig } from './VariantConfig';
import { Theme } from '../../theme';

type BaseProps = Omit<SystemProps<Theme>, 'appearance'>;
import { BaseConfigProps } from '@/utility-types/BaseConfigProps';

const size = {
small: {
Expand All @@ -25,7 +22,7 @@ const size = {
h: 'large',
text: 'body-large',
},
} as const satisfies Record<'small' | 'medium' | 'large', BaseProps>;
} as const satisfies Record<'small' | 'medium' | 'large', BaseConfigProps>;

const commonConfig = {
display: 'inline-flex',
Expand All @@ -51,7 +48,7 @@ const commonConfig = {
},
transition: true,
transitionDuration: 200,
} as const satisfies BaseProps;
} as const satisfies BaseConfigProps;

const defaultConfig = {
...commonConfig,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button } from './Button';
import { render } from '../../tests/render';

const getButton = (jsx: JSX.Element) => {
const { getByRole, getByTestId } = render(jsx);
const { getByRole } = render(jsx);

return getByRole('button');
};
Expand Down
4 changes: 2 additions & 2 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { stylesBuilder } from './stylesBuilder/stylesBuilder';
import { ButtonAppearance } from './types/ButtonAppearance.type';
import { ButtonVariant } from './types/ButtonType.type';
import { tet } from '../../tetrisly';
import { Loader } from '../Loader';

import { MarginProps } from '@/types/MarginProps';

Expand Down Expand Up @@ -36,8 +37,7 @@ export const Button = <
style={{ textUnderlineOffset: '3px', textDecorationThickness: '1px' }}
{...rest}
>
{/* TODO(Loader): update Loader when implemented */}
{state === 'loading' && '...'}
{state === 'loading' && <Loader size="small" shape="circle" />}
{beforeIcon && state !== 'loading' && <Icon name={beforeIcon} />}
{label}
{dropdown && <Icon name="20-chevron-down" />}
Expand Down
1 change: 0 additions & 1 deletion src/components/Button/types/ButtonIntent.type.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ButtonAppearance } from './ButtonAppearance.type';
import { ButtonVariant } from './ButtonType.type';
import { Equal, Expect } from '../../../utility-types/testing';

type Intent = 'none' | 'success' | 'destructive';

Expand Down
31 changes: 31 additions & 0 deletions src/components/Loader/AnimatedPath.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import styled, { keyframes } from '@xstyled/styled-components';

import { tet } from '@/tetrisly';

const animationCircle = keyframes`
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
`;

const animationBar = keyframes`
0% {
transform: translate(-50%, 0px);
}
100% {
transform: translate(150%, 0px);
}
`;

export const AnimatedPath = styled(tet.path)<{
shape: string;
}>`
aspect-ratio: 1;
transform-origin: center center;
animation: ${({ shape }) =>
shape === 'circle' ? animationCircle : animationBar}
1.4s infinite linear;
`;
15 changes: 15 additions & 0 deletions src/components/Loader/Loader.props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SystemProps } from '@xstyled/styled-components';

import { config } from './Loader.styles';
import type { LoaderAppearance, LoaderShape, LoaderSize } from './types';

import { Theme } from '@/theme';
import { DeepPartial } from '@/utility-types/DeepPartial';

export type LoaderProps = {
appearance?: LoaderAppearance;
size?: LoaderSize;
progress?: number;
shape: LoaderShape;
custom?: DeepPartial<SystemProps<Theme> & typeof config>;
};
74 changes: 74 additions & 0 deletions src/components/Loader/Loader.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Meta, StoryObj } from '@storybook/react';
import { useEffect, useState } from 'react';

import { Loader } from './Loader';

const meta = {
title: 'Components/Loader',
component: Loader,
tags: ['autodocs'],
} satisfies Meta<typeof Loader>;

export default meta;
type Story = StoryObj<typeof meta>;

export const DefaultCircle: Story = {
args: {
shape: 'circle',
},
};

export const DefaultBar: Story = {
args: {
shape: 'bar',
},
};

export const ProgressCircle = () => {
const [progress, setProgress] = useState(0);
useEffect(() => {
const interval = setInterval(() => setProgress((p) => (p + 0.01) % 1), 50);
return () => clearInterval(interval);
}, []);
return <Loader shape="circle" progress={progress} />;
};

export const ProgressBar = () => {
const [progress, setProgress] = useState(0);
useEffect(() => {
const interval = setInterval(() => setProgress((p) => (p + 0.01) % 1), 50);
return () => clearInterval(interval);
}, []);
return <Loader shape="bar" progress={progress} />;
};

export const White: Story = {
args: {
shape: 'circle',
appearance: 'white',
},
parameters: {
backgrounds: {
default: 'dark',
},
},
};

export const Inverted: Story = {
args: {
shape: 'bar',
appearance: 'inverted',
},
parameters: {
backgrounds: {
default: 'dark',
},
},
};

export const Greyscale: Story = {
args: {
shape: 'circle',
appearance: 'greyscale',
},
};
100 changes: 100 additions & 0 deletions src/components/Loader/Loader.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { SystemProps } from '@xstyled/styled-components';
import { SvgProperties } from 'csstype';

import type { LoaderAppearance, LoaderShape, LoaderSize } from './types';

import { BaseConfigProps } from '@/utility-types/BaseConfigProps';

type Config = {
size: Record<
LoaderShape,
Record<LoaderSize, SystemProps & Pick<SvgProperties, 'strokeWidth'>>
>;
appearance: Record<
LoaderAppearance,
Record<'base' | 'progress', SystemProps>
>;
svg: SystemProps;
progress: SystemProps & Pick<SvgProperties, 'strokeLinecap'>;
};

export const config = {
size: {
circle: {
large: {
w: 48,
h: 48,
strokeWidth: '2',
},
medium: {
w: 32,
h: 32,
strokeWidth: '2',
},
small: {
w: 20,
h: 20,
strokeWidth: '2',
},
},
bar: {
large: {
w: 128,
h: 8,
strokeWidth: '8',
},
medium: {
w: 128,
h: 6,
strokeWidth: '6',
},
small: {
w: 128,
h: 4,
strokeWidth: '4',
},
},
},
appearance: {
primary: {
base: {
stroke: 'interaction-neutral-subtle-normal',
},
progress: {
stroke: 'interaction-default-normal',
},
},
inverted: {
base: {
stroke: 'interaction-inverted-normal',
},
progress: {
stroke: 'interaction-default-normal',
},
},
white: {
base: {
stroke: 'interaction-inverted-normal',
opacity: 0.4,
},
progress: {
stroke: 'interaction-inverted-normal',
},
},
greyscale: {
base: {
stroke: 'interaction-neutral-subtle-normal',
},
progress: {
stroke: 'interaction-neutral-normal',
},
},
},
svg: {
fill: 'none',
borderRadius: 'large',
},
progress: {
strokeLinecap: 'round',
},
} as const satisfies BaseConfigProps & Config;
90 changes: 90 additions & 0 deletions src/components/Loader/Loader.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Loader } from './Loader';
import { render } from '../../tests/render';

const getLoader = (jsx: JSX.Element) => {
const { queryByTestId } = render(jsx);

return {
loader: queryByTestId('loader'),
base: queryByTestId('loader-base'),
progress: queryByTestId('loader-progress'),
};
};

describe('Loader', () => {
it('should render the loader', () => {
const { loader } = getLoader(<Loader shape="circle" />);
expect(loader).toBeInTheDocument();
});

it('should render correct size (circle small)', () => {
const { loader } = getLoader(<Loader shape="circle" size="small" />);
expect(loader).toHaveStyle('width: 20px');
expect(loader).toHaveStyle('height: 20px');
});

it('should render correct size (circle medium)', () => {
const { loader } = getLoader(<Loader shape="circle" size="medium" />);
expect(loader).toHaveStyle('width: 32px');
expect(loader).toHaveStyle('height: 32px');
});

it('should render correct size (circle large)', () => {
const { loader } = getLoader(<Loader shape="circle" size="large" />);
expect(loader).toHaveStyle('width: 48px');
expect(loader).toHaveStyle('height: 48px');
});

it('should render correct size (bar small)', () => {
const { loader } = getLoader(<Loader shape="bar" size="small" />);
expect(loader).toHaveStyle('width: 128px');
expect(loader).toHaveStyle('height: 4px');
});

it('should render correct size (bar medium)', () => {
const { loader } = getLoader(<Loader shape="bar" size="medium" />);
expect(loader).toHaveStyle('width: 128px');
expect(loader).toHaveStyle('height: 6px');
});

it('should render correct size (bar large)', () => {
const { loader } = getLoader(<Loader shape="bar" size="large" />);
expect(loader).toHaveStyle('width: 128px');
expect(loader).toHaveStyle('height: 8px');
});

it('should animate when progress is undefined', () => {
const { progress } = getLoader(<Loader shape="bar" size="medium" />);
expect(progress).toHaveStyle('animation: fBJDHo 1.4s infinite linear');
});

it('should render correct appearance', () => {
const { base, progress } = getLoader(<Loader shape="circle" />);
expect(base).toHaveStyle('stroke: hsla(204,20%,95%,1);');
expect(progress).toHaveStyle('stroke: hsla(222,66%,51%,1);');
});

it('should render correct appearance (greyscale)', () => {
const { base, progress } = getLoader(
<Loader shape="circle" appearance="greyscale" />
);
expect(base).toHaveStyle('stroke: hsla(204,20%,95%,1); ');
expect(progress).toHaveStyle('stroke: hsla(210,12%,33%,1);');
});

it('should render correct appearance (inverted)', () => {
const { base, progress } = getLoader(
<Loader shape="circle" appearance="inverted" />
);
expect(base).toHaveStyle('stroke: hsla(0,0%,100%,1);');
expect(progress).toHaveStyle('stroke: hsla(222,66%,51%,1);');
});

it('should render correct appearance (white)', () => {
const { base, progress } = getLoader(
<Loader shape="circle" appearance="white" />
);
expect(base).toHaveStyle('stroke: hsla(0,0%,100%,1);');
expect(progress).toHaveStyle('stroke: hsla(0,0%,100%,1);');
});
});
Loading

0 comments on commit 261715b

Please sign in to comment.