Skip to content

Commit 61b6e73

Browse files
committed
feat: added Circular Progress
1 parent 38913e5 commit 61b6e73

File tree

8 files changed

+265
-2
lines changed

8 files changed

+265
-2
lines changed

apps/web/content/docs/components/progress.mdx

+12
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ Set your own custom colors for the progress bar component by using the `color` p
4343

4444
<Example name="progress.colors" />
4545

46+
## Circular Progress
47+
48+
Use this Circular progress example to show a progress bar where you can set the progress rate using the `progress` prop from React which should be a number from 1 to 100.
49+
50+
<Example name="progress.circular" />
51+
52+
## Circular Progress With Text
53+
54+
Use this Circular progress example to show a progress bar with a label. You can set the label text using the `textLabel` prop and the progress text using the `labelText` prop.
55+
56+
<Example name="progress.circularWithText" />
57+
4658
## Theme
4759

4860
To learn more about how to customize the appearance of components, please see the [Theme docs](/docs/customize/theme).

apps/web/examples/progress/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export { circularProgress } from "./progress.circular";
2+
export { circularProgressWithText } from "./progress.circularWithText";
13
export { colors } from "./progress.colors";
24
export { positioning } from "./progress.positioning";
35
export { root } from "./progress.root";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Progress } from "flowbite-react";
2+
import { type CodeData } from "~/components/code-demo";
3+
4+
const code = `
5+
"use client";
6+
7+
import { Progress } from "flowbite-react";
8+
9+
export function Component() {
10+
return <Progress.Circular progress={45} />;
11+
}
12+
`;
13+
14+
const codeRSC = `
15+
import { Progress } from "flowbite-react";
16+
17+
export function Component() {
18+
return <Progress.Circular progress={45} />;
19+
}
20+
`;
21+
22+
export function Component() {
23+
return <Progress.Circular progress={45} />;
24+
}
25+
26+
export const circularProgress: CodeData = {
27+
type: "single",
28+
code: [
29+
{
30+
fileName: "client",
31+
language: "tsx",
32+
code,
33+
},
34+
{
35+
fileName: "server",
36+
language: "tsx",
37+
code: codeRSC,
38+
},
39+
],
40+
githubSlug: "progress/progress.circular.tsx",
41+
component: <Component />,
42+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Progress } from "flowbite-react";
2+
import { type CodeData } from "~/components/code-demo";
3+
4+
const code = `
5+
"use client";
6+
7+
import { Progress } from "flowbite-react";
8+
9+
export function Component() {
10+
return <Progress.Circular progress={45} textLabel="45%" />;
11+
}
12+
`;
13+
14+
const codeRSC = `
15+
import { Progress } from "flowbite-react";
16+
17+
export function Component() {
18+
return <Progress.Circular progress={45} textLabel="45%" />;
19+
}
20+
`;
21+
22+
export function Component() {
23+
return <Progress.Circular progress={45} textLabel="45%" />;
24+
}
25+
26+
export const circularProgressWithText: CodeData = {
27+
type: "single",
28+
code: [
29+
{
30+
fileName: "client",
31+
language: "tsx",
32+
code,
33+
},
34+
{
35+
fileName: "server",
36+
language: "tsx",
37+
code: codeRSC,
38+
},
39+
],
40+
githubSlug: "progress/progress.circularWithText.tsx",
41+
component: <Component />,
42+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { Meta, StoryFn } from "@storybook/react";
2+
import type { CircularProgressProps } from "./ProgressCircular";
3+
import { CircularProgress } from "./ProgressCircular";
4+
5+
export default {
6+
title: "Components/Circular Progress",
7+
component: CircularProgress,
8+
decorators: [
9+
(Story): JSX.Element => (
10+
<div className="flex w-1/2 flex-col">
11+
<Story />
12+
</div>
13+
),
14+
],
15+
} as Meta;
16+
17+
const CircularTemplate: StoryFn<CircularProgressProps> = (args) => <CircularProgress {...args} />;
18+
19+
export const CircularProgressBar = CircularTemplate.bind({});
20+
CircularProgressBar.storyName = "Circular Progress";
21+
CircularProgressBar.args = {
22+
progress: 25,
23+
};
24+
25+
export const CircularProgressBarWithText = CircularTemplate.bind({});
26+
CircularProgressBarWithText.storyName = "Circular Progress With Text";
27+
CircularProgressBarWithText.args = {
28+
progress: 25,
29+
labelText: true,
30+
textLabel: "25%",
31+
};

packages/ui/src/components/Progress/Progress.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import { mergeDeep } from "../../helpers/merge-deep";
55
import { getTheme } from "../../theme-store";
66
import type { DeepPartial, DynamicStringEnumKeysOf } from "../../types";
77
import type { FlowbiteColors, FlowbiteSizes } from "../Flowbite";
8+
import type { FlowbiteCircularProgressTheme } from "./ProgressCircular";
9+
import { CircularProgress } from "./ProgressCircular";
810

911
export interface FlowbiteProgressTheme {
1012
base: string;
1113
label: string;
1214
bar: string;
1315
color: ProgressColor;
1416
size: ProgressSizes;
17+
circular: FlowbiteCircularProgressTheme;
1518
}
1619

1720
export interface ProgressColor
@@ -37,7 +40,7 @@ export interface ProgressProps extends ComponentProps<"div"> {
3740
theme?: DeepPartial<FlowbiteProgressTheme>;
3841
}
3942

40-
export const Progress: FC<ProgressProps> = ({
43+
const ProgressComponent: FC<ProgressProps> = ({
4144
className,
4245
color = "cyan",
4346
labelProgress = false,
@@ -83,4 +86,8 @@ export const Progress: FC<ProgressProps> = ({
8386
);
8487
};
8588

86-
Progress.displayName = "Progress";
89+
ProgressComponent.displayName = "Progress";
90+
91+
export const Progress = Object.assign(ProgressComponent, {
92+
Circular: CircularProgress,
93+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type { ComponentProps, FC } from "react";
2+
import { useId } from "react";
3+
import { twMerge } from "tailwind-merge";
4+
import { mergeDeep } from "../../helpers/merge-deep";
5+
import { getTheme } from "../../theme-store";
6+
import type { DeepPartial } from "../../types";
7+
import type { FlowbiteColors } from "../Flowbite";
8+
9+
export interface FlowbiteCircularProgressTheme {
10+
base: string;
11+
bar: string;
12+
label: {
13+
base: string;
14+
text: string;
15+
textColor: CircularProgressColor;
16+
};
17+
color: {
18+
bgColor: string;
19+
barColor: CircularProgressColor;
20+
};
21+
}
22+
23+
export interface CircularProgressColor
24+
extends Pick<
25+
FlowbiteColors,
26+
"dark" | "blue" | "red" | "green" | "yellow" | "indigo" | "purple" | "cyan" | "gray" | "lime" | "pink" | "teal"
27+
> {
28+
[key: string]: string;
29+
}
30+
31+
export interface CircularProgressProps extends ComponentProps<"div"> {
32+
labelText?: boolean;
33+
progress: number;
34+
textLabel?: string;
35+
theme?: DeepPartial<FlowbiteCircularProgressTheme>;
36+
}
37+
38+
export const CircularProgress: FC<CircularProgressProps> = ({
39+
className,
40+
color = "cyan",
41+
labelText = false,
42+
progress,
43+
textLabel = "65%",
44+
theme: customTheme = {},
45+
...props
46+
}) => {
47+
const id = useId();
48+
const theme = mergeDeep(getTheme().progress.circular, customTheme);
49+
50+
// Calculate the circumference of the Circle
51+
const circumference = 2 * Math.PI * 16;
52+
53+
// Calculate the stroke-dashoffset
54+
const offset = circumference * (1 - progress / 100);
55+
56+
return (
57+
<div id={id} aria-valuenow={progress} role="progressbar" {...props}>
58+
<div className={twMerge(theme.base, className)}>
59+
<svg className={theme.bar} viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
60+
<circle cx="18" cy="18" r="16" fill="none" className={theme.color.bgColor} strokeWidth="2" />
61+
62+
<circle
63+
cx="18"
64+
cy="18"
65+
r="16"
66+
fill="none"
67+
className={theme.color.barColor[color]}
68+
strokeWidth="2"
69+
strokeDasharray="100"
70+
strokeDashoffset={offset}
71+
strokeLinecap="round"
72+
/>
73+
</svg>
74+
75+
{labelText && textLabel ? (
76+
<div className={theme.label.base}>
77+
<span
78+
data-testid="flowbite-circular-progress-label"
79+
className={twMerge(theme.label.text, theme.label.textColor[color])}
80+
>
81+
{textLabel}
82+
</span>
83+
</div>
84+
) : null}
85+
</div>
86+
</div>
87+
);
88+
};

packages/ui/src/components/Progress/theme.ts

+39
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,43 @@ export const progressTheme: FlowbiteProgressTheme = createTheme({
2525
lg: "h-4",
2626
xl: "h-6",
2727
},
28+
circular: {
29+
base: "relative size-40",
30+
bar: "size-full -rotate-90",
31+
color: {
32+
barColor: {
33+
dark: "stroke-current text-gray-600 dark:text-gray-300",
34+
blue: "stroke-current text-blue-600",
35+
red: "stroke-current text-red-600 dark:text-red-500",
36+
green: "stroke-current text-green-600 dark:text-green-500",
37+
yellow: "stroke-current text-yellow-400",
38+
indigo: "stroke-current text-indigo-600 dark:text-indigo-500",
39+
purple: "stroke-current text-purple-600 dark:text-purple-500",
40+
cyan: "stroke-current text-cyan-600",
41+
gray: "stroke-current text-gray-500",
42+
lime: "stroke-current text-lime-600",
43+
pink: "stroke-current text-pink-500",
44+
teal: "stroke-current text-teal-600",
45+
},
46+
bgColor: "stroke-current text-gray-200 dark:text-neutral-700",
47+
},
48+
label: {
49+
base: "absolute start-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform",
50+
text: "text-center text-2xl font-bold",
51+
textColor: {
52+
dark: "text-gray-600 dark:text-gray-300",
53+
blue: "text-blue-600",
54+
red: "text-red-600 dark:text-red-500",
55+
green: "text-green-600 dark:text-green-500",
56+
yellow: "text-yellow-400",
57+
indigo: "text-indigo-600 dark:text-indigo-500",
58+
purple: "text-purple-600 dark:text-purple-500",
59+
cyan: "text-cyan-600",
60+
gray: "text-gray-500",
61+
lime: "text-lime-600",
62+
pink: "text-pink-500",
63+
teal: "text-teal-600",
64+
},
65+
},
66+
},
2867
});

0 commit comments

Comments
 (0)