Repository: mui/material-ui (TypeScript) — Feature

Issue:
[ToggleButtonGroup] -  Feature Request - Add a size property



Discussion:

Resolving patch:
diff --git a/docs/src/pages/components/toggle-button/ToggleButtonSizes.js b/docs/src/pages/components/toggle-button/ToggleButtonSizes.js
new file mode 100644
--- /dev/null
+++ b/docs/src/pages/components/toggle-button/ToggleButtonSizes.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import FormatAlignLeftIcon from '@material-ui/icons/FormatAlignLeft';
+import FormatAlignCenterIcon from '@material-ui/icons/FormatAlignCenter';
+import FormatAlignRightIcon from '@material-ui/icons/FormatAlignRight';
+import FormatAlignJustifyIcon from '@material-ui/icons/FormatAlignJustify';
+import Grid from '@material-ui/core/Grid';
+import ToggleButton from '@material-ui/lab/ToggleButton';
+import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
+
+export default function ToggleButtonSizes() {
+  const [alignment, setAlignment] = React.useState('left');
+
+  const handleChange = (event, newAlignment) => {
+    setAlignment(newAlignment);
+  };
+
+  const children = [
+    <ToggleButton key={1} value="left">
+      <FormatAlignLeftIcon />
+    </ToggleButton>,
+    <ToggleButton key={2} value="center">
+      <FormatAlignCenterIcon />
+    </ToggleButton>,
+    <ToggleButton key={3} value="right">
+      <FormatAlignRightIcon />
+    </ToggleButton>,
+    <ToggleButton key={4} value="justify" disabled>
+      <FormatAlignJustifyIcon />
+    </ToggleButton>,
+  ];
+
+  return (
+    <Grid container spacing={2} direction="column" alignItems="center">
+      <Grid item>
+        <ToggleButtonGroup size="small" value={alignment} exclusive onChange={handleChange}>
+          {children}
+        </ToggleButtonGroup>
+      </Grid>
+      <Grid item>
+        <ToggleButtonGroup size="medium" value={alignment} exclusive onChange={handleChange}>
+          {children}
+        </ToggleButtonGroup>
+      </Grid>
+      <Grid item>
+        <ToggleButtonGroup size="large" value={alignment} exclusive onChange={handleChange}>
+          {children}
+        </ToggleButtonGroup>
+      </Grid>
+    </Grid>
+  );
+}
diff --git a/docs/src/pages/components/toggle-button/ToggleButtons.js b/docs/src/pages/components/toggle-button/ToggleButtons.js
--- a/docs/src/pages/components/toggle-button/ToggleButtons.js
+++ b/docs/src/pages/components/toggle-button/ToggleButtons.js
@@ -1,6 +1,5 @@
 import React from 'react';
-import PropTypes from 'prop-types';
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/core/styles';
 import FormatAlignLeftIcon from '@material-ui/icons/FormatAlignLeft';
 import FormatAlignCenterIcon from '@material-ui/icons/FormatAlignCenter';
 import FormatAlignRightIcon from '@material-ui/icons/FormatAlignRight';
@@ -15,87 +14,76 @@ import Grid from '@material-ui/core/Grid';
 import ToggleButton from '@material-ui/lab/ToggleButton';
 import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
 
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
   toggleContainer: {
     margin: theme.spacing(2, 0),
   },
-});
+}));
 
-class ToggleButtons extends React.Component {
-  state = {
-    alignment: 'left',
-    formats: ['bold'],
-  };
+export default function ToggleButtons() {
+  const [alignment, setAlignment] = React.useState('left');
+  const [formats, setFormats] = React.useState(() => ['bold']);
 
-  handleFormat = (event, formats) => {
-    this.setState({ formats });
+  const handleFormat = (event, newFormats) => {
+    setFormats(newFormats);
   };
 
-  handleAlignment = (event, alignment) => {
-    this.setState({ alignment });
+  const handleAlignment = (event, newAlignment) => {
+    setAlignment(newAlignment);
   };
 
-  render() {
-    const { classes } = this.props;
-    const { alignment, formats } = this.state;
+  const classes = useStyles();
 
-    return (
-      <Grid container spacing={2}>
-        <Grid item sm={12} md={6}>
-          <div className={classes.toggleContainer}>
-            <ToggleButtonGroup value={alignment} exclusive onChange={this.handleAlignment}>
-              <ToggleButton value="left">
-                <FormatAlignLeftIcon />
-              </ToggleButton>
-              <ToggleButton value="center">
-                <FormatAlignCenterIcon />
-              </ToggleButton>
-              <ToggleButton value="right">
-                <FormatAlignRightIcon />
-              </ToggleButton>
-              <ToggleButton value="justify" disabled>
-                <FormatAlignJustifyIcon />
-              </ToggleButton>
-            </ToggleButtonGroup>
-          </div>
-          <Typography gutterBottom>Exclusive Selection</Typography>
-          <Typography>
-            Text justification toggle buttons present options for left, right, center, full, and
-            justified text with only one item available for selection at a time. Selecting one
-            option deselects any other.
-          </Typography>
-        </Grid>
-        <Grid item sm={12} md={6}>
-          <div className={classes.toggleContainer}>
-            <ToggleButtonGroup value={formats} onChange={this.handleFormat}>
-              <ToggleButton value="bold">
-                <FormatBoldIcon />
-              </ToggleButton>
-              <ToggleButton value="italic">
-                <FormatItalicIcon />
-              </ToggleButton>
-              <ToggleButton value="underlined">
-                <FormatUnderlinedIcon />
-              </ToggleButton>
-              <ToggleButton disabled value="color">
-                <FormatColorFillIcon />
-                <ArrowDropDownIcon />
-              </ToggleButton>
-            </ToggleButtonGroup>
-          </div>
-          <Typography gutterBottom>Multiple Selection</Typography>
-          <Typography>
-            Logically-grouped options, like Bold, Italic, and Underline, allow multiple options to
-            be selected.
-          </Typography>
-        </Grid>
+  return (
+    <Grid container spacing={2}>
+      <Grid item sm={12} md={6}>
+        <div className={classes.toggleContainer}>
+          <ToggleButtonGroup value={alignment} exclusive onChange={handleAlignment}>
+            <ToggleButton value="left">
+              <FormatAlignLeftIcon />
+            </ToggleButton>
+            <ToggleButton value="center">
+              <FormatAlignCenterIcon />
+            </ToggleButton>
+            <ToggleButton value="right">
+              <FormatAlignRightIcon />
+            </ToggleButton>
+            <ToggleButton value="justify" disabled>
+              <FormatAlignJustifyIcon />
+            </ToggleButton>
+          </ToggleButtonGroup>
+        </div>
+        <Typography gutterBottom>Exclusive Selection</Typography>
+        <Typography>
+          Text justification toggle buttons present options for left, right, center, full, and
+          justified text with only one item available for selection at a time. Selecting one option
+          deselects any other.
+        </Typography>
+      </Grid>
+      <Grid item sm={12} md={6}>
+        <div className={classes.toggleContainer}>
+          <ToggleButtonGroup value={formats} onChange={handleFormat}>
+            <ToggleButton value="bold">
+              <FormatBoldIcon />
+            </ToggleButton>
+            <ToggleButton value="italic">
+              <FormatItalicIcon />
+            </ToggleButton>
+            <ToggleButton value="underlined">
+              <FormatUnderlinedIcon />
+            </ToggleButton>
+            <ToggleButton disabled value="color">
+              <FormatColorFillIcon />
+              <ArrowDropDownIcon />
+            </ToggleButton>
+          </ToggleButtonGroup>
+        </div>
+        <Typography gutterBottom>Multiple Selection</Typography>
+        <Typography>
+          Logically-grouped options, like Bold, Italic, and Underline, allow multiple options to be
+          selected.
+        </Typography>
       </Grid>
-    );
-  }
+    </Grid>
+  );
 }
-
-ToggleButtons.propTypes = {
-  classes: PropTypes.object.isRequired,
-};
-
-export default withStyles(styles)(ToggleButtons);
diff --git a/docs/src/pages/components/toggle-button/toggle-button.md b/docs/src/pages/components/toggle-button/toggle-button.md
--- a/docs/src/pages/components/toggle-button/toggle-button.md
+++ b/docs/src/pages/components/toggle-button/toggle-button.md
@@ -14,3 +14,9 @@ The `ToggleButtonGroup` will control the selected of its child buttons when
 given its own `value` prop.
 
 {{"demo": "pages/components/toggle-button/ToggleButtons.js"}}
+
+## Sizes
+
+Fancy larger or smaller buttons? Use the `size` property.
+
+{{"demo": "pages/components/toggle-button/ToggleButtonSizes.js"}}
diff --git a/packages/material-ui-lab/src/ToggleButton/ToggleButton.js b/packages/material-ui-lab/src/ToggleButton/ToggleButton.js
--- a/packages/material-ui-lab/src/ToggleButton/ToggleButton.js
+++ b/packages/material-ui-lab/src/ToggleButton/ToggleButton.js
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
 import clsx from 'clsx';
 import { fade, withStyles } from '@material-ui/core/styles';
 import ButtonBase from '@material-ui/core/ButtonBase';
+import { capitalize } from '@material-ui/core/utils/helpers';
 
 export const styles = theme => ({
   /* Styles applied to the root element. */
@@ -12,7 +13,7 @@ export const styles = theme => ({
     ...theme.typography.button,
     boxSizing: 'border-box',
     height: 48,
-    minWidth: 48,
+    minWidth: 49,
     padding: '0px 11px 0px 12px',
     border: `1px solid ${fade(theme.palette.action.active, 0.12)}`,
     color: fade(theme.palette.action.active, 0.38),
@@ -66,6 +67,18 @@ export const styles = theme => ({
     alignItems: 'inherit',
     justifyContent: 'inherit',
   },
+  /* Styles applied to the root element if `size="small"`. */
+  sizeSmall: {
+    height: 40,
+    minWidth: 41,
+    fontSize: theme.typography.pxToRem(13),
+  },
+  /* Styles applied to the root element if `size="large"`. */
+  sizeLarge: {
+    height: 56,
+    minWidth: 57,
+    fontSize: theme.typography.pxToRem(15),
+  },
 });
 
 const ToggleButton = React.forwardRef(function ToggleButton(props, ref) {
@@ -78,6 +91,7 @@ const ToggleButton = React.forwardRef(function ToggleButton(props, ref) {
     onChange,
     onClick,
     selected,
+    size,
     value,
     ...other
   } = props;
@@ -102,6 +116,7 @@ const ToggleButton = React.forwardRef(function ToggleButton(props, ref) {
         {
           [classes.disabled]: disabled,
           [classes.selected]: selected,
+          [classes[`size${capitalize(size)}`]]: size !== 'medium',
         },
         className,
       )}
@@ -157,6 +172,10 @@ ToggleButton.propTypes = {
    * If `true`, the button will be rendered in an active state.
    */
   selected: PropTypes.bool,
+  /**
+   * @ignore
+   */
+  size: PropTypes.oneOf(['small', 'medium', 'large']),
   /**
    * The value to associate with the button when selected in a
    * ToggleButtonGroup.
@@ -168,6 +187,7 @@ ToggleButton.defaultProps = {
   disabled: false,
   disableFocusRipple: false,
   disableRipple: false,
+  size: 'medium',
 };
 
 export default withStyles(styles, { name: 'MuiToggleButton' })(ToggleButton);
diff --git a/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.js b/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.js
--- a/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.js
+++ b/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.js
@@ -15,7 +15,7 @@ export const styles = theme => ({
 });
 
 const ToggleButtonGroup = React.forwardRef(function ToggleButton(props, ref) {
-  const { children, className, classes, exclusive, onChange, value, ...other } = props;
+  const { children, className, classes, exclusive, onChange, size, value, ...other } = props;
 
   const handleChange = (event, buttonValue) => {
     if (!onChange) {
@@ -65,6 +65,7 @@ const ToggleButtonGroup = React.forwardRef(function ToggleButton(props, ref) {
         return React.cloneElement(child, {
           selected,
           onChange: exclusive ? handleExclusiveChange : handleChange,
+          size,
         });
       })}
     </div>
@@ -98,6 +99,10 @@ ToggleButtonGroup.propTypes = {
    * is selected and `exclusive` is true the value is null; when false an empty array.
    */
   onChange: PropTypes.func,
+  /**
+   * The size of the buttons.
+   */
+  size: PropTypes.oneOf(['small', 'medium', 'large']),
   /**
    * The currently selected value within the group or an array of selected
    * values when `exclusive` is false.
@@ -107,6 +112,7 @@ ToggleButtonGroup.propTypes = {
 
 ToggleButtonGroup.defaultProps = {
   exclusive: false,
+  size: 'medium',
 };
 
 export default withStyles(styles, { name: 'MuiToggleButtonGroup' })(ToggleButtonGroup);
diff --git a/pages/api/toggle-button-group.md b/pages/api/toggle-button-group.md
--- a/pages/api/toggle-button-group.md
+++ b/pages/api/toggle-button-group.md
@@ -22,6 +22,7 @@ import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
 | <span class="prop-name">classes</span> | <span class="prop-type">object</span> |  | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
 | <span class="prop-name">exclusive</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, only allow one of the child ToggleButton values to be selected. |
 | <span class="prop-name">onChange</span> | <span class="prop-type">func</span> |  | Callback fired when the value changes.<br><br>**Signature:**<br>`function(event: object, value: object) => void`<br>*event:* The event source of the callback<br>*value:* of the selected buttons. When `exclusive` is true this is a single value; when false an array of selected values. If no value is selected and `exclusive` is true the value is null; when false an empty array. |
+| <span class="prop-name">size</span> | <span class="prop-type">enum:&nbsp;'small'&nbsp;&#124;<br>&nbsp;'medium'&nbsp;&#124;<br>&nbsp;'large'<br></span> | <span class="prop-default">'medium'</span> | The size of the buttons. |
 | <span class="prop-name">value</span> | <span class="prop-type">any</span> |  | The currently selected value within the group or an array of selected values when `exclusive` is false. |
 
 The component cannot hold a ref.
diff --git a/pages/api/toggle-button.md b/pages/api/toggle-button.md
--- a/pages/api/toggle-button.md
+++ b/pages/api/toggle-button.md
@@ -42,6 +42,8 @@ This property accepts the following keys:
 | <span class="prop-name">disabled</span> | Styles applied to the root element if `disabled={true}`.
 | <span class="prop-name">selected</span> | Styles applied to the root element if `selected={true}`.
 | <span class="prop-name">label</span> | Styles applied to the `label` wrapper element.
+| <span class="prop-name">sizeSmall</span> | Styles applied to the root element if `size="small"`.
+| <span class="prop-name">sizeLarge</span> | Styles applied to the root element if `size="large"`.
 
 Have a look at the [overriding styles with classes](/customization/components/#overriding-styles-with-classes) section
 and the [implementation of the component](https://github.com/mui-org/material-ui/blob/next/packages/material-ui-lab/src/ToggleButton/ToggleButton.js)


Test patch:
diff --git a/packages/material-ui-lab/src/ToggleButton/ToggleButton.test.js b/packages/material-ui-lab/src/ToggleButton/ToggleButton.test.js
--- a/packages/material-ui-lab/src/ToggleButton/ToggleButton.test.js
+++ b/packages/material-ui-lab/src/ToggleButton/ToggleButton.test.js
@@ -67,6 +67,30 @@ describe('<ToggleButton />', () => {
     assert.strictEqual(root.is('button[disabled]'), true);
   });
 
+  it('should render a small button', () => {
+    const wrapper = mount(
+      <ToggleButton size="small" value="hello">
+        Hello World
+      </ToggleButton>,
+    );
+    const root = findOutermostIntrinsic(wrapper);
+    assert.strictEqual(root.hasClass(classes.root), true);
+    assert.strictEqual(root.hasClass(classes.sizeSmall), true);
+    assert.strictEqual(root.hasClass(classes.sizeLarge), false);
+  });
+
+  it('should render a large button', () => {
+    const wrapper = mount(
+      <ToggleButton size="large" value="hello">
+        Hello World
+      </ToggleButton>,
+    );
+    const root = findOutermostIntrinsic(wrapper);
+    assert.strictEqual(root.hasClass(classes.root), true);
+    assert.strictEqual(root.hasClass(classes.sizeSmall), false);
+    assert.strictEqual(root.hasClass(classes.sizeLarge), true);
+  });
+
   describe('prop: onChange', () => {
     it('should be called when clicked', () => {
       const handleChange = spy();