diff --git a/docs/data/material/components/progress/CircularCustomScale.js b/docs/data/material/components/progress/CircularCustomScale.js
new file mode 100644
index 00000000000000..4c1634a3fb50de
--- /dev/null
+++ b/docs/data/material/components/progress/CircularCustomScale.js
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import CircularProgress from '@mui/material/CircularProgress';
+
+export default function CircularCustomScale() {
+ const [progress, setProgress] = React.useState(10);
+
+ React.useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress((prevProgress) => (prevProgress >= 20 ? 10 : prevProgress + 2));
+ }, 800);
+
+ return () => {
+ clearInterval(timer);
+ };
+ }, []);
+
+ return (
+
+ );
+}
diff --git a/docs/data/material/components/progress/CircularCustomScale.tsx b/docs/data/material/components/progress/CircularCustomScale.tsx
new file mode 100644
index 00000000000000..4c1634a3fb50de
--- /dev/null
+++ b/docs/data/material/components/progress/CircularCustomScale.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import CircularProgress from '@mui/material/CircularProgress';
+
+export default function CircularCustomScale() {
+ const [progress, setProgress] = React.useState(10);
+
+ React.useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress((prevProgress) => (prevProgress >= 20 ? 10 : prevProgress + 2));
+ }, 800);
+
+ return () => {
+ clearInterval(timer);
+ };
+ }, []);
+
+ return (
+
+ );
+}
diff --git a/docs/data/material/components/progress/CircularCustomScale.tsx.preview b/docs/data/material/components/progress/CircularCustomScale.tsx.preview
new file mode 100644
index 00000000000000..a7c766ecf33d4c
--- /dev/null
+++ b/docs/data/material/components/progress/CircularCustomScale.tsx.preview
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/docs/data/material/components/progress/CircularWithValueLabel.js b/docs/data/material/components/progress/CircularWithValueLabel.js
index a5e42a9386ede1..9a1d3aca6e6130 100644
--- a/docs/data/material/components/progress/CircularWithValueLabel.js
+++ b/docs/data/material/components/progress/CircularWithValueLabel.js
@@ -39,7 +39,7 @@ function CircularProgressWithLabel(props) {
CircularProgressWithLabel.propTypes = {
/**
* The value of the progress indicator for the determinate variant.
- * Value between 0 and 100.
+ * Value between `min` and `max`.
* @default 0
*/
value: PropTypes.number.isRequired,
diff --git a/docs/data/material/components/progress/LinearQuery.js b/docs/data/material/components/progress/LinearQuery.js
new file mode 100644
index 00000000000000..4785ca8d14b3cd
--- /dev/null
+++ b/docs/data/material/components/progress/LinearQuery.js
@@ -0,0 +1,10 @@
+import Box from '@mui/material/Box';
+import LinearProgress from '@mui/material/LinearProgress';
+
+export default function LinearQuery() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/material/components/progress/LinearQuery.tsx b/docs/data/material/components/progress/LinearQuery.tsx
new file mode 100644
index 00000000000000..4785ca8d14b3cd
--- /dev/null
+++ b/docs/data/material/components/progress/LinearQuery.tsx
@@ -0,0 +1,10 @@
+import Box from '@mui/material/Box';
+import LinearProgress from '@mui/material/LinearProgress';
+
+export default function LinearQuery() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/material/components/progress/LinearQuery.tsx.preview b/docs/data/material/components/progress/LinearQuery.tsx.preview
new file mode 100644
index 00000000000000..b14a4d9082d17d
--- /dev/null
+++ b/docs/data/material/components/progress/LinearQuery.tsx.preview
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/data/material/components/progress/LinearWithAriaValueText.js b/docs/data/material/components/progress/LinearWithAriaValueText.js
new file mode 100644
index 00000000000000..3aa59fda652f93
--- /dev/null
+++ b/docs/data/material/components/progress/LinearWithAriaValueText.js
@@ -0,0 +1,77 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import LinearProgress from '@mui/material/LinearProgress';
+import Typography from '@mui/material/Typography';
+import Box from '@mui/material/Box';
+
+function LinearProgressWithLabelAndValue({ max, min, value, ...rest }) {
+ const progressText = `Elevator at floor ${value} out of ${max}.`;
+ const progressId = React.useId();
+ return (
+
+
+ Elevator status
+
+
+
+
+
+
+
+ {progressText}
+
+
+
+
+ );
+}
+
+LinearProgressWithLabelAndValue.propTypes = {
+ /**
+ * The maximum value for the progress indicator for the determinate and buffer variants.
+ * @default 100
+ */
+ max: PropTypes.number.isRequired,
+ /**
+ * The minimum value for the progress indicator for the determinate and buffer variants.
+ * @default 0
+ */
+ min: PropTypes.number.isRequired,
+ /**
+ * The value of the progress indicator for the determinate and buffer variants.
+ * Value between `min` and `max`.
+ */
+ value: PropTypes.number.isRequired,
+};
+
+export default function LinearWithAriaValueText() {
+ const [progress, setProgress] = React.useState(1);
+
+ React.useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress((prevProgress) => (prevProgress >= 10 ? 1 : prevProgress + 1));
+ }, 800);
+ return () => {
+ clearInterval(timer);
+ };
+ }, []);
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/material/components/progress/LinearWithAriaValueText.tsx b/docs/data/material/components/progress/LinearWithAriaValueText.tsx
new file mode 100644
index 00000000000000..0ec123cad46176
--- /dev/null
+++ b/docs/data/material/components/progress/LinearWithAriaValueText.tsx
@@ -0,0 +1,69 @@
+import * as React from 'react';
+import LinearProgress, { LinearProgressProps } from '@mui/material/LinearProgress';
+import Typography from '@mui/material/Typography';
+import Box from '@mui/material/Box';
+
+type LinearProgressWithLabelAndValueProps = LinearProgressProps & {
+ min: number;
+ max: number;
+ value: number;
+};
+
+function LinearProgressWithLabelAndValue({
+ max,
+ min,
+ value,
+ ...rest
+}: LinearProgressWithLabelAndValueProps) {
+ const progressText = `Elevator at floor ${value} out of ${max}.`;
+ const progressId = React.useId();
+ return (
+
+
+ Elevator status
+
+
+
+
+
+
+
+ {progressText}
+
+
+
+
+ );
+}
+
+export default function LinearWithAriaValueText() {
+ const [progress, setProgress] = React.useState(1);
+
+ React.useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress((prevProgress) => (prevProgress >= 10 ? 1 : prevProgress + 1));
+ }, 800);
+ return () => {
+ clearInterval(timer);
+ };
+ }, []);
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/material/components/progress/LinearWithAriaValueText.tsx.preview b/docs/data/material/components/progress/LinearWithAriaValueText.tsx.preview
new file mode 100644
index 00000000000000..1595531620a5ba
--- /dev/null
+++ b/docs/data/material/components/progress/LinearWithAriaValueText.tsx.preview
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/data/material/components/progress/LinearWithValueLabel.js b/docs/data/material/components/progress/LinearWithValueLabel.js
index 85251478f227df..4bb45a6ad23769 100644
--- a/docs/data/material/components/progress/LinearWithValueLabel.js
+++ b/docs/data/material/components/progress/LinearWithValueLabel.js
@@ -4,29 +4,40 @@ import LinearProgress from '@mui/material/LinearProgress';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
-function LinearProgressWithLabel(props) {
+function LinearProgressWithLabelAndValue(props) {
+ const progressId = React.useId();
return (
-
-
-
+
+
+ Uploading photos…
+
+
+
+
+
+
+
+ {`${Math.round(props.value)}%`}
+
+
-
-
- {`${Math.round(props.value)}%`}
-
-
-
+
);
}
-LinearProgressWithLabel.propTypes = {
+LinearProgressWithLabelAndValue.propTypes = {
/**
* The value of the progress indicator for the determinate and buffer variants.
- * Value between 0 and 100.
+ * Value between `min` and `max`.
*/
value: PropTypes.number.isRequired,
};
@@ -45,7 +56,7 @@ export default function LinearWithValueLabel() {
return (
-
+
);
}
diff --git a/docs/data/material/components/progress/LinearWithValueLabel.tsx b/docs/data/material/components/progress/LinearWithValueLabel.tsx
index 49d05a435fdf26..a35eafeeb34512 100644
--- a/docs/data/material/components/progress/LinearWithValueLabel.tsx
+++ b/docs/data/material/components/progress/LinearWithValueLabel.tsx
@@ -3,23 +3,35 @@ import LinearProgress, { LinearProgressProps } from '@mui/material/LinearProgres
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
-function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) {
+function LinearProgressWithLabelAndValue(
+ props: LinearProgressProps & { value: number },
+) {
+ const progressId = React.useId();
return (
-
-
-
+
+
+ Uploading photos…
+
+
+
+
+
+
+
+ {`${Math.round(props.value)}%`}
+
+
-
- {`${Math.round(props.value)}%`}
-
-
+
);
}
@@ -37,7 +49,7 @@ export default function LinearWithValueLabel() {
return (
-
+
);
}
diff --git a/docs/data/material/components/progress/LinearWithValueLabel.tsx.preview b/docs/data/material/components/progress/LinearWithValueLabel.tsx.preview
index 106f18a93820c2..62528e5a310292 100644
--- a/docs/data/material/components/progress/LinearWithValueLabel.tsx.preview
+++ b/docs/data/material/components/progress/LinearWithValueLabel.tsx.preview
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/data/material/components/progress/progress.md b/docs/data/material/components/progress/progress.md
index b60007b63f5fa1..38f1f5c2545a15 100644
--- a/docs/data/material/components/progress/progress.md
+++ b/docs/data/material/components/progress/progress.md
@@ -38,6 +38,12 @@ The animations of the components rely on CSS as much as possible to work even be
{{"demo": "CircularDeterminate.js"}}
+### Circular custom scale
+
+By default, progress values are expected in the 0–100 range. You can customize this range by using the `min` and `max` props.
+
+{{"demo": "CircularCustomScale.js"}}
+
### Circular track
{{"demo": "CircularEnableTrack.js"}}
@@ -56,6 +62,10 @@ The animations of the components rely on CSS as much as possible to work even be
{{"demo": "LinearIndeterminate.js"}}
+### Linear query
+
+{{"demo": "LinearQuery.js"}}
+
### Linear color
{{"demo": "LinearColor.js"}}
@@ -68,38 +78,15 @@ The animations of the components rely on CSS as much as possible to work even be
{{"demo": "LinearBuffer.js"}}
-### Linear with label
+### Linear with label and value label
{{"demo": "LinearWithValueLabel.js"}}
-## Non-standard ranges
-
-The progress components accept a value in the range 0 - 100. This simplifies things for screen-reader users, where these are the default min / max values. Sometimes, however, you might be working with a data source where the values fall outside this range. Here's how you can easily transform a value in any range to a scale of 0 - 100:
-
-```jsx
-// MIN = Minimum expected value
-// MAX = Maximum expected value
-// Function to normalise the values (MIN / MAX could be integrated)
-const normalise = (value) => ((value - MIN) * 100) / (MAX - MIN);
-
-// Example component that utilizes the `normalise` function at the point of render.
-function Progress(props) {
- return (
-
-
-
-
- );
-}
-```
+### Linear with custom value text
+
+By default, the progress value is read by assistive technology as percentages. Use `aria-valuetext` when the progress value does not involve percentages.
+
+{{"demo": "LinearWithAriaValueText.js"}}
## Customization
diff --git a/docs/pages/material-ui/api/circular-progress.json b/docs/pages/material-ui/api/circular-progress.json
index f63f73040a471b..fc7517c646aa5e 100644
--- a/docs/pages/material-ui/api/circular-progress.json
+++ b/docs/pages/material-ui/api/circular-progress.json
@@ -10,6 +10,8 @@
},
"disableShrink": { "type": { "name": "custom", "description": "bool" }, "default": "false" },
"enableTrackSlot": { "type": { "name": "bool" }, "default": "false" },
+ "max": { "type": { "name": "number" }, "default": "100" },
+ "min": { "type": { "name": "number" }, "default": "0" },
"size": {
"type": { "name": "union", "description": "number
| string" },
"default": "40"
diff --git a/docs/pages/material-ui/api/linear-progress.json b/docs/pages/material-ui/api/linear-progress.json
index 24bad2d39cb85c..7d8e28f3c14f49 100644
--- a/docs/pages/material-ui/api/linear-progress.json
+++ b/docs/pages/material-ui/api/linear-progress.json
@@ -8,6 +8,8 @@
},
"default": "'primary'"
},
+ "max": { "type": { "name": "number" }, "default": "100" },
+ "min": { "type": { "name": "number" }, "default": "0" },
"sx": {
"type": {
"name": "union",
diff --git a/docs/translations/api-docs/circular-progress/circular-progress.json b/docs/translations/api-docs/circular-progress/circular-progress.json
index d8c41375452659..57dbd12f9bc486 100644
--- a/docs/translations/api-docs/circular-progress/circular-progress.json
+++ b/docs/translations/api-docs/circular-progress/circular-progress.json
@@ -11,6 +11,12 @@
"enableTrackSlot": {
"description": "If true, a track circle slot is mounted to show a subtle background for the progress. The size and thickness apply to the track slot to be consistent with the progress circle."
},
+ "max": {
+ "description": "The maximum value for the progress indicator for the determinate variant."
+ },
+ "min": {
+ "description": "The minimum value for the progress indicator for the determinate variant."
+ },
"size": {
"description": "The size of the component. If using a number, the pixel unit is assumed. If using a string, you need to provide the CSS unit, for example '3rem'."
},
@@ -19,7 +25,7 @@
},
"thickness": { "description": "The thickness of the circle." },
"value": {
- "description": "The value of the progress indicator for the determinate variant. Value between 0 and 100."
+ "description": "The value of the progress indicator for the determinate variant. Value between min and max."
},
"variant": {
"description": "The variant to use. Use indeterminate when there is no progress value."
diff --git a/docs/translations/api-docs/linear-progress/linear-progress.json b/docs/translations/api-docs/linear-progress/linear-progress.json
index 58187bd226c6a9..2e778c18335e1d 100644
--- a/docs/translations/api-docs/linear-progress/linear-progress.json
+++ b/docs/translations/api-docs/linear-progress/linear-progress.json
@@ -5,13 +5,21 @@
"color": {
"description": "The color of the component. It supports both default and custom theme colors, which can be added as shown in the palette customization guide."
},
+ "max": {
+ "description": "The maximum value for the progress indicator for the determinate and buffer variants."
+ },
+ "min": {
+ "description": "The minimum value for the progress indicator for the determinate and buffer variants."
+ },
"sx": {
"description": "The system prop that allows defining system overrides as well as additional CSS styles."
},
"value": {
- "description": "The value of the progress indicator for the determinate and buffer variants. Value between 0 and 100."
+ "description": "The value of the progress indicator for the determinate and buffer variants. Value between min and max."
+ },
+ "valueBuffer": {
+ "description": "The value for the buffer variant. Value between min and max."
},
- "valueBuffer": { "description": "The value for the buffer variant. Value between 0 and 100." },
"variant": {
"description": "The variant to use. Use indeterminate or query when there is no progress value."
}
diff --git a/packages/mui-material/src/CircularProgress/CircularProgress.d.ts b/packages/mui-material/src/CircularProgress/CircularProgress.d.ts
index ccfaacc659102b..5b0bd16bb2f0db 100644
--- a/packages/mui-material/src/CircularProgress/CircularProgress.d.ts
+++ b/packages/mui-material/src/CircularProgress/CircularProgress.d.ts
@@ -40,6 +40,16 @@ export interface CircularProgressProps extends StandardProps<
* @default false
*/
enableTrackSlot?: boolean | undefined;
+ /**
+ * The maximum value for the progress indicator for the determinate variant.
+ * @default 100
+ */
+ max?: number | undefined;
+ /**
+ * The minimum value for the progress indicator for the determinate variant.
+ * @default 0
+ */
+ min?: number | undefined;
/**
* The size of the component.
* If using a number, the pixel unit is assumed.
@@ -58,7 +68,7 @@ export interface CircularProgressProps extends StandardProps<
thickness?: number | undefined;
/**
* The value of the progress indicator for the determinate variant.
- * Value between 0 and 100.
+ * Value between `min` and `max`.
* @default 0
*/
value?: number | undefined;
diff --git a/packages/mui-material/src/CircularProgress/CircularProgress.js b/packages/mui-material/src/CircularProgress/CircularProgress.js
index 0686fade4245c8..953a45df7e6d71 100644
--- a/packages/mui-material/src/CircularProgress/CircularProgress.js
+++ b/packages/mui-material/src/CircularProgress/CircularProgress.js
@@ -155,7 +155,7 @@ const CircularProgressCircle = styled('circle', {
props: ({ ownerState }) =>
ownerState.variant === 'indeterminate' && !ownerState.disableShrink,
style: dashAnimation || {
- // At runtime for Pigment CSS, `bufferAnimation` will be null and the generated keyframe will be used.
+ // At runtime for Pigment CSS, `dashAnimation` will be null and the generated keyframe will be used.
animation: `${circularDashKeyframe} 1.4s ease-in-out infinite`,
},
},
@@ -187,6 +187,8 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref
color = 'primary',
disableShrink = false,
enableTrackSlot = false,
+ min: minProp,
+ max: maxProp,
size = 40,
style,
thickness = 3.6,
@@ -195,6 +197,17 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref
...other
} = props;
+ if (process.env.NODE_ENV !== 'production') {
+ if (variant === 'indeterminate' && (minProp !== undefined || maxProp !== undefined)) {
+ console.warn(
+ `MUI: You have provided the \`min\` or \`max\` props with a 'indeterminate' variant. These props will have no effect.`,
+ );
+ }
+ }
+
+ const min = minProp ?? 0;
+ const max = maxProp ?? 100;
+
const ownerState = {
...props,
color,
@@ -214,10 +227,24 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref
if (variant === 'determinate') {
const circumference = 2 * Math.PI * ((SIZE - thickness) / 2);
+
+ if (process.env.NODE_ENV !== 'production') {
+ if (value < min || value > max || min >= max) {
+ console.error(
+ `MUI: The min, max, and value props in CircularProgress should be numbers where min < max and min <= value <= max. Received min=${min}, max=${max}, value=${value}.`,
+ );
+ }
+ }
+
+ const range = max - min;
circleStyle.strokeDasharray = circumference.toFixed(3);
- rootProps['aria-valuenow'] = Math.round(value);
- circleStyle.strokeDashoffset = `${(((100 - value) / 100) * circumference).toFixed(3)}px`;
+ circleStyle.strokeDashoffset =
+ range > 0 ? `${(((max - value) / range) * circumference).toFixed(3)}px` : '0px';
rootStyle.transform = 'rotate(-90deg)';
+
+ rootProps['aria-valuenow'] = Math.round(value);
+ rootProps['aria-valuemin'] = min;
+ rootProps['aria-valuemax'] = max;
}
return (
@@ -306,6 +333,16 @@ CircularProgress.propTypes /* remove-proptypes */ = {
* @default false
*/
enableTrackSlot: PropTypes.bool,
+ /**
+ * The maximum value for the progress indicator for the determinate variant.
+ * @default 100
+ */
+ max: PropTypes.number,
+ /**
+ * The minimum value for the progress indicator for the determinate variant.
+ * @default 0
+ */
+ min: PropTypes.number,
/**
* The size of the component.
* If using a number, the pixel unit is assumed.
@@ -332,7 +369,7 @@ CircularProgress.propTypes /* remove-proptypes */ = {
thickness: PropTypes.number,
/**
* The value of the progress indicator for the determinate variant.
- * Value between 0 and 100.
+ * Value between `min` and `max`.
* @default 0
*/
value: PropTypes.number,
diff --git a/packages/mui-material/src/CircularProgress/CircularProgress.test.js b/packages/mui-material/src/CircularProgress/CircularProgress.test.js
index 706d5e2d104202..0cad1c6017a708 100644
--- a/packages/mui-material/src/CircularProgress/CircularProgress.test.js
+++ b/packages/mui-material/src/CircularProgress/CircularProgress.test.js
@@ -1,5 +1,9 @@
import { expect } from 'chai';
-import { createRenderer } from '@mui/internal-test-utils';
+import {
+ createRenderer,
+ strictModeDoubleLoggingSuppressed,
+ screen,
+} from '@mui/internal-test-utils';
import CircularProgress, {
circularProgressClasses as classes,
} from '@mui/material/CircularProgress';
@@ -170,4 +174,80 @@ describe('', () => {
expect(trackEl.style.strokeDashoffset).to.equal('');
});
});
+
+ describe('prop: min & max', () => {
+ it('should be able to use custom min and max values', () => {
+ render();
+ const progressbar = screen.getByRole('progressbar');
+
+ expect(progressbar).to.have.attribute('aria-valuenow', '5');
+ expect(progressbar).to.have.attribute('aria-valuemin', '0');
+ expect(progressbar).to.have.attribute('aria-valuemax', '10');
+ });
+
+ it('min and max values should be used to calculate the circumference of the circle', () => {
+ const { container } = render(
+ ,
+ );
+ const [circle] = container.querySelectorAll('svg circle');
+ const progressbar = screen.getByRole('progressbar');
+
+ expect(progressbar).to.have.nested.property('style.transform', 'rotate(-90deg)');
+ expect(circle.style.strokeDasharray).to.match(/126\.920?(px)?/gm);
+ expect(circle.style.strokeDashoffset).to.match(/95\.190?(px)?/gm);
+ });
+
+ it('should fallback to 0px strokeDashoffset if max is less than min', () => {
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
+
+ const { container } = render(
+ ,
+ );
+ const [circle] = container.querySelectorAll('svg circle');
+ expect(circle.style.strokeDashoffset).to.equal('0px');
+
+ errorSpy.mockRestore();
+ });
+
+ it('should error if min, max, and value props are invalid', () => {
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'MUI: The min, max, and value props in CircularProgress should be numbers where min < max and min <= value <= max. Received min=10, max=0, value=5.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'MUI: The min, max, and value props in CircularProgress should be numbers where min < max and min <= value <= max. Received min=10, max=0, value=5.',
+ ]);
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'MUI: The min, max, and value props in CircularProgress should be numbers where min < max and min <= value <= max. Received min=10, max=10, value=15.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'MUI: The min, max, and value props in CircularProgress should be numbers where min < max and min <= value <= max. Received min=10, max=10, value=15.',
+ ]);
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'MUI: The min, max, and value props in CircularProgress should be numbers where min < max and min <= value <= max. Received min=10, max=20, value=5.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'MUI: The min, max, and value props in CircularProgress should be numbers where min < max and min <= value <= max. Received min=10, max=20, value=5.',
+ ]);
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'MUI: The min, max, and value props in CircularProgress should be numbers where min < max and min <= value <= max. Received min=10, max=20, value=25.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'MUI: The min, max, and value props in CircularProgress should be numbers where min < max and min <= value <= max. Received min=10, max=20, value=25.',
+ ]);
+ });
+
+ it('should warn if min and max props are provided with an indeterminate variant', () => {
+ expect(() => {
+ render();
+ }).toWarnDev([
+ "MUI: You have provided the `min` or `max` props with a 'indeterminate' variant. These props will have no effect.",
+ !strictModeDoubleLoggingSuppressed &&
+ "MUI: You have provided the `min` or `max` props with a 'indeterminate' variant. These props will have no effect.",
+ ]);
+ });
+ });
});
diff --git a/packages/mui-material/src/LinearProgress/LinearProgress.d.ts b/packages/mui-material/src/LinearProgress/LinearProgress.d.ts
index b6f062fd51d47b..8592d522225b98 100644
--- a/packages/mui-material/src/LinearProgress/LinearProgress.d.ts
+++ b/packages/mui-material/src/LinearProgress/LinearProgress.d.ts
@@ -28,18 +28,28 @@ export interface LinearProgressProps extends StandardProps<
LinearProgressPropsColorOverrides
>
| undefined;
+ /**
+ * The maximum value for the progress indicator for the determinate and buffer variants.
+ * @default 100
+ */
+ max?: number | undefined;
+ /**
+ * The minimum value for the progress indicator for the determinate and buffer variants.
+ * @default 0
+ */
+ min?: number | undefined;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps | undefined;
/**
* The value of the progress indicator for the determinate and buffer variants.
- * Value between 0 and 100.
+ * Value between `min` and `max`.
*/
value?: number | undefined;
/**
* The value for the buffer variant.
- * Value between 0 and 100.
+ * Value between `min` and `max`.
*/
valueBuffer?: number | undefined;
/**
diff --git a/packages/mui-material/src/LinearProgress/LinearProgress.js b/packages/mui-material/src/LinearProgress/LinearProgress.js
index 620156e49a4e62..1f3c071e33665c 100644
--- a/packages/mui-material/src/LinearProgress/LinearProgress.js
+++ b/packages/mui-material/src/LinearProgress/LinearProgress.js
@@ -357,6 +357,8 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) {
const {
className,
color = 'primary',
+ max: maxProp,
+ min: minProp,
value,
valueBuffer,
variant = 'indeterminate',
@@ -368,6 +370,20 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) {
variant,
};
+ if (process.env.NODE_ENV !== 'production') {
+ if (
+ ['indeterminate', 'query'].includes(variant) &&
+ (minProp !== undefined || maxProp !== undefined)
+ ) {
+ console.warn(
+ `MUI: You have provided the \`min\` or \`max\` props with a 'indeterminate' or 'query' variant. These props will have no effect.`,
+ );
+ }
+ }
+
+ const min = minProp ?? 0;
+ const max = maxProp ?? 100;
+
const classes = useUtilityClasses(ownerState);
const isRtl = useRtl();
@@ -376,28 +392,47 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) {
if (variant === 'determinate' || variant === 'buffer') {
if (value !== undefined) {
- rootProps['aria-valuenow'] = Math.round(value);
- rootProps['aria-valuemin'] = 0;
- rootProps['aria-valuemax'] = 100;
- let transform = value - 100;
+ if (process.env.NODE_ENV !== 'production') {
+ if (value < min || value > max || min >= max) {
+ console.error(
+ `MUI: The min, max, and value props in LinearProgress should be numbers where min < max and min <= value <= max. Received min=${min}, max=${max}, value=${value}.`,
+ );
+ }
+ }
+
+ const range = max - min;
+ let transform = ((value - min) / range) * 100 - 100;
if (isRtl) {
transform = -transform;
}
- inlineStyles.bar1.transform = `translateX(${transform}%)`;
+ inlineStyles.bar1.transform = range > 0 ? `translateX(${transform}%)` : undefined;
+
+ rootProps['aria-valuenow'] = Math.round(value);
+ rootProps['aria-valuemin'] = min;
+ rootProps['aria-valuemax'] = max;
} else if (process.env.NODE_ENV !== 'production') {
console.error(
'MUI: You need to provide a value prop ' +
- 'when using the determinate or buffer variant of LinearProgress .',
+ 'when using the determinate or buffer variant of LinearProgress.',
);
}
}
if (variant === 'buffer') {
if (valueBuffer !== undefined) {
- let transform = (valueBuffer || 0) - 100;
+ if (process.env.NODE_ENV !== 'production') {
+ if (valueBuffer < min || valueBuffer > max || valueBuffer < value || min >= max) {
+ console.error(
+ `MUI: The min, max, value, and valueBuffer props in LinearProgress should be numbers where min < max and min <= value <= valueBuffer <= max. Received min=${min}, max=${max}, value=${value}, valueBuffer=${valueBuffer}.`,
+ );
+ }
+ }
+
+ const range = max - min;
+ let transform = ((valueBuffer - min) / range) * 100 - 100;
if (isRtl) {
transform = -transform;
}
- inlineStyles.bar2.transform = `translateX(${transform}%)`;
+ inlineStyles.bar2.transform = range > 0 ? `translateX(${transform}%)` : undefined;
} else if (process.env.NODE_ENV !== 'production') {
console.error(
'MUI: You need to provide a valueBuffer prop ' +
@@ -457,6 +492,16 @@ LinearProgress.propTypes /* remove-proptypes */ = {
PropTypes.oneOf(['inherit', 'primary', 'secondary']),
PropTypes.string,
]),
+ /**
+ * The maximum value for the progress indicator for the determinate and buffer variants.
+ * @default 100
+ */
+ max: PropTypes.number,
+ /**
+ * The minimum value for the progress indicator for the determinate and buffer variants.
+ * @default 0
+ */
+ min: PropTypes.number,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
@@ -467,12 +512,12 @@ LinearProgress.propTypes /* remove-proptypes */ = {
]),
/**
* The value of the progress indicator for the determinate and buffer variants.
- * Value between 0 and 100.
+ * Value between `min` and `max`.
*/
value: PropTypes.number,
/**
* The value for the buffer variant.
- * Value between 0 and 100.
+ * Value between `min` and `max`.
*/
valueBuffer: PropTypes.number,
/**
diff --git a/packages/mui-material/src/LinearProgress/LinearProgress.test.js b/packages/mui-material/src/LinearProgress/LinearProgress.test.js
index aebcd614c9f69c..f153820010dd7b 100644
--- a/packages/mui-material/src/LinearProgress/LinearProgress.test.js
+++ b/packages/mui-material/src/LinearProgress/LinearProgress.test.js
@@ -4,6 +4,7 @@ import {
screen,
strictModeDoubleLoggingSuppressed,
} from '@mui/internal-test-utils';
+import RtlProvider from '@mui/system/RtlProvider';
import LinearProgress, { linearProgressClasses as classes } from '@mui/material/LinearProgress';
import describeConformance from '../../test/describeConformance';
@@ -83,6 +84,17 @@ describe('', () => {
expect(progressbar.children[0]).to.have.nested.property('style.transform', 'translateX(-23%)');
});
+ it('should set opposite width of bar1 on determinate variant in RTL', () => {
+ render(
+
+ ,
+ ,
+ );
+ const progressbar = screen.getByRole('progressbar');
+
+ expect(progressbar.children[0]).to.have.nested.property('style.transform', 'translateX(23%)');
+ });
+
it('should render with buffer classes for the primary color by default', () => {
render();
const progressbar = screen.getByRole('progressbar');
@@ -132,6 +144,7 @@ describe('', () => {
it('should render with query classes', () => {
render();
+
const progressbar = screen.getByRole('progressbar');
expect(progressbar).to.have.class(classes.query);
@@ -169,4 +182,141 @@ describe('', () => {
]);
});
});
+
+ describe('prop: min & max', () => {
+ it('should be able to use custom min and max values', () => {
+ render();
+ const progressbar = screen.getByRole('progressbar');
+
+ expect(progressbar).to.have.attribute('aria-valuenow', '5');
+ expect(progressbar).to.have.attribute('aria-valuemin', '0');
+ expect(progressbar).to.have.attribute('aria-valuemax', '10');
+ });
+
+ it('min and max values should be used to calculate the width of the bar', () => {
+ render();
+ const progressbar = screen.getByRole('progressbar');
+
+ expect(progressbar.children[0]).to.have.nested.property(
+ 'style.transform',
+ 'translateX(-75%)',
+ );
+ });
+
+ it('min and max values should be used to calculate the width of the buffer bar', () => {
+ render();
+ const progressbar = screen.getByRole('progressbar');
+
+ expect(progressbar.querySelector(`.${classes.bar1}`)).to.have.nested.property(
+ 'style.transform',
+ 'translateX(-75%)',
+ );
+ expect(progressbar.querySelector(`.${classes.bar2}`)).to.have.nested.property(
+ 'style.transform',
+ 'translateX(-25%)',
+ );
+ });
+
+ it('should not add transform style to the progress bar when min is equal or larger than max', () => {
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
+
+ render();
+ const progressbar = screen.getByRole('progressbar');
+
+ expect(progressbar.children[0].style.transform).to.equal('');
+
+ errorSpy.mockRestore();
+ });
+
+ it('should not add transform style to the buffer bar when min is equal or larger than max', () => {
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
+
+ render();
+ const progressbar = screen.getByRole('progressbar');
+
+ expect(progressbar.children[1].style.transform).to.equal('');
+ expect(progressbar.children[2].style.transform).to.equal('');
+
+ errorSpy.mockRestore();
+ });
+
+ it('should warn if the value is out of range', () => {
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'The min, max, and value props in LinearProgress should be numbers where min < max and min <= value <= max. Received min=0, max=10, value=-1.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'The min, max, and value props in LinearProgress should be numbers where min < max and min <= value <= max. Received min=0, max=10, value=-1.',
+ ]);
+
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'The min, max, and value props in LinearProgress should be numbers where min < max and min <= value <= max. Received min=0, max=10, value=11.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'The min, max, and value props in LinearProgress should be numbers where min < max and min <= value <= max. Received min=0, max=10, value=11.',
+ ]);
+ });
+
+ it('should error if the valueBuffer is out of range or less than the value prop', () => {
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'The min, max, value, and valueBuffer props in LinearProgress should be numbers where min < max and min <= value <= valueBuffer <= max. Received min=0, max=10, value=5, valueBuffer=4.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'The min, max, value, and valueBuffer props in LinearProgress should be numbers where min < max and min <= value <= valueBuffer <= max. Received min=0, max=10, value=5, valueBuffer=4.',
+ ]);
+
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'The min, max, value, and valueBuffer props in LinearProgress should be numbers where min < max and min <= value <= valueBuffer <= max. Received min=0, max=10, value=5, valueBuffer=11.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'The min, max, value, and valueBuffer props in LinearProgress should be numbers where min < max and min <= value <= valueBuffer <= max. Received min=0, max=10, value=5, valueBuffer=11.',
+ ]);
+
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'The min, max, value, and valueBuffer props in LinearProgress should be numbers where min < max and min <= value <= valueBuffer <= max. Received min=0, max=10, value=5, valueBuffer=-1.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'The min, max, value, and valueBuffer props in LinearProgress should be numbers where min < max and min <= value <= valueBuffer <= max. Received min=0, max=10, value=5, valueBuffer=-1.',
+ ]);
+ });
+
+ it('should error if min is equal or greater than max', () => {
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'The min, max, and value props in LinearProgress should be numbers where min < max and min <= value <= max. Received min=10, max=0, value=5.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'The min, max, and value props in LinearProgress should be numbers where min < max and min <= value <= max. Received min=10, max=0, value=5.',
+ ]);
+ expect(() => {
+ render();
+ }).toErrorDev([
+ 'The min, max, and value props in LinearProgress should be numbers where min < max and min <= value <= max. Received min=10, max=10, value=5.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'The min, max, and value props in LinearProgress should be numbers where min < max and min <= value <= max. Received min=10, max=10, value=5.',
+ ]);
+ });
+
+ it('should warn if variant is indeterminate or query and min or max props are provided', () => {
+ expect(() => {
+ render();
+ }).toWarnDev([
+ "MUI: You have provided the `min` or `max` props with a 'indeterminate' or 'query' variant. These props will have no effect.",
+ !strictModeDoubleLoggingSuppressed &&
+ "MUI: You have provided the `min` or `max` props with a 'indeterminate' or 'query' variant. These props will have no effect.",
+ ]);
+
+ expect(() => {
+ render();
+ }).toWarnDev([
+ "MUI: You have provided the `min` or `max` props with a 'indeterminate' or 'query' variant. These props will have no effect.",
+ !strictModeDoubleLoggingSuppressed &&
+ "MUI: You have provided the `min` or `max` props with a 'indeterminate' or 'query' variant. These props will have no effect.",
+ ]);
+ });
+ });
});