Skip to content

Commit

Permalink
refactor: tet-218 loader (#63)
Browse files Browse the repository at this point in the history
* refactor: TET-218 loader

* fix: TET-218 explicity typed styles builder

* fix: workaround of svg props TET-218

---------

Co-authored-by: Adrian Potępa <[email protected]>
  • Loading branch information
mateusz-kleszcz and adrian-potepa authored Sep 13, 2023
1 parent 4828690 commit 90c3d4d
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const animationBar = keyframes`
}
`;

export const AnimatedPath = styled(tet.path)<{
export const AnimatedProgress = styled(tet.path)<{
shape: string;
}>`
aspect-ratio: 1;
Expand Down
6 changes: 2 additions & 4 deletions src/components/Loader/Loader.props.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { LoaderConfig } from './Loader.styles';
import type { LoaderConfig } from './Loader.styles';
import type { LoaderAppearance, LoaderShape, LoaderSize } from './types';

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

export type LoaderProps = {
appearance?: LoaderAppearance;
size?: LoaderSize;
progress?: number;
shape: LoaderShape;
custom?: DeepPartial<LoaderConfig>;
custom?: LoaderConfig;
};
184 changes: 107 additions & 77 deletions src/components/Loader/Loader.styles.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,130 @@
import { SystemProps } from '@xstyled/styled-components';
import { SvgProperties } from 'csstype';
import { ThemeColor } from '@xstyled/styled-components';
import type { Property } from 'csstype';

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

import { Theme } from '@/theme';
import { BaseProps } from '@/types/BaseProps';

export type SVGProps = Omit<
BaseProps,
| 'opacity'
| 'display'
| 'order'
| 'cursor'
| 'pointerEvents'
| 'overflow'
| 'visibility'
| 'fill'
| 'transform'
| 'rotate'
| 'scale'
| 'stroke'
| 'fontFamily'
| 'fontSize'
| 'fontStyle'
| 'fontStyle'
| 'fontVariant'
| 'fontWeight'
| 'letterSpacing'
| 'textDecoration'
> & {
fill?: ThemeColor<Theme> | Property.Fill;
strokeWidth?: Property.StrokeWidth<string | number>;
strokeLinecap?: 'inherit' | 'round' | 'butt' | 'square';
};

export type LoaderConfig = {
size: Record<
LoaderShape,
Record<LoaderSize, SystemProps & Pick<SvgProperties, 'strokeWidth'>>
>;
appearance: Record<
LoaderAppearance,
Record<'base' | 'progress', SystemProps>
shape?: Partial<
Record<LoaderShape, { size?: Partial<Record<LoaderSize, SVGProps>> }>
>;
svg: SystemProps;
progress: SystemProps & Pick<SvgProperties, 'strokeLinecap'>;
innerElements?: {
base?: {
appearance?: Partial<Record<LoaderAppearance, BaseProps>>;
} & SVGProps;
progress?: {
appearance?: Partial<Record<LoaderAppearance, BaseProps>>;
} & SVGProps;
};
} & BaseProps;

export const defaultConfig = {
size: {
fill: 'none',
borderRadius: 'large',
shape: {
circle: {
large: {
w: 48,
h: 48,
strokeWidth: '2',
},
medium: {
w: 32,
h: 32,
strokeWidth: '2',
},
small: {
w: 20,
h: 20,
strokeWidth: '2',
size: {
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',
size: {
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',
innerElements: {
base: {
appearance: {
primary: {
stroke: 'interaction-neutral-subtle-normal',
},
inverted: {
stroke: 'interaction-inverted-normal',
},
white: {
stroke: 'interaction-inverted-normal',
opacity: 0.4,
},
greyscale: {
stroke: 'interaction-neutral-subtle-normal',
},
},
},
greyscale: {
base: {
stroke: 'interaction-neutral-subtle-normal',
},
progress: {
stroke: 'interaction-neutral-normal',
progress: {
strokeLinecap: 'round',
appearance: {
primary: {
stroke: 'interaction-default-normal',
},
inverted: {
stroke: 'interaction-default-normal',
},
white: {
stroke: 'interaction-inverted-normal',
},
greyscale: {
stroke: 'interaction-neutral-normal',
},
},
},
},
svg: {
fill: 'none',
borderRadius: 'large',
},
progress: {
strokeLinecap: 'round',
},
} satisfies LoaderConfig;
16 changes: 16 additions & 0 deletions src/components/Loader/Loader.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Loader } from './Loader';
import { render } from '../../tests/render';

import { customPropTester } from '@/tests/customPropTester';

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

Expand All @@ -12,6 +14,20 @@ const getLoader = (jsx: JSX.Element) => {
};

describe('Loader', () => {
customPropTester(<Loader shape="circle" />, {
containerId: 'loader',
props: {
size: ['small', 'medium', 'big'],
appearance: ['primary', 'white', 'inverted', 'greyscale'],
shape: ['circle', 'bar'],
},
innerElements: {
_: [['shape', 'size']],
base: ['appearance'],
progress: ['appearance'],
},
});

it('should render the loader', () => {
const { loader } = getLoader(<Loader shape="circle" />);
expect(loader).toBeInTheDocument();
Expand Down
23 changes: 13 additions & 10 deletions src/components/Loader/Loader.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { MarginProps } from '@xstyled/styled-components';
import { useMemo } from 'react';
import { FC, useMemo } from 'react';

import { AnimatedPath } from './AnimatedPath';
import { AnimatedProgress } from './AnimatedProgress';
import { LoaderProps } from './Loader.props';
import { stylesBuilder } from './stylesBuilder';

import { tet } from '@/tetrisly';

export const Loader = ({
export const Loader: FC<LoaderProps & MarginProps> = ({
appearance = 'primary',
progress,
shape,
size = 'medium',
custom,
}: LoaderProps & MarginProps) => {
const { svgStyles, baseStyles, progressStyles } = useMemo(
...restProps
}) => {
const styles = useMemo(
() =>
stylesBuilder({
appearance,
Expand All @@ -25,21 +26,23 @@ export const Loader = ({
}),
[appearance, progress, shape, size, custom],
);

return (
<tet.svg
{...svgStyles}
{...styles.container}
xmlns="http://www.w3.org/2000/svg"
data-testid="loader"
{...restProps}
>
<tet.path {...baseStyles} data-testid="loader-base" />
<tet.path {...styles.base} data-testid="loader-base" />
{progress === undefined ? (
<AnimatedPath
<AnimatedProgress
shape={shape}
{...progressStyles}
{...styles.progress}
data-testid="loader-progress"
/>
) : (
<tet.path {...progressStyles} data-testid="loader-progress" />
<tet.path {...styles.progress} data-testid="loader-progress" />
)}
</tet.svg>
);
Expand Down
Loading

0 comments on commit 90c3d4d

Please sign in to comment.