>(undefined);
-
-export const useColorInversion = (childVariant: VariantProp | undefined) => {
- const overridableVariants = React.useContext(ColorInversion);
- return {
- /**
- * Resolve the `color` value for the component.
- * @param {ColorPaletteProp | 'inherit' | undefined} instanceColorProp The color defined on the instance.
- * @param {ColorPaletteProp | 'inherit' | undefined} defaultColorProp The default color to use when variant inversion is not enabled.
- */
- getColor: (
- instanceColorProp: ColorPaletteProp | 'inherit' | undefined,
- defaultColorProp: ColorPaletteProp | 'inherit' | undefined,
- ): ColorPaletteProp | 'context' | undefined => {
- if (overridableVariants && childVariant) {
- if (overridableVariants.includes(childVariant)) {
- // @ts-ignore internal logic
- return instanceColorProp || 'context';
- }
- }
- // @ts-ignore internal logic
- return instanceColorProp || defaultColorProp;
- },
- };
-};
-
-interface ColorInversionProviderProps {
- children: React.ReactNode;
- variant?: VariantProp;
-}
-
-export function ColorInversionProvider({ children, variant }: ColorInversionProviderProps) {
- const theme = useTheme();
- return (
-
- {children}
-
- );
-}
-
-export default ColorInversion;
diff --git a/packages/mui-joy/src/styles/CssVarsProvider.test.tsx b/packages/mui-joy/src/styles/CssVarsProvider.test.tsx
index 7d139fda1b3ec7..6bf22459d370ba 100644
--- a/packages/mui-joy/src/styles/CssVarsProvider.test.tsx
+++ b/packages/mui-joy/src/styles/CssVarsProvider.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { expect } from 'chai';
import { createRenderer, screen } from '@mui-internal/test-utils';
-import { CssVarsProvider, extendTheme, useTheme, shouldSkipGeneratingVar } from '@mui/joy/styles';
+import { CssVarsProvider, useTheme, shouldSkipGeneratingVar } from '@mui/joy/styles';
describe('[Joy] CssVarsProvider', () => {
let originalMatchmedia: typeof window.matchMedia;
@@ -439,68 +439,6 @@ describe('[Joy] CssVarsProvider', () => {
});
});
- describe('Color Inversion', () => {
- it('should be customizable', () => {
- function Text() {
- const theme = useTheme();
- return {theme.colorInversion.solid.primary['--variant-plain'] as string}
;
- }
-
- const { container } = render(
-
-
- ,
- );
-
- expect(container.firstChild?.textContent).to.equal('black');
- });
-
- it('should be customizable with a callback', () => {
- function Text() {
- const theme = useTheme();
- return (
-
- {
- (theme.colorInversion.soft.primary['[data-joy-color-scheme="dark"] &'] as any)[
- '--variant-plain'
- ]
- }
-
- );
- }
-
- const { container } = render(
- ({
- soft: {
- primary: {
- [theme.getColorSchemeSelector('dark')]: {
- '--variant-plain': 'red',
- },
- },
- },
- }),
- })}
- >
-
- ,
- );
-
- expect(container.firstChild?.textContent).to.equal('red');
- });
- });
-
describe('Focus', () => {
it('contain expected focus', function test() {
function Text() {
@@ -571,21 +509,6 @@ describe('[Joy] CssVarsProvider', () => {
].join(','),
);
});
-
- it('contain expected colorInversion', function test() {
- function Text() {
- const theme = useTheme();
- return {Object.keys(theme.colorInversion).join(',')}
;
- }
-
- const { container } = render(
-
-
- ,
- );
-
- expect(container.firstChild?.textContent).to.equal(['soft', 'solid'].join(','));
- });
});
describe('Spacing', () => {
diff --git a/packages/mui-joy/src/styles/CssVarsProvider.tsx b/packages/mui-joy/src/styles/CssVarsProvider.tsx
index 0195d61b27ea59..4d5a9ac4e3f260 100644
--- a/packages/mui-joy/src/styles/CssVarsProvider.tsx
+++ b/packages/mui-joy/src/styles/CssVarsProvider.tsx
@@ -1,10 +1,7 @@
'use client';
-import { deepmerge } from '@mui/utils';
import { unstable_createCssVarsProvider as createCssVarsProvider } from '@mui/system';
import defaultTheme from './defaultTheme';
-import { CssVarsThemeOptions } from './extendTheme';
-import { createSoftInversion, createSolidInversion } from './variantUtils';
-import type { Theme, DefaultColorScheme, ExtendedColorScheme } from './types';
+import type { DefaultColorScheme, ExtendedColorScheme } from './types';
import THEME_ID from './identifier';
const { CssVarsProvider, useColorScheme, getInitColorSchemeScript } = createCssVarsProvider<
@@ -20,20 +17,6 @@ const { CssVarsProvider, useColorScheme, getInitColorSchemeScript } = createCssV
light: 'light',
dark: 'dark',
},
- resolveTheme: (mergedTheme: Theme) => {
- const colorInversionInput = mergedTheme.colorInversion as CssVarsThemeOptions['colorInversion'];
- mergedTheme.colorInversion = deepmerge(
- {
- soft: createSoftInversion(mergedTheme),
- solid: createSolidInversion(mergedTheme),
- },
- typeof colorInversionInput === 'function'
- ? colorInversionInput(mergedTheme)
- : colorInversionInput,
- { clone: false },
- );
- return mergedTheme;
- },
});
export { CssVarsProvider, useColorScheme, getInitColorSchemeScript };
diff --git a/packages/mui-joy/src/styles/defaultTheme.test.js b/packages/mui-joy/src/styles/defaultTheme.test.js
index d0d8ce22df80a8..154e11cbd2707a 100644
--- a/packages/mui-joy/src/styles/defaultTheme.test.js
+++ b/packages/mui-joy/src/styles/defaultTheme.test.js
@@ -24,8 +24,6 @@ describe('defaultTheme', () => {
'zIndex',
'typography',
'variants',
- 'colorInversion',
- 'colorInversionConfig',
'vars',
'cssVarPrefix',
'getColorSchemeSelector',
diff --git a/packages/mui-joy/src/styles/extendTheme.spec.ts b/packages/mui-joy/src/styles/extendTheme.spec.ts
index 73e42542bd3583..38c31364a17de1 100644
--- a/packages/mui-joy/src/styles/extendTheme.spec.ts
+++ b/packages/mui-joy/src/styles/extendTheme.spec.ts
@@ -71,16 +71,6 @@ import { TooltipOwnerState } from '@mui/joy/Tooltip';
import { TypographyOwnerState } from '@mui/joy/Typography';
extendTheme({
- colorInversion: (theme) => ({
- soft: {
- primary: {
- '--variant-plainColor': `rgba(${theme.getCssVar('palette-primary-darkChannel')} / 0.4)`,
- [theme.getColorSchemeSelector('dark')]: {
- '--variant-plainColor': `rgba(${theme.getCssVar('palette-primary-lightChannel')} / 0.4)`,
- },
- },
- },
- }),
components: {
JoyAccordion: {
defaultProps: {
diff --git a/packages/mui-joy/src/styles/extendTheme.test.js b/packages/mui-joy/src/styles/extendTheme.test.js
index 88a47614004503..7568b02be2dbcf 100644
--- a/packages/mui-joy/src/styles/extendTheme.test.js
+++ b/packages/mui-joy/src/styles/extendTheme.test.js
@@ -22,13 +22,11 @@ describe('extendTheme', () => {
'shadow',
'zIndex',
'typography',
- 'colorInversionConfig',
'variants',
'cssVarPrefix',
'palette',
'vars',
'getColorSchemeSelector',
- 'colorInversion',
'unstable_sxConfig',
'unstable_sx',
'shouldSkipGeneratingVar',
diff --git a/packages/mui-joy/src/styles/extendTheme.ts b/packages/mui-joy/src/styles/extendTheme.ts
index 20193542959c65..818cd124fcbdb0 100644
--- a/packages/mui-joy/src/styles/extendTheme.ts
+++ b/packages/mui-joy/src/styles/extendTheme.ts
@@ -17,11 +17,11 @@ import { DefaultColorScheme, ExtendedColorScheme, SupportedColorScheme } from '.
import { ColorSystem, ColorPaletteProp, Palette, PaletteOptions } from './types/colorSystem';
import { Focus } from './types/focus';
import { TypographySystemOptions, FontSize } from './types/typography';
-import { Variants, ColorInversion, ColorInversionConfig } from './types/variants';
+import { Variants } from './types/variants';
import { Theme, ThemeCssVar, ThemeScalesOptions, SxProps, ThemeVars } from './types';
import { Components } from './components';
import { generateUtilityClass } from '../className';
-import { createSoftInversion, createSolidInversion, createVariant } from './variantUtils';
+import { createVariant } from './variantUtils';
import { MergeDefault } from './types/utils';
type Partial2Level = {
@@ -66,10 +66,6 @@ export interface CssVarsThemeOptions extends Partial2Level {
focus?: Partial;
typography?: Partial;
variants?: Partial2Level;
- colorInversion?:
- | Partial2Level
- | ((theme: Theme) => Partial2Level);
- colorInversionConfig?: ColorInversionConfig;
breakpoints?: BreakpointsOptions;
spacing?: SpacingOptions;
components?: Components;
@@ -95,7 +91,6 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme {
spacing,
components: componentsInput,
variants: variantsInput,
- colorInversion: colorInversionInput,
shouldSkipGeneratingVar = defaultShouldSkipGeneratingVar,
...scalesInput
} = themeOptions || {};
@@ -549,13 +544,9 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme {
color: `var(--Icon-color, ${theme.vars.palette.text.icon})`,
...(ownerState.color &&
ownerState.color !== 'inherit' &&
- ownerState.color !== 'context' &&
themeProp.vars.palette[ownerState.color!] && {
color: `rgba(${themeProp.vars.palette[ownerState.color]?.mainChannel} / 1)`,
}),
- ...(ownerState.color === 'context' && {
- color: themeProp.vars.palette.text.secondary,
- }),
}),
...(instanceFontSize &&
instanceFontSize !== 'inherit' && {
@@ -571,10 +562,6 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme {
cssVarPrefix,
getCssVar,
spacing: createSpacing(spacing),
- colorInversionConfig: {
- soft: ['plain', 'outlined', 'soft', 'solid'],
- solid: ['plain', 'outlined', 'soft', 'solid'],
- },
} as unknown as Theme; // Need type casting due to module augmentation inside the repo
/**
@@ -674,18 +661,5 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme {
theme.shouldSkipGeneratingVar = shouldSkipGeneratingVar;
- // @ts-ignore if the colorInversion is provided as callbacks, it needs to be resolved in the CssVarsProvider
- theme.colorInversion =
- typeof colorInversionInput === 'function'
- ? colorInversionInput
- : deepmerge(
- {
- soft: createSoftInversion(theme, true),
- solid: createSolidInversion(theme, true),
- },
- colorInversionInput || {},
- { clone: false },
- );
-
return theme;
}
diff --git a/packages/mui-joy/src/styles/index.ts b/packages/mui-joy/src/styles/index.ts
index 64dab7c658b1e9..4805789c378999 100644
--- a/packages/mui-joy/src/styles/index.ts
+++ b/packages/mui-joy/src/styles/index.ts
@@ -81,7 +81,6 @@ export { default as styled } from './styled';
export { default as ThemeProvider } from './ThemeProvider';
export * from './ThemeProvider';
export { default as useThemeProps } from './useThemeProps';
-export { ColorInversionProvider, useColorInversion } from './ColorInversion';
export { default as extendTheme, createGetCssVar } from './extendTheme';
export type { CssVarsThemeOptions } from './extendTheme';
export { default as StyledEngineProvider } from './StyledEngineProvider';
diff --git a/packages/mui-joy/src/styles/shouldSkipGeneratingVar.ts b/packages/mui-joy/src/styles/shouldSkipGeneratingVar.ts
index 90e293742fc2e1..d61b8f695cc5b8 100644
--- a/packages/mui-joy/src/styles/shouldSkipGeneratingVar.ts
+++ b/packages/mui-joy/src/styles/shouldSkipGeneratingVar.ts
@@ -1,6 +1,6 @@
export default function shouldSkipGeneratingVar(keys: string[]) {
return (
- !!keys[0].match(/^(typography|variants|breakpoints|colorInversion|colorInversionConfig)$/) ||
+ !!keys[0].match(/^(typography|variants|breakpoints)$/) ||
!!keys[0].match(/sxConfig$/) || // ends with sxConfig
(keys[0] === 'palette' && !!keys[1]?.match(/^(mode)$/)) ||
(keys[0] === 'focus' && keys[1] !== 'thickness')
diff --git a/packages/mui-joy/src/styles/types/colorSystem.ts b/packages/mui-joy/src/styles/types/colorSystem.ts
index 22837ba7d47c97..92bc781248e5ca 100644
--- a/packages/mui-joy/src/styles/types/colorSystem.ts
+++ b/packages/mui-joy/src/styles/types/colorSystem.ts
@@ -176,8 +176,4 @@ export interface ColorSystem {
shadowChannel: string;
}
-export type ApplyColorInversion = Simplify<
- Omit & {
- color: T['color'] | 'context';
- }
->;
+export type ApplyColorInversion = Simplify;
diff --git a/packages/mui-joy/src/styles/types/theme.ts b/packages/mui-joy/src/styles/types/theme.ts
index 598b77fce1c7f8..c380d259ebcc12 100644
--- a/packages/mui-joy/src/styles/types/theme.ts
+++ b/packages/mui-joy/src/styles/types/theme.ts
@@ -23,7 +23,7 @@ import {
DefaultFontWeight,
DefaultLineHeight,
} from './typography';
-import { Variants, ColorInversion, ColorInversionConfig } from './variants';
+import { Variants } from './variants';
import { DefaultZIndex, ZIndex } from './zIndex';
import { MergeDefault } from './utils';
@@ -98,8 +98,6 @@ export interface Theme extends ThemeScales, RuntimeColorSystem {
focus: Focus;
typography: TypographySystem;
variants: Variants;
- colorInversion: ColorInversion;
- colorInversionConfig: ColorInversionConfig;
spacing: Spacing;
breakpoints: Breakpoints;
cssVarPrefix: string;
diff --git a/packages/mui-joy/src/styles/types/variants.ts b/packages/mui-joy/src/styles/types/variants.ts
index 626e21d167d16f..88c6e98ea46679 100644
--- a/packages/mui-joy/src/styles/types/variants.ts
+++ b/packages/mui-joy/src/styles/types/variants.ts
@@ -18,25 +18,25 @@ export type DefaultVariantKey =
// Split interfaces into multiple chunks so that they can be augmented independently
-export interface VariantPlain extends Record {}
-export interface VariantPlainHover extends Record {}
-export interface VariantPlainActive extends Record {}
-export interface VariantPlainDisabled extends Record {}
-
-export interface VariantOutlined extends Record {}
-export interface VariantOutlinedHover extends Record {}
-export interface VariantOutlinedActive extends Record {}
-export interface VariantOutlinedDisabled extends Record {}
-
-export interface VariantSoft extends Record {}
-export interface VariantSoftHover extends Record {}
-export interface VariantSoftActive extends Record {}
-export interface VariantSoftDisabled extends Record {}
-
-export interface VariantSolid extends Record {}
-export interface VariantSolidHover extends Record {}
-export interface VariantSolidActive extends Record {}
-export interface VariantSolidDisabled extends Record {}
+export interface VariantPlain extends Record {}
+export interface VariantPlainHover extends Record {}
+export interface VariantPlainActive extends Record {}
+export interface VariantPlainDisabled extends Record {}
+
+export interface VariantOutlined extends Record {}
+export interface VariantOutlinedHover extends Record {}
+export interface VariantOutlinedActive extends Record {}
+export interface VariantOutlinedDisabled extends Record {}
+
+export interface VariantSoft extends Record {}
+export interface VariantSoftHover extends Record {}
+export interface VariantSoftActive extends Record {}
+export interface VariantSoftDisabled extends Record {}
+
+export interface VariantSolid extends Record {}
+export interface VariantSolidHover extends Record {}
+export interface VariantSolidActive extends Record {}
+export interface VariantSolidDisabled extends Record {}
export interface Variants {
plain: VariantPlain;
@@ -64,13 +64,3 @@ export interface VariantPlainInversion extends Record {}
export interface VariantSoftInversion extends Record {}
export interface VariantSolidInversion extends Record {}
-
-export interface ColorInversion {
- plain?: VariantPlainInversion;
- outlined?: VariantOutlinedInversion;
- soft: VariantSoftInversion;
- solid: VariantSolidInversion;
-}
-
-export interface ColorInversionConfig
- extends Partial | undefined>> {}
diff --git a/packages/mui-joy/src/styles/variantUtils.test.js b/packages/mui-joy/src/styles/variantUtils.test.js
index 3d02f0452bf10c..24befca0656dce 100644
--- a/packages/mui-joy/src/styles/variantUtils.test.js
+++ b/packages/mui-joy/src/styles/variantUtils.test.js
@@ -302,7 +302,8 @@ describe('variant utils', () => {
},
});
expect(result.primary).to.deep.include({
- borderColor: 'var(--joy-palette-primary-outlinedActiveBorder, #aaa)',
+ borderColor:
+ 'var(--variant-outlinedActiveBorder, var(--joy-palette-primary-outlinedActiveBorder, #aaa))',
});
});
@@ -319,13 +320,13 @@ describe('variant utils', () => {
};
const softResult = createVariant('soft', theme);
expect(softResult.customColor).to.deep.include({
- color: 'var(--joy-palette-customColor-softColor, #aaa)',
- backgroundColor: 'var(--joy-palette-customColor-softBg, #bbb)',
+ color: 'var(--variant-softColor, var(--joy-palette-customColor-softColor, #aaa))',
+ backgroundColor: 'var(--variant-softBg, var(--joy-palette-customColor-softBg, #bbb))',
});
const softHoverResult = createVariant('softHover', theme);
expect(softHoverResult.customColor).to.deep.include({
- color: 'var(--joy-palette-customColor-softHoverColor, #ccc)',
+ color: 'var(--variant-softHoverColor, var(--joy-palette-customColor-softHoverColor, #ccc))',
});
});
diff --git a/packages/mui-joy/src/styles/variantUtils.ts b/packages/mui-joy/src/styles/variantUtils.ts
index 6d8f512b530a2b..c02969a48d9119 100644
--- a/packages/mui-joy/src/styles/variantUtils.ts
+++ b/packages/mui-joy/src/styles/variantUtils.ts
@@ -1,5 +1,4 @@
-import { CSSObject, unstable_createGetCssVar as createGetCssVar } from '@mui/system';
-import { DefaultColorScheme, ExtendedColorScheme } from './types/colorScheme';
+import { CSSObject } from '@mui/system';
import { DefaultColorPalette, PaletteVariant } from './types/colorSystem';
import { VariantKey } from './types/variants';
@@ -98,11 +97,6 @@ interface ThemeFragment {
palette: Record;
}
-const createPrefixVar = (cssVarPrefix: string | undefined | null) => {
- return (cssVar: string) =>
- `--${cssVarPrefix ? `${cssVarPrefix}-` : ''}${cssVar.replace(/^--/, '')}`;
-};
-
// It's used only in extendTheme, so it's safe to always include default values
export const createVariant = (variant: VariantKey, theme?: ThemeFragment) => {
let result = {} as Record;
@@ -116,8 +110,14 @@ export const createVariant = (variant: VariantKey, theme?: ThemeFragment) => {
if (isVariantPalette(colorPalette) && typeof colorPalette === 'object') {
result = {
...result,
- [color]: createVariantStyle(variant, colorPalette, (variantVar) =>
- getCssVar(`palette-${color}-${variantVar}`, palette[color][variantVar]),
+ [color]: createVariantStyle(
+ variant,
+ colorPalette,
+ (variantVar) =>
+ `var(--variant-${variantVar}, ${getCssVar(
+ `palette-${color}-${variantVar}`,
+ palette[color][variantVar],
+ )})`,
),
};
}
@@ -157,266 +157,3 @@ export const createVariant = (variant: VariantKey, theme?: ThemeFragment) => {
});
return result;
};
-
-export const createSoftInversion = (
- theme: ThemeFragment & {
- getColorSchemeSelector: (colorScheme: DefaultColorScheme | ExtendedColorScheme) => string;
- },
- addDefaultValues?: boolean,
-) => {
- const getCssVarDefault = createGetCssVar(theme.cssVarPrefix);
- const prefixVar = createPrefixVar(theme.cssVarPrefix);
- const result = {} as Record;
-
- const getCssVar = addDefaultValues
- ? (cssVar: string) => {
- const tokens = cssVar.split('-');
- const color = tokens[1];
- const value = tokens[2];
- return getCssVarDefault(cssVar, theme.palette?.[color]?.[value]);
- }
- : getCssVarDefault;
- Object.entries(theme.palette).forEach((entry) => {
- const [color, colorPalette] = entry as [
- DefaultColorPalette,
- string | number | Record,
- ];
- if (isVariantPalette(colorPalette)) {
- result[color] = {
- '--Badge-ringColor': getCssVar(`palette-${color}-softBg`),
- [theme.getColorSchemeSelector('dark')]: {
- [prefixVar('--palette-focusVisible')]: getCssVar(`palette-${color}-300`),
- [prefixVar('--palette-background-body')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.1)`,
- [prefixVar('--palette-background-surface')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.08)`,
- [prefixVar('--palette-background-level1')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.2)`,
- [prefixVar('--palette-background-level2')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.4)`,
- [prefixVar('--palette-background-level3')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.6)`,
- [prefixVar('--palette-text-primary')]: getCssVar(`palette-${color}-100`),
- [prefixVar('--palette-text-secondary')]: `rgba(${getCssVar(
- `palette-${color}-lightChannel`,
- )} / 0.72)`,
- [prefixVar('--palette-text-tertiary')]: `rgba(${getCssVar(
- `palette-${color}-lightChannel`,
- )} / 0.6)`,
- [prefixVar('--palette-text-icon')]: `rgba(${getCssVar(
- `palette-${color}-lightChannel`,
- )} / 0.6)`,
- [prefixVar('--palette-divider')]: `rgba(${getCssVar(
- `palette-${color}-lightChannel`,
- )} / 0.2)`,
- '--variant-plainColor': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 1)`,
- '--variant-plainHoverColor': getCssVar(`palette-${color}-50`),
- '--variant-plainHoverBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.16)`,
- '--variant-plainActiveBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.32)`,
- '--variant-plainDisabledColor': `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.72)`,
-
- '--variant-outlinedColor': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 1)`,
- '--variant-outlinedHoverColor': getCssVar(`palette-${color}-50`),
- '--variant-outlinedBg': 'initial',
- '--variant-outlinedBorder': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.4)`,
- '--variant-outlinedHoverBorder': getCssVar(`palette-${color}-600`),
- '--variant-outlinedHoverBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.16)`,
- '--variant-outlinedActiveBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.32)`,
- '--variant-outlinedDisabledColor': `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.72)`,
- '--variant-outlinedDisabledBorder': `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.2)`,
-
- '--variant-softColor': getCssVar(`palette-${color}-200`),
- '--variant-softBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.24)`,
- '--variant-softHoverColor': '#fff',
- '--variant-softHoverBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.32)`,
- '--variant-softActiveBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.48)`,
- '--variant-softDisabledColor': `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.72)`,
- '--variant-softDisabledBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.12)`,
-
- '--variant-solidColor': '#fff',
- '--variant-solidBg': getCssVar(`palette-${color}-500`),
- '--variant-solidHoverColor': '#fff',
- '--variant-solidHoverBg': getCssVar(`palette-${color}-600`),
- '--variant-solidActiveBg': getCssVar(`palette-${color}-600`),
- '--variant-solidDisabledColor': `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.72)`,
- '--variant-solidDisabledBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.12)`,
- },
- // `light` (default color scheme) should come last in case that `theme.getColorSchemeSelector()` return the same value
- [theme.getColorSchemeSelector('light')]: {
- [prefixVar('--palette-focusVisible')]: getCssVar(`palette-${color}-500`),
- [prefixVar('--palette-background-body')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.1)`,
- [prefixVar('--palette-background-surface')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.08)`,
- [prefixVar('--palette-background-level1')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.2)`,
- [prefixVar('--palette-background-level2')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.32)`,
- [prefixVar('--palette-background-level3')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.48)`,
- [prefixVar('--palette-text-primary')]: getCssVar(`palette-${color}-700`),
- [prefixVar('--palette-text-secondary')]: `rgba(${getCssVar(
- `palette-${color}-darkChannel`,
- )} / 0.8)`,
- [prefixVar('--palette-text-tertiary')]: `rgba(${getCssVar(
- `palette-${color}-darkChannel`,
- )} / 0.68)`,
- [prefixVar('--palette-text-icon')]: getCssVar(`palette-${color}-500`),
- [prefixVar('--palette-divider')]: `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.32)`,
- '--variant-plainColor': `rgba(${getCssVar(`palette-${color}-darkChannel`)} / 0.8)`,
- '--variant-plainHoverColor': `rgba(${getCssVar(`palette-${color}-darkChannel`)} / 1)`,
- '--variant-plainHoverBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.12)`,
- '--variant-plainActiveBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.24)`,
- '--variant-plainDisabledColor': `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.6)`,
-
- '--variant-outlinedColor': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 1)`,
- '--variant-outlinedBorder': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.4)`,
- '--variant-outlinedHoverColor': getCssVar(`palette-${color}-600`),
- '--variant-outlinedHoverBorder': getCssVar(`palette-${color}-300`),
- '--variant-outlinedHoverBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.12)`,
- '--variant-outlinedActiveBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.24)`,
- '--variant-outlinedDisabledColor': `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.6)`,
- '--variant-outlinedDisabledBorder': `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.12)`,
-
- '--variant-softColor': getCssVar(`palette-${color}-600`),
- '--variant-softBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.8)`,
- '--variant-softHoverColor': getCssVar(`palette-${color}-700`),
- '--variant-softHoverBg': getCssVar(`palette-${color}-200`),
- '--variant-softActiveBg': getCssVar(`palette-${color}-300`),
- '--variant-softDisabledColor': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.6)`,
- '--variant-softDisabledBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.08)`,
-
- '--variant-solidColor': getCssVar('palette-common-white'),
- '--variant-solidBg': getCssVar(`palette-${color}-${color === 'neutral' ? '700' : '500'}`),
- '--variant-solidHoverColor': getCssVar('palette-common-white'),
- '--variant-solidHoverBg': getCssVar(
- `palette-${color}-${color === 'neutral' ? '600' : '600'}`,
- ),
- '--variant-solidActiveBg': getCssVar(
- `palette-${color}-${color === 'neutral' ? '600' : '600'}`,
- ),
- '--variant-solidDisabledColor': `rgba(${getCssVar(
- `palette-${color}-mainChannel`,
- )} / 0.6)`,
- '--variant-solidDisabledBg': `rgba(${getCssVar(`palette-${color}-mainChannel`)} / 0.08)`,
- },
- };
- }
- });
- return result;
-};
-
-export const createSolidInversion = (theme: ThemeFragment, addDefaultValues?: boolean) => {
- const getCssVarDefault = createGetCssVar(theme.cssVarPrefix);
- const prefixVar = createPrefixVar(theme.cssVarPrefix);
- const result = {} as Record;
-
- const getCssVar = addDefaultValues
- ? (cssVar: string) => {
- const tokens = cssVar.split('-');
- const color = tokens[1];
- const value = tokens[2];
- return getCssVarDefault(cssVar, theme.palette[color][value]);
- }
- : getCssVarDefault;
-
- Object.entries(theme.palette).forEach((entry) => {
- const [color, colorPalette] = entry as [
- DefaultColorPalette,
- string | number | Record,
- ];
- if (isVariantPalette(colorPalette)) {
- result[color] = {
- colorScheme: 'dark',
- '--Badge-ringColor': getCssVar(`palette-${color}-solidBg`),
- [prefixVar('--palette-focusVisible')]: getCssVar(`palette-${color}-200`),
- [prefixVar('--palette-background-body')]: 'rgba(0 0 0 / 0.1)',
- [prefixVar('--palette-background-surface')]: 'rgba(0 0 0 / 0.06)',
- [prefixVar('--palette-background-popup')]: getCssVar(`palette-${color}-700`),
- [prefixVar('--palette-background-level1')]: `rgba(${getCssVar(
- `palette-${color}-darkChannel`,
- )} / 0.2)`,
- [prefixVar('--palette-background-level2')]: `rgba(${getCssVar(
- `palette-${color}-darkChannel`,
- )} / 0.36)`,
- [prefixVar('--palette-background-level3')]: `rgba(${getCssVar(
- `palette-${color}-darkChannel`,
- )} / 0.6)`,
- [prefixVar('--palette-text-primary')]: getCssVar(`palette-common-white`),
- [prefixVar('--palette-text-secondary')]: getCssVar(`palette-${color}-200`),
- [prefixVar('--palette-text-tertiary')]: getCssVar(`palette-${color}-300`),
- [prefixVar('--palette-text-icon')]: getCssVar(`palette-${color}-200`),
- [prefixVar('--palette-divider')]: `rgba(${getCssVar(
- `palette-${color}-lightChannel`,
- )} / 0.32)`,
-
- '--variant-plainColor': getCssVar(`palette-${color}-50`),
- '--variant-plainHoverColor': `#fff`,
- '--variant-plainHoverBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.12)`,
- '--variant-plainActiveBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.32)`,
- '--variant-plainDisabledColor': `rgba(${getCssVar(
- `palette-${color}-lightChannel`,
- )} / 0.72)`,
-
- '--variant-outlinedColor': getCssVar(`palette-${color}-50`),
- '--variant-outlinedBorder': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.5)`,
- '--variant-outlinedHoverColor': `#fff`,
- '--variant-outlinedHoverBorder': getCssVar(`palette-${color}-300`),
- '--variant-outlinedHoverBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.12)`,
- '--variant-outlinedActiveBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.32)`,
- '--variant-outlinedDisabledColor': `rgba(${getCssVar(
- `palette-${color}-lightChannel`,
- )} / 0.72)`,
- '--variant-outlinedDisabledBorder': `rgba(255 255 255 / 0.2)`,
-
- '--variant-softColor': getCssVar(`palette-common-white`),
- '--variant-softHoverColor': getCssVar(`palette-common-white`),
- '--variant-softBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.24)`,
- '--variant-softHoverBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.36)`,
- '--variant-softActiveBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.16)`,
- '--variant-softDisabledColor': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.72)`,
- '--variant-softDisabledBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.1)`,
-
- '--variant-solidColor': getCssVar(
- `palette-${color}-${color === 'neutral' ? '600' : '500'}`,
- ),
- '--variant-solidBg': getCssVar(`palette-common-white`),
- '--variant-solidHoverBg': getCssVar(`palette-common-white`),
- '--variant-solidActiveBg': getCssVar(`palette-${color}-100`),
- '--variant-solidDisabledColor': `rgba(${getCssVar(
- `palette-${color}-lightChannel`,
- )} / 0.72)`,
- '--variant-solidDisabledBg': `rgba(${getCssVar(`palette-${color}-lightChannel`)} / 0.1)`,
- };
- }
- });
- return result;
-};
diff --git a/packages/mui-joy/src/utils/useSlot.test.tsx b/packages/mui-joy/src/utils/useSlot.test.tsx
index 0684216f95ac35..7aab3a0233e590 100644
--- a/packages/mui-joy/src/utils/useSlot.test.tsx
+++ b/packages/mui-joy/src/utils/useSlot.test.tsx
@@ -2,11 +2,9 @@ import * as React from 'react';
import { expect } from 'chai';
import { createRenderer } from '@mui-internal/test-utils';
import { Popper } from '@mui/base/Popper';
-import { ColorPaletteProp, styled, VariantProp } from '../styles';
-import { CreateSlotsAndSlotProps, SlotProps } from './types';
+import { styled } from '../styles';
+import { SlotProps } from './types';
import useSlot from './useSlot';
-import { ApplyColorInversion } from '../styles/types';
-import ColorInversion, { useColorInversion } from '../styles/ColorInversion';
describe('useSlot', () => {
const { render } = createRenderer();
@@ -289,105 +287,4 @@ describe('useSlot', () => {
expect(getByRole('menuitem')).to.have.tagName('div');
});
});
-
- describe('color inversion', () => {
- const ItemRoot = styled('button')({});
- const ItemDecorator = styled('span')({});
- type Props = {
- className?: string;
- component?: React.ElementType;
- href?: string;
- variant?: VariantProp;
- color?: ColorPaletteProp;
- };
- type OwnerState = Partial>;
- const Item = React.forwardRef<
- HTMLButtonElement,
- Props &
- CreateSlotsAndSlotProps<
- {
- root: React.ElementType;
- decorator: React.ElementType;
- },
- {
- root: SlotProps<'button', {}, OwnerState>;
- decorator: SlotProps<'span', {}, OwnerState>;
- }
- >
- >(({ variant = 'plain', color: colorProp, ...other }, ref) => {
- const { getColor } = useColorInversion(variant);
- const color = getColor(colorProp, 'neutral');
- const [SlotRoot, rootProps] = useSlot('root', {
- ref,
- className: 'root',
- elementType: ItemRoot,
- externalForwardedProps: other,
- ownerState: {
- variant,
- color,
- },
- });
- const [SlotDecorator, decoratorProps] = useSlot('decorator', {
- className: 'decorator',
- elementType: ItemDecorator,
- externalForwardedProps: other,
- ownerState: {
- variant,
- color,
- },
- getSlotOwnerState: (mergedProps) => ({
- color: mergedProps.color ?? 'neutral',
- }),
- });
- return (
-
-
-
- );
- });
-
- it('should have `context` color if the feature is enabled', () => {
- const { getByRole } = render(
-
-
- ,
- );
- expect(getByRole('button')).to.have.class('color-context');
- expect(getByRole('button').firstChild).to.have.class('color-context');
- });
-
- it('should use color from prop even if the feature is enabled', () => {
- const { getByRole } = render(
-
-
- ,
- );
- expect(getByRole('button')).to.have.class('color-danger');
- });
-
- it('should use color from slotProps even if the feature is enabled', () => {
- const { getByRole } = render(
-
-
- ,
- );
- expect(getByRole('button')).to.have.class('color-danger');
- expect(getByRole('button').firstChild).to.have.class('color-success');
- });
- });
});
diff --git a/packages/mui-joy/src/utils/useSlot.ts b/packages/mui-joy/src/utils/useSlot.ts
index 78ba64fe8fd1b7..630a30408f1dd9 100644
--- a/packages/mui-joy/src/utils/useSlot.ts
+++ b/packages/mui-joy/src/utils/useSlot.ts
@@ -3,7 +3,6 @@ import * as React from 'react';
import { ClassValue } from 'clsx';
import { unstable_useForkRef as useForkRef } from '@mui/utils';
import { appendOwnerState, resolveComponentProps, mergeSlotProps } from '@mui/base/utils';
-import { useColorInversion } from '../styles/ColorInversion';
import { ApplyColorInversion } from '../styles/types';
export type WithCommonProps = T & {
@@ -134,20 +133,9 @@ export default function useSlot<
const ref = useForkRef(internalRef, resolvedComponentsProps?.ref, parameters.ref);
- // @ts-ignore internal logic
- const { disableColorInversion = false, ...slotOwnerState } = getSlotOwnerState
- ? getSlotOwnerState(mergedProps as any)
- : {};
+ const slotOwnerState = getSlotOwnerState ? getSlotOwnerState(mergedProps as any) : {};
const finalOwnerState = { ...ownerState, ...slotOwnerState } as any;
- const { getColor } = useColorInversion(finalOwnerState.variant);
- if (name === 'root') {
- // for the root slot, color inversion is calculated before the `useSlot` and pass through `ownerState`.
- finalOwnerState.color = (mergedProps as any).color ?? (ownerState as any).color;
- } else if (!disableColorInversion) {
- finalOwnerState.color = getColor((mergedProps as any).color, finalOwnerState.color);
- }
-
const LeafComponent = (name === 'root' ? slotComponent || rootComponent : slotComponent) as
| React.ElementType
| undefined;
diff --git a/packages/mui-lab/package.json b/packages/mui-lab/package.json
index ff6f507c5b4a27..90842a786c5e1f 100644
--- a/packages/mui-lab/package.json
+++ b/packages/mui-lab/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/lab",
- "version": "5.0.0-alpha.146",
+ "version": "5.0.0-alpha.147",
"private": false,
"author": "MUI Team",
"description": "Laboratory for new MUI modules.",
@@ -42,10 +42,10 @@
},
"dependencies": {
"@babel/runtime": "^7.23.1",
- "@mui/base": "5.0.0-beta.17",
- "@mui/system": "^5.14.11",
- "@mui/types": "^7.2.4",
- "@mui/utils": "^5.14.11",
+ "@mui/base": "5.0.0-beta.18",
+ "@mui/system": "^5.14.12",
+ "@mui/types": "^7.2.5",
+ "@mui/utils": "^5.14.12",
"@mui/x-tree-view": "6.0.0-alpha.1",
"clsx": "^2.0.0",
"prop-types": "^15.8.1"
@@ -55,9 +55,9 @@
"@types/chai": "^4.3.6",
"@types/prop-types": "^15.7.7",
"@types/react": "^18.2.23",
- "@types/react-dom": "^18.2.7",
- "@types/sinon": "^10.0.16",
- "chai": "^4.3.8",
+ "@types/react-dom": "^18.2.11",
+ "@types/sinon": "^10.0.19",
+ "chai": "^4.3.10",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sinon": "^15.2.0"
diff --git a/packages/mui-material-next/package.json b/packages/mui-material-next/package.json
index ffc33923c9a2e0..04d5f9648d4c8d 100644
--- a/packages/mui-material-next/package.json
+++ b/packages/mui-material-next/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/material-next",
- "version": "6.0.0-alpha.103",
+ "version": "6.0.0-alpha.104",
"private": false,
"author": "MUI Team",
"description": "v6-alpha: React components that implement Google's Material Design",
@@ -41,12 +41,12 @@
},
"dependencies": {
"@babel/runtime": "^7.23.1",
- "@mui/base": "5.0.0-beta.17",
- "@mui/material": "^5.14.11",
- "@mui/system": "^5.14.11",
- "@mui/types": "^7.2.4",
- "@mui/utils": "^5.14.11",
- "@types/react-transition-group": "^4.4.6",
+ "@mui/base": "5.0.0-beta.18",
+ "@mui/material": "^5.14.12",
+ "@mui/system": "^5.14.12",
+ "@mui/types": "^7.2.5",
+ "@mui/utils": "^5.14.12",
+ "@types/react-transition-group": "^4.4.7",
"clsx": "^2.0.0",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
@@ -57,9 +57,9 @@
"@types/chai": "^4.3.6",
"@types/prop-types": "^15.7.7",
"@types/react": "^18.2.23",
- "@types/react-dom": "^18.2.7",
- "@types/sinon": "^10.0.16",
- "chai": "^4.3.8",
+ "@types/react-dom": "^18.2.11",
+ "@types/sinon": "^10.0.19",
+ "chai": "^4.3.10",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
diff --git a/packages/mui-material-next/src/FormHelperText/FormHelperText.d.ts b/packages/mui-material-next/src/FormHelperText/FormHelperText.d.ts
new file mode 100644
index 00000000000000..02a9462ca0f191
--- /dev/null
+++ b/packages/mui-material-next/src/FormHelperText/FormHelperText.d.ts
@@ -0,0 +1,84 @@
+import * as React from 'react';
+import { SxProps } from '@mui/system';
+import { OverridableComponent, OverrideProps, OverridableStringUnion } from '@mui/types';
+import { Theme } from '../styles';
+import { FormHelperTextClasses } from './formHelperTextClasses';
+
+export interface FormHelperTextPropsVariantOverrides {}
+
+export interface FormHelperTextOwnProps {
+ /**
+ * The content of the component.
+ *
+ * If `' '` is provided, the component reserves one line height for displaying a future message.
+ */
+ children?: React.ReactNode;
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+ /**
+ * If `true`, the helper text should be displayed in a disabled state.
+ */
+ disabled?: boolean;
+ /**
+ * If `true`, helper text should be displayed in an error state.
+ */
+ error?: boolean;
+ /**
+ * If `true`, the helper text should use filled classes key.
+ */
+ filled?: boolean;
+ /**
+ * If `true`, the helper text should use focused classes key.
+ */
+ focused?: boolean;
+ /**
+ * If `dense`, will adjust vertical spacing. This is normally obtained via context from
+ * FormControl.
+ */
+ margin?: 'dense';
+ /**
+ * If `true`, the helper text should use required classes key.
+ */
+ required?: boolean;
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+ /**
+ * The variant to use.
+ */
+ variant?: OverridableStringUnion<
+ 'standard' | 'outlined' | 'filled',
+ FormHelperTextPropsVariantOverrides
+ >;
+}
+
+export interface FormHelperTextTypeMap<
+ AdditionalProps = {},
+ RootComponent extends React.ElementType = 'p',
+> {
+ props: AdditionalProps & FormHelperTextOwnProps;
+ defaultComponent: RootComponent;
+}
+/**
+ *
+ * Demos:
+ *
+ * - [Text Field](https://mui.com/material-ui/react-text-field/)
+ *
+ * API:
+ *
+ * - [FormHelperText API](https://mui.com/material-ui/api/form-helper-text/)
+ */
+declare const FormHelperText: OverridableComponent;
+
+export type FormHelperTextProps<
+ RootComponent extends React.ElementType = FormHelperTextTypeMap['defaultComponent'],
+ AdditionalProps = {},
+> = OverrideProps, RootComponent> & {
+ component?: React.ElementType;
+};
+
+export default FormHelperText;
diff --git a/packages/mui-material-next/src/FormHelperText/FormHelperText.js b/packages/mui-material-next/src/FormHelperText/FormHelperText.js
new file mode 100644
index 00000000000000..12bde80d986988
--- /dev/null
+++ b/packages/mui-material-next/src/FormHelperText/FormHelperText.js
@@ -0,0 +1,189 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import { unstable_capitalize as capitalize } from '@mui/utils';
+import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses';
+import formControlState from '../FormControl/formControlState';
+import useFormControl from '../FormControl/useFormControl';
+import styled from '../styles/styled';
+import formHelperTextClasses, { getFormHelperTextUtilityClasses } from './formHelperTextClasses';
+import useThemeProps from '../styles/useThemeProps';
+
+const useUtilityClasses = (ownerState) => {
+ const { classes, contained, size, disabled, error, filled, focused, required } = ownerState;
+ const slots = {
+ root: [
+ 'root',
+ disabled && 'disabled',
+ error && 'error',
+ size && `size${capitalize(size)}`,
+ contained && 'contained',
+ focused && 'focused',
+ filled && 'filled',
+ required && 'required',
+ ],
+ };
+
+ return composeClasses(slots, getFormHelperTextUtilityClasses, classes);
+};
+
+const FormHelperTextRoot = styled('p', {
+ name: 'MuiFormHelperText',
+ slot: 'Root',
+ overridesResolver: (props, styles) => {
+ const { ownerState } = props;
+
+ return [
+ styles.root,
+ ownerState.size && styles[`size${capitalize(ownerState.size)}`],
+ ownerState.contained && styles.contained,
+ ownerState.filled && styles.filled,
+ ];
+ },
+})(({ theme, ownerState }) => ({
+ color: (theme.vars || theme).palette.text.secondary,
+ ...theme.typography.caption,
+ textAlign: 'left',
+ marginTop: 3,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ [`&.${formHelperTextClasses.disabled}`]: {
+ color: (theme.vars || theme).palette.text.disabled,
+ },
+ [`&.${formHelperTextClasses.error}`]: {
+ color: (theme.vars || theme).palette.error.main,
+ },
+ ...(ownerState.size === 'small' && {
+ marginTop: 4,
+ }),
+ ...(ownerState.contained && {
+ marginLeft: 14,
+ marginRight: 14,
+ }),
+}));
+
+const FormHelperText = React.forwardRef(function FormHelperText(inProps, ref) {
+ const props = useThemeProps({ props: inProps, name: 'MuiFormHelperText' });
+ const {
+ children,
+ className,
+ component = 'p',
+ disabled,
+ error,
+ filled,
+ focused,
+ margin,
+ required,
+ variant,
+ ...other
+ } = props;
+
+ const muiFormControl = useFormControl();
+ const fcs = formControlState({
+ props,
+ muiFormControl,
+ states: ['variant', 'size', 'disabled', 'error', 'filled', 'focused', 'required'],
+ });
+
+ const ownerState = {
+ ...props,
+ component,
+ contained: fcs.variant === 'filled' || fcs.variant === 'outlined',
+ variant: fcs.variant,
+ size: fcs.size,
+ disabled: fcs.disabled,
+ error: fcs.error,
+ filled: fcs.filled,
+ focused: fcs.focused,
+ required: fcs.required,
+ };
+
+ const classes = useUtilityClasses(ownerState);
+
+ return (
+
+ {children === ' ' ? (
+ // notranslate needed while Google Translate will not fix zero-width space issue
+
+ ) : (
+ children
+ )}
+
+ );
+});
+
+FormHelperText.propTypes /* remove-proptypes */ = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the d.ts file and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * The content of the component.
+ *
+ * If `' '` is provided, the component reserves one line height for displaying a future message.
+ */
+ children: PropTypes.node,
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The component used for the root node.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * If `true`, the helper text should be displayed in a disabled state.
+ */
+ disabled: PropTypes.bool,
+ /**
+ * If `true`, helper text should be displayed in an error state.
+ */
+ error: PropTypes.bool,
+ /**
+ * If `true`, the helper text should use filled classes key.
+ */
+ filled: PropTypes.bool,
+ /**
+ * If `true`, the helper text should use focused classes key.
+ */
+ focused: PropTypes.bool,
+ /**
+ * If `dense`, will adjust vertical spacing. This is normally obtained via context from
+ * FormControl.
+ */
+ margin: PropTypes.oneOf(['dense']),
+ /**
+ * If `true`, the helper text should use required classes key.
+ */
+ required: PropTypes.bool,
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
+ PropTypes.func,
+ PropTypes.object,
+ ]),
+ /**
+ * The variant to use.
+ */
+ variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['filled', 'outlined', 'standard']),
+ PropTypes.string,
+ ]),
+};
+
+export default FormHelperText;
diff --git a/packages/mui-material-next/src/FormHelperText/FormHelperText.spec.tsx b/packages/mui-material-next/src/FormHelperText/FormHelperText.spec.tsx
new file mode 100644
index 00000000000000..760992705a7887
--- /dev/null
+++ b/packages/mui-material-next/src/FormHelperText/FormHelperText.spec.tsx
@@ -0,0 +1,61 @@
+import * as React from 'react';
+import { expectType } from '@mui/types';
+import FormHelperText, { FormHelperTextProps } from '@mui/material-next/FormHelperText';
+
+const CustomComponent: React.FC<{ stringProp: string; numberProp: number }> =
+ function CustomComponent() {
+ return ;
+ };
+
+const props1: FormHelperTextProps<'div'> = {
+ component: 'div',
+ onChange: (event) => {
+ expectType, typeof event>(event);
+ },
+};
+
+const props2: FormHelperTextProps = {
+ onChange: (event) => {
+ expectType, typeof event>(event);
+ },
+};
+
+const props3: FormHelperTextProps = {
+ component: CustomComponent,
+ stringProp: '2',
+ numberProp: 2,
+};
+
+const props4: FormHelperTextProps = {
+ component: CustomComponent,
+ stringProp: '2',
+ numberProp: 2,
+ // @ts-expect-error CustomComponent does not accept incorrectProp
+ incorrectProp: 3,
+};
+
+// @ts-expect-error missing props
+const props5: FormHelperTextProps = {
+ component: CustomComponent,
+};
+
+const TestComponent = () => {
+ return (
+
+
+
+
+
+ {
+ // @ts-expect-error missing props
+
+ }
+ {
+ expectType, typeof event>(event);
+ }}
+ />
+
+ );
+};
diff --git a/packages/mui-material-next/src/FormHelperText/FormHelperText.test.js b/packages/mui-material-next/src/FormHelperText/FormHelperText.test.js
new file mode 100644
index 00000000000000..1d56b54bd16a31
--- /dev/null
+++ b/packages/mui-material-next/src/FormHelperText/FormHelperText.test.js
@@ -0,0 +1,94 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { createRenderer, describeConformance } from '@mui-internal/test-utils';
+import FormHelperText, { formHelperTextClasses as classes } from '@mui/material/FormHelperText';
+import FormControl from '@mui/material/FormControl';
+
+describe('', () => {
+ const { render } = createRenderer();
+
+ describeConformance(, () => ({
+ classes,
+ inheritComponent: 'p',
+ render,
+ refInstanceof: window.HTMLParagraphElement,
+ testComponentPropWith: 'div',
+ muiName: 'MuiFormHelperText',
+ testVariantProps: { size: 'small' },
+ skip: ['componentsProp'],
+ }));
+
+ describe('prop: error', () => {
+ it('should have an error class', () => {
+ const { container } = render();
+ expect(container.firstChild).to.have.class(classes.error);
+ });
+ });
+
+ describe('with FormControl', () => {
+ ['error', 'disabled'].forEach((visualState) => {
+ describe(visualState, () => {
+ function FormHelperTextInFormControl(props) {
+ return (
+
+ Foo
+
+ );
+ }
+
+ it(`should have the ${visualState} class`, () => {
+ const { getByText } = render(
+ Foo,
+ );
+
+ expect(getByText(/Foo/)).to.have.class(classes[visualState]);
+ });
+
+ it('should be overridden by props', () => {
+ const { getByText, setProps } = render(
+
+ Foo
+ ,
+ );
+
+ expect(getByText(/Foo/)).not.to.have.class(classes[visualState]);
+
+ setProps({ [visualState]: true });
+ expect(getByText(/Foo/)).to.have.class(classes[visualState]);
+ });
+ });
+ });
+
+ describe('size', () => {
+ describe('small margin FormControl', () => {
+ it('should have the small class', () => {
+ const { getByText } = render(
+
+ Foo
+ ,
+ );
+
+ expect(getByText(/Foo/)).to.have.class(classes.sizeSmall);
+ });
+ });
+
+ it('should be overridden by props', () => {
+ function FormHelperTextInFormControl(props) {
+ return (
+
+ Foo
+
+ );
+ }
+
+ const { getByText, setProps } = render(
+ Foo,
+ );
+
+ expect(getByText(/Foo/)).not.to.have.class(classes.sizeSmall);
+ setProps({ size: 'small' });
+ expect(getByText(/Foo/)).to.have.class(classes.sizeSmall);
+ });
+ });
+ });
+});
diff --git a/packages/mui-material-next/src/FormHelperText/formHelperTextClasses.ts b/packages/mui-material-next/src/FormHelperText/formHelperTextClasses.ts
new file mode 100644
index 00000000000000..4b9afb25838503
--- /dev/null
+++ b/packages/mui-material-next/src/FormHelperText/formHelperTextClasses.ts
@@ -0,0 +1,43 @@
+import {
+ unstable_generateUtilityClasses as generateUtilityClasses,
+ unstable_generateUtilityClass as generateUtilityClass,
+} from '@mui/utils';
+
+export interface FormHelperTextClasses {
+ /** Styles applied to the root element. */
+ root: string;
+ /** State class applied to the root element if `error={true}`. */
+ error: string;
+ /** State class applied to the root element if `disabled={true}`. */
+ disabled: string;
+ /** Styles applied to the root element if `size="small"`. */
+ sizeSmall: string;
+ /** Styles applied to the root element if `variant="filled"` or `variant="outlined"`. */
+ contained: string;
+ /** State class applied to the root element if `focused={true}`. */
+ focused: string;
+ /** State class applied to the root element if `filled={true}`. */
+ filled: string;
+ /** State class applied to the root element if `required={true}`. */
+ required: string;
+}
+
+export type FormHelperTextClassKey = keyof FormHelperTextClasses;
+
+export function getFormHelperTextUtilityClasses(slot: string): string {
+ return generateUtilityClass('MuiFormHelperText', slot);
+}
+
+const formHelperTextClasses: FormHelperTextClasses = generateUtilityClasses('MuiFormHelperText', [
+ 'root',
+ 'error',
+ 'disabled',
+ 'sizeSmall',
+ 'sizeMedium',
+ 'contained',
+ 'focused',
+ 'filled',
+ 'required',
+]);
+
+export default formHelperTextClasses;
diff --git a/packages/mui-material-next/src/FormHelperText/index.d.ts b/packages/mui-material-next/src/FormHelperText/index.d.ts
new file mode 100644
index 00000000000000..86a36e0378a79f
--- /dev/null
+++ b/packages/mui-material-next/src/FormHelperText/index.d.ts
@@ -0,0 +1,5 @@
+export { default } from './FormHelperText';
+export * from './FormHelperText';
+
+export { default as formHelperTextClasses } from './formHelperTextClasses';
+export * from './formHelperTextClasses';
diff --git a/packages/mui-material-next/src/FormHelperText/index.js b/packages/mui-material-next/src/FormHelperText/index.js
new file mode 100644
index 00000000000000..e2e5b0a06e3444
--- /dev/null
+++ b/packages/mui-material-next/src/FormHelperText/index.js
@@ -0,0 +1,5 @@
+'use client';
+export { default } from './FormHelperText';
+
+export { default as formHelperTextClasses } from './formHelperTextClasses';
+export * from './formHelperTextClasses';
diff --git a/packages/mui-material-next/src/FormLabel/FormLabel.d.ts b/packages/mui-material-next/src/FormLabel/FormLabel.d.ts
new file mode 100644
index 00000000000000..ccade1df4af856
--- /dev/null
+++ b/packages/mui-material-next/src/FormLabel/FormLabel.d.ts
@@ -0,0 +1,97 @@
+import * as React from 'react';
+import { SxProps } from '@mui/system';
+import {
+ OverridableStringUnion,
+ OverridableComponent,
+ OverrideProps,
+ OverridableTypeMap,
+} from '@mui/types';
+import { Theme } from '../styles';
+import { FormLabelClasses } from './formLabelClasses';
+
+export interface FormLabelPropsColorOverrides {}
+
+/**
+ * This type is kept for compatibility. Use `FormLabelOwnProps` instead.
+ */
+export type FormLabelBaseProps = React.LabelHTMLAttributes;
+
+export interface FormLabelOwnProps {
+ /**
+ * The content of the component.
+ */
+ children?: React.ReactNode;
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+ /**
+ * The color of the component.
+ * It supports both default and custom theme colors, which can be added as shown in the
+ * [palette customization guide](https://mui.com/material-ui/customization/palette/#adding-new-colors).
+ */
+ color?: OverridableStringUnion<
+ 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning',
+ FormLabelPropsColorOverrides
+ >;
+ /**
+ * If `true`, the label should be displayed in a disabled state.
+ */
+ disabled?: boolean;
+ /**
+ * If `true`, the label is displayed in an error state.
+ */
+ error?: boolean;
+ /**
+ * If `true`, the label should use filled classes key.
+ */
+ filled?: boolean;
+ /**
+ * If `true`, the input of this label is focused (used by `FormGroup` components).
+ */
+ focused?: boolean;
+ /**
+ * If `true`, the label will indicate that the `input` is required.
+ */
+ required?: boolean;
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+}
+
+export interface FormLabelTypeMap<
+ AdditionalProps = {},
+ RootComponent extends React.ElementType = 'label',
+> {
+ props: AdditionalProps & FormLabelBaseProps & FormLabelOwnProps;
+ defaultComponent: RootComponent;
+}
+
+/**
+ *
+ * Demos:
+ *
+ * - [Checkbox](https://mui.com/material-ui/react-checkbox/)
+ * - [Radio Group](https://mui.com/material-ui/react-radio-button/)
+ * - [Switch](https://mui.com/material-ui/react-switch/)
+ *
+ * API:
+ *
+ * - [FormLabel API](https://mui.com/material-ui/api/form-label/)
+ */
+declare const FormLabel: OverridableComponent;
+
+export interface ExtendFormLabelTypeMap {
+ props: TypeMap['props'] & Pick;
+ defaultComponent: TypeMap['defaultComponent'];
+}
+
+export type FormLabelProps<
+ RootComponent extends React.ElementType = FormLabelTypeMap['defaultComponent'],
+ AdditionalProps = {},
+> = OverrideProps, RootComponent> & {
+ component?: React.ElementType;
+};
+
+export default FormLabel;
diff --git a/packages/mui-material-next/src/FormLabel/FormLabel.js b/packages/mui-material-next/src/FormLabel/FormLabel.js
new file mode 100644
index 00000000000000..d666eb862b4902
--- /dev/null
+++ b/packages/mui-material-next/src/FormLabel/FormLabel.js
@@ -0,0 +1,182 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import { unstable_capitalize as capitalize } from '@mui/utils';
+import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses';
+import formControlState from '../FormControl/formControlState';
+import useFormControl from '../FormControl/useFormControl';
+import useThemeProps from '../styles/useThemeProps';
+import styled from '../styles/styled';
+import formLabelClasses, { getFormLabelUtilityClasses } from './formLabelClasses';
+
+const useUtilityClasses = (ownerState) => {
+ const { classes, color, focused, disabled, error, filled, required } = ownerState;
+ const slots = {
+ root: [
+ 'root',
+ `color${capitalize(color)}`,
+ disabled && 'disabled',
+ error && 'error',
+ filled && 'filled',
+ focused && 'focused',
+ required && 'required',
+ ],
+ asterisk: ['asterisk', error && 'error'],
+ };
+
+ return composeClasses(slots, getFormLabelUtilityClasses, classes);
+};
+
+export const FormLabelRoot = styled('label', {
+ name: 'MuiFormLabel',
+ slot: 'Root',
+ overridesResolver: ({ ownerState }, styles) => {
+ return {
+ ...styles.root,
+ ...(ownerState.color === 'secondary' && styles.colorSecondary),
+ ...(ownerState.filled && styles.filled),
+ };
+ },
+})(({ theme, ownerState }) => ({
+ color: (theme.vars || theme).palette.text.secondary,
+ ...theme.typography.body1,
+ lineHeight: '1.4375em',
+ padding: 0,
+ position: 'relative',
+ [`&.${formLabelClasses.focused}`]: {
+ color: (theme.vars || theme).palette[ownerState.color].main,
+ },
+ [`&.${formLabelClasses.disabled}`]: {
+ color: (theme.vars || theme).palette.text.disabled,
+ },
+ [`&.${formLabelClasses.error}`]: {
+ color: (theme.vars || theme).palette.error.main,
+ },
+}));
+
+const AsteriskComponent = styled('span', {
+ name: 'MuiFormLabel',
+ slot: 'Asterisk',
+ overridesResolver: (props, styles) => styles.asterisk,
+})(({ theme }) => ({
+ [`&.${formLabelClasses.error}`]: {
+ color: (theme.vars || theme).palette.error.main,
+ },
+}));
+
+const FormLabel = React.forwardRef(function FormLabel(inProps, ref) {
+ const props = useThemeProps({ props: inProps, name: 'MuiFormLabel' });
+ const {
+ children,
+ className,
+ color,
+ component = 'label',
+ disabled,
+ error,
+ filled,
+ focused,
+ required,
+ ...other
+ } = props;
+
+ const muiFormControl = useFormControl();
+ const fcs = formControlState({
+ props,
+ muiFormControl,
+ states: ['color', 'required', 'focused', 'disabled', 'error', 'filled'],
+ });
+
+ const ownerState = {
+ ...props,
+ color: fcs.color || 'primary',
+ component,
+ disabled: fcs.disabled,
+ error: fcs.error,
+ filled: fcs.filled,
+ focused: fcs.focused,
+ required: fcs.required,
+ };
+
+ const classes = useUtilityClasses(ownerState);
+
+ return (
+
+ {children}
+ {fcs.required && (
+
+ {'*'}
+
+ )}
+
+ );
+});
+
+FormLabel.propTypes /* remove-proptypes */ = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the d.ts file and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * The content of the component.
+ */
+ children: PropTypes.node,
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The color of the component.
+ * It supports both default and custom theme colors, which can be added as shown in the
+ * [palette customization guide](https://mui.com/material-ui/customization/palette/#adding-new-colors).
+ */
+ color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['error', 'info', 'primary', 'secondary', 'success', 'warning']),
+ PropTypes.string,
+ ]),
+ /**
+ * The component used for the root node.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * If `true`, the label should be displayed in a disabled state.
+ */
+ disabled: PropTypes.bool,
+ /**
+ * If `true`, the label is displayed in an error state.
+ */
+ error: PropTypes.bool,
+ /**
+ * If `true`, the label should use filled classes key.
+ */
+ filled: PropTypes.bool,
+ /**
+ * If `true`, the input of this label is focused (used by `FormGroup` components).
+ */
+ focused: PropTypes.bool,
+ /**
+ * If `true`, the label will indicate that the `input` is required.
+ */
+ required: PropTypes.bool,
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
+ PropTypes.func,
+ PropTypes.object,
+ ]),
+};
+
+export default FormLabel;
diff --git a/packages/mui-material-next/src/FormLabel/FormLabel.test.js b/packages/mui-material-next/src/FormLabel/FormLabel.test.js
new file mode 100644
index 00000000000000..0f50f06c6ee756
--- /dev/null
+++ b/packages/mui-material-next/src/FormLabel/FormLabel.test.js
@@ -0,0 +1,192 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { expect } from 'chai';
+import { describeConformance, act, createRenderer } from '@mui-internal/test-utils';
+import FormLabel, { formLabelClasses as classes } from '@mui/material-next/FormLabel';
+import FormControl, { useFormControl } from '@mui/material-next/FormControl';
+// TODO v6: re-export from material-next
+import { hexToRgb } from '@mui/system';
+// TODO v6: remove after implementing Material You styles
+import { ThemeProvider, createTheme } from '@mui/material/styles';
+
+// TODO v6: remove after implementing Material You styles
+const MaterialV5DefaultTheme = createTheme();
+
+describe('', () => {
+ const { render } = createRenderer();
+
+ describeConformance(, () => ({
+ classes,
+ inheritComponent: 'label',
+ render,
+ refInstanceof: window.HTMLLabelElement,
+ testComponentPropWith: 'div',
+ muiName: 'MuiFormLabel',
+ testVariantProps: { color: 'secondary' },
+ skip: ['componentsProp'],
+ }));
+
+ describe('prop: required', () => {
+ it('should visually show an asterisk but not include it in the a11y tree', () => {
+ const { container } = render(name);
+
+ expect(container.querySelector('label')).to.have.text('name\u2009*');
+ expect(container.querySelectorAll(`.${classes.asterisk}`)).to.have.lengthOf(1);
+ expect(container.querySelectorAll(`.${classes.asterisk}`)[0]).toBeAriaHidden();
+ });
+
+ it('should not show an asterisk by default', () => {
+ const { container } = render(name);
+
+ expect(container.querySelector('label')).to.have.text('name');
+ expect(container.querySelectorAll(`.${classes.asterisk}`)).to.have.lengthOf(0);
+ });
+ });
+
+ describe('prop: error', () => {
+ it('should have an error class', () => {
+ const { container } = render();
+
+ expect(container.querySelectorAll(`.${classes.asterisk}`)).to.have.lengthOf(1);
+ expect(container.querySelector(`.${classes.asterisk}`)).to.have.class(classes.error);
+ expect(container.querySelectorAll(`.${classes.asterisk}`)[0]).toBeAriaHidden();
+ expect(container.firstChild).to.have.class(classes.error);
+ });
+ });
+
+ describe('with FormControl', () => {
+ describe('error', () => {
+ function Wrapper(props) {
+ return ;
+ }
+
+ it(`should have the error class`, () => {
+ const { container } = render(, {
+ wrapper: Wrapper,
+ });
+
+ expect(container.querySelector('label')).to.have.class(classes.error);
+ });
+
+ it('should be overridden by props', () => {
+ const { container, setProps } = render(
+ ,
+ {
+ wrapper: Wrapper,
+ },
+ );
+
+ expect(container.querySelector('label')).not.to.have.class(classes.error);
+
+ setProps({ error: true });
+ expect(container.querySelector('label')).to.have.class(classes.error);
+ });
+ });
+
+ describe('focused', () => {
+ const FormController = React.forwardRef((_, ref) => {
+ const formControl = useFormControl();
+ React.useImperativeHandle(ref, () => formControl, [formControl]);
+ return null;
+ });
+
+ it(`should have the focused class`, () => {
+ const formControlRef = React.createRef();
+ const { container } = render(
+
+
+
+ ,
+ );
+
+ expect(container.querySelector('label')).not.to.have.class(classes.focused);
+
+ act(() => {
+ formControlRef.current.onFocus();
+ });
+ expect(container.querySelector('label')).to.have.class(classes.focused);
+ });
+
+ it('should be overridden by props', () => {
+ const formControlRef = React.createRef();
+ function Wrapper({ children }) {
+ return (
+
+ {children}
+
+
+ );
+ }
+ Wrapper.propTypes = { children: PropTypes.node };
+ const { container, setProps } = render(, {
+ wrapper: Wrapper,
+ });
+ act(() => {
+ formControlRef.current.onFocus();
+ });
+
+ expect(container.querySelector('label')).to.have.class(classes.focused);
+
+ setProps({ focused: false });
+ expect(container.querySelector('label')).not.to.have.class(classes.focused);
+
+ setProps({ focused: true });
+ expect(container.querySelector('label')).to.have.class(classes.focused);
+ });
+ });
+
+ describe('required', () => {
+ it('should show an asterisk', () => {
+ const { container } = render(
+
+ name
+ ,
+ );
+
+ expect(container).to.have.text('name\u2009*');
+ });
+
+ it('should be overridden by props', () => {
+ const { container, setProps } = render(name, {
+ wrapper: (props) => ,
+ });
+
+ expect(container).to.have.text('name');
+
+ setProps({ required: true });
+ expect(container).to.have.text('name\u2009*');
+ });
+ });
+ });
+
+ describe('prop: color', () => {
+ it('should have color secondary class', () => {
+ const { container } = render();
+ expect(container.firstChild).to.have.class(classes.colorSecondary);
+ });
+
+ it('should have the focused class and style', () => {
+ const { container, getByTestId } = render(
+
+
+ ,
+ );
+ expect(container.querySelector(`.${classes.colorSecondary}`)).to.have.class(classes.focused);
+ expect(getByTestId('FormLabel')).toHaveComputedStyle({
+ color: hexToRgb(MaterialV5DefaultTheme.palette.secondary.main),
+ });
+ });
+
+ it('should have the error class and style, even when focused', () => {
+ const { container, getByTestId } = render(
+
+
+ ,
+ );
+ expect(container.querySelector(`.${classes.colorSecondary}`)).to.have.class(classes.error);
+ expect(getByTestId('FormLabel')).toHaveComputedStyle({
+ color: hexToRgb(MaterialV5DefaultTheme.palette.error.main),
+ });
+ });
+ });
+});
diff --git a/packages/mui-material-next/src/FormLabel/formLabelClasses.ts b/packages/mui-material-next/src/FormLabel/formLabelClasses.ts
new file mode 100644
index 00000000000000..b1b69e98466e86
--- /dev/null
+++ b/packages/mui-material-next/src/FormLabel/formLabelClasses.ts
@@ -0,0 +1,42 @@
+import {
+ unstable_generateUtilityClass as generateUtilityClass,
+ unstable_generateUtilityClasses as generateUtilityClasses,
+} from '@mui/utils';
+
+export interface FormLabelClasses {
+ /** Styles applied to the root element. */
+ root: string;
+ /** Styles applied to the root element if the color is secondary. */
+ colorSecondary: string;
+ /** State class applied to the root element if `focused={true}`. */
+ focused: string;
+ /** State class applied to the root element if `disabled={true}`. */
+ disabled: string;
+ /** State class applied to the root element if `error={true}`. */
+ error: string;
+ /** State class applied to the root element if `filled={true}`. */
+ filled: string;
+ /** State class applied to the root element if `required={true}`. */
+ required: string;
+ /** Styles applied to the asterisk element. */
+ asterisk: string;
+}
+
+export type FormLabelClassKey = keyof FormLabelClasses;
+
+export function getFormLabelUtilityClasses(slot: string): string {
+ return generateUtilityClass('MuiFormLabel', slot);
+}
+
+const formLabelClasses: FormLabelClasses = generateUtilityClasses('MuiFormLabel', [
+ 'root',
+ 'colorSecondary',
+ 'focused',
+ 'disabled',
+ 'error',
+ 'filled',
+ 'required',
+ 'asterisk',
+]);
+
+export default formLabelClasses;
diff --git a/packages/mui-material-next/src/FormLabel/index.d.ts b/packages/mui-material-next/src/FormLabel/index.d.ts
new file mode 100644
index 00000000000000..f81d2d6d4ed4a4
--- /dev/null
+++ b/packages/mui-material-next/src/FormLabel/index.d.ts
@@ -0,0 +1,5 @@
+export { default } from './FormLabel';
+export * from './FormLabel';
+
+export { default as formLabelClasses } from './formLabelClasses';
+export * from './formLabelClasses';
diff --git a/packages/mui-material-next/src/FormLabel/index.js b/packages/mui-material-next/src/FormLabel/index.js
new file mode 100644
index 00000000000000..db05e838ce6da7
--- /dev/null
+++ b/packages/mui-material-next/src/FormLabel/index.js
@@ -0,0 +1,6 @@
+'use client';
+export { default } from './FormLabel';
+export * from './FormLabel';
+
+export { default as formLabelClasses } from './formLabelClasses';
+export * from './formLabelClasses';
diff --git a/packages/mui-material-next/src/InputAdornment/InputAdornment.d.ts b/packages/mui-material-next/src/InputAdornment/InputAdornment.d.ts
new file mode 100644
index 00000000000000..a4769605aabfa5
--- /dev/null
+++ b/packages/mui-material-next/src/InputAdornment/InputAdornment.d.ts
@@ -0,0 +1,69 @@
+import * as React from 'react';
+import { SxProps } from '@mui/system';
+import { OverridableComponent, OverrideProps } from '@mui/types';
+import { Theme } from '..';
+import { InputAdornmentClasses } from './inputAdornmentClasses';
+
+export interface InputAdornmentOwnProps {
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+ /**
+ * The content of the component, normally an `IconButton` or string.
+ */
+ children?: React.ReactNode;
+ /**
+ * Disable pointer events on the root.
+ * This allows for the content of the adornment to focus the `input` on click.
+ * @default false
+ */
+ disablePointerEvents?: boolean;
+ /**
+ * If children is a string then disable wrapping in a Typography component.
+ * @default false
+ */
+ disableTypography?: boolean;
+ /**
+ * The position this adornment should appear relative to the `Input`.
+ */
+ position: 'start' | 'end';
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+ /**
+ * The variant to use.
+ * Note: If you are using the `TextField` component or the `FormControl` component
+ * you do not have to set this manually.
+ */
+ variant?: 'standard' | 'outlined' | 'filled';
+}
+
+export interface InputAdornmentTypeMap<
+ AdditionalProps = {},
+ RootComponent extends React.ElementType = 'div',
+> {
+ props: AdditionalProps & InputAdornmentOwnProps;
+ defaultComponent: RootComponent;
+}
+/**
+ *
+ * Demos:
+ *
+ * - [Text Field](https://mui.com/material-ui/react-text-field/)
+ *
+ * API:
+ *
+ * - [InputAdornment API](https://mui.com/material-ui/api/input-adornment/)
+ */
+declare const InputAdornment: OverridableComponent;
+
+export type InputAdornmentProps<
+ RootComponent extends React.ElementType = InputAdornmentTypeMap['defaultComponent'],
+ AdditionalProps = {},
+> = OverrideProps, RootComponent> & {
+ component?: React.ElementType;
+};
+
+export default InputAdornment;
diff --git a/packages/mui-material-next/src/InputAdornment/InputAdornment.js b/packages/mui-material-next/src/InputAdornment/InputAdornment.js
new file mode 100644
index 00000000000000..115dffc1c061ab
--- /dev/null
+++ b/packages/mui-material-next/src/InputAdornment/InputAdornment.js
@@ -0,0 +1,196 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import {
+ unstable_composeClasses as composeClasses,
+ unstable_capitalize as capitalize,
+} from '@mui/base/composeClasses';
+import Typography from '@mui/material/Typography';
+import FormControlContext from '../FormControl/FormControlContext';
+import useFormControl from '../FormControl/useFormControl';
+import styled from '../styles/styled';
+import inputAdornmentClasses, { getInputAdornmentUtilityClass } from './inputAdornmentClasses';
+import useThemeProps from '../styles/useThemeProps';
+
+const overridesResolver = (props, styles) => {
+ const { ownerState } = props;
+
+ return [
+ styles.root,
+ styles[`position${capitalize(ownerState.position)}`],
+ ownerState.disablePointerEvents === true && styles.disablePointerEvents,
+ styles[ownerState.variant],
+ ];
+};
+
+const useUtilityClasses = (ownerState) => {
+ const { classes, disablePointerEvents, hiddenLabel, position, size, variant } = ownerState;
+ const slots = {
+ root: [
+ 'root',
+ disablePointerEvents && 'disablePointerEvents',
+ position && `position${capitalize(position)}`,
+ variant,
+ hiddenLabel && 'hiddenLabel',
+ size && `size${capitalize(size)}`,
+ ],
+ };
+
+ return composeClasses(slots, getInputAdornmentUtilityClass, classes);
+};
+
+const InputAdornmentRoot = styled('div', {
+ name: 'MuiInputAdornment',
+ slot: 'Root',
+ overridesResolver,
+})(({ theme, ownerState }) => ({
+ display: 'flex',
+ height: '0.01em', // Fix IE11 flexbox alignment. To remove at some point.
+ maxHeight: '2em',
+ alignItems: 'center',
+ whiteSpace: 'nowrap',
+ color: (theme.vars || theme).palette.action.active,
+ ...(ownerState.variant === 'filled' && {
+ // Styles applied to the root element if `variant="filled"`.
+ [`&.${inputAdornmentClasses.positionStart}&:not(.${inputAdornmentClasses.hiddenLabel})`]: {
+ marginTop: 16,
+ },
+ }),
+ ...(ownerState.position === 'start' && {
+ // Styles applied to the root element if `position="start"`.
+ marginRight: 8,
+ }),
+ ...(ownerState.position === 'end' && {
+ // Styles applied to the root element if `position="end"`.
+ marginLeft: 8,
+ }),
+ ...(ownerState.disablePointerEvents === true && {
+ // Styles applied to the root element if `disablePointerEvents={true}`.
+ pointerEvents: 'none',
+ }),
+}));
+
+const InputAdornment = React.forwardRef(function InputAdornment(inProps, ref) {
+ const props = useThemeProps({ props: inProps, name: 'MuiInputAdornment' });
+ const {
+ children,
+ className,
+ component = 'div',
+ disablePointerEvents = false,
+ disableTypography = false,
+ position,
+ variant: variantProp,
+ ...other
+ } = props;
+
+ const muiFormControl = useFormControl() || {};
+
+ let variant = variantProp;
+
+ if (variantProp && muiFormControl.variant) {
+ if (process.env.NODE_ENV !== 'production') {
+ if (variantProp === muiFormControl.variant) {
+ console.error(
+ 'MUI: The `InputAdornment` variant infers the variant prop ' +
+ 'you do not have to provide one.',
+ );
+ }
+ }
+ }
+
+ if (muiFormControl && !variant) {
+ variant = muiFormControl.variant;
+ }
+
+ const ownerState = {
+ ...props,
+ hiddenLabel: muiFormControl.hiddenLabel,
+ size: muiFormControl.size,
+ disablePointerEvents,
+ position,
+ variant,
+ };
+
+ const classes = useUtilityClasses(ownerState);
+
+ return (
+
+
+ {typeof children === 'string' && !disableTypography ? (
+ {children}
+ ) : (
+
+ {/* To have the correct vertical alignment baseline */}
+ {position === 'start' ? (
+ /* notranslate needed while Google Translate will not fix zero-width space issue */
+
+ ) : null}
+ {children}
+
+ )}
+
+
+ );
+});
+
+InputAdornment.propTypes /* remove-proptypes */ = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the d.ts file and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * The content of the component, normally an `IconButton` or string.
+ */
+ children: PropTypes.node,
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The component used for the root node.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * Disable pointer events on the root.
+ * This allows for the content of the adornment to focus the `input` on click.
+ * @default false
+ */
+ disablePointerEvents: PropTypes.bool,
+ /**
+ * If children is a string then disable wrapping in a Typography component.
+ * @default false
+ */
+ disableTypography: PropTypes.bool,
+ /**
+ * The position this adornment should appear relative to the `Input`.
+ */
+ position: PropTypes.oneOf(['end', 'start']).isRequired,
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
+ PropTypes.func,
+ PropTypes.object,
+ ]),
+ /**
+ * The variant to use.
+ * Note: If you are using the `TextField` component or the `FormControl` component
+ * you do not have to set this manually.
+ */
+ variant: PropTypes.oneOf(['filled', 'outlined', 'standard']),
+};
+
+export default InputAdornment;
diff --git a/packages/mui-material-next/src/InputAdornment/InputAdornment.test.js b/packages/mui-material-next/src/InputAdornment/InputAdornment.test.js
new file mode 100644
index 00000000000000..8cb9c32096e4b6
--- /dev/null
+++ b/packages/mui-material-next/src/InputAdornment/InputAdornment.test.js
@@ -0,0 +1,220 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import {
+ createRenderer,
+ describeConformance,
+ strictModeDoubleLoggingSuppressed,
+} from '@mui-internal/test-utils';
+import { typographyClasses } from '@mui/material/Typography';
+import InputAdornment, { inputAdornmentClasses as classes } from '@mui/material/InputAdornment';
+import TextField from '@mui/material/TextField';
+import FormControl from '@mui/material/FormControl';
+import Input from '@mui/material/Input';
+
+describe('', () => {
+ const { render } = createRenderer();
+
+ describeConformance(foo, () => ({
+ classes,
+ inheritComponent: 'div',
+ render,
+ muiName: 'MuiInputAdornment',
+ testVariantProps: { color: 'primary' },
+ refInstanceof: window.HTMLDivElement,
+ skip: ['componentsProp'],
+ testComponentPropWith: 'span',
+ }));
+
+ it('should wrap text children in a Typography', () => {
+ const { container } = render(foo);
+ const typography = container.querySelector(`.${typographyClasses.root}`);
+
+ expect(typography).not.to.equal(null);
+ expect(typography).to.have.text('foo');
+ });
+
+ it('should have the root and start class when position is start', () => {
+ const { container } = render(foo);
+ const adornment = container.firstChild;
+
+ expect(adornment).to.have.class(classes.root);
+ expect(adornment).to.have.class(classes.positionStart);
+ });
+
+ it('should have the root and end class when position is end', () => {
+ const { container } = render(foo);
+ const adornment = container.firstChild;
+
+ expect(adornment).to.have.class(classes.root);
+ expect(adornment).to.have.class(classes.positionEnd);
+ });
+
+ describe('prop: variant', () => {
+ it("should inherit the TextField's variant", () => {
+ const { getByTestId } = render(
+
+ foo
+
+ ),
+ }}
+ />,
+ );
+ const adornment = getByTestId('InputAdornment');
+
+ expect(adornment).to.have.class(classes.root);
+ expect(adornment).to.have.class(classes.positionStart);
+ expect(adornment).to.have.class(classes.filled);
+ });
+
+ it("should inherit the FormControl's variant", () => {
+ const { getByTestId } = render(
+
+
+ foo
+
+ ,
+ );
+ const adornment = getByTestId('InputAdornment');
+
+ expect(adornment).to.have.class(classes.root);
+ expect(adornment).to.have.class(classes.positionStart);
+ expect(adornment).to.have.class(classes.filled);
+ });
+
+ it('should override the inherited variant', () => {
+ const { getByTestId } = render(
+
+ foo
+
+ ),
+ }}
+ />,
+ );
+ const adornment = getByTestId('InputAdornment');
+
+ expect(adornment).to.have.class(classes.root);
+ expect(adornment).to.have.class(classes.positionStart);
+ expect(adornment).not.to.have.class(classes.filled);
+ });
+
+ it('should have the filled root and class when variant is filled', () => {
+ const { container } = render(
+
+ foo
+ ,
+ );
+ const adornment = container.firstChild;
+
+ expect(adornment).to.have.class(classes.root);
+ expect(adornment).to.have.class(classes.positionStart);
+ expect(adornment).to.have.class(classes.filled);
+ });
+
+ it('should warn if the variant supplied is equal to the variant inferred', () => {
+ expect(() => {
+ render(
+
+
+ foo
+
+ }
+ />
+ ,
+ );
+ }).toErrorDev([
+ 'MUI: The `InputAdornment` variant infers the variant ' +
+ 'prop you do not have to provide one.',
+ !strictModeDoubleLoggingSuppressed &&
+ 'MUI: The `InputAdornment` variant infers the variant ' +
+ 'prop you do not have to provide one.',
+ ]);
+ });
+ });
+
+ it('should have the disabled pointer events class when disabledPointerEvents true', () => {
+ const { container } = render(
+
+ foo
+ ,
+ );
+ const adornment = container.firstChild;
+
+ expect(adornment).to.have.class(classes.disablePointerEvents);
+ });
+
+ it('should not wrap text children in a Typography when disableTypography true', () => {
+ const { container } = render(
+
+ foo
+ ,
+ );
+
+ expect(container.querySelector(`.${typographyClasses.root}`)).to.equal(null);
+ });
+
+ it('should render children', () => {
+ const { container } = render(
+
+ foo
+ ,
+ );
+ const adornment = container.firstChild;
+
+ expect(adornment.firstChild).to.have.property('nodeName', 'DIV');
+ });
+
+ describe('prop: position', () => {
+ it('should render span for vertical baseline alignment', () => {
+ const { container } = render(
+
+ foo
+ ,
+ );
+ const adornment = container.firstChild;
+
+ expect(adornment.firstChild).to.have.tagName('span');
+ expect(adornment.firstChild).to.have.class('notranslate');
+ expect(adornment.childNodes[1]).to.have.tagName('div');
+ });
+ });
+
+ it('applies a size small class inside ', () => {
+ const { getByTestId } = render(
+
+
+ $
+
+ ,
+ );
+
+ expect(getByTestId('root')).to.have.class(classes.sizeSmall);
+ });
+
+ it('applies a hiddenLabel class inside ', () => {
+ const { getByTestId } = render(
+
+
+ $
+
+ ,
+ );
+
+ expect(getByTestId('root')).to.have.class(classes.hiddenLabel);
+ });
+});
diff --git a/packages/mui-material-next/src/InputAdornment/index.d.ts b/packages/mui-material-next/src/InputAdornment/index.d.ts
new file mode 100644
index 00000000000000..48e5ebc6741b70
--- /dev/null
+++ b/packages/mui-material-next/src/InputAdornment/index.d.ts
@@ -0,0 +1,5 @@
+export * from './InputAdornment';
+export { default } from './InputAdornment';
+
+export { default as inputAdornmentClasses } from './inputAdornmentClasses';
+export * from './inputAdornmentClasses';
diff --git a/packages/mui-material-next/src/InputAdornment/index.js b/packages/mui-material-next/src/InputAdornment/index.js
new file mode 100644
index 00000000000000..e8757bd82b4074
--- /dev/null
+++ b/packages/mui-material-next/src/InputAdornment/index.js
@@ -0,0 +1,5 @@
+'use client';
+export { default } from './InputAdornment';
+
+export { default as inputAdornmentClasses } from './inputAdornmentClasses';
+export * from './inputAdornmentClasses';
diff --git a/packages/mui-material-next/src/InputAdornment/inputAdornmentClasses.ts b/packages/mui-material-next/src/InputAdornment/inputAdornmentClasses.ts
new file mode 100644
index 00000000000000..1b1fadb95e731f
--- /dev/null
+++ b/packages/mui-material-next/src/InputAdornment/inputAdornmentClasses.ts
@@ -0,0 +1,45 @@
+import {
+ unstable_generateUtilityClasses as generateUtilityClasses,
+ unstable_generateUtilityClass as generateUtilityClass,
+} from '@mui/utils';
+
+export interface InputAdornmentClasses {
+ /** Styles applied to the root element. */
+ root: string;
+ /** Styles applied to the root element if `variant="filled"`. */
+ filled: string;
+ /** Styles applied to the root element if `variant="outlined"`. */
+ outlined: string;
+ /** Styles applied to the root element if `variant="standard"`. */
+ standard: string;
+ /** Styles applied to the root element if `position="start"`. */
+ positionStart: string;
+ /** Styles applied to the root element if `position="end"`. */
+ positionEnd: string;
+ /** Styles applied to the root element if `disablePointerEvents={true}`. */
+ disablePointerEvents: string;
+ /** Styles applied if the adornment is used inside . */
+ hiddenLabel: string;
+ /** Styles applied if the adornment is used inside . */
+ sizeSmall: string;
+}
+
+export type InputAdornmentClassKey = keyof InputAdornmentClasses;
+
+export function getInputAdornmentUtilityClass(slot: string): string {
+ return generateUtilityClass('MuiInputAdornment', slot);
+}
+
+const inputAdornmentClasses: InputAdornmentClasses = generateUtilityClasses('MuiInputAdornment', [
+ 'root',
+ 'filled',
+ 'standard',
+ 'outlined',
+ 'positionStart',
+ 'positionEnd',
+ 'disablePointerEvents',
+ 'hiddenLabel',
+ 'sizeSmall',
+]);
+
+export default inputAdornmentClasses;
diff --git a/packages/mui-material-next/src/InputLabel/InputLabel.d.ts b/packages/mui-material-next/src/InputLabel/InputLabel.d.ts
new file mode 100644
index 00000000000000..9b704a57d331b7
--- /dev/null
+++ b/packages/mui-material-next/src/InputLabel/InputLabel.d.ts
@@ -0,0 +1,93 @@
+import * as React from 'react';
+import { SxProps } from '@mui/system';
+import { OverridableStringUnion, OverridableComponent, OverrideProps } from '@mui/types';
+import { FormLabelProps, ExtendFormLabelTypeMap } from '../FormLabel';
+import { Theme } from '../styles';
+import { InputLabelClasses } from './inputLabelClasses';
+
+export interface InputLabelPropsSizeOverrides {}
+
+export interface InputLabelOwnProps {
+ /**
+ * The content of the component.
+ */
+ children?: React.ReactNode;
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+ color?: FormLabelProps['color'];
+ /**
+ * If `true`, the transition animation is disabled.
+ * @default false
+ */
+ disableAnimation?: boolean;
+ /**
+ * If `true`, the component is disabled.
+ */
+ disabled?: boolean;
+ /**
+ * If `true`, the label is displayed in an error state.
+ */
+ error?: boolean;
+ /**
+ * If `true`, the `input` of this label is focused.
+ */
+ focused?: boolean;
+ /**
+ * If `dense`, will adjust vertical spacing. This is normally obtained via context from
+ * FormControl.
+ */
+ margin?: 'dense';
+ /**
+ * if `true`, the label will indicate that the `input` is required.
+ */
+ required?: boolean;
+ /**
+ * If `true`, the label is shrunk.
+ */
+ shrink?: boolean;
+ /**
+ * The size of the component.
+ * @default 'normal'
+ */
+ size?: OverridableStringUnion<'small' | 'normal', InputLabelPropsSizeOverrides>;
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+ /**
+ * The variant to use.
+ */
+ variant?: 'standard' | 'outlined' | 'filled';
+}
+
+export type InputLabelTypeMap<
+ AdditionalProps = {},
+ RootComponent extends React.ElementType = 'label',
+> = ExtendFormLabelTypeMap<{
+ props: AdditionalProps & InputLabelOwnProps;
+ defaultComponent: RootComponent;
+}>;
+
+/**
+ *
+ * Demos:
+ *
+ * - [Text Field](https://mui.com/material-ui/react-text-field/)
+ *
+ * API:
+ *
+ * - [InputLabel API](https://mui.com/material-ui/api/input-label/)
+ * - inherits [FormLabel API](https://mui.com/material-ui/api/form-label/)
+ */
+declare const InputLabel: OverridableComponent;
+
+export type InputLabelProps<
+ RootComponent extends React.ElementType = InputLabelTypeMap['defaultComponent'],
+ AdditionalProps = {},
+> = OverrideProps, RootComponent> & {
+ component?: React.ElementType;
+};
+
+export default InputLabel;
diff --git a/packages/mui-material-next/src/InputLabel/InputLabel.js b/packages/mui-material-next/src/InputLabel/InputLabel.js
new file mode 100644
index 00000000000000..54b6561f9f6cb8
--- /dev/null
+++ b/packages/mui-material-next/src/InputLabel/InputLabel.js
@@ -0,0 +1,249 @@
+'use client';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses';
+import { unstable_capitalize as capitalize } from '@mui/utils';
+import clsx from 'clsx';
+import formControlState from '../FormControl/formControlState';
+import useFormControl from '../FormControl/useFormControl';
+import FormLabel, { formLabelClasses } from '../FormLabel';
+import useThemeProps from '../styles/useThemeProps';
+import styled, { rootShouldForwardProp } from '../styles/styled';
+import { getInputLabelUtilityClasses } from './inputLabelClasses';
+
+const useUtilityClasses = (ownerState) => {
+ const { classes, formControl, size, shrink, disableAnimation, variant, required } = ownerState;
+ const slots = {
+ root: [
+ 'root',
+ formControl && 'formControl',
+ !disableAnimation && 'animated',
+ shrink && 'shrink',
+ size && size !== 'normal' && `size${capitalize(size)}`,
+ variant,
+ ],
+ asterisk: [required && 'asterisk'],
+ };
+
+ const composedClasses = composeClasses(slots, getInputLabelUtilityClasses, classes);
+
+ return {
+ ...classes, // forward the focused, disabled, etc. classes to the FormLabel
+ ...composedClasses,
+ };
+};
+
+const InputLabelRoot = styled(FormLabel, {
+ shouldForwardProp: (prop) => rootShouldForwardProp(prop) || prop === 'classes',
+ name: 'MuiInputLabel',
+ slot: 'Root',
+ overridesResolver: (props, styles) => {
+ const { ownerState } = props;
+ return [
+ { [`& .${formLabelClasses.asterisk}`]: styles.asterisk },
+ styles.root,
+ ownerState.formControl && styles.formControl,
+ ownerState.size === 'small' && styles.sizeSmall,
+ ownerState.shrink && styles.shrink,
+ !ownerState.disableAnimation && styles.animated,
+ styles[ownerState.variant],
+ ];
+ },
+})(({ theme, ownerState }) => ({
+ display: 'block',
+ transformOrigin: 'top left',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ maxWidth: '100%',
+ ...(ownerState.formControl && {
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ // slight alteration to spec spacing to match visual spec result
+ transform: 'translate(0, 20px) scale(1)',
+ }),
+ ...(ownerState.size === 'small' && {
+ // Compensation for the `Input.inputSizeSmall` style.
+ transform: 'translate(0, 17px) scale(1)',
+ }),
+ ...(ownerState.shrink && {
+ transform: 'translate(0, -1.5px) scale(0.75)',
+ transformOrigin: 'top left',
+ maxWidth: '133%',
+ }),
+ ...(!ownerState.disableAnimation && {
+ transition: theme.transitions.create(['color', 'transform', 'max-width'], {
+ duration: theme.transitions.duration.shorter,
+ easing: theme.transitions.easing.easeOut,
+ }),
+ }),
+ ...(ownerState.variant === 'filled' && {
+ // Chrome's autofill feature gives the input field a yellow background.
+ // Since the input field is behind the label in the HTML tree,
+ // the input field is drawn last and hides the label with an opaque background color.
+ // zIndex: 1 will raise the label above opaque background-colors of input.
+ zIndex: 1,
+ pointerEvents: 'none',
+ transform: 'translate(12px, 16px) scale(1)',
+ maxWidth: 'calc(100% - 24px)',
+ ...(ownerState.size === 'small' && {
+ transform: 'translate(12px, 13px) scale(1)',
+ }),
+ ...(ownerState.shrink && {
+ userSelect: 'none',
+ pointerEvents: 'auto',
+ transform: 'translate(12px, 7px) scale(0.75)',
+ maxWidth: 'calc(133% - 24px)',
+ ...(ownerState.size === 'small' && {
+ transform: 'translate(12px, 4px) scale(0.75)',
+ }),
+ }),
+ }),
+ ...(ownerState.variant === 'outlined' && {
+ // see comment above on filled.zIndex
+ zIndex: 1,
+ pointerEvents: 'none',
+ transform: 'translate(14px, 16px) scale(1)',
+ maxWidth: 'calc(100% - 24px)',
+ ...(ownerState.size === 'small' && {
+ transform: 'translate(14px, 9px) scale(1)',
+ }),
+ ...(ownerState.shrink && {
+ userSelect: 'none',
+ pointerEvents: 'auto',
+ // Theoretically, we should have (8+5)*2/0.75 = 34px
+ // but it feels a better when it bleeds a bit on the left, so 32px.
+ maxWidth: 'calc(133% - 32px)',
+ transform: 'translate(14px, -9px) scale(0.75)',
+ }),
+ }),
+}));
+
+const InputLabel = React.forwardRef(function InputLabel(inProps, ref) {
+ const props = useThemeProps({ name: 'MuiInputLabel', props: inProps });
+ const {
+ disableAnimation = false,
+ margin,
+ shrink: shrinkProp,
+ variant,
+ className,
+ ...other
+ } = props;
+
+ const muiFormControl = useFormControl();
+
+ let shrink = shrinkProp;
+ if (typeof shrink === 'undefined' && muiFormControl) {
+ shrink = muiFormControl.filled || muiFormControl.focused || muiFormControl.adornedStart;
+ }
+
+ const fcs = formControlState({
+ props,
+ muiFormControl,
+ states: ['size', 'variant', 'required'],
+ });
+
+ const ownerState = {
+ ...props,
+ disableAnimation,
+ formControl: muiFormControl,
+ shrink,
+ size: fcs.size,
+ variant: fcs.variant,
+ required: fcs.required,
+ };
+
+ const classes = useUtilityClasses(ownerState);
+
+ return (
+
+ );
+});
+
+InputLabel.propTypes /* remove-proptypes */ = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the d.ts file and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * The content of the component.
+ */
+ children: PropTypes.node,
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The color of the component.
+ * It supports both default and custom theme colors, which can be added as shown in the
+ * [palette customization guide](https://mui.com/material-ui/customization/palette/#adding-new-colors).
+ */
+ color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['error', 'info', 'primary', 'secondary', 'success', 'warning']),
+ PropTypes.string,
+ ]),
+ /**
+ * If `true`, the transition animation is disabled.
+ * @default false
+ */
+ disableAnimation: PropTypes.bool,
+ /**
+ * If `true`, the component is disabled.
+ */
+ disabled: PropTypes.bool,
+ /**
+ * If `true`, the label is displayed in an error state.
+ */
+ error: PropTypes.bool,
+ /**
+ * If `true`, the `input` of this label is focused.
+ */
+ focused: PropTypes.bool,
+ /**
+ * If `dense`, will adjust vertical spacing. This is normally obtained via context from
+ * FormControl.
+ */
+ margin: PropTypes.oneOf(['dense']),
+ /**
+ * if `true`, the label will indicate that the `input` is required.
+ */
+ required: PropTypes.bool,
+ /**
+ * If `true`, the label is shrunk.
+ */
+ shrink: PropTypes.bool,
+ /**
+ * The size of the component.
+ * @default 'normal'
+ */
+ size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['normal', 'small']),
+ PropTypes.string,
+ ]),
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
+ PropTypes.func,
+ PropTypes.object,
+ ]),
+ /**
+ * The variant to use.
+ */
+ variant: PropTypes.oneOf(['filled', 'outlined', 'standard']),
+};
+
+export default InputLabel;
diff --git a/packages/mui-material-next/src/InputLabel/InputLabel.spec.tsx b/packages/mui-material-next/src/InputLabel/InputLabel.spec.tsx
new file mode 100644
index 00000000000000..e9e6603b0fa0c8
--- /dev/null
+++ b/packages/mui-material-next/src/InputLabel/InputLabel.spec.tsx
@@ -0,0 +1,32 @@
+import * as React from 'react';
+import { expectType } from '@mui/types';
+import InputLabel from '@mui/material/InputLabel';
+
+const CustomComponent: React.FC<{ prop1: string; prop2: number }> = function CustomComponent() {
+ return ;
+};
+
+const InputLabelTest = () => {
+ return (
+
+
+
+ {
+ expectType, typeof event>(event);
+ }}
+ />
+
+ {/* @ts-expect-error */}
+
+ {/* @ts-expect-error */}
+
+
+ {/* @ts-expect-error */}
+
+ {/* @ts-expect-error */}
+
+
+ );
+};
diff --git a/packages/mui-material-next/src/InputLabel/InputLabel.test.js b/packages/mui-material-next/src/InputLabel/InputLabel.test.js
new file mode 100644
index 00000000000000..1a1b2a7e038f7c
--- /dev/null
+++ b/packages/mui-material-next/src/InputLabel/InputLabel.test.js
@@ -0,0 +1,165 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { expect } from 'chai';
+import { describeConformance, act, createRenderer } from '@mui-internal/test-utils';
+import { ClassNames } from '@emotion/react';
+import FormControl from '@mui/material/FormControl';
+import Input from '@mui/material/Input';
+import FormLabel from '@mui/material/FormLabel';
+import InputLabel, { inputLabelClasses as classes } from '@mui/material/InputLabel';
+
+describe('', () => {
+ const { render } = createRenderer();
+
+ describeConformance(Foo, () => ({
+ classes,
+ inheritComponent: FormLabel,
+ render,
+ refInstanceof: window.HTMLLabelElement,
+ muiName: 'MuiInputLabel',
+ testVariantProps: { size: 'small' },
+ skip: ['componentsProp'],
+ }));
+
+ it('should render a label with text', () => {
+ const { container } = render(Foo);
+ expect(container.querySelector('label')).to.have.text('Foo');
+ });
+
+ it('should have the animated class by default', () => {
+ const { container } = render(Foo);
+ expect(container.firstChild).to.have.class(classes.animated);
+ });
+
+ it('should not have the animated class when disabled', () => {
+ const { container } = render(Foo);
+ expect(container.firstChild).not.to.have.class(classes.animated);
+ });
+
+ it('should forward the asterisk class to AsteriskComponent when required', () => {
+ const { container } = render(
+
+ Foo
+ ,
+ );
+ expect(container.querySelector('.my-asterisk')).to.have.class('MuiFormLabel-asterisk');
+ expect(container.querySelector('.my-asterisk')).to.have.class('MuiInputLabel-asterisk');
+ });
+
+ describe('with FormControl', () => {
+ it('should have the formControl class', () => {
+ const { getByTestId } = render(
+
+
+ ,
+ );
+ expect(getByTestId('root')).to.have.class(classes.formControl);
+ });
+
+ it('should have the small class', () => {
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ expect(getByTestId('root')).to.have.class(classes.sizeSmall);
+ });
+
+ describe('filled', () => {
+ it('applies a shrink class that can be controlled by props', () => {
+ function Wrapper({ children }) {
+ return (
+
+
+ {children}
+
+ );
+ }
+ Wrapper.propTypes = { children: PropTypes.node };
+ const { getByTestId, setProps } = render(name, {
+ wrapper: Wrapper,
+ });
+
+ expect(getByTestId('root')).to.have.class(classes.shrink);
+
+ setProps({ shrink: false });
+ expect(getByTestId('root')).not.to.have.class(classes.shrink);
+
+ setProps({ shrink: true });
+ expect(getByTestId('root')).to.have.class(classes.shrink);
+ });
+ });
+
+ describe('focused', () => {
+ it('applies a shrink class that can be controlled by props', () => {
+ function Wrapper({ children }) {
+ return (
+
+
+ {children}
+
+ );
+ }
+ Wrapper.propTypes = { children: PropTypes.node };
+
+ const { container, getByTestId, setProps } = render(, {
+ wrapper: Wrapper,
+ });
+ act(() => {
+ container.querySelector('input').focus();
+ });
+
+ expect(getByTestId('root')).to.have.class(classes.shrink);
+
+ setProps({ shrink: false });
+ expect(getByTestId('root')).not.to.have.class(classes.shrink);
+
+ setProps({ shrink: true });
+ expect(getByTestId('root')).to.have.class(classes.shrink);
+ });
+ });
+ });
+
+ describe('Emotion compatibility', () => {
+ it('classes.root should overwrite built-in styles.', () => {
+ const text = 'The label';
+
+ const { getByText } = render(
+
+ {({ css }) => (
+
+ {text}
+
+ )}
+ ,
+ );
+ const label = getByText(text);
+
+ expect(getComputedStyle(label).position).to.equal('static');
+ });
+
+ it('className should overwrite classes.root and built-in styles.', () => {
+ const text = 'The label';
+
+ const { getByText } = render(
+
+ {({ css }) => (
+
+
+ {text}
+
+
+ )}
+ ,
+ );
+ const label = getByText(text);
+
+ expect(getComputedStyle(label).position).to.equal('static');
+ });
+ });
+});
diff --git a/packages/mui-material-next/src/InputLabel/index.d.ts b/packages/mui-material-next/src/InputLabel/index.d.ts
new file mode 100644
index 00000000000000..1bee4cf24899ca
--- /dev/null
+++ b/packages/mui-material-next/src/InputLabel/index.d.ts
@@ -0,0 +1,5 @@
+export { default } from './InputLabel';
+export * from './InputLabel';
+
+export { default as inputLabelClasses } from './inputLabelClasses';
+export * from './inputLabelClasses';
diff --git a/packages/mui-material-next/src/InputLabel/index.js b/packages/mui-material-next/src/InputLabel/index.js
new file mode 100644
index 00000000000000..f1050f1d809098
--- /dev/null
+++ b/packages/mui-material-next/src/InputLabel/index.js
@@ -0,0 +1,5 @@
+'use client';
+export { default } from './InputLabel';
+
+export { default as inputLabelClasses } from './inputLabelClasses';
+export * from './inputLabelClasses';
diff --git a/packages/mui-material-next/src/InputLabel/inputLabelClasses.ts b/packages/mui-material-next/src/InputLabel/inputLabelClasses.ts
new file mode 100644
index 00000000000000..6ea43d13ce1719
--- /dev/null
+++ b/packages/mui-material-next/src/InputLabel/inputLabelClasses.ts
@@ -0,0 +1,57 @@
+import {
+ unstable_generateUtilityClass as generateUtilityClass,
+ unstable_generateUtilityClasses as generateUtilityClasses,
+} from '@mui/utils';
+
+export interface InputLabelClasses {
+ /** Styles applied to the root element. */
+ root: string;
+ /** State class applied to the root element if `focused={true}`. */
+ focused: string;
+ /** State class applied to the root element if `disabled={true}`. */
+ disabled: string;
+ /** State class applied to the root element if `error={true}`. */
+ error: string;
+ /** State class applied to the root element if `required={true}`. */
+ required: string;
+ /** State class applied to the asterisk element. */
+ asterisk: string;
+ /** Styles applied to the root element if the component is a descendant of `FormControl`. */
+ formControl: string;
+ /** Styles applied to the root element if `size="small"`. */
+ sizeSmall: string;
+ /** Styles applied to the input element if `shrink={true}`. */
+ shrink: string;
+ /** Styles applied to the input element unless `disableAnimation={true}`. */
+ animated: string;
+ /** Styles applied to the root element if `variant="filled"`. */
+ filled: string;
+ /** Styles applied to the root element if `variant="outlined"`. */
+ outlined: string;
+ /** Styles applied to the root element if `variant="standard"`. */
+ standard: string;
+}
+
+export type InputLabelClassKey = keyof InputLabelClasses;
+
+export function getInputLabelUtilityClasses(slot: string): string {
+ return generateUtilityClass('MuiInputLabel', slot);
+}
+
+const inputLabelClasses: InputLabelClasses = generateUtilityClasses('MuiInputLabel', [
+ 'root',
+ 'focused',
+ 'disabled',
+ 'error',
+ 'required',
+ 'asterisk',
+ 'formControl',
+ 'sizeSmall',
+ 'shrink',
+ 'animated',
+ 'standard',
+ 'filled',
+ 'outlined',
+]);
+
+export default inputLabelClasses;
diff --git a/packages/mui-material-next/src/Menu/Menu.d.ts b/packages/mui-material-next/src/Menu/Menu.d.ts
new file mode 100644
index 00000000000000..772dd71055b489
--- /dev/null
+++ b/packages/mui-material-next/src/Menu/Menu.d.ts
@@ -0,0 +1,97 @@
+/* eslint-disable no-restricted-imports */
+import * as React from 'react';
+import { SxProps } from '@mui/system';
+import { InternalStandardProps as StandardProps, Theme } from '@mui/material';
+import { PaperProps } from '@mui/material/Paper';
+import { PopoverProps } from '@mui/material/Popover';
+import { MenuListProps } from '@mui/material/MenuList';
+import { TransitionProps } from '@mui/material/transitions/transition';
+import { MenuClasses } from './menuClasses';
+
+export interface MenuProps extends StandardProps {
+ /**
+ * An HTML element, or a function that returns one.
+ * It's used to set the position of the menu.
+ */
+ anchorEl?: PopoverProps['anchorEl'];
+ /**
+ * If `true` (Default) will focus the `[role="menu"]` if no focusable child is found. Disabled
+ * children are not focusable. If you set this prop to `false` focus will be placed
+ * on the parent modal container. This has severe accessibility implications
+ * and should only be considered if you manage focus otherwise.
+ * @default true
+ */
+ autoFocus?: boolean;
+ /**
+ * Menu contents, normally `MenuItem`s.
+ */
+ children?: React.ReactNode;
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+ /**
+ * When opening the menu will not focus the active item but the `[role="menu"]`
+ * unless `autoFocus` is also set to `false`. Not using the default means not
+ * following WAI-ARIA authoring practices. Please be considerate about possible
+ * accessibility implications.
+ * @default false
+ */
+ disableAutoFocusItem?: boolean;
+ /**
+ * Props applied to the [`MenuList`](/material-ui/api/menu-list/) element.
+ * @default {}
+ */
+ MenuListProps?: Partial;
+ /**
+ * Callback fired when the component requests to be closed.
+ *
+ * @param {object} event The event source of the callback.
+ * @param {string} reason Can be: `"escapeKeyDown"`, `"backdropClick"`, `"tabKeyDown"`.
+ */
+ onClose?: PopoverProps['onClose'];
+ /**
+ * If `true`, the component is shown.
+ */
+ open: boolean;
+ /**
+ * `classes` prop applied to the [`Popover`](/material-ui/api/popover/) element.
+ */
+ PopoverClasses?: PopoverProps['classes'];
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+ /**
+ * The length of the transition in `ms`, or 'auto'
+ * @default 'auto'
+ */
+ transitionDuration?: TransitionProps['timeout'] | 'auto';
+ /**
+ * Props applied to the transition element.
+ * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component.
+ * @default {}
+ */
+ TransitionProps?: TransitionProps;
+ /**
+ * The variant to use. Use `menu` to prevent selected items from impacting the initial focus.
+ * @default 'selectedMenu'
+ */
+ variant?: 'menu' | 'selectedMenu';
+}
+
+export declare const MenuPaper: React.FC;
+
+/**
+ *
+ * Demos:
+ *
+ * - [App Bar](https://mui.com/material-ui/react-app-bar/)
+ * - [Menu](https://mui.com/material-ui/react-menu/)
+ *
+ * API:
+ *
+ * - [Menu API](https://mui.com/material-ui/api/menu/)
+ * - inherits [Popover API](https://mui.com/material-ui/api/popover/)
+ */
+export default function Menu(props: MenuProps): JSX.Element;
diff --git a/packages/mui-material-next/src/Menu/Menu.js b/packages/mui-material-next/src/Menu/Menu.js
new file mode 100644
index 00000000000000..928821b54d23a1
--- /dev/null
+++ b/packages/mui-material-next/src/Menu/Menu.js
@@ -0,0 +1,337 @@
+'use client';
+import * as React from 'react';
+import { isFragment } from 'react-is';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses';
+import { useSlotProps } from '@mui/base/utils';
+import { HTMLElementType } from '@mui/utils';
+import MenuList from '@mui/material/MenuList';
+import Popover, { PopoverPaper } from '@mui/material/Popover';
+import styled, { rootShouldForwardProp } from '../styles/styled';
+import useTheme from '../styles/useTheme';
+import useThemeProps from '../styles/useThemeProps';
+import { getMenuUtilityClass } from './menuClasses';
+
+const RTL_ORIGIN = {
+ vertical: 'top',
+ horizontal: 'right',
+};
+
+const LTR_ORIGIN = {
+ vertical: 'top',
+ horizontal: 'left',
+};
+
+const useUtilityClasses = (ownerState) => {
+ const { classes } = ownerState;
+
+ const slots = {
+ root: ['root'],
+ paper: ['paper'],
+ list: ['list'],
+ };
+
+ return composeClasses(slots, getMenuUtilityClass, classes);
+};
+
+const MenuRoot = styled(Popover, {
+ shouldForwardProp: (prop) => rootShouldForwardProp(prop) || prop === 'classes',
+ name: 'MuiMenu',
+ slot: 'Root',
+ overridesResolver: (props, styles) => styles.root,
+})({});
+
+export const MenuPaper = styled(PopoverPaper, {
+ name: 'MuiMenu',
+ slot: 'Paper',
+ overridesResolver: (props, styles) => styles.paper,
+})({
+ // specZ: The maximum height of a simple menu should be one or more rows less than the view
+ // height. This ensures a tappable area outside of the simple menu with which to dismiss
+ // the menu.
+ maxHeight: 'calc(100% - 96px)',
+ // Add iOS momentum scrolling for iOS < 13.0
+ WebkitOverflowScrolling: 'touch',
+});
+
+const MenuMenuList = styled(MenuList, {
+ name: 'MuiMenu',
+ slot: 'List',
+ overridesResolver: (props, styles) => styles.list,
+})({
+ // We disable the focus ring for mouse, touch and keyboard users.
+ outline: 0,
+});
+
+const Menu = React.forwardRef(function Menu(inProps, ref) {
+ const props = useThemeProps({ props: inProps, name: 'MuiMenu' });
+
+ const {
+ autoFocus = true,
+ children,
+ className,
+ disableAutoFocusItem = false,
+ MenuListProps = {},
+ onClose,
+ open,
+ PaperProps = {},
+ PopoverClasses,
+ transitionDuration = 'auto',
+ TransitionProps: { onEntering, ...TransitionProps } = {},
+ variant = 'selectedMenu',
+ slots = {},
+ slotProps = {},
+ ...other
+ } = props;
+
+ const theme = useTheme();
+ const isRtl = theme.direction === 'rtl';
+
+ const ownerState = {
+ ...props,
+ autoFocus,
+ disableAutoFocusItem,
+ MenuListProps,
+ onEntering,
+ PaperProps,
+ transitionDuration,
+ TransitionProps,
+ variant,
+ };
+
+ const classes = useUtilityClasses(ownerState);
+
+ const autoFocusItem = autoFocus && !disableAutoFocusItem && open;
+
+ const menuListActionsRef = React.useRef(null);
+
+ const handleEntering = (element, isAppearing) => {
+ if (menuListActionsRef.current) {
+ menuListActionsRef.current.adjustStyleForScrollbar(element, theme);
+ }
+
+ if (onEntering) {
+ onEntering(element, isAppearing);
+ }
+ };
+
+ const handleListKeyDown = (event) => {
+ if (event.key === 'Tab') {
+ event.preventDefault();
+
+ if (onClose) {
+ onClose(event, 'tabKeyDown');
+ }
+ }
+ };
+
+ /**
+ * the index of the item should receive focus
+ * in a `variant="selectedMenu"` it's the first `selected` item
+ * otherwise it's the very first item.
+ */
+ let activeItemIndex = -1;
+ // since we inject focus related props into children we have to do a lookahead
+ // to check if there is a `selected` item. We're looking for the last `selected`
+ // item and use the first valid item as a fallback
+ React.Children.map(children, (child, index) => {
+ if (!React.isValidElement(child)) {
+ return;
+ }
+
+ if (process.env.NODE_ENV !== 'production') {
+ if (isFragment(child)) {
+ console.error(
+ [
+ "MUI: The Menu component doesn't accept a Fragment as a child.",
+ 'Consider providing an array instead.',
+ ].join('\n'),
+ );
+ }
+ }
+
+ if (!child.props.disabled) {
+ if (variant === 'selectedMenu' && child.props.selected) {
+ activeItemIndex = index;
+ } else if (activeItemIndex === -1) {
+ activeItemIndex = index;
+ }
+ }
+ });
+
+ const PaperSlot = slots.paper ?? MenuPaper;
+ const paperExternalSlotProps = slotProps.paper ?? PaperProps;
+
+ const rootSlotProps = useSlotProps({
+ elementType: slots.root,
+ externalSlotProps: slotProps.root,
+ ownerState,
+ className: [classes.root, className],
+ });
+
+ const paperSlotProps = useSlotProps({
+ elementType: PaperSlot,
+ externalSlotProps: paperExternalSlotProps,
+ ownerState,
+ className: classes.paper,
+ });
+
+ return (
+
+
+ {children}
+
+
+ );
+});
+
+Menu.propTypes /* remove-proptypes */ = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the d.ts file and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * An HTML element, or a function that returns one.
+ * It's used to set the position of the menu.
+ */
+ anchorEl: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ HTMLElementType,
+ PropTypes.func,
+ ]),
+ /**
+ * If `true` (Default) will focus the `[role="menu"]` if no focusable child is found. Disabled
+ * children are not focusable. If you set this prop to `false` focus will be placed
+ * on the parent modal container. This has severe accessibility implications
+ * and should only be considered if you manage focus otherwise.
+ * @default true
+ */
+ autoFocus: PropTypes.bool,
+ /**
+ * Menu contents, normally `MenuItem`s.
+ */
+ children: PropTypes.node,
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * When opening the menu will not focus the active item but the `[role="menu"]`
+ * unless `autoFocus` is also set to `false`. Not using the default means not
+ * following WAI-ARIA authoring practices. Please be considerate about possible
+ * accessibility implications.
+ * @default false
+ */
+ disableAutoFocusItem: PropTypes.bool,
+ /**
+ * Props applied to the [`MenuList`](/material-ui/api/menu-list/) element.
+ * @default {}
+ */
+ MenuListProps: PropTypes.object,
+ /**
+ * Callback fired when the component requests to be closed.
+ *
+ * @param {object} event The event source of the callback.
+ * @param {string} reason Can be: `"escapeKeyDown"`, `"backdropClick"`, `"tabKeyDown"`.
+ */
+ onClose: PropTypes.func,
+ /**
+ * If `true`, the component is shown.
+ */
+ open: PropTypes.bool.isRequired,
+ /**
+ * @ignore
+ */
+ PaperProps: PropTypes.object,
+ /**
+ * `classes` prop applied to the [`Popover`](/material-ui/api/popover/) element.
+ */
+ PopoverClasses: PropTypes.object,
+ /**
+ * The extra props for the slot components.
+ * You can override the existing props or add new ones.
+ *
+ * @default {}
+ */
+ slotProps: PropTypes.shape({
+ paper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+ root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+ }),
+ /**
+ * The components used for each slot inside.
+ *
+ * @default {}
+ */
+ slots: PropTypes.shape({
+ paper: PropTypes.elementType,
+ root: PropTypes.elementType,
+ }),
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
+ PropTypes.func,
+ PropTypes.object,
+ ]),
+ /**
+ * The length of the transition in `ms`, or 'auto'
+ * @default 'auto'
+ */
+ transitionDuration: PropTypes.oneOfType([
+ PropTypes.oneOf(['auto']),
+ PropTypes.number,
+ PropTypes.shape({
+ appear: PropTypes.number,
+ enter: PropTypes.number,
+ exit: PropTypes.number,
+ }),
+ ]),
+ /**
+ * Props applied to the transition element.
+ * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component.
+ * @default {}
+ */
+ TransitionProps: PropTypes.object,
+ /**
+ * The variant to use. Use `menu` to prevent selected items from impacting the initial focus.
+ * @default 'selectedMenu'
+ */
+ variant: PropTypes.oneOf(['menu', 'selectedMenu']),
+};
+
+export default Menu;
diff --git a/packages/mui-material-next/src/Menu/Menu.test.js b/packages/mui-material-next/src/Menu/Menu.test.js
new file mode 100644
index 00000000000000..a44da7bede2841
--- /dev/null
+++ b/packages/mui-material-next/src/Menu/Menu.test.js
@@ -0,0 +1,407 @@
+import * as React from 'react';
+import { spy } from 'sinon';
+import { expect } from 'chai';
+import {
+ createRenderer,
+ createMount,
+ describeConformance,
+ screen,
+ fireEvent,
+ strictModeDoubleLoggingSuppressed,
+} from '@mui-internal/test-utils';
+import Popover from '@mui/material/Popover';
+import { createTheme, ThemeProvider } from '@mui/material/styles';
+import Menu, { MenuPaper } from './Menu';
+import classes from './menuClasses';
+
+describe('', () => {
+ const { render } = createRenderer({ clock: 'fake' });
+ const mount = createMount();
+
+ describeConformance(