diff --git a/docs/pages/material-ui/api/slider.json b/docs/pages/material-ui/api/slider.json
index aa9ca8c8932c9e..3b9b5f1c2e8379 100644
--- a/docs/pages/material-ui/api/slider.json
+++ b/docs/pages/material-ui/api/slider.json
@@ -137,7 +137,7 @@
"spread": true,
"forwardsRefTo": "HTMLSpanElement",
"filename": "/packages/mui-material/src/Slider/Slider.js",
- "inheritance": { "component": "SliderUnstyled", "pathname": "/base/api/slider-unstyled/" },
+ "inheritance": null,
"demos": "
",
"cssComponent": false
}
diff --git a/packages/mui-material/src/Slider/Slider.d.ts b/packages/mui-material/src/Slider/Slider.d.ts
index ddf13dd5ff2416..1922ef01e6b711 100644
--- a/packages/mui-material/src/Slider/Slider.d.ts
+++ b/packages/mui-material/src/Slider/Slider.d.ts
@@ -84,7 +84,7 @@ export type SliderTypeMap<
defaultComponent: D;
}>;
-export { SliderValueLabelProps } from '@mui/base/SliderUnstyled';
+export { SliderValueLabelProps };
type SliderRootProps = NonNullable['root'];
type SliderMarkProps = NonNullable['mark'];
@@ -110,7 +110,6 @@ export declare const SliderValueLabel: React.FC;
* API:
*
* - [Slider API](https://mui.com/material-ui/api/slider/)
- * - inherits [SliderUnstyled API](https://mui.com/base/api/slider-unstyled/)
*/
declare const Slider: ExtendSliderUnstyled;
diff --git a/packages/mui-material/src/Slider/Slider.js b/packages/mui-material/src/Slider/Slider.js
index 24573b1243930d..abc9f58cf1b99f 100644
--- a/packages/mui-material/src/Slider/Slider.js
+++ b/packages/mui-material/src/Slider/Slider.js
@@ -1,33 +1,25 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
+import { chainPropTypes } from '@mui/utils';
import {
- chainPropTypes,
- unstable_generateUtilityClasses as generateUtilityClasses,
-} from '@mui/utils';
-import SliderUnstyled, {
- SliderValueLabelUnstyled,
- sliderUnstyledClasses,
- getSliderUtilityClass,
-} from '@mui/base/SliderUnstyled';
+ isHostComponent,
+ useSlotProps,
+ unstable_composeClasses as composeClasses,
+} from '@mui/base';
+import { useSlider, getSliderUtilityClass } from '@mui/base/SliderUnstyled';
import { alpha, lighten, darken } from '@mui/system';
import useThemeProps from '../styles/useThemeProps';
import styled, { slotShouldForwardProp } from '../styles/styled';
import useTheme from '../styles/useTheme';
import shouldSpreadAdditionalProps from '../utils/shouldSpreadAdditionalProps';
import capitalize from '../utils/capitalize';
+import SliderValueLabelComponent from './SliderValueLabel';
+import sliderClasses from './sliderClasses';
-export const sliderClasses = {
- ...sliderUnstyledClasses,
- ...generateUtilityClasses('MuiSlider', [
- 'colorPrimary',
- 'colorSecondary',
- 'thumbColorPrimary',
- 'thumbColorSecondary',
- 'sizeSmall',
- 'thumbSizeSmall',
- ]),
-};
+const valueToPercent = (value, min, max) => ((value - min) * 100) / (max - min);
+
+const Identity = (x) => x;
const SliderRoot = styled('span', {
name: 'MuiSlider',
@@ -304,7 +296,7 @@ SliderThumb.propTypes /* remove-proptypes */ = {
export { SliderThumb };
-const SliderValueLabel = styled(SliderValueLabelUnstyled, {
+const SliderValueLabel = styled(SliderValueLabelComponent, {
name: 'MuiSlider',
slot: 'ValueLabel',
overridesResolver: (props, styles) => styles.valueLabel,
@@ -460,28 +452,44 @@ SliderMarkLabel.propTypes /* remove-proptypes */ = {
export { SliderMarkLabel };
-const extendUtilityClasses = (ownerState) => {
- const { color, size, classes = {} } = ownerState;
+const useUtilityClasses = (ownerState) => {
+ const { disabled, dragging, marked, orientation, track, classes, color, size } = ownerState;
- return {
- ...classes,
- root: clsx(
- classes.root,
- getSliderUtilityClass(`color${capitalize(color)}`),
- classes[`color${capitalize(color)}`],
- size && getSliderUtilityClass(`size${capitalize(size)}`),
- size && classes[`size${capitalize(size)}`],
- ),
- thumb: clsx(
- classes.thumb,
- getSliderUtilityClass(`thumbColor${capitalize(color)}`),
- classes[`thumbColor${capitalize(color)}`],
- size && getSliderUtilityClass(`thumbSize${capitalize(size)}`),
- size && classes[`thumbSize${capitalize(size)}`],
- ),
+ const slots = {
+ root: [
+ 'root',
+ disabled && 'disabled',
+ dragging && 'dragging',
+ marked && 'marked',
+ orientation === 'vertical' && 'vertical',
+ track === 'inverted' && 'trackInverted',
+ track === false && 'trackFalse',
+ color && `color${capitalize(color)}`,
+ size && `size${capitalize(size)}`,
+ ],
+ rail: ['rail'],
+ track: ['track'],
+ mark: ['mark'],
+ markActive: ['markActive'],
+ markLabel: ['markLabel'],
+ markLabelActive: ['markLabelActive'],
+ valueLabel: ['valueLabel'],
+ thumb: [
+ 'thumb',
+ disabled && 'disabled',
+ size && `thumbSize${capitalize(size)}`,
+ color && `thumbColor${capitalize(color)}`,
+ ],
+ active: ['active'],
+ disabled: ['disabled'],
+ focusVisible: ['focusVisible'],
};
+
+ return composeClasses(slots, getSliderUtilityClass, classes);
};
+const Forward = ({ children }) => children;
+
const Slider = React.forwardRef(function Slider(inputProps, ref) {
const props = useThemeProps({ props: inputProps, name: 'MuiSlider' });
@@ -489,20 +497,82 @@ const Slider = React.forwardRef(function Slider(inputProps, ref) {
const isRtl = theme.direction === 'rtl';
const {
+ 'aria-label': ariaLabel,
+ 'aria-valuetext': ariaValuetext,
+ 'aria-labelledby': ariaLabelledby,
// eslint-disable-next-line react/prop-types
component = 'span',
components = {},
componentsProps = {},
color = 'primary',
+ classes: classesProp,
+ // eslint-disable-next-line react/prop-types
+ className,
+ disableSwap = false,
+ disabled = false,
+ getAriaLabel,
+ getAriaValueText,
+ marks: marksProp = false,
+ max = 100,
+ min = 0,
+ name,
+ onChange,
+ onChangeCommitted,
+ orientation = 'horizontal',
size = 'medium',
+ step = 1,
+ scale = Identity,
slotProps,
slots,
+ tabIndex,
+ track = 'normal',
+ value: valueProp,
+ valueLabelDisplay = 'off',
+ valueLabelFormat = Identity,
...other
} = props;
- const ownerState = { ...props, color, size };
+ const ownerState = {
+ ...props,
+ isRtl,
+ max,
+ min,
+ classes: classesProp,
+ disabled,
+ disableSwap,
+ orientation,
+ marks: marksProp,
+ color,
+ size,
+ step,
+ scale,
+ track,
+ valueLabelDisplay,
+ valueLabelFormat,
+ };
+
+ const {
+ axisProps,
+ getRootProps,
+ getHiddenInputProps,
+ getThumbProps,
+ open,
+ active,
+ axis,
+ focusedThumbIndex,
+ range,
+ dragging,
+ marks,
+ values,
+ trackOffset,
+ trackLeap,
+ } = useSlider({ ...ownerState, ref });
+
+ ownerState.marked = marks.length > 0 && marks.some((mark) => mark.label);
+ ownerState.dragging = dragging;
+ ownerState.focusedThumbIndex = focusedThumbIndex;
- const classes = extendUtilityClasses(ownerState);
+ const classes = useUtilityClasses(ownerState);
// support both `slots` and `components` for backward compatibility
const RootSlot = slots?.root ?? components.Root ?? SliderRoot;
@@ -512,7 +582,7 @@ const Slider = React.forwardRef(function Slider(inputProps, ref) {
const ValueLabelSlot = slots?.valueLabel ?? components.ValueLabel ?? SliderValueLabel;
const MarkSlot = slots?.mark ?? components.Mark ?? SliderMark;
const MarkLabelSlot = slots?.markLabel ?? components.MarkLabel ?? SliderMarkLabel;
- const InputSlot = slots?.input ?? components.Input;
+ const InputSlot = slots?.input ?? components.Input ?? 'input';
const rootSlotProps = slotProps?.root ?? componentsProps.root;
const railSlotProps = slotProps?.rail ?? componentsProps.rail;
@@ -523,55 +593,197 @@ const Slider = React.forwardRef(function Slider(inputProps, ref) {
const markLabelSlotProps = slotProps?.markLabel ?? componentsProps.markLabel;
const inputSlotProps = slotProps?.input ?? componentsProps.input;
+ const rootProps = useSlotProps({
+ elementType: RootSlot,
+ getSlotProps: getRootProps,
+ externalSlotProps: rootSlotProps,
+ externalForwardedProps: other,
+ additionalProps: {
+ ...(shouldSpreadAdditionalProps(RootSlot) && {
+ as: component,
+ }),
+ },
+ ownerState: {
+ ...ownerState,
+ ...rootSlotProps?.ownerState,
+ },
+ className: [classes.root, className],
+ });
+
+ const railProps = useSlotProps({
+ elementType: RailSlot,
+ externalSlotProps: railSlotProps,
+ ownerState,
+ className: classes.rail,
+ });
+
+ const trackProps = useSlotProps({
+ elementType: TrackSlot,
+ externalSlotProps: trackSlotProps,
+ additionalProps: {
+ style: {
+ ...axisProps[axis].offset(trackOffset),
+ ...axisProps[axis].leap(trackLeap),
+ },
+ },
+ ownerState: {
+ ...ownerState,
+ ...trackSlotProps?.ownerState,
+ },
+ className: classes.track,
+ });
+
+ const thumbProps = useSlotProps({
+ elementType: ThumbSlot,
+ getSlotProps: getThumbProps,
+ externalSlotProps: thumbSlotProps,
+ ownerState: {
+ ...ownerState,
+ ...thumbSlotProps?.ownerState,
+ },
+ });
+
+ const valueLabelProps = useSlotProps({
+ elementType: ValueLabelSlot,
+ externalSlotProps: valueLabelSlotProps,
+ ownerState: {
+ ...ownerState,
+ ...valueLabelSlotProps?.ownerState,
+ },
+ className: classes.valueLabel,
+ });
+
+ const markProps = useSlotProps({
+ elementType: MarkSlot,
+ externalSlotProps: markSlotProps,
+ ownerState,
+ className: classes.mark,
+ });
+
+ const markLabelProps = useSlotProps({
+ elementType: MarkLabelSlot,
+ externalSlotProps: markLabelSlotProps,
+ ownerState,
+ });
+
+ const inputSliderProps = useSlotProps({
+ elementType: InputSlot,
+ getSlotProps: getHiddenInputProps,
+ externalSlotProps: inputSlotProps,
+ ownerState,
+ });
+
return (
-
+
+
+
+ {marks
+ .filter((mark) => mark.value >= min && mark.value <= max)
+ .map((mark, index) => {
+ const percent = valueToPercent(mark.value, min, max);
+ const style = axisProps[axis].offset(percent);
+
+ let markActive;
+ if (track === false) {
+ markActive = values.indexOf(mark.value) !== -1;
+ } else {
+ markActive =
+ (track === 'normal' &&
+ (range
+ ? mark.value >= values[0] && mark.value <= values[values.length - 1]
+ : mark.value <= values[0])) ||
+ (track === 'inverted' &&
+ (range
+ ? mark.value <= values[0] || mark.value >= values[values.length - 1]
+ : mark.value >= values[0]));
+ }
+
+ return (
+
+
+ {mark.label != null ? (
+
+ {mark.label}
+
+ ) : null}
+
+ );
+ })}
+ {values.map((value, index) => {
+ const percent = valueToPercent(value, min, max);
+ const style = axisProps[axis].offset(percent);
+
+ const ValueLabelComponent = valueLabelDisplay === 'off' ? Forward : ValueLabelSlot;
+
+ return (
+
+ {/* TODO v6: Change component structure. It will help in avoiding the complicated React.cloneElement API added in SliderValueLabel component. Should be: Thumb -> Input, ValueLabel. Follow Joy UI's Slider structure. */}
+
+
+
+
+
+
+ );
+ })}
+
);
});
diff --git a/packages/mui-material/src/Slider/Slider.test.js b/packages/mui-material/src/Slider/Slider.test.js
index ea495be50ec8cc..d2d842e0106d13 100644
--- a/packages/mui-material/src/Slider/Slider.test.js
+++ b/packages/mui-material/src/Slider/Slider.test.js
@@ -33,7 +33,7 @@ describe('', () => {
,
() => ({
classes,
- inheritComponent: SliderUnstyled,
+ inheritComponent: 'span',
render,
refInstanceof: window.HTMLSpanElement,
muiName: 'MuiSlider',
diff --git a/packages/mui-material/src/Slider/SliderValueLabel.tsx b/packages/mui-material/src/Slider/SliderValueLabel.tsx
new file mode 100644
index 00000000000000..0b1ac785d0192f
--- /dev/null
+++ b/packages/mui-material/src/Slider/SliderValueLabel.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import { SliderValueLabelProps } from './SliderValueLabel.types';
+import sliderClasses from './sliderClasses';
+
+const useValueLabelClasses = (props: SliderValueLabelProps) => {
+ const { open } = props;
+
+ const utilityClasses = {
+ offset: clsx({
+ [sliderClasses.valueLabelOpen]: open,
+ }),
+ circle: sliderClasses.valueLabelCircle,
+ label: sliderClasses.valueLabelLabel,
+ };
+
+ return utilityClasses;
+};
+
+/**
+ * @ignore - internal component.
+ */
+export default function SliderValueLabel(props: SliderValueLabelProps) {
+ const { children, className, value } = props;
+ const classes = useValueLabelClasses(props);
+
+ return React.cloneElement(
+ children,
+ {
+ className: clsx(children.props.className),
+ },
+
+ {children.props.children}
+
+
+ {value}
+
+
+ ,
+ );
+}
+
+SliderValueLabel.propTypes = {
+ children: PropTypes.element.isRequired,
+ className: PropTypes.string,
+ value: PropTypes.node,
+};
diff --git a/packages/mui-material/src/Slider/SliderValueLabel.types.ts b/packages/mui-material/src/Slider/SliderValueLabel.types.ts
new file mode 100644
index 00000000000000..21747c25bb3537
--- /dev/null
+++ b/packages/mui-material/src/Slider/SliderValueLabel.types.ts
@@ -0,0 +1,23 @@
+export interface SliderValueLabelProps {
+ children: React.ReactElement;
+ className?: string;
+ style?: React.CSSProperties;
+ /**
+ * If `true`, the value label is visible.
+ */
+ open: boolean;
+ /**
+ * The value of the slider.
+ * For ranged sliders, provide an array with two values.
+ */
+ value: number;
+ /**
+ * Controls when the value label is displayed:
+ *
+ * - `auto` the value label will display when the thumb is hovered or focused.
+ * - `on` will display persistently.
+ * - `off` will never display.
+ * @default 'off'
+ */
+ valueLabelDisplay?: 'on' | 'auto' | 'off';
+}
diff --git a/packages/mui-material/src/Slider/index.d.ts b/packages/mui-material/src/Slider/index.d.ts
index 006f966fe2404f..b367aa2014dc31 100644
--- a/packages/mui-material/src/Slider/index.d.ts
+++ b/packages/mui-material/src/Slider/index.d.ts
@@ -1,2 +1,3 @@
export { default } from './Slider';
export * from './Slider';
+export { default as sliderClasses } from './sliderClasses';
diff --git a/packages/mui-material/src/Slider/index.js b/packages/mui-material/src/Slider/index.js
index 006f966fe2404f..b367aa2014dc31 100644
--- a/packages/mui-material/src/Slider/index.js
+++ b/packages/mui-material/src/Slider/index.js
@@ -1,2 +1,3 @@
export { default } from './Slider';
export * from './Slider';
+export { default as sliderClasses } from './sliderClasses';
diff --git a/packages/mui-material/src/Slider/sliderClasses.ts b/packages/mui-material/src/Slider/sliderClasses.ts
new file mode 100644
index 00000000000000..36228115c31e2c
--- /dev/null
+++ b/packages/mui-material/src/Slider/sliderClasses.ts
@@ -0,0 +1,16 @@
+import { unstable_generateUtilityClasses as generateUtilityClasses } from '@mui/utils';
+import { sliderUnstyledClasses } from '@mui/base/SliderUnstyled';
+
+const sliderClasses = {
+ ...sliderUnstyledClasses,
+ ...generateUtilityClasses('MuiSlider', [
+ 'colorPrimary',
+ 'colorSecondary',
+ 'thumbColorPrimary',
+ 'thumbColorSecondary',
+ 'sizeSmall',
+ 'thumbSizeSmall',
+ ]),
+};
+
+export default sliderClasses;