{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "stacked-progress-bar", "description": "A composable stacked progress bar for shadcn/ui to display multiple statuses in one track, with optional legend, tooltips, and accessible semantics.", "dependencies": [ "react", "class-variance-authority" ], "registryDependencies": [ "utils" ], "files": [ { "path": "src/stacked-progress-bar.tsx", "content": "import { cva, type VariantProps } from \"class-variance-authority\";\r\nimport * as React from \"react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ntype StackedProgressBarContextValue = {\r\n max: number;\r\n legendId: string | null;\r\n setLegendId: React.Dispatch>;\r\n};\r\n\r\nconst StackedProgressBarContext =\r\n React.createContext(null);\r\n\r\nfunction useStackedProgressBarContext(componentName: string) {\r\n const context = React.use(StackedProgressBarContext);\r\n\r\n if (!context) {\r\n throw new Error(\r\n `${componentName} must be used within StackedProgressBarProvider.`,\r\n );\r\n }\r\n\r\n return context;\r\n}\r\n\r\nexport function StackedProgressBarSegment({\r\n value,\r\n style,\r\n className,\r\n \"aria-hidden\": ariaHidden,\r\n ...props\r\n}: { value: number } & React.ComponentProps<\"div\">) {\r\n const { max } = useStackedProgressBarContext(\"StackedProgressBarSegment\");\r\n const safeValue = Number.isFinite(value)\r\n ? Math.min(Math.max(value, 0), max)\r\n : 0;\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport function StackedProgressBar({\r\n value,\r\n className,\r\n role,\r\n \"aria-label\": ariaLabel,\r\n \"aria-labelledby\": ariaLabelledBy,\r\n \"aria-describedby\": ariaDescribedBy,\r\n \"aria-valuemin\": ariaValueMin,\r\n \"aria-valuemax\": ariaValueMax,\r\n \"aria-valuenow\": ariaValueNow,\r\n \"aria-valuetext\": ariaValueText,\r\n ...props\r\n}: { value?: number } & React.ComponentProps<\"div\">) {\r\n const context = useStackedProgressBarContext(\"StackedProgressBar\");\r\n const max = context?.max ?? 100;\r\n const safeValue =\r\n value !== undefined && Number.isFinite(value)\r\n ? Math.min(Math.max(value, 0), max)\r\n : undefined;\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport function StackedProgressBarLegend({\r\n id,\r\n className,\r\n role,\r\n ...props\r\n}: React.ComponentProps<\"div\">) {\r\n const context = useStackedProgressBarContext(\"StackedProgressBarLegend\");\r\n const generatedId = React.useId();\r\n const legendId = id ?? generatedId;\r\n\r\n React.useEffect(() => {\r\n context.setLegendId((currentLegendId) => {\r\n if (currentLegendId && currentLegendId !== legendId) {\r\n console.warn(\r\n \"Only one StackedProgressBarLegend is allowed within StackedProgressBarProvider.\",\r\n );\r\n\r\n return currentLegendId;\r\n }\r\n\r\n return legendId;\r\n });\r\n\r\n return () => {\r\n context.setLegendId((currentLegendId) => {\r\n return currentLegendId === legendId ? null : currentLegendId;\r\n });\r\n };\r\n }, [context, legendId]);\r\n\r\n return (\r\n \r\n );\r\n}\r\n\r\nconst stackedProgressBarLegendDotVariant = cva(\r\n \"inline-block mr-1.5 size-2 rounded-full shrink-0 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\r\n {\r\n variants: {\r\n variant: {\r\n default: \"bg-muted\",\r\n icon: \"text-muted-foreground bg-transparent flex shrink-0 items-center mr-2.5 size-auto justify-center rounded-lg [&_svg:not([class*='size-'])]:size-4\",\r\n },\r\n },\r\n defaultVariants: {\r\n variant: \"default\",\r\n },\r\n },\r\n);\r\n\r\nexport function StackedProgressBarLegendDot({\r\n className,\r\n variant = \"default\",\r\n \"aria-hidden\": ariaHidden = true,\r\n ...props\r\n}: React.ComponentProps<\"span\"> &\r\n VariantProps) {\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport function StackedProgressBarLegendLabel({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"div\">) {\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport function StackedProgressBarLegendItem({\r\n className,\r\n role,\r\n ...props\r\n}: React.ComponentProps<\"div\">) {\r\n return (\r\n \r\n );\r\n}\r\n\r\nexport function StackedProgressBarProvider({\r\n max = 100,\r\n ...props\r\n}: { max?: number } & Omit<\r\n React.ComponentProps,\r\n \"value\"\r\n>) {\r\n const [legendId, setLegendId] = React.useState(null);\r\n const safeMax = Number.isFinite(max) && max > 0 ? max : 100;\r\n\r\n return (\r\n \r\n );\r\n}\r\n", "type": "registry:ui" } ], "type": "registry:ui" }