{ "name": "circular-progress", "type": "registry:ui", "dependencies": [ "radix-ui" ], "cssVars": { "theme": { "--animate-spin-around": "spin-around 0.8s linear infinite" } }, "css": { "@keyframes spin-around": { "0%": { "transform": "rotate(-90deg)" }, "100%": { "transform": "rotate(270deg)" } } }, "files": [ { "path": "ui/circular-progress.tsx", "type": "registry:ui", "content": "\"use client\";\n\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst CIRCULAR_PROGRESS_NAME = \"CircularProgress\";\nconst INDICATOR_NAME = \"CircularProgressIndicator\";\nconst TRACK_NAME = \"CircularProgressTrack\";\nconst RANGE_NAME = \"CircularProgressRange\";\nconst VALUE_TEXT_NAME = \"CircularProgressValueText\";\n\nconst DEFAULT_MAX = 100;\n\ntype ProgressState = \"indeterminate\" | \"complete\" | \"loading\";\n\nfunction getProgressState(\n value: number | undefined | null,\n maxValue: number,\n): ProgressState {\n return value == null\n ? \"indeterminate\"\n : value === maxValue\n ? \"complete\"\n : \"loading\";\n}\n\nfunction getIsValidNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction getIsValidMaxNumber(max: unknown): max is number {\n return getIsValidNumber(max) && max > 0;\n}\n\nfunction getIsValidValueNumber(\n value: unknown,\n min: number,\n max: number,\n): value is number {\n return getIsValidNumber(value) && value <= max && value >= min;\n}\n\nfunction getDefaultValueText(value: number, min: number, max: number): string {\n const percentage = max === min ? 100 : ((value - min) / (max - min)) * 100;\n return `${Math.round(percentage)}%`;\n}\n\nfunction getInvalidValueError(\n propValue: string,\n componentName: string,\n): string {\n return `Invalid prop \\`value\\` of value \\`${propValue}\\` supplied to \\`${componentName}\\`. The \\`value\\` prop must be a number between \\`min\\` and \\`max\\` (inclusive), or \\`null\\`/\\`undefined\\` for indeterminate progress. The value will be clamped to the valid range.`;\n}\n\nfunction getInvalidMaxError(propValue: string, componentName: string): string {\n return `Invalid prop \\`max\\` of value \\`${propValue}\\` supplied to \\`${componentName}\\`. Only numbers greater than 0 are valid. Defaulting to ${DEFAULT_MAX}.`;\n}\n\ninterface CircularProgressContextValue {\n value: number | null;\n valueText: string | undefined;\n max: number;\n min: number;\n state: ProgressState;\n radius: number;\n thickness: number;\n size: number;\n center: number;\n circumference: number;\n percentage: number | null;\n valueTextId?: string;\n}\n\nconst CircularProgressContext =\n React.createContext(null);\n\nfunction useCircularProgressContext(consumerName: string) {\n const context = React.useContext(CircularProgressContext);\n if (!context) {\n throw new Error(\n `\\`${consumerName}\\` must be used within \\`${CIRCULAR_PROGRESS_NAME}\\``,\n );\n }\n return context;\n}\n\ninterface CircularProgressProps extends React.ComponentProps<\"div\"> {\n value?: number | null | undefined;\n getValueText?(value: number, min: number, max: number): string;\n min?: number;\n max?: number;\n size?: number;\n thickness?: number;\n label?: string;\n asChild?: boolean;\n}\n\nfunction CircularProgress(props: CircularProgressProps) {\n const {\n value: valueProp = null,\n getValueText = getDefaultValueText,\n min: minProp = 0,\n max: maxProp,\n size = 48,\n thickness = 4,\n label,\n asChild,\n className,\n children,\n ...rootProps\n } = props;\n\n if ((maxProp || maxProp === 0) && !getIsValidMaxNumber(maxProp)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(getInvalidMaxError(`${maxProp}`, CIRCULAR_PROGRESS_NAME));\n }\n }\n\n const rawMax = getIsValidMaxNumber(maxProp) ? maxProp : DEFAULT_MAX;\n const min = getIsValidNumber(minProp) ? minProp : 0;\n const max = rawMax <= min ? min + 1 : rawMax;\n\n if (process.env.NODE_ENV !== \"production\" && thickness >= size) {\n console.warn(\n `CircularProgress: thickness (${thickness}) should be less than size (${size}) for proper rendering.`,\n );\n }\n\n if (valueProp !== null && !getIsValidValueNumber(valueProp, min, max)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(\n getInvalidValueError(`${valueProp}`, CIRCULAR_PROGRESS_NAME),\n );\n }\n }\n\n const value = getIsValidValueNumber(valueProp, min, max)\n ? valueProp\n : getIsValidNumber(valueProp) && valueProp > max\n ? max\n : getIsValidNumber(valueProp) && valueProp < min\n ? min\n : null;\n\n const valueText = getIsValidNumber(value)\n ? getValueText(value, min, max)\n : undefined;\n const state = getProgressState(value, max);\n const radius = Math.max(0, (size - thickness) / 2);\n const center = size / 2;\n const circumference = 2 * Math.PI * radius;\n\n const percentage = getIsValidNumber(value)\n ? max === min\n ? 1\n : (value - min) / (max - min)\n : null;\n\n const labelId = React.useId();\n const valueTextId = React.useId();\n\n const contextValue = React.useMemo(\n () => ({\n value,\n valueText,\n max,\n min,\n state,\n radius,\n thickness,\n size,\n center,\n circumference,\n percentage,\n valueTextId,\n }),\n [\n value,\n valueText,\n max,\n min,\n state,\n radius,\n thickness,\n size,\n center,\n circumference,\n percentage,\n valueTextId,\n ],\n );\n\n const RootPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n return (\n \n \n {children}\n {label &&
{label}
}\n \n
\n );\n}\n\nfunction CircularProgressIndicator(props: React.ComponentProps<\"svg\">) {\n const { className, ...indicatorProps } = props;\n\n const context = useCircularProgressContext(INDICATOR_NAME);\n\n return (\n \n );\n}\n\nCircularProgressIndicator.displayName = INDICATOR_NAME;\n\nfunction CircularProgressTrack(props: React.ComponentProps<\"circle\">) {\n const { className, ...trackProps } = props;\n\n const context = useCircularProgressContext(TRACK_NAME);\n\n return (\n \n );\n}\n\nfunction CircularProgressRange(props: React.ComponentProps<\"circle\">) {\n const { className, ...rangeProps } = props;\n\n const context = useCircularProgressContext(RANGE_NAME);\n\n const strokeDasharray = context.circumference;\n const strokeDashoffset =\n context.state === \"indeterminate\"\n ? context.circumference * 0.75\n : context.percentage !== null\n ? context.circumference - context.percentage * context.circumference\n : context.circumference;\n\n return (\n \n );\n}\n\ninterface CircularProgressValueTextProps extends React.ComponentProps<\"span\"> {\n asChild?: boolean;\n}\n\nfunction CircularProgressValueText(props: CircularProgressValueTextProps) {\n const { asChild, className, children, ...valueTextProps } = props;\n\n const context = useCircularProgressContext(VALUE_TEXT_NAME);\n\n const ValueTextPrimitive = asChild ? SlotPrimitive.Slot : \"span\";\n\n return (\n \n {children ?? context.valueText}\n \n );\n}\n\nfunction CircularProgressCombined(props: CircularProgressProps) {\n return (\n \n \n \n \n \n \n \n );\n}\n\nexport {\n CircularProgress,\n CircularProgressCombined,\n CircularProgressIndicator,\n type CircularProgressProps,\n CircularProgressRange,\n CircularProgressTrack,\n CircularProgressValueText,\n};\n", "target": "" } ] }