Skip to content

Commit 4972ad1

Browse files
authored
chore: refactor ProgressBar to use radix primitive (#1732)
1 parent c7fc2dd commit 4972ad1

File tree

12 files changed

+222
-112
lines changed

12 files changed

+222
-112
lines changed

.changeset/shy-masks-share.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@strapi/design-system': major
3+
---
4+
5+
chore: refactor ProgressBar to use radix primitive

docs/.storybook/preview-head.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<style>
2+
html {
3+
height: 100%;
4+
}
5+
26
body {
37
margin: 0;
48
padding: 0 !important;

docs/stories/04-components/Dialog.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ A dialog that interrupts the user with important content and expects a response.
2525
## Usage
2626

2727
```js
28-
import { Modal } from '@strapi/design-system';
28+
import { Dialog } from '@strapi/design-system';
2929
```
3030

3131
## Props

docs/stories/04-components/Dialog.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const meta: Meta<DialogArgs> = {
1313
title: 'Components/Dialog',
1414
decorators: [
1515
(Story) => (
16-
<Flex style={{ width: '1280px', height: '720px' }} justifyContent="center">
16+
<Flex width="100%" height="100%" justifyContent="center">
1717
<Story />
1818
</Flex>
1919
),

docs/stories/04-components/Modal.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const meta: Meta<ModalArgs> = {
2929
component: Modal.Root,
3030
decorators: [
3131
(Story) => (
32-
<Flex style={{ width: '1280px', height: '720px' }} justifyContent="center">
32+
<Flex width="100%" height="100%" justifyContent="center">
3333
<Story />
3434
</Flex>
3535
),
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Meta, Canvas } from '@storybook/blocks';
2+
import { ProgressBar } from '@strapi/design-system';
3+
4+
import * as ProgressBarStories from './ProgressBar.stories';
5+
6+
<Meta of={ProgressBarStories} />
7+
8+
# ProgressBar
9+
10+
- [Overview](#overview)
11+
- [Usage](#usage)
12+
- [Props](#props)
13+
- [Accessibility](#accessibility)
14+
15+
Displays an indicator showing the completion progress of a task, typically displayed as a progress bar. Automatically
16+
animates to the new progress value.
17+
18+
<ViewSource path="components/ProgressBar" />
19+
20+
<Canvas of={ProgressBarStories.Animated} />
21+
22+
## Usage
23+
24+
```js
25+
import { ProgressBar } from '@strapi/design-system';
26+
```
27+
28+
By default, the `ProgressBar` does not automatically update it's progress.
29+
30+
<Canvas of={ProgressBarStories.Base} />
31+
32+
## Props
33+
34+
<TypeTable of={ProgressBar} />
35+
36+
## Accessibility
37+
38+
Adheres to the [Meter WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/meter/).
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import * as React from 'react';
2+
3+
import { Meta, StoryObj } from '@storybook/react';
4+
import { Flex, ProgressBar, ProgressBarProps, Typography } from '@strapi/design-system';
5+
import { outdent } from 'outdent';
6+
7+
interface ProgressArgs extends ProgressBarProps {}
8+
9+
const meta: Meta<ProgressArgs> = {
10+
title: 'Components/ProgressBar',
11+
component: ProgressBar,
12+
parameters: {
13+
chromatic: { disableSnapshot: false },
14+
},
15+
decorators: [
16+
(Story) => (
17+
<Flex padding={10} width="100%" height="100%" justifyContent="center" background="neutral800">
18+
<Story />
19+
</Flex>
20+
),
21+
],
22+
render: (args) => {
23+
return <ProgressBar {...args} />;
24+
},
25+
argTypes: {
26+
size: {
27+
control: 'select',
28+
options: ['S', 'M'],
29+
},
30+
value: {
31+
control: 'range',
32+
},
33+
},
34+
args: {
35+
children: 'loading',
36+
size: 'M',
37+
value: 50,
38+
},
39+
};
40+
41+
export default meta;
42+
43+
type Story = StoryObj<ProgressArgs>;
44+
45+
export const Base = {
46+
name: 'base',
47+
} satisfies Story;
48+
49+
export const Animated = {
50+
name: 'animated preview',
51+
parameters: {
52+
chromatic: { disableSnapshot: true },
53+
docs: {
54+
source: {
55+
code: outdent`
56+
<Flex direction="column" gap={4}>
57+
<ProgressBar {...args} value={progress} />
58+
<Typography textColor="neutral0">{\`Loading \${progress}%\`}</Typography>
59+
</Flex>
60+
`,
61+
},
62+
},
63+
},
64+
render: (args) => {
65+
const [progress, setProgress] = React.useState(0);
66+
const interval = React.useRef<NodeJS.Timeout>();
67+
68+
const startAnimation = React.useCallback(() => {
69+
interval.current = setInterval(() => {
70+
setProgress((prev) => {
71+
/**
72+
* Random number between 0-10
73+
*/
74+
const newProgress = Math.floor(Math.random() * 10);
75+
76+
return Math.min(prev + newProgress, 100);
77+
});
78+
}, 400);
79+
}, []);
80+
81+
React.useEffect(() => {
82+
startAnimation();
83+
84+
return () => {
85+
if (interval.current) {
86+
clearInterval(interval.current);
87+
interval.current = undefined;
88+
}
89+
};
90+
}, [startAnimation]);
91+
92+
React.useEffect(() => {
93+
if (progress === 100) {
94+
if (interval.current) {
95+
clearInterval(interval.current);
96+
setProgress(0);
97+
startAnimation();
98+
}
99+
}
100+
}, [progress, startAnimation]);
101+
102+
return (
103+
<Flex direction="column" gap={4}>
104+
<ProgressBar {...args} value={progress} />
105+
<Typography textColor="neutral0">{`Loading ${progress}%`}</Typography>
106+
</Flex>
107+
);
108+
},
109+
} satisfies Story;

docs/stories/ProgressBar.mdx

Lines changed: 0 additions & 36 deletions
This file was deleted.

docs/stories/ProgressBar.stories.tsx

Lines changed: 0 additions & 33 deletions
This file was deleted.

packages/design-system/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@radix-ui/react-dropdown-menu": "2.0.6",
2525
"@radix-ui/react-focus-scope": "1.0.4",
2626
"@radix-ui/react-popover": "1.0.7",
27+
"@radix-ui/react-progress": "1.0.3",
2728
"@radix-ui/react-radio-group": "1.1.3",
2829
"@radix-ui/react-scroll-area": "1.0.5",
2930
"@radix-ui/react-tabs": "1.0.4",
Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,49 @@
1-
import { styled } from 'styled-components';
2-
3-
import { Box, BoxComponent, BoxProps } from '../Box';
1+
import * as React from 'react';
42

5-
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
6-
? Acc[number]
7-
: Enumerate<N, [...Acc, Acc['length']]>;
3+
import * as Progress from '@radix-ui/react-progress';
4+
import { styled } from 'styled-components';
85

9-
type IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
6+
type Size = 'S' | 'M';
107

11-
export interface ProgressBarProps
12-
extends Omit<BoxProps, 'background' | 'children' | 'hasRadius' | 'height' | 'position' | 'width'> {
8+
interface ProgressBarProps extends Progress.ProgressProps {
139
children?: string;
14-
max?: IntRange<0, 101>;
15-
min?: IntRange<0, 101>;
16-
size?: 'S' | 'M';
17-
value?: IntRange<0, 101>;
10+
size?: Size;
1811
}
1912

20-
const ProgressbarBase = styled<BoxComponent>(Box)<{ $value: number }>`
21-
&:before {
22-
background-color: ${({ theme }) => theme.colors.neutral0};
23-
border-radius: ${({ theme }) => theme.borderRadius};
24-
bottom: 0;
25-
content: '';
26-
position: absolute;
27-
top: 0;
28-
width: ${({ $value }) => `${$value}%`};
13+
const ProgressBar = React.forwardRef<HTMLDivElement, ProgressBarProps>(
14+
({ size = 'M', value, ...restProps }, forwardedRef) => {
15+
return (
16+
<ProgressRoot ref={forwardedRef} $size={size} {...restProps}>
17+
<ProgressIndicator style={{ transform: `translate3D(-${100 - (value ?? 0)}%, 0, 0)` }} />
18+
</ProgressRoot>
19+
);
20+
},
21+
);
22+
23+
const ProgressRoot = styled(Progress.Root)<{ $size: Size }>`
24+
position: relative;
25+
overflow: hidden;
26+
width: ${(props) => (props.$size === 'S' ? '7.8rem' : '10.2rem')};
27+
height: ${(props) => (props.$size === 'S' ? '0.4rem' : '0.8rem')};
28+
background-color: ${(props) => props.theme.colors.neutral600};
29+
border-radius: ${(props) => props.theme.borderRadius};
30+
31+
/* Fix overflow clipping in Safari */
32+
/* https://gist.github.com/domske/b66047671c780a238b51c51ffde8d3a0 */
33+
transform: translateZ(0);
34+
`;
35+
36+
const ProgressIndicator = styled(Progress.Indicator)`
37+
background-color: ${({ theme }) => theme.colors.neutral0};
38+
border-radius: ${({ theme }) => theme.borderRadius};
39+
width: 100%;
40+
height: 100%;
41+
42+
@media (prefers-reduced-motion: no-preference) {
43+
transition: transform ${(props) => props.theme.motion.timings['320']}
44+
${(props) => props.theme.motion.easings.authenticMotion};
2945
}
3046
`;
3147

32-
export const ProgressBar = ({ min = 0, max = 100, value = 0, children, size = 'M', ...props }: ProgressBarProps) => {
33-
return (
34-
<ProgressbarBase
35-
background="neutral600"
36-
hasRadius
37-
aria-label={children}
38-
aria-valuemax={max}
39-
aria-valuemin={min}
40-
aria-valuenow={value}
41-
height={size === 'S' ? 1 : 2}
42-
position="relative"
43-
role="progressbar"
44-
$value={value}
45-
width={size === 'S' ? '78px' : '102px'}
46-
{...props}
47-
/>
48-
);
49-
};
48+
export { ProgressBar };
49+
export type { ProgressBarProps, Size as ProgressBarSize };

yarn.lock

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4099,6 +4099,27 @@ __metadata:
40994099
languageName: node
41004100
linkType: hard
41014101

4102+
"@radix-ui/react-progress@npm:1.0.3":
4103+
version: 1.0.3
4104+
resolution: "@radix-ui/react-progress@npm:1.0.3"
4105+
dependencies:
4106+
"@babel/runtime": ^7.13.10
4107+
"@radix-ui/react-context": 1.0.1
4108+
"@radix-ui/react-primitive": 1.0.3
4109+
peerDependencies:
4110+
"@types/react": "*"
4111+
"@types/react-dom": "*"
4112+
react: ^16.8 || ^17.0 || ^18.0
4113+
react-dom: ^16.8 || ^17.0 || ^18.0
4114+
peerDependenciesMeta:
4115+
"@types/react":
4116+
optional: true
4117+
"@types/react-dom":
4118+
optional: true
4119+
checksum: a4398812315ae5b25f8637a6553daf85bd36e6e08d15f7486248538a994a99268752e2c21e4af15277c17e861c1825fdd7292938c515c112dc2cdce63ec1a892
4120+
languageName: node
4121+
linkType: hard
4122+
41024123
"@radix-ui/react-radio-group@npm:1.1.3":
41034124
version: 1.1.3
41044125
resolution: "@radix-ui/react-radio-group@npm:1.1.3"
@@ -5586,6 +5607,7 @@ __metadata:
55865607
"@radix-ui/react-dropdown-menu": 2.0.6
55875608
"@radix-ui/react-focus-scope": 1.0.4
55885609
"@radix-ui/react-popover": 1.0.7
5610+
"@radix-ui/react-progress": 1.0.3
55895611
"@radix-ui/react-radio-group": 1.1.3
55905612
"@radix-ui/react-scroll-area": 1.0.5
55915613
"@radix-ui/react-tabs": 1.0.4

0 commit comments

Comments
 (0)