Skip to content

Commit

Permalink
feat: add circular progress component
Browse files Browse the repository at this point in the history
  • Loading branch information
itsjavi committed Sep 27, 2024
1 parent 828eaa2 commit f34becc
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ shadcn/ui components with glassmorphism variants, with many extras to make your
- ComboBox (basically is a combination of Command and Popover elements)
- Dot Indicator (special badge with 1/1 ratio, e.g. for notification counters)
- Heading Title (with gradient variants)
- Circular Progress

### Upcoming
- Submit Button (with loading indicator and using useFormStatus under the hood)
Expand Down
100 changes: 100 additions & 0 deletions src/components/ui-extras/circular-progress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"use client";

import { cn } from "@/lib/utils";
import React from "react";

type RootProps = React.ComponentPropsWithoutRef<"div"> & {
size?: "sm" | "md" | "lg";
children?: React.ReactNode;
};

const Root = React.forwardRef<React.ElementRef<"div">, RootProps>(
({ size = "md", children, className, ...props }, ref) => {
const sizeClasses = {
sm: "w-16 h-16 text-sm",
md: "w-20 h-20 text-base",
lg: "w-24 h-24 text-xl",
};

return (
<div
ref={ref}
className={cn("relative", sizeClasses[size], className)}
{...props}
>
{children}
</div>
);
},
);

const Label = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & { value: number; suffix?: string }
>(({ className, children, value, suffix, ...props }, ref) => {
return (
<div
ref={ref}
className={cn(
"absolute inset-0 flex items-center justify-center",
className,
)}
{...props}
>
<span className="font-semibold">
{value.toFixed(1)}
{suffix ?? "%"}
</span>
</div>
);
});

const Vector = React.forwardRef<
React.ElementRef<"svg">,
React.ComponentPropsWithoutRef<"svg"> & { value: number }
>(({ className, children, value, ...props }, ref) => {
const circumference = 2 * Math.PI * 45; // 45 is the radius of the circle
const strokeDashoffset = circumference - (value / 100) * circumference;

return (
<svg
ref={ref}
className={cn("w-full h-full -rotate-90", className)}
viewBox="0 0 100 100"
{...props}
>
<title>Circular Progress {value}</title>
<circle
className="text-gray-200 stroke-gray-500/30"
strokeWidth="10"
cx="50"
cy="50"
r="45"
fill="transparent"
/>
<circle
className="text-accent-600 progress-ring__circle stroke-current animate-circular-stroke"
strokeWidth="10"
strokeLinecap="round"
cx="50"
cy="50"
r="45"
fill="transparent"
style={
{
strokeDasharray: `${circumference} ${circumference}`,
strokeDashoffset,
"--circular-progress-dash-offset": strokeDashoffset,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
} as any
}
/>
</svg>
);
});

export const CircularProgress = {
Root,
Label,
Vector,
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export * from "./components/ui/tooltip";
export * from "./components/icons/spinner-icon";

// extra components
export * from "./components/ui-extras/circular-progress";
export * from "./components/ui-extras/combobox";
export * from "./components/ui-extras/dot-indicator";
export * from "./components/ui-extras/heading-title";
Expand Down
9 changes: 9 additions & 0 deletions src/lib/create-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,14 @@ export function createTailwindPreset(
fulldw: "100dvw",
},
keyframes: {
"circular-stroke": {
from: {
strokeDashoffset: String(2 * Math.PI * 45),
},
to: {
strokeDashoffset: "var(--circular-progress-dash-offset)",
},
},
"accordion-down": {
from: {
height: "0",
Expand All @@ -351,6 +359,7 @@ export function createTailwindPreset(
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"circular-stroke": "circular-stroke 0.5s ease-out",
},
},
},
Expand Down
34 changes: 34 additions & 0 deletions stories/01_extras/circular-progress.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { CircularProgress } from "@/components/ui-extras/circular-progress";
import type { Meta, StoryObj } from "@storybook/react";

const meta: Meta<typeof CircularProgress.Label> = {
title: "UI/Circular Progress",
component: CircularProgress.Label,
// tags: ['autodocs'],
args: {
value: 33.33,
},
argTypes: {
value: {
control: 'number',
},
size: {
control: "select",
options: ["sm", "md", "lg"],
},
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
} as any,
};

export default meta;
type Story = StoryObj<typeof CircularProgress.Label>;

export const Default: Story = {
render: (args) => (
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
<CircularProgress.Root size={(args as any).size}>
<CircularProgress.Vector value={args.value} />
<CircularProgress.Label {...args} />
</CircularProgress.Root>
),
};

0 comments on commit f34becc

Please sign in to comment.