shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
68.54k stars 4.06k forks source link

Circular Progress Indicator #697

Closed sonigeez closed 4 weeks ago

sonigeez commented 1 year ago

why we don't have any circular progress indicator in library?

chungweileong94 commented 1 year ago

This can be easily done with tailwind animate-spin;

// Icon.tsx

import {Loader2} from 'lucide-react';

export const Icons = {
  spinner: Loader2,
};
// Usage
<Icons.spinner className="h-4 w-4 animate-spin" />
AnandChowdhary commented 1 year ago

I think they meant something like this, where you can have a specific progress % rather than a general spinner:

image

adaboese commented 10 months ago

@sonigeez , @AnandChowdhary provided more context. I think everyone meant the above.

xpluscal commented 7 months ago

@sonigeez @AnandChowdhary any chance this will get picked up again?

kailingding commented 6 months ago

+1

navkuun commented 5 months ago

+1 was also looking for this

17Amir17 commented 5 months ago

+1

mahmudulnayeem commented 5 months ago
Screenshot 2024-03-19 at 2 24 26 PM

looking for circular progress

sgiovo commented 5 months ago

+1

Kiro369 commented 5 months ago

+1

Kathenae commented 5 months ago

+1

BertVanHecke commented 5 months ago

+1

shahnewaz-labib commented 5 months ago

This would be a great addition

ialmanzaj commented 5 months ago

+1

HarshDev2 commented 4 months ago

+1

nparashar150 commented 4 months ago

+1

gustavo-fior commented 4 months ago

+1

flkrnr commented 4 months ago

+1

Syntarex commented 4 months ago

+1

sabin-thapa commented 4 months ago

+1

1337Impact commented 4 months ago

+1

gajakannan commented 4 months ago

+1 for Radial progress

image

thallysondias commented 4 months ago

+1

mjurincic commented 3 months ago

+1

ericaig commented 3 months ago

+1

serhii-kucherenko commented 3 months ago

+1

christianblandford commented 3 months ago

+1

SkyNotBlueEnough commented 3 months ago

+1

Ritik1330 commented 3 months ago

Paste this code into progress.tsx and utilize CircleProgress.

export const CircleProgress = React.forwardRef<
  React.ElementRef<typeof ProgressPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
  <ProgressPrimitive.Root
    ref={ref}
    className={cn(
      `relative h-20 w-20 overflow-hidden rounded-full bg-primary/20 flex justify-center items-center`,
      className
    )}
    {...props}
    style={{ background: `radial-gradient(closest-side, white 79%, transparent 80% 100%), conic-gradient(green ${(value || 0)}%, pink 0)` }}
  >
    <div className="">{`${(value || 0)}%`}</div>

  </ProgressPrimitive.Root>
))

image

nambui98 commented 2 months ago

this is my solution

import { motion } from 'framer-motion';
type Props = {
  value: number;
};

export default function CircleProgress({ value }: Props) {
  const percentage = Math.min(Math.max(value, 0), 100);
  const width = 216;
  const radius = 98;

  const circumference = 2 * Math.PI * radius;

  const offset = circumference - (percentage / 100) * circumference;
  return (
    <svg width={width} height={width} xmlns="http://www.w3.org/2000/svg">
      <defs>
        <radialGradient
          id="circle-progress"
          cx="0"
          cy="0"
          r="1"
          gradientUnits="userSpaceOnUse"
          gradientTransform="translate(53.1659 -18.1884) rotate(51.1683) scale(267.012 282.957)"
        >
          <stop stopColor="#F05F84" />
          <stop offset="1" stopColor="#FD312E" />
        </radialGradient>
      </defs>
      <circle
        cx={width / 2}
        cy={width / 2}
        r={radius}
        strokeLinecap="round"
        className="fill-none stroke-neutral-300 stroke-[20px]"
        style={{
          strokeDasharray: circumference,
          strokeDashoffset: circumference,
        }}
      />

      <motion.circle
        cx={'108'}
        cy={'108'}
        r="98"
        strokeLinecap="round"
        className="fill-none stroke-[url('#circle-progress')] stroke-[20px]"
        initial={{
          strokeDashoffset: circumference,
          strokeDasharray: circumference,
        }}
        animate={{ strokeDashoffset: offset }}
        transition={{
          stiffness: 260,
          damping: 20,
          delay: 0.5,
          duration: 1,
          ease: 'easeOut',
        }}
      />
    </svg>
  );
}
abuaboud commented 1 month ago

+1

FESEVA commented 1 month ago

It should be good to have same behaviour as MUI react-circular-progress

image

It would be great if shadcn implement it to avoid doing tricks to cover this common usecase.

QuentinScDS commented 1 month ago

Radial Charts ? image

jschuur commented 1 month ago

Radial Charts ?

Indeed, this does seem solved now: https://ui.shadcn.com/charts#radial-chart

shadcn commented 4 weeks ago

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

Mechanikum commented 2 weeks ago

Adjusted nambui98 solution a bit, couldn't find a sweet spot to calculate strokeWidth so it's set manually, the rest works good enough for me as generic circular progress.

import React, { useRef, useEffect, useState } from 'react';
import { motion } from 'framer-motion';

interface CircularProgressProps extends React.HTMLAttributes<HTMLDivElement> {
    value: number; // Something between 1 and 100
    strokeWidth: number;
}

const CircularProgress: React.FC<CircularProgressProps> = ({ value, strokeWidth, ...divProps }) => {
    const containerRef = useRef<HTMLDivElement | null>(null);
    const [size, setSize] = useState(0);

    useEffect(() => {
        if (containerRef.current && "getBoundingClientRect" in containerRef.current) {
            const {width, height} = containerRef.current.getBoundingClientRect();
            setSize(Math.min(width, height));
        }
    }, []);

    const percentage = Math.min(Math.max(value, 0), 100);
    const radius = (size - strokeWidth) / 2;
    const circumference = 2 * Math.PI * radius;
    const offset = circumference - (percentage / 100) * circumference;

    return (
        <div ref={containerRef} {...divProps}>
            {size > 0 && (
                <svg width={size} height={size} xmlns="http://www.w3.org/2000/svg">
                    <defs>
                        <radialGradient
                            id="circle-progress"
                            cx="0"
                            cy="0"
                            r="1"
                            gradientUnits="userSpaceOnUse"
                            gradientTransform="translate(53.1659 -18.1884) rotate(51.1683) scale(267.012 282.957)"
                        >
                            <stop stopColor="currentColor" />
                            <stop offset="1" stopColor="currentColor" />
                        </radialGradient>
                    </defs>
                    <circle
                        cx={size / 2}
                        cy={size / 2}
                        r={radius}
                        strokeLinecap="round"
                        className="fill-none stroke-neutral-300"
                        style={{
                            strokeWidth,
                            strokeDasharray: circumference,
                            strokeDashoffset: circumference,
                        }}
                    />
                    <motion.circle
                        cx={size / 2}
                        cy={size / 2}
                        r={radius}
                        strokeLinecap="round"
                        className="fill-none"
                        style={{ stroke: "url(#circle-progress)", strokeWidth }}
                        initial={{
                            strokeDashoffset: circumference,
                            strokeDasharray: circumference,
                        }}
                        animate={{ strokeDashoffset: offset }}
                        transition={{
                            ease: 'easeOut',
                        }}
                    />
                </svg>
            )}
        </div>
    );
};

export default CircularProgress;

Usage example in button

const ProgressButton = React.forwardRef<HTMLButtonElement, ProgressButtonProps>(
    ({ value = 0, children, disabled, className, ...props }, ref) => {
        return (
            <Button
                ref={ref}
                disabled={value < 100 || disabled}
                className={cn("transition-all duration-300 delay-300", value < 100 ? "pl-4" : "pl-0", className)}
                {...props}
            >
                <CircularProgress value={value} strokeWidth={3} className={cn("transition-all duration-300 delay-300 h-4", value < 100 ? "w-4 mr-2 scale-1" : "w-2 scale-0 mr-1 ml-1")}/>
                {children}
            </Button>
        );
    }
);