Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ProgressBarStepped): create component #905

Merged
merged 8 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 5 additions & 0 deletions playroom/snippets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2484,6 +2484,11 @@ export default [
name: 'ProgressBar',
code: '<ProgressBar progressPercent={35} />',
},
{
group: 'Progress',
name: 'ProgressBarStepped',
code: '<ProgressBarStepped steps={6} currentStep={3} />',
},
{
group: 'NavigationBreadcrumbs',
name: 'NavigationBreadcrumbs',
Expand Down
marcoskolodny marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 11 additions & 1 deletion src/__screenshot_tests__/progress-bar-screenshot-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ import {openStoryPage, screen} from '../test-utils';
const COLORS = ['default', 'red'];

test.each(COLORS)('ProgressBar - color={%s}', async (color) => {
await openStoryPage({id: 'components-progressbar--default', args: {color}});
await openStoryPage({id: 'components-progress-bar--progress-bar-story', args: {color}});

const stepper = await screen.findByTestId('progress-bar');

const image = await stepper.screenshot();

expect(image).toMatchImageSnapshot();
});

test.each(COLORS)('ProgressBarStepped - color={%s}', async (color) => {
await openStoryPage({id: 'components-progress-bar--progress-bar-stepped-story', args: {color}});

const stepper = await screen.findByTestId('progress-bar-stepped');

const image = await stepper.screenshot();

expect(image).toMatchImageSnapshot();
});
58 changes: 45 additions & 13 deletions src/__stories__/progress-bar-story.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react';
import {ProgressBar} from '..';
import {ProgressBar, ProgressBarStepped} from '..';

export default {
title: 'Components/ProgressBar',
title: 'Components/Progress bar',
argTypes: {
color: {
options: ['default', 'red'],
Expand All @@ -11,25 +11,57 @@ export default {
},
};

type Args = {
type ProgressBarStoryArgs = {
reverse: boolean;
progressPercent: number;
color: 'default' | 'red';
marcoskolodny marked this conversation as resolved.
Show resolved Hide resolved
};

export const Default: StoryComponent<Args> = ({reverse, progressPercent, color}) => (
<div data-testid="progress-bar">
<ProgressBar
progressPercent={progressPercent}
reverse={reverse}
color={color === 'default' ? undefined : color}
/>
</div>
export const ProgressBarStory: StoryComponent<ProgressBarStoryArgs> = ({reverse, progressPercent, color}) => (
<ProgressBar
dataAttributes={{testid: 'progress-bar'}}
progressPercent={progressPercent}
reverse={reverse}
color={color === 'default' ? undefined : color}
/>
);

Default.storyName = 'ProgressBar';
Default.args = {
type ProgressBarSteppedStoryArgs = {
steps: number;
currentStep: number;
color: 'default' | 'red';
};

export const ProgressBarSteppedStory: StoryComponent<ProgressBarSteppedStoryArgs> = ({
steps,
currentStep,
color,
}) => (
<ProgressBarStepped
steps={steps}
currentStep={currentStep}
dataAttributes={{testid: 'progress-bar-stepped'}}
color={color === 'default' ? undefined : color}
/>
);

ProgressBarStory.storyName = 'ProgressBar';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move this up, near ProgressBarStory definition

ProgressBarSteppedStory.storyName = 'ProgressBarStepped';

ProgressBarStory.args = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for this

reverse: false,
progressPercent: 30,
color: 'default',
};

ProgressBarSteppedStory.args = {
steps: 4,
currentStep: 3,
color: 'default',
};

ProgressBarSteppedStory.argTypes = {
steps: {
control: {type: 'range', min: 1, max: 6, step: 1},
},
};
2 changes: 1 addition & 1 deletion src/community/blocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Text2, Text3, Text5, Text8} from '../text';
import {vars} from '../skins/skin-contract.css';
import Inline from '../inline';
import Box from '../box';
import ProgressBar from '../progress-bar';
import {ProgressBar} from '../progress-bar';
import classNames from 'classnames';

import type StackingGroup from '../stacking-group';
Expand Down
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export {default as Inline} from './inline';
export {default as HorizontalScroll} from './horizontal-scroll';
export {default as HighlightedCard} from './highlighted-card';
export {default as Stepper} from './stepper';
export {default as ProgressBar} from './progress-bar';
export {ProgressBar, ProgressBarStepped} from './progress-bar';
export {
MediaCard,
DataCard,
Expand Down
28 changes: 18 additions & 10 deletions src/progress-bar.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,24 @@ const barKeyFramesInverte = keyframes({
},
});

export const normal = style([
{
transition: `max-width ${transition}`,
animation: `${barKeyFrames} ${transition}`,
export const normal = style({
transition: `max-width ${transition}`,
animation: `${barKeyFrames} ${transition}`,
'@media': {
['(prefers-reduced-motion)']: {
transition: 'none',
animation: 'none',
},
},
]);
});

export const inverse = style([
{
transition: `max-width ${transition}`,
animation: `${barKeyFramesInverte} ${transition}`,
export const inverse = style({
transition: `max-width ${transition}`,
animation: `${barKeyFramesInverte} ${transition}`,
'@media': {
['(prefers-reduced-motion)']: {
transition: 'none',
animation: 'none',
},
},
]);
});
90 changes: 83 additions & 7 deletions src/progress-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import {vars} from './skins/skin-contract.css';
import * as styles from './progress-bar.css';
import {getPrefixedDataAttributes} from './utils/dom';
import classNames from 'classnames';
import Inline from './inline';

import type {DataAttributes} from './utils/types';

type Props = {
type ProgressBarProps = {
progressPercent: number;
color?: string;
children?: void;
Expand All @@ -17,7 +18,7 @@ type Props = {
reverse?: boolean;
};

const ProgressBar: React.FC<Props> = ({
export const ProgressBar: React.FC<ProgressBarProps> = ({
progressPercent,
color,
'aria-label': ariaLabel,
Expand All @@ -26,14 +27,20 @@ const ProgressBar: React.FC<Props> = ({
reverse = false,
}) => {
const {texts} = useTheme();
const defaultLabel = texts.loading;
const label = ariaLabelledBy ? undefined : ariaLabel || defaultLabel;
const progressValue = Math.max(0, Math.min(100, progressPercent));

const getFormattedLabel = () => {
return `${ariaLabel || texts.loading}, ${progressValue}% ${texts.progressBarCompletedLabel}`;
};

const label = ariaLabelledBy ? undefined : getFormattedLabel();

return (
<div
{...getPrefixedDataAttributes(dataAttributes, 'ProgressBar')}
className={styles.barBackground}
role="progressbar"
aria-valuenow={progressPercent}
aria-valuenow={progressValue}
aria-valuemin={0}
aria-valuemax={100}
aria-label={label}
Expand All @@ -42,12 +49,81 @@ const ProgressBar: React.FC<Props> = ({
<div
className={classNames(styles.bar, reverse ? styles.inverse : styles.normal)}
style={{
maxWidth: `${progressPercent}%`,
maxWidth: `${progressValue}%`,
backgroundColor: color ?? vars.colors.controlActivated,
}}
/>
</div>
);
};

export default ProgressBar;
type ProgressBarSteppedProps = {
steps: number;
currentStep?: number;
color?: string;
dataAttributes?: DataAttributes;
'aria-label'?: string;
'aria-labelledby'?: string;
};

export const ProgressBarStepped: React.FC<ProgressBarSteppedProps> = ({
steps,
currentStep = 0,
color,
dataAttributes,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
}) => {
const {texts} = useTheme();
const step = Math.max(0, Math.min(steps, Math.ceil(currentStep)));
const previousStepRef = React.useRef(step);
const isBack = previousStepRef.current > step;

const getFormattedLabel = () => {
const label = texts.progressBarStepLabel.replace('1$s', String(step)).replace('2$s', String(steps));
return ariaLabel ? `${ariaLabel}, ${label.toLowerCase()}` : label;
};

const label = ariaLabelledBy ? undefined : getFormattedLabel();

React.useEffect(() => {
previousStepRef.current = step;
}, [step]);

return (
<div
{...getPrefixedDataAttributes(dataAttributes, 'ProgressBarStepped')}
role="progressbar"
aria-valuenow={step}
aria-valuemin={0}
aria-valuemax={steps}
aria-label={label}
aria-labelledby={ariaLabelledBy}
>
<Inline space={8} fullWidth>
{Array.from({length: steps}, (_, index) => {
const isCurrent = index === step;
const isCompleted = index < step;
const hasAnimation = index === step - 1;

return (
<div key={index} className={styles.barBackground} aria-hidden="true">
{(isCompleted || isCurrent) && (
<div
className={classNames(styles.bar, {
[styles.normal]: hasAnimation && !isBack,
[styles.inverse]: isCurrent && isBack,
})}
style={{
backgroundColor: color ?? vars.colors.controlActivated,
maxWidth: isCompleted || (hasAnimation && !isBack) ? '100%' : '0',
}}
/>
)}
</div>
);
})}
</Inline>
</div>
);
};
8 changes: 8 additions & 0 deletions src/theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const TEXTS_ES = {
playIconButtonLabel: 'Reproducir',
pauseIconButtonLabel: 'Pausar',
sheetConfirmButton: 'Continuar',
progressBarCompletedLabel: 'completo',
progressBarStepLabel: 'Paso 1$s de 2$s',
};

const TEXTS_EN: ThemeTexts = {
Expand Down Expand Up @@ -77,6 +79,8 @@ const TEXTS_EN: ThemeTexts = {
playIconButtonLabel: 'Play',
pauseIconButtonLabel: 'Pause',
sheetConfirmButton: 'Continue',
progressBarCompletedLabel: 'completed',
progressBarStepLabel: 'Step 1$s of 2$s',
};

const TEXTS_DE: ThemeTexts = {
Expand Down Expand Up @@ -113,6 +117,8 @@ const TEXTS_DE: ThemeTexts = {
playIconButtonLabel: 'Abspielen',
pauseIconButtonLabel: 'Pausieren',
sheetConfirmButton: 'Fortfahren',
progressBarCompletedLabel: 'vollendet',
progressBarStepLabel: 'Schritt 1$s von 2$s',
};

const TEXTS_PT: ThemeTexts = {
Expand Down Expand Up @@ -149,6 +155,8 @@ const TEXTS_PT: ThemeTexts = {
playIconButtonLabel: 'Reproduzir',
pauseIconButtonLabel: 'Pausar',
sheetConfirmButton: 'Continuar',
progressBarCompletedLabel: 'concluído',
progressBarStepLabel: 'Etapa 1$s de 2$s',
};

export const getTexts = (locale: Locale): typeof TEXTS_ES => {
Expand Down