> {
focused?: boolean;
hasClearIcon?: boolean;
hasPopupIcon?: boolean;
diff --git a/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts
index e48385d4651176..22ced03f1bc388 100644
--- a/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts
+++ b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts
@@ -53,6 +53,8 @@ export interface AutocompleteClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `size="sm"`. */
sizeSm: string;
/** Styles applied to the root element if `size="md"`. */
@@ -102,6 +104,7 @@ const autocompleteClasses: AutocompleteClasses = generateUtilityClasses('JoyAuto
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'sizeSm',
'sizeMd',
'sizeLg',
diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx
index c1dce2700792d3..d2c36e7be137a8 100644
--- a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx
+++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { describeConformance, createRenderer } from 'test/utils';
+import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import AutocompleteListbox, {
autocompleteListboxClasses as classes,
@@ -21,6 +21,11 @@ describe('Joy ', () => {
skip: ['componentsProp', 'classesRoot'],
}));
+ describeJoyColorInversion(, {
+ muiName: 'JoyAutocompleteListbox',
+ classes,
+ });
+
it('should have ul tag', () => {
const { container } = render();
expect(container.firstChild).to.have.tagName('ul');
diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx
index bd58846f76264e..0aba3162179d35 100644
--- a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx
+++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx
@@ -14,6 +14,7 @@ import {
import listItemClasses from '../ListItem/listItemClasses';
import listClasses from '../List/listClasses';
import { scopedVariables } from '../List/ListProvider';
+import { useColorInversion } from '../styles/ColorInversion';
const useUtilityClasses = (ownerState: AutocompleteListboxOwnerState) => {
const { variant, color, size } = ownerState;
@@ -93,11 +94,13 @@ const AutocompleteListbox = React.forwardRef(function AutocompleteListbox(inProp
children,
className,
component,
- color = 'neutral',
+ color: colorProp = 'neutral',
variant = 'outlined',
size = 'md',
...otherProps
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const ownerState = {
...props,
diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts
index d48cfd219c1d0a..f14f02d67f74d7 100644
--- a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts
+++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts
@@ -1,27 +1,30 @@
import * as React from 'react';
-import { OverrideProps } from '@mui/types';
-import { ListProps } from '../List/ListProps';
-import { SxProps } from '../styles/types';
+import { OverrideProps, OverridableStringUnion } from '@mui/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
export type AutocompleteListboxSlot = 'root';
+export interface AutocompleteListboxPropsSizeOverrides {}
+export interface AutocompleteListboxPropsColorOverrides {}
+export interface AutocompleteListboxPropsVariantOverrides {}
+
export interface AutocompleteListboxTypeMap {
props: P & {
/**
* The color of the component. It supports those theme colors that make sense for this component.
* @default 'neutral'
*/
- color?: ListProps['color'];
+ color?: OverridableStringUnion;
/**
* The variant to use.
* @default 'outlined'
*/
- variant?: ListProps['variant'];
+ variant?: OverridableStringUnion;
/**
* The size of the component (affect other nested list* components).
* @default 'md'
*/
- size?: ListProps['size'];
+ size?: OverridableStringUnion<'sm' | 'md' | 'lg', AutocompleteListboxPropsSizeOverrides>;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
@@ -37,4 +40,5 @@ export type AutocompleteListboxProps<
},
> = OverrideProps, D>;
-export interface AutocompleteListboxOwnerState extends AutocompleteListboxProps {}
+export interface AutocompleteListboxOwnerState
+ extends ApplyColorInversion {}
diff --git a/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts
index d3fa27a8717fe7..a8a348abcaa19a 100644
--- a/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts
+++ b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts
@@ -21,6 +21,8 @@ export interface AutocompleteListboxClasses {
colorSuccess: string;
/** Classname applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Classname applied to the root element if `variant="plain"`. */
variantPlain: string;
/** Classname applied to the root element if `variant="outlined"`. */
@@ -50,6 +52,7 @@ const autocompleteListboxClasses: AutocompleteListboxClasses = generateUtilityCl
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantOutlined',
'variantSoft',
diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js
index 205980e72aa61f..546fc62e96954f 100644
--- a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js
+++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { describeConformance, createRenderer } from 'test/utils';
+import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import AutocompleteOption, {
autocompleteOptionClasses as classes,
@@ -21,6 +21,8 @@ describe('Joy ', () => {
skip: ['componentsProp', 'classesRoot'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyAutocompleteOption', classes });
+
it('should have li tag', () => {
const { getByRole } = render();
expect(getByRole('option')).to.have.tagName('li');
diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx
index b54098681d529d..7c8ad4f5b13397 100644
--- a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx
+++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx
@@ -10,6 +10,7 @@ import autocompleteOptionClasses, {
getAutocompleteOptionUtilityClass,
} from './autocompleteOptionClasses';
import { AutocompleteOptionOwnerState, AutocompleteOptionTypeMap } from './AutocompleteOptionProps';
+import { useColorInversion } from '../styles/ColorInversion';
const useUtilityClasses = (ownerState: AutocompleteOptionOwnerState) => {
const { color, variant } = ownerState;
@@ -33,8 +34,10 @@ export const StyledAutocompleteOption = styled(StyledListItemButton as unknown a
},
'&[aria-disabled="true"]': theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!],
'&[aria-selected="true"]': {
- color: theme.vars.palette.primary.softColor,
- backgroundColor: theme.vars.palette.primary.softBg,
+ color: theme.variants.soft?.[ownerState.color === 'context' ? 'context' : 'primary']?.color,
+ backgroundColor:
+ theme.variants.soft?.[ownerState.color === 'context' ? 'context' : 'primary']
+ ?.backgroundColor,
fontWeight: theme.vars.fontWeight.md,
},
[`&.${autocompleteOptionClasses.focused}:not([aria-selected="true"]):not(:hover)`]: {
@@ -59,11 +62,13 @@ const AutocompleteOption = React.forwardRef(function AutocompleteOption(inProps,
const {
children,
component = 'li',
- color = 'neutral',
+ color: colorProp = 'neutral',
variant = 'plain',
className,
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const ownerState = {
...props,
diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts b/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts
index 4d2e672dc482b4..4146e121d861fa 100644
--- a/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts
+++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts
@@ -1,22 +1,24 @@
import * as React from 'react';
-import { OverrideProps } from '@mui/types';
-import { ListItemButtonProps } from '../ListItemButton/ListItemButtonProps';
-import { SxProps } from '../styles/types';
+import { OverrideProps, OverridableStringUnion } from '@mui/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
export type AutocompleteOptionSlot = 'root';
+export interface AutocompleteOptionPropsColorOverrides {}
+export interface AutocompleteOptionPropsVariantOverrides {}
+
export interface AutocompleteOptionTypeMap {
props: P & {
/**
* The color of the component. It supports those theme colors that make sense for this component.
* @default 'neutral'
*/
- color?: ListItemButtonProps['color'];
+ color?: OverridableStringUnion;
/**
* The variant to use.
* @default 'plain'
*/
- variant?: ListItemButtonProps['variant'];
+ variant?: OverridableStringUnion;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
@@ -32,4 +34,5 @@ export type AutocompleteOptionProps<
},
> = OverrideProps, D>;
-export interface AutocompleteOptionOwnerState extends AutocompleteOptionProps {}
+export interface AutocompleteOptionOwnerState
+ extends ApplyColorInversion {}
diff --git a/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts
index 711ee09fc73203..f393cf0e6e91c9 100644
--- a/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts
+++ b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts
@@ -19,6 +19,8 @@ export interface AutocompleteOptionClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** State class applied to the root element if `variant="plain"`. */
variantPlain: string;
/** State class applied to the root element if `variant="soft"`. */
@@ -47,6 +49,7 @@ const autocompleteOptionClasses: AutocompleteOptionClasses = generateUtilityClas
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantSoft',
'variantOutlined',
diff --git a/packages/mui-joy/src/Avatar/Avatar.test.js b/packages/mui-joy/src/Avatar/Avatar.test.js
index 1c3539c39bd6b6..166c290800ccd2 100644
--- a/packages/mui-joy/src/Avatar/Avatar.test.js
+++ b/packages/mui-joy/src/Avatar/Avatar.test.js
@@ -1,7 +1,12 @@
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
-import { createRenderer, describeConformance, fireEvent } from 'test/utils';
+import {
+ createRenderer,
+ describeConformance,
+ describeJoyColorInversion,
+ fireEvent,
+} from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import Avatar, { avatarClasses as classes } from '@mui/joy/Avatar';
import { unstable_capitalize as capitalize } from '@mui/utils';
@@ -28,6 +33,8 @@ describe('', () => {
skip: ['classesRoot', 'componentsProp'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyAvatar', classes });
+
describe('prop: variant', () => {
it('soft by default', () => {
const { getByTestId } = render();
diff --git a/packages/mui-joy/src/Avatar/Avatar.tsx b/packages/mui-joy/src/Avatar/Avatar.tsx
index 8871021bfeed1d..ac99fdfed9a804 100644
--- a/packages/mui-joy/src/Avatar/Avatar.tsx
+++ b/packages/mui-joy/src/Avatar/Avatar.tsx
@@ -6,6 +6,7 @@ import { unstable_capitalize as capitalize } from '@mui/utils';
import { useThemeProps } from '../styles';
import useSlot from '../utils/useSlot';
import styled from '../styles/styled';
+import { useColorInversion } from '../styles/ColorInversion';
import Person from '../internal/svg-icons/Person';
import { getAvatarUtilityClass } from './avatarClasses';
import { AvatarProps, AvatarOwnerState, AvatarTypeMap } from './AvatarProps';
@@ -155,8 +156,9 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) {
children: childrenProp,
...other
} = props;
- const color = inProps.color || groupContext?.color || colorProp;
const variant = inProps.variant || groupContext?.variant || variantProp;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color || groupContext?.color, colorProp);
const size = inProps.size || groupContext?.size || sizeProp;
let children = null;
diff --git a/packages/mui-joy/src/Avatar/AvatarProps.ts b/packages/mui-joy/src/Avatar/AvatarProps.ts
index a1b43d176c93dd..8f211b7444e4ca 100644
--- a/packages/mui-joy/src/Avatar/AvatarProps.ts
+++ b/packages/mui-joy/src/Avatar/AvatarProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types';
+import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
export type AvatarSlot = 'root' | 'img' | 'fallback';
@@ -76,7 +76,7 @@ export type AvatarProps<
P = { component?: React.ElementType },
> = OverrideProps, D>;
-export interface AvatarOwnerState extends AvatarProps {
+export interface AvatarOwnerState extends ApplyColorInversion {
/**
* The avatar is wrapped by AvatarGroup component.
*/
diff --git a/packages/mui-joy/src/Avatar/avatarClasses.ts b/packages/mui-joy/src/Avatar/avatarClasses.ts
index 4d42d4c9d2d8ea..2094f9c82dcb8e 100644
--- a/packages/mui-joy/src/Avatar/avatarClasses.ts
+++ b/packages/mui-joy/src/Avatar/avatarClasses.ts
@@ -15,6 +15,8 @@ export interface AvatarClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the fallback icon. */
fallback: string;
/** Styles applied to the root element if `size="sm"`. */
@@ -47,6 +49,7 @@ const avatarClasses: AvatarClasses = generateUtilityClasses('JoyAvatar', [
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'fallback',
'sizeSm',
'sizeMd',
diff --git a/packages/mui-joy/src/Badge/Badge.test.js b/packages/mui-joy/src/Badge/Badge.test.js
index 0841b89083f6c7..c2fb3c81d51ebd 100644
--- a/packages/mui-joy/src/Badge/Badge.test.js
+++ b/packages/mui-joy/src/Badge/Badge.test.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { createRenderer, describeConformance } from 'test/utils';
+import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils';
import { unstable_capitalize as capitalize } from '@mui/utils';
import { ThemeProvider } from '@mui/joy/styles';
import Badge, { badgeClasses as classes } from '@mui/joy/Badge';
@@ -42,6 +42,13 @@ describe('', () => {
}),
);
+ describeJoyColorInversion(
+
+
+ ,
+ { muiName: 'JoyBadge', classes },
+ );
+
it('renders children and badgeContent', () => {
const children = ;
const badge = ;
diff --git a/packages/mui-joy/src/Badge/Badge.tsx b/packages/mui-joy/src/Badge/Badge.tsx
index 2eaf531532fcaa..69d553c1292e0f 100644
--- a/packages/mui-joy/src/Badge/Badge.tsx
+++ b/packages/mui-joy/src/Badge/Badge.tsx
@@ -5,6 +5,7 @@ import { unstable_capitalize as capitalize, usePreviousProps } from '@mui/utils'
import { unstable_composeClasses as composeClasses } from '@mui/base';
import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
+import { useColorInversion } from '../styles/ColorInversion';
import useSlot from '../utils/useSlot';
import badgeClasses, { getBadgeUtilityClass } from './badgeClasses';
import { BadgeProps, BadgeOwnerState, BadgeTypeMap } from './BadgeProps';
@@ -174,13 +175,16 @@ const Badge = React.forwardRef(function Badge(inProps, ref) {
}
const {
- color = colorProp,
+ color: internalColor = colorProp,
size = sizeProp,
anchorOrigin = anchorOriginProp,
variant = variantProp,
badgeInset = badgeInsetProp,
} = invisible ? prevProps : props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, internalColor);
+
const ownerState = { ...props, anchorOrigin, badgeInset, variant, invisible, color, size };
const classes = useUtilityClasses(ownerState);
let displayValue =
diff --git a/packages/mui-joy/src/Badge/BadgeProps.ts b/packages/mui-joy/src/Badge/BadgeProps.ts
index a4619198d86587..73bbba2bbdd399 100644
--- a/packages/mui-joy/src/Badge/BadgeProps.ts
+++ b/packages/mui-joy/src/Badge/BadgeProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types';
+import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types';
import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types';
export type BadgeSlot = 'root' | 'badge';
@@ -91,4 +91,4 @@ export type BadgeProps<
P = { component?: React.ElementType },
> = OverrideProps, D>;
-export interface BadgeOwnerState extends BadgeProps {}
+export interface BadgeOwnerState extends ApplyColorInversion {}
diff --git a/packages/mui-joy/src/Badge/badgeClasses.ts b/packages/mui-joy/src/Badge/badgeClasses.ts
index a7457ce9d22b4c..1f1a2d8e6c914e 100644
--- a/packages/mui-joy/src/Badge/badgeClasses.ts
+++ b/packages/mui-joy/src/Badge/badgeClasses.ts
@@ -25,6 +25,8 @@ export interface BadgeClasses {
colorSuccess: string;
/** Styles applied to the badge `span` element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** State class applied to the badge `span` element if `invisible={true}`. */
invisible: string;
/** State class applied to the badge `span` element if `location="inside"`. */
@@ -66,6 +68,7 @@ const badgeClasses: BadgeClasses = generateUtilityClasses('JoyBadge', [
'colorNeutral',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'invisible',
'locationInside',
'locationOutside',
diff --git a/packages/mui-joy/src/Button/Button.test.js b/packages/mui-joy/src/Button/Button.test.js
index 0c1dbc2cbfb22e..80a3e9c4570af1 100644
--- a/packages/mui-joy/src/Button/Button.test.js
+++ b/packages/mui-joy/src/Button/Button.test.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { describeConformance, createRenderer } from 'test/utils';
+import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils';
import Button, { buttonClasses as classes } from '@mui/joy/Button';
import { ThemeProvider } from '@mui/joy/styles';
@@ -29,6 +29,8 @@ describe('Joy ', () => {
}),
);
+ describeJoyColorInversion(, { muiName: 'JoyButton', classes });
+
it('by default, should render with the root, variantSolid, sizeMd and colorPrimary classes', () => {
const { getByRole } = render();
const button = getByRole('button');
diff --git a/packages/mui-joy/src/Button/Button.tsx b/packages/mui-joy/src/Button/Button.tsx
index e7c59022f78e64..af12b54e24d790 100644
--- a/packages/mui-joy/src/Button/Button.tsx
+++ b/packages/mui-joy/src/Button/Button.tsx
@@ -190,7 +190,10 @@ const Button = React.forwardRef(function Button(inProps, ref) {
});
const loadingIndicator = loadingIndicatorProp ?? (
-
+
);
React.useImperativeHandle(
diff --git a/packages/mui-joy/src/Button/ButtonProps.ts b/packages/mui-joy/src/Button/ButtonProps.ts
index 898ce397d3552f..5046d6ad1b62ec 100644
--- a/packages/mui-joy/src/Button/ButtonProps.ts
+++ b/packages/mui-joy/src/Button/ButtonProps.ts
@@ -5,7 +5,7 @@ import {
OverridableTypeMap,
OverrideProps,
} from '@mui/types';
-import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types';
+import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types';
import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types';
export type ButtonSlot = 'root' | 'startDecorator' | 'endDecorator' | 'loadingIndicatorCenter';
@@ -114,8 +114,7 @@ export type ButtonProps<
},
> = OverrideProps, D>;
-export interface ButtonOwnerState extends Omit {
- color: ButtonProps['color'] | 'context';
+export interface ButtonOwnerState extends ApplyColorInversion {
/**
* If `true`, the button's focus is visible.
*/
diff --git a/packages/mui-joy/src/Button/buttonClasses.ts b/packages/mui-joy/src/Button/buttonClasses.ts
index 405baa4fe22bb6..a47e6f895db806 100644
--- a/packages/mui-joy/src/Button/buttonClasses.ts
+++ b/packages/mui-joy/src/Button/buttonClasses.ts
@@ -15,6 +15,8 @@ export interface ButtonClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `variant="plain"`. */
variantPlain: string;
/** Styles applied to the root element if `variant="outlined"`. */
@@ -59,6 +61,7 @@ const buttonClasses: ButtonClasses = generateUtilityClasses('JoyButton', [
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantOutlined',
'variantSoft',
diff --git a/packages/mui-joy/src/Card/Card.test.js b/packages/mui-joy/src/Card/Card.test.js
index 08c0239c769001..c81d64825bab0c 100644
--- a/packages/mui-joy/src/Card/Card.test.js
+++ b/packages/mui-joy/src/Card/Card.test.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { createRenderer, describeConformance } from 'test/utils';
+import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import Card, { cardClasses as classes } from '@mui/joy/Card';
import { unstable_capitalize as capitalize } from '@mui/utils';
@@ -21,6 +21,8 @@ describe('', () => {
skip: ['classesRoot', 'componentsProp'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyCard', classes });
+
describe('prop: variant', () => {
it('plain by default', () => {
const { getByTestId } = render(Hello World);
diff --git a/packages/mui-joy/src/Card/Card.tsx b/packages/mui-joy/src/Card/Card.tsx
index 73c9813f331fff..1d35f6c933ff24 100644
--- a/packages/mui-joy/src/Card/Card.tsx
+++ b/packages/mui-joy/src/Card/Card.tsx
@@ -9,12 +9,13 @@ import {
} from '@mui/utils';
import { useThemeProps } from '../styles';
import styled from '../styles/styled';
+import { ColorInversionProvider, useColorInversion } from '../styles/ColorInversion';
import { getCardUtilityClass } from './cardClasses';
-import { CardProps, CardTypeMap } from './CardProps';
+import { CardProps, CardOwnerState, CardTypeMap } from './CardProps';
import { resolveSxValue } from '../styles/styleUtils';
import { CardRowContext } from './CardContext';
-const useUtilityClasses = (ownerState: CardProps) => {
+const useUtilityClasses = (ownerState: CardOwnerState) => {
const { size, variant, color, row } = ownerState;
const slots = {
@@ -34,7 +35,7 @@ const CardRoot = styled('div', {
name: 'JoyCard',
slot: 'Root',
overridesResolver: (props, styles) => styles.root,
-})<{ ownerState: CardProps }>(({ theme, ownerState }) => [
+})<{ ownerState: CardOwnerState }>(({ theme, ownerState }) => [
{
// a context variable for any child component
'--Card-childRadius':
@@ -82,6 +83,9 @@ const CardRoot = styled('div', {
flexDirection: ownerState.row ? 'row' : 'column',
},
theme.variants[ownerState.variant!]?.[ownerState.color!],
+ ownerState.color !== 'context' &&
+ ownerState.invertedColors &&
+ theme.colorInversion[ownerState.variant!]?.[ownerState.color!],
]);
const Card = React.forwardRef(function Card(inProps, ref) {
@@ -92,14 +96,17 @@ const Card = React.forwardRef(function Card(inProps, ref) {
const {
className,
- color = 'neutral',
+ color: colorProp = 'neutral',
component = 'div',
+ invertedColors = false,
size = 'md',
variant = 'plain',
children,
row = false,
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const ownerState = {
...props,
@@ -112,7 +119,7 @@ const Card = React.forwardRef(function Card(inProps, ref) {
const classes = useUtilityClasses(ownerState);
- return (
+ const result = (
);
+
+ if (invertedColors) {
+ return {result};
+ }
+ return result;
}) as OverridableComponent;
Card.propTypes /* remove-proptypes */ = {
@@ -173,6 +185,11 @@ Card.propTypes /* remove-proptypes */ = {
* Either a string to use a HTML element or a component.
*/
component: PropTypes.elementType,
+ /**
+ * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color.
+ * @default false
+ */
+ invertedColors: PropTypes.bool,
/**
* If `true`, flex direction is set to 'row'.
* @default false
diff --git a/packages/mui-joy/src/Card/CardProps.ts b/packages/mui-joy/src/Card/CardProps.ts
index 5aa19039932eb9..cd3619d30e5c60 100644
--- a/packages/mui-joy/src/Card/CardProps.ts
+++ b/packages/mui-joy/src/Card/CardProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
export type CardSlot = 'root';
@@ -20,6 +20,11 @@ export interface CardTypeMap {
* @default 'neutral'
*/
color?: OverridableStringUnion;
+ /**
+ * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color.
+ * @default false
+ */
+ invertedColors?: boolean;
/**
* If `true`, flex direction is set to 'row'.
* @default false
@@ -49,4 +54,4 @@ export type CardProps<
P = { component?: React.ElementType },
> = OverrideProps, D>;
-export interface CardOwnerState extends CardProps {}
+export interface CardOwnerState extends ApplyColorInversion {}
diff --git a/packages/mui-joy/src/Card/cardClasses.ts b/packages/mui-joy/src/Card/cardClasses.ts
index 687062fdf18d50..6fae87bdfe765f 100644
--- a/packages/mui-joy/src/Card/cardClasses.ts
+++ b/packages/mui-joy/src/Card/cardClasses.ts
@@ -15,6 +15,8 @@ export interface CardClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `variant="plain"`. */
variantPlain: string;
/** Styles applied to the root element if `variant="outlined"`. */
@@ -47,6 +49,7 @@ const cardClasses: CardClasses = generateUtilityClasses('JoyCard', [
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantOutlined',
'variantSoft',
diff --git a/packages/mui-joy/src/CardOverflow/CardOverflow.test.js b/packages/mui-joy/src/CardOverflow/CardOverflow.test.js
index f5433675915d12..3843b7c68adf01 100644
--- a/packages/mui-joy/src/CardOverflow/CardOverflow.test.js
+++ b/packages/mui-joy/src/CardOverflow/CardOverflow.test.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { createRenderer, describeConformance } from 'test/utils';
+import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils';
import { unstable_capitalize as capitalize } from '@mui/utils';
import { ThemeProvider } from '@mui/joy/styles';
import CardOverflow, { cardOverflowClasses as classes } from '@mui/joy/CardOverflow';
@@ -21,6 +21,8 @@ describe('', () => {
skip: ['classesRoot', 'componentsProp'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyCardOverflow', classes });
+
describe('prop: variant', () => {
it('plain by default', () => {
const { getByTestId } = render(Hello World);
diff --git a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx
index bda7cd29a9d2da..8e30b37b16b619 100644
--- a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx
+++ b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx
@@ -6,11 +6,16 @@ import { OverridableComponent } from '@mui/types';
import { unstable_capitalize as capitalize } from '@mui/utils';
import { useThemeProps } from '../styles';
import styled from '../styles/styled';
+import { useColorInversion } from '../styles/ColorInversion';
import { getCardOverflowUtilityClass } from './cardOverflowClasses';
-import { CardOverflowProps, CardOverflowTypeMap } from './CardOverflowProps';
+import {
+ CardOverflowProps,
+ CardOverflowOwnerState,
+ CardOverflowTypeMap,
+} from './CardOverflowProps';
import { CardRowContext } from '../Card/CardContext';
-const useUtilityClasses = (ownerState: CardOverflowProps) => {
+const useUtilityClasses = (ownerState: CardOverflowOwnerState) => {
const { variant, color } = ownerState;
const slots = {
root: [
@@ -28,7 +33,7 @@ const CardOverflowRoot = styled('div', {
slot: 'Root',
overridesResolver: (props, styles) => styles.root,
})<{
- ownerState: CardOverflowProps & {
+ ownerState: CardOverflowOwnerState & {
row: boolean;
'data-first-child'?: string;
'data-last-child'?: string;
@@ -95,10 +100,12 @@ const CardOverflow = React.forwardRef(function CardOverflow(inProps, ref) {
className,
component = 'div',
children,
- color = 'neutral',
+ color: colorProp = 'neutral',
variant = 'plain',
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const ownerState = {
...props,
diff --git a/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts b/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts
index 9063abb4b44a6a..7b69d67fa0545d 100644
--- a/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts
+++ b/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
export type CardOverflowSlot = 'root';
@@ -37,4 +37,4 @@ export type CardOverflowProps<
P = { component?: React.ElementType },
> = OverrideProps, D>;
-export interface CardOverflowOwnerState extends CardOverflowProps {}
+export interface CardOverflowOwnerState extends ApplyColorInversion {}
diff --git a/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts b/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts
index dd445e4e83e4d2..14f85734a87023 100644
--- a/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts
+++ b/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts
@@ -15,6 +15,8 @@ export interface CardOverflowClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `variant="plain"`. */
variantPlain: string;
/** Styles applied to the root element if `variant="outlined"`. */
@@ -39,6 +41,7 @@ const aspectRatioClasses: CardOverflowClasses = generateUtilityClasses('JoyCardO
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantOutlined',
'variantSoft',
diff --git a/packages/mui-joy/src/Checkbox/Checkbox.test.js b/packages/mui-joy/src/Checkbox/Checkbox.test.js
index 31433c05d9ddaf..48731c34a5c7a9 100644
--- a/packages/mui-joy/src/Checkbox/Checkbox.test.js
+++ b/packages/mui-joy/src/Checkbox/Checkbox.test.js
@@ -1,6 +1,12 @@
import * as React from 'react';
import { expect } from 'chai';
-import { describeConformance, act, createRenderer, fireEvent } from 'test/utils';
+import {
+ describeConformance,
+ act,
+ createRenderer,
+ fireEvent,
+ describeJoyColorInversion,
+} from 'test/utils';
import Checkbox, { checkboxClasses as classes } from '@mui/joy/Checkbox';
import { ThemeProvider } from '@mui/joy/styles';
@@ -26,6 +32,8 @@ describe('', () => {
skip: ['componentProp', 'componentsProp', 'classesRoot', 'propsSpread', 'themeVariants'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyCheckbox', classes });
+
it('should have the classes required for Checkbox', () => {
expect(classes).to.include.all.keys(['root', 'checked', 'disabled']);
});
diff --git a/packages/mui-joy/src/Checkbox/Checkbox.tsx b/packages/mui-joy/src/Checkbox/Checkbox.tsx
index 6852f48fd3e29b..f8a82b7786d8ef 100644
--- a/packages/mui-joy/src/Checkbox/Checkbox.tsx
+++ b/packages/mui-joy/src/Checkbox/Checkbox.tsx
@@ -5,6 +5,7 @@ import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui
import { unstable_composeClasses as composeClasses } from '@mui/base';
import { useSwitch } from '@mui/base/SwitchUnstyled';
import { styled, useThemeProps } from '../styles';
+import { useColorInversion } from '../styles/ColorInversion';
import useSlot from '../utils/useSlot';
import checkboxClasses, { getCheckboxUtilityClass } from './checkboxClasses';
import { CheckboxOwnerState, CheckboxTypeMap } from './CheckboxProps';
@@ -64,15 +65,15 @@ const CheckboxRoot = styled('span', {
position: ownerState.overlay ? 'initial' : 'relative',
display: 'inline-flex',
fontFamily: theme.vars.fontFamily.body,
- lineHeight: 'var(--Checkbox-size)', // prevent label from having larger height than the checkbox
+ lineHeight: 'var(--Checkbox-size)',
color: theme.vars.palette.text.primary,
[`&.${checkboxClasses.disabled}`]: {
- color: theme.vars.palette[ownerState.color!]?.plainDisabledColor,
+ color: theme.variants.plainDisabled?.[ownerState.color!]?.color,
},
...(ownerState.disableIcon && {
- color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`],
+ color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color,
[`&.${checkboxClasses.disabled}`]: {
- color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`],
+ color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color,
},
}),
}));
@@ -204,7 +205,7 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) {
required,
value,
color: colorProp,
- variant,
+ variant: variantProp,
size: sizeProp = 'md',
...other
} = props;
@@ -212,7 +213,6 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) {
const formControl = React.useContext(FormControlContext);
const disabledProp = inProps.disabled ?? formControl?.disabled ?? disabledExternalProp;
const size = inProps.size ?? formControl?.size ?? sizeProp;
- const color = formControl?.error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp;
if (process.env.NODE_ENV !== 'production') {
const registerEffect = formControl?.registerEffect;
@@ -241,10 +241,17 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) {
const { getInputProps, checked, disabled, focusVisible } = useSwitch(useCheckboxProps);
const isCheckboxActive = checked || indeterminate;
+ const activeVariant = variantProp || 'solid';
+ const inactiveVariant = variantProp || 'outlined';
+ const variant = isCheckboxActive ? activeVariant : inactiveVariant;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(
+ inProps.color,
+ formControl?.error ? 'danger' : formControl?.color ?? colorProp,
+ );
+
const activeColor = color || 'primary';
const inactiveColor = color || 'neutral';
- const activeVariant = variant || 'solid';
- const inactiveVariant = variant || 'outlined';
const ownerState = {
...props,
@@ -254,7 +261,7 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) {
overlay,
focusVisible,
color: isCheckboxActive ? activeColor : inactiveColor,
- variant: isCheckboxActive ? activeVariant : inactiveVariant,
+ variant,
size,
};
diff --git a/packages/mui-joy/src/Checkbox/CheckboxProps.ts b/packages/mui-joy/src/Checkbox/CheckboxProps.ts
index 70e493bbc92da4..f8908b4b2fc01a 100644
--- a/packages/mui-joy/src/Checkbox/CheckboxProps.ts
+++ b/packages/mui-joy/src/Checkbox/CheckboxProps.ts
@@ -1,7 +1,7 @@
import * as React from 'react';
import { UseSwitchParameters } from '@mui/base/SwitchUnstyled';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types';
+import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types';
import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types';
export type CheckboxSlot = 'root' | 'checkbox' | 'action' | 'input' | 'label';
@@ -105,7 +105,7 @@ export type CheckboxProps<
},
> = OverrideProps, D>;
-export interface CheckboxOwnerState extends CheckboxProps {
+export interface CheckboxOwnerState extends ApplyColorInversion {
/**
* If `true`, the checkbox's focus is visible.
*/
diff --git a/packages/mui-joy/src/Checkbox/checkboxClasses.ts b/packages/mui-joy/src/Checkbox/checkboxClasses.ts
index 41c5a8725fdfcb..0d612a729a6759 100644
--- a/packages/mui-joy/src/Checkbox/checkboxClasses.ts
+++ b/packages/mui-joy/src/Checkbox/checkboxClasses.ts
@@ -31,6 +31,8 @@ export interface CheckboxClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `size="sm"`. */
sizeSm: string;
/** Styles applied to the root element if `size="md"`. */
@@ -67,6 +69,7 @@ const checkboxClasses: CheckboxClasses = generateUtilityClasses('JoyCheckbox', [
'colorNeutral',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'sizeSm',
'sizeMd',
'sizeLg',
diff --git a/packages/mui-joy/src/Chip/Chip.test.js b/packages/mui-joy/src/Chip/Chip.test.js
index ba26272452aeb7..7e6548bd2cd823 100644
--- a/packages/mui-joy/src/Chip/Chip.test.js
+++ b/packages/mui-joy/src/Chip/Chip.test.js
@@ -1,7 +1,12 @@
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
-import { createRenderer, describeConformance, fireEvent } from 'test/utils';
+import {
+ createRenderer,
+ describeConformance,
+ describeJoyColorInversion,
+ fireEvent,
+} from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import Chip, { chipClasses as classes } from '@mui/joy/Chip';
import { unstable_capitalize as capitalize } from '@mui/utils';
@@ -34,6 +39,8 @@ describe('', () => {
}),
);
+ describeJoyColorInversion(, { muiName: 'JoyChip', classes });
+
it('renders children', () => {
const { getByText } = render(
diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx
index a0eed3a96d5103..291428b3771604 100644
--- a/packages/mui-joy/src/Chip/Chip.tsx
+++ b/packages/mui-joy/src/Chip/Chip.tsx
@@ -6,6 +6,7 @@ import { OverridableComponent } from '@mui/types';
import { unstable_capitalize as capitalize, unstable_useId as useId } from '@mui/utils';
import { useThemeProps } from '../styles';
import styled from '../styles/styled';
+import { useColorInversion } from '../styles/ColorInversion';
import chipClasses, { getChipUtilityClass } from './chipClasses';
import { ChipProps, ChipOwnerState, ChipTypeMap } from './ChipProps';
import ChipContext from './ChipContext';
@@ -94,7 +95,7 @@ const ChipRoot = styled('div', {
verticalAlign: 'middle',
boxSizing: 'border-box',
[`&.${chipClasses.disabled}`]: {
- color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`],
+ color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color,
},
},
...(!ownerState.clickable
@@ -108,7 +109,7 @@ const ChipRoot = styled('div', {
: [
{
'--variant-borderWidth': '0px',
- color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`],
+ color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color,
},
]),
];
@@ -202,7 +203,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
const {
children,
className,
- color = 'primary',
+ color: colorProp = 'primary',
slotProps = {},
onClick,
disabled = false,
@@ -212,6 +213,8 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
endDecorator,
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const clickable = !!onClick || !!slotProps.action;
const ownerState: ChipOwnerState = {
@@ -284,7 +287,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
});
const chipContextValue = React.useMemo(
- () => ({ disabled, variant, color }),
+ () => ({ disabled, variant, color: color === 'context' ? undefined : color }),
[color, disabled, variant],
);
diff --git a/packages/mui-joy/src/Chip/ChipProps.ts b/packages/mui-joy/src/Chip/ChipProps.ts
index 848c6aff25ca8e..4a39ec66b9615c 100644
--- a/packages/mui-joy/src/Chip/ChipProps.ts
+++ b/packages/mui-joy/src/Chip/ChipProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types';
+import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
export type ChipSlot = 'root' | 'label' | 'action' | 'startDecorator' | 'endDecorator';
@@ -80,7 +80,7 @@ export type ChipProps<
P = { component?: React.ElementType },
> = OverrideProps, D>;
-export interface ChipOwnerState extends ChipProps {
+export interface ChipOwnerState extends ApplyColorInversion {
/**
* If `true`, the chip is clickable.
*/
diff --git a/packages/mui-joy/src/Chip/chipClasses.ts b/packages/mui-joy/src/Chip/chipClasses.ts
index 6c70e90450b5a0..53ecbf73b7d765 100644
--- a/packages/mui-joy/src/Chip/chipClasses.ts
+++ b/packages/mui-joy/src/Chip/chipClasses.ts
@@ -15,6 +15,8 @@ export interface ChipClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** State class applied to the root element if `disabled={true}`. */
disabled: string;
/** Styles applied to the endDecorator element if supplied. */
@@ -62,6 +64,7 @@ const chipClasses: ChipClasses = generateUtilityClasses('JoyChip', [
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'disabled',
'endDecorator',
'focusVisible',
diff --git a/packages/mui-joy/src/ChipDelete/ChipDelete.test.js b/packages/mui-joy/src/ChipDelete/ChipDelete.test.js
index 55fd5647ce561c..ad332a90a290dc 100644
--- a/packages/mui-joy/src/ChipDelete/ChipDelete.test.js
+++ b/packages/mui-joy/src/ChipDelete/ChipDelete.test.js
@@ -1,7 +1,13 @@
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
-import { createRenderer, describeConformance, act, fireEvent } from 'test/utils';
+import {
+ createRenderer,
+ describeConformance,
+ describeJoyColorInversion,
+ act,
+ fireEvent,
+} from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import Chip from '@mui/joy/Chip';
import ChipDelete, { chipDeleteClasses as classes } from '@mui/joy/ChipDelete';
@@ -22,6 +28,8 @@ describe('', () => {
skip: ['classesRoot', 'componentsProp'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyChipDelete', classes });
+
describe('Chip context', () => {
it('disabled', () => {
const { getByRole } = render(
diff --git a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx
index 19b5309dc467e9..afc5009e1dd622 100644
--- a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx
+++ b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx
@@ -6,6 +6,7 @@ import { unstable_composeClasses as composeClasses, useButton } from '@mui/base'
import { useSlotProps } from '@mui/base/utils';
import { useThemeProps } from '../styles';
import styled from '../styles/styled';
+import { useColorInversion } from '../styles/ColorInversion';
import Cancel from '../internal/svg-icons/Cancel';
import chipDeleteClasses, { getChipDeleteUtilityClass } from './chipDeleteClasses';
import { ChipDeleteProps, ChipDeleteOwnerState, ChipDeleteTypeMap } from './ChipDeleteProps';
@@ -82,8 +83,9 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) {
...other
} = props;
const chipContext = React.useContext(ChipContext);
- const color = colorProp || chipContext.color || 'primary';
const variant = variantProp || chipVariantMapping[chipContext.variant!] || 'solid';
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp || chipContext.color || 'primary');
const disabled = disabledProp ?? chipContext.disabled;
const buttonRef = React.useRef(null);
diff --git a/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts b/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts
index f94220f58e1001..870dafd8db3405 100644
--- a/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts
+++ b/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
export type ChipDeleteSlot = 'root';
@@ -49,7 +49,7 @@ export type ChipDeleteProps<
P = { component?: React.ElementType },
> = OverrideProps, D>;
-export interface ChipDeleteOwnerState extends ChipDeleteProps {
+export interface ChipDeleteOwnerState extends ApplyColorInversion {
/**
* If `true`, the element's focus is visible.
*/
diff --git a/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts b/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts
index 6b44ed513cc84e..da156ca19badc4 100644
--- a/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts
+++ b/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts
@@ -19,6 +19,8 @@ export interface ChipDeleteClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `variant="plain"`. */
variantPlain: string;
/** Styles applied to the root element if `variant="solid"`. */
@@ -43,6 +45,7 @@ const chipDeleteClasses: ChipDeleteClasses = generateUtilityClasses('JoyChipDele
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantSolid',
'variantSoft',
diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx
index 99422dcd1fb5e1..9f1d8f49f646c4 100644
--- a/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx
+++ b/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { createRenderer, describeConformance } from 'test/utils';
+import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import CircularProgress, { circularProgressClasses as classes } from '@mui/joy/CircularProgress';
import { unstable_capitalize as capitalize } from '@mui/utils';
@@ -28,6 +28,8 @@ describe('', () => {
skip: ['classesRoot', 'componentsProp'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyCircularProgress', classes });
+
describe('prop: determinate', () => {
it('should render a determinate circular progress', () => {
const { getByRole } = render();
diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx
index 9477c90cdeadb5..f239e32603fa20 100644
--- a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx
+++ b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx
@@ -7,6 +7,7 @@ import { unstable_composeClasses as composeClasses } from '@mui/base';
import { css, keyframes } from '@mui/system';
import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
+import { useColorInversion } from '../styles/ColorInversion';
import useSlot from '../utils/useSlot';
import { getCircularProgressUtilityClass } from './circularProgressClasses';
import {
@@ -206,7 +207,7 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref
const {
children,
className,
- color = 'primary',
+ color: colorProp = 'primary',
size = 'md',
variant = 'soft',
thickness,
@@ -214,6 +215,8 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref
value = determinate ? 0 : 25, // `25` is the 1/4 of the circle.
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const ownerState = {
...props,
diff --git a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts
index f1e053c23dc860..215e3f7f25c41d 100644
--- a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts
+++ b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types';
+import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
export type CircularProgressSlot = 'root' | 'svg' | 'track' | 'progress';
@@ -68,7 +68,7 @@ export type CircularProgressProps<
P = { component?: React.ElementType },
> = OverrideProps, D>;
-export interface CircularProgressOwnerState extends CircularProgressProps {
+export interface CircularProgressOwnerState extends ApplyColorInversion {
/**
* @internal the explicit size on the instance:
*/
diff --git a/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts b/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts
index fa3065de409a7a..deec0ccf079ea6 100644
--- a/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts
+++ b/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts
@@ -23,6 +23,8 @@ export interface CircularProgressClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `size="sm"`. */
sizeSm: string;
/** Styles applied to the root element if `size="md"`. */
@@ -59,6 +61,7 @@ const circularProgressClasses: CircularProgressClasses = generateUtilityClasses(
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'sizeSm',
'sizeMd',
'sizeLg',
diff --git a/packages/mui-joy/src/IconButton/IconButton.test.js b/packages/mui-joy/src/IconButton/IconButton.test.js
index 164e66081a72ec..033f9d85ec8657 100644
--- a/packages/mui-joy/src/IconButton/IconButton.test.js
+++ b/packages/mui-joy/src/IconButton/IconButton.test.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { describeConformance, createRenderer } from 'test/utils';
+import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils';
import IconButton, { iconButtonClasses as classes } from '@mui/joy/IconButton';
import { ThemeProvider } from '@mui/joy/styles';
@@ -21,6 +21,8 @@ describe('Joy ', () => {
skip: ['propsSpread', 'componentsProp', 'classesRoot'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyIconButton', classes });
+
it('by default, should render with the root, variantSolid, sizeMd and colorPrimary classes', () => {
const { getByRole } = render(Hello World);
const button = getByRole('button');
diff --git a/packages/mui-joy/src/IconButton/IconButton.tsx b/packages/mui-joy/src/IconButton/IconButton.tsx
index 5ba634b7627f70..2afbe401ffd568 100644
--- a/packages/mui-joy/src/IconButton/IconButton.tsx
+++ b/packages/mui-joy/src/IconButton/IconButton.tsx
@@ -4,6 +4,7 @@ import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef }
import { useButton } from '@mui/base/ButtonUnstyled';
import composeClasses from '@mui/base/composeClasses';
import { styled, useThemeProps } from '../styles';
+import { useColorInversion } from '../styles/ColorInversion';
import useSlot from '../utils/useSlot';
import iconButtonClasses, { getIconButtonUtilityClass } from './iconButtonClasses';
import { IconButtonOwnerState, IconButtonTypeMap, ExtendIconButton } from './IconButtonProps';
@@ -102,11 +103,13 @@ const IconButton = React.forwardRef(function IconButton(inProps, ref) {
children,
action,
component = 'button',
- color = 'primary',
+ color: colorProp = 'primary',
variant = 'soft',
size = 'md',
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const buttonRef = React.useRef(null);
const handleRef = useForkRef(buttonRef, ref);
diff --git a/packages/mui-joy/src/IconButton/IconButtonProps.ts b/packages/mui-joy/src/IconButton/IconButtonProps.ts
index 3aab381f8f04f8..80929e799bd854 100644
--- a/packages/mui-joy/src/IconButton/IconButtonProps.ts
+++ b/packages/mui-joy/src/IconButton/IconButtonProps.ts
@@ -5,7 +5,7 @@ import {
OverridableTypeMap,
OverrideProps,
} from '@mui/types';
-import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
export type IconButtonSlot = 'root';
@@ -73,7 +73,7 @@ export type IconButtonProps<
},
> = OverrideProps, D>;
-export interface IconButtonOwnerState extends IconButtonProps {
+export interface IconButtonOwnerState extends ApplyColorInversion {
/**
* If `true`, the element's focus is visible.
*/
diff --git a/packages/mui-joy/src/IconButton/iconButtonClasses.ts b/packages/mui-joy/src/IconButton/iconButtonClasses.ts
index 2d89f71fc67376..3028400351d277 100644
--- a/packages/mui-joy/src/IconButton/iconButtonClasses.ts
+++ b/packages/mui-joy/src/IconButton/iconButtonClasses.ts
@@ -15,6 +15,8 @@ export interface IconButtonClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `variant="plain"`. */
variantPlain: string;
/** Styles applied to the root element if `variant="outlined"`. */
@@ -49,6 +51,7 @@ const iconButtonClasses: IconButtonClasses = generateUtilityClasses('JoyIconButt
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantOutlined',
'variantSoft',
diff --git a/packages/mui-joy/src/Input/Input.test.js b/packages/mui-joy/src/Input/Input.test.js
index 5f876f581a5302..77d37fa9f47e35 100644
--- a/packages/mui-joy/src/Input/Input.test.js
+++ b/packages/mui-joy/src/Input/Input.test.js
@@ -1,7 +1,13 @@
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
-import { describeConformance, createRenderer, screen, act } from 'test/utils';
+import {
+ describeConformance,
+ describeJoyColorInversion,
+ createRenderer,
+ screen,
+ act,
+} from 'test/utils';
import Input, { inputClasses as classes } from '@mui/joy/Input';
import { ThemeProvider } from '@mui/joy/styles';
@@ -26,6 +32,8 @@ describe('Joy ', () => {
skip: ['propsSpread', 'componentsProp', 'classesRoot'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyInput', classes });
+
it('should have error classes', () => {
const { container } = render();
expect(container.firstChild).to.have.class(classes.error);
diff --git a/packages/mui-joy/src/Input/Input.tsx b/packages/mui-joy/src/Input/Input.tsx
index 3d4c6c6ceb4eac..1bd0a414566767 100644
--- a/packages/mui-joy/src/Input/Input.tsx
+++ b/packages/mui-joy/src/Input/Input.tsx
@@ -5,6 +5,7 @@ import { OverridableComponent } from '@mui/types';
import composeClasses from '@mui/base/composeClasses';
import { EventHandlers } from '@mui/base/utils';
import { styled, useThemeProps } from '../styles';
+import { useColorInversion } from '../styles/ColorInversion';
import useSlot from '../utils/useSlot';
import { InputTypeMap, InputProps, InputOwnerState } from './InputProps';
import inputClasses, { getInputUtilityClass } from './inputClasses';
@@ -289,7 +290,8 @@ const Input = React.forwardRef(function Input(inProps, ref) {
const error = inProps.error ?? formControl?.error ?? errorProp;
const size = inProps.size ?? formControl?.size ?? sizeProp;
- const color = error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, error ? 'danger' : formControl?.color ?? colorProp);
const ownerState = {
...props,
diff --git a/packages/mui-joy/src/Input/inputClasses.ts b/packages/mui-joy/src/Input/inputClasses.ts
index 8d11b6abbdcec8..e69e7b353ea00c 100644
--- a/packages/mui-joy/src/Input/inputClasses.ts
+++ b/packages/mui-joy/src/Input/inputClasses.ts
@@ -25,6 +25,8 @@ export interface InputClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `size="sm"`. */
sizeSm: string;
/** Styles applied to the root element if `size="md"`. */
@@ -68,6 +70,7 @@ const inputClasses: InputClasses = generateUtilityClasses('JoyInput', [
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'sizeSm',
'sizeMd',
'sizeLg',
diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx
index f5c135e0ecb978..721fd5912a9485 100644
--- a/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx
+++ b/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { createRenderer, describeConformance } from 'test/utils';
+import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import LinearProgress, { linearProgressClasses as classes } from '@mui/joy/LinearProgress';
import { unstable_capitalize as capitalize } from '@mui/utils';
@@ -19,6 +19,8 @@ describe('', () => {
skip: ['classesRoot', 'componentsProp'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyLinearProgress', classes });
+
describe('prop: determinate', () => {
it('should render a determinate circular progress', () => {
const { getByRole } = render();
diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx
index bfc14be306af41..66e897e7429416 100644
--- a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx
+++ b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx
@@ -7,6 +7,7 @@ import { unstable_composeClasses as composeClasses } from '@mui/base';
import { css, keyframes } from '@mui/system';
import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
+import { useColorInversion } from '../styles/ColorInversion';
import { getLinearProgressUtilityClass } from './linearProgressClasses';
import {
LinearProgressOwnerState,
@@ -146,7 +147,7 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) {
children,
className,
component,
- color = 'primary',
+ color: colorProp = 'primary',
size = 'md',
variant = 'soft',
thickness,
@@ -155,6 +156,8 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) {
style,
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const ownerState = {
...props,
diff --git a/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts b/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts
index b72f310b3000fe..090e9687da2dd0 100644
--- a/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts
+++ b/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types';
+import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types';
export type LinearProgressSlot = 'root';
@@ -56,7 +56,7 @@ export type LinearProgressProps<
P = { component?: React.ElementType },
> = OverrideProps, D>;
-export interface LinearProgressOwnerState extends LinearProgressProps {
+export interface LinearProgressOwnerState extends ApplyColorInversion {
/**
* @internal the explicit size on the instance:
*/
diff --git a/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts
index 9df29535022d2e..0e1437aa8bd3ad 100644
--- a/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts
+++ b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts
@@ -17,6 +17,8 @@ export interface LinearProgressClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Styles applied to the root element if `size="sm"`. */
sizeSm: string;
/** Styles applied to the root element if `size="md"`. */
@@ -48,6 +50,7 @@ const linearProgressClasses: LinearProgressClasses = generateUtilityClasses('Joy
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'sizeSm',
'sizeMd',
'sizeLg',
diff --git a/packages/mui-joy/src/Link/Link.test.js b/packages/mui-joy/src/Link/Link.test.js
index 858c8875e34b89..e7bb048c3d4739 100644
--- a/packages/mui-joy/src/Link/Link.test.js
+++ b/packages/mui-joy/src/Link/Link.test.js
@@ -1,7 +1,13 @@
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
-import { act, createRenderer, fireEvent, describeConformance } from 'test/utils';
+import {
+ act,
+ createRenderer,
+ fireEvent,
+ describeConformance,
+ describeJoyColorInversion,
+} from 'test/utils';
import Link, { linkClasses as classes } from '@mui/joy/Link';
import Typography from '@mui/joy/Typography';
import { ThemeProvider } from '@mui/joy/styles';
@@ -40,6 +46,8 @@ describe('', () => {
}),
);
+ describeJoyColorInversion(, { muiName: 'JoyLink', classes });
+
it('should render children', () => {
const { queryByText } = render(Home);
diff --git a/packages/mui-joy/src/Link/Link.tsx b/packages/mui-joy/src/Link/Link.tsx
index cf2336f9bba2c8..8b6e35cd0b8fee 100644
--- a/packages/mui-joy/src/Link/Link.tsx
+++ b/packages/mui-joy/src/Link/Link.tsx
@@ -10,6 +10,7 @@ import {
import { unstable_extendSxProp as extendSxProp } from '@mui/system';
import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
+import { useColorInversion } from '../styles/ColorInversion';
import useSlot from '../utils/useSlot';
import linkClasses, { getLinkUtilityClass } from './linkClasses';
import { LinkProps, LinkOwnerState, LinkTypeMap } from './LinkProps';
@@ -94,9 +95,11 @@ const LinkRoot = styled('a', {
borderRadius: theme.vars.radius.xs,
padding: 0, // Remove the padding in Firefox
cursor: 'pointer',
- textDecorationColor: `rgba(${
- theme.vars.palette[ownerState.color!]?.mainChannel
- } / var(--Link-underlineOpacity, 0.72))`,
+ ...(ownerState.color !== 'context' && {
+ textDecorationColor: `rgba(${
+ theme.vars.palette[ownerState.color!]?.mainChannel
+ } / var(--Link-underlineOpacity, 0.72))`,
+ }),
...(ownerState.variant
? {
paddingBlock: 'min(0.15em, 4px)',
@@ -106,10 +109,14 @@ const LinkRoot = styled('a', {
}),
}
: {
- color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)`,
+ ...(ownerState.color !== 'context' && {
+ color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)`,
+ }),
[`&.${linkClasses.disabled}`]: {
pointerEvents: 'none',
- color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 0.6)`,
+ ...(ownerState.color !== 'context' && {
+ color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 0.6)`,
+ }),
},
}),
userSelect: 'none',
@@ -153,14 +160,17 @@ const LinkRoot = styled('a', {
const Link = React.forwardRef(function Link(inProps, ref) {
const {
- color = 'primary',
+ color: colorProp = 'primary',
textColor,
+ variant,
...themeProps
} = useThemeProps({
props: inProps,
name: 'JoyLink',
});
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const nested = React.useContext(TypographyContext);
const props = extendSxProp({ ...themeProps, color: textColor }) as LinkProps;
@@ -173,7 +183,6 @@ const Link = React.forwardRef(function Link(inProps, ref) {
level: levelProp = 'body1',
overlay = false,
underline = 'hover',
- variant,
endDecorator,
startDecorator,
...other
diff --git a/packages/mui-joy/src/Link/LinkProps.ts b/packages/mui-joy/src/Link/LinkProps.ts
index 9362780a4e396b..93e5ec8857cd2a 100644
--- a/packages/mui-joy/src/Link/LinkProps.ts
+++ b/packages/mui-joy/src/Link/LinkProps.ts
@@ -4,6 +4,7 @@ import {
ColorPaletteProp,
SxProps,
SystemProps,
+ ApplyColorInversion,
TypographySystem,
VariantProp,
} from '../styles/types';
@@ -91,7 +92,7 @@ export type LinkProps<
},
> = OverrideProps, D>;
-export interface LinkOwnerState extends LinkProps {
+export interface LinkOwnerState extends ApplyColorInversion {
/**
* If `true`, the element's focus is visible.
*/
diff --git a/packages/mui-joy/src/Link/linkClasses.ts b/packages/mui-joy/src/Link/linkClasses.ts
index 0fa388bdc91d78..bcde0b06ab16c3 100644
--- a/packages/mui-joy/src/Link/linkClasses.ts
+++ b/packages/mui-joy/src/Link/linkClasses.ts
@@ -15,6 +15,8 @@ export interface LinkClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** State class applied to the root element if `disabled={true}`. */
disabled: string;
/** State class applied to the root element if the link is keyboard focused. */
@@ -73,6 +75,7 @@ const linkClasses: LinkClasses = generateUtilityClasses('JoyLink', [
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'focusVisible',
'variantPlain',
'variantOutlined',
diff --git a/packages/mui-joy/src/List/List.test.js b/packages/mui-joy/src/List/List.test.js
index 6ed684e29fa6ad..948f7d06762ade 100644
--- a/packages/mui-joy/src/List/List.test.js
+++ b/packages/mui-joy/src/List/List.test.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { describeConformance, createRenderer, screen } from 'test/utils';
+import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import List, { listClasses as classes } from '@mui/joy/List';
import ListItem from '@mui/joy/ListItem';
@@ -24,6 +24,8 @@ describe('Joy
', () => {
skip: ['componentsProp', 'classesRoot'],
}));
+ describeJoyColorInversion(
, { muiName: 'JoyList', classes });
+
it('should have root className', () => {
const { container } = render(
);
expect(container.firstChild).to.have.class(classes.root);
diff --git a/packages/mui-joy/src/List/List.tsx b/packages/mui-joy/src/List/List.tsx
index b4af0a8cde9116..477b4d3d6f89a7 100644
--- a/packages/mui-joy/src/List/List.tsx
+++ b/packages/mui-joy/src/List/List.tsx
@@ -7,6 +7,7 @@ import composeClasses from '@mui/base/composeClasses';
import { MenuUnstyledContext } from '@mui/base/MenuUnstyled';
import { SelectUnstyledContext } from '@mui/base/SelectUnstyled';
import { styled, useThemeProps } from '../styles';
+import { useColorInversion } from '../styles/ColorInversion';
import { ListProps, ListOwnerState, ListTypeMap } from './ListProps';
import { getListUtilityClass } from './listClasses';
import NestedListContext from './NestedListContext';
@@ -160,10 +161,12 @@ const List = React.forwardRef(function List(inProps, ref) {
row = false,
wrap = false,
variant = 'plain',
- color = 'neutral',
+ color: colorProp = 'neutral',
role: roleProp,
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
let role;
if (menuContext || selectContext) {
diff --git a/packages/mui-joy/src/List/ListProps.ts b/packages/mui-joy/src/List/ListProps.ts
index 4eb6123163ee63..e0c5636694edc1 100644
--- a/packages/mui-joy/src/List/ListProps.ts
+++ b/packages/mui-joy/src/List/ListProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
export type ListSlot = 'root';
@@ -56,7 +56,7 @@ export type ListProps<
},
> = OverrideProps, D>;
-export interface ListOwnerState extends ListProps {
+export interface ListOwnerState extends ApplyColorInversion {
/**
* @internal
* The explicit size specified on the element instance.
diff --git a/packages/mui-joy/src/List/listClasses.ts b/packages/mui-joy/src/List/listClasses.ts
index 574d3ce2c3220a..431c29214f62c3 100644
--- a/packages/mui-joy/src/List/listClasses.ts
+++ b/packages/mui-joy/src/List/listClasses.ts
@@ -27,6 +27,8 @@ export interface ListClasses {
colorSuccess: string;
/** Classname applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** Classname applied to the root element if `variant="plain"`. */
variantPlain: string;
/** Classname applied to the root element if `variant="outlined"`. */
@@ -57,6 +59,7 @@ const listClasses: ListClasses = generateUtilityClasses('JoyList', [
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantOutlined',
'variantSoft',
diff --git a/packages/mui-joy/src/ListItem/ListItem.test.js b/packages/mui-joy/src/ListItem/ListItem.test.js
index ec32e3140f1660..bc10d7db269953 100644
--- a/packages/mui-joy/src/ListItem/ListItem.test.js
+++ b/packages/mui-joy/src/ListItem/ListItem.test.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { describeConformance, createRenderer, screen } from 'test/utils';
+import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import MenuList from '@mui/joy/MenuList';
import List from '@mui/joy/List';
@@ -27,6 +27,8 @@ describe('Joy ', () => {
skip: ['componentsProp', 'classesRoot'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyListItem', classes });
+
it('should have root className', () => {
const { container } = render();
expect(container.firstChild).to.have.class(classes.root);
diff --git a/packages/mui-joy/src/ListItem/ListItem.tsx b/packages/mui-joy/src/ListItem/ListItem.tsx
index b3f7dd57e38124..715af23dd0c435 100644
--- a/packages/mui-joy/src/ListItem/ListItem.tsx
+++ b/packages/mui-joy/src/ListItem/ListItem.tsx
@@ -9,8 +9,9 @@ import { OverridableComponent } from '@mui/types';
import composeClasses from '@mui/base/composeClasses';
import { MenuUnstyledContext } from '@mui/base/MenuUnstyled';
import { styled, useThemeProps } from '../styles';
+import { useColorInversion } from '../styles/ColorInversion';
import useSlot from '../utils/useSlot';
-import { ListItemProps, ListItemOwnerState, ListItemTypeMap } from './ListItemProps';
+import { ListItemOwnerState, ListItemTypeMap } from './ListItemProps';
import { getListItemUtilityClass } from './listItemClasses';
import NestedListContext from '../List/NestedListContext';
import RowListContext from '../List/RowListContext';
@@ -112,7 +113,7 @@ const ListItemStartAction = styled('div', {
name: 'JoyListItem',
slot: 'StartAction',
overridesResolver: (props, styles) => styles.startAction,
-})<{ ownerState: ListItemProps }>(({ ownerState }) => ({
+})<{ ownerState: ListItemOwnerState }>(({ ownerState }) => ({
display: 'inherit',
position: 'absolute',
top: ownerState.nested ? 'calc(var(--List-item-minHeight) / 2)' : '50%',
@@ -125,7 +126,7 @@ const ListItemEndAction = styled('div', {
name: 'JoyListItem',
slot: 'StartAction',
overridesResolver: (props, styles) => styles.startAction,
-})<{ ownerState: ListItemProps }>(({ ownerState }) => ({
+})<{ ownerState: ListItemOwnerState }>(({ ownerState }) => ({
display: 'inherit',
position: 'absolute',
top: ownerState.nested ? 'calc(var(--List-item-minHeight) / 2)' : '50%',
@@ -153,12 +154,14 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) {
nested = false,
sticky = false,
variant = 'plain',
- color = 'neutral',
+ color: colorProp = 'neutral',
startAction,
endAction,
role: roleProp,
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const [subheaderId, setSubheaderId] = React.useState('');
@@ -178,6 +181,7 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) {
}
const ownerState = {
+ ...props,
sticky,
startAction,
endAction,
@@ -189,7 +193,6 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) {
nested,
component,
role,
- ...props,
};
const classes = useUtilityClasses(ownerState);
diff --git a/packages/mui-joy/src/ListItem/ListItemProps.ts b/packages/mui-joy/src/ListItem/ListItemProps.ts
index 171328792a7888..2dc941b5e8e9ba 100644
--- a/packages/mui-joy/src/ListItem/ListItemProps.ts
+++ b/packages/mui-joy/src/ListItem/ListItemProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types';
+import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
export type ListItemSlot = 'root' | 'startAction' | 'endAction';
@@ -67,7 +67,7 @@ export type ListItemProps<
},
> = OverrideProps, D>;
-export interface ListItemOwnerState extends ListItemProps {
+export interface ListItemOwnerState extends ApplyColorInversion {
/**
* If `true`, the element is rendered in a horizontal list.
* @internal
diff --git a/packages/mui-joy/src/ListItem/listItemClasses.ts b/packages/mui-joy/src/ListItem/listItemClasses.ts
index f5feeaebd3fdb7..0dd5c74faa9c56 100644
--- a/packages/mui-joy/src/ListItem/listItemClasses.ts
+++ b/packages/mui-joy/src/ListItem/listItemClasses.ts
@@ -25,6 +25,8 @@ export interface ListItemClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** State class applied to the root element if `variant="plain"`. */
variantPlain: string;
/** State class applied to the root element if `variant="soft"`. */
@@ -54,6 +56,7 @@ const listItemClasses: ListItemClasses = generateUtilityClasses('JoyListItem', [
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantSoft',
'variantOutlined',
diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.test.js b/packages/mui-joy/src/ListItemButton/ListItemButton.test.js
index abacdc0b318c0d..b3073e17e32d43 100644
--- a/packages/mui-joy/src/ListItemButton/ListItemButton.test.js
+++ b/packages/mui-joy/src/ListItemButton/ListItemButton.test.js
@@ -1,6 +1,12 @@
import * as React from 'react';
import { expect } from 'chai';
-import { describeConformance, createRenderer, act, fireEvent } from 'test/utils';
+import {
+ describeConformance,
+ describeJoyColorInversion,
+ createRenderer,
+ act,
+ fireEvent,
+} from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import ListItemButton, { listItemButtonClasses as classes } from '@mui/joy/ListItemButton';
@@ -19,6 +25,8 @@ describe('Joy ', () => {
skip: ['componentsProp', 'classesRoot'],
}));
+ describeJoyColorInversion(, { muiName: 'JoyListItemButton', classes });
+
it('should render with the selected class', () => {
const { getByRole } = render();
expect(getByRole('button')).to.have.class(classes.selected);
diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx
index a80e2aab288491..1489bb92218028 100644
--- a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx
+++ b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx
@@ -5,6 +5,7 @@ import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef }
import composeClasses from '@mui/base/composeClasses';
import { useButton } from '@mui/base/ButtonUnstyled';
import { styled, useThemeProps } from '../styles';
+import { useColorInversion } from '../styles/ColorInversion';
import {
ListItemButtonOwnerState,
ExtendListItemButton,
@@ -45,7 +46,7 @@ export const StyledListItemButton = styled('div')<{ ownerState: ListItemButtonOw
}),
...(ownerState.disabled && {
'--List-decorator-color':
- theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`],
+ theme.variants?.[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color,
}),
WebkitTapHighlightColor: 'transparent',
boxSizing: 'border-box',
@@ -120,11 +121,14 @@ const ListItemButton = React.forwardRef(function ListItemButton(inProps, ref) {
orientation = 'horizontal',
role,
selected = false,
- color = selected ? 'primary' : 'neutral',
+ color: colorProp = selected ? 'primary' : 'neutral',
variant = 'plain',
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
+
const buttonRef = React.useRef(null);
const handleRef = useForkRef(buttonRef, ref);
diff --git a/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts b/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts
index ecbfdd857f73b5..853bd6a41b7036 100644
--- a/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts
+++ b/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts
@@ -5,7 +5,7 @@ import {
OverridableTypeMap,
OverrideProps,
} from '@mui/types';
-import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
import { ListItemButtonClasses } from './listItemButtonClasses';
export type ListItemButtonSlot = 'root';
@@ -94,7 +94,7 @@ export type ListItemButtonProps<
},
> = OverrideProps, D>;
-export interface ListItemButtonOwnerState extends ListItemButtonProps {
+export interface ListItemButtonOwnerState extends ApplyColorInversion {
/**
* If `true`, the element's focus is visible.
*/
diff --git a/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx b/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx
index e2624ce6d676cd..ff7eb82c9ef59e 100644
--- a/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx
+++ b/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
-import { describeConformance, createRenderer } from 'test/utils';
+import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import ListSubheader, { listSubheaderClasses as classes } from '@mui/joy/ListSubheader';
import ListSubheaderDispatch from './ListSubheaderContext';
@@ -21,6 +21,11 @@ describe('Joy ', () => {
skip: ['componentsProp', 'classesRoot'],
}));
+ describeJoyColorInversion(, {
+ muiName: 'JoyListSubheader',
+ classes,
+ });
+
it('should have root className', () => {
const { container } = render();
expect(container.firstChild).to.have.class(classes.root);
diff --git a/packages/mui-joy/src/ListSubheader/ListSubheader.tsx b/packages/mui-joy/src/ListSubheader/ListSubheader.tsx
index 0ac1ce43fe88e0..1d34452f0b114c 100644
--- a/packages/mui-joy/src/ListSubheader/ListSubheader.tsx
+++ b/packages/mui-joy/src/ListSubheader/ListSubheader.tsx
@@ -5,6 +5,7 @@ import { OverridableComponent } from '@mui/types';
import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui/utils';
import composeClasses from '@mui/base/composeClasses';
import { styled, useThemeProps } from '../styles';
+import { useColorInversion } from '../styles/ColorInversion';
import { ListSubheaderOwnerState, ListSubheaderTypeMap } from './ListSubheaderProps';
import { getListSubheaderUtilityClass } from './listSubheaderClasses';
import ListSubheaderDispatch from './ListSubheaderContext';
@@ -47,9 +48,10 @@ const ListSubheaderRoot = styled('div', {
zIndex: 1,
background: 'var(--List-item-stickyBackground)',
}),
- color: ownerState.color
- ? `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)`
- : theme.vars.palette.text.tertiary,
+ color:
+ ownerState.color && ownerState.color !== 'context'
+ ? `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)`
+ : theme.vars.palette.text.tertiary,
...theme.variants[ownerState.variant!]?.[ownerState.color!],
}));
@@ -66,9 +68,11 @@ const ListSubheader = React.forwardRef(function ListSubheader(inProps, ref) {
id: idOverride,
sticky = false,
variant,
- color,
+ color: colorProp,
...other
} = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
const id = useId(idOverride);
const setSubheaderId = React.useContext(ListSubheaderDispatch);
diff --git a/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts b/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts
index 049653316bd689..c0e5a2e18d33e2 100644
--- a/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts
+++ b/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
import { OverridableStringUnion, OverrideProps } from '@mui/types';
-import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
export type ListSubheaderSlot = 'root';
@@ -42,4 +42,4 @@ export type ListSubheaderProps<
},
> = OverrideProps, D>;
-export interface ListSubheaderOwnerState extends ListSubheaderProps {}
+export interface ListSubheaderOwnerState extends ApplyColorInversion {}
diff --git a/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts b/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts
index 5662fa2210b053..de8f9045987e8a 100644
--- a/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts
+++ b/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts
@@ -17,6 +17,8 @@ export interface ListSubheaderClasses {
colorSuccess: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
/** State class applied to the root element if `variant="plain"`. */
variantPlain: string;
/** State class applied to the root element if `variant="soft"`. */
@@ -42,6 +44,7 @@ const listSubheaderClasses: ListSubheaderClasses = generateUtilityClasses('JoyLi
'colorInfo',
'colorSuccess',
'colorWarning',
+ 'colorContext',
'variantPlain',
'variantSoft',
'variantOutlined',
diff --git a/packages/mui-joy/src/Menu/Menu.test.js b/packages/mui-joy/src/Menu/Menu.test.js
index 0af5376984aa75..5887bc3724a3e2 100644
--- a/packages/mui-joy/src/Menu/Menu.test.js
+++ b/packages/mui-joy/src/Menu/Menu.test.js
@@ -1,7 +1,14 @@
import * as React from 'react';
import { spy } from 'sinon';
import { expect } from 'chai';
-import { act, createRenderer, describeConformance, screen, fireEvent } from 'test/utils';
+import {
+ act,
+ createRenderer,
+ describeConformance,
+ screen,
+ fireEvent,
+ describeJoyColorInversion,
+} from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import Menu, { menuClasses as classes } from '@mui/joy/Menu';
import MenuItem from '@mui/joy/MenuItem';
@@ -30,6 +37,20 @@ describe('Joy ', () => {
],
}));
+ describeJoyColorInversion(
+ )}
+ {renderComponents(
+
+ 25%
+ ,
+ )}
+ {renderComponents()}
+ {renderComponents(
+ ,
+ )}
+ {renderComponents(Link)}
+
+ Item 1
+
+ Item 2
+ Item 3
+
+ {renderComponents(Item)}
+ {renderComponents(Item)}
+ {renderComponents(Subheader)}
+ {renderComponents(
+ ,
+ )}
+ {renderComponents()}
+ {renderComponents()}
+ {renderComponents()}
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+
+ {renderComponents()}
+ {renderComponents(Text)}
+
+ );
+ return (
+
+
+
+ {elements}
+
+
+ {elements}
+
+
+ );
+}
diff --git a/test/regressions/fixtures/JoyColorInversion/JoyColorInversionPortal.js b/test/regressions/fixtures/JoyColorInversion/JoyColorInversionPortal.js
new file mode 100644
index 00000000000000..b4555a43466169
--- /dev/null
+++ b/test/regressions/fixtures/JoyColorInversion/JoyColorInversionPortal.js
@@ -0,0 +1,125 @@
+import * as React from 'react';
+import { CssVarsProvider } from '@mui/joy/styles';
+import CssBaseline from '@mui/joy/CssBaseline';
+import Autocomplete from '@mui/joy/Autocomplete';
+import Button from '@mui/joy/Button';
+import Card from '@mui/joy/Card';
+import Menu from '@mui/joy/Menu';
+import MenuItem from '@mui/joy/MenuItem';
+import ListDivider from '@mui/joy/ListDivider';
+import Tooltip from '@mui/joy/Tooltip';
+import Stack from '@mui/joy/Stack';
+import Select from '@mui/joy/Select';
+import Option from '@mui/joy/Option';
+
+const films = [
+ { label: 'The Shawshank Redemption', year: 1994 },
+ { label: 'The Godfather', year: 1972 },
+ { label: 'The Godfather: Part II', year: 1974 },
+ { label: 'The Dark Knight', year: 2008 },
+ { label: '12 Angry Men', year: 1957 },
+ { label: "Schindler's List", year: 1993 },
+ { label: 'Pulp Fiction', year: 1994 },
+];
+
+export default function ColorInversionPopup() {
+ const [menuButton, setMenuButton] = React.useState(null);
+ const [menuButton2, setMenuButton2] = React.useState(null);
+ return (
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+ );
+}
diff --git a/test/utils/describeJoyColorInversion.tsx b/test/utils/describeJoyColorInversion.tsx
new file mode 100644
index 00000000000000..318f62621007ed
--- /dev/null
+++ b/test/utils/describeJoyColorInversion.tsx
@@ -0,0 +1,125 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { ThemeProvider } from '@mui/joy/styles';
+import Sheet from '@mui/joy/Sheet';
+import { createRenderer, MuiRenderResult } from './createRenderer';
+
+/**
+ * Test suite for checking the color inversion conformance for a component.
+ *
+ * Specify the `data-testid="test-element"` to the slot that apply `colorContext` className
+ * otherwise the root slot will be used as a test element.
+ */
+export default function describeJoyColorInversion(
+ element: React.ReactElement,
+ options: {
+ muiName: string;
+ classes: { colorContext: string; colorPrimary: string; colorSuccess: string };
+ wrapper?: (node: React.ReactElement) => React.ReactElement;
+ portalSlot?: string;
+ },
+) {
+ const { classes, muiName, wrapper = (node) => node, portalSlot } = options;
+ const { render } = createRenderer();
+ const getTestElement = (result: MuiRenderResult, id = '') => {
+ const { container, queryByTestId } = result;
+ let testElement = queryByTestId('test-element') ?? container.firstChild?.firstChild;
+ if (id) {
+ testElement = queryByTestId(id) ?? testElement;
+ }
+ return testElement;
+ };
+ describe('Color Inversion', () => {
+ describe('Feature enabled', () => {
+ it('implicit color value', () => {
+ const result = render(
+
+
+ {wrapper(element)}
+
+ ,
+ );
+ expect(getTestElement(result)).to.have.class(classes.colorContext);
+ });
+
+ it('implicit color with theme default color', () => {
+ const result = render(
+
+
+ {wrapper(element)}
+
+ ,
+ );
+ expect(getTestElement(result)).to.have.class(classes.colorContext);
+ });
+ });
+
+ describe('Portal slot', () => {
+ if (!portalSlot) {
+ return;
+ }
+
+ const getProps = (disablePortal: boolean) =>
+ portalSlot === 'root'
+ ? {
+ disablePortal,
+ ...(!element.props.slotProps?.root?.['data-testid'] && {
+ 'data-testid': 'test-portal',
+ }),
+ }
+ : {
+ slotProps: {
+ ...element.props.slotProps,
+ [portalSlot]: {
+ ...element.props.slotProps?.[portalSlot],
+ disablePortal,
+ 'data-testid': 'test-portal',
+ },
+ },
+ };
+
+ it('If `disablePortal` is false, color inversion should NOT apply', () => {
+ const result = render(
+
+
+ {wrapper(React.cloneElement(element, getProps(false)))}
+
+ ,
+ );
+ expect(getTestElement(result, 'test-portal')).not.to.have.class(classes.colorContext);
+ });
+
+ it('If `disablePortal` is true, color inversion should WORK', () => {
+ const result = render(
+
+
+ {wrapper(React.cloneElement(element, getProps(true)))}
+
+ ,
+ );
+ expect(getTestElement(result, 'test-portal')).to.have.class(classes.colorContext);
+ });
+ });
+
+ it('should use instance color', () => {
+ const result = render(
+
+
+ {wrapper(React.cloneElement(element, { color: 'primary' }))}
+
+ ,
+ );
+ expect(getTestElement(result)).to.have.class(classes.colorPrimary);
+ });
+ });
+}
diff --git a/test/utils/index.js b/test/utils/index.js
index b5ead3436a0483..50fcac6d4c93ee 100644
--- a/test/utils/index.js
+++ b/test/utils/index.js
@@ -3,6 +3,7 @@ import * as React from 'react';
export * from './components';
export { default as describeConformance } from './describeConformance';
export { default as describeConformanceUnstyled } from './describeConformanceUnstyled';
+export { default as describeJoyColorInversion } from './describeJoyColorInversion';
export { default as createDescribe } from './createDescribe';
export * from './createRenderer';
export { default as createMount } from './createMount';