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

[IconButton] Migrate to emotion #24542

Merged
merged 18 commits into from
Jan 23, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/pages/api-docs/icon-button.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"size": {
"type": { "name": "enum", "description": "'medium'<br>&#124;&nbsp;'small'" },
"default": "'medium'"
}
},
"sx": { "type": { "name": "object" } }
},
"name": "IconButton",
"styles": {
Expand All @@ -45,6 +46,6 @@
"filename": "/packages/material-ui/src/IconButton/IconButton.js",
"inheritance": { "component": "ButtonBase", "pathname": "/api/button-base/" },
"demos": "<ul><li><a href=\"/components/buttons/\">Buttons</a></li></ul>",
"styledComponent": false,
"styledComponent": true,
"cssComponent": false
}
3 changes: 2 additions & 1 deletion docs/translations/api-docs/icon-button/icon-button.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"disableFocusRipple": "If <code>true</code>, the keyboard focus ripple is disabled.",
"disableRipple": "If <code>true</code>, the ripple effect is disabled.<br>⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure to highlight the element by applying separate styles with the <code>.Mui-focusedVisible</code> class.",
"edge": "If given, uses a negative margin to counteract the padding on one side (this is often helpful for aligning the left or right side of the icon with content above or below, without ruining the border size and shape).",
"size": "The size of the component. <code>small</code> is equivalent to the dense button styling."
"size": "The size of the component. <code>small</code> is equivalent to the dense button styling.",
"sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the <a href=\"/system/basics/#the-sx-prop\">`sx` page</a> for more details."
},
"classDescriptions": {
"root": { "description": "Styles applied to the root element." },
Expand Down
2 changes: 1 addition & 1 deletion framer/scripts/framerConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export const componentSettings = {
template: 'icon.txt',
},
IconButton: {
ignoredProps: ['children', 'edge', 'disableRipple', 'disableFocusRipple'],
ignoredProps: ['children', 'edge', 'disableRipple', 'disableFocusRipple', 'sx'],
propValues: {
icon: "'favorite'",
iconTheme: 'Filled',
Expand Down
8 changes: 7 additions & 1 deletion packages/material-ui/src/IconButton/IconButton.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { PropTypes } from '..';
import * as React from 'react';
import { SxProps } from '@material-ui/system';
import { PropTypes, Theme } from '..';
import { ExtendButtonBase, ExtendButtonBaseTypeMap } from '../ButtonBase';
import { OverrideProps } from '../OverridableComponent';

Expand Down Expand Up @@ -63,6 +65,10 @@ export type IconButtonTypeMap<
* @default 'medium'
*/
size?: 'small' | 'medium';
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme>;
};
defaultComponent: D;
}>;
Expand Down
208 changes: 132 additions & 76 deletions packages/material-ui/src/IconButton/IconButton.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,54 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { chainPropTypes } from '@material-ui/utils';
import withStyles from '../styles/withStyles';
import { chainPropTypes, deepmerge } from '@material-ui/utils';
import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled';
import experimentalStyled from '../styles/experimentalStyled';
import useThemeProps from '../styles/useThemeProps';
import { alpha } from '../styles/colorManipulator';
import ButtonBase from '../ButtonBase';
import capitalize from '../utils/capitalize';
import iconButtonClasses, { getIconButtonUtilityClass } from './iconButtonClasses';

export const styles = (theme) => ({
/* Styles applied to the root element. */
root: {
const overridesResolver = (props, styles) => {
const { styleProps } = props;

return deepmerge(styles.root || {}, {
...(styleProps.color !== 'default' && styles[`color${capitalize(styleProps.color)}`]),
...(styleProps.edge && styles[`edge${capitalize(styleProps.edge)}`]),
...styles[`size${capitalize(styleProps.size)}`],
[`& .${iconButtonClasses.label}`]: styles.label,
});
};

const useUtilityClasses = (styleProps) => {
const { classes, disabled, color, edge, size } = styleProps;

const slots = {
root: [
'root',
disabled && 'disabled',
color !== 'default' && `color${capitalize(color)}`,
edge && `edge${capitalize(edge)}`,
`size${capitalize(size)}`,
],
label: ['label'],
};

return composeClasses(slots, getIconButtonUtilityClass, classes);
};

const IconButtonRoot = experimentalStyled(
ButtonBase,
{},
{
name: 'MuiIconButton',
slot: 'Root',
overridesResolver,
},
)(
({ theme }) => ({
/* Styles applied to the root element. */
textAlign: 'center',
flex: '0 0 auto',
fontSize: theme.typography.pxToRem(24),
Expand All @@ -27,76 +66,85 @@ export const styles = (theme) => ({
backgroundColor: 'transparent',
},
},
'&$disabled': {
backgroundColor: 'transparent',
color: theme.palette.action.disabled,
},
},
/* Styles applied to the root element if `edge="start"`. */
edgeStart: {
marginLeft: -12,
'$sizeSmall&': {
marginLeft: -3,
},
},
/* Styles applied to the root element if `edge="end"`. */
edgeEnd: {
marginRight: -12,
'$sizeSmall&': {
marginRight: -3,
},
},
/* Styles applied to the root element if `color="inherit"`. */
colorInherit: {
color: 'inherit',
},
/* Styles applied to the root element if `color="primary"`. */
colorPrimary: {
color: theme.palette.primary.main,
'&:hover': {
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.hoverOpacity),
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
}),
({ styleProps }) => ({
queengooborg marked this conversation as resolved.
Show resolved Hide resolved
/* Styles applied to the root element if `edge="start"`. */
...(styleProps.edge === 'start' && {
marginLeft: styleProps.size === 'small' ? -3 : -12,
}),
/* Styles applied to the root element if `edge="end"`. */
...(styleProps.edge === 'end' && {
marginRight: styleProps.size === 'small' ? -3 : -12,
}),
}),
({ theme, styleProps }) => ({
/* Styles applied to the root element if `color="inherit"`. */
...(styleProps.color === 'inherit' && {
color: 'inherit',
}),
/* Styles applied to the root element if `color="primary"`. */
...(styleProps.color === 'primary' && {
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
color: theme.palette.primary.main,
'&:hover': {
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.hoverOpacity),
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
},
},
},
},
/* Styles applied to the root element if `color="secondary"`. */
colorSecondary: {
color: theme.palette.secondary.main,
'&:hover': {
backgroundColor: alpha(theme.palette.secondary.main, theme.palette.action.hoverOpacity),
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
}),
/* Styles applied to the root element if `color="secondary"`. */
...(styleProps.color === 'secondary' && {
color: theme.palette.secondary.main,
'&:hover': {
backgroundColor: alpha(theme.palette.secondary.main, theme.palette.action.hoverOpacity),
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
},
},
}),
}),
({ theme, styleProps }) => ({
queengooborg marked this conversation as resolved.
Show resolved Hide resolved
/* Styles applied to the root element if `size="small"`. */
...(styleProps.size === 'small' && {
padding: 3,
fontSize: theme.typography.pxToRem(18),
}),
}),
({ theme }) => ({
queengooborg marked this conversation as resolved.
Show resolved Hide resolved
/* Styles applied to the root element if `disabled={true}`. */
[`&.${iconButtonClasses.disabled}`]: {
backgroundColor: 'transparent',
color: theme.palette.action.disabled,
},
}),
);

const IconButtonLabel = experimentalStyled(
'span',
{},
{
name: 'MuiIconButton',
slot: 'Label',
},
/* Pseudo-class applied to the root element if `disabled={true}`. */
disabled: {},
/* Styles applied to the root element if `size="small"`. */
sizeSmall: {
padding: 3,
fontSize: theme.typography.pxToRem(18),
},
)({
/* Styles applied to the children container element. */
label: {
width: '100%',
display: 'flex',
alignItems: 'inherit',
justifyContent: 'inherit',
},
width: '100%',
display: 'flex',
alignItems: 'inherit',
justifyContent: 'inherit',
});

/**
* Refer to the [Icons](/components/icons/) section of the documentation
* regarding the available icon options.
*/
const IconButton = React.forwardRef(function IconButton(props, ref) {
const IconButton = React.forwardRef(function IconButton(inProps, ref) {
const props = useThemeProps({ props: inProps, name: 'MuiIconButton' });
const {
edge = false,
children,
classes,
className,
color = 'default',
disabled = false,
Expand All @@ -105,27 +153,31 @@ const IconButton = React.forwardRef(function IconButton(props, ref) {
...other
} = props;

const styleProps = {
...props,
edge,
color,
disabled,
queengooborg marked this conversation as resolved.
Show resolved Hide resolved
disableFocusRipple,
size,
};

const classes = useUtilityClasses(styleProps);

return (
<ButtonBase
className={clsx(
classes.root,
{
[classes[`color${capitalize(color)}`]]: color !== 'default',
[classes.disabled]: disabled,
[classes[`size${capitalize(size)}`]]: size !== 'medium',
[classes.edgeStart]: edge === 'start',
[classes.edgeEnd]: edge === 'end',
},
className,
)}
<IconButtonRoot
className={clsx(classes.root, className)}
centerRipple
focusRipple={!disableFocusRipple}
disabled={disabled}
ref={ref}
styleProps={styleProps}
{...other}
>
<span className={classes.label}>{children}</span>
</ButtonBase>
<IconButtonLabel className={classes.label} styleProps={styleProps}>
{children}
</IconButtonLabel>
</IconButtonRoot>
);
});

Expand Down Expand Up @@ -199,6 +251,10 @@ IconButton.propTypes = {
* @default 'medium'
*/
size: PropTypes.oneOf(['medium', 'small']),
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.object,
};

export default withStyles(styles, { name: 'MuiIconButton' })(IconButton);
export default IconButton;
35 changes: 17 additions & 18 deletions packages/material-ui/src/IconButton/IconButton.test.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import * as React from 'react';
import { expect } from 'chai';
import PropTypes from 'prop-types';
import { getClasses, createMount, createClientRender, describeConformance } from 'test/utils';
import { createMount, createClientRender, describeConformanceV5 } from 'test/utils';
queengooborg marked this conversation as resolved.
Show resolved Hide resolved
import Icon from '../Icon';
import ButtonBase from '../ButtonBase';
import IconButton from './IconButton';
import classes from './iconButtonClasses';

describe('<IconButton />', () => {
let classes;
const mount = createMount();
const render = createClientRender({ strict: false });

before(() => {
classes = getClasses(<IconButton />);
});

describeConformance(<IconButton>book</IconButton>, () => ({
describeConformanceV5(<IconButton>book</IconButton>, () => ({
classes,
inheritComponent: ButtonBase,
mount,
refInstanceof: window.HTMLButtonElement,
skip: ['componentProp'],
muiName: 'MuiIconButton',
testVariantProps: { edge: 'end', disabled: true },
testDeepOverrides: { slotName: 'label', slotClassName: classes.label },
skip: ['componentProp', 'componentsProp'],
}));

it('should render an inner label span (bloody safari)', () => {
Expand Down Expand Up @@ -104,15 +103,15 @@ describe('<IconButton />', () => {
expect(button).to.have.class(classes.disabled);
});
});
queengooborg marked this conversation as resolved.
Show resolved Hide resolved

it('should raise a warning about onClick in children because of Firefox', () => {
expect(() => {
PropTypes.checkPropTypes(
IconButton.Naked.propTypes,
{ classes: {}, children: <svg onClick={() => {}} /> },
'prop',
'MockedName',
);
}).toErrorDev(['Material-UI: You are providing an onClick event listener']);
it('should raise a warning about onClick in children because of Firefox', () => {
expect(() => {
PropTypes.checkPropTypes(
IconButton.propTypes,
{ classes: {}, children: <svg onClick={() => {}} /> },
'prop',
'MockedName',
);
}).toErrorDev(['Material-UI: You are providing an onClick event listener']);
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
});
});
18 changes: 18 additions & 0 deletions packages/material-ui/src/IconButton/iconButtonClasses.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export interface IconButtonClasses {
root: string;
disabled: string;
colorInherit: string;
colorPrimary: string;
colorSecondary: string;
edgeStart: string;
edgeEnd: string;
sizeSmall: string;
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
sizeMedium: string;
label: string;
}

declare const iconButtonClasses: IconButtonClasses;

export function getIconButtonUtilityClass(slot: string): string;

export default iconButtonClasses;
Loading