Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export const ProgressBarBase = React.forwardRef(function ProgressBarBase(

let barStyle: CSSProperties = {};
if (!isIndeterminate) {
let percentage = (value - minValue) / (maxValue - minValue);
let range = maxValue - minValue;
let percentage = range === 0 ? 0 : (value - minValue) / range;
barStyle.width = `${Math.round(percentage * 100)}%`;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ export const ProgressCircle = React.forwardRef(function ProgressCircle(
let subMask1Style: CSSProperties = {};
let subMask2Style: CSSProperties = {};
if (!isIndeterminate) {
let percentage = ((value - minValue) / (maxValue - minValue)) * 100;
let range = maxValue - minValue;
let percentage = range === 0 ? 0 : ((value - minValue) / range) * 100;
let angle;
if (percentage > 0 && percentage <= 50) {
angle = -180 + (percentage / 50) * 180;
Expand Down
4 changes: 2 additions & 2 deletions packages/react-aria-components/src/Meter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*/

import {AriaMeterProps, useMeter} from 'react-aria/useMeter';

import {clamp} from 'react-stately/private/utils/number';
import {
ClassNameOrFunction,
Expand Down Expand Up @@ -69,12 +68,13 @@ export const Meter = /*#__PURE__*/ (forwardRef as forwardRefType)(function Meter
[props, ref] = useContextProps(props, ref, MeterContext);
let {value = 0, minValue = 0, maxValue = 100} = props;
value = clamp(value, minValue, maxValue);
let range = maxValue - minValue;

let [labelRef, label] = useSlot(!props['aria-label'] && !props['aria-labelledby']);
let {meterProps, labelProps} = useMeter({...props, label});

// Calculate the width of the progress bar as a percentage
let percentage = ((value - minValue) / (maxValue - minValue)) * 100;
let percentage = range === 0 ? 0 : ((value - minValue) / range) * 100;

let renderProps = useRenderProps({
...props,
Expand Down
11 changes: 9 additions & 2 deletions packages/react-aria-components/src/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*/

import {AriaProgressBarProps, useProgressBar} from 'react-aria/useProgressBar';

import {clamp} from 'react-stately/private/utils/number';
import {
ClassNameOrFunction,
Expand Down Expand Up @@ -81,8 +80,16 @@ export const ProgressBar = forwardRef(function ProgressBar(
let [labelRef, label] = useSlot(!props['aria-label'] && !props['aria-labelledby']);
let {progressBarProps, labelProps} = useProgressBar({...props, label});

let range = maxValue - minValue;
// Calculate the width of the progress bar as a percentage
let percentage = isIndeterminate ? undefined : ((value - minValue) / (maxValue - minValue)) * 100;
let percentage: number | undefined = undefined;
if (!isIndeterminate) {
if (range === 0) {
percentage = 0;
} else {
percentage = ((value - minValue) / range) * 100;
}
}

let renderProps = useRenderProps({
...props,
Expand Down
40 changes: 40 additions & 0 deletions packages/react-aria-components/test/Meter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ let TestMeter = props => (
<>
<Label>Storage space</Label>
<span className="value">{valueText}</span>
<span className="percentage">{percentage}</span>
<div className="bar" style={{width: percentage + '%'}} />
</>
)}
Expand All @@ -48,6 +49,45 @@ describe('Meter', () => {
expect(bar).toHaveStyle('width: 25%');
});

it('supports a custom range', () => {
let {getByRole} = render(<TestMeter value={3} minValue={0} maxValue={6} />);

let meter = getByRole('meter');
expect(meter).toHaveAttribute('aria-valuenow', '3');
expect(meter).toHaveAttribute('aria-valuemin', '0');
expect(meter).toHaveAttribute('aria-valuemax', '6');
expect(meter).toHaveAttribute('aria-valuetext', '50%');

let value = meter.querySelector('.value');
expect(value).toHaveTextContent('50%');

let percentage = meter.querySelector('.percentage');
expect(percentage).toHaveTextContent('50');

let bar = meter.querySelector('.bar');
expect(bar).toHaveStyle('width: 50%');
});

it('renders 0 percent for an empty range', () => {
let {getByRole} = render(<TestMeter value={0} minValue={0} maxValue={0} />);

let meter = getByRole('meter');
expect(meter).toHaveAttribute('aria-valuenow', '0');
expect(meter).toHaveAttribute('aria-valuemin', '0');
expect(meter).toHaveAttribute('aria-valuemax', '0');
expect(meter).toHaveAttribute('aria-valuetext', '0%');
expect(meter).not.toHaveAttribute('aria-valuetext', 'NaN%');

let value = meter.querySelector('.value');
expect(value).toHaveTextContent('0%');

let percentage = meter.querySelector('.percentage');
expect(percentage).toHaveTextContent('0');

let bar = meter.querySelector('.bar');
expect(bar).toHaveStyle('width: 0%');
});

it('should support slot', () => {
let {getByRole} = render(
<MeterContext.Provider value={{slots: {test: {'aria-label': 'test'}}}}>
Expand Down
73 changes: 67 additions & 6 deletions packages/react-aria-components/test/ProgressBar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ let TestProgressBar = props => (
<>
<Label>Loading…</Label>
<span className="value">{valueText}</span>
<span className="percentage">{percentage}</span>
<div className="bar" style={{width: percentage + '%'}} />
</>
)}
Expand All @@ -48,23 +49,83 @@ describe('ProgressBar', () => {
expect(bar).toHaveStyle('width: 25%');
});

it('supports a custom range', () => {
let {getByRole} = render(<TestProgressBar value={3} minValue={0} maxValue={6} />);

let progressbar = getByRole('progressbar');
expect(progressbar).toHaveAttribute('aria-valuenow', '3');
expect(progressbar).toHaveAttribute('aria-valuemin', '0');
expect(progressbar).toHaveAttribute('aria-valuemax', '6');
expect(progressbar).toHaveAttribute('aria-valuetext', '50%');

let value = progressbar.querySelector('.value');
expect(value).toHaveTextContent('50%');

let percentage = progressbar.querySelector('.percentage');
expect(percentage).toHaveTextContent('50');

let bar = progressbar.querySelector('.bar');
expect(bar).toHaveStyle('width: 50%');
});

it('renders 0 percent for an empty range', () => {
let {getByRole} = render(<TestProgressBar value={0} minValue={0} maxValue={0} />);

let progressbar = getByRole('progressbar');
expect(progressbar).toHaveAttribute('aria-valuenow', '0');
expect(progressbar).toHaveAttribute('aria-valuemin', '0');
expect(progressbar).toHaveAttribute('aria-valuemax', '0');
expect(progressbar).toHaveAttribute('aria-valuetext', '0%');
expect(progressbar).not.toHaveAttribute('aria-valuetext', 'NaN%');

let value = progressbar.querySelector('.value');
expect(value).toHaveTextContent('0%');

let percentage = progressbar.querySelector('.percentage');
expect(percentage).toHaveTextContent('0');

let bar = progressbar.querySelector('.bar');
expect(bar).toHaveStyle('width: 0%');
});

it('renders 0 percent for an empty range with a non-zero bound', () => {
let {getByRole} = render(<TestProgressBar value={5} minValue={5} maxValue={5} />);

let progressbar = getByRole('progressbar');
expect(progressbar).toHaveAttribute('aria-valuenow', '5');
expect(progressbar).toHaveAttribute('aria-valuemin', '5');
expect(progressbar).toHaveAttribute('aria-valuemax', '5');
expect(progressbar).toHaveAttribute('aria-valuetext', '0%');

let percentage = progressbar.querySelector('.percentage');
expect(percentage).toHaveTextContent('0');

let bar = progressbar.querySelector('.bar');
expect(bar).toHaveStyle('width: 0%');
});

it('supports indeterminate state', () => {
let renderedPercentage;
let {getByRole} = render(
<ProgressBar
isIndeterminate
className={({isIndeterminate}) => `progressbar ${isIndeterminate ? 'indeterminate' : ''}`}>
{({percentage, valueText}) => (
<>
<Label>Loading…</Label>
<div className="bar" style={{width: percentage + '%'}} />
</>
)}
{({percentage}) => {
renderedPercentage = percentage;
return (
<>
<Label>Loading…</Label>
<div className="bar" style={{width: percentage + '%'}} />
</>
);
}}
</ProgressBar>
);

let progressbar = getByRole('progressbar');
expect(progressbar).toHaveAttribute('class', 'progressbar indeterminate');
expect(progressbar).not.toHaveAttribute('aria-valuenow');
expect(renderedPercentage).toBeUndefined();

let bar = progressbar.querySelector('.bar');
expect(bar.style.width).toBe('');
Expand Down
3 changes: 2 additions & 1 deletion packages/react-aria/src/progress/useProgressBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ export function useProgressBar(props: AriaProgressBarProps): ProgressBarAria {
});

value = clamp(value, minValue, maxValue);
let percentage = (value - minValue) / (maxValue - minValue);
let range = maxValue - minValue;
let percentage = range === 0 ? 0 : (value - minValue) / range;
let formatter = useNumberFormatter(formatOptions);

if (!isIndeterminate && !valueLabel) {
Expand Down