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 all 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
202 changes: 126 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, styleProps }) => ({
/* Styles applied to the root element. */
textAlign: 'center',
flex: '0 0 auto',
fontSize: theme.typography.pxToRem(24),
Expand All @@ -27,76 +66,79 @@ 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',
/* 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',
},
},
}),
/* Styles applied to the root element if `size="small"`. */
...(styleProps.size === 'small' && {
padding: 3,
fontSize: theme.typography.pxToRem(18),
}),
/* 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 +147,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 +245,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;
17 changes: 8 additions & 9 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 @@ -108,7 +107,7 @@ describe('<IconButton />', () => {
it('should raise a warning about onClick in children because of Firefox', () => {
expect(() => {
PropTypes.checkPropTypes(
IconButton.Naked.propTypes,
IconButton.propTypes,
{ classes: {}, children: <svg onClick={() => {}} /> },
'prop',
'MockedName',
Expand Down
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;
20 changes: 20 additions & 0 deletions packages/material-ui/src/IconButton/iconButtonClasses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled';

export function getIconButtonUtilityClass(slot) {
return generateUtilityClass('MuiIconButton', slot);
}

const iconButtonClasses = generateUtilityClasses('MuiIconButton', [
'root',
'disabled',
'colorInherit',
'colorPrimary',
'colorSecondary',
'edgeStart',
'edgeEnd',
'sizeSmall',
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
'sizeMedium',
'label',
]);

export default iconButtonClasses;
3 changes: 3 additions & 0 deletions packages/material-ui/src/IconButton/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export { default } from './IconButton';
export * from './IconButton';

export { default as iconButtonClasses } from './iconButtonClasses';
export * from './iconButtonClasses';
Loading