Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added Circular Progress #1488

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/web/content/docs/components/progress.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ Set your own custom colors for the progress bar component by using the `color` p

<Example name="progress.colors" />

## Circular Progress

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.

<Example name="progress.circularProgress" />

## Circular Progress With Text

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.

<Example name="progress.circularProgressWithText" />

## Theme

To learn more about how to customize the appearance of components, please see the [Theme docs](/docs/customize/theme).
Expand Down
2 changes: 2 additions & 0 deletions apps/web/examples/progress/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { circularProgress } from "./progress.circular";
export { circularProgressWithText } from "./progress.circularWithText";
export { colors } from "./progress.colors";
export { positioning } from "./progress.positioning";
export { root } from "./progress.root";
Expand Down
42 changes: 42 additions & 0 deletions apps/web/examples/progress/progress.circular.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Progress } from "flowbite-react";
import { type CodeData } from "~/components/code-demo";

const code = `
"use client";

import { Progress } from "flowbite-react";

export function Component() {
return <Progress.Circular progress={45} />;
}
`;

const codeRSC = `
import { ProgressCircular } from "flowbite-react";

export function Component() {
return <ProgressCircular progress={45} />;
}
`;

export function Component() {
return <Progress.Circular progress={45} />;
}
dhavalveera marked this conversation as resolved.
Show resolved Hide resolved

export const circularProgress: CodeData = {
type: "single",
code: [
{
fileName: "client",
language: "tsx",
code,
},
{
fileName: "server",
language: "tsx",
code: codeRSC,
},
],
githubSlug: "progress/progress.circular.tsx",
component: <Component />,
};
42 changes: 42 additions & 0 deletions apps/web/examples/progress/progress.circularWithText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Progress } from "flowbite-react";
import { type CodeData } from "~/components/code-demo";

const code = `
"use client";

import { Progress } from "flowbite-react";

export function Component() {
return <Progress.Circular progress={45} labelText textLabel="45%" />;
}
`;

const codeRSC = `
import { ProgressCircular } from "flowbite-react";

export function Component() {
return <ProgressCircular progress={45} labelText textLabel="45%" />;
}
`;

export function Component() {
return <Progress.Circular progress={45} labelText textLabel="45%" />;
}
dhavalveera marked this conversation as resolved.
Show resolved Hide resolved

export const circularProgressWithText: CodeData = {
type: "single",
code: [
{
fileName: "client",
language: "tsx",
code,
},
{
fileName: "server",
language: "tsx",
code: codeRSC,
},
],
githubSlug: "progress/progress.circularWithText.tsx",
component: <Component />,
};
31 changes: 31 additions & 0 deletions packages/ui/src/components/Progress/CircularProgress.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Meta, StoryFn } from "@storybook/react";
import type { CircularProgressProps } from "./ProgressCircular";
import { CircularProgress } from "./ProgressCircular";

export default {
title: "Components/Circular Progress",
component: CircularProgress,
decorators: [
(Story): JSX.Element => (
<div className="flex w-1/2 flex-col">
<Story />
</div>
),
],
} as Meta;

const CircularTemplate: StoryFn<CircularProgressProps> = (args) => <CircularProgress {...args} />;

export const CircularProgressBar = CircularTemplate.bind({});
CircularProgressBar.storyName = "Circular Progress";
CircularProgressBar.args = {
progress: 25,
};

export const CircularProgressBarWithText = CircularTemplate.bind({});
CircularProgressBarWithText.storyName = "Circular Progress With Text";
CircularProgressBarWithText.args = {
progress: 25,
labelText: true,
textLabel: "25%",
};
11 changes: 9 additions & 2 deletions packages/ui/src/components/Progress/Progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import { mergeDeep } from "../../helpers/merge-deep";
import { getTheme } from "../../theme-store";
import type { DeepPartial, DynamicStringEnumKeysOf } from "../../types";
import type { FlowbiteColors, FlowbiteSizes } from "../Flowbite";
import type { FlowbiteCircularProgressTheme } from "./ProgressCircular";
import { CircularProgress } from "./ProgressCircular";

export interface FlowbiteProgressTheme {
base: string;
label: string;
bar: string;
color: ProgressColor;
size: ProgressSizes;
circular: FlowbiteCircularProgressTheme;
}

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

export const Progress: FC<ProgressProps> = ({
const ProgressComponent: FC<ProgressProps> = ({
className,
color = "cyan",
labelProgress = false,
Expand Down Expand Up @@ -83,4 +86,8 @@ export const Progress: FC<ProgressProps> = ({
);
};

Progress.displayName = "Progress";
ProgressComponent.displayName = "Progress";

export const Progress = Object.assign(ProgressComponent, {
Circular: CircularProgress,
});
92 changes: 92 additions & 0 deletions packages/ui/src/components/Progress/ProgressCircular.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { ComponentProps, FC } from "react";
import { useId, useMemo } from "react";
import { twMerge } from "tailwind-merge";
import { mergeDeep } from "../../helpers/merge-deep";
import { getTheme } from "../../theme-store";
import type { DeepPartial } from "../../types";
import type { FlowbiteColors } from "../Flowbite";

export interface FlowbiteCircularProgressTheme {
base: string;
bar: string;
label: {
base: string;
text: string;
textColor: CircularProgressColor;
};
color: {
bgColor: string;
barColor: CircularProgressColor;
};
}

export interface CircularProgressColor
extends Pick<
FlowbiteColors,
"dark" | "blue" | "red" | "green" | "yellow" | "indigo" | "purple" | "cyan" | "gray" | "lime" | "pink" | "teal"
> {
[key: string]: string;
}

export interface CircularProgressProps extends ComponentProps<"div"> {
labelText?: boolean;
progress: number;
textLabel?: string;
theme?: DeepPartial<FlowbiteCircularProgressTheme>;
progressColor?: keyof CircularProgressColor;
}

export const CircularProgress: FC<CircularProgressProps> = ({
className,
progressColor = "cyan",
labelText = false,
progress,
dhavalveera marked this conversation as resolved.
Show resolved Hide resolved
textLabel = "65%",
dhavalveera marked this conversation as resolved.
Show resolved Hide resolved
theme: customTheme = {},
...props
}) => {
const id = useId();
const theme = mergeDeep(getTheme().progress.circular, customTheme);

// Memoize calculations for the circumference and stroke offset to avoid recalculating on each render
const { offset } = useMemo(() => {
const circumference = 2 * Math.PI * 16; // Fixed radius of 16

const offset = circumference * (1 - progress / 100); // Stroke dash offset based on progress

return { offset };
}, [progress]);

return (
<div id={id} aria-valuenow={progress} role="progressbar" {...props}>
<div className={twMerge(theme.base, className)}>
<svg className={theme.bar} viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle cx="18" cy="18" r="16" fill="none" className={theme.color.bgColor} strokeWidth="2" />

<circle
cx="18"
cy="18"
r="16"
fill="none"
className={theme.color.barColor[progressColor]}
strokeWidth="2"
strokeDasharray="100"
strokeDashoffset={offset}
dhavalveera marked this conversation as resolved.
Show resolved Hide resolved
strokeLinecap="round"
/>
</svg>

{labelText && textLabel ? (
<div className={theme.label.base}>
<span
data-testid="flowbite-circular-progress-label"
className={twMerge(theme.label.text, theme.label.textColor[progressColor])}
>
{textLabel}
</span>
</div>
) : null}
</div>
</div>
);
};
39 changes: 39 additions & 0 deletions packages/ui/src/components/Progress/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,43 @@ export const progressTheme: FlowbiteProgressTheme = createTheme({
lg: "h-4",
xl: "h-6",
},
circular: {
base: "relative size-40",
bar: "size-full -rotate-90",
color: {
barColor: {
dark: "stroke-current text-gray-600 dark:text-gray-300",
blue: "stroke-current text-blue-600",
red: "stroke-current text-red-600 dark:text-red-500",
green: "stroke-current text-green-600 dark:text-green-500",
yellow: "stroke-current text-yellow-400",
indigo: "stroke-current text-indigo-600 dark:text-indigo-500",
purple: "stroke-current text-purple-600 dark:text-purple-500",
cyan: "stroke-current text-cyan-600",
gray: "stroke-current text-gray-500",
lime: "stroke-current text-lime-600",
pink: "stroke-current text-pink-500",
teal: "stroke-current text-teal-600",
},
bgColor: "stroke-current text-gray-200 dark:text-neutral-700",
},
label: {
base: "absolute start-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform",
text: "text-center text-2xl font-bold",
textColor: {
dark: "text-gray-600 dark:text-gray-300",
blue: "text-blue-600",
red: "text-red-600 dark:text-red-500",
green: "text-green-600 dark:text-green-500",
yellow: "text-yellow-400",
indigo: "text-indigo-600 dark:text-indigo-500",
purple: "text-purple-600 dark:text-purple-500",
cyan: "text-cyan-600",
gray: "text-gray-500",
lime: "text-lime-600",
pink: "text-pink-500",
teal: "text-teal-600",
},
},
},
});
Loading