|
| 1 | +import { clamp } from '@react-aria/utils'; |
| 2 | +import React, { CSSProperties, HTMLAttributes } from 'react'; |
| 3 | +import { DOMRef, ProgressBarProps, ACProgressBarBaseProps } from '../types'; |
| 4 | +import { classNames, useDOMRef, useStyleProps } from '../utils'; |
| 5 | +import { progressBarCSS } from './styles'; |
| 6 | +interface ProgressBarBaseProps |
| 7 | + extends ACProgressBarBaseProps, |
| 8 | + ProgressBarProps { |
| 9 | + barClassName?: string; |
| 10 | + barProps?: HTMLAttributes<HTMLDivElement>; |
| 11 | + labelProps?: HTMLAttributes<HTMLLabelElement>; |
| 12 | +} |
| 13 | + |
| 14 | +// Base ProgressBar component shared with Meter. |
| 15 | +function ProgressBarBase( |
| 16 | + props: ProgressBarBaseProps, |
| 17 | + ref: DOMRef<HTMLDivElement> |
| 18 | +) { |
| 19 | + let { |
| 20 | + value = 0, |
| 21 | + minValue = 0, |
| 22 | + maxValue = 100, |
| 23 | + size = 'L', |
| 24 | + label, |
| 25 | + barClassName, |
| 26 | + showValueLabel = !!label, |
| 27 | + labelPosition = 'top', |
| 28 | + isIndeterminate = false, |
| 29 | + barProps, |
| 30 | + labelProps, |
| 31 | + 'aria-label': ariaLabel, |
| 32 | + 'aria-labelledby': ariaLabelledby, |
| 33 | + ...otherProps |
| 34 | + } = props; |
| 35 | + let domRef = useDOMRef(ref); |
| 36 | + let { styleProps } = useStyleProps(otherProps); |
| 37 | + |
| 38 | + value = clamp(value, minValue, maxValue); |
| 39 | + |
| 40 | + let barStyle: CSSProperties = {}; |
| 41 | + if (!isIndeterminate) { |
| 42 | + let percentage = (value - minValue) / (maxValue - minValue); |
| 43 | + barStyle.width = `${Math.round(percentage * 100)}%`; |
| 44 | + } |
| 45 | + |
| 46 | + // Ideally this should be in useProgressBar, but children |
| 47 | + // are not supported in ProgressCircle which shares that hook... |
| 48 | + if (!label && !ariaLabel && !ariaLabelledby) { |
| 49 | + // eslint-disable-next-line no-console |
| 50 | + console.warn( |
| 51 | + 'If you do not provide a visible label via children, you must specify an aria-label or aria-labelledby attribute for accessibility' |
| 52 | + ); |
| 53 | + } |
| 54 | + // use inline style for fit-content because cssnano is too smart for us and will strip out the -moz prefix in css files |
| 55 | + return ( |
| 56 | + <div |
| 57 | + {...barProps} |
| 58 | + ref={domRef} |
| 59 | + className={classNames( |
| 60 | + 'ac-barloader', |
| 61 | + { |
| 62 | + 'ac-barloader--small': size === 'S', |
| 63 | + 'ac-barloader--large': size === 'L', |
| 64 | + 'ac-barloader--indeterminate': isIndeterminate, |
| 65 | + 'ac-barloader--sideLabel': labelPosition === 'side', |
| 66 | + }, |
| 67 | + barClassName, |
| 68 | + styleProps.className |
| 69 | + )} |
| 70 | + css={progressBarCSS} |
| 71 | + style={{ minWidth: '-moz-fit-content', ...styleProps.style }} |
| 72 | + > |
| 73 | + {label && ( |
| 74 | + <span {...labelProps} className="ac-barloader-label"> |
| 75 | + {label} |
| 76 | + </span> |
| 77 | + )} |
| 78 | + {showValueLabel && barProps && ( |
| 79 | + <div className={'ac-barloader-percentage'}> |
| 80 | + {barProps['aria-valuetext']} |
| 81 | + </div> |
| 82 | + )} |
| 83 | + <div className={'ac-barloader-track'}> |
| 84 | + <div className={'ac-barloader-fill'} style={barStyle} /> |
| 85 | + </div> |
| 86 | + </div> |
| 87 | + ); |
| 88 | +} |
| 89 | + |
| 90 | +let _ProgressBarBase = React.forwardRef(ProgressBarBase); |
| 91 | +export { _ProgressBarBase as ProgressBarBase }; |
0 commit comments