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

[Switch] Add optional track slot to SwitchUnstyled #27916

Merged
merged 4 commits into from Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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