From 322565685dc4bdc413b16d58aed2f73fd48791e2 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 7 Aug 2020 14:21:01 +0200 Subject: [PATCH] [Badge] Add overlap circular and rectangular (#22076) --- docs/pages/api-docs/badge.md | 10 +- docs/src/modules/components/ThemeContext.js | 6 + .../pages/components/avatars/BadgeAvatars.js | 4 +- .../pages/components/avatars/BadgeAvatars.tsx | 4 +- .../pages/components/badges/BadgeOverlap.js | 4 +- .../pages/components/badges/BadgeOverlap.tsx | 4 +- packages/material-ui/src/Badge/Badge.d.ts | 9 +- packages/material-ui/src/Badge/Badge.js | 131 ++++++++++++++- packages/material-ui/src/Badge/Badge.test.js | 157 ++++++++++++++++++ 9 files changed, 317 insertions(+), 12 deletions(-) diff --git a/docs/pages/api-docs/badge.md b/docs/pages/api-docs/badge.md index b181a89f3da2f4..595c360bf4d2eb 100644 --- a/docs/pages/api-docs/badge.md +++ b/docs/pages/api-docs/badge.md @@ -36,7 +36,7 @@ The `MuiBadge` name can be used for providing [default props](/customization/glo | component | elementType | 'span' | The component used for the root node. Either a string to use a HTML element or a component. | | invisible | bool | | If `true`, the badge will be invisible. | | max | number | 99 | Max count to show. | -| overlap | 'circle'
| 'rectangle'
| 'rectangle' | Wrapped shape the badge should overlap. | +| overlap | 'circle'
| 'rectangle'
| 'circular'
| 'rectangular'
| 'rectangle' | Wrapped shape the badge should overlap. | | showZero | bool | false | Controls whether the badge is hidden when `badgeContent` is zero. | | variant | 'dot'
| 'standard'
| 'standard' | The variant to use. | @@ -55,13 +55,21 @@ Any other props supplied will be provided to the root element (native element). | colorError | .MuiBadge-colorError | Styles applied to the root element if `color="error"`. | dot | .MuiBadge-dot | Styles applied to the root element if `variant="dot"`. | anchorOriginTopRightRectangle | .MuiBadge-anchorOriginTopRightRectangle | Styles applied to the root element if `anchorOrigin={{ 'top', 'right' }} overlap="rectangle"`. +| anchorOriginTopRightRectangular | .MuiBadge-anchorOriginTopRightRectangular | Styles applied to the root element if `anchorOrigin={{ 'top', 'right' }} overlap="rectangular"`. | anchorOriginBottomRightRectangle | .MuiBadge-anchorOriginBottomRightRectangle | Styles applied to the root element if `anchorOrigin={{ 'bottom', 'right' }} overlap="rectangle"`. +| anchorOriginBottomRightRectangular | .MuiBadge-anchorOriginBottomRightRectangular | Styles applied to the root element if `anchorOrigin={{ 'bottom', 'right' }} overlap="rectangular"`. | anchorOriginTopLeftRectangle | .MuiBadge-anchorOriginTopLeftRectangle | Styles applied to the root element if `anchorOrigin={{ 'top', 'left' }} overlap="rectangle"`. +| anchorOriginTopLeftRectangular | .MuiBadge-anchorOriginTopLeftRectangular | Styles applied to the root element if `anchorOrigin={{ 'top', 'left' }} overlap="rectangular"`. | anchorOriginBottomLeftRectangle | .MuiBadge-anchorOriginBottomLeftRectangle | Styles applied to the root element if `anchorOrigin={{ 'bottom', 'left' }} overlap="rectangle"`. +| anchorOriginBottomLeftRectangular | .MuiBadge-anchorOriginBottomLeftRectangular | Styles applied to the root element if `anchorOrigin={{ 'bottom', 'left' }} overlap="rectangular"`. | anchorOriginTopRightCircle | .MuiBadge-anchorOriginTopRightCircle | Styles applied to the root element if `anchorOrigin={{ 'top', 'right' }} overlap="circle"`. +| anchorOriginTopRightCircular | .MuiBadge-anchorOriginTopRightCircular | Styles applied to the root element if `anchorOrigin={{ 'top', 'right' }} overlap="circular"`. | anchorOriginBottomRightCircle | .MuiBadge-anchorOriginBottomRightCircle | Styles applied to the root element if `anchorOrigin={{ 'bottom', 'right' }} overlap="circle"`. +| anchorOriginBottomRightCircular | .MuiBadge-anchorOriginBottomRightCircular | Styles applied to the root element if `anchorOrigin={{ 'bottom', 'right' }} overlap="circular"`. | anchorOriginTopLeftCircle | .MuiBadge-anchorOriginTopLeftCircle | Styles applied to the root element if `anchorOrigin={{ 'top', 'left' }} overlap="circle"`. +| anchorOriginTopLeftCircular | .MuiBadge-anchorOriginTopLeftCircular | Styles applied to the root element if `anchorOrigin={{ 'top', 'left' }} overlap="circular"`. | anchorOriginBottomLeftCircle | .MuiBadge-anchorOriginBottomLeftCircle | Styles applied to the root element if `anchorOrigin={{ 'bottom', 'left' }} overlap="circle"`. +| anchorOriginBottomLeftCircular | .MuiBadge-anchorOriginBottomLeftCircular | Styles applied to the root element if `anchorOrigin={{ 'bottom', 'left' }} overlap="circular"`. | invisible | .MuiBadge-invisible | Pseudo-class to the badge `span` element if `invisible={true}`. You can override the style of the component thanks to one of these customization points: diff --git a/docs/src/modules/components/ThemeContext.js b/docs/src/modules/components/ThemeContext.js index 08eacbe30b05fd..fee17252f0b868 100644 --- a/docs/src/modules/components/ThemeContext.js +++ b/docs/src/modules/components/ThemeContext.js @@ -203,6 +203,12 @@ export function ThemeProvider(props) { }, ...paletteColors, }, + // v5 migration + props: { + MuiBadge: { + overlap: 'rectangular', + }, + }, spacing, }, dense ? highDensity : null, diff --git a/docs/src/pages/components/avatars/BadgeAvatars.js b/docs/src/pages/components/avatars/BadgeAvatars.js index 3628f06eaa70ff..e047ffbf3584d1 100644 --- a/docs/src/pages/components/avatars/BadgeAvatars.js +++ b/docs/src/pages/components/avatars/BadgeAvatars.js @@ -55,7 +55,7 @@ export default function BadgeAvatars() { return (
{rectangle} - + {circle} - + {circle}
diff --git a/docs/src/pages/components/badges/BadgeOverlap.tsx b/docs/src/pages/components/badges/BadgeOverlap.tsx index 29669b01930e9c..cd0971147637c8 100644 --- a/docs/src/pages/components/badges/BadgeOverlap.tsx +++ b/docs/src/pages/components/badges/BadgeOverlap.tsx @@ -35,10 +35,10 @@ export default function BadgeOverlap() { {rectangle} - + {circle} - + {circle} diff --git a/packages/material-ui/src/Badge/Badge.d.ts b/packages/material-ui/src/Badge/Badge.d.ts index e30b1a0e6890dd..7602be86e1fa80 100644 --- a/packages/material-ui/src/Badge/Badge.d.ts +++ b/packages/material-ui/src/Badge/Badge.d.ts @@ -15,7 +15,7 @@ export interface BadgeTypeMap

{ /** * Wrapped shape the badge should overlap. */ - overlap?: 'rectangle' | 'circle'; + overlap?: 'rectangle' | 'circle' | 'rectangular' | 'circular'; /** * The content rendered within the badge. */ @@ -63,6 +63,13 @@ export type BadgeClassKey = | 'anchorOriginTopRightCircle' | 'anchorOriginBottomRightCircle' | 'anchorOriginTopLeftCircle' + | 'anchorOriginTopRightRectangular' + | 'anchorOriginBottomRightRectangular' + | 'anchorOriginTopLeftRectangular' + | 'anchorOriginBottomLeftRectangular' + | 'anchorOriginTopRightCircular' + | 'anchorOriginBottomRightCircular' + | 'anchorOriginTopLeftCircular' | 'invisible'; /** * diff --git a/packages/material-ui/src/Badge/Badge.js b/packages/material-ui/src/Badge/Badge.js index c52adb0bb1f9e3..032280ab67e8e9 100644 --- a/packages/material-ui/src/Badge/Badge.js +++ b/packages/material-ui/src/Badge/Badge.js @@ -1,6 +1,7 @@ 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 capitalize from '../utils/capitalize'; @@ -72,6 +73,16 @@ export const styles = (theme) => ({ transform: 'scale(0) translate(50%, -50%)', }, }, + /* Styles applied to the root element if `anchorOrigin={{ 'top', 'right' }} overlap="rectangular"`. */ + anchorOriginTopRightRectangular: { + top: 0, + right: 0, + transform: 'scale(1) translate(50%, -50%)', + transformOrigin: '100% 0%', + '&$invisible': { + transform: 'scale(0) translate(50%, -50%)', + }, + }, /* Styles applied to the root element if `anchorOrigin={{ 'bottom', 'right' }} overlap="rectangle"`. */ anchorOriginBottomRightRectangle: { bottom: 0, @@ -82,6 +93,16 @@ export const styles = (theme) => ({ transform: 'scale(0) translate(50%, 50%)', }, }, + /* Styles applied to the root element if `anchorOrigin={{ 'bottom', 'right' }} overlap="rectangular"`. */ + anchorOriginBottomRightRectangular: { + bottom: 0, + right: 0, + transform: 'scale(1) translate(50%, 50%)', + transformOrigin: '100% 100%', + '&$invisible': { + transform: 'scale(0) translate(50%, 50%)', + }, + }, /* Styles applied to the root element if `anchorOrigin={{ 'top', 'left' }} overlap="rectangle"`. */ anchorOriginTopLeftRectangle: { top: 0, @@ -92,6 +113,16 @@ export const styles = (theme) => ({ transform: 'scale(0) translate(-50%, -50%)', }, }, + /* Styles applied to the root element if `anchorOrigin={{ 'top', 'left' }} overlap="rectangular"`. */ + anchorOriginTopLeftRectangular: { + top: 0, + left: 0, + transform: 'scale(1) translate(-50%, -50%)', + transformOrigin: '0% 0%', + '&$invisible': { + transform: 'scale(0) translate(-50%, -50%)', + }, + }, /* Styles applied to the root element if `anchorOrigin={{ 'bottom', 'left' }} overlap="rectangle"`. */ anchorOriginBottomLeftRectangle: { bottom: 0, @@ -102,6 +133,16 @@ export const styles = (theme) => ({ transform: 'scale(0) translate(-50%, 50%)', }, }, + /* Styles applied to the root element if `anchorOrigin={{ 'bottom', 'left' }} overlap="rectangular"`. */ + anchorOriginBottomLeftRectangular: { + bottom: 0, + left: 0, + transform: 'scale(1) translate(-50%, 50%)', + transformOrigin: '0% 100%', + '&$invisible': { + transform: 'scale(0) translate(-50%, 50%)', + }, + }, /* Styles applied to the root element if `anchorOrigin={{ 'top', 'right' }} overlap="circle"`. */ anchorOriginTopRightCircle: { top: '14%', @@ -112,6 +153,16 @@ export const styles = (theme) => ({ transform: 'scale(0) translate(50%, -50%)', }, }, + /* Styles applied to the root element if `anchorOrigin={{ 'top', 'right' }} overlap="circular"`. */ + anchorOriginTopRightCircular: { + top: '14%', + right: '14%', + transform: 'scale(1) translate(50%, -50%)', + transformOrigin: '100% 0%', + '&$invisible': { + transform: 'scale(0) translate(50%, -50%)', + }, + }, /* Styles applied to the root element if `anchorOrigin={{ 'bottom', 'right' }} overlap="circle"`. */ anchorOriginBottomRightCircle: { bottom: '14%', @@ -122,6 +173,16 @@ export const styles = (theme) => ({ transform: 'scale(0) translate(50%, 50%)', }, }, + /* Styles applied to the root element if `anchorOrigin={{ 'bottom', 'right' }} overlap="circular"`. */ + anchorOriginBottomRightCircular: { + bottom: '14%', + right: '14%', + transform: 'scale(1) translate(50%, 50%)', + transformOrigin: '100% 100%', + '&$invisible': { + transform: 'scale(0) translate(50%, 50%)', + }, + }, /* Styles applied to the root element if `anchorOrigin={{ 'top', 'left' }} overlap="circle"`. */ anchorOriginTopLeftCircle: { top: '14%', @@ -132,6 +193,16 @@ export const styles = (theme) => ({ transform: 'scale(0) translate(-50%, -50%)', }, }, + /* Styles applied to the root element if `anchorOrigin={{ 'top', 'left' }} overlap="circular"`. */ + anchorOriginTopLeftCircular: { + top: '14%', + left: '14%', + transform: 'scale(1) translate(-50%, -50%)', + transformOrigin: '0% 0%', + '&$invisible': { + transform: 'scale(0) translate(-50%, -50%)', + }, + }, /* Styles applied to the root element if `anchorOrigin={{ 'bottom', 'left' }} overlap="circle"`. */ anchorOriginBottomLeftCircle: { bottom: '14%', @@ -142,6 +213,16 @@ export const styles = (theme) => ({ transform: 'scale(0) translate(-50%, 50%)', }, }, + /* Styles applied to the root element if `anchorOrigin={{ 'bottom', 'left' }} overlap="circular"`. */ + anchorOriginBottomLeftCircular: { + bottom: '14%', + left: '14%', + transform: 'scale(1) translate(-50%, 50%)', + transformOrigin: '0% 100%', + '&$invisible': { + transform: 'scale(0) translate(-50%, 50%)', + }, + }, /* Pseudo-class to the badge `span` element if `invisible={true}`. */ invisible: { transition: theme.transitions.create('transform', { @@ -235,7 +316,34 @@ Badge.propTypes = { * Override or extend the styles applied to the component. * See [CSS API](#css) below for more details. */ - classes: PropTypes.object, + classes: chainPropTypes(PropTypes.object, (props) => { + const { classes } = props; + if (classes == null) { + return null; + } + + [ + ['anchorOriginTopRightRectangle', 'anchorOriginTopRightRectangular'], + ['anchorOriginBottomRightRectangle', 'anchorOriginBottomRightRectangular'], + ['anchorOriginTopLeftRectangle', 'anchorOriginTopLeftRectangular'], + ['anchorOriginBottomLeftRectangle', 'anchorOriginBottomLeftRectangular'], + ['anchorOriginTopRightCircle', 'anchorOriginTopRightCircular'], + ['anchorOriginBottomRightCircle', 'anchorOriginBottomRightCircular'], + ['anchorOriginTopLeftCircle', 'anchorOriginTopLeftCircular'], + ].forEach(([deprecatedClassKey, newClassKey]) => { + if ( + classes[deprecatedClassKey] != null && + // 2 classnames? one from withStyles the other must be custom + classes[deprecatedClassKey].split(' ').length > 1 + ) { + throw new Error( + `Material-UI: The \`${deprecatedClassKey}\` class was deprecated. Use \`${newClassKey}\` instead.`, + ); + } + }); + + return null; + }), /** * @ignore */ @@ -260,7 +368,26 @@ Badge.propTypes = { /** * Wrapped shape the badge should overlap. */ - overlap: PropTypes.oneOf(['circle', 'rectangle']), + overlap: chainPropTypes( + PropTypes.oneOf(['circle', 'rectangle', 'circular', 'rectangular']), + (props) => { + const { overlap } = props; + + if (overlap === 'rectangle') { + throw new Error( + 'Material-UI: `overlap="rectangle"` was deprecated. Use `overlap="rectangular"` instead.', + ); + } + + if (overlap === 'circle') { + throw new Error( + 'Material-UI: `overlap="circle"` was deprecated. Use `overlap="circular"` instead.', + ); + } + + return null; + }, + ), /** * Controls whether the badge is hidden when `badgeContent` is zero. */ diff --git a/packages/material-ui/src/Badge/Badge.test.js b/packages/material-ui/src/Badge/Badge.test.js index b5b842cad3dbef..d9f9cfc55ea086 100644 --- a/packages/material-ui/src/Badge/Badge.test.js +++ b/packages/material-ui/src/Badge/Badge.test.js @@ -1,5 +1,7 @@ import * as React from 'react'; +import * as PropTypes from 'prop-types'; import { expect } from 'chai'; +import { stub } from 'sinon'; import { getClasses } from '@material-ui/core/test-utils'; import createMount from 'test/utils/createMount'; import { createClientRender } from 'test/utils/createClientRender'; @@ -170,4 +172,159 @@ describe('', () => { expect(findBadge(container)).to.have.text('50'); }); }); + + describe('v5 deprecations', () => { + beforeEach(() => { + PropTypes.resetWarningCache(); + stub(console, 'error'); + }); + + afterEach(() => { + console.error.restore(); + }); + + it('issues a warning for overlap="circle"', () => { + PropTypes.checkPropTypes( + Badge.Naked.propTypes, + { + overlap: 'circle', + }, + 'props', + 'Badge', + ); + + expect(console.error.callCount).to.equal(1); + expect(console.error.firstCall.args[0]).to.equal( + 'Warning: Failed props type: Material-UI: `overlap="circle"` was deprecated. Use `overlap="circular"` instead.', + ); + }); + + it('issues a warning for overlap="rectangle"', () => { + PropTypes.checkPropTypes( + Badge.Naked.propTypes, + { + overlap: 'rectangle', + }, + 'props', + 'Badge', + ); + + expect(console.error.callCount).to.equal(1); + expect(console.error.firstCall.args[0]).to.equal( + 'Warning: Failed props type: Material-UI: `overlap="rectangle"` was deprecated. Use `overlap="rectangular"` instead.', + ); + }); + + it('issues a warning for the `anchorOriginTopRightRectangle` class', () => { + PropTypes.checkPropTypes( + Badge.Naked.propTypes, + { + classes: { anchorOriginTopRightRectangle: 'mui-class my-class' }, + }, + 'props', + 'Badge', + ); + + expect(console.error.callCount).to.equal(1); + expect(console.error.firstCall.args[0]).to.equal( + 'Warning: Failed props type: Material-UI: The `anchorOriginTopRightRectangle` class was deprecated. Use `anchorOriginTopRightRectangular` instead.', + ); + }); + + it('issues a warning for the `anchorOriginBottomRightRectangle` class', () => { + PropTypes.checkPropTypes( + Badge.Naked.propTypes, + { + classes: { anchorOriginBottomRightRectangle: 'mui-class my-class' }, + }, + 'props', + 'Badge', + ); + + expect(console.error.callCount).to.equal(1); + expect(console.error.firstCall.args[0]).to.equal( + 'Warning: Failed props type: Material-UI: The `anchorOriginBottomRightRectangle` class was deprecated. Use `anchorOriginBottomRightRectangular` instead.', + ); + }); + + it('issues a warning for the `anchorOriginTopLeftRectangle` class', () => { + PropTypes.checkPropTypes( + Badge.Naked.propTypes, + { + classes: { anchorOriginTopLeftRectangle: 'mui-class my-class' }, + }, + 'props', + 'Badge', + ); + + expect(console.error.callCount).to.equal(1); + expect(console.error.firstCall.args[0]).to.equal( + 'Warning: Failed props type: Material-UI: The `anchorOriginTopLeftRectangle` class was deprecated. Use `anchorOriginTopLeftRectangular` instead.', + ); + }); + + it('issues a warning for the `anchorOriginBottomLeftRectangle` class', () => { + PropTypes.checkPropTypes( + Badge.Naked.propTypes, + { + classes: { anchorOriginBottomLeftRectangle: 'mui-class my-class' }, + }, + 'props', + 'Badge', + ); + + expect(console.error.callCount).to.equal(1); + expect(console.error.firstCall.args[0]).to.equal( + 'Warning: Failed props type: Material-UI: The `anchorOriginBottomLeftRectangle` class was deprecated. Use `anchorOriginBottomLeftRectangular` instead.', + ); + }); + + it('issues a warning for the `anchorOriginTopRightCircle` class', () => { + PropTypes.checkPropTypes( + Badge.Naked.propTypes, + { + classes: { anchorOriginTopRightCircle: 'mui-class my-class' }, + }, + 'props', + 'Badge', + ); + + expect(console.error.callCount).to.equal(1); + expect(console.error.firstCall.args[0]).to.equal( + 'Warning: Failed props type: Material-UI: The `anchorOriginTopRightCircle` class was deprecated. Use `anchorOriginTopRightCircular` instead.', + ); + }); + + it('issues a warning for the `anchorOriginBottomRightCircle` class', () => { + PropTypes.checkPropTypes( + Badge.Naked.propTypes, + { + classes: { anchorOriginBottomRightCircle: 'mui-class my-class' }, + }, + 'props', + 'Badge', + ); + + expect(console.error.callCount).to.equal(1); + expect(console.error.firstCall.args[0]).to.equal( + 'Warning: Failed props type: Material-UI: The `anchorOriginBottomRightCircle` class was deprecated. Use `anchorOriginBottomRightCircular` instead.', + ); + }); + + it('issues a warning for the `anchorOriginTopLeftCircle` class', () => { + PropTypes.checkPropTypes( + Badge.Naked.propTypes, + { + classes: { anchorOriginTopLeftCircle: 'mui-class my-class' }, + }, + 'props', + 'Badge', + ); + + expect(console.error.callCount).to.equal(1); + expect(console.error.firstCall.args[0]).to.equal( + 'Warning: Failed props type: Material-UI: The `anchorOriginTopLeftCircle` class was deprecated. Use `anchorOriginTopLeftCircular` instead.', + ); + }); + }); });