Modern Progress

PreviousNext

A modern progress component for Next.js apps with next-themes and Tailwind CSS, supporting system, light, and dark modes.

Docs
buchariteshui

Preview

Loading preview…
registry/bucharitesh/modern-progress.tsx
import { cn } from '@/lib/utils';
import { type VariantProps, cva } from 'class-variance-authority';
import * as React from 'react';

const progressVariants = cva(
  'relative flex w-full items-center justify-center overflow-hidden',
  {
    variants: {
      variant: {
        default: 'border-4 border-primary bg-neutral-950',
        secondary: 'border-4 border-secondary bg-neutral-900',
        destructive: 'border-4 border-destructive bg-neutral-950',
      },
      radius: {
        default: 'rounded-3xl',
        full: 'rounded-full',
        none: 'rounded-none',
      },
    },
    defaultVariants: {
      variant: 'default',
      radius: 'default',
    },
  }
);

const indicatorVariants = cva(
  'absolute bottom-0 left-0 z-20 w-full transition-[height,background-color] duration-300',
  {
    variants: {
      variant: {
        default: 'bg-primary',
        secondary: 'bg-secondary',
        destructive: 'bg-destructive',
      },
      striped: {
        true: '[&>div]:bg-[linear-gradient(45deg,rgba(255,255,255,.15)_25%,transparent_25%,transparent_50%,rgba(255,255,255,.15)_50%,rgba(255,255,255,.15)_75%,transparent_75%,transparent)] [&>div]:bg-size-1',
        false: '',
      },
    },
    defaultVariants: {
      variant: 'default',
      striped: true,
    },
  }
);

export interface ProgressProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof progressVariants> {
  value?: number;
  max?: number;
  striped?: boolean;
  indicatorClassName?: string;
  showText?: boolean;
}

const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
  (
    {
      className,
      value = 0,
      max = 100,
      variant,
      radius,
      striped = true,
      indicatorClassName,
      showText,
      ...props
    },
    ref
  ) => {
    const percentage = Math.min(Math.max((value / max) * 100, 0), 100);

    return (
      <div
        ref={ref}
        role="progressbar"
        aria-valuemin={0}
        aria-valuemax={max}
        aria-valuenow={value}
        className={cn(progressVariants({ variant, radius }), className)}
        {...props}
      >
        <div className="relative aspect-video w-full">
          <div
            className={cn(
              indicatorVariants({ variant, striped }),
              indicatorClassName
            )}
            style={{
              height: `${percentage}%`,
            }}
          >
            <div
              data-pattern="stripes"
              className="relative z-10 h-full w-full transition-colors duration-300"
            />
          </div>
          {showText && (
            <div className="-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2 z-20 flex h-full w-full items-center justify-center">
              <span className="font-bold text-5xl text-white">
                {percentage.toFixed(0)}
                <span className="font-medium text-base text-white">%</span>
              </span>
            </div>
          )}
        </div>
      </div>
    );
  }
);
Progress.displayName = 'Progress';

export { Progress };

Installation

npx shadcn@latest add @bucharitesh/modern-progress

Usage

import { ModernProgress } from "@/components/ui/modern-progress"
<ModernProgress />