Skip to content

Commit 322d26b

Browse files
authored
Merge pull request #133 from yaswanth-deriv/yaswanth/FEQ-1858_Implement_circular_progressbar
[FEQ]Yaswanth/FEQ-1858_Implemented Circular ProgressBar component
2 parents 8f71df3 + 49b41d9 commit 322d26b

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.deriv-circular-progress {
2+
position: relative;
3+
line-height: 0;
4+
width: fit-content;
5+
6+
&__content {
7+
position: absolute;
8+
inset:0;
9+
display: flex;
10+
flex-direction: column;
11+
align-items: center;
12+
justify-content: center;
13+
}
14+
15+
&__bar {
16+
transform: scaleX(-1) rotate(-90deg);
17+
transform-origin: 50% 50%;
18+
transition: stroke-dashoffset 1s;
19+
stroke: var(--du-brand-secondary, #85acb0);
20+
21+
&--warning {
22+
stroke: var(--du-status-warning, #ffad3a);
23+
}
24+
25+
&--danger {
26+
stroke: var(--du-status-danger, #ec3f3f)
27+
}
28+
}
29+
30+
&--clockwise {
31+
transform: rotate(-90deg);
32+
}
33+
34+
&__icon {
35+
position: absolute;
36+
width: 1.6rem;
37+
height: 100%;
38+
left: 50%;
39+
top: 50%;
40+
transform: translate(-50%, -50%);
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
import { CircularProgressBar } from "..";
4+
5+
describe("CircularProgressBar Component", () => {
6+
it("renders correctly with default props", () => {
7+
const { container } = render(<CircularProgressBar/>);
8+
expect(container.querySelector(".deriv-circular-progress__bar")).toBeInTheDocument();
9+
});
10+
11+
it("renders children correctly", () => {
12+
const { getByText } = render(
13+
<CircularProgressBar>
14+
<div className="circular-progress-content">Child content</div>
15+
</CircularProgressBar>
16+
);
17+
expect(getByText("Child content")).toBeInTheDocument();
18+
});
19+
20+
it("renders correctly with isClockwise prop as True", () => {
21+
const { container } = render(<CircularProgressBar is_clockwise/>);
22+
expect(container.querySelector(".deriv-circular-progress--clockwise")).toBeInTheDocument();
23+
});
24+
25+
it("renders circular progress bar with custom className", () => {
26+
const { container } = render(<CircularProgressBar className="custom-class" />);
27+
const circularProgressBar = container.querySelector(".custom-class");
28+
expect(circularProgressBar).toBeInTheDocument();
29+
expect(circularProgressBar).toHaveClass("deriv-circular-progress", "custom-class");
30+
});
31+
32+
it("renders circular progress bar with custom radius and stroke", () => {
33+
const { container } = render(<CircularProgressBar radius={30} stroke={5} />);
34+
const circularProgressBar = container.querySelector(".deriv-circular-progress__bar");
35+
expect(circularProgressBar).toHaveAttribute("r", "27.5");
36+
expect(circularProgressBar).toHaveAttribute("stroke-width", "5");
37+
});
38+
39+
it("renders circular progress bar with custom progress and warning limits", () => {
40+
const { container } = render(<CircularProgressBar progress={20} warning_limit={30} danger_limit={10} />);
41+
const circularProgressBar = container.querySelector(".deriv-circular-progress__bar");
42+
expect(circularProgressBar).toHaveClass("deriv-circular-progress__bar--warning");
43+
});
44+
45+
it("renders circular progress bar with custom progress and danger limits", () => {
46+
const { container } = render(<CircularProgressBar progress={5} warning_limit={30} danger_limit={10} />);
47+
const circularProgressBar = container.querySelector(".deriv-circular-progress__bar");
48+
expect(circularProgressBar).toHaveClass("deriv-circular-progress__bar--danger");
49+
});
50+
51+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import clsx from "clsx";
2+
import React, { ReactNode} from "react";
3+
import "./CircularProgressBar.scss"
4+
5+
type TCircularProgressProps = {
6+
children?: ReactNode;
7+
className?: string;
8+
danger_limit?: number;
9+
is_clockwise?: boolean;
10+
progress?: number;
11+
radius?: number;
12+
stroke?: number;
13+
warning_limit?: number;
14+
icon?: ReactNode;
15+
};
16+
17+
export const CircularProgressBar = ({
18+
children,
19+
className,
20+
danger_limit = 20,
21+
is_clockwise = false,
22+
progress = 0,
23+
radius = 22,
24+
stroke = 3,
25+
warning_limit = 50,
26+
}: TCircularProgressProps) => {
27+
const normalizedRadius = radius - stroke / 2;
28+
const circumference = normalizedRadius * 2 * Math.PI;
29+
const strokeDashoffset = circumference - (progress / 100) * circumference;
30+
31+
return (
32+
<div className={clsx("deriv-circular-progress", className)}>
33+
<svg height={radius * 2} width={radius * 2}>
34+
{children && (
35+
<foreignObject x="0" y="0" width={radius * 2} height={radius * 2}>
36+
<div className={clsx("deriv-circular-progress__content")}>
37+
{children}
38+
</div>
39+
</foreignObject>
40+
)}
41+
<circle
42+
className={clsx("deriv-circular-progress__bar", {
43+
"deriv-circular-progress--clockwise": is_clockwise,
44+
"deriv-circular-progress__bar--warning": progress <= warning_limit && progress > danger_limit,
45+
"deriv-circular-progress__bar--danger": progress <= danger_limit,
46+
})}
47+
cx={radius}
48+
cy={radius}
49+
fill={"transparent"}
50+
r={normalizedRadius}
51+
strokeDasharray={`${circumference} ${circumference}`}
52+
strokeWidth={stroke}
53+
style={{ strokeDashoffset }}
54+
/>
55+
</svg>
56+
</div>
57+
);
58+
};

src/main.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { ActionScreen } from "./components/ActionScreen";
33
export { Button } from "./components/Button";
44
export { Badge } from "./components/Badge";
55
export { Checkbox } from "./components/Checkbox";
6+
export { CircularProgressBar } from "./components/CircularProgressBar"
67
export { Divider } from "./components/Divider";
78
export { Dialog } from "./components/Dialog";
89
export { Dropdown } from "./components/Dropdown";
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { CircularProgressBar } from "../src/components/CircularProgressBar";
3+
4+
const meta = {
5+
title: "Components/CircularProgressBar",
6+
component: CircularProgressBar,
7+
tags: ["autodocs"],
8+
parameters: {
9+
layout: "centered",
10+
},
11+
args: {
12+
progress: 50,
13+
radius: 22,
14+
stroke: 3,
15+
danger_limit: 20,
16+
warning_limit: 50,
17+
is_clockwise:false
18+
},
19+
argTypes: {
20+
progress: {
21+
control: { type: "range", min: 0, max: 100, step: 1 },
22+
},
23+
is_clockwise: {
24+
options: ["true", "false"],
25+
control: { type: "boolean" },
26+
}
27+
},
28+
} as Meta<typeof CircularProgressBar>;
29+
export default meta;
30+
type Story = StoryObj<typeof meta>;
31+
32+
export const DefaultClockWise: Story = {
33+
args: {
34+
progress: 50,
35+
}
36+
}
37+
38+
export const CustomRadiusAndStrokeClockwise: Story = {
39+
args: {
40+
progress: 70,
41+
radius: 30,
42+
stroke: 5,
43+
}
44+
}
45+
46+
export const ClockwiseProgress: Story = {
47+
args: {
48+
progress: 60,
49+
is_clockwise: true,
50+
}
51+
}
52+
53+
export const WarningLimitClockWise = {
54+
args: {
55+
progress: 30,
56+
warning_limit: 30,
57+
}
58+
}
59+
export const DangerLimitClockWise: Story = {
60+
args: {
61+
progress: 10,
62+
danger_limit: 20,
63+
}
64+
}

0 commit comments

Comments
 (0)