import * as React from 'react' import * as Progress from '@radix-ui/react-progress' import type { SAILLabelPosition, SAILMarginSize, SAILColorInput } from '../../types/sail' import { isPaletteColor, resolveColorClass } from '../../utils/colorResolver' import { FieldLabel } from '../shared/FieldLabel' import { mergeClasses } from '../../utils/classNames' import { marginAboveMap, marginBelowMap } from '../../utils/sailMaps' export type ProgressBarColor = "ACCENT" | "POSITIVE" | "NEGATIVE" | "WARN" | SAILColorInput export type ProgressBarStyle = "THIN" | "THICK" export interface ProgressBarProps { /** Text to display as the field label */ label?: string /** Supplemental text about this field */ instructions?: string /** Number to display between 0 and 100 */ percentage: number /** Determines where the label appears */ labelPosition?: SAILLabelPosition /** Help tooltip text */ helpTooltip?: string /** Additional text for screen readers only */ accessibilityText?: string /** Progress bar color - semantic values or hex color */ color?: ProgressBarColor /** Whether the component is displayed */ showWhen?: boolean /** Thickness of the progress bar */ style?: ProgressBarStyle /** Whether to display the percentage text */ showPercentage?: boolean /** Space above the component */ marginAbove?: SAILMarginSize /** Space below the component */ marginBelow?: SAILMarginSize /** Additional Tailwind classes for prototype-specific styling (not part of SAIL API) */ className?: string } export const ProgressBar: React.FC = ({ label, instructions, percentage, labelPosition = "ABOVE", helpTooltip, accessibilityText, color = "ACCENT", showWhen = true, style = "THIN", showPercentage = true, marginAbove = "NONE", marginBelow = "STANDARD", className: classNameProp }) => { // Visibility control if (!showWhen) return null // Clamp percentage between 0 and 100 for display, but show actual value in text const clampedPercentage = Math.max(0, Math.min(100, percentage)) const displayPercentage = Math.round(percentage) // Generate unique ID for accessibility const progressId = React.useId() const styleMap: Record = { THIN: { height: 'h-2', textSize: 'text-sm' }, THICK: { height: 'h-6', textSize: 'text-base' } } // Semantic color mappings const colorMap: Record = { ACCENT: 'bg-blue-500', POSITIVE: 'bg-green-700', NEGATIVE: 'bg-red-700', WARN: 'bg-yellow-600' } // Determine progress bar color const isSemanticColor = color in colorMap const isPalette = !isSemanticColor && isPaletteColor(color) const progressColor = isSemanticColor ? colorMap[color] : isPalette ? resolveColorClass(color, 'bg') : '' // Build CSS classes const sailClasses = [ marginAboveMap[marginAbove], marginBelowMap[marginBelow] ].filter(Boolean).join(' ') const containerClasses = mergeClasses(sailClasses, classNameProp) const progressBarClasses = [ 'relative', 'overflow-hidden', 'bg-gray-300', 'rounded-sm', styleMap[style].height ].join(' ') const progressIndicatorClasses = [ 'h-full', 'transition-transform', 'duration-300', 'ease-in-out', (isSemanticColor || isPalette) ? progressColor : 'bg-gray-500' ].join(' ') // Inline styles for custom colors const indicatorStyle: React.CSSProperties = {} if (!isSemanticColor && !isPalette && color) { indicatorStyle.backgroundColor = color } return (
{/* Percentage overlaid for THICK style */} {showPercentage && style === "THICK" && clampedPercentage > 15 && (
{displayPercentage}%
)} {/* Fallback for very low percentages */} {showPercentage && style === "THICK" && clampedPercentage <= 15 && (
{displayPercentage}%
)}
{/* Instructions and THIN percentage below bar */} {(instructions || (showPercentage && style === "THIN")) && (
{instructions && (

{instructions}

)}
{showPercentage && style === "THIN" && ( {displayPercentage}% )}
)} {/* Screen reader only text */} {accessibilityText && ( {accessibilityText} )}
) }