Skip to content

Commit

Permalink
[Switch] Add optional track slot to SwitchUnstyled (#27916)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaldudak committed Aug 24, 2021
1 parent ef4d010 commit 64dafca
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 18 deletions.
2 changes: 1 addition & 1 deletion docs/pages/api-docs/switch-unstyled.json
Expand Up @@ -6,7 +6,7 @@
"components": {
"type": {
"name": "shape",
"description": "{ Input?: elementType, Root?: elementType, Thumb?: elementType }"
"description": "{ Input?: elementType, Root?: elementType, Thumb?: elementType, Track?: elementType<br>&#124;&nbsp;null }"
},
"default": "{}"
},
Expand Down
26 changes: 18 additions & 8 deletions docs/src/pages/components/switches/UnstyledSwitches.js
Expand Up @@ -10,8 +10,7 @@ const Root = styled('span')(`
display: inline-block;
width: 32px;
height: 20px;
background: #B3C3D3;
border-radius: 10px;
margin: 10px;
cursor: pointer;
Expand All @@ -20,8 +19,13 @@ const Root = styled('span')(`
cursor: not-allowed;
}
&.${switchUnstyledClasses.checked} {
background: #007FFF;
& .${switchUnstyledClasses.track} {
background: #B3C3D3;
border-radius: 10px;
display: block;
height: 100%;
width: 100%;
position: absolute;
}
& .${switchUnstyledClasses.thumb} {
Expand All @@ -41,10 +45,16 @@ const Root = styled('span')(`
box-shadow: 0 0 1px 8px rgba(0,0,0,0.25);
}
&.${switchUnstyledClasses.checked} .${switchUnstyledClasses.thumb} {
left: 14px;
top: 3px;
background-color: #FFF;
&.${switchUnstyledClasses.checked} {
.${switchUnstyledClasses.thumb} {
left: 14px;
top: 3px;
background-color: #FFF;
}
.${switchUnstyledClasses.track} {
background: #007FFF;
}
}
& .${switchUnstyledClasses.input} {
Expand Down
26 changes: 18 additions & 8 deletions docs/src/pages/components/switches/UnstyledSwitches.tsx
Expand Up @@ -10,8 +10,7 @@ const Root = styled('span')(`
display: inline-block;
width: 32px;
height: 20px;
background: #B3C3D3;
border-radius: 10px;
margin: 10px;
cursor: pointer;
Expand All @@ -20,8 +19,13 @@ const Root = styled('span')(`
cursor: not-allowed;
}
&.${switchUnstyledClasses.checked} {
background: #007FFF;
& .${switchUnstyledClasses.track} {
background: #B3C3D3;
border-radius: 10px;
display: block;
height: 100%;
width: 100%;
position: absolute;
}
& .${switchUnstyledClasses.thumb} {
Expand All @@ -41,10 +45,16 @@ const Root = styled('span')(`
box-shadow: 0 0 1px 8px rgba(0,0,0,0.25);
}
&.${switchUnstyledClasses.checked} .${switchUnstyledClasses.thumb} {
left: 14px;
top: 3px;
background-color: #FFF;
&.${switchUnstyledClasses.checked} {
.${switchUnstyledClasses.thumb} {
left: 14px;
top: 3px;
background-color: #FFF;
}
.${switchUnstyledClasses.track} {
background: #007FFF;
}
}
& .${switchUnstyledClasses.input} {
Expand Down
Expand Up @@ -383,6 +383,7 @@ const Switch = React.forwardRef(function Switch(inProps, ref) {
Root: SwitchLayout,
Input: SwitchInput,
Thumb: SwitchThumb,
Track: null,
};

const componentsProps = {
Expand Down
Expand Up @@ -28,6 +28,10 @@ describe('<SwitchUnstyled />', () => {
testWithElement: 'input',
expectedClassName: switchUnstyledClasses.input,
},
track: {
expectedClassName: switchUnstyledClasses.track,
isOptional: true,
},
},
}));

Expand Down
Expand Up @@ -25,6 +25,7 @@ export interface SwitchUnstyledProps extends UseSwitchProps {
Root?: React.ElementType;
Thumb?: React.ElementType;
Input?: React.ElementType;
Track?: React.ElementType | null;
};

/**
Expand All @@ -35,6 +36,7 @@ export interface SwitchUnstyledProps extends UseSwitchProps {
root?: {};
thumb?: {};
input?: {};
track?: {};
};
}

Expand Down Expand Up @@ -100,6 +102,10 @@ const SwitchUnstyled = React.forwardRef(function SwitchUnstyled(
const Input: React.ElementType = components.Input ?? 'input';
const inputProps = appendOwnerState(Input, componentsProps.input ?? {}, ownerState);

const Track: React.ElementType =
components.Track === null ? () => null : components.Track ?? 'span';
const trackProps = appendOwnerState(Track, componentsProps.track ?? {}, ownerState);

const stateClasses = {
[classes.checked]: checked,
[classes.disabled]: disabled,
Expand All @@ -113,6 +119,7 @@ const SwitchUnstyled = React.forwardRef(function SwitchUnstyled(
{...rootProps}
className={clsx(classes.root, stateClasses, className, rootProps?.className)}
>
<Track {...trackProps} className={clsx(classes.track, trackProps?.className)} />
<Thumb {...thumbProps} className={clsx(classes.thumb, thumbProps?.className)} />
<Input
{...getInputProps(inputProps)}
Expand Down Expand Up @@ -146,10 +153,11 @@ SwitchUnstyled.propTypes /* remove-proptypes */ = {
* Either a string to use a HTML element or a component.
* @default {}
*/
components: PropTypes.shape({
components: PropTypes /* @typescript-to-proptypes-ignore */.shape({
Input: PropTypes.elementType,
Root: PropTypes.elementType,
Thumb: PropTypes.elementType,
Track: PropTypes.oneOfType([PropTypes.elementType, PropTypes.oneOf([null])]),
}),
/**
* The props used for each slot inside the Switch.
Expand Down
Expand Up @@ -6,6 +6,8 @@ export interface SwitchUnstyledClasses {
root: string;
/** Class applied to the internal input element */
input: string;
/** Class applied to the track element */
track: string;
/** Class applied to the thumb element */
thumb: string;
/** Class applied to the root element if the switch is checked */
Expand All @@ -27,6 +29,7 @@ export function getSwitchUnstyledUtilityClass(slot: string): string {
const switchUnstyledClasses: SwitchUnstyledClasses = generateUtilityClasses('MuiSwitch', [
'root',
'input',
'track',
'thumb',
'checked',
'disabled',
Expand Down
12 changes: 12 additions & 0 deletions test/utils/describeConformanceUnstyled.tsx
Expand Up @@ -15,6 +15,7 @@ export interface SlotTestingOptions {
testWithComponent?: React.ComponentType;
testWithElement?: keyof JSX.IntrinsicElements;
expectedClassName: string;
isOptional?: boolean;
}

export interface UnstyledConformanceOptions
Expand Down Expand Up @@ -149,6 +150,17 @@ function testComponentsProp(
const thumb = container.querySelector(slotElement);
expect(thumb).to.have.class(slotOptions.expectedClassName);
});

if (slotOptions.isOptional) {
it(`alows omitting the optional ${capitalize(slotName)} slot by providing null`, () => {
const components = {
[capitalize(slotName)]: null,
};

const { container } = render(React.cloneElement(element, { components }));
expect(container.querySelectorAll(`.${slotOptions.expectedClassName}`)).to.have.length(0);
});
}
});

it('uses the component provided in component prop when both component and components.Root are provided', () => {
Expand Down

0 comments on commit 64dafca

Please sign in to comment.