Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SliderUnstyled] Use useSlotProps #33132

Merged
merged 12 commits into from Jun 24, 2022
2 changes: 1 addition & 1 deletion docs/pages/base/api/slider-unstyled.json
Expand Up @@ -15,7 +15,7 @@
"componentsProps": {
"type": {
"name": "shape",
"description": "{ input?: object, mark?: object, markLabel?: object, rail?: object, root?: object, thumb?: object, track?: object, valueLabel?: { children?: element, className?: string, components?: { Root?: elementType }, open?: bool, style?: object, value?: number, valueLabelDisplay?: 'auto'<br>&#124;&nbsp;'off'<br>&#124;&nbsp;'on' } }"
"description": "{ input?: func<br>&#124;&nbsp;object, mark?: func<br>&#124;&nbsp;object, markLabel?: func<br>&#124;&nbsp;object, rail?: func<br>&#124;&nbsp;object, root?: func<br>&#124;&nbsp;object, thumb?: func<br>&#124;&nbsp;object, track?: func<br>&#124;&nbsp;object, valueLabel?: func<br>&#124;&nbsp;{ children?: element, className?: string, components?: { Root?: elementType }, open?: bool, style?: object, value?: number, valueLabelDisplay?: 'auto'<br>&#124;&nbsp;'off'<br>&#124;&nbsp;'on' } }"
},
"default": "{}"
},
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/material-ui/api/slider.json
Expand Up @@ -21,7 +21,7 @@
"componentsProps": {
"type": {
"name": "shape",
"description": "{ input?: object, mark?: object, markLabel?: object, rail?: object, root?: object, thumb?: object, track?: object, valueLabel?: { children?: element, className?: string, components?: { Root?: elementType }, open?: bool, style?: object, value?: number, valueLabelDisplay?: 'auto'<br>&#124;&nbsp;'off'<br>&#124;&nbsp;'on' } }"
"description": "{ input?: func<br>&#124;&nbsp;object, mark?: func<br>&#124;&nbsp;object, markLabel?: func<br>&#124;&nbsp;object, rail?: func<br>&#124;&nbsp;object, root?: func<br>&#124;&nbsp;object, thumb?: func<br>&#124;&nbsp;object, track?: func<br>&#124;&nbsp;object, valueLabel?: func<br>&#124;&nbsp;{ children?: element, className?: string, components?: { Root?: elementType }, open?: bool, style?: object, value?: number, valueLabelDisplay?: 'auto'<br>&#124;&nbsp;'off'<br>&#124;&nbsp;'on' } }"
},
"default": "{}"
},
Expand Down
19 changes: 10 additions & 9 deletions packages/mui-base/src/FormControlUnstyled/FormControlUnstyled.tsx
Expand Up @@ -131,29 +131,30 @@ const FormControlUnstyled = React.forwardRef(function FormControlUnstyled<

const classes = useUtilityClasses(ownerState);

const renderChildren = () => {
if (typeof children === 'function') {
return children(childContext);
}

return children;
};

const Root = component ?? components.Root ?? 'div';
const rootProps: WithOptionalOwnerState<FormControlUnstyledRootSlotProps> = useSlotProps({
elementType: Root,
externalSlotProps: componentsProps.root,
externalForwardedProps: other,
additionalProps: {
ref,
children: renderChildren(),
},
ownerState,
className: classes.root,
});

const renderChildren = () => {
if (typeof children === 'function') {
return children(childContext);
}

return children;
};

return (
<FormControlUnstyledContext.Provider value={childContext}>
<Root {...rootProps}>{renderChildren()}</Root>
<Root {...rootProps} />
</FormControlUnstyledContext.Provider>
);
}) as OverridableComponent<FormControlUnstyledTypeMap>;
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-base/src/InputUnstyled/InputUnstyled.types.ts
Expand Up @@ -145,7 +145,7 @@ export type InputUnstyledInputSlotProps = Simplify<
'aria-labelledby': React.AriaAttributes['aria-labelledby'];
autoComplete: string | undefined;
autoFocus: boolean | undefined;
className: string;
className?: string;
id: string | undefined;
name: string | undefined;
onKeyDown: React.KeyboardEventHandler<HTMLInputElement> | undefined;
Expand Down
146 changes: 85 additions & 61 deletions packages/mui-base/src/SliderUnstyled/SliderUnstyled.js
Expand Up @@ -2,12 +2,12 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { chainPropTypes } from '@mui/utils';
import appendOwnerState from '../utils/appendOwnerState';
import isHostComponent from '../utils/isHostComponent';
import composeClasses from '../composeClasses';
import { getSliderUtilityClass } from './sliderUnstyledClasses';
import SliderValueLabelUnstyled from './SliderValueLabelUnstyled';
import useSlider, { valueToPercent } from './useSlider';
import useSlotProps from '../utils/useSlotProps';

const Identity = (x) => x;

Expand Down Expand Up @@ -59,7 +59,6 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {
name,
onChange,
onChangeCommitted,
onMouseDown,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't have to be specified explicitly anymore, as all extra event handlers from props are passed into the getRootProps

orientation = 'horizontal',
scale = Identity,
step = 1,
Expand Down Expand Up @@ -101,7 +100,7 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {
active,
axis,
range,
focusVisible,
focusedThumbIndex,
dragging,
marks,
values,
Expand All @@ -111,50 +110,84 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {

ownerState.marked = marks.length > 0 && marks.some((mark) => mark.label);
ownerState.dragging = dragging;
ownerState.focusedThumbIndex = focusedThumbIndex;

const classes = useUtilityClasses(ownerState);

const Root = component ?? components.Root ?? 'span';
const rootProps = appendOwnerState(Root, { ...other, ...componentsProps.root }, ownerState);
const rootProps = useSlotProps({
elementType: Root,
getSlotProps: getRootProps,
externalSlotProps: componentsProps.root,
externalForwardedProps: other,
ownerState,
className: [classes.root, className],
});

const Rail = components.Rail ?? 'span';
const railProps = appendOwnerState(Rail, componentsProps.rail, ownerState);
const railProps = useSlotProps({
elementType: Rail,
externalSlotProps: componentsProps.rail,
ownerState,
className: classes.rail,
});

const Track = components.Track ?? 'span';
const trackProps = appendOwnerState(Track, componentsProps.track, ownerState);
const trackStyle = {
...axisProps[axis].offset(trackOffset),
...axisProps[axis].leap(trackLeap),
};
const trackProps = useSlotProps({
elementType: Track,
externalSlotProps: componentsProps.track,
additionalProps: {
style: {
...axisProps[axis].offset(trackOffset),
...axisProps[axis].leap(trackLeap),
},
},
ownerState,
className: classes.track,
});

const Thumb = components.Thumb ?? 'span';
const thumbProps = appendOwnerState(Thumb, componentsProps.thumb, ownerState);
const thumbProps = useSlotProps({
elementType: Thumb,
getSlotProps: getThumbProps,
externalSlotProps: componentsProps.thumb,
ownerState,
});

const ValueLabel = components.ValueLabel ?? SliderValueLabelUnstyled;
const valueLabelProps = appendOwnerState(ValueLabel, componentsProps.valueLabel, ownerState);
const valueLabelProps = useSlotProps({
elementType: ValueLabel,
externalSlotProps: componentsProps.valueLabel,
ownerState,
});

const Mark = components.Mark ?? 'span';
const markProps = appendOwnerState(Mark, componentsProps.mark, ownerState);
const markProps = useSlotProps({
elementType: Mark,
externalSlotProps: componentsProps.mark,
ownerState,
className: classes.mark,
});

const MarkLabel = components.MarkLabel ?? 'span';
const markLabelProps = appendOwnerState(MarkLabel, componentsProps.markLabel, ownerState);
const markLabelProps = useSlotProps({
elementType: MarkLabel,
externalSlotProps: componentsProps.markLabel,
ownerState,
});

const Input = components.Input || 'input';
const inputProps = appendOwnerState(Input, componentsProps.input, ownerState);
const hiddenInputProps = getHiddenInputProps();

const classes = useUtilityClasses(ownerState);
const inputProps = useSlotProps({
elementType: Input,
getSlotProps: getHiddenInputProps,
externalSlotProps: componentsProps.input,
ownerState,
});

return (
<Root
{...rootProps}
{...getRootProps({ onMouseDown })}
className={clsx(classes.root, rootProps.className, className)}
>
<Rail {...railProps} className={clsx(classes.rail, railProps.className)} />
<Track
{...trackProps}
className={clsx(classes.track, trackProps.className)}
style={{ ...trackStyle, ...trackProps.style }}
/>
<Root {...rootProps}>
<Rail {...railProps} />
<Track {...trackProps} />
{marks
.filter((mark) => mark.value >= min && mark.value <= max)
.map((mark, index) => {
Expand Down Expand Up @@ -185,7 +218,7 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {
markActive,
})}
style={{ ...style, ...markProps.style }}
className={clsx(classes.mark, markProps.className, {
className={clsx(markProps.className, {
[classes.markActive]: markActive,
})}
/>
Expand Down Expand Up @@ -233,11 +266,11 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {
>
<Thumb
data-index={index}
data-focusvisible={focusedThumbIndex === index}
{...thumbProps}
{...getThumbProps()}
className={clsx(classes.thumb, thumbProps.className, {
[classes.active]: active === index,
[classes.focusVisible]: focusVisible === index,
[classes.focusVisible]: focusedThumbIndex === index,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means in the styles/classes people would need to apply the focus visible classes if data-index === ownerState.focusedThumbIndex. I think this is super confusing, I would rather just update the ownerState of the thumb component to have the focusedVisible if the condition is met.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting point.
So far, in all components, ownerState was exactly the same in all slots (it's an owner state, not a slot one). I'd like to keep it this way.
We can either provide this information to the Thumb directly as a prop, or add a function to the ownerState: isThumbFocusVisible(index: number): boolean

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same doubt as it is not really an ownerState. Adding a new prop sounds great 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Had the same doubt when I was implementing the Slider for Joy.

})}
style={{
...style,
Expand All @@ -246,22 +279,14 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {
}}
>
<Input
{...hiddenInputProps}
data-index={index}
aria-label={getAriaLabel ? getAriaLabel(index) : ariaLabel}
aria-valuenow={scale(value)}
aria-valuetext={
getAriaValueText ? getAriaValueText(scale(value), index) : ariaValuetext
}
value={values[index]}
{...(!isHostComponent(Input) && {
ownerState: { ...ownerState, ...inputProps.ownerState },
})}
{...inputProps}
style={{
...hiddenInputProps.style,
...inputProps.style,
}}
/>
</Thumb>
</ValueLabelComponent>
Expand Down Expand Up @@ -346,24 +371,27 @@ SliderUnstyled.propTypes /* remove-proptypes */ = {
* @default {}
*/
componentsProps: PropTypes.shape({
input: PropTypes.object,
mark: PropTypes.object,
markLabel: PropTypes.object,
rail: PropTypes.object,
root: PropTypes.object,
thumb: PropTypes.object,
track: PropTypes.object,
valueLabel: PropTypes.shape({
children: PropTypes.element,
className: PropTypes.string,
components: PropTypes.shape({
Root: PropTypes.elementType,
input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
mark: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
markLabel: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
rail: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
thumb: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
track: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
valueLabel: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({
children: PropTypes.element,
className: PropTypes.string,
components: PropTypes.shape({
Root: PropTypes.elementType,
}),
open: PropTypes.bool,
style: PropTypes.object,
value: PropTypes.number,
valueLabelDisplay: PropTypes.oneOf(['auto', 'off', 'on']),
}),
open: PropTypes.bool,
style: PropTypes.object,
value: PropTypes.number,
valueLabelDisplay: PropTypes.oneOf(['auto', 'off', 'on']),
}),
]),
}),
/**
* The default value. Use when the component is not controlled.
Expand Down Expand Up @@ -447,10 +475,6 @@ SliderUnstyled.propTypes /* remove-proptypes */ = {
* @param {number | number[]} value The new value.
*/
onChangeCommitted: PropTypes.func,
/**
* @ignore
*/
onMouseDown: PropTypes.func,
/**
* The component orientation.
* @default 'horizontal'
Expand Down
Expand Up @@ -38,7 +38,7 @@ const Thumb = React.forwardRef(function Thumb(
props: SliderUnstyledThumbSlotProps,
ref: React.ForwardedRef<HTMLDivElement>,
) {
const { 'data-index': index, ownerState, ...other } = props;
const { 'data-index': index, 'data-focusvisible': focusVisible, ownerState, ...other } = props;
return <div data-track={ownerState.track} {...other} ref={ref} />;
});

Expand Down