diff --git a/docs/pages/api-docs/badge-unstyled.json b/docs/pages/api-docs/badge-unstyled.json index 0b9fd34b56ccc9..177acded5ad169 100644 --- a/docs/pages/api-docs/badge-unstyled.json +++ b/docs/pages/api-docs/badge-unstyled.json @@ -18,10 +18,6 @@ "componentsProps": { "type": { "name": "object" }, "default": "{}" }, "invisible": { "type": { "name": "bool" } }, "max": { "type": { "name": "number" }, "default": "99" }, - "overlap": { - "type": { "name": "enum", "description": "'circular'
| 'rectangular'" }, - "default": "'rectangular'" - }, "showZero": { "type": { "name": "bool" } }, "variant": { "type": { "name": "string" }, "default": "'standard'" } }, @@ -32,14 +28,10 @@ "badge", "dot", "standard", - "anchorOriginTopRightRectangular", - "anchorOriginBottomRightRectangular", - "anchorOriginTopLeftRectangular", - "anchorOriginBottomLeftRectangular", - "anchorOriginTopRightCircular", - "anchorOriginBottomRightCircular", - "anchorOriginTopLeftCircular", - "anchorOriginBottomLeftCircular", + "anchorOriginTopRight", + "anchorOriginBottomRight", + "anchorOriginTopLeft", + "anchorOriginBottomLeft", "invisible" ], "globalClasses": { @@ -47,14 +39,10 @@ "badge": "MuiBadge-badge", "dot": "MuiBadge-dot", "standard": "MuiBadge-standard", - "anchorOriginTopRightRectangular": "MuiBadge-anchorOriginTopRightRectangular", - "anchorOriginBottomRightRectangular": "MuiBadge-anchorOriginBottomRightRectangular", - "anchorOriginTopLeftRectangular": "MuiBadge-anchorOriginTopLeftRectangular", - "anchorOriginBottomLeftRectangular": "MuiBadge-anchorOriginBottomLeftRectangular", - "anchorOriginTopRightCircular": "MuiBadge-anchorOriginTopRightCircular", - "anchorOriginBottomRightCircular": "MuiBadge-anchorOriginBottomRightCircular", - "anchorOriginTopLeftCircular": "MuiBadge-anchorOriginTopLeftCircular", - "anchorOriginBottomLeftCircular": "MuiBadge-anchorOriginBottomLeftCircular", + "anchorOriginTopRight": "MuiBadge-anchorOriginTopRight", + "anchorOriginBottomRight": "MuiBadge-anchorOriginBottomRight", + "anchorOriginTopLeft": "MuiBadge-anchorOriginTopLeft", + "anchorOriginBottomLeft": "MuiBadge-anchorOriginBottomLeft", "invisible": "MuiBadge-invisible" }, "name": null diff --git a/docs/pages/api-docs/badge.json b/docs/pages/api-docs/badge.json index 0f8801cb05b139..6afe7c28592ab1 100644 --- a/docs/pages/api-docs/badge.json +++ b/docs/pages/api-docs/badge.json @@ -51,6 +51,17 @@ "badge", "dot", "standard", + "anchorOriginTopRight", + "anchorOriginBottomRight", + "anchorOriginTopLeft", + "anchorOriginBottomLeft", + "invisible", + "colorPrimary", + "colorSecondary", + "colorError", + "colorInfo", + "colorSuccess", + "colorWarning", "anchorOriginTopRightRectangular", "anchorOriginBottomRightRectangular", "anchorOriginTopLeftRectangular", @@ -59,13 +70,8 @@ "anchorOriginBottomRightCircular", "anchorOriginTopLeftCircular", "anchorOriginBottomLeftCircular", - "invisible", - "colorPrimary", - "colorSecondary", - "colorError", - "colorInfo", - "colorSuccess", - "colorWarning" + "overlapRectangular", + "overlapCircular" ], "globalClasses": {}, "name": "MuiBadge" diff --git a/docs/src/pages/components/badges/UnstyledBadge.js b/docs/src/pages/components/badges/UnstyledBadge.js index 51b2f2afdc34d5..a011f753c29087 100644 --- a/docs/src/pages/components/badges/UnstyledBadge.js +++ b/docs/src/pages/components/badges/UnstyledBadge.js @@ -45,7 +45,7 @@ const StyledBadge = styled(BadgeUnstyled)` box-shadow: 0 0 0 1px #fff; } - & .MuiBadge-anchorOriginTopRightCircular { + & .MuiBadge-anchorOriginTopRight { position: absolute; top: 0; right: 0; @@ -73,10 +73,10 @@ function BadgeContent() { export default function UnstyledBadge() { return ( :not(style) + :not(style)': { ml: 4 } }}> - + - + diff --git a/docs/src/pages/components/badges/UnstyledBadge.tsx b/docs/src/pages/components/badges/UnstyledBadge.tsx index 51b2f2afdc34d5..a011f753c29087 100644 --- a/docs/src/pages/components/badges/UnstyledBadge.tsx +++ b/docs/src/pages/components/badges/UnstyledBadge.tsx @@ -45,7 +45,7 @@ const StyledBadge = styled(BadgeUnstyled)` box-shadow: 0 0 0 1px #fff; } - & .MuiBadge-anchorOriginTopRightCircular { + & .MuiBadge-anchorOriginTopRight { position: absolute; top: 0; right: 0; @@ -73,10 +73,10 @@ function BadgeContent() { export default function UnstyledBadge() { return ( :not(style) + :not(style)': { ml: 4 } }}> - + - + diff --git a/docs/src/pages/components/badges/UnstyledBadge.tsx.preview b/docs/src/pages/components/badges/UnstyledBadge.tsx.preview index 5e47c182bd8c33..55c0c93be1c769 100644 --- a/docs/src/pages/components/badges/UnstyledBadge.tsx.preview +++ b/docs/src/pages/components/badges/UnstyledBadge.tsx.preview @@ -1,6 +1,6 @@ - + - + \ No newline at end of file diff --git a/docs/translations/api-docs/badge-unstyled/badge-unstyled.json b/docs/translations/api-docs/badge-unstyled/badge-unstyled.json index 311e42d8b67eba..554dfd8183d080 100644 --- a/docs/translations/api-docs/badge-unstyled/badge-unstyled.json +++ b/docs/translations/api-docs/badge-unstyled/badge-unstyled.json @@ -10,7 +10,6 @@ "componentsProps": "The props used for each slot inside the Badge.", "invisible": "If true, the badge is invisible.", "max": "Max count to show.", - "overlap": "Wrapped shape the badge should overlap.", "showZero": "Controls whether the badge is hidden when badgeContent is zero.", "variant": "The variant to use." }, @@ -30,45 +29,25 @@ "nodeName": "the badge `span` element", "conditions": "variant=\"standard\"" }, - "anchorOriginTopRightRectangular": { + "anchorOriginTopRight": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'top', 'right' }} overlap=\"rectangular\"" + "conditions": "anchorOrigin={{ 'top', 'right' }}" }, - "anchorOriginBottomRightRectangular": { + "anchorOriginBottomRight": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'bottom', 'right' }} overlap=\"rectangular\"" + "conditions": "anchorOrigin={{ 'bottom', 'right' }}" }, - "anchorOriginTopLeftRectangular": { + "anchorOriginTopLeft": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'top', 'left' }} overlap=\"rectangular\"" + "conditions": "anchorOrigin={{ 'top', 'left' }}" }, - "anchorOriginBottomLeftRectangular": { + "anchorOriginBottomLeft": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'bottom', 'left' }} overlap=\"rectangular\"" - }, - "anchorOriginTopRightCircular": { - "description": "Class name applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'top', 'right' }} overlap=\"circular\"" - }, - "anchorOriginBottomRightCircular": { - "description": "Class name applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'bottom', 'right' }} overlap=\"circular\"" - }, - "anchorOriginTopLeftCircular": { - "description": "Class name applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'top', 'left' }} overlap=\"circular\"" - }, - "anchorOriginBottomLeftCircular": { - "description": "Class name applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'bottom', 'left' }} overlap=\"circular\"" + "conditions": "anchorOrigin={{ 'bottom', 'left' }}" }, "invisible": { "description": "State class applied to {{nodeName}} if {{conditions}}.", diff --git a/docs/translations/api-docs/badge/badge.json b/docs/translations/api-docs/badge/badge.json index 7110d48ca1bca7..83ca23b6e8bf51 100644 --- a/docs/translations/api-docs/badge/badge.json +++ b/docs/translations/api-docs/badge/badge.json @@ -32,45 +32,25 @@ "nodeName": "the badge `span` element", "conditions": "variant=\"standard\"" }, - "anchorOriginTopRightRectangular": { + "anchorOriginTopRight": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'top', 'right' }} overlap=\"rectangular\"" + "conditions": "anchorOrigin={{ 'top', 'right' }}" }, - "anchorOriginBottomRightRectangular": { + "anchorOriginBottomRight": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'bottom', 'right' }} overlap=\"rectangular\"" + "conditions": "anchorOrigin={{ 'bottom', 'right' }}" }, - "anchorOriginTopLeftRectangular": { + "anchorOriginTopLeft": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'top', 'left' }} overlap=\"rectangular\"" + "conditions": "anchorOrigin={{ 'top', 'left' }}" }, - "anchorOriginBottomLeftRectangular": { + "anchorOriginBottomLeft": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'bottom', 'left' }} overlap=\"rectangular\"" - }, - "anchorOriginTopRightCircular": { - "description": "Class name applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'top', 'right' }} overlap=\"circular\"" - }, - "anchorOriginBottomRightCircular": { - "description": "Class name applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'bottom', 'right' }} overlap=\"circular\"" - }, - "anchorOriginTopLeftCircular": { - "description": "Class name applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'top', 'left' }} overlap=\"circular\"" - }, - "anchorOriginBottomLeftCircular": { - "description": "Class name applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the badge `span` element", - "conditions": "anchorOrigin={{ 'bottom', 'left' }} overlap=\"circular\"" + "conditions": "anchorOrigin={{ 'bottom', 'left' }}" }, "invisible": { "description": "State class applied to {{nodeName}} if {{conditions}}.", @@ -106,6 +86,56 @@ "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the badge `span` element", "conditions": "color=\"warning\"" + }, + "anchorOriginTopRightRectangular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "anchorOrigin={{ 'top', 'right' }} overlap=\"rectangular\"" + }, + "anchorOriginBottomRightRectangular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "anchorOrigin={{ 'bottom', 'right' }} overlap=\"rectangular\"" + }, + "anchorOriginTopLeftRectangular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "anchorOrigin={{ 'top', 'left' }} overlap=\"rectangular\"" + }, + "anchorOriginBottomLeftRectangular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "anchorOrigin={{ 'bottom', 'left' }} overlap=\"rectangular\"" + }, + "anchorOriginTopRightCircular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "anchorOrigin={{ 'top', 'right' }} overlap=\"circular\"" + }, + "anchorOriginBottomRightCircular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "anchorOrigin={{ 'bottom', 'right' }} overlap=\"circular\"" + }, + "anchorOriginTopLeftCircular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "anchorOrigin={{ 'top', 'left' }} overlap=\"circular\"" + }, + "anchorOriginBottomLeftCircular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "anchorOrigin={{ 'bottom', 'left' }} overlap=\"circular\"" + }, + "overlapRectangular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "overlap=\"rectangular\"" + }, + "overlapCircular": { + "description": "Class name applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the badge `span` element", + "conditions": "overlap=\"circular\"" } } } diff --git a/packages/mui-base/src/BadgeUnstyled/BadgeUnstyled.d.ts b/packages/mui-base/src/BadgeUnstyled/BadgeUnstyled.d.ts index 6ebfb8d4bd6b15..fefd2b2d99fd88 100644 --- a/packages/mui-base/src/BadgeUnstyled/BadgeUnstyled.d.ts +++ b/packages/mui-base/src/BadgeUnstyled/BadgeUnstyled.d.ts @@ -1,88 +1,5 @@ -import * as React from 'react'; -import { OverridableComponent, OverridableTypeMap, OverrideProps } from '@mui/types'; -import { BadgeUnstyledClasses } from './badgeUnstyledClasses'; - -export interface BadgeOrigin { - vertical: 'top' | 'bottom'; - horizontal: 'left' | 'right'; -} - -export interface BadgeUnstyledComponentsPropsOverrides {} - -export interface BadgeUnstyledTypeMap

{ - props: P & { - /** - * The anchor of the badge. - * @default { - * vertical: 'top', - * horizontal: 'right', - * } - */ - anchorOrigin?: BadgeOrigin; - /** - * The components used for each slot inside the Badge. - * Either a string to use a HTML element or a component. - * @default {} - */ - components?: { - Root?: React.ElementType; - Badge?: React.ElementType; - }; - /** - * The props used for each slot inside the Badge. - * @default {} - */ - componentsProps?: { - root?: React.HTMLAttributes & BadgeUnstyledComponentsPropsOverrides; - badge?: React.HTMLAttributes & BadgeUnstyledComponentsPropsOverrides; - }; - /** - * Wrapped shape the badge should overlap. - * @default 'rectangular' - */ - overlap?: 'rectangular' | 'circular'; - /** - * The content rendered within the badge. - */ - badgeContent?: React.ReactNode; - /** - * The badge will be added relative to this node. - */ - children?: React.ReactNode; - /** - * Override or extend the styles applied to the component. - */ - classes?: Partial; - /** - * If `true`, the badge is invisible. - */ - invisible?: boolean; - /** - * Max count to show. - * @default 99 - */ - max?: number; - /** - * Controls whether the badge is hidden when `badgeContent` is zero. - * @default false - */ - showZero?: boolean; - /** - * The variant to use. - * @default 'standard' - */ - variant?: string; - }; - defaultComponent: D; -} - -/** - * Utility to create component types that inherit props from BadgeUnstyled. - */ -export interface ExtendBadgeUnstyledTypeMap { - props: M['props'] & BadgeUnstyledTypeMap['props']; - defaultComponent: M['defaultComponent']; -} +import { OverridableComponent, OverridableTypeMap } from '@mui/types'; +import { ExtendBadgeUnstyledTypeMap, BadgeUnstyledTypeMap } from './BadgeUnstyledProps'; export type ExtendBadgeUnstyled = OverridableComponent< ExtendBadgeUnstyledTypeMap @@ -100,9 +17,4 @@ export type ExtendBadgeUnstyled = OverridableCompo */ declare const BadgeUnstyled: OverridableComponent; -export type BadgeUnstyledProps< - D extends React.ElementType = BadgeUnstyledTypeMap['defaultComponent'], - P = {}, -> = OverrideProps, D>; - export default BadgeUnstyled; diff --git a/packages/mui-base/src/BadgeUnstyled/BadgeUnstyled.js b/packages/mui-base/src/BadgeUnstyled/BadgeUnstyled.js index d680bf5517cdda..3a7e5f9a18b686 100644 --- a/packages/mui-base/src/BadgeUnstyled/BadgeUnstyled.js +++ b/packages/mui-base/src/BadgeUnstyled/BadgeUnstyled.js @@ -1,22 +1,21 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; -import { unstable_capitalize as capitalize, usePreviousProps } from '@mui/utils'; +import { unstable_capitalize as capitalize } from '@mui/utils'; import composeClasses from '../composeClasses'; import appendOwnerState from '../utils/appendOwnerState'; +import useBadge from './useBadge'; import { getBadgeUtilityClass } from './badgeUnstyledClasses'; const useUtilityClasses = (ownerState) => { - const { variant, anchorOrigin, overlap, invisible, classes } = ownerState; + const { variant, anchorOrigin, invisible, classes } = ownerState; const slots = { root: ['root'], badge: [ 'badge', variant, - `anchorOrigin${capitalize(anchorOrigin.vertical)}${capitalize( - anchorOrigin.horizontal, - )}${capitalize(overlap)}`, + `anchorOrigin${capitalize(anchorOrigin.vertical)}${capitalize(anchorOrigin.horizontal)}`, invisible && 'invisible', ], }; @@ -39,39 +38,18 @@ const BadgeUnstyled = React.forwardRef(function BadgeUnstyled(props, ref) { componentsProps = {}, invisible: invisibleProp, max: maxProp = 99, - overlap: overlapProp = 'rectangular', showZero = false, variant: variantProp = 'standard', - /* eslint-disable react/prop-types */ - theme, ...other } = props; - const prevProps = usePreviousProps({ + const { anchorOrigin, badgeContent, max, variant, displayValue, invisible } = useBadge({ + ...props, anchorOrigin: anchorOriginProp, - badgeContent: badgeContentProp, max: maxProp, - overlap: overlapProp, variant: variantProp, }); - let invisible = invisibleProp; - - if ( - invisibleProp == null && - ((badgeContentProp === 0 && !showZero) || (badgeContentProp == null && variantProp !== 'dot')) - ) { - invisible = true; - } - - const { - anchorOrigin = anchorOriginProp, - badgeContent, - max = maxProp, - overlap = overlapProp, - variant = variantProp, - } = invisible ? prevProps : props; - const ownerState = { ...props, anchorOrigin, @@ -79,16 +57,10 @@ const BadgeUnstyled = React.forwardRef(function BadgeUnstyled(props, ref) { classes: classesProp, invisible, max, - overlap, variant, + showZero, }; - let displayValue = ''; - - if (variant !== 'dot') { - displayValue = badgeContent > max ? `${max}+` : badgeContent; - } - const classes = useUtilityClasses(ownerState); const Root = component || components.Root || 'span'; @@ -172,11 +144,6 @@ BadgeUnstyled.propTypes /* remove-proptypes */ = { * @default 99 */ max: PropTypes.number, - /** - * Wrapped shape the badge should overlap. - * @default 'rectangular' - */ - overlap: PropTypes.oneOf(['circular', 'rectangular']), /** * Controls whether the badge is hidden when `badgeContent` is zero. * @default false diff --git a/packages/mui-base/src/BadgeUnstyled/BadgeUnstyledProps.ts b/packages/mui-base/src/BadgeUnstyled/BadgeUnstyledProps.ts new file mode 100644 index 00000000000000..5a9fbfec94588a --- /dev/null +++ b/packages/mui-base/src/BadgeUnstyled/BadgeUnstyledProps.ts @@ -0,0 +1,87 @@ +import * as React from 'react'; +import { OverrideProps, OverridableTypeMap } from '@mui/types'; +import { BadgeUnstyledClasses } from './badgeUnstyledClasses'; + +export interface BadgeOrigin { + vertical: 'top' | 'bottom'; + horizontal: 'left' | 'right'; +} + +export interface BadgeUnstyledComponentsPropsOverrides {} + +export interface BadgeUnstyledTypeMap

{ + props: P & { + /** + * The anchor of the badge. + * @default { + * vertical: 'top', + * horizontal: 'right', + * } + */ + anchorOrigin?: BadgeOrigin; + /** + * The components used for each slot inside the Badge. + * Either a string to use a HTML element or a component. + * @default {} + */ + components?: { + Root?: React.ElementType; + Badge?: React.ElementType; + }; + /** + * The props used for each slot inside the Badge. + * @default {} + */ + componentsProps?: { + root?: React.HTMLAttributes & BadgeUnstyledComponentsPropsOverrides; + badge?: React.HTMLAttributes & BadgeUnstyledComponentsPropsOverrides; + }; + /** + * The content rendered within the badge. + */ + badgeContent?: React.ReactNode; + /** + * The badge will be added relative to this node. + */ + children?: React.ReactNode; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial; + /** + * If `true`, the badge is invisible. + */ + invisible?: boolean; + /** + * Max count to show. + * @default 99 + */ + max?: number; + /** + * Controls whether the badge is hidden when `badgeContent` is zero. + * @default false + */ + showZero?: boolean; + /** + * The variant to use. + * @default 'standard' + */ + variant?: string; + }; + defaultComponent: D; +} + +/** + * Utility to create component types that inherit props from BadgeUnstyled. + */ +export interface ExtendBadgeUnstyledTypeMap { + props: M['props'] & BadgeUnstyledTypeMap['props']; + defaultComponent: M['defaultComponent']; +} + +type BadgeUnstyledProps< + D extends React.ElementType = BadgeUnstyledTypeMap['defaultComponent'], + P = {}, +> = OverrideProps, D>; + +export default BadgeUnstyledProps; diff --git a/packages/mui-base/src/BadgeUnstyled/badgeUnstyledClasses.ts b/packages/mui-base/src/BadgeUnstyled/badgeUnstyledClasses.ts index 48b66ee50cdda5..92382836a8205d 100644 --- a/packages/mui-base/src/BadgeUnstyled/badgeUnstyledClasses.ts +++ b/packages/mui-base/src/BadgeUnstyled/badgeUnstyledClasses.ts @@ -10,22 +10,14 @@ export interface BadgeUnstyledClasses { dot: string; /** Class name applied to the badge `span` element if `variant="standard"`. */ standard: string; - /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'right' }} overlap="rectangular"`. */ - anchorOriginTopRightRectangular: string; - /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'right' }} overlap="rectangular"`. */ - anchorOriginBottomRightRectangular: string; - /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'left' }} overlap="rectangular"`. */ - anchorOriginTopLeftRectangular: string; - /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'left' }} overlap="rectangular"`. */ - anchorOriginBottomLeftRectangular: string; - /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'right' }} overlap="circular"`. */ - anchorOriginTopRightCircular: string; - /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'right' }} overlap="circular"`. */ - anchorOriginBottomRightCircular: string; - /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'left' }} overlap="circular"`. */ - anchorOriginTopLeftCircular: string; - /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'left' }} overlap="circular"`. */ - anchorOriginBottomLeftCircular: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'right' }}`. */ + anchorOriginTopRight: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'right' }}`. */ + anchorOriginBottomRight: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'left' }}`. */ + anchorOriginTopLeft: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'left' }}`. */ + anchorOriginBottomLeft: string; /** State class applied to the badge `span` element if `invisible={true}`. */ invisible: string; } @@ -41,14 +33,10 @@ const badgeUnstyledClasses: BadgeUnstyledClasses = generateUtilityClasses('MuiBa 'badge', 'dot', 'standard', - 'anchorOriginTopLeftCircular', - 'anchorOriginTopLeftRectangular', - 'anchorOriginTopRightCircular', - 'anchorOriginTopRightRectangular', - 'anchorOriginBottomLeftCircular', - 'anchorOriginBottomLeftRectangular', - 'anchorOriginBottomRightCircular', - 'anchorOriginBottomRightRectangular', + 'anchorOriginTopLeft', + 'anchorOriginTopRight', + 'anchorOriginBottomLeft', + 'anchorOriginBottomRight', 'invisible', ]); diff --git a/packages/mui-base/src/BadgeUnstyled/index.d.ts b/packages/mui-base/src/BadgeUnstyled/index.d.ts index 179d10d6514903..0f472bdce5332b 100644 --- a/packages/mui-base/src/BadgeUnstyled/index.d.ts +++ b/packages/mui-base/src/BadgeUnstyled/index.d.ts @@ -1,5 +1,11 @@ export { default } from './BadgeUnstyled'; export * from './BadgeUnstyled'; +export { default as useBadge } from './useBadge'; +export * from './useBadge'; + +export { default as BadgeUnstyledProps } from './BadgeUnstyledProps'; +export * from './BadgeUnstyledProps'; + export { default as badgeUnstyledClasses } from './badgeUnstyledClasses'; export * from './badgeUnstyledClasses'; diff --git a/packages/mui-base/src/BadgeUnstyled/index.js b/packages/mui-base/src/BadgeUnstyled/index.js index 1b99c3b126d89c..4aa33b06390b8f 100644 --- a/packages/mui-base/src/BadgeUnstyled/index.js +++ b/packages/mui-base/src/BadgeUnstyled/index.js @@ -1,2 +1,3 @@ export { default } from './BadgeUnstyled'; +export { default as useBadge } from './useBadge'; export { default as badgeUnstyledClasses, getBadgeUtilityClass } from './badgeUnstyledClasses'; diff --git a/packages/mui-base/src/BadgeUnstyled/useBadge.ts b/packages/mui-base/src/BadgeUnstyled/useBadge.ts new file mode 100644 index 00000000000000..ac5c2f5990f11c --- /dev/null +++ b/packages/mui-base/src/BadgeUnstyled/useBadge.ts @@ -0,0 +1,64 @@ +import * as React from 'react'; +import { usePreviousProps } from '@mui/utils'; +import BadgeUnstyledProps from './BadgeUnstyledProps'; + +export interface UseBadgeProps { + anchorOrigin: BadgeUnstyledProps['anchorOrigin']; + badgeContent: BadgeUnstyledProps['badgeContent']; + invisible: BadgeUnstyledProps['invisible']; + max: BadgeUnstyledProps['max']; + showZero: BadgeUnstyledProps['showZero']; + variant: BadgeUnstyledProps['variant']; +} + +export default function useBadge(props: UseBadgeProps) { + const { + anchorOrigin: anchorOriginProp = { + vertical: 'top', + horizontal: 'right', + }, + badgeContent: badgeContentProp, + invisible: invisibleProp, + max: maxProp = 99, + showZero = false, + variant: variantProp = 'standard', + } = props; + + const prevProps: Partial = usePreviousProps({ + anchorOrigin: anchorOriginProp, + badgeContent: badgeContentProp, + max: maxProp, + variant: variantProp, + }); + + let invisible = invisibleProp; + + if ( + invisibleProp == null && + ((badgeContentProp === 0 && !showZero) || (badgeContentProp == null && variantProp !== 'dot')) + ) { + invisible = true; + } + + const { + anchorOrigin = anchorOriginProp, + badgeContent, + max = maxProp, + variant = variantProp, + } = invisible ? prevProps : props; + + let displayValue: React.ReactNode = ''; + + if (variant !== 'dot') { + displayValue = badgeContent && Number(badgeContent) > max ? `${max}+` : badgeContent; + } + + return { + anchorOrigin, + badgeContent, + invisible, + max, + variant, + displayValue, + }; +} diff --git a/packages/mui-material/src/Badge/Badge.d.ts b/packages/mui-material/src/Badge/Badge.d.ts index 0627880c6fec6b..99c1ad1df5f4e7 100644 --- a/packages/mui-material/src/Badge/Badge.d.ts +++ b/packages/mui-material/src/Badge/Badge.d.ts @@ -30,6 +30,26 @@ export type BadgeTypeMap< colorSuccess?: string; /** Styles applied to the badge `span` element if `color="warning"`. */ colorWarning?: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'right' }} overlap="rectangular"`. */ + anchorOriginTopRightRectangular: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'right' }} overlap="rectangular"`. */ + anchorOriginBottomRightRectangular: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'left' }} overlap="rectangular"`. */ + anchorOriginTopLeftRectangular: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'left' }} overlap="rectangular"`. */ + anchorOriginBottomLeftRectangular: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'right' }} overlap="circular"`. */ + anchorOriginTopRightCircular: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'right' }} overlap="circular"`. */ + anchorOriginBottomRightCircular: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'top', 'left' }} overlap="circular"`. */ + anchorOriginTopLeftCircular: string; + /** Class name applied to the badge `span` element if `anchorOrigin={{ 'bottom', 'left' }} overlap="circular"`. */ + anchorOriginBottomLeftCircular: string; + /** Class name applied to the badge `span` element if `overlap="rectangular"`. */ + overlapRectangular: string; + /** Class name applied to the badge `span` element if `overlap="circular"`. */ + overlapCircular: string; }; /** * The color of the component. It supports those theme colors that make sense for this component. @@ -39,6 +59,11 @@ export type BadgeTypeMap< 'primary' | 'secondary' | 'default' | 'error' | 'info' | 'success' | 'warning', BadgePropsColorOverrides >; + /** + * Wrapped shape the badge should overlap. + * @default 'rectangular' + */ + overlap?: 'rectangular' | 'circular'; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-material/src/Badge/Badge.js b/packages/mui-material/src/Badge/Badge.js index c670f55e6f1033..3144241c90d65a 100644 --- a/packages/mui-material/src/Badge/Badge.js +++ b/packages/mui-material/src/Badge/Badge.js @@ -17,6 +17,17 @@ export const badgeClasses = { 'colorSecondary', 'colorSuccess', 'colorWarning', + 'overlapRectangular', + 'overlapCircular', + // TODO: v6 remove the overlap value from these class keys + 'anchorOriginTopLeftCircular', + 'anchorOriginTopLeftRectangular', + 'anchorOriginTopRightCircular', + 'anchorOriginTopRightRectangular', + 'anchorOriginBottomLeftCircular', + 'anchorOriginBottomLeftRectangular', + 'anchorOriginBottomRightCircular', + 'anchorOriginBottomRightRectangular', ]), }; @@ -24,14 +35,23 @@ const RADIUS_STANDARD = 10; const RADIUS_DOT = 4; const extendUtilityClasses = (ownerState) => { - const { color, classes = {} } = ownerState; + const { color, anchorOrigin, overlap, classes = {} } = ownerState; return { ...classes, - badge: clsx(classes.badge, { - [getBadgeUtilityClass(`color${capitalize(color)}`)]: color !== 'default', - [classes[`color${capitalize(color)}`]]: color !== 'default', - }), + badge: clsx( + classes.badge, + getBadgeUtilityClass( + `anchorOrigin${capitalize(anchorOrigin.vertical)}${capitalize( + anchorOrigin.horizontal, + )}${capitalize(overlap)}`, + ), + getBadgeUtilityClass(`overlap${capitalize(overlap)}`), + { + [getBadgeUtilityClass(`color${capitalize(color)}`)]: color !== 'default', + [classes[`color${capitalize(color)}`]]: color !== 'default', + }, + ), }; }; @@ -200,9 +220,14 @@ const shouldSpreadAdditionalProps = (Slot) => { const Badge = React.forwardRef(function Badge(inProps, ref) { const props = useThemeProps({ props: inProps, name: 'MuiBadge' }); const { + anchorOrigin: anchorOriginProp = { + vertical: 'top', + horizontal: 'right', + }, component = 'span', components = {}, componentsProps = {}, + overlap: overlapProp = 'rectangular', color: colorProp = 'default', invisible: invisibleProp, badgeContent: badgeContentProp, @@ -212,7 +237,9 @@ const Badge = React.forwardRef(function Badge(inProps, ref) { } = props; const prevProps = usePreviousProps({ + anchorOrigin: anchorOriginProp, color: colorProp, + overlap: overlapProp, }); let invisible = invisibleProp; @@ -224,13 +251,18 @@ const Badge = React.forwardRef(function Badge(inProps, ref) { invisible = true; } - const { color = colorProp } = invisible ? prevProps : props; + const { + color = colorProp, + overlap = overlapProp, + anchorOrigin = anchorOriginProp, + } = invisible ? prevProps : props; - const ownerState = { ...props, invisible, color }; + const ownerState = { ...props, anchorOrigin, invisible, color, overlap }; const classes = extendUtilityClasses(ownerState); return ( { const isExternal = filename !== tsFile; - const implementedByUnstyledVariant = filename === unstyledFile; + const implementedByUnstyledVariant = + filename === unstyledFile || filename === unstyledPropsFile; if (!isExternal || implementedByUnstyledVariant) { shouldDocument = true; }