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
94 changes: 60 additions & 34 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 @@ -111,50 +110,83 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {

ownerState.marked = marks.length > 0 && marks.some((mark) => mark.label);
ownerState.dragging = dragging;
ownerState.focusVisible = focusVisible;
Copy link
Member

Choose a reason for hiding this comment

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

This may be misleading, as the focusVisible state in the Slider is the index of the focused thumb.

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 is misleading indeed. I'd rename it to 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,
});

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 @@ -234,7 +266,6 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {
<Thumb
data-index={index}
{...thumbProps}
{...getThumbProps()}
className={clsx(classes.thumb, thumbProps.className, {
[classes.active]: active === index,
[classes.focusVisible]: focusVisible === index,
Copy link
Member

Choose a reason for hiding this comment

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

Take a look at what should be the focusVisible owner state for the thumb component.

Expand All @@ -246,7 +277,6 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {
}}
>
<Input
{...hiddenInputProps}
data-index={index}
aria-label={getAriaLabel ? getAriaLabel(index) : ariaLabel}
aria-valuenow={scale(value)}
Expand All @@ -258,10 +288,6 @@ const SliderUnstyled = React.forwardRef(function SliderUnstyled(props, ref) {
ownerState: { ...ownerState, ...inputProps.ownerState },
})}
{...inputProps}
style={{
...hiddenInputProps.style,
...inputProps.style,
}}
/>
</Thumb>
</ValueLabelComponent>
Expand Down
Expand Up @@ -11,6 +11,7 @@ import {

export type SliderUnstyledOwnerState = SliderUnstyledProps & {
disabled: boolean;
focusVisible: boolean;
Copy link
Member Author

Choose a reason for hiding this comment

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

This was missing in ownerState

isRtl: boolean;
mark: boolean | Mark[];
max: number;
Expand Down
43 changes: 43 additions & 0 deletions packages/mui-base/src/utils/mergeSlotProps.test.ts
Expand Up @@ -75,6 +75,49 @@ describe('mergeSlotProps', () => {
expect(merged.props.className).to.contain('externalSlot');
});

it('merges the style props', () => {
const getSlotProps = () => ({
style: {
fontSize: '12px',
textAlign: 'center' as const,
},
});

const additionalProps = {
style: {
fontSize: '14px',
color: 'red',
},
};

const externalForwardedProps = {
style: {
fontWeight: 500,
},
};

const externalSlotProps = {
style: {
textDecoration: 'underline',
},
};

const merged = mergeSlotProps({
getSlotProps,
additionalProps,
externalForwardedProps,
externalSlotProps,
});

expect(merged.props.style).to.deep.equal({
textAlign: 'center',
color: 'red',
fontSize: '14px',
fontWeight: 500,
textDecoration: 'underline',
});
});

it('returns the ref returned from the getSlotProps function', () => {
const ref = React.createRef();
const getSlotProps = () => ({
Expand Down
76 changes: 47 additions & 29 deletions packages/mui-base/src/utils/mergeSlotProps.ts
Expand Up @@ -4,11 +4,9 @@ import { EventHandlers } from './types';
import extractEventHandlers from './extractEventHandlers';
import omitEventHandlers, { OmitEventHandlers } from './omitEventHandlers';

export type WithClassName<T> = T & {
export type WithCommonProps<T> = T & {
className?: string;
};

export type WithRef<T> = T & {
style?: React.CSSProperties;
ref?: React.Ref<any>;
};

Expand All @@ -23,20 +21,20 @@ export interface MergeSlotPropsParameters<
* It accepts the event handlers passed into the component by the user
* and is responsible for calling them where appropriate.
*/
getSlotProps?: (other: EventHandlers) => WithClassName<SlotProps>;
getSlotProps?: (other: EventHandlers) => WithCommonProps<SlotProps>;
/**
* Props provided to the `componentsProps.*` of the unstyled component.
*/
externalSlotProps?: WithClassName<ExternalSlotProps>;
externalSlotProps?: WithCommonProps<ExternalSlotProps>;
/**
* Extra props placed on the unstyled component that should be forwarded to the slot.
* This should usually be used only for the root slot.
*/
externalForwardedProps?: WithClassName<ExternalForwardedProps>;
externalForwardedProps?: WithCommonProps<ExternalForwardedProps>;
/**
* Additional props to be placed on the slot.
*/
additionalProps?: WithClassName<AdditionalProps>;
additionalProps?: WithCommonProps<AdditionalProps>;
/**
* Extra class name(s) to be placed on the slot.
*/
Expand All @@ -53,7 +51,7 @@ export type MergeSlotPropsResult<
SlotProps &
OmitEventHandlers<ExternalForwardedProps> &
OmitEventHandlers<ExternalSlotProps> &
AdditionalProps & { className?: string }
AdditionalProps & { className?: string; style?: React.CSSProperties }
>;
internalRef: React.Ref<any> | undefined;
};
Expand All @@ -78,7 +76,7 @@ export default function mergeSlotProps<
AdditionalProps,
>(
parameters: MergeSlotPropsParameters<
WithRef<SlotProps>,
SlotProps,
ExternalForwardedProps,
ExternalSlotProps,
AdditionalProps
Expand All @@ -97,20 +95,29 @@ export default function mergeSlotProps<
additionalProps?.className,
);

const mergedStyle = {
...additionalProps?.style,
...externalForwardedProps?.style,
...externalSlotProps?.style,
};

const props = {
...additionalProps,
...externalForwardedProps,
...externalSlotProps,
className: joinedClasses,
} as Simplify<
SlotProps &
ExternalForwardedProps &
ExternalSlotProps &
AdditionalProps & { className?: string }
>;

if (joinedClasses.length === 0) {
delete props.className;
} as MergeSlotPropsResult<
SlotProps,
ExternalForwardedProps,
ExternalSlotProps,
AdditionalProps
>['props'];

if (joinedClasses.length > 0) {
props.className = joinedClasses;
}

if (Object.keys(mergedStyle).length > 0) {
props.style = mergedStyle;
}

return {
Expand All @@ -135,20 +142,31 @@ export default function mergeSlotProps<
internalSlotProps?.className,
);

const mergedStyle = {
...internalSlotProps?.style,
...additionalProps?.style,
...externalForwardedProps?.style,
...externalSlotProps?.style,
};

const props = {
...internalSlotProps,
...additionalProps,
...otherPropsWithoutEventHandlers,
...componentsPropsWithoutEventHandlers,
className: joinedClasses,
} as Simplify<
SlotProps &
OmitEventHandlers<ExternalForwardedProps> &
OmitEventHandlers<ExternalSlotProps> &
AdditionalProps & { className?: string }
>;
if (joinedClasses.length === 0) {
delete props.className;
} as MergeSlotPropsResult<
SlotProps,
ExternalForwardedProps,
ExternalSlotProps,
AdditionalProps
>['props'];

if (joinedClasses.length > 0) {
props.className = joinedClasses;
}

if (Object.keys(mergedStyle).length > 0) {
props.style = mergedStyle;
}

return {
Expand Down
6 changes: 3 additions & 3 deletions packages/mui-base/src/utils/useSlotProps.ts
@@ -1,7 +1,7 @@
import * as React from 'react';
import { unstable_useForkRef as useForkRef } from '@mui/utils';
import appendOwnerState from './appendOwnerState';
import mergeSlotProps, { MergeSlotPropsParameters, WithRef } from './mergeSlotProps';
import mergeSlotProps, { MergeSlotPropsParameters, WithCommonProps } from './mergeSlotProps';
import resolveComponentProps from './resolveComponentProps';

export type UseSlotPropsParameters<
Expand Down Expand Up @@ -60,8 +60,8 @@ export default function useSlotProps<
parameters: UseSlotPropsParameters<
SlotProps,
ExternalForwardedProps,
WithRef<ExternalSlotProps>,
WithRef<AdditionalProps>,
WithCommonProps<ExternalSlotProps>,
WithCommonProps<AdditionalProps>,
OwnerState
>,
) {
Expand Down