diff --git a/docs/pages/api-docs/switch.json b/docs/pages/api-docs/switch.json index 2c9f0422029c13..1da7991e1ec1a6 100644 --- a/docs/pages/api-docs/switch.json +++ b/docs/pages/api-docs/switch.json @@ -34,7 +34,11 @@ "default": "'medium'" }, "sx": { "type": { "name": "object" } }, - "value": { "type": { "name": "any" } } + "value": { "type": { "name": "any" } }, + "variant": { + "type": { "name": "enum", "description": "'contained'
| 'normal'" }, + "default": "'normal'" + } }, "name": "Switch", "styles": { diff --git a/docs/src/pages/components/switches/ContainedSwitches.js b/docs/src/pages/components/switches/ContainedSwitches.js new file mode 100644 index 00000000000000..247f58e2b1d4f7 --- /dev/null +++ b/docs/src/pages/components/switches/ContainedSwitches.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import Switch from '@material-ui/core/Switch'; + +const label = { inputProps: { 'aria-label': 'Switch demo' } }; + +export default function ContainedSwitches() { + return ( +
+ + + + + + + + +
+ ); +} diff --git a/docs/src/pages/components/switches/ContainedSwitches.tsx b/docs/src/pages/components/switches/ContainedSwitches.tsx new file mode 100644 index 00000000000000..247f58e2b1d4f7 --- /dev/null +++ b/docs/src/pages/components/switches/ContainedSwitches.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import Switch from '@material-ui/core/Switch'; + +const label = { inputProps: { 'aria-label': 'Switch demo' } }; + +export default function ContainedSwitches() { + return ( +
+ + + + + + + + +
+ ); +} diff --git a/docs/src/pages/components/switches/switches.md b/docs/src/pages/components/switches/switches.md index d22345fba2928d..810d6ab2601e23 100644 --- a/docs/src/pages/components/switches/switches.md +++ b/docs/src/pages/components/switches/switches.md @@ -19,6 +19,12 @@ should be made clear from the corresponding inline label. {{"demo": "pages/components/switches/BasicSwitches.js"}} +## Variants + +If you need an contained switch, use the `variant` prop. + +{{"demo": "pages/components/switches/ContainedSwitches.js"}} + ## Label You can provide a label to the `Switch` thanks to the `FormControlLabel` component. diff --git a/docs/translations/api-docs/switch/switch.json b/docs/translations/api-docs/switch/switch.json index 29c49a1b0fc182..c8bd19506de975 100644 --- a/docs/translations/api-docs/switch/switch.json +++ b/docs/translations/api-docs/switch/switch.json @@ -17,7 +17,8 @@ "required": "If true, the input element is required.", "size": "The size of the component. small is equivalent to the dense switch styling.", "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", - "value": "The value of the component. The DOM API casts this to a string. The browser uses "on" as the default value." + "value": "The value of the component. The DOM API casts this to a string. The browser uses "on" as the default value.", + "variant": "The variant to use." }, "classDescriptions": { "root": { "description": "Styles applied to the root element." }, diff --git a/framer/Material-UI.framerfx/code/Switch.tsx b/framer/Material-UI.framerfx/code/Switch.tsx index bb11971f3b7145..49e8c9c6f8219e 100644 --- a/framer/Material-UI.framerfx/code/Switch.tsx +++ b/framer/Material-UI.framerfx/code/Switch.tsx @@ -7,6 +7,7 @@ interface Props { checked: boolean; defaultChecked?: boolean; disabled: boolean; + variant: 'contained' | 'normal'; label: string; width: number | string; height: number; @@ -43,6 +44,7 @@ export function Switch(props: Props) { Switch.defaultProps = { checked: false, disabled: false, + variant: 'normal' as 'normal', label: 'Switch', width: 100, height: 38, @@ -61,6 +63,11 @@ addPropertyControls(Switch, { type: ControlType.Boolean, title: 'Disabled', }, + variant: { + type: ControlType.Enum, + title: 'Variant', + options: ['contained', 'normal'], + }, label: { type: ControlType.String, title: 'Label', diff --git a/packages/material-ui/src/Switch/Switch.d.ts b/packages/material-ui/src/Switch/Switch.d.ts index 99fb72bfdb132b..190e06a319ecfd 100644 --- a/packages/material-ui/src/Switch/Switch.d.ts +++ b/packages/material-ui/src/Switch/Switch.d.ts @@ -4,6 +4,8 @@ import { OverridableStringUnion } from '@material-ui/types'; import { InternalStandardProps as StandardProps, Theme } from '..'; import { SwitchBaseProps } from '../internal/SwitchBase'; +export interface SwitchPropsVariantOverrides {} + export interface SwitchPropsSizeOverrides {} export interface SwitchPropsColorOverrides {} @@ -73,6 +75,11 @@ export interface SwitchProps * The browser uses "on" as the default value. */ value?: unknown; + /** + * The variant to use. + * @default 'normal' + */ + variant?: OverridableStringUnion<'normal' | 'contained', SwitchPropsVariantOverrides>; } export type SwitchClassKey = keyof NonNullable; diff --git a/packages/material-ui/src/Switch/Switch.js b/packages/material-ui/src/Switch/Switch.js index 6cfa60b0f3f6d5..5e8f4171f2b378 100644 --- a/packages/material-ui/src/Switch/Switch.js +++ b/packages/material-ui/src/Switch/Switch.js @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'; import clsx from 'clsx'; import { refType } from '@material-ui/utils'; import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; +import CheckIcon from '../internal/svg-icons/Check'; +import MinusIcon from '../internal/svg-icons/Remove'; import { alpha, darken, lighten } from '../styles/colorManipulator'; import capitalize from '../utils/capitalize'; import SwitchBase from '../internal/SwitchBase'; @@ -57,7 +59,7 @@ const SwitchRoot = experimentalStyled( width: 34 + 12 * 2, height: 14 + 12 * 2, overflow: 'hidden', - padding: 12, + padding: styleProps.variant === 'contained' ? 8 : 12, boxSizing: 'border-box', position: 'relative', flexShrink: 0, @@ -77,10 +79,10 @@ const SwitchRoot = experimentalStyled( ...(styleProps.size === 'small' && { width: 40, height: 24, - padding: 7, + padding: styleProps.variant === 'contained' ? 3 : 7, [`& .${switchClasses.thumb}`]: { - width: 16, - height: 16, + width: styleProps.variant === 'contained' ? 12 : 16, + height: styleProps.variant === 'contained' ? 12 : 16, }, [`& .${switchClasses.switchBase}`]: { padding: 4, @@ -172,11 +174,15 @@ const SwitchTrack = experimentalStyled( slot: 'Track', overridesResolver: (props, styles) => styles.track, }, -)(({ theme }) => ({ +)(({ theme, styleProps }) => ({ /* Styles applied to the track element. */ height: '100%', width: '100%', - borderRadius: 14 / 2, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: 4, + borderRadius: styleProps.variant === 'contained' ? 14 : 14 / 2, zIndex: -1, transition: theme.transitions.create(['opacity', 'background-color'], { duration: theme.transitions.duration.shortest, @@ -186,6 +192,27 @@ const SwitchTrack = experimentalStyled( opacity: theme.palette.mode === 'light' ? 0.38 : 0.3, })); +const SwitchTrackIcons = experimentalStyled( + 'div', + {}, + { name: 'MuiSwitch', slot: 'TrackIcons' }, +)(({ styleProps }) => ({ + /* Styles applied to the track icons element. */ + position: 'absolute', + width: '100%', + height: '100%', + left: 0, + top: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: styleProps.size === 'small' ? '5px 4px' : 12, + opacity: styleProps.disabled ? 0.5 : 1, + [`& svg`]: { + height: '100%', + }, +})); + const SwitchThumb = experimentalStyled( 'span', {}, @@ -194,24 +221,34 @@ const SwitchThumb = experimentalStyled( slot: 'Thumb', overridesResolver: (props, styles) => styles.thumb, }, -)(({ theme }) => ({ +)(({ theme, styleProps }) => ({ /* Styles used to create the thumb passed to the internal `SwitchBase` component `icon` prop. */ - boxShadow: theme.shadows[1], + boxShadow: styleProps.variant === 'contained' ? 'none' : theme.shadows[1], backgroundColor: 'currentColor', - width: 20, - height: 20, + width: styleProps.variant === 'contained' ? 16 : 20, + height: styleProps.variant === 'contained' ? 16 : 20, + margin: styleProps.variant === 'contained' ? 2 : undefined, borderRadius: '50%', })); const Switch = React.forwardRef(function Switch(inProps, ref) { const props = useThemeProps({ props: inProps, name: 'MuiSwitch' }); - const { className, color = 'secondary', edge = false, size = 'medium', sx, ...other } = props; + const { + className, + color = 'secondary', + edge = false, + size = 'medium', + sx, + variant = 'normal', + ...other + } = props; const styleProps = { ...props, color, edge, size, + variant, }; const classes = useUtilityClasses(styleProps); @@ -232,6 +269,12 @@ const Switch = React.forwardRef(function Switch(inProps, ref) { }} /> + {variant === 'contained' ? ( + + + + + ) : null} ); }); @@ -331,6 +374,11 @@ Switch.propTypes /* remove-proptypes */ = { * The browser uses "on" as the default value. */ value: PropTypes.any, + /** + * The variant to use. + * @default 'normal' + */ + variant: PropTypes.oneOf(['contained', 'normal']), }; export default Switch; diff --git a/packages/material-ui/src/internal/svg-icons/Check.js b/packages/material-ui/src/internal/svg-icons/Check.js new file mode 100644 index 00000000000000..697dc4f1199d2c --- /dev/null +++ b/packages/material-ui/src/internal/svg-icons/Check.js @@ -0,0 +1,10 @@ +import * as React from 'react'; +import createSvgIcon from '../../utils/createSvgIcon'; + +/** + * @ignore - internal component. + */ +export default createSvgIcon( + , + 'Check', +); diff --git a/packages/material-ui/src/internal/svg-icons/Remove.js b/packages/material-ui/src/internal/svg-icons/Remove.js new file mode 100644 index 00000000000000..b0ccb0e0d3d068 --- /dev/null +++ b/packages/material-ui/src/internal/svg-icons/Remove.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import createSvgIcon from '../../utils/createSvgIcon'; + +/** + * @ignore - internal component. + */ +export default createSvgIcon(, 'Remove');