From b3cace62fac1e0edd9b81b169fa731ebd738bf41 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 30 May 2019 12:39:41 +0100 Subject: [PATCH] [Chip] Add size prop for small option (#15751) Co-Authored-By: Olivier Tassinari --- .../pages/components/chips/ChipsPlayground.js | 55 ++++--- docs/src/pages/components/chips/SmallChips.js | 134 ++++++++++++++++ .../src/pages/components/chips/SmallChips.tsx | 136 ++++++++++++++++ .../components/chips/SmallOutlinedChips.js | 144 +++++++++++++++++ .../components/chips/SmallOutlinedChips.tsx | 146 ++++++++++++++++++ docs/src/pages/components/chips/chips.md | 12 ++ packages/material-ui/src/Chip/Chip.d.ts | 6 + packages/material-ui/src/Chip/Chip.js | 47 +++++- packages/material-ui/src/Chip/Chip.test.js | 44 ++++++ pages/api/chip.md | 6 + 10 files changed, 707 insertions(+), 23 deletions(-) create mode 100644 docs/src/pages/components/chips/SmallChips.js create mode 100644 docs/src/pages/components/chips/SmallChips.tsx create mode 100644 docs/src/pages/components/chips/SmallOutlinedChips.js create mode 100644 docs/src/pages/components/chips/SmallOutlinedChips.tsx diff --git a/docs/src/pages/components/chips/ChipsPlayground.js b/docs/src/pages/components/chips/ChipsPlayground.js index b0411e999c1d87..8429eacae1ca8a 100644 --- a/docs/src/pages/components/chips/ChipsPlayground.js +++ b/docs/src/pages/components/chips/ChipsPlayground.js @@ -22,7 +22,8 @@ const styles = theme => ({ padding: theme.spacing(2), }, chipWrapper: { - marginBottom: theme.spacing(5), + height: theme.spacing(8), + marginBottom: theme.spacing(4), }, }); @@ -33,6 +34,7 @@ class ChipsPlayground extends React.Component { avatar: 'none', icon: 'none', variant: 'default', + size: 'medium', }; handleChange = key => (event, value) => { @@ -47,9 +49,10 @@ class ChipsPlayground extends React.Component { render() { const { classes } = this.props; - const { color, onDelete, avatar, icon, variant } = this.state; + const { color, onDelete, avatar, icon, variant, size } = this.state; const colorToCode = color !== 'default' ? `color="${color}" ` : ''; + const sizeToCode = size === 'small' ? `size="small" ` : ''; const variantToCode = variant !== 'default' ? `variant="${variant}" ` : ''; let onDeleteToCode; @@ -108,7 +111,7 @@ class ChipsPlayground extends React.Component { const code = ` \`\`\`jsx - + \`\`\` `; @@ -118,13 +121,14 @@ class ChipsPlayground extends React.Component { : undefined} onDelete={onDelete !== 'none' ? this.handleDeleteExample : undefined} avatar={avatarToPlayground} icon={iconToPlayground} variant={variant} + size={size} /> @@ -132,6 +136,21 @@ class ChipsPlayground extends React.Component { + + + variant + + } label="default" /> + } label="outlined" /> + + + color @@ -150,17 +169,16 @@ class ChipsPlayground extends React.Component { - onDelete + size - } label="none" /> - } label="default" /> - } label="custom" /> + } label="medium" /> + } label="small" /> @@ -198,16 +216,17 @@ class ChipsPlayground extends React.Component { - variant + onDelete + } label="none" /> } label="default" /> - } label="outlined" /> + } label="custom" /> diff --git a/docs/src/pages/components/chips/SmallChips.js b/docs/src/pages/components/chips/SmallChips.js new file mode 100644 index 00000000000000..598ea68ee87218 --- /dev/null +++ b/docs/src/pages/components/chips/SmallChips.js @@ -0,0 +1,134 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import Avatar from '@material-ui/core/Avatar'; +import Chip from '@material-ui/core/Chip'; +import FaceIcon from '@material-ui/icons/Face'; +import DoneIcon from '@material-ui/icons/Done'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + justifyContent: 'center', + flexWrap: 'wrap', + }, + chip: { + margin: theme.spacing(1), + }, +})); + +function SmallChips() { + const classes = useStyles(); + + function handleDelete() { + alert('You clicked the delete icon.'); + } + + function handleClick() { + alert('You clicked the Chip.'); + } + + return ( +
+ + MB} + label="Clickable Chip" + onClick={handleClick} + className={classes.chip} + /> + } + label="Deletable Chip" + onDelete={handleDelete} + className={classes.chip} + /> + + + + } + label="Clickable Deletable Chip" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + } + label="Clickable Deletable Chip" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + } + /> + + MB} + label="Primary Clickable Chip" + clickable + className={classes.chip} + color="primary" + onDelete={handleDelete} + deleteIcon={} + /> + } + label="Primary Clickable Chip" + clickable + className={classes.chip} + color="primary" + onDelete={handleDelete} + deleteIcon={} + /> + + + + + } + label="Deletable Secondary Chip" + onDelete={handleDelete} + className={classes.chip} + color="secondary" + /> + } + label="Deletable Secondary Chip" + onDelete={handleDelete} + className={classes.chip} + color="secondary" + /> +
+ ); +} + +export default SmallChips; diff --git a/docs/src/pages/components/chips/SmallChips.tsx b/docs/src/pages/components/chips/SmallChips.tsx new file mode 100644 index 00000000000000..a2d63bf635d8b9 --- /dev/null +++ b/docs/src/pages/components/chips/SmallChips.tsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'; +import Avatar from '@material-ui/core/Avatar'; +import Chip from '@material-ui/core/Chip'; +import FaceIcon from '@material-ui/icons/Face'; +import DoneIcon from '@material-ui/icons/Done'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + display: 'flex', + justifyContent: 'center', + flexWrap: 'wrap', + }, + chip: { + margin: theme.spacing(1), + }, + }), +); + +function SmallChips() { + const classes = useStyles(); + + function handleDelete() { + alert('You clicked the delete icon.'); + } + + function handleClick() { + alert('You clicked the Chip.'); + } + + return ( +
+ + MB} + label="Clickable Chip" + onClick={handleClick} + className={classes.chip} + /> + } + label="Deletable Chip" + onDelete={handleDelete} + className={classes.chip} + /> + + + + } + label="Clickable Deletable Chip" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + } + label="Clickable Deletable Chip" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + } + /> + + MB} + label="Primary Clickable Chip" + clickable + className={classes.chip} + color="primary" + onDelete={handleDelete} + deleteIcon={} + /> + } + label="Primary Clickable Chip" + clickable + className={classes.chip} + color="primary" + onDelete={handleDelete} + deleteIcon={} + /> + + + + + } + label="Deletable Secondary Chip" + onDelete={handleDelete} + className={classes.chip} + color="secondary" + /> + } + label="Deletable Secondary Chip" + onDelete={handleDelete} + className={classes.chip} + color="secondary" + /> +
+ ); +} + +export default SmallChips; diff --git a/docs/src/pages/components/chips/SmallOutlinedChips.js b/docs/src/pages/components/chips/SmallOutlinedChips.js new file mode 100644 index 00000000000000..175750861dbbe4 --- /dev/null +++ b/docs/src/pages/components/chips/SmallOutlinedChips.js @@ -0,0 +1,144 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import Avatar from '@material-ui/core/Avatar'; +import Chip from '@material-ui/core/Chip'; +import FaceIcon from '@material-ui/icons/Face'; +import DoneIcon from '@material-ui/icons/Done'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + justifyContent: 'center', + flexWrap: 'wrap', + }, + chip: { + margin: theme.spacing(1), + }, +})); + +function SmallOutlinedChips() { + const classes = useStyles(); + + function handleDelete() { + alert('You clicked the delete icon.'); + } + + function handleClick() { + alert('You clicked the Chip.'); + } + + return ( +
+ + MB} + label="Clickable Chip" + onClick={handleClick} + className={classes.chip} + /> + } + label="Deletable Chip" + onDelete={handleDelete} + className={classes.chip} + /> + + + + } + label="Clickable Deletable Chip" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + } + label="Clickable Deletable Chip" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + } + /> + + MB} + label="Primary Clickable Chip" + clickable + className={classes.chip} + color="primary" + onDelete={handleDelete} + deleteIcon={} + /> + } + label="Primary Clickable Chip" + clickable + className={classes.chip} + color="primary" + onDelete={handleDelete} + deleteIcon={} + /> + + + + + } + label="Deletable Secondary Chip" + onDelete={handleDelete} + className={classes.chip} + color="secondary" + /> + } + label="Deletable Secondary Chip" + onDelete={handleDelete} + className={classes.chip} + color="secondary" + /> +
+ ); +} + +export default SmallOutlinedChips; diff --git a/docs/src/pages/components/chips/SmallOutlinedChips.tsx b/docs/src/pages/components/chips/SmallOutlinedChips.tsx new file mode 100644 index 00000000000000..dbc2e603ed3be6 --- /dev/null +++ b/docs/src/pages/components/chips/SmallOutlinedChips.tsx @@ -0,0 +1,146 @@ +import React from 'react'; +import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'; +import Avatar from '@material-ui/core/Avatar'; +import Chip from '@material-ui/core/Chip'; +import FaceIcon from '@material-ui/icons/Face'; +import DoneIcon from '@material-ui/icons/Done'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + display: 'flex', + justifyContent: 'center', + flexWrap: 'wrap', + }, + chip: { + margin: theme.spacing(1), + }, + }), +); + +function SmallOutlinedChips() { + const classes = useStyles(); + + function handleDelete() { + alert('You clicked the delete icon.'); + } + + function handleClick() { + alert('You clicked the Chip.'); + } + + return ( +
+ + MB} + label="Clickable Chip" + onClick={handleClick} + className={classes.chip} + /> + } + label="Deletable Chip" + onDelete={handleDelete} + className={classes.chip} + /> + + + + } + label="Clickable Deletable Chip" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + } + label="Clickable Deletable Chip" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + } + /> + + MB} + label="Primary Clickable Chip" + clickable + className={classes.chip} + color="primary" + onDelete={handleDelete} + deleteIcon={} + /> + } + label="Primary Clickable Chip" + clickable + className={classes.chip} + color="primary" + onDelete={handleDelete} + deleteIcon={} + /> + + + + + } + label="Deletable Secondary Chip" + onDelete={handleDelete} + className={classes.chip} + color="secondary" + /> + } + label="Deletable Secondary Chip" + onDelete={handleDelete} + className={classes.chip} + color="secondary" + /> +
+ ); +} + +export default SmallOutlinedChips; diff --git a/docs/src/pages/components/chips/chips.md b/docs/src/pages/components/chips/chips.md index cd9308fb74e7dd..90d7b1cbfcc71a 100644 --- a/docs/src/pages/components/chips/chips.md +++ b/docs/src/pages/components/chips/chips.md @@ -40,6 +40,18 @@ gain depth while clicked or touched. {{"demo": "pages/components/chips/ChipsArray.js"}} +## Small Chip + +You can use the `size` prop to define a small Chip. + +### Default variant + +{{"demo": "pages/components/chips/SmallChips.js"}} + +### Outlined variant + +{{"demo": "pages/components/chips/SmallOutlinedChips.js"}} + ## Chip Playground {{"demo": "pages/components/chips/ChipsPlayground.js", "hideHeader": true}} diff --git a/packages/material-ui/src/Chip/Chip.d.ts b/packages/material-ui/src/Chip/Chip.d.ts index 0de3463d3128f1..62aa77960336d0 100644 --- a/packages/material-ui/src/Chip/Chip.d.ts +++ b/packages/material-ui/src/Chip/Chip.d.ts @@ -11,6 +11,7 @@ declare const Chip: OverridableComponent<{ icon?: React.ReactElement; label?: React.ReactNode; onDelete?: React.EventHandler; + size?: 'small' | 'medium'; variant?: 'default' | 'outlined'; }; defaultComponent: 'div'; @@ -19,6 +20,7 @@ declare const Chip: OverridableComponent<{ export type ChipClassKey = | 'root' + | 'sizeSmall' | 'colorPrimary' | 'colorSecondary' | 'clickable' @@ -31,14 +33,18 @@ export type ChipClassKey = | 'outlinedPrimary' | 'outlinedSecondary' | 'avatar' + | 'avatarSmall' | 'avatarColorPrimary' | 'avatarColorSecondary' | 'avatarChildren' | 'icon' + | 'iconSmall' | 'iconColorPrimary' | 'iconColorSecondary' | 'label' + | 'labelSmall' | 'deleteIcon' + | 'deleteIconSmall' | 'deleteIconColorPrimary' | 'deleteIconColorSecondary' | 'deleteIconOutlinedColorPrimary' diff --git a/packages/material-ui/src/Chip/Chip.js b/packages/material-ui/src/Chip/Chip.js index b45d2b0b84ff52..08ae064eb9293b 100644 --- a/packages/material-ui/src/Chip/Chip.js +++ b/packages/material-ui/src/Chip/Chip.js @@ -12,6 +12,7 @@ import '../Avatar/Avatar'; // So we don't have any override priority issue. export const styles = theme => { const height = 32; + const smallHeight = 24; const backgroundColor = theme.palette.type === 'light' ? theme.palette.grey[300] : theme.palette.grey[700]; const deleteIconColor = fade(theme.palette.text.primary, 0.26); @@ -40,6 +41,10 @@ export const styles = theme => { verticalAlign: 'middle', boxSizing: 'border-box', }, + /* Styles applied to the root element if `size="small"`. */ + sizeSmall: { + height: smallHeight, + }, /* Styles applied to the root element if `color="primary"`. */ colorPrimary: { backgroundColor: theme.palette.primary.main, @@ -135,6 +140,11 @@ export const styles = theme => { color: theme.palette.type === 'light' ? theme.palette.grey[700] : theme.palette.grey[300], fontSize: theme.typography.pxToRem(16), }, + avatarSmall: { + width: smallHeight, + height: smallHeight, + fontSize: theme.typography.pxToRem(12), + }, /* Styles applied to the `avatar` element if `color="primary"`. */ avatarColorPrimary: { color: theme.palette.primary.contrastText, @@ -147,15 +157,18 @@ export const styles = theme => { }, /* Styles applied to the `avatar` elements children. */ avatarChildren: { - width: 19, - height: 19, + height: 18, }, /* Styles applied to the `icon` element. */ icon: { color: theme.palette.type === 'light' ? theme.palette.grey[700] : theme.palette.grey[300], - marginLeft: 4, + marginLeft: 5, marginRight: -8, }, + iconSmall: { + width: 16, + marginRight: -5, + }, /* Styles applied to the `icon` element if `color="primary"`. */ iconColorPrimary: { color: 'inherit', @@ -174,6 +187,10 @@ export const styles = theme => { whiteSpace: 'nowrap', cursor: 'inherit', }, + labelSmall: { + paddingLeft: 8, + paddingRight: 8, + }, /* Styles applied to the `deleteIcon` element. */ deleteIcon: { // Remove grey highlight @@ -181,11 +198,15 @@ export const styles = theme => { color: deleteIconColor, cursor: 'pointer', height: 'auto', - margin: '0 4px 0 -8px', + margin: '0 5px 0 -8px', '&:hover': { color: fade(deleteIconColor, 0.4), }, }, + deleteIconSmall: { + height: 16, + margin: '0 1px 0 -9px', + }, /* Styles applied to the deleteIcon element if `color="primary"` and `variant="default"`. */ deleteIconColorPrimary: { color: fade(theme.palette.primary.contrastText, 0.7), @@ -235,6 +256,7 @@ const Chip = React.forwardRef(function Chip(props, ref) { onDelete, onKeyDown, onKeyUp, + size = 'medium', variant = 'default', ...other } = props; @@ -292,10 +314,12 @@ const Chip = React.forwardRef(function Chip(props, ref) { }; const clickable = clickableProp !== false && onClick ? true : clickableProp; + const small = size === 'small'; const className = clsx( classes.root, { + [classes.sizeSmall]: small, [classes[`color${capitalize(color)}`]]: color !== 'default', [classes.clickable]: clickable, [classes[`clickableColor${capitalize(color)}`]]: clickable && color !== 'default', @@ -311,6 +335,7 @@ const Chip = React.forwardRef(function Chip(props, ref) { let deleteIcon = null; if (onDelete) { const customClasses = clsx({ + [classes.deleteIconSmall]: small, [classes[`deleteIconColor${capitalize(color)}`]]: color !== 'default' && variant !== 'outlined', [classes[`deleteIconOutlinedColor${capitalize(color)}`]]: @@ -335,6 +360,7 @@ const Chip = React.forwardRef(function Chip(props, ref) { if (avatarProp && React.isValidElement(avatarProp)) { avatar = React.cloneElement(avatarProp, { className: clsx(classes.avatar, avatarProp.props.className, { + [classes.avatarSmall]: small, [classes[`avatarColor${capitalize(color)}`]]: color !== 'default', }), childrenClassName: clsx(classes.avatarChildren, avatarProp.props.childrenClassName), @@ -345,6 +371,7 @@ const Chip = React.forwardRef(function Chip(props, ref) { if (iconProp && React.isValidElement(iconProp)) { icon = React.cloneElement(iconProp, { className: clsx(classes.icon, iconProp.props.className, { + [classes.iconSmall]: small, [classes[`iconColor${capitalize(color)}`]]: color !== 'default', }), }); @@ -370,7 +397,13 @@ const Chip = React.forwardRef(function Chip(props, ref) { {...other} > {avatar || icon} - {label} + + {label} + {deleteIcon} ); @@ -441,6 +474,10 @@ Chip.propTypes = { * @ignore */ onKeyUp: PropTypes.func, + /** + * The size of the chip. + */ + size: PropTypes.oneOf(['small', 'medium']), /** * The variant to use. */ diff --git a/packages/material-ui/src/Chip/Chip.test.js b/packages/material-ui/src/Chip/Chip.test.js index d7ec678fcd8c4b..a6a12ca8749d8d 100644 --- a/packages/material-ui/src/Chip/Chip.test.js +++ b/packages/material-ui/src/Chip/Chip.test.js @@ -559,4 +559,48 @@ describe('', () => { assert.strictEqual(wrapper.find('span#test-icon').hasClass(classes.icon), true); }); }); + + describe('prop: size', () => { + it('should render with the sizeSmall class', () => { + const wrapper = mount(); + const chip = wrapper.find(`.${classes.root}`).hostNodes(); + + assert.strictEqual(chip.hasClass(classes.sizeSmall), true); + }); + + it('should render the label with the labelSmall class', () => { + const wrapper = mount(); + const chip = wrapper.find(`.${classes.root}`).hostNodes(); + const label = chip.find(`.${classes.label}`).hostNodes(); + + assert.strictEqual(label.hasClass(classes.labelSmall), true); + }); + + it('should render an avatar with the avatarSmall class', () => { + const wrapper = mount( + MB} />, + ); + const chip = wrapper.find(`.${classes.root}`).hostNodes(); + const avatar = chip.find('.my-Avatar').hostNodes(); + + assert.strictEqual(avatar.hasClass(classes.avatar), true); + assert.strictEqual(avatar.hasClass(classes.avatarSmall), true); + }); + + it('should render an icon with the icon and iconSmall classes', () => { + const wrapper = mount(} />); + const icon = wrapper.find('span#test-icon'); + + assert.strictEqual(icon.hasClass(classes.icon), true); + assert.strictEqual(icon.hasClass(classes.iconSmall), true); + }); + + it('should render the delete icon with the deleteIcon and deleteIconSmall classes', () => { + const wrapper = mount( {}} />); + const iconWrapper = wrapper.find('svg[data-mui-test="CancelIcon"]'); + + assert.strictEqual(iconWrapper.hasClass(classes.deleteIcon), true); + assert.strictEqual(iconWrapper.hasClass(classes.deleteIconSmall), true); + }); + }); }); diff --git a/pages/api/chip.md b/pages/api/chip.md index 0b74c499dac303..2c27bc8fec7609 100644 --- a/pages/api/chip.md +++ b/pages/api/chip.md @@ -28,6 +28,7 @@ Chips represent complex entities in small blocks, such as a contact. | icon | element | | Icon element. | | label | node | | The content of the label. | | onDelete | func | | Callback function fired when the delete icon is clicked. If set, the delete icon will be shown. | +| size | enum: 'small' |
 'medium'
| 'medium' | The size of the chip. | | variant | enum: 'default' |
 'outlined'
| 'default' | The variant to use. | The `ref` is forwarded to the root element. @@ -43,6 +44,7 @@ This property accepts the following keys: | Name | Description | |:-----|:------------| | root | Styles applied to the root element. +| sizeSmall | Styles applied to the root element if `size="small"`. | colorPrimary | Styles applied to the root element if `color="primary"`. | colorSecondary | Styles applied to the root element if `color="secondary"`. | clickable | Styles applied to the root element if `onClick` is defined or `clickable={true}`. @@ -55,14 +57,18 @@ This property accepts the following keys: | outlinedPrimary | Styles applied to the root element if `variant="outlined"` and `color="primary"`. | outlinedSecondary | Styles applied to the root element if `variant="outlined"` and `color="secondary"`. | avatar | Styles applied to the `avatar` element. +| avatarSmall | | avatarColorPrimary | Styles applied to the `avatar` element if `color="primary"`. | avatarColorSecondary | Styles applied to the `avatar` element if `color="secondary"`. | avatarChildren | Styles applied to the `avatar` elements children. | icon | Styles applied to the `icon` element. +| iconSmall | | iconColorPrimary | Styles applied to the `icon` element if `color="primary"`. | iconColorSecondary | Styles applied to the `icon` element if `color="secondary"`. | label | Styles applied to the label `span` element`. +| labelSmall | | deleteIcon | Styles applied to the `deleteIcon` element. +| deleteIconSmall | | deleteIconColorPrimary | Styles applied to the deleteIcon element if `color="primary"` and `variant="default"`. | deleteIconColorSecondary | Styles applied to the deleteIcon element if `color="secondary"` and `variant="default"`. | deleteIconOutlinedColorPrimary | Styles applied to the deleteIcon element if `color="primary"` and `variant="outlined"`.