diff --git a/src/components/CircularProgressBar/CircularProgressBar.scss b/src/components/CircularProgressBar/CircularProgressBar.scss new file mode 100644 index 00000000..8fcfb26a --- /dev/null +++ b/src/components/CircularProgressBar/CircularProgressBar.scss @@ -0,0 +1,42 @@ +.deriv-circular-progress { + position: relative; + line-height: 0; + width: fit-content; + + &__content { + position: absolute; + inset:0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + &__bar { + transform: scaleX(-1) rotate(-90deg); + transform-origin: 50% 50%; + transition: stroke-dashoffset 1s; + stroke: var(--du-brand-secondary, #85acb0); + + &--warning { + stroke: var(--du-status-warning, #ffad3a); + } + + &--danger { + stroke: var(--du-status-danger, #ec3f3f) + } + } + + &--clockwise { + transform: rotate(-90deg); + } + + &__icon { + position: absolute; + width: 1.6rem; + height: 100%; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } +} diff --git a/src/components/CircularProgressBar/__test__/CircularProgressBar.spec.tsx b/src/components/CircularProgressBar/__test__/CircularProgressBar.spec.tsx new file mode 100644 index 00000000..9cb86f31 --- /dev/null +++ b/src/components/CircularProgressBar/__test__/CircularProgressBar.spec.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import { render } from "@testing-library/react"; +import { CircularProgressBar } from ".."; + +describe("CircularProgressBar Component", () => { + it("renders correctly with default props", () => { + const { container } = render(); + expect(container.querySelector(".deriv-circular-progress__bar")).toBeInTheDocument(); + }); + + it("renders children correctly", () => { + const { getByText } = render( + +
Child content
+
+ ); + expect(getByText("Child content")).toBeInTheDocument(); + }); + + it("renders correctly with isClockwise prop as True", () => { + const { container } = render(); + expect(container.querySelector(".deriv-circular-progress--clockwise")).toBeInTheDocument(); + }); + + it("renders circular progress bar with custom className", () => { + const { container } = render(); + const circularProgressBar = container.querySelector(".custom-class"); + expect(circularProgressBar).toBeInTheDocument(); + expect(circularProgressBar).toHaveClass("deriv-circular-progress", "custom-class"); + }); + + it("renders circular progress bar with custom radius and stroke", () => { + const { container } = render(); + const circularProgressBar = container.querySelector(".deriv-circular-progress__bar"); + expect(circularProgressBar).toHaveAttribute("r", "27.5"); + expect(circularProgressBar).toHaveAttribute("stroke-width", "5"); + }); + + it("renders circular progress bar with custom progress and warning limits", () => { + const { container } = render(); + const circularProgressBar = container.querySelector(".deriv-circular-progress__bar"); + expect(circularProgressBar).toHaveClass("deriv-circular-progress__bar--warning"); + }); + + it("renders circular progress bar with custom progress and danger limits", () => { + const { container } = render(); + const circularProgressBar = container.querySelector(".deriv-circular-progress__bar"); + expect(circularProgressBar).toHaveClass("deriv-circular-progress__bar--danger"); + }); + +}); diff --git a/src/components/CircularProgressBar/index.tsx b/src/components/CircularProgressBar/index.tsx new file mode 100644 index 00000000..09bfeb0f --- /dev/null +++ b/src/components/CircularProgressBar/index.tsx @@ -0,0 +1,58 @@ +import clsx from "clsx"; +import React, { ReactNode} from "react"; +import "./CircularProgressBar.scss" + +type TCircularProgressProps = { + children?: ReactNode; + className?: string; + danger_limit?: number; + is_clockwise?: boolean; + progress?: number; + radius?: number; + stroke?: number; + warning_limit?: number; + icon?: ReactNode; +}; + +export const CircularProgressBar = ({ + children, + className, + danger_limit = 20, + is_clockwise = false, + progress = 0, + radius = 22, + stroke = 3, + warning_limit = 50, +}: TCircularProgressProps) => { + const normalizedRadius = radius - stroke / 2; + const circumference = normalizedRadius * 2 * Math.PI; + const strokeDashoffset = circumference - (progress / 100) * circumference; + + return ( +
+ + {children && ( + +
+ {children} +
+
+ )} + danger_limit, + "deriv-circular-progress__bar--danger": progress <= danger_limit, + })} + cx={radius} + cy={radius} + fill={"transparent"} + r={normalizedRadius} + strokeDasharray={`${circumference} ${circumference}`} + strokeWidth={stroke} + style={{ strokeDashoffset }} + /> +
+
+ ); +}; diff --git a/src/main.ts b/src/main.ts index 87bfc34e..d9f56cbe 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ export { ActionScreen } from "./components/ActionScreen"; export { Button } from "./components/Button"; export { Badge } from "./components/Badge"; export { Checkbox } from "./components/Checkbox"; +export { CircularProgressBar } from "./components/CircularProgressBar" export { Divider } from "./components/Divider"; export { Dialog } from "./components/Dialog"; export { Dropdown } from "./components/Dropdown"; diff --git a/stories/CircularProgressBar.stories.tsx b/stories/CircularProgressBar.stories.tsx new file mode 100644 index 00000000..c44d4f3c --- /dev/null +++ b/stories/CircularProgressBar.stories.tsx @@ -0,0 +1,64 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { CircularProgressBar } from "../src/components/CircularProgressBar"; + +const meta = { + title: "Components/CircularProgressBar", + component: CircularProgressBar, + tags: ["autodocs"], + parameters: { + layout: "centered", + }, + args: { + progress: 50, + radius: 22, + stroke: 3, + danger_limit: 20, + warning_limit: 50, + is_clockwise:false + }, + argTypes: { + progress: { + control: { type: "range", min: 0, max: 100, step: 1 }, + }, + is_clockwise: { + options: ["true", "false"], + control: { type: "boolean" }, + } + }, +} as Meta; +export default meta; +type Story = StoryObj; + +export const DefaultClockWise: Story = { + args: { + progress: 50, + } +} + +export const CustomRadiusAndStrokeClockwise: Story = { + args: { + progress: 70, + radius: 30, + stroke: 5, + } +} + +export const ClockwiseProgress: Story = { + args: { + progress: 60, + is_clockwise: true, + } +} + +export const WarningLimitClockWise = { + args: { + progress: 30, + warning_limit: 30, + } +} +export const DangerLimitClockWise: Story = { + args: { + progress: 10, + danger_limit: 20, + } +}