Repository: mui/material-ui (TypeScript)
Date: 2019-01-08 22:52:35+00:00
Task type: Feature

Issue:
Badge bug
Badge bug. Width badge is not chenged for size content. I fix with my style.
![default](https://user-images.githubusercontent.com/23016311/50738089-fdca1c00-11d8-11e9-8c2c-cc6de0622ec7.PNG)
![1](https://user-images.githubusercontent.com/23016311/50738090-fdca1c00-11d8-11e9-93b0-cfcedf6f6d08.PNG)




Issue discussion:
@maystrenkoYurii Thank you for sharing this problem. I agree. I think that we can apply the following changes:
```diff
@@ -5,7 +5,7 @@ import { componentPropType } from '@material-ui/utils';
 import withStyles from '../styles/withStyles';
 import { capitalize } from '../utils/helpers';

-const RADIUS = 11;
+const RADIUS = 10;

 export const styles = theme => ({
   /* Styles applied to the root element. */
@@ -24,22 +24,23 @@ export const styles = theme => ({
     alignContent: 'center',
     alignItems: 'center',
     position: 'absolute',
-    top: -RADIUS,
-    right: -RADIUS,
+    top: 0,
+    right: 0,
     fontFamily: theme.typography.fontFamily,
-    fontWeight: theme.typography.fontWeight,
+    fontWeight: theme.typography.fontWeightMedium,
     fontSize: theme.typography.pxToRem(12),
-    width: RADIUS * 2,
+    minWidth: RADIUS * 2,
+    padding: '0 4px',
     height: RADIUS * 2,
-    borderRadius: '50%',
+    borderRadius: RADIUS,
     backgroundColor: theme.palette.color,
     color: theme.palette.textColor,
     zIndex: 1, // Render the badge on top of potential ripples.
+    transform: 'scale(1) translate(50%, -50%)',
     transition: theme.transitions.create('transform', {
       easing: theme.transitions.easing.easeInOut,
       duration: theme.transitions.duration.enteringScreen,
     }),
-    transform: 'scale(1)',
   },
   /* Styles applied to the root element if `color="primary"`. */
   colorPrimary: {
```

![capture d ecran 2019-01-06 a 23 49 04](https://user-images.githubusercontent.com/3165635/50742587-b447f400-120d-11e9-8fa6-fe7e683b8542.png)

@joshwooding Given you have already looked at it, what do you propose?
Ok, thanks, good you propose))
I'll pick this up 👍 

Solution patch:
diff --git a/.size-limit.js b/.size-limit.js
--- a/.size-limit.js
+++ b/.size-limit.js
@@ -22,7 +22,7 @@ module.exports = [
     name: 'The size of the @material-ui/core modules',
     webpack: true,
     path: 'packages/material-ui/build/index.js',
-    limit: '96 KB',
+    limit: '96.2 KB',
   },
   {
     name: 'The size of the @material-ui/styles modules',
diff --git a/docs/src/pages/demos/badges/BadgeMax.js b/docs/src/pages/demos/badges/BadgeMax.js
new file mode 100644
--- /dev/null
+++ b/docs/src/pages/demos/badges/BadgeMax.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withStyles } from '@material-ui/core/styles';
+import Badge from '@material-ui/core/Badge';
+import MailIcon from '@material-ui/icons/Mail';
+
+const styles = theme => ({
+  margin: {
+    margin: theme.spacing.unit * 2,
+    marginRight: theme.spacing.unit * 3,
+  },
+});
+
+function BadgeMax(props) {
+  const { classes } = props;
+
+  return (
+    <React.Fragment>
+      <Badge className={classes.margin} badgeContent={99} color="primary">
+        <MailIcon />
+      </Badge>
+      <Badge className={classes.margin} badgeContent={100} color="primary">
+        <MailIcon />
+      </Badge>
+      <Badge className={classes.margin} badgeContent={1000} max={999} color="primary">
+        <MailIcon />
+      </Badge>
+    </React.Fragment>
+  );
+}
+
+BadgeMax.propTypes = {
+  classes: PropTypes.object.isRequired,
+};
+
+export default withStyles(styles)(BadgeMax);
diff --git a/docs/src/pages/demos/badges/BadgeVisibility.hooks.js b/docs/src/pages/demos/badges/BadgeVisibility.hooks.js
--- a/docs/src/pages/demos/badges/BadgeVisibility.hooks.js
+++ b/docs/src/pages/demos/badges/BadgeVisibility.hooks.js
@@ -5,16 +5,24 @@ import MailIcon from '@material-ui/icons/Mail';
 import Switch from '@material-ui/core/Switch';
 import FormGroup from '@material-ui/core/FormGroup';
 import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Divider from '@material-ui/core/Divider';
 
 const useStyles = makeStyles(theme => ({
   root: {
     display: 'flex',
     flexDirection: 'column',
     alignItems: 'center',
+    width: '100%',
   },
   margin: {
     margin: theme.spacing.unit,
   },
+  divider: {
+    width: '100%',
+  },
+  row: {
+    marginTop: theme.spacing.unit * 2,
+  },
 }));
 
 function BadgeVisibility() {
@@ -27,8 +35,11 @@ function BadgeVisibility() {
 
   return (
     <div className={classes.root}>
-      <div className={classes.margin}>
-        <Badge color="secondary" badgeContent={4} invisible={invisible}>
+      <div className={classes.row}>
+        <Badge color="secondary" badgeContent={4} invisible={invisible} className={classes.margin}>
+          <MailIcon />
+        </Badge>
+        <Badge color="secondary" variant="dot" invisible={invisible} className={classes.margin}>
           <MailIcon />
         </Badge>
       </div>
@@ -38,6 +49,15 @@ function BadgeVisibility() {
           label="Show Badge"
         />
       </FormGroup>
+      <Divider className={classes.divider} />
+      <div className={classes.row}>
+        <Badge color="secondary" badgeContent={0} className={classes.margin}>
+          <MailIcon />
+        </Badge>
+        <Badge color="secondary" badgeContent={0} showZero className={classes.margin}>
+          <MailIcon />
+        </Badge>
+      </div>
     </div>
   );
 }
diff --git a/docs/src/pages/demos/badges/BadgeVisibility.js b/docs/src/pages/demos/badges/BadgeVisibility.js
--- a/docs/src/pages/demos/badges/BadgeVisibility.js
+++ b/docs/src/pages/demos/badges/BadgeVisibility.js
@@ -6,16 +6,24 @@ import MailIcon from '@material-ui/icons/Mail';
 import Switch from '@material-ui/core/Switch';
 import FormGroup from '@material-ui/core/FormGroup';
 import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Divider from '@material-ui/core/Divider';
 
 const styles = theme => ({
   root: {
     display: 'flex',
     flexDirection: 'column',
     alignItems: 'center',
+    width: '100%',
   },
   margin: {
     margin: theme.spacing.unit,
   },
+  divider: {
+    width: '100%',
+  },
+  row: {
+    marginTop: theme.spacing.unit * 2,
+  },
 });
 
 class BadgeVisibility extends React.Component {
@@ -33,8 +41,16 @@ class BadgeVisibility extends React.Component {
 
     return (
       <div className={classes.root}>
-        <div className={classes.margin}>
-          <Badge color="secondary" badgeContent={4} invisible={invisible}>
+        <div className={classes.row}>
+          <Badge
+            color="secondary"
+            badgeContent={4}
+            invisible={invisible}
+            className={classes.margin}
+          >
+            <MailIcon />
+          </Badge>
+          <Badge color="secondary" variant="dot" invisible={invisible} className={classes.margin}>
             <MailIcon />
           </Badge>
         </div>
@@ -46,6 +62,15 @@ class BadgeVisibility extends React.Component {
             label="Show Badge"
           />
         </FormGroup>
+        <Divider className={classes.divider} />
+        <div className={classes.row}>
+          <Badge color="secondary" badgeContent={0} className={classes.margin}>
+            <MailIcon />
+          </Badge>
+          <Badge color="secondary" badgeContent={0} showZero className={classes.margin}>
+            <MailIcon />
+          </Badge>
+        </div>
       </div>
     );
   }
diff --git a/docs/src/pages/demos/badges/CustomizedBadge.js b/docs/src/pages/demos/badges/CustomizedBadge.js
--- a/docs/src/pages/demos/badges/CustomizedBadge.js
+++ b/docs/src/pages/demos/badges/CustomizedBadge.js
@@ -7,8 +7,8 @@ import ShoppingCartIcon from '@material-ui/icons/ShoppingCart';
 
 const styles = theme => ({
   badge: {
-    top: 1,
-    right: -15,
+    top: '50%',
+    right: -3,
     // The border color match the background color.
     border: `2px solid ${
       theme.palette.type === 'light' ? theme.palette.grey[200] : theme.palette.grey[900]
diff --git a/docs/src/pages/demos/badges/DotBadge.js b/docs/src/pages/demos/badges/DotBadge.js
new file mode 100644
--- /dev/null
+++ b/docs/src/pages/demos/badges/DotBadge.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withStyles } from '@material-ui/core/styles';
+import Badge from '@material-ui/core/Badge';
+import MailIcon from '@material-ui/icons/Mail';
+import Typography from '@material-ui/core/Typography';
+
+const styles = theme => ({
+  margin: {
+    margin: theme.spacing.unit * 2,
+  },
+});
+
+function DotBadge(props) {
+  const { classes } = props;
+
+  return (
+    <div>
+      <div>
+        <Badge className={classes.margin} color="primary" variant="dot">
+          <MailIcon />
+        </Badge>
+        <Badge className={classes.margin} color="secondary" variant="dot">
+          <MailIcon />
+        </Badge>
+      </div>
+      <Badge color="primary" className={classes.margin} variant="dot">
+        <Typography>Typography</Typography>
+      </Badge>
+    </div>
+  );
+}
+
+DotBadge.propTypes = {
+  classes: PropTypes.object.isRequired,
+};
+
+export default withStyles(styles)(DotBadge);
diff --git a/docs/src/pages/demos/badges/badges.md b/docs/src/pages/demos/badges/badges.md
--- a/docs/src/pages/demos/badges/badges.md
+++ b/docs/src/pages/demos/badges/badges.md
@@ -13,10 +13,24 @@ Examples of badges containing text, using primary and secondary colors. The badg
 
 {{"demo": "pages/demos/badges/SimpleBadge.js"}}
 
+## Maximum Value
+
+You can use the `max` property to cap the value of the badge content.
+
+{{"demo": "pages/demos/badges/BadgeMax.js"}}
+
+## Dot Badge
+
+The `dot` property changes a badge into a small dot. This can be used as a notification that something has changed without giving a count.
+
+{{"demo": "pages/demos/badges/DotBadge.js"}}
+
 ## Badge visibility
 
 The visibility of badges can be controlled using the `invisible` property.
 
+The badge auto hides with badgeContent is zero. You can override this with the `showZero` property.
+
 {{"demo": "pages/demos/badges/BadgeVisibility.js"}}
 
 ## Customized Badge
diff --git a/packages/material-ui/src/Badge/Badge.d.ts b/packages/material-ui/src/Badge/Badge.d.ts
--- a/packages/material-ui/src/Badge/Badge.d.ts
+++ b/packages/material-ui/src/Badge/Badge.d.ts
@@ -8,9 +8,18 @@ export interface BadgeProps
   color?: PropTypes.Color | 'error';
   component?: React.ReactType<BadgeProps>;
   invisible?: boolean;
+  max?: number;
+  showZero?: boolean;
+  variant?: 'standard' | 'dot';
 }
 
-export type BadgeClassKey = 'root' | 'badge' | 'colorPrimary' | 'colorSecondary' | 'invisible';
+export type BadgeClassKey =
+  | 'root'
+  | 'badge'
+  | 'colorPrimary'
+  | 'colorSecondary'
+  | 'invisible'
+  | 'dot';
 
 declare const Badge: React.ComponentType<BadgeProps>;
 
diff --git a/packages/material-ui/src/Badge/Badge.js b/packages/material-ui/src/Badge/Badge.js
--- a/packages/material-ui/src/Badge/Badge.js
+++ b/packages/material-ui/src/Badge/Badge.js
@@ -5,7 +5,7 @@ import { componentPropType } from '@material-ui/utils';
 import withStyles from '../styles/withStyles';
 import { capitalize } from '../utils/helpers';
 
-const RADIUS = 11;
+const RADIUS = 10;
 
 export const styles = theme => ({
   /* Styles applied to the root element. */
@@ -24,22 +24,25 @@ export const styles = theme => ({
     alignContent: 'center',
     alignItems: 'center',
     position: 'absolute',
-    top: -RADIUS,
-    right: -RADIUS,
+    top: 0,
+    right: 0,
+    boxSizing: 'border-box',
     fontFamily: theme.typography.fontFamily,
-    fontWeight: theme.typography.fontWeight,
+    fontWeight: theme.typography.fontWeightMedium,
     fontSize: theme.typography.pxToRem(12),
-    width: RADIUS * 2,
+    minWidth: RADIUS * 2,
+    padding: '0 4px',
     height: RADIUS * 2,
-    borderRadius: '50%',
+    borderRadius: RADIUS,
     backgroundColor: theme.palette.color,
     color: theme.palette.textColor,
     zIndex: 1, // Render the badge on top of potential ripples.
+    transform: 'scale(1) translate(50%, -50%)',
+    transformOrigin: '100% 0%',
     transition: theme.transitions.create('transform', {
       easing: theme.transitions.easing.easeInOut,
       duration: theme.transitions.duration.enteringScreen,
     }),
-    transform: 'scale(1)',
   },
   /* Styles applied to the root element if `color="primary"`. */
   colorPrimary: {
@@ -62,7 +65,14 @@ export const styles = theme => ({
       easing: theme.transitions.easing.easeInOut,
       duration: theme.transitions.duration.leavingScreen,
     }),
-    transform: 'scale(0)',
+    transform: 'scale(0) translate(50%, -50%)',
+    transformOrigin: '100% 0%',
+  },
+  /* Styles applied to the root element if `variant="dot"`. */
+  dot: {
+    height: 6,
+    minWidth: 6,
+    padding: 0,
   },
 });
 
@@ -74,19 +84,34 @@ function Badge(props) {
     className,
     color,
     component: ComponentProp,
-    invisible,
+    invisible: invisibleProp,
+    showZero,
+    max,
+    variant,
     ...other
   } = props;
 
+  let invisible = invisibleProp;
+
+  if (invisibleProp == null && Number(badgeContent) === 0 && !showZero) {
+    invisible = true;
+  }
+
   const badgeClassName = classNames(classes.badge, {
     [classes[`color${capitalize(color)}`]]: color !== 'default',
     [classes.invisible]: invisible,
+    [classes.dot]: variant === 'dot',
   });
+  let displayValue = '';
+
+  if (variant !== 'dot') {
+    displayValue = badgeContent > max ? `${max}+` : badgeContent;
+  }
 
   return (
     <ComponentProp className={classNames(classes.root, className)} {...other}>
       {children}
-      <span className={badgeClassName}>{badgeContent}</span>
+      <span className={badgeClassName}>{displayValue}</span>
     </ComponentProp>
   );
 }
@@ -95,7 +120,7 @@ Badge.propTypes = {
   /**
    * The content rendered within the badge.
    */
-  badgeContent: PropTypes.node.isRequired,
+  badgeContent: PropTypes.node,
   /**
    * The badge will be added relative to this node.
    */
@@ -122,12 +147,26 @@ Badge.propTypes = {
    * If `true`, the badge will be invisible.
    */
   invisible: PropTypes.bool,
+  /**
+   * Max count to show.
+   */
+  max: PropTypes.number,
+  /**
+   * Controls whether the badge is hidden when `badgeContent` is zero.
+   */
+  showZero: PropTypes.bool,
+  /**
+   * The variant to use.
+   */
+  variant: PropTypes.oneOf(['standard', 'dot']),
 };
 
 Badge.defaultProps = {
   color: 'default',
   component: 'span',
-  invisible: false,
+  max: 99,
+  showZero: false,
+  variant: 'standard',
 };
 
 export default withStyles(styles, { name: 'MuiBadge' })(Badge);
diff --git a/pages/api/badge.md b/pages/api/badge.md
--- a/pages/api/badge.md
+++ b/pages/api/badge.md
@@ -18,12 +18,15 @@ import Badge from '@material-ui/core/Badge';
 
 | Name | Type | Default | Description |
 |:-----|:-----|:--------|:------------|
-| <span class="prop-name required">badgeContent *</span> | <span class="prop-type">node</span> |   | The content rendered within the badge. |
+| <span class="prop-name">badgeContent</span> | <span class="prop-type">node</span> |   | The content rendered within the badge. |
 | <span class="prop-name required">children *</span> | <span class="prop-type">node</span> |   | The badge will be added relative to this node. |
 | <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-api) below for more details. |
 | <span class="prop-name">color</span> | <span class="prop-type">enum:&nbsp;'default'&nbsp;&#124;<br>&nbsp;'primary'&nbsp;&#124;<br>&nbsp;'secondary'&nbsp;&#124;<br>&nbsp;'error'<br></span> | <span class="prop-default">'default'</span> | The color of the component. It supports those theme colors that make sense for this component. |
 | <span class="prop-name">component</span> | <span class="prop-type">componentPropType</span> | <span class="prop-default">'span'</span> | The component used for the root node. Either a string to use a DOM element or a component. |
-| <span class="prop-name">invisible</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the badge will be invisible. |
+| <span class="prop-name">invisible</span> | <span class="prop-type">bool</span> |   | If `true`, the badge will be invisible. |
+| <span class="prop-name">max</span> | <span class="prop-type">number</span> | <span class="prop-default">99</span> | Max count to show. |
+| <span class="prop-name">showZero</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Controls whether the badge is hidden when `badgeContent` is zero. |
+| <span class="prop-name">variant</span> | <span class="prop-type">enum:&nbsp;'standard'&nbsp;&#124;<br>&nbsp;'dot'<br></span> | <span class="prop-default">'standard'</span> | The variant to use. |
 
 Any other properties supplied will be spread to the root element (native element).
 
@@ -41,6 +44,7 @@ This property accepts the following keys:
 | <span class="prop-name">colorSecondary</span> | Styles applied to the root element if `color="secondary"`.
 | <span class="prop-name">colorError</span> | Styles applied to the root element if `color="error"`.
 | <span class="prop-name">invisible</span> | Styles applied to the badge `span` element if `invisible={true}`.
+| <span class="prop-name">dot</span> | Styles applied to the root element if `variant="dot"`.
 
 Have a look at [overriding with classes](/customization/overrides/#overriding-with-classes) section
 and the [implementation of the component](https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Badge/Badge.js)


Test patch:
diff --git a/packages/material-ui/src/Badge/Badge.test.js b/packages/material-ui/src/Badge/Badge.test.js
--- a/packages/material-ui/src/Badge/Badge.test.js
+++ b/packages/material-ui/src/Badge/Badge.test.js
@@ -163,4 +163,213 @@ describe('<Badge />', () => {
       );
     });
   });
+
+  describe('prop: showZero', () => {
+    it('should default to false', () => {
+      const wrapper = shallow(<Badge badgeContent={0}>{testChildren}</Badge>);
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.invisible),
+        true,
+      );
+    });
+
+    it('should render without the invisible class when false and badgeContent is not 0', () => {
+      const wrapper = shallow(
+        <Badge badgeContent={10} showZero>
+          {testChildren}
+        </Badge>,
+      );
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.invisible),
+        false,
+      );
+    });
+
+    it('should render without the invisible class when true and badgeContent is 0', () => {
+      const wrapper = shallow(
+        <Badge badgeContent={0} showZero>
+          {testChildren}
+        </Badge>,
+      );
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.invisible),
+        false,
+      );
+    });
+
+    it('should render with the invisible class when false and badgeContent is 0', () => {
+      const wrapper = shallow(
+        <Badge badgeContent={0} showZero={false}>
+          {testChildren}
+        </Badge>,
+      );
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.invisible),
+        true,
+      );
+    });
+  });
+
+  describe('prop: variant', () => {
+    it('should default to standard', () => {
+      const wrapper = shallow(<Badge badgeContent={10}>{testChildren}</Badge>);
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.badge),
+        true,
+      );
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.dot),
+        false,
+      );
+    });
+
+    it('should render without the standard class when variant="standard"', () => {
+      const wrapper = shallow(
+        <Badge badgeContent={10} variant="standard">
+          {testChildren}
+        </Badge>,
+      );
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.badge),
+        true,
+      );
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.dot),
+        false,
+      );
+    });
+
+    if (
+      ('should not render badgeContent',
+      () => {
+        const wrapper = shallow(
+          <Badge badgeContent={10} variant="dot">
+            {testChildren}
+          </Badge>,
+        );
+
+        assert.strictEqual(
+          wrapper
+            .find('span')
+            .at(1)
+            .text(),
+          '',
+        );
+      })
+    );
+
+    it('should render with the dot class when variant="dot"', () => {
+      const wrapper = shallow(
+        <Badge badgeContent={10} variant="dot">
+          {testChildren}
+        </Badge>,
+      );
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.badge),
+        true,
+      );
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .hasClass(classes.dot),
+        true,
+      );
+    });
+  });
+
+  describe('prop: max', () => {
+    it('should default to 99', () => {
+      const wrapper = shallow(<Badge badgeContent={100}>{testChildren}</Badge>);
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .text(),
+        '99+',
+      );
+    });
+
+    it('should cap badgeContent', () => {
+      const wrapper = shallow(
+        <Badge badgeContent={1000} max={999}>
+          {testChildren}
+        </Badge>,
+      );
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .text(),
+        '999+',
+      );
+    });
+
+    it('should not cap if badgeContent and max are equal', () => {
+      const wrapper = shallow(
+        <Badge badgeContent={1000} max={1000}>
+          {testChildren}
+        </Badge>,
+      );
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .text(),
+        '1000',
+      );
+    });
+
+    it('should not cap if badgeContent is lower than max', () => {
+      const wrapper = shallow(
+        <Badge badgeContent={50} max={1000}>
+          {testChildren}
+        </Badge>,
+      );
+
+      assert.strictEqual(
+        wrapper
+          .find('span')
+          .at(1)
+          .text(),
+        '50',
+      );
+    });
+  });
 });