Skip to content
This repository has been archived by the owner on Mar 23, 2023. It is now read-only.

feat: add <CircleProgressBar> component #2106

Merged
merged 13 commits into from
Jun 12, 2020
29 changes: 29 additions & 0 deletions src/app/components/CircleProgressBar/CircleProgressBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";
import { number, text, withKnobs } from "@storybook/addon-knobs";
import { CircleProgressBar } from "./CircleProgressBar";

export default {
title: "Components / CircleProgressBar",
decorators: [withKnobs],
};

export const Default = () => {
const size = number("Size", 10);
const trailStrokeWidth = number("Trail Width", 2);
const trailStrokeColor = text("Trail Color", "#e2f0e6");
const strokeWidth = number("Stroke Width", 2);
const strokeColor = text("Stroke Color", "#289548");
const percentageColor = text("Percentage Color", "#289548");

return (
<CircleProgressBar
percentage={100}
size={size}
trailStrokeWidth={trailStrokeWidth}
trailStrokeColor={trailStrokeColor}
strokeWidth={strokeWidth}
strokeColor={strokeColor}
percentageColor={percentageColor}
/>
);
};
110 changes: 110 additions & 0 deletions src/app/components/CircleProgressBar/CircleProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Packages
import React, { useState, useEffect } from "react";
import tw, { styled } from "twin.macro";

import twConfig from "tailwind.config";

type CircleProgressBarProps = {
size: number;
trailStrokeWidth?: number;
trailStrokeColor?: string;
strokeWidth?: number;
strokeColor?: string;
percentage: number;
percentageColor: string;
};

const Container = styled.figure<{ size: number }>`
max-width: ${(props) => `${props.size}%`};
vertical-align: middle;
`;

const InnerContainer = styled.g<{ percentageColor: string }>`
fill: ${(props) => props.percentageColor};
transform: translateY(0.45rem);
`;

const Text = styled.text`
${tw`font-semibold leading-none`}
font-size: 0.5rem;
text-anchor: middle;
transform: translateY(-0.25rem);
`;

const CIRCLE_CONFIG = {
viewBox: "0 0 38 38",
x: "19",
y: "19",
radio: "15.91549430918954",
};

export const CircleProgressBar = ({
size,
trailStrokeWidth,
trailStrokeColor,
strokeWidth,
strokeColor,
percentage,
percentageColor,
}: CircleProgressBarProps) => {
const [progressBar, setProgressBar] = useState(0);

const updatePercentage = () => {
setProgressBar(progressBar + 1);
};

useEffect(() => {
if (percentage > 0 && percentage < 100) {
updatePercentage();
}
}, [percentage]);

useEffect(() => {
if (progressBar < percentage) {
updatePercentage();
}
}, [progressBar]);

return (
<Container size={size}>
<svg viewBox={CIRCLE_CONFIG.viewBox}>
<circle
cx={CIRCLE_CONFIG.x}
cy={CIRCLE_CONFIG.y}
r={CIRCLE_CONFIG.radio}
fill="transparent"
stroke={trailStrokeColor}
strokeWidth={trailStrokeWidth}
strokeDasharray={0}
/>

<circle
cx={CIRCLE_CONFIG.x}
cy={CIRCLE_CONFIG.y}
r={CIRCLE_CONFIG.radio}
fill="transparent"
stroke={strokeColor}
strokeWidth={strokeWidth}
strokeDasharray={`${progressBar} ${100 - progressBar}`}
strokeDashoffset={25}
/>

<InnerContainer percentageColor={percentageColor}>
<Text x="50%" y="50%" data-testid="circle-progress-bar__progress">
{progressBar}%
</Text>
</InnerContainer>
</svg>
</Container>
);
};

CircleProgressBar.defaultProps = {
size: 10,
trailStrokeWidth: 2,
trailStrokeColor: twConfig.theme.colors["theme-success-200"],
strokeWidth: 2,
strokeColor: twConfig.theme.colors["theme-success-600"],
percentage: 0,
percentageColor: twConfig.theme.colors["theme-success-600"],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react";
import { render } from "@testing-library/react";

import { CircleProgressBar } from "../CircleProgressBar";

describe("CircleProgressBar", () => {
it("should render", () => {
const { container, asFragment, getByTestId } = render(<CircleProgressBar />);

expect(container).toBeTruthy();
expect(asFragment()).toMatchSnapshot();
expect(getByTestId("circle-progress-bar__progress")).toHaveTextContent("0%");
});

it("should render the percentage of progress", () => {
const { container } = render(<CircleProgressBar percentage={50} />);

expect(container).toMatchSnapshot();
});

it("should render the percentage color", () => {
const { container } = render(<CircleProgressBar percentage={50} percentageColor="#333" />);

expect(container).toMatchSnapshot();
});

it("should render the progress small", () => {
const { container } = render(<CircleProgressBar percentage={50} size={5} />);

expect(container).toMatchSnapshot();
});

it("should render the trail circle", () => {
const { container } = render(
<CircleProgressBar percentage={50} trailStrokeWidth={5} trailStrokeColor="#000" />,
);

expect(container).toMatchSnapshot();
});

it("should render the progress circle", () => {
const { container } = render(<CircleProgressBar percentage={50} strokeWidth={5} strokeColor="#00f" />);

expect(container).toMatchSnapshot();
});
});
Loading