Skip to content
Open
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
1 change: 1 addition & 0 deletions libs/react/ui-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './lib/progress/progress';
export * from './lib/switch/switch';
export * from './lib/modal/modal';
export * from './lib/no-data/no-data';
Expand Down
18 changes: 18 additions & 0 deletions libs/react/ui-core/src/lib/progress/ProgressProps.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DefaultParams } from '../../default-types/defaultParams';

export interface ProgressProps extends DefaultParams {
/** Current step of the progress */
currentStep: number;
/** Total amount of steps */
steps: number;
/** Type of the progress */
type?: 'linear' | 'radial';
/** Size of the progress */
size?: 'small' | 'standart';
/** State of the process shown by progress */
state?: 'progress' | 'error' | 'success';
/** Minimum width for label in linear progress */
minLabelWidth?: string;
/** Formatter to show progress. By default x/y */
format?: (currentStep: number, steps: number) => string;
}
1 change: 1 addition & 0 deletions libs/react/ui-core/src/lib/progress/progress.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@use '../../../../../../styles/components/progress' as *;
10 changes: 10 additions & 0 deletions libs/react/ui-core/src/lib/progress/progress.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { render } from '@testing-library/react';

import { Progress } from './progress';

describe('Progress', () => {
it('should render successfully', () => {
const { baseElement } = render(<Progress currentStep={2} steps={4} />);
expect(baseElement).toBeTruthy();
});
});
14 changes: 14 additions & 0 deletions libs/react/ui-core/src/lib/progress/progress.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Progress } from './progress';

export default {
component: Progress,
title: 'Progress',
} as ComponentMeta<typeof Progress>;

const Template: ComponentStory<typeof Progress> = (args) => (
<Progress {...args} />
);

export const Primary = Template.bind({});
Primary.args = {currentStep: 0, steps: 10};
127 changes: 127 additions & 0 deletions libs/react/ui-core/src/lib/progress/progress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import styles from './progress.module.scss';
import { forwardRef, memo, useMemo } from 'react';
import { ProgressProps } from './ProgressProps';
import { useProgress } from './useProgress';
import Icon from '../icon/icon';

const LinearBar = memo(({ fillPercentage }: { fillPercentage: string }) => {
return (
<div className={styles['figure']}>
<div className={styles['track']}></div>
<div
className={styles['indicator']}
style={{ width: fillPercentage }}
></div>
</div>
);
});

const RadialBar = memo(
({ dashArray, dashOffset }: { dashArray: number; dashOffset: number }) => {
return (
<svg
viewBox="0 0 164 164"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={styles['figure']}
>
<circle
className={styles['track']}
cx="82"
cy="82"
r="72"
strokeWidth="20"
/>
<circle
strokeDasharray={dashArray}
strokeDashoffset={dashOffset}
className={styles['indicator']}
strokeLinecap="round"
cx="82"
cy="82"
r="72"
strokeWidth="20"
/>
</svg>
);
}
);

const Label = memo(
({
labelText,
state,
type,
minWidth,
}: {
labelText?: string;
state?: 'progress' | 'error' | 'success';
type?: 'linear' | 'radial';
minWidth?: string;
}) => {
const content = useMemo(() => {
switch (state) {
case 'progress':
return (
<div
className={styles['label']}
{...(type === 'linear' && { style: { minWidth: minWidth } })}
>
{labelText}
</div>
);
break;
case 'error':
return (
<Icon
name={type === 'linear' ? 'close-circle' : 'close'}
type="fill"
className={styles['label-icon']}
/>
);
break;
case 'success':
return (
<Icon
name={type === 'linear' ? 'checkbox-circle' : 'check'}
type="fill"
className={styles['label-icon']}
/>
);
break;
default:
return <div className={styles['label']}>{labelText}</div>;
break;
}
}, [labelText, state, type]);
return content;
}
);

export const Progress = forwardRef((props: ProgressProps, ref: any) => {
const { classes, fillPercentage, labelText, dashArray, dashOffset } =
useProgress(props);
return (
<div className={classes} ref={ref} style={props.style}>
{props.type === 'linear' ? (
<LinearBar fillPercentage={fillPercentage} />
) : (
<RadialBar dashArray={dashArray} dashOffset={dashOffset} />
)}
<Label
labelText={labelText}
state={props.state}
type={props.type}
minWidth={props.minLabelWidth}
/>
</div>
);
});

Progress.defaultProps = {
size: 'standart',
type: 'linear',
state: 'progress',
minLabelWidth: '40px',
format: (currentStep: number, steps: number) => `${currentStep}/${steps}`,
};
52 changes: 52 additions & 0 deletions libs/react/ui-core/src/lib/progress/useProgress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import styles from './progress.module.scss';
import { useMemo } from 'react';
import { getClasses } from '../../utils/getClasses';
import { ProgressProps } from './ProgressProps';

export function useProgress(props: ProgressProps) {
const classes = useMemo(() => {
const conditions = {
'progress': true,
'progress-success': props.state === 'success',
'progress-error': props.state === 'error',
'progress-regular': props.state === 'progress',
'progress-radial': props.type === 'radial',
'progress-linear': props.type === 'linear',
'progress-small': props.size === 'small',
'progress-standart': props.size === 'standart',
};
return getClasses(conditions, styles, props.className);
}, [props.state, props.size, props.className, props.type]);

const fillPortion = useMemo(() => {
// Rounds to 2 digits.
const portion = Math.round((props.currentStep / props.steps) * 100) / 100;
return portion <= 1 ? (portion >= 0 ? portion : 0) : 1;
}, [props.currentStep, props.steps]);

const dashArray = useMemo(() => {
const radius = 72;
return 2 * Math.PI * radius;
}, [props.size]);

const dashOffset = useMemo(() => {
return dashArray * (1 - fillPortion);
}, [dashArray, fillPortion]);

const fillPercentage = useMemo(() => {
return `${fillPortion * 100}%`;
}, [fillPortion]);

const labelText = useMemo(() => {
return props.format?.(props.currentStep, props.steps);
}, [props.currentStep, props.steps, props.format]);

return {
classes,
fillPortion,
fillPercentage,
labelText,
dashArray,
dashOffset,
};
}
Loading