import { type CSSProperties, type HTMLAttributes, type ReactElement, useMemo, } from "react"; import { type LabelRequiredForA11y } from "../types.js"; import { useEnsuredId } from "../useEnsuredId.js"; import { getPercentage } from "../utils/getPercentage.js"; import { type CircularProgressClassNameOptions, circularProgress, circularProgressCircle, circularProgressSvg, } from "./circularProgressStyles.js"; import { type ProgressProps } from "./types.js"; /** * @since 6.0.0 Added the `disableShrink` prop. * @since 6.0.0 Renamed `small` to `dense`. * @since 6.0.0 Renamed `centered` to `disableCentered`. * @since 6.0.0 Removed the `maxRotation` prop since the determinate state no * longer rotates while increasing value. */ export interface CircularProgressProps extends Omit, "id">, ProgressProps, CircularProgressClassNameOptions { /** * An optional style to apply to the svg within the circular progress. The * values of this style object will be merged with the current determinate * style (if it exists). */ svgStyle?: CSSProperties; /** * An optional className to apply to the svg within the circular progress. */ svgClassName?: string; /** * An optional style to apply to the circle within the circular progress. The * values of this style object will be merged with the current determinate * style (if it exists). */ circleStyle?: CSSProperties; /** * An optional className to apply to the circle within the circular progress. */ circleClassName?: string; /** * The radius for the circle. It is generally recommended to have the radius * be 1/2 of the viewbox and minus a few more pixels so that there is some * surrounding padding. You probably shouldn't really be changing this prop * though. * * @defaultValue `30` */ radius?: number; /** * The center point for the circle. This should be half of the `viewBox` prop * 99% of the time and probably won't be changed. * * @defaultValue `33` */ center?: number; /** * The viewbox for the child svg. I wouldn't recommend changing this value as * you will also need to update the `dashoffset` in both Sass and this prop to * get the animation to look nice again. * * @defaultValue `0 0 66 66` */ viewBox?: string; /** * The `stroke-dashoffset` for the circle within the SVG. You probably won't * be changing this value that much as it should match the * `$rmd-progress-circle-dashoffset` Sass variable. This is really just used * to help to create the determinate progress animation. * * @defaultValue `187` */ dashoffset?: number; /** * Set this to `true` to update the indeterminate behavior to only rotate * which will increase performance during CPU-intensive tasks or when many * loading spinners are displayed at once on the page. * * @defaultValue `false` * @since 6.0.0 */ disableShrink?: boolean; } /** * @example Indeterminate Example * ```tsx * import { CircularProgress } from "@react-md/core/progress/CircularProgress": * import { type ReactElement } from "react"; * * function Example(): ReactElement { * return ; * } * ``` * * @example Determinate Example * ```tsx * import { CircularProgress } from "@react-md/core/progress/CircularProgress": * import { useState, type ReactElement } from "react"; * * function Example(): ReactElement { * // a number from 0 - 100 * const [progress, setProgress] = useState(0); * * return ; * } * ``` * * @see {@link https://react-md.dev/components/progress#circular-progress | Progress Demos} * @since 6.0.0 Updated the determinate circular progress to no longer * rotate while increasing the value and require a label for accessibility. */ export function CircularProgress( props: LabelRequiredForA11y ): ReactElement { const { ref, id: propId, className, svgStyle, svgClassName, circleStyle: propCircleStyle, circleClassName, value, min = 0, max = 100, radius = 30, center = 33, viewBox = "0 0 66 66", theme, dense, dashoffset = 187, disableShrink, disableCentered, disableTransition, ...remaining } = props; const id = useEnsuredId(propId, "circular-progress"); let progress: number | undefined; if (typeof value === "number") { progress = getPercentage({ min, max, value, validate: true }); } const circleStyle = useMemo(() => { if (typeof progress !== "number") { return propCircleStyle; } return { ...propCircleStyle, strokeDashoffset: dashoffset - dashoffset * progress, }; }, [progress, propCircleStyle, dashoffset]); const indeterminate = typeof progress !== "number"; return ( ); }