From e039510335b2020c578867c1b0d24ecb7935756f Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Tue, 13 Dec 2022 14:13:29 +0100 Subject: [PATCH 01/88] [Material You] Add shape design tokens (#35393) --- docs/pages/experiments/md3/index.tsx | 2 +- .../mui-material-next/src/Button/Button.tsx | 2 +- .../src/styles/Theme.types.ts | 40 ++++++++++++------- .../src/styles/defaultTheme.ts | 3 -- .../src/styles/extendTheme.ts | 16 ++++---- .../mui-material-next/src/styles/shape.ts | 19 +++++++++ .../src/styles/{states.ts => state.ts} | 4 +- .../src/styles/styled.test.js | 26 ++++++++++++ .../mui-material-next/src/styles/sxConfig.ts | 29 +++++++++++++- packages/mui-system/src/spacing.d.ts | 4 +- 10 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 packages/mui-material-next/src/styles/shape.ts rename packages/mui-material-next/src/styles/{states.ts => state.ts} (74%) diff --git a/docs/pages/experiments/md3/index.tsx b/docs/pages/experiments/md3/index.tsx index b2eb04b23f59d6..6916f9d02b6ccd 100644 --- a/docs/pages/experiments/md3/index.tsx +++ b/docs/pages/experiments/md3/index.tsx @@ -159,7 +159,7 @@ function DemoComponents() { bgcolor: 'error', border: 1, borderColor: 'tertiary', - borderRadius: '1px', + borderRadius: 'medium', }} > Button diff --git a/packages/mui-material-next/src/Button/Button.tsx b/packages/mui-material-next/src/Button/Button.tsx index bf482406fed6ba..bc6394f4e9e7fe 100644 --- a/packages/mui-material-next/src/Button/Button.tsx +++ b/packages/mui-material-next/src/Button/Button.tsx @@ -268,7 +268,7 @@ export const ButtonRoot = styled('button', { theme.sys.typescale.label.large.tracking / theme.sys.typescale.label.large.size }rem`; - const borderRadiusValue: string | number = tokens.md3.shape.borderRadius; + const borderRadiusValue: string | number = tokens.sys.shape.corner.full; const borderRadius = Number.isNaN(Number(borderRadiusValue)) ? borderRadiusValue : `${borderRadiusValue}px`; diff --git a/packages/mui-material-next/src/styles/Theme.types.ts b/packages/mui-material-next/src/styles/Theme.types.ts index 440530c0da2c57..3c0a16c371edb9 100644 --- a/packages/mui-material-next/src/styles/Theme.types.ts +++ b/packages/mui-material-next/src/styles/Theme.types.ts @@ -89,7 +89,7 @@ export interface MD3Typeface { }; } -export interface MD3States { +export interface MD3State { hover: { stateLayerOpacity: number; }; @@ -129,21 +129,35 @@ export interface MD3Typescale { display: TypescaleValue; } -export interface Shapes { - borderRadius: number; +export interface MD3Shape { + corner: { + none: string; + extraSmall: string; + extraSmallTop: string; + small: string; + medium: string; + large: string; + largeEnd: string; + largeTop: string; + extraLarge: string; + extraLargeTop: string; + full: string; + }; +} + +export interface MD3ShapeOptions { + corner?: Partial; } export interface MD3CssVarsThemeOptions extends Omit { - md3?: { - shape?: Partial; - }; ref?: { typeface?: Partial; }; sys?: { typescale?: Partial; - state?: Partial; + state?: Partial; elevation?: string[]; + shape?: MD3ShapeOptions; }; } @@ -173,11 +187,9 @@ export interface Theme extends Omit { sys: { color: MD3ColorSchemeTokens; typescale: MD3Typescale; - state: MD3States; + state: MD3State; elevation: string[]; - }; - md3: { - shape: Shapes; + shape: MD3Shape; }; palette: MD2Theme['palette']; vars: MD2Theme['vars'] & { @@ -189,11 +201,9 @@ export interface Theme extends Omit { sys: { color: MD3ColorSchemeTokens; typescale: MD3Typescale; - state: MD3States; + state: MD3State; elevation: string[]; - }; - md3: { - shape: Shapes; + shape: MD3Shape; }; }; unstable_sxConfig: SxConfig; diff --git a/packages/mui-material-next/src/styles/defaultTheme.ts b/packages/mui-material-next/src/styles/defaultTheme.ts index 3daba490449ec5..75b041c1185e21 100644 --- a/packages/mui-material-next/src/styles/defaultTheme.ts +++ b/packages/mui-material-next/src/styles/defaultTheme.ts @@ -12,7 +12,6 @@ export const getThemeWithVars = ( opacity, overlays, shape, - md3, ref, sys, palette: paletteInput, @@ -37,7 +36,6 @@ export const getThemeWithVars = ( opacity, overlays, shape, - md3, ref: { ...ref, ...colorSchemeRef, @@ -72,7 +70,6 @@ export const getThemeWithVars = ( ...sys, ...colorSchemeSys, }, - md3, palette, }, } as unknown as Theme; diff --git a/packages/mui-material-next/src/styles/extendTheme.ts b/packages/mui-material-next/src/styles/extendTheme.ts index f4f9b19c1c73d6..460c461c1c084f 100644 --- a/packages/mui-material-next/src/styles/extendTheme.ts +++ b/packages/mui-material-next/src/styles/extendTheme.ts @@ -23,8 +23,9 @@ import createMd3LightColorScheme from './createLightColorScheme'; import createMd3DarkColorScheme from './createDarkColorScheme'; import md3Typescale from './typescale'; import md3Typeface from './typeface'; -import md3State from './states'; +import md3State from './state'; import { elevationLight, elevationDark } from './elevation'; +import md3shape from './shape'; const defaultLightOverlays: Overlays = [...Array(25)].map(() => undefined) as Overlays; const defaultDarkOverlays: Overlays = [...Array(25)].map((_, index) => { @@ -55,6 +56,11 @@ export default function extendTheme(options: CssVarsThemeOptions = {}, ...args: const md3LightColors = createMd3LightColorScheme(getCssVar, md3CommonPalette); const md3DarkColors = createMd3DarkColorScheme(getCssVar, md3CommonPalette); + const shape = { + ...input.sys?.shape, + ...md3shape, + corner: { ...input.sys?.shape?.corner, ...md3shape.corner }, + }; const { palette: lightPalette, @@ -79,12 +85,7 @@ export default function extendTheme(options: CssVarsThemeOptions = {}, ...args: state: { ...md3State, ...input.sys?.state }, color: { ...md3LightColors, ...colorSchemesInput.light?.sys?.color }, elevation: colorSchemesInput.light?.sys?.elevation ?? elevationLight, - }, - md3: { - shape: { - borderRadius: 100, - ...input?.shape, - }, + shape, }, palette: { ...(colorSchemesInput.light && colorSchemesInput.light?.palette), @@ -114,6 +115,7 @@ export default function extendTheme(options: CssVarsThemeOptions = {}, ...args: state: { ...md3State, ...input.sys?.state }, color: { ...md3DarkColors, ...colorSchemesInput.dark?.sys?.color }, elevation: colorSchemesInput.dark?.sys?.elevation ?? elevationDark, + shape, }, }); diff --git a/packages/mui-material-next/src/styles/shape.ts b/packages/mui-material-next/src/styles/shape.ts new file mode 100644 index 00000000000000..8da414f66e4e2a --- /dev/null +++ b/packages/mui-material-next/src/styles/shape.ts @@ -0,0 +1,19 @@ +import { MD3Shape } from './Theme.types'; + +const mdSysShape: MD3Shape = { + corner: { + none: '0px', + extraSmall: '4px', + extraSmallTop: '4px 4px 0 0', + small: '8px', + medium: '12px', + large: '16px', + largeEnd: '0 16px 16px 0', + largeTop: '16px 16px 0 0', + extraLarge: '28px', + extraLargeTop: '28px 28px 0 0', + full: '100px', + }, +}; + +export default mdSysShape; diff --git a/packages/mui-material-next/src/styles/states.ts b/packages/mui-material-next/src/styles/state.ts similarity index 74% rename from packages/mui-material-next/src/styles/states.ts rename to packages/mui-material-next/src/styles/state.ts index 13f07fc543a7d2..2db34a26b47660 100644 --- a/packages/mui-material-next/src/styles/states.ts +++ b/packages/mui-material-next/src/styles/state.ts @@ -1,4 +1,6 @@ -const mdSysState = { +import { MD3State } from './Theme.types'; + +const mdSysState: MD3State = { hover: { stateLayerOpacity: 0.08, }, diff --git a/packages/mui-material-next/src/styles/styled.test.js b/packages/mui-material-next/src/styles/styled.test.js index 43fa2987f0b07e..28d4a0ef06dae6 100644 --- a/packages/mui-material-next/src/styles/styled.test.js +++ b/packages/mui-material-next/src/styles/styled.test.js @@ -198,5 +198,31 @@ describe('styled', () => { borderRightColor: 'rgb(242, 184, 181)', }); }); + + it('should apply borderRadius from sys.shape.corner', function test() { + if (/jsdom/.test(window.navigator.userAgent)) { + this.skip(); + } + const Div = styled('div')``; + + render(
); + + expect(screen.getByTestId('target')).toHaveComputedStyle({ + borderTopLeftRadius: '8px', + }); + }); + + it('should multiple borderRadius with theme.shape.borderRadius if provided as number', function test() { + if (/jsdom/.test(window.navigator.userAgent)) { + this.skip(); + } + const Div = styled('div')``; + + render(
); + + expect(screen.getByTestId('target')).toHaveComputedStyle({ + borderTopLeftRadius: '16px', + }); + }); }); }); diff --git a/packages/mui-material-next/src/styles/sxConfig.ts b/packages/mui-material-next/src/styles/sxConfig.ts index eb2bc0efa2743d..83f2932de1c743 100644 --- a/packages/mui-material-next/src/styles/sxConfig.ts +++ b/packages/mui-material-next/src/styles/sxConfig.ts @@ -1,4 +1,11 @@ -import { getPath, handleBreakpoints, SxConfig, unstable_defaultSxConfig } from '@mui/system'; +import { + getPath, + handleBreakpoints, + SxConfig, + unstable_defaultSxConfig, + createUnaryUnit, + getValue, +} from '@mui/system'; interface PaletteStyleOptions { prop: string; @@ -33,6 +40,23 @@ function createPaletteStyle(options: PaletteStyleOptions = { prop: 'color' }) { return fn; } +// eslint-disable-next-line no-restricted-globals +const isNumber = (value: string | number) => typeof value === 'number' || !isNaN(parseFloat(value)); + +const createBorderRadiusStyle = (props: Record) => { + if (props.borderRadius !== undefined && props.borderRadius !== null) { + const numberTransformer = createUnaryUnit(props.theme, 'shape.borderRadius', 4, 'borderRadius'); + const styleFromPropValue = (propValue: string | number) => ({ + borderRadius: isNumber(propValue) + ? getValue(numberTransformer, propValue) + : getPath(props.theme, `sys.shape.corner.${propValue}`, true), + }); + return handleBreakpoints(props, props.borderRadius, styleFromPropValue); + } + + return null; +}; + const sxConfig: SxConfig = { ...unstable_defaultSxConfig, color: { @@ -59,6 +83,9 @@ const sxConfig: SxConfig = { borderRightColor: { style: createPaletteStyle({ prop: 'borderRightColor' }), }, + borderRadius: { + style: createBorderRadiusStyle, + }, }; export default sxConfig; diff --git a/packages/mui-system/src/spacing.d.ts b/packages/mui-system/src/spacing.d.ts index d5587dc06beeb9..bd50acd23899ea 100644 --- a/packages/mui-system/src/spacing.d.ts +++ b/packages/mui-system/src/spacing.d.ts @@ -1,5 +1,6 @@ import { SimpleStyleFunction, spacing, PropsFor } from './Box'; +export type SpacingValueType = string | number | null | undefined; export type SpacingProps = PropsFor; export function createUnarySpacing(theme: { spacing: Spacing }): Spacing extends number ? (abs: number | string) => number | number @@ -16,7 +17,7 @@ export function createUnaryUnit( defaultValue: Spacing, propName: string, ): Spacing extends number - ? (abs: number | string) => number | number + ? (abs: SpacingValueType) => number | number : Spacing extends any[] ? (abs: Index | string) => Spacing[Index] | string : Spacing extends (...args: unknown[]) => unknown @@ -72,7 +73,6 @@ export const padding: SimpleStyleFunction< | 'paddingBlockEnd' >; -export type SpacingValueType = string | number | null | undefined; export function getValue( transformer: (prop: SpacingValueType) => SpacingValueType, propValue: SpacingValueType, From 56c59c69db8b1ab84ea42b54eaa57b7893275787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Tue, 13 Dec 2022 15:19:11 +0100 Subject: [PATCH 02/88] [Button][base] Set active class when a subcomponent is clicked (#35410) --- .../src/ButtonUnstyled/useButton.test.tsx | 40 +++++++++++++++++++ .../mui-base/src/ButtonUnstyled/useButton.ts | 18 ++++----- .../src/ButtonUnstyled/useButton.types.ts | 1 - 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/packages/mui-base/src/ButtonUnstyled/useButton.test.tsx b/packages/mui-base/src/ButtonUnstyled/useButton.test.tsx index 18aebe3319c1ab..b3f066f44091ad 100644 --- a/packages/mui-base/src/ButtonUnstyled/useButton.test.tsx +++ b/packages/mui-base/src/ButtonUnstyled/useButton.test.tsx @@ -41,6 +41,46 @@ describe('useButton', () => { fireEvent.keyUp(button, { key: ' ' }); expect(button).not.to.have.class('active'); }); + + it('is set when clicked on an element inside the button', () => { + function TestComponent() { + const buttonRef = React.useRef(null); + const { active, getRootProps } = useButton({ ref: buttonRef }); + + return ( + + ); + } + + const { getByText, getByRole } = render(); + const span = getByText('Click here'); + const button = getByRole('button'); + fireEvent.mouseDown(span); + expect(button).to.have.class('active'); + }); + + it('is unset when mouse button is released above another element', () => { + function TestComponent() { + const buttonRef = React.useRef(null); + const { active, getRootProps } = useButton({ ref: buttonRef }); + + return ( +
+
+ ); + } + + const { getByRole, getByTestId } = render(); + const button = getByRole('button'); + const background = getByTestId('parent'); + fireEvent.mouseDown(button); + expect(button).to.have.class('active'); + fireEvent.mouseUp(background); + expect(button).not.to.have.class('active'); + }); }); describe('when using a span element', () => { diff --git a/packages/mui-base/src/ButtonUnstyled/useButton.ts b/packages/mui-base/src/ButtonUnstyled/useButton.ts index ec5e1cfea2cfc3..5ad1c81dc671a8 100644 --- a/packages/mui-base/src/ButtonUnstyled/useButton.ts +++ b/packages/mui-base/src/ButtonUnstyled/useButton.ts @@ -91,21 +91,20 @@ export default function useButton(parameters: UseButtonParameters) { }; const createHandleMouseDown = (otherHandlers: EventHandlers) => (event: React.MouseEvent) => { - if (event.target === event.currentTarget && !disabled) { + if (!disabled) { setActive(true); + document.addEventListener( + 'mouseup', + () => { + setActive(false); + }, + { once: true }, + ); } otherHandlers.onMouseDown?.(event); }; - const createHandleMouseUp = (otherHandlers: EventHandlers) => (event: React.MouseEvent) => { - if (event.target === event.currentTarget) { - setActive(false); - } - - otherHandlers.onMouseUp?.(event); - }; - const createHandleKeyDown = (otherHandlers: EventHandlers) => (event: React.KeyboardEvent) => { otherHandlers.onKeyDown?.(event); @@ -213,7 +212,6 @@ export default function useButton(parameters: UseButtonParameters) { onKeyUp: createHandleKeyUp(externalEventHandlers), onMouseDown: createHandleMouseDown(externalEventHandlers), onMouseLeave: createHandleMouseLeave(externalEventHandlers), - onMouseUp: createHandleMouseUp(externalEventHandlers), ref: handleRef, }; }; diff --git a/packages/mui-base/src/ButtonUnstyled/useButton.types.ts b/packages/mui-base/src/ButtonUnstyled/useButton.types.ts index 60dddbfe2d5b13..d2665a2ac3dd10 100644 --- a/packages/mui-base/src/ButtonUnstyled/useButton.types.ts +++ b/packages/mui-base/src/ButtonUnstyled/useButton.types.ts @@ -12,7 +12,6 @@ export interface UseButtonRootSlotOwnProps { onKeyUp: React.KeyboardEventHandler; onMouseDown: React.MouseEventHandler; onMouseLeave: React.MouseEventHandler; - onMouseUp: React.MouseEventHandler; ref: React.Ref; } From 36d3c1d55f09b02d210d69408e4ffd6bed8e74cb Mon Sep 17 00:00:00 2001 From: Talgat Uspanov Date: Tue, 13 Dec 2022 21:40:41 +0600 Subject: [PATCH 03/88] [l10n] Change Kazakh locale name to match ISO-639-1 codes (#34664) --- docs/data/material/guides/localization/localization-pt.md | 2 +- docs/data/material/guides/localization/localization-zh.md | 2 +- docs/data/material/guides/localization/localization.md | 2 +- packages/mui-material/src/locale/index.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data/material/guides/localization/localization-pt.md b/docs/data/material/guides/localization/localization-pt.md index 9eeab71e4d8ed3..773db72e0335b0 100644 --- a/docs/data/material/guides/localization/localization-pt.md +++ b/docs/data/material/guides/localization/localization-pt.md @@ -67,7 +67,7 @@ const theme = createTheme( | Italian | it-IT | `itIT` | | Japanese | ja-JP | `jaJP` | | Khmer | kh-KH | `khKH` | -| Kazakh | kz-KZ | `kzKZ` | +| Kazakh | kk-KZ | `kkKZ` | | Korean | ko-KR | `koKR` | | Macedonian | mk-MK | `mkMK` | | Norwegian (bokmål) | nb-NO | `nbNO` | diff --git a/docs/data/material/guides/localization/localization-zh.md b/docs/data/material/guides/localization/localization-zh.md index 2a32dbd3f36a0a..56fa87bb63d496 100644 --- a/docs/data/material/guides/localization/localization-zh.md +++ b/docs/data/material/guides/localization/localization-zh.md @@ -67,7 +67,7 @@ const theme = createTheme( | Italian | it-IT | `itIT` | | Japanese | ja-JP | `jaJP` | | Khmer | kh-KH | `khKH` | -| Kazakh | kz-KZ | `kzKZ` | +| Kazakh | kk-KZ | `kkKZ` | | Korean | ko-KR | `koKR` | | Macedonian | mk-MK | `mkMK` | | Norwegian (bokmål) | nb-NO | `nbNO` | diff --git a/docs/data/material/guides/localization/localization.md b/docs/data/material/guides/localization/localization.md index 5b131c3152ee1e..9896a3412dff93 100644 --- a/docs/data/material/guides/localization/localization.md +++ b/docs/data/material/guides/localization/localization.md @@ -68,7 +68,7 @@ The [Data Grid and Data Grid Pro](/x/react-data-grid/) components have their own | Italian | it-IT | `itIT` | | Japanese | ja-JP | `jaJP` | | Khmer | kh-KH | `khKH` | -| Kazakh | kz-KZ | `kzKZ` | +| Kazakh | kk-KZ | `kkKZ` | | Korean | ko-KR | `koKR` | | Macedonian | mk-MK | `mkMK` | | Norwegian (bokmål) | nb-NO | `nbNO` | diff --git a/packages/mui-material/src/locale/index.ts b/packages/mui-material/src/locale/index.ts index 5a1896e06f8192..faa60779229b6e 100644 --- a/packages/mui-material/src/locale/index.ts +++ b/packages/mui-material/src/locale/index.ts @@ -2110,7 +2110,7 @@ export const koKR: Localization = { }, }; -export const kzKZ: Localization = { +export const kkKZ: Localization = { components: { MuiBreadcrumbs: { defaultProps: { From f063900f456c9875be059eed86b71330e02a8db2 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Tue, 13 Dec 2022 17:07:32 +0100 Subject: [PATCH 04/88] [Material You] Add motion design tokens (#35384) --- .../mui-material-next/src/Button/Button.tsx | 5 +- .../src/styles/Theme.types.ts | 54 ++++++ .../src/styles/extendTheme.ts | 20 ++- .../src/styles/motion.test.js | 156 ++++++++++++++++++ .../mui-material-next/src/styles/motion.ts | 118 +++++++++++++ 5 files changed, 344 insertions(+), 9 deletions(-) create mode 100644 packages/mui-material-next/src/styles/motion.test.js create mode 100644 packages/mui-material-next/src/styles/motion.ts diff --git a/packages/mui-material-next/src/Button/Button.tsx b/packages/mui-material-next/src/Button/Button.tsx index bc6394f4e9e7fe..938c708524f6c8 100644 --- a/packages/mui-material-next/src/Button/Button.tsx +++ b/packages/mui-material-next/src/Button/Button.tsx @@ -306,11 +306,10 @@ export const ButtonRoot = styled('button', { padding: '10px 24px', minWidth: 64, letterSpacing, - // Taken from MD2, haven't really found a spec on transitions - transition: theme.transitions.create( + transition: theme.sys.motion.create( ['background-color', 'box-shadow', 'border-color', 'color'], { - duration: theme.transitions.duration.short, + duration: tokens.sys.motion.duration.short3, }, ), fontFamily: tokens.sys.typescale.label.large.family, diff --git a/packages/mui-material-next/src/styles/Theme.types.ts b/packages/mui-material-next/src/styles/Theme.types.ts index 3c0a16c371edb9..8b16651acd7904 100644 --- a/packages/mui-material-next/src/styles/Theme.types.ts +++ b/packages/mui-material-next/src/styles/Theme.types.ts @@ -149,6 +149,57 @@ export interface MD3ShapeOptions { corner?: Partial; } +export interface MD3Easing { + linear: string; + standard: string; + standardAccelerate: string; + standardDecelerate: string; + emphasized: string; + emphasizedDecelerate: string; + emphasizedAccelerate: string; + legacy: string; + legacyDecelerate: string; + legacyAccelerate: string; +} +export interface MD3Duration { + short1: string; + short2: string; + short3: string; + short4: string; + medium1: string; + medium2: string; + medium3: string; + medium4: string; + long1: string; + long2: string; + long3: string; + long4: string; + extraLong1: string; + extraLong2: string; + extraLong3: string; + extraLong4: string; +} + +export interface MotionOptions { + easing?: Partial; + duration?: Partial; + create?: ( + props: string | string[], + options?: Partial<{ duration: number | string; easing: string; delay: number | string }>, + ) => string; + getAutoHeightDuration?: (height: number) => number; +} + +export interface Motion { + easing: MD3Easing; + duration: MD3Duration; + create: ( + props: string | string[], + options?: Partial<{ duration: number | string; easing: string; delay: number | string }>, + ) => string; + getAutoHeightDuration: (height: number) => number; +} + export interface MD3CssVarsThemeOptions extends Omit { ref?: { typeface?: Partial; @@ -157,6 +208,7 @@ export interface MD3CssVarsThemeOptions extends Omit; state?: Partial; elevation?: string[]; + motion?: MotionOptions; shape?: MD3ShapeOptions; }; } @@ -189,6 +241,7 @@ export interface Theme extends Omit { typescale: MD3Typescale; state: MD3State; elevation: string[]; + motion: Motion; shape: MD3Shape; }; palette: MD2Theme['palette']; @@ -203,6 +256,7 @@ export interface Theme extends Omit { typescale: MD3Typescale; state: MD3State; elevation: string[]; + motion: Omit; shape: MD3Shape; }; }; diff --git a/packages/mui-material-next/src/styles/extendTheme.ts b/packages/mui-material-next/src/styles/extendTheme.ts index 460c461c1c084f..0b58148f721dd0 100644 --- a/packages/mui-material-next/src/styles/extendTheme.ts +++ b/packages/mui-material-next/src/styles/extendTheme.ts @@ -25,6 +25,7 @@ import md3Typescale from './typescale'; import md3Typeface from './typeface'; import md3State from './state'; import { elevationLight, elevationDark } from './elevation'; +import createMotions from './motion'; import md3shape from './shape'; const defaultLightOverlays: Overlays = [...Array(25)].map(() => undefined) as Overlays; @@ -62,6 +63,11 @@ export default function extendTheme(options: CssVarsThemeOptions = {}, ...args: corner: { ...input.sys?.shape?.corner, ...md3shape.corner }, }; + const motion = createMotions(input.sys?.motion); + const typescale = { ...md3Typescale, ...input.sys?.typescale }; + const typeface = { ...md3Typeface, ...input.ref?.typeface }; + const state = { ...md3State, ...input.sys?.state }; + const { palette: lightPalette, // @ts-ignore - sys is md3 specific token @@ -76,13 +82,14 @@ export default function extendTheme(options: CssVarsThemeOptions = {}, ...args: useMaterialYou: true, ref: { ...input.ref, - typeface: { ...md3Typeface, ...input.ref?.typeface }, + typeface, palette: deepmerge(md3CommonPalette, colorSchemesInput.light?.ref?.palette), }, sys: { ...input.sys, - typescale: { ...md3Typescale, ...input.sys?.typescale }, - state: { ...md3State, ...input.sys?.state }, + typescale, + state, + motion, color: { ...md3LightColors, ...colorSchemesInput.light?.sys?.color }, elevation: colorSchemesInput.light?.sys?.elevation ?? elevationLight, shape, @@ -106,13 +113,14 @@ export default function extendTheme(options: CssVarsThemeOptions = {}, ...args: // @ts-ignore - it's fine, everything that is not supported will be spread ref: { ...input.ref, - typeface: { ...md3Typeface, ...input.ref?.typeface }, + typeface, palette: deepmerge(md3CommonPalette, colorSchemesInput.dark?.ref?.palette), }, sys: { ...input.sys, - typescale: { ...md3Typescale, ...input.sys?.typescale }, - state: { ...md3State, ...input.sys?.state }, + typescale, + state, + motion, color: { ...md3DarkColors, ...colorSchemesInput.dark?.sys?.color }, elevation: colorSchemesInput.dark?.sys?.elevation ?? elevationDark, shape, diff --git a/packages/mui-material-next/src/styles/motion.test.js b/packages/mui-material-next/src/styles/motion.test.js new file mode 100644 index 00000000000000..d62934285bc8d2 --- /dev/null +++ b/packages/mui-material-next/src/styles/motion.test.js @@ -0,0 +1,156 @@ +import { expect } from 'chai'; +import { extendTheme } from '@mui/material-next/styles'; +import createMotion, { easing, duration } from './motion'; + +describe('motion', () => { + const motion = createMotion({}); + const create = motion.create; + const getAutoHeightDuration = motion.getAutoHeightDuration; + + it('should allow to customize the default duration', () => { + const theme = extendTheme({ + sys: { + motion: { + duration: { + medium1: '310ms', + }, + }, + }, + }); + expect(theme.sys.motion.create('color')).to.equal(`color 310ms ${easing.standard} 0ms`); + }); + + describe('create() function', () => { + describe('warnings', () => { + it('should warn when first argument is of bad type', () => { + expect(() => create(5554)).toErrorDev('MUI: Argument "props" must be a string or Array'); + expect(() => create({})).toErrorDev('MUI: Argument "props" must be a string or Array'); + }); + + it('should warn when bad "duration" option type', () => { + expect(() => create('font', { duration: null })).toErrorDev( + 'MUI: Argument "duration" must be a number or a string but found null', + ); + expect(() => create('font', { duration: {} })).toErrorDev( + 'MUI: Argument "duration" must be a number or a string but found [object Object]', + ); + }); + + it('should warn when bad "easing" option type', () => { + expect(() => create('transform', { easing: 123 })).toErrorDev( + 'MUI: Argument "easing" must be a string', + ); + expect(() => create('transform', { easing: {} })).toErrorDev( + 'MUI: Argument "easing" must be a string', + ); + }); + + it('should warn when bad "delay" option type', () => { + expect(() => create('size', { delay: null })).toErrorDev( + 'MUI: Argument "delay" must be a number or a string', + ); + expect(() => create('size', { delay: {} })).toErrorDev( + 'MUI: Argument "delay" must be a number or a string', + ); + }); + + it('should warn when passed unrecognized option', () => { + expect(() => create('size', { fffds: 'value' })).toErrorDev( + 'MUI: Unrecognized argument(s) [fffds]', + ); + }); + }); + + it('should create default transition without arguments', () => { + const transition = create(); + expect(transition).to.equal(`all ${duration.medium1} ${easing.standard} 0ms`); + }); + + it('should take string props as a first argument', () => { + const transition = create('color'); + expect(transition).to.equal(`color ${duration.medium1} ${easing.standard} 0ms`); + }); + + it('should also take array of props as first argument', () => { + const options = { delay: 20 }; + const multiple = create(['color', 'size'], options); + const single1 = create('color', options); + const single2 = create('size', options); + const expected = `${single1},${single2}`; + expect(multiple).to.equal(expected); + }); + + it('should optionally accept number "duration" option in second argument', () => { + const transition = create('font', { duration: 500 }); + expect(transition).to.equal(`font 500ms ${easing.standard} 0ms`); + }); + + it('should optionally accept string "duration" option in second argument', () => { + const transition = create('font', { duration: '500ms' }); + expect(transition).to.equal(`font 500ms ${easing.standard} 0ms`); + }); + + it('should round decimal digits of "duration" prop to whole numbers', () => { + const transition = create('font', { duration: 12.125 }); + expect(transition).to.equal(`font 12ms ${easing.standard} 0ms`); + }); + + it('should optionally accept string "easing" option in second argument', () => { + const transition = create('transform', { easing: easing.linear }); + expect(transition).to.equal(`transform ${duration.medium1} ${easing.linear} 0ms`); + }); + + it('should optionally accept number "delay" option in second argument', () => { + const transition = create('size', { delay: 150 }); + expect(transition).to.equal(`size ${duration.medium1} ${easing.standard} 150ms`); + }); + + it('should optionally accept string "delay" option in second argument', () => { + const transition = create('size', { delay: '150ms' }); + expect(transition).to.equal(`size ${duration.medium1} ${easing.standard} 150ms`); + }); + + it('should round decimal digits of "delay" prop to whole numbers', () => { + const transition = create('size', { delay: 1.547 }); + expect(transition).to.equal(`size ${duration.medium1} ${easing.standard} 2ms`); + }); + + it('should return zero when not passed arguments', () => { + const zeroHeightDuration = getAutoHeightDuration(); + expect(zeroHeightDuration).to.equal(0); + }); + + it('should return zero when passed undefined', () => { + const zeroHeightDuration = getAutoHeightDuration(undefined); + expect(zeroHeightDuration).to.equal(0); + }); + + it('should return zero when passed null', () => { + const zeroHeightDuration = getAutoHeightDuration(null); + expect(zeroHeightDuration).to.equal(0); + }); + + it('should return NaN when passed a negative number', () => { + const zeroHeightDurationNegativeOne = getAutoHeightDuration(-1); + // eslint-disable-next-line no-restricted-globals + expect(isNaN(zeroHeightDurationNegativeOne)).to.equal(true); + const zeroHeightDurationSmallNegative = getAutoHeightDuration(-0.000001); + // eslint-disable-next-line no-restricted-globals + expect(isNaN(zeroHeightDurationSmallNegative)).to.equal(true); + const zeroHeightDurationBigNegative = getAutoHeightDuration(-100000); + // eslint-disable-next-line no-restricted-globals + expect(isNaN(zeroHeightDurationBigNegative)).to.equal(true); + }); + + it('should return values for pre-calculated positive examples', () => { + let zeroHeightDuration = getAutoHeightDuration(14); + expect(zeroHeightDuration).to.equal(159); + zeroHeightDuration = getAutoHeightDuration(100); + expect(zeroHeightDuration).to.equal(239); + zeroHeightDuration = getAutoHeightDuration(0.0001); + expect(zeroHeightDuration).to.equal(46); + zeroHeightDuration = getAutoHeightDuration(100000); + expect(zeroHeightDuration).to.equal(6685); + }); + }); +}); diff --git a/packages/mui-material-next/src/styles/motion.ts b/packages/mui-material-next/src/styles/motion.ts new file mode 100644 index 00000000000000..8e8f0a09855f58 --- /dev/null +++ b/packages/mui-material-next/src/styles/motion.ts @@ -0,0 +1,118 @@ +import { MD3Duration, MD3Easing, MotionOptions } from './Theme.types'; + +// Follows https://m3.material.io/styles/motion/easing-and-duration/tokens-specs +export const duration: MD3Duration = { + short1: '50ms', + short2: '100ms', + short3: '150ms', + short4: '200ms', + medium1: '250ms', + medium2: '300ms', + medium3: '350ms', + medium4: '400ms', + long1: '450ms', + long2: '500ms', + long3: '550ms', + long4: '600ms', + extraLong1: '700ms', + extraLong2: '800ms', + extraLong3: '900ms', + extraLong4: '1000ms', +}; + +export const easing: MD3Easing = { + linear: 'cubic-bezier(0, 0, 1, 1)', + standard: 'cubic-bezier(0.2, 0, 0, 1)', + standardAccelerate: 'cubic-bezier(0.3, 0, 1, 1)', + standardDecelerate: 'cubic-bezier(0, 0, 0, 1)', + emphasized: 'cubic-bezier(0.2, 0, 0, 1)', + emphasizedDecelerate: 'cubic-bezier(0.05, 0.7, 0.1, 1)', + emphasizedAccelerate: 'cubic-bezier(0.3, 0, 0.8, 0.15)', + legacy: 'cubic-bezier(0.4, 0, 0.2, 1)', + legacyDecelerate: 'cubic-bezier(0.0, 0, 0.2, 1)', + legacyAccelerate: 'cubic-bezier(0.4, 0, 1.0, 1)', +}; + +function formatMs(milliseconds: number) { + return `${Math.round(milliseconds)}ms`; +} + +function getAutoHeightDuration(height: number) { + if (!height) { + return 0; + } + + const constant = height / 36; + + // https://www.wolframalpha.com/input/?i=(4+%2B+15+*+(x+%2F+36+)+**+0.25+%2B+(x+%2F+36)+%2F+5)+*+10 + return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10); +} + +export default function createMotions(inputMotion: MotionOptions = {}) { + const mergedEasing = { + ...easing, + ...inputMotion.easing, + }; + + const mergedDuration = { + ...duration, + ...inputMotion.duration, + }; + + const create = ( + props = ['all'], + options: { duration?: number | string; easing?: string | string; delay?: number | string } = {}, + ) => { + const { + duration: durationOption = mergedDuration.medium1, + easing: easingOption = mergedEasing.standard, + delay = 0, + ...other + } = options; + + if (process.env.NODE_ENV !== 'production') { + const isString = (value: any) => typeof value === 'string'; + // IE11 support, replace with Number.isNaN + // eslint-disable-next-line no-restricted-globals + const isNumber = (value: any) => !isNaN(parseFloat(value)); + if (!isString(props) && !Array.isArray(props)) { + console.error('MUI: Argument "props" must be a string or Array.'); + } + + if (!isNumber(durationOption) && !isString(durationOption)) { + console.error( + `MUI: Argument "duration" must be a number or a string but found ${durationOption}.`, + ); + } + + if (!isString(easingOption)) { + console.error('MUI: Argument "easing" must be a string.'); + } + + if (!isNumber(delay) && !isString(delay)) { + console.error('MUI: Argument "delay" must be a number or a string.'); + } + + if (Object.keys(other).length !== 0) { + console.error(`MUI: Unrecognized argument(s) [${Object.keys(other).join(',')}].`); + } + } + + return (Array.isArray(props) ? props : [props]) + .map( + (animatedProp) => + `${animatedProp} ${ + typeof durationOption === 'string' ? durationOption : formatMs(durationOption) + } ${easingOption} ${typeof delay === 'string' ? delay : formatMs(delay)}`, + ) + .join(','); + }; + + return { + getAutoHeightDuration, + create, + ...inputMotion, + easing: mergedEasing, + duration: mergedDuration, + }; +} From d8078d98cac429ccc015e3a4c9087e309e220684 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 13 Dec 2022 15:30:04 -0300 Subject: [PATCH 05/88] [website] Update product icons (#35413) --- .../static/branding/product-advanced-dark.svg | 26 ++++++++++++++++++- .../branding/product-advanced-light.svg | 2 +- .../static/branding/product-core-dark.svg | 2 +- .../static/branding/product-core-light.svg | 2 +- .../branding/product-designkits-dark.svg | 2 +- .../branding/product-designkits-light.svg | 2 +- .../branding/product-templates-dark.svg | 2 +- .../branding/product-templates-light.svg | 2 +- .../static/branding/product-toolpad-dark.svg | 2 +- .../static/branding/product-toolpad-light.svg | 2 +- 10 files changed, 34 insertions(+), 10 deletions(-) diff --git a/docs/public/static/branding/product-advanced-dark.svg b/docs/public/static/branding/product-advanced-dark.svg index 05687f16d60435..0518c815d329c0 100644 --- a/docs/public/static/branding/product-advanced-dark.svg +++ b/docs/public/static/branding/product-advanced-dark.svg @@ -1 +1,25 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/static/branding/product-advanced-light.svg b/docs/public/static/branding/product-advanced-light.svg index 73f83f9328268b..7aa9b4b3f0d511 100644 --- a/docs/public/static/branding/product-advanced-light.svg +++ b/docs/public/static/branding/product-advanced-light.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/public/static/branding/product-core-dark.svg b/docs/public/static/branding/product-core-dark.svg index 1a3498917f4a91..1ef2969d795e0d 100644 --- a/docs/public/static/branding/product-core-dark.svg +++ b/docs/public/static/branding/product-core-dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/public/static/branding/product-core-light.svg b/docs/public/static/branding/product-core-light.svg index 7315993c50b8bd..4b8fdfabe90456 100644 --- a/docs/public/static/branding/product-core-light.svg +++ b/docs/public/static/branding/product-core-light.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/public/static/branding/product-designkits-dark.svg b/docs/public/static/branding/product-designkits-dark.svg index 91c151b8223301..34a5d628f225b2 100644 --- a/docs/public/static/branding/product-designkits-dark.svg +++ b/docs/public/static/branding/product-designkits-dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/public/static/branding/product-designkits-light.svg b/docs/public/static/branding/product-designkits-light.svg index 2dcda68d5e8e09..2bdb5b4e1ad175 100644 --- a/docs/public/static/branding/product-designkits-light.svg +++ b/docs/public/static/branding/product-designkits-light.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/public/static/branding/product-templates-dark.svg b/docs/public/static/branding/product-templates-dark.svg index d90b2bd3b4abbd..42622104ac6f32 100644 --- a/docs/public/static/branding/product-templates-dark.svg +++ b/docs/public/static/branding/product-templates-dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/public/static/branding/product-templates-light.svg b/docs/public/static/branding/product-templates-light.svg index 6d16c8ed32b15e..cd61aa49a80b10 100644 --- a/docs/public/static/branding/product-templates-light.svg +++ b/docs/public/static/branding/product-templates-light.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/public/static/branding/product-toolpad-dark.svg b/docs/public/static/branding/product-toolpad-dark.svg index 1d7f7abd44c9f9..f30e2fb4285a92 100644 --- a/docs/public/static/branding/product-toolpad-dark.svg +++ b/docs/public/static/branding/product-toolpad-dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/public/static/branding/product-toolpad-light.svg b/docs/public/static/branding/product-toolpad-light.svg index e2fa92f09f1200..eb0d806c41129f 100644 --- a/docs/public/static/branding/product-toolpad-light.svg +++ b/docs/public/static/branding/product-toolpad-light.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 362229dcbcf1e35ed22f69526e13ab791050b41c Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Wed, 14 Dec 2022 17:57:44 +0700 Subject: [PATCH 06/88] [Tooltip][Joy] Fix arrow does not appear (#35473) --- docs/data/joy/components/tooltip/ArrowTooltips.js | 9 ++++++--- docs/data/joy/components/tooltip/ArrowTooltips.tsx | 9 ++++++--- .../joy/components/tooltip/ArrowTooltips.tsx.preview | 8 +++++--- packages/mui-joy/src/Tooltip/Tooltip.tsx | 4 +--- packages/mui-joy/src/utils/useSlot.ts | 10 ++++------ 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/docs/data/joy/components/tooltip/ArrowTooltips.js b/docs/data/joy/components/tooltip/ArrowTooltips.js index 9adbf036cfe59b..3699ca7d80b14d 100644 --- a/docs/data/joy/components/tooltip/ArrowTooltips.js +++ b/docs/data/joy/components/tooltip/ArrowTooltips.js @@ -1,11 +1,14 @@ import * as React from 'react'; import Button from '@mui/joy/Button'; +import Sheet from '@mui/joy/Sheet'; import Tooltip from '@mui/joy/Tooltip'; export default function ArrowTooltips() { return ( - - - + + + + + ); } diff --git a/docs/data/joy/components/tooltip/ArrowTooltips.tsx b/docs/data/joy/components/tooltip/ArrowTooltips.tsx index 9adbf036cfe59b..3699ca7d80b14d 100644 --- a/docs/data/joy/components/tooltip/ArrowTooltips.tsx +++ b/docs/data/joy/components/tooltip/ArrowTooltips.tsx @@ -1,11 +1,14 @@ import * as React from 'react'; import Button from '@mui/joy/Button'; +import Sheet from '@mui/joy/Sheet'; import Tooltip from '@mui/joy/Tooltip'; export default function ArrowTooltips() { return ( - - - + + + + + ); } diff --git a/docs/data/joy/components/tooltip/ArrowTooltips.tsx.preview b/docs/data/joy/components/tooltip/ArrowTooltips.tsx.preview index 609cf9ae271afc..8b6d7d124e1790 100644 --- a/docs/data/joy/components/tooltip/ArrowTooltips.tsx.preview +++ b/docs/data/joy/components/tooltip/ArrowTooltips.tsx.preview @@ -1,3 +1,5 @@ - - - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/packages/mui-joy/src/Tooltip/Tooltip.tsx b/packages/mui-joy/src/Tooltip/Tooltip.tsx index 3bba6eaad4ae8d..ed56ebaa61d076 100644 --- a/packages/mui-joy/src/Tooltip/Tooltip.tsx +++ b/packages/mui-joy/src/Tooltip/Tooltip.tsx @@ -588,9 +588,7 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { }); const [SlotArrow, arrowProps] = useSlot('arrow', { - additionalProps: { - ref: setArrowRef, - }, + ref: setArrowRef, className: classes.arrow, elementType: TooltipArrow, externalForwardedProps: other, diff --git a/packages/mui-joy/src/utils/useSlot.ts b/packages/mui-joy/src/utils/useSlot.ts index 915668fabef754..f3e6400aefe8e6 100644 --- a/packages/mui-joy/src/utils/useSlot.ts +++ b/packages/mui-joy/src/utils/useSlot.ts @@ -52,7 +52,9 @@ export default function useSlot< * e.g. the `externalForwardedProps` are spread to `root` slot but not other slots. */ name: T, - parameters: (T extends 'root' ? { ref: React.ForwardedRef } : {}) & { + parameters: (T extends 'root' // root slot must pass a `ref` as a parameter + ? { ref: React.ForwardedRef } + : { ref?: React.ForwardedRef }) & { /** * The slot's className */ @@ -129,11 +131,7 @@ export default function useSlot< externalSlotProps: resolvedComponentsProps, }); - const ref = useForkRef( - internalRef, - // @ts-ignore `ref` is required for the 'root' slot - useForkRef(resolvedComponentsProps?.ref, name === 'root' ? parameters.ref : undefined), - ) as ((instance: any | null) => void) | null; + const ref = useForkRef(internalRef, resolvedComponentsProps?.ref, parameters.ref); const finalOwnerState = getSlotOwnerState ? { ...ownerState, ...getSlotOwnerState(mergedProps as any) } From 15998ea1fa601d788f0ba5e2bec032bd69187daa Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Wed, 14 Dec 2022 23:28:00 +0100 Subject: [PATCH 07/88] [test] Fix broken master branch (#35446) Co-authored-by: Marija Najdova --- packages/mui-joy/src/Box/Box.test.js | 26 +----- .../mui-joy/src/styles/extendTheme.test.js | 83 ++++++++++++++++++- 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/packages/mui-joy/src/Box/Box.test.js b/packages/mui-joy/src/Box/Box.test.js index b99f66c0393e17..94868c0cb761bd 100644 --- a/packages/mui-joy/src/Box/Box.test.js +++ b/packages/mui-joy/src/Box/Box.test.js @@ -23,13 +23,7 @@ describe('Joy ', () => { refInstanceof: window.HTMLDivElement, })); - it('respects theme from context', function test() { - const isJSDOM = /jsdom/.test(window.navigator.userAgent); - - if (isJSDOM) { - this.skip(); - } - + it('respects theme from context', () => { const { container } = render( ', () => { }); }); - it('letterSpacing', function test() { - const isJSDOM = /jsdom/.test(window.navigator.userAgent); - - if (isJSDOM) { - this.skip(); - } - - const { container } = render( - - - , - ); - - expect(container.firstChild).toHaveComputedStyle({ - letterSpacing: '1.328px', - }); - }); - it('lineHeight', function test() { const isJSDOM = /jsdom/.test(window.navigator.userAgent); diff --git a/packages/mui-joy/src/styles/extendTheme.test.js b/packages/mui-joy/src/styles/extendTheme.test.js index cf39c5667968c9..e72cf9f195005e 100644 --- a/packages/mui-joy/src/styles/extendTheme.test.js +++ b/packages/mui-joy/src/styles/extendTheme.test.js @@ -1,5 +1,7 @@ +import * as React from 'react'; import { expect } from 'chai'; -import extendTheme from './extendTheme'; +import { createRenderer } from 'test/utils'; +import { extendTheme, useTheme, CssVarsProvider } from '@mui/joy/styles'; describe('extendTheme', () => { it('the output contains required fields', () => { @@ -46,4 +48,83 @@ describe('extendTheme', () => { expect(theme.cssVarPrefix).to.equal(''); expect(theme.typography.body1.fontSize).to.equal('var(--fontSize-md)'); }); + + describe('theme.unstable_sx', () => { + const { render } = createRenderer(); + + let originalMatchmedia; + const storage = {}; + beforeEach(() => { + originalMatchmedia = window.matchMedia; + // Create mocks of localStorage getItem and setItem functions + Object.defineProperty(global, 'localStorage', { + value: { + getItem: (key) => storage[key], + setItem: (key, value) => { + storage[key] = value; + }, + }, + configurable: true, + }); + window.matchMedia = () => ({ + addListener: () => {}, + removeListener: () => {}, + }); + }); + afterEach(() => { + window.matchMedia = originalMatchmedia; + }); + + const customTheme = extendTheme({ + colorSchemes: { + light: { + palette: { + primary: { + 500: 'rgb(0, 0, 255)', + }, + }, + }, + }, + }); + + it('bgcolor', () => { + let styles = {}; + + function Test() { + const theme = useTheme(); + styles = theme.unstable_sx({ bgcolor: 'primary.500' }); + return null; + } + + render( + + + , + ); + + expect(styles).to.deep.equal({ + backgroundColor: 'var(--joy-palette-primary-500)', + }); + }); + + it('borderRadius', () => { + let styles = {}; + + function Test() { + const theme = useTheme(); + styles = theme.unstable_sx({ borderRadius: 'md' }); + return null; + } + + render( + + + , + ); + + expect(styles).to.deep.equal({ + borderRadius: 'var(--joy-radius-md)', + }); + }); + }); }); From a9f5db9ba6db110f3b7a93e7c09236eeada6f108 Mon Sep 17 00:00:00 2001 From: Myagmarsuren <48021917+Miigaarino@users.noreply.github.com> Date: Thu, 15 Dec 2022 14:44:56 +0800 Subject: [PATCH 08/88] [Joy][docs] List component introduction example default code is missing ListItemContent component (#35492) --- docs/data/joy/components/list/ListUsage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/joy/components/list/ListUsage.js b/docs/data/joy/components/list/ListUsage.js index 3450c79fbb9b41..2f7197d697aae0 100644 --- a/docs/data/joy/components/list/ListUsage.js +++ b/docs/data/joy/components/list/ListUsage.js @@ -39,7 +39,7 @@ export default function ListUsage() { { propName: 'children', defaultValue: ` - Home + Home `, }, ]} From 2b72ec9413614f8369f117320257f928f5f24fc7 Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Thu, 15 Dec 2022 13:45:46 +0700 Subject: [PATCH 09/88] [docs] Fix `unstable_sxConfig` typo (#35478) --- .../configure-the-sx-prop/configure-the-sx-prop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/system/experimental-api/configure-the-sx-prop/configure-the-sx-prop.md b/docs/data/system/experimental-api/configure-the-sx-prop/configure-the-sx-prop.md index 1e80481d137ec6..ee3c7bb99ed91f 100644 --- a/docs/data/system/experimental-api/configure-the-sx-prop/configure-the-sx-prop.md +++ b/docs/data/system/experimental-api/configure-the-sx-prop/configure-the-sx-prop.md @@ -18,7 +18,7 @@ You can change this behavior by providing a custom config for the `borderRadius` ## API -Each key in the `unstable_sx` config can define the following properties: +Each value of the config inside `unstable_sxConfig` accepts the following properties: - `cssProperty` (_string_ [optional]): Indicates the CSS property, if it is different than the key - `themeKey` (_string_ [optional]): The path of the theme mapping From 96080b29d91f6caa1c0a196cfa8bd61902ce6bc2 Mon Sep 17 00:00:00 2001 From: Lucas Meulengracht Fredmark Date: Thu, 15 Dec 2022 08:48:01 +0100 Subject: [PATCH 10/88] [docs] Add `CardMedia` example without `component="img"` prop (#35470) --- docs/data/material/components/cards/MediaCard.js | 5 ++--- docs/data/material/components/cards/MediaCard.tsx | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/data/material/components/cards/MediaCard.js b/docs/data/material/components/cards/MediaCard.js index 8b168f776c3180..e4df1a9876f3c1 100644 --- a/docs/data/material/components/cards/MediaCard.js +++ b/docs/data/material/components/cards/MediaCard.js @@ -10,10 +10,9 @@ export default function MediaCard() { return ( diff --git a/docs/data/material/components/cards/MediaCard.tsx b/docs/data/material/components/cards/MediaCard.tsx index 8b168f776c3180..e4df1a9876f3c1 100644 --- a/docs/data/material/components/cards/MediaCard.tsx +++ b/docs/data/material/components/cards/MediaCard.tsx @@ -10,10 +10,9 @@ export default function MediaCard() { return ( From 9e3d1db539bb1ff6d4bb405deffb35220f522bed Mon Sep 17 00:00:00 2001 From: Mibiki <108873902+MickaelAustoni@users.noreply.github.com> Date: Thu, 15 Dec 2022 11:10:55 +0200 Subject: [PATCH 11/88] [InputLabel] Enable `size` prop overrides via TypeScript module augmentation (#35460) --- docs/pages/material-ui/api/input-label.json | 5 ++++- .../mui-material/src/InputLabel/InputLabel.d.ts | 5 ++++- packages/mui-material/src/InputLabel/InputLabel.js | 5 ++++- .../InputLabelCustomProps.spec.tsx | 13 +++++++++++++ .../InputLabelCustomProps.tsconfig.json | 4 ++++ 5 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 packages/mui-material/test/typescript/moduleAugmentation/InputLabelCustomProps.spec.tsx create mode 100644 packages/mui-material/test/typescript/moduleAugmentation/InputLabelCustomProps.tsconfig.json diff --git a/docs/pages/material-ui/api/input-label.json b/docs/pages/material-ui/api/input-label.json index 65861580971e2f..716c178dcf478a 100644 --- a/docs/pages/material-ui/api/input-label.json +++ b/docs/pages/material-ui/api/input-label.json @@ -16,7 +16,10 @@ "required": { "type": { "name": "bool" } }, "shrink": { "type": { "name": "bool" } }, "size": { - "type": { "name": "enum", "description": "'normal'
| 'small'" }, + "type": { + "name": "union", + "description": "'normal'
| 'small'
| string" + }, "default": "'normal'" }, "sx": { diff --git a/packages/mui-material/src/InputLabel/InputLabel.d.ts b/packages/mui-material/src/InputLabel/InputLabel.d.ts index 285ad1f64cb952..e15cdc45fe8972 100644 --- a/packages/mui-material/src/InputLabel/InputLabel.d.ts +++ b/packages/mui-material/src/InputLabel/InputLabel.d.ts @@ -1,10 +1,13 @@ import * as React from 'react'; import { SxProps } from '@mui/system'; +import { OverridableStringUnion } from '@mui/types'; import { InternalStandardProps as StandardProps } from '..'; import { FormLabelProps } from '../FormLabel'; import { Theme } from '../styles'; import { InputLabelClasses } from './inputLabelClasses'; +export interface InputLabelPropsSizeOverrides {} + export interface InputLabelProps extends StandardProps { /** * The content of the component. @@ -49,7 +52,7 @@ export interface InputLabelProps extends StandardProps { * The size of the component. * @default 'normal' */ - size?: 'small' | 'normal'; + size?: OverridableStringUnion<'small' | 'normal', InputLabelPropsSizeOverrides>; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-material/src/InputLabel/InputLabel.js b/packages/mui-material/src/InputLabel/InputLabel.js index a6af7ad1b3c423..5318af7bd25fc2 100644 --- a/packages/mui-material/src/InputLabel/InputLabel.js +++ b/packages/mui-material/src/InputLabel/InputLabel.js @@ -224,7 +224,10 @@ InputLabel.propTypes /* remove-proptypes */ = { * The size of the component. * @default 'normal' */ - size: PropTypes.oneOf(['normal', 'small']), + 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. */ diff --git a/packages/mui-material/test/typescript/moduleAugmentation/InputLabelCustomProps.spec.tsx b/packages/mui-material/test/typescript/moduleAugmentation/InputLabelCustomProps.spec.tsx new file mode 100644 index 00000000000000..42d25f1922ff95 --- /dev/null +++ b/packages/mui-material/test/typescript/moduleAugmentation/InputLabelCustomProps.spec.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import InputLabel from '@mui/material/InputLabel'; + +declare module '@mui/material/InputLabel' { + interface InputLabelPropsSizeOverrides { + customSize: true; + } +} + +; + +// @ts-expect-error unknown size +; diff --git a/packages/mui-material/test/typescript/moduleAugmentation/InputLabelCustomProps.tsconfig.json b/packages/mui-material/test/typescript/moduleAugmentation/InputLabelCustomProps.tsconfig.json new file mode 100644 index 00000000000000..f53a33cae0908f --- /dev/null +++ b/packages/mui-material/test/typescript/moduleAugmentation/InputLabelCustomProps.tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../../../tsconfig", + "files": ["InputLabelCustomProps.spec.tsx"] +} From 9c472c1e06b63b67ee4ce31eb96e5ca14c6af2a4 Mon Sep 17 00:00:00 2001 From: DimaAbr <13933191+DimaAbr@users.noreply.github.com> Date: Fri, 16 Dec 2022 14:22:59 +0200 Subject: [PATCH 12/88] [Chip] Fix hover and focus style with CSS Variables (#35502) Signed-off-by: DimaAbr <13933191+DimaAbr@users.noreply.github.com> --- packages/mui-material/src/Chip/Chip.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/mui-material/src/Chip/Chip.js b/packages/mui-material/src/Chip/Chip.js index 87925389a8fc51..36d8b42087a9bf 100644 --- a/packages/mui-material/src/Chip/Chip.js +++ b/packages/mui-material/src/Chip/Chip.js @@ -180,9 +180,7 @@ const ChipRoot = styled('div', { ...(ownerState.onDelete && { [`&.${chipClasses.focusVisible}`]: { backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.action.selectedChannel} / calc(${ - theme.vars.palette.action.selectedOpacity + theme.vars.palette.action.focusOpacity - }))` + ? `rgba(${theme.vars.palette.action.selectedChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` : alpha( theme.palette.action.selected, theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity, @@ -204,9 +202,7 @@ const ChipRoot = styled('div', { cursor: 'pointer', '&:hover': { backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.action.selectedChannel} / calc(${ - theme.vars.palette.action.selectedOpacity + theme.vars.palette.action.hoverOpacity - }))` + ? `rgba(${theme.vars.palette.action.selectedChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.hoverOpacity}))` : alpha( theme.palette.action.selected, theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity, @@ -214,9 +210,7 @@ const ChipRoot = styled('div', { }, [`&.${chipClasses.focusVisible}`]: { backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.action.selectedChannel} / calc(${ - theme.vars.palette.action.selectedOpacity + theme.vars.palette.action.focusOpacity - }))` + ? `rgba(${theme.vars.palette.action.selectedChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` : alpha( theme.palette.action.selected, theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity, From e0dafd262d2ab3b9ef2d24672353229dbe5ff82d Mon Sep 17 00:00:00 2001 From: Cassidy Williams <1454517+cassidoo@users.noreply.github.com> Date: Fri, 16 Dec 2022 23:42:45 -0600 Subject: [PATCH 13/88] [docs] Add missing comma to `Providing the colors directly` section (#35507) Signed-off-by: Cassidy Williams <1454517+cassidoo@users.noreply.github.com> Co-authored-by: ZeeshanTamboli --- docs/data/material/customization/palette/palette.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/data/material/customization/palette/palette.md b/docs/data/material/customization/palette/palette.md index b2f62b4e9af3fd..1934902aa21c0e 100644 --- a/docs/data/material/customization/palette/palette.md +++ b/docs/data/material/customization/palette/palette.md @@ -87,16 +87,16 @@ const theme = createTheme({ // dark: will be calculated from palette.secondary.main, contrastText: '#ffcc00', }, - // Provide every color token (light, main, dark, and contrastText) when using - // custom colors for props in Material UI's components. - // Then you will be able to use it like this: `
, @@ -368,7 +375,7 @@ describe('', () => { }); it('should restore the focus', () => { - function Test(props) { + function Test(props: GenericProps) { return (
diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.js b/packages/mui-base/src/FocusTrap/FocusTrap.tsx similarity index 79% rename from packages/mui-base/src/FocusTrap/FocusTrap.js rename to packages/mui-base/src/FocusTrap/FocusTrap.tsx index c03dd7cc57fda9..962387189a2ea3 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.js +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -7,6 +7,7 @@ import { unstable_useForkRef as useForkRef, unstable_ownerDocument as ownerDocument, } from '@mui/utils'; +import { FocusTrapProps } from './FocusTrap.types'; // Inspired by https://github.com/focus-trap/tabbable const candidatesSelector = [ @@ -21,8 +22,14 @@ const candidatesSelector = [ '[contenteditable]:not([contenteditable="false"])', ].join(','); -function getTabIndex(node) { - const tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); +interface OrderedTabNode { + documentOrder: number; + tabIndex: number; + node: HTMLElement; +} + +function getTabIndex(node: HTMLElement): number { + const tabindexAttr = parseInt(node.getAttribute('tabindex') || '', 10); if (!Number.isNaN(tabindexAttr)) { return tabindexAttr; @@ -47,7 +54,7 @@ function getTabIndex(node) { return node.tabIndex; } -function isNonTabbableRadio(node) { +function isNonTabbableRadio(node: HTMLInputElement): boolean { if (node.tagName !== 'INPUT' || node.type !== 'radio') { return false; } @@ -56,7 +63,8 @@ function isNonTabbableRadio(node) { return false; } - const getRadio = (selector) => node.ownerDocument.querySelector(`input[type="radio"]${selector}`); + const getRadio = (selector: string) => + node.ownerDocument.querySelector(`input[type="radio"]${selector}`); let roving = getRadio(`[name="${node.name}"]:checked`); @@ -67,7 +75,7 @@ function isNonTabbableRadio(node) { return roving !== node; } -function isNodeMatchingSelectorFocusable(node) { +function isNodeMatchingSelectorFocusable(node: HTMLInputElement): boolean { if ( node.disabled || (node.tagName === 'INPUT' && node.type === 'hidden') || @@ -78,24 +86,24 @@ function isNodeMatchingSelectorFocusable(node) { return true; } -function defaultGetTabbable(root) { - const regularTabNodes = []; - const orderedTabNodes = []; +function defaultGetTabbable(root: HTMLElement): HTMLElement[] { + const regularTabNodes: HTMLElement[] = []; + const orderedTabNodes: OrderedTabNode[] = []; Array.from(root.querySelectorAll(candidatesSelector)).forEach((node, i) => { - const nodeTabIndex = getTabIndex(node); + const nodeTabIndex = getTabIndex(node as HTMLElement); - if (nodeTabIndex === -1 || !isNodeMatchingSelectorFocusable(node)) { + if (nodeTabIndex === -1 || !isNodeMatchingSelectorFocusable(node as HTMLInputElement)) { return; } if (nodeTabIndex === 0) { - regularTabNodes.push(node); + regularTabNodes.push(node as HTMLElement); } else { orderedTabNodes.push({ documentOrder: i, tabIndex: nodeTabIndex, - node, + node: node as HTMLElement, }); } }); @@ -108,14 +116,22 @@ function defaultGetTabbable(root) { .concat(regularTabNodes); } -function defaultIsEnabled() { +function defaultIsEnabled(): boolean { return true; } /** * Utility component that locks focus inside the component. + * + * Demos: + * + * - [Focus Trap](https://mui.com/base/react-focus-trap/) + * + * API: + * + * - [FocusTrap API](https://mui.com/base/api/focus-trap/) */ -function FocusTrap(props) { +function FocusTrap(props: FocusTrapProps) { const { children, disableAutoFocus = false, @@ -125,18 +141,19 @@ function FocusTrap(props) { isEnabled = defaultIsEnabled, open, } = props; - const ignoreNextEnforceFocus = React.useRef(); - const sentinelStart = React.useRef(null); - const sentinelEnd = React.useRef(null); - const nodeToRestore = React.useRef(null); - const reactFocusEventTarget = React.useRef(null); + const ignoreNextEnforceFocus = React.useRef(false); + const sentinelStart = React.useRef(null); + const sentinelEnd = React.useRef(null); + const nodeToRestore = React.useRef(null); + const reactFocusEventTarget = React.useRef(null); // This variable is useful when disableAutoFocus is true. // It waits for the active element to move into the component to activate. const activated = React.useRef(false); - const rootRef = React.useRef(null); + const rootRef = React.useRef(null); + // @ts-expect-error TODO upstream fix const handleRef = useForkRef(children.ref, rootRef); - const lastKeydown = React.useRef(null); + const lastKeydown = React.useRef(null); React.useEffect(() => { // We might render an empty child. @@ -166,7 +183,7 @@ function FocusTrap(props) { ].join('\n'), ); } - rootRef.current.setAttribute('tabIndex', -1); + rootRef.current.setAttribute('tabIndex', '-1'); } if (activated.current) { @@ -181,9 +198,9 @@ function FocusTrap(props) { // in nodeToRestore.current being null. // Not all elements in IE11 have a focus method. // Once IE11 support is dropped the focus() call can be unconditional. - if (nodeToRestore.current && nodeToRestore.current.focus) { + if (nodeToRestore.current && (nodeToRestore.current as HTMLElement).focus) { ignoreNextEnforceFocus.current = true; - nodeToRestore.current.focus(); + (nodeToRestore.current as HTMLElement).focus(); } nodeToRestore.current = null; @@ -202,8 +219,9 @@ function FocusTrap(props) { const doc = ownerDocument(rootRef.current); - const contain = (nativeEvent) => { + const contain = (nativeEvent: FocusEvent | null) => { const { current: rootElement } = rootRef; + // Cleanup functions are executed lazily in React 17. // Contain can be called between the component being unmounted and its cleanup function being run. if (rootElement === null) { @@ -235,12 +253,12 @@ function FocusTrap(props) { return; } - let tabbable = []; + let tabbable: string[] | HTMLElement[] = []; if ( doc.activeElement === sentinelStart.current || doc.activeElement === sentinelEnd.current ) { - tabbable = getTabbable(rootRef.current); + tabbable = getTabbable(rootRef.current as HTMLElement); } if (tabbable.length > 0) { @@ -251,10 +269,12 @@ function FocusTrap(props) { const focusNext = tabbable[0]; const focusPrevious = tabbable[tabbable.length - 1]; - if (isShiftTab) { - focusPrevious.focus(); - } else { - focusNext.focus(); + if (typeof focusNext !== 'string' && typeof focusPrevious !== 'string') { + if (isShiftTab) { + focusPrevious.focus(); + } else { + focusNext.focus(); + } } } else { rootElement.focus(); @@ -262,7 +282,7 @@ function FocusTrap(props) { } }; - const loopFocus = (nativeEvent) => { + const loopFocus = (nativeEvent: KeyboardEvent) => { lastKeydown.current = nativeEvent; if (disableEnforceFocus || !isEnabled() || nativeEvent.key !== 'Tab') { @@ -275,7 +295,9 @@ function FocusTrap(props) { // We need to ignore the next contain as // it will try to move the focus back to the rootRef element. ignoreNextEnforceFocus.current = true; - sentinelEnd.current.focus(); + if (sentinelEnd.current) { + sentinelEnd.current.focus(); + } } }; @@ -289,8 +311,8 @@ function FocusTrap(props) { // The whatwg spec defines how the browser should behave but does not explicitly mention any events: // https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule. const interval = setInterval(() => { - if (doc.activeElement.tagName === 'BODY') { - contain(); + if (doc.activeElement && doc.activeElement.tagName === 'BODY') { + contain(null); } }, 50); @@ -302,7 +324,7 @@ function FocusTrap(props) { }; }, [disableAutoFocus, disableEnforceFocus, disableRestoreFocus, isEnabled, open, getTabbable]); - const onFocus = (event) => { + const onFocus = (event: FocusEvent) => { if (nodeToRestore.current === null) { nodeToRestore.current = event.relatedTarget; } @@ -315,7 +337,7 @@ function FocusTrap(props) { } }; - const handleFocusSentinel = (event) => { + const handleFocusSentinel = (event: React.FocusEvent) => { if (nodeToRestore.current === null) { nodeToRestore.current = event.relatedTarget; } @@ -344,7 +366,7 @@ function FocusTrap(props) { FocusTrap.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" | + // | To update them edit TypeScript types and run "yarn proptypes" | // ---------------------------------------------------------------------- /** * A single child content element. @@ -385,7 +407,7 @@ FocusTrap.propTypes /* remove-proptypes */ = { * It allows to toggle the open state without having to wait for a rerender when changing the `open` prop. * This prop should be memoized. * It can be used to support multiple focus trap mounted at the same time. - * @default function defaultIsEnabled() { + * @default function defaultIsEnabled(): boolean { * return true; * } */ @@ -394,11 +416,11 @@ FocusTrap.propTypes /* remove-proptypes */ = { * If `true`, focus is locked. */ open: PropTypes.bool.isRequired, -}; +} as any; if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line - FocusTrap['propTypes' + ''] = exactProp(FocusTrap.propTypes); + (FocusTrap as any)['propTypes' + ''] = exactProp(FocusTrap.propTypes); } export default FocusTrap; diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.d.ts b/packages/mui-base/src/FocusTrap/FocusTrap.types.ts similarity index 82% rename from packages/mui-base/src/FocusTrap/FocusTrap.d.ts rename to packages/mui-base/src/FocusTrap/FocusTrap.types.ts index 55cc5d7d88bfb6..90a746233ea97e 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.d.ts +++ b/packages/mui-base/src/FocusTrap/FocusTrap.types.ts @@ -16,7 +16,7 @@ export interface FocusTrapProps { * It allows to toggle the open state without having to wait for a rerender when changing the `open` prop. * This prop should be memoized. * It can be used to support multiple focus trap mounted at the same time. - * @default function defaultIsEnabled() { + * @default function defaultIsEnabled(): boolean { * return true; * } */ @@ -24,7 +24,7 @@ export interface FocusTrapProps { /** * A single child content element. */ - children: React.ReactElement; + children: React.ReactElement; /** * If `true`, the focus trap will not automatically shift focus to itself when it opens, and * replace it to the last focused element when it closes. @@ -50,16 +50,3 @@ export interface FocusTrapProps { */ disableRestoreFocus?: boolean; } - -/** - * Utility component that locks focus inside the component. - * - * Demos: - * - * - [Focus Trap](https://mui.com/base/react-focus-trap/) - * - * API: - * - * - [FocusTrap API](https://mui.com/base/api/focus-trap/) - */ -export default function FocusTrap(props: FocusTrapProps): JSX.Element; diff --git a/packages/mui-base/src/FocusTrap/index.d.ts b/packages/mui-base/src/FocusTrap/index.d.ts deleted file mode 100644 index 6206922d9df2c0..00000000000000 --- a/packages/mui-base/src/FocusTrap/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './FocusTrap'; -export * from './FocusTrap'; diff --git a/packages/mui-base/src/FocusTrap/index.js b/packages/mui-base/src/FocusTrap/index.ts similarity index 52% rename from packages/mui-base/src/FocusTrap/index.js rename to packages/mui-base/src/FocusTrap/index.ts index 5131ffa9169f3a..93f0fe84d13a4a 100644 --- a/packages/mui-base/src/FocusTrap/index.js +++ b/packages/mui-base/src/FocusTrap/index.ts @@ -1 +1,2 @@ export { default } from './FocusTrap'; +export * from './FocusTrap.types'; From ba0ba65a3c6c83f6f07a607b5db758b8922ef855 Mon Sep 17 00:00:00 2001 From: Yuri <118747540+sldk-yuri@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:24:08 +0300 Subject: [PATCH 42/88] [Utils] mergedeep deeply clones source key if it's an object (#35364) --- packages/mui-utils/src/deepmerge.test.ts | 15 +++++++++++++++ packages/mui-utils/src/deepmerge.ts | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/mui-utils/src/deepmerge.test.ts b/packages/mui-utils/src/deepmerge.test.ts index 3d7e267d9962f6..d0aceef896854a 100644 --- a/packages/mui-utils/src/deepmerge.test.ts +++ b/packages/mui-utils/src/deepmerge.test.ts @@ -45,4 +45,19 @@ describe('deepmerge', () => { bar: 'test', }); }); + + it('should deep clone source key object if target key does not exist', () => { + const foo = { foo: { baz: 'test' } }; + const bar = {}; + + const result = deepmerge(bar, foo); + + expect(result).to.deep.equal({ foo: { baz: 'test' } }); + + // @ts-ignore + result.foo.baz = 'new test'; + + expect(result).to.deep.equal({ foo: { baz: 'new test' } }); + expect(foo).to.deep.equal({ foo: { baz: 'test' } }); + }); }); diff --git a/packages/mui-utils/src/deepmerge.ts b/packages/mui-utils/src/deepmerge.ts index da53439d4da521..1d0c9a44b8a0ab 100644 --- a/packages/mui-utils/src/deepmerge.ts +++ b/packages/mui-utils/src/deepmerge.ts @@ -6,6 +6,20 @@ export interface DeepmergeOptions { clone?: boolean; } +function deepClone(source: T): T | Record { + if (!isPlainObject(source)) { + return source; + } + + const output: Record = {}; + + Object.keys(source).forEach((key) => { + output[key] = deepClone(source[key]); + }); + + return output; +} + export default function deepmerge( target: T, source: unknown, @@ -23,6 +37,10 @@ export default function deepmerge( if (isPlainObject(source[key]) && key in target && isPlainObject(target[key])) { // Since `output` is a clone of `target` and we have narrowed `target` in this block we can cast to the same type. (output as Record)[key] = deepmerge(target[key], source[key], options); + } else if (options.clone) { + (output as Record)[key] = isPlainObject(source[key]) + ? deepClone(source[key]) + : source[key]; } else { (output as Record)[key] = source[key]; } From 5b2583a1c8b227661c4bf4113a79346634ea53af Mon Sep 17 00:00:00 2001 From: Huynh Tan Date: Wed, 21 Dec 2022 03:35:32 +0700 Subject: [PATCH 43/88] [Popper] Convert code to typescript (#34771) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Dudak --- docs/pages/base/api/popper-unstyled.json | 2 +- docs/pages/material-ui/api/popper.json | 10 +- docs/translations/api-docs/popper/popper.json | 3 +- ...styled.test.js => PopperUnstyled.test.tsx} | 2 +- .../{PopperUnstyled.js => PopperUnstyled.tsx} | 144 +++++++++++++----- ...rUnstyled.d.ts => PopperUnstyled.types.ts} | 65 ++++---- .../mui-base/src/PopperUnstyled/index.d.ts | 5 - packages/mui-base/src/PopperUnstyled/index.js | 4 - packages/mui-base/src/PopperUnstyled/index.ts | 16 ++ packages/mui-material/src/Popper/Popper.tsx | 4 + 10 files changed, 164 insertions(+), 91 deletions(-) rename packages/mui-base/src/PopperUnstyled/{PopperUnstyled.test.js => PopperUnstyled.test.tsx} (93%) rename packages/mui-base/src/PopperUnstyled/{PopperUnstyled.js => PopperUnstyled.tsx} (77%) rename packages/mui-base/src/PopperUnstyled/{PopperUnstyled.d.ts => PopperUnstyled.types.ts} (74%) delete mode 100644 packages/mui-base/src/PopperUnstyled/index.d.ts delete mode 100644 packages/mui-base/src/PopperUnstyled/index.js create mode 100644 packages/mui-base/src/PopperUnstyled/index.ts diff --git a/docs/pages/base/api/popper-unstyled.json b/docs/pages/base/api/popper-unstyled.json index c59aafe0473eca..0b02a1cbcf978a 100644 --- a/docs/pages/base/api/popper-unstyled.json +++ b/docs/pages/base/api/popper-unstyled.json @@ -50,7 +50,7 @@ "styles": { "classes": [], "globalClasses": {}, "name": null }, "spread": true, "forwardsRefTo": "HTMLDivElement", - "filename": "/packages/mui-base/src/PopperUnstyled/PopperUnstyled.js", + "filename": "/packages/mui-base/src/PopperUnstyled/PopperUnstyled.tsx", "inheritance": null, "demos": "", "cssComponent": false diff --git a/docs/pages/material-ui/api/popper.json b/docs/pages/material-ui/api/popper.json index c37e718ae6ef1d..f8dbd99d5a1b72 100644 --- a/docs/pages/material-ui/api/popper.json +++ b/docs/pages/material-ui/api/popper.json @@ -13,10 +13,6 @@ "type": { "name": "shape", "description": "{ root?: func
| object }" } }, "container": { "type": { "name": "union", "description": "HTML element
| func" } }, - "direction": { - "type": { "name": "enum", "description": "'ltr'
| 'rtl'" }, - "default": "'ltr'" - }, "disablePortal": { "type": { "name": "bool" } }, "keepMounted": { "type": { "name": "bool" } }, "modifiers": { @@ -29,15 +25,13 @@ "type": { "name": "enum", "description": "'auto-end'
| 'auto-start'
| 'auto'
| 'bottom-end'
| 'bottom-start'
| 'bottom'
| 'left-end'
| 'left-start'
| 'left'
| 'right-end'
| 'right-start'
| 'right'
| 'top-end'
| 'top-start'
| 'top'" - }, - "default": "'bottom'" + } }, "popperOptions": { "type": { "name": "shape", "description": "{ modifiers?: array, onFirstUpdate?: func, placement?: 'auto-end'
| 'auto-start'
| 'auto'
| 'bottom-end'
| 'bottom-start'
| 'bottom'
| 'left-end'
| 'left-start'
| 'left'
| 'right-end'
| 'right-start'
| 'right'
| 'top-end'
| 'top-start'
| 'top', strategy?: 'absolute'
| 'fixed' }" - }, - "default": "{}" + } }, "popperRef": { "type": { "name": "custom", "description": "ref" } }, "slotProps": { diff --git a/docs/translations/api-docs/popper/popper.json b/docs/translations/api-docs/popper/popper.json index 6ffc8353adf6f2..b6533b390e8cf1 100644 --- a/docs/translations/api-docs/popper/popper.json +++ b/docs/translations/api-docs/popper/popper.json @@ -16,8 +16,7 @@ "slotProps": "The props used for each slot inside the Popper.", "slots": "The components used for each slot inside the Popper. Either a string to use a HTML element or a component.", "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", - "transition": "Help supporting a react-transition-group/Transition component.", - "direction": "Direction of the text." + "transition": "Help supporting a react-transition-group/Transition component." }, "classDescriptions": {} } diff --git a/packages/mui-base/src/PopperUnstyled/PopperUnstyled.test.js b/packages/mui-base/src/PopperUnstyled/PopperUnstyled.test.tsx similarity index 93% rename from packages/mui-base/src/PopperUnstyled/PopperUnstyled.test.js rename to packages/mui-base/src/PopperUnstyled/PopperUnstyled.test.tsx index ee065fbedd6cd7..ffb9186a524518 100644 --- a/packages/mui-base/src/PopperUnstyled/PopperUnstyled.test.js +++ b/packages/mui-base/src/PopperUnstyled/PopperUnstyled.test.tsx @@ -30,7 +30,7 @@ describe('', () => { })); it('should pass ownerState to overridable component', () => { - const CustomComponent = React.forwardRef(({ ownerState }, ref) => ( + const CustomComponent = React.forwardRef(({ ownerState }, ref) => (
)); render( diff --git a/packages/mui-base/src/PopperUnstyled/PopperUnstyled.js b/packages/mui-base/src/PopperUnstyled/PopperUnstyled.tsx similarity index 77% rename from packages/mui-base/src/PopperUnstyled/PopperUnstyled.js rename to packages/mui-base/src/PopperUnstyled/PopperUnstyled.tsx index 18a22c135cdc81..21eb36b9121814 100644 --- a/packages/mui-base/src/PopperUnstyled/PopperUnstyled.js +++ b/packages/mui-base/src/PopperUnstyled/PopperUnstyled.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { OverridableComponent } from '@mui/types'; import { chainPropTypes, HTMLElementType, @@ -7,14 +8,24 @@ import { unstable_useEnhancedEffect as useEnhancedEffect, unstable_useForkRef as useForkRef, } from '@mui/utils'; -import { createPopper } from '@popperjs/core'; +import { createPopper, Instance, Modifier, Placement, State, VirtualElement } from '@popperjs/core'; import PropTypes from 'prop-types'; import composeClasses from '../composeClasses'; import Portal from '../Portal'; import { getPopperUnstyledUtilityClass } from './popperUnstyledClasses'; -import { useSlotProps } from '../utils'; - -function flipPlacement(placement, direction) { +import { useSlotProps, WithOptionalOwnerState } from '../utils'; +import { + PopperPlacementType, + PopperTooltipProps, + PopperTooltipTypeMap, + PopperUnstyledChildrenProps, + PopperUnstyledProps, + PopperUnstyledRootSlotProps, + PopperUnstyledTransitionProps, + PopperUnstyledTypeMap, +} from './PopperUnstyled.types'; + +function flipPlacement(placement?: PopperPlacementType, direction?: 'ltr' | 'rtl') { if (direction === 'ltr') { return placement; } @@ -33,10 +44,26 @@ function flipPlacement(placement, direction) { } } -function resolveAnchorEl(anchorEl) { +function resolveAnchorEl( + anchorEl: + | VirtualElement + | (() => VirtualElement) + | HTMLElement + | (() => HTMLElement) + | null + | undefined, +): HTMLElement | VirtualElement | null | undefined { return typeof anchorEl === 'function' ? anchorEl() : anchorEl; } +function isHTMLElement(element: HTMLElement | VirtualElement): element is HTMLElement { + return (element as HTMLElement).nodeType !== undefined; +} + +function isVirtualElement(element: HTMLElement | VirtualElement): element is VirtualElement { + return !isHTMLElement(element); +} + const useUtilityClasses = () => { const slots = { root: ['root'], @@ -47,8 +74,10 @@ const useUtilityClasses = () => { const defaultPopperOptions = {}; -/* eslint-disable react/prop-types */ -const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) { +const PopperTooltip = React.forwardRef(function PopperTooltip( + props: PopperTooltipProps, + ref: React.ForwardedRef, +) { const { anchorEl, children, @@ -67,26 +96,26 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) { ...other } = props; - const tooltipRef = React.useRef(null); + const tooltipRef = React.useRef(null); const ownRef = useForkRef(tooltipRef, ref); - const popperRef = React.useRef(null); + const popperRef = React.useRef(null); const handlePopperRef = useForkRef(popperRef, popperRefProp); const handlePopperRefRef = React.useRef(handlePopperRef); useEnhancedEffect(() => { handlePopperRefRef.current = handlePopperRef; }, [handlePopperRef]); - React.useImperativeHandle(popperRefProp, () => popperRef.current, []); + React.useImperativeHandle(popperRefProp, () => popperRef.current!, []); const rtlPlacement = flipPlacement(initialPlacement, direction); /** * placement initialized from prop but can change during lifetime if modifiers.flip. * modifiers.flip is essentially a flip for controlled/uncontrolled behavior */ - const [placement, setPlacement] = React.useState(rtlPlacement); - const [resolvedAnchorElement, setResolvedAnchorElement] = React.useState( - resolveAnchorEl(anchorEl), - ); + const [placement, setPlacement] = React.useState(rtlPlacement); + const [resolvedAnchorElement, setResolvedAnchorElement] = React.useState< + HTMLElement | VirtualElement | null | undefined + >(resolveAnchorEl(anchorEl)); React.useEffect(() => { if (popperRef.current) { @@ -105,12 +134,16 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) { return undefined; } - const handlePopperUpdate = (data) => { + const handlePopperUpdate = (data: State) => { setPlacement(data.placement); }; if (process.env.NODE_ENV !== 'production') { - if (resolvedAnchorElement && resolvedAnchorElement.nodeType === 1) { + if ( + resolvedAnchorElement && + isHTMLElement(resolvedAnchorElement) && + resolvedAnchorElement.nodeType === 1 + ) { const box = resolvedAnchorElement.getBoundingClientRect(); if ( @@ -131,7 +164,7 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) { } } - let popperModifiers = [ + let popperModifiers: Partial>[] = [ { name: 'preventOverflow', options: { @@ -161,21 +194,21 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) { popperModifiers = popperModifiers.concat(popperOptions.modifiers); } - const popper = createPopper(resolvedAnchorElement, tooltipRef.current, { + const popper = createPopper(resolvedAnchorElement, tooltipRef.current!, { placement: rtlPlacement, ...popperOptions, modifiers: popperModifiers, }); - handlePopperRefRef.current(popper); + handlePopperRefRef.current!(popper); return () => { popper.destroy(); - handlePopperRefRef.current(null); + handlePopperRefRef.current!(null); }; }, [resolvedAnchorElement, disablePortal, modifiers, open, popperOptions, rtlPlacement]); - const childProps = { placement }; + const childProps: PopperUnstyledChildrenProps = { placement: placement! }; if (TransitionProps !== null) { childProps.TransitionProps = TransitionProps; @@ -183,7 +216,7 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) { const classes = useUtilityClasses(); const Root = component ?? slots.root ?? 'div'; - const rootProps = useSlotProps({ + const rootProps: WithOptionalOwnerState = useSlotProps({ elementType: Root, externalSlotProps: slotProps.root, externalForwardedProps: other, @@ -202,13 +235,23 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) { return ( {typeof children === 'function' ? children(childProps) : children} ); -}); -/* eslint-enable react/prop-types */ +}) as OverridableComponent; /** * Poppers rely on the 3rd party library [Popper.js](https://popper.js.org/docs/v2/) for positioning. + * + * Demos: + * + * - [Unstyled Popper](https://mui.com/base/react-popper/) + * + * API: + * + * - [PopperUnstyled API](https://mui.com/base/api/popper-unstyled/) */ -const PopperUnstyled = React.forwardRef(function PopperUnstyled(props, ref) { +const PopperUnstyled = React.forwardRef(function PopperUnstyled( + props: PopperUnstyledProps, + ref: React.ForwardedRef, +) { const { anchorEl, children, @@ -223,6 +266,8 @@ const PopperUnstyled = React.forwardRef(function PopperUnstyled(props, ref) { popperRef, style, transition = false, + slotProps = {}, + slots = {}, ...other } = props; @@ -243,8 +288,24 @@ const PopperUnstyled = React.forwardRef(function PopperUnstyled(props, ref) { // If the container prop is provided, use that // If the anchorEl prop is provided, use its parent body element as the container // If neither are provided let the Modal take care of choosing the container - const container = - containerProp || (anchorEl ? ownerDocument(resolveAnchorEl(anchorEl)).body : undefined); + let container; + if (containerProp) { + container = containerProp; + } else if (anchorEl) { + const resolvedAnchorEl = resolveAnchorEl(anchorEl); + container = + resolvedAnchorEl && isHTMLElement(resolvedAnchorEl) + ? ownerDocument(resolvedAnchorEl).body + : ownerDocument(null).body; + } + const display = !open && keepMounted && (!transition || exited) ? 'none' : undefined; + const transitionProps: PopperUnstyledTransitionProps | undefined = transition + ? { + in: open, + onEnter: handleEnter, + onExited: handleExited, + } + : undefined; return ( @@ -258,6 +319,8 @@ const PopperUnstyled = React.forwardRef(function PopperUnstyled(props, ref) { placement={placement} popperOptions={popperOptions} popperRef={popperRef} + slotProps={slotProps} + slots={slots} {...other} style={{ // Prevents scroll issue, waiting for Popper.js to add this style once initiated. @@ -265,29 +328,21 @@ const PopperUnstyled = React.forwardRef(function PopperUnstyled(props, ref) { // Fix Popper.js display issue top: 0, left: 0, - display: !open && keepMounted && (!transition || exited) ? 'none' : null, + display, ...style, }} - TransitionProps={ - transition - ? { - in: open, - onEnter: handleEnter, - onExited: handleExited, - } - : null - } + TransitionProps={transitionProps} > {children} ); -}); +}) as OverridableComponent; PopperUnstyled.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" | + // | To update them edit TypeScript types and run "yarn proptypes" | // ---------------------------------------------------------------------- /** * An HTML element, [virtualElement](https://popper.js.org/docs/v2/virtual-elements/), @@ -301,7 +356,11 @@ PopperUnstyled.propTypes /* remove-proptypes */ = { if (props.open) { const resolvedAnchorEl = resolveAnchorEl(props.anchorEl); - if (resolvedAnchorEl && resolvedAnchorEl.nodeType === 1) { + if ( + resolvedAnchorEl && + isHTMLElement(resolvedAnchorEl) && + resolvedAnchorEl.nodeType === 1 + ) { const box = resolvedAnchorEl.getBoundingClientRect(); if ( @@ -322,7 +381,8 @@ PopperUnstyled.propTypes /* remove-proptypes */ = { } else if ( !resolvedAnchorEl || typeof resolvedAnchorEl.getBoundingClientRect !== 'function' || - (resolvedAnchorEl.contextElement != null && + (isVirtualElement(resolvedAnchorEl) && + resolvedAnchorEl.contextElement != null && resolvedAnchorEl.contextElement.nodeType !== 1) ) { return new Error( @@ -484,6 +544,6 @@ PopperUnstyled.propTypes /* remove-proptypes */ = { * @default false */ transition: PropTypes.bool, -}; +} as any; export default PopperUnstyled; diff --git a/packages/mui-base/src/PopperUnstyled/PopperUnstyled.d.ts b/packages/mui-base/src/PopperUnstyled/PopperUnstyled.types.ts similarity index 74% rename from packages/mui-base/src/PopperUnstyled/PopperUnstyled.d.ts rename to packages/mui-base/src/PopperUnstyled/PopperUnstyled.types.ts index 615fb2e6631766..de036405e9f6dc 100644 --- a/packages/mui-base/src/PopperUnstyled/PopperUnstyled.d.ts +++ b/packages/mui-base/src/PopperUnstyled/PopperUnstyled.types.ts @@ -1,6 +1,6 @@ +import { OverrideProps } from '@mui/types'; +import { Instance, Options, OptionsGeneric, VirtualElement } from '@popperjs/core'; import * as React from 'react'; -import { OverridableComponent, OverrideProps } from '@mui/types'; -import { Instance, VirtualElement, Options, OptionsGeneric } from '@popperjs/core'; import { PortalProps } from '../Portal'; import { SlotComponentProps } from '../utils'; @@ -8,6 +8,17 @@ export type PopperPlacementType = Options['placement']; interface PopperUnstyledComponentsPropsOverrides {} +export interface PopperUnstyledTransitionProps { + in: boolean; + onEnter: () => void; + onExited: () => void; +} + +export interface PopperUnstyledChildrenProps { + placement: PopperPlacementType; + TransitionProps?: PopperUnstyledTransitionProps; +} + export interface PopperUnstyledOwnProps { /** * An HTML element, [virtualElement](https://popper.js.org/docs/v2/virtual-elements/), @@ -15,20 +26,11 @@ export interface PopperUnstyledOwnProps { * It's used to set the position of the popper. * The return value will passed as the reference object of the Popper instance. */ - anchorEl?: null | VirtualElement | (() => VirtualElement); + anchorEl?: null | VirtualElement | HTMLElement | (() => HTMLElement) | (() => VirtualElement); /** * Popper render function or node. */ - children?: - | React.ReactNode - | ((props: { - placement: PopperPlacementType; - TransitionProps?: { - in: boolean; - onEnter: () => {}; - onExited: () => {}; - }; - }) => React.ReactNode); + children?: React.ReactNode | ((props: PopperUnstyledChildrenProps) => React.ReactNode); /** * An HTML element or function that returns one. * The `container` will have the portal children appended to it. @@ -106,9 +108,11 @@ export interface PopperUnstyledOwnProps { * @default false */ transition?: boolean; + + ownerState?: any; } -export interface PopperUnstyledOwnerState extends PopperUnstyledOwnProps {} +export type PopperUnstyledOwnerState = Omit; export interface PopperUnstyledTypeMap

{ props: P & PopperUnstyledOwnProps; @@ -122,22 +126,27 @@ export type PopperUnstyledProps< component?: D; }; +export type PopperTooltipOwnProps = Omit< + PopperUnstyledOwnProps, + 'container' | 'keepMounted' | 'transition' +> & { + TransitionProps?: PopperUnstyledTransitionProps; +}; + +export interface PopperTooltipTypeMap

{ + props: P & PopperTooltipOwnProps; + defaultComponent: D; +} + +export type PopperTooltipProps< + D extends React.ElementType = PopperTooltipTypeMap['defaultComponent'], + P = {}, +> = OverrideProps, D> & { + component?: D; +}; + export interface PopperUnstyledRootSlotProps { className?: string; ref: React.Ref; ownerState: PopperUnstyledOwnerState; } -/** - * Poppers rely on the 3rd party library [Popper.js](https://popper.js.org/docs/v2/) for positioning. - * - * Demos: - * - * - [Unstyled Popper](https://mui.com/base/react-popper/) - * - * API: - * - * - [PopperUnstyled API](https://mui.com/base/api/popper-unstyled/) - */ -declare const PopperUnstyled: OverridableComponent; - -export default PopperUnstyled; diff --git a/packages/mui-base/src/PopperUnstyled/index.d.ts b/packages/mui-base/src/PopperUnstyled/index.d.ts deleted file mode 100644 index 0550601a07f062..00000000000000 --- a/packages/mui-base/src/PopperUnstyled/index.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default } from './PopperUnstyled'; -export * from './PopperUnstyled'; - -export { default as popperUnstyledClasses } from './popperUnstyledClasses'; -export * from './popperUnstyledClasses'; diff --git a/packages/mui-base/src/PopperUnstyled/index.js b/packages/mui-base/src/PopperUnstyled/index.js deleted file mode 100644 index 264df8cd2af84e..00000000000000 --- a/packages/mui-base/src/PopperUnstyled/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { default } from './PopperUnstyled'; - -export { default as popperUnstyledClasses } from './popperUnstyledClasses'; -export * from './popperUnstyledClasses'; diff --git a/packages/mui-base/src/PopperUnstyled/index.ts b/packages/mui-base/src/PopperUnstyled/index.ts new file mode 100644 index 00000000000000..9f8d876a0c45c7 --- /dev/null +++ b/packages/mui-base/src/PopperUnstyled/index.ts @@ -0,0 +1,16 @@ +export { default } from './PopperUnstyled'; +export type { + PopperPlacementType, + PopperUnstyledTransitionProps, + PopperUnstyledChildrenProps, + PopperUnstyledOwnProps, + PopperUnstyledOwnerState, + PopperUnstyledTypeMap, + PopperUnstyledProps, + PopperUnstyledRootSlotProps, +} from './PopperUnstyled.types'; +export { + default as popperUnstyledClasses, + getPopperUnstyledUtilityClass, +} from './popperUnstyledClasses'; +export type { PopperUnstyledClassKey, PopperUnstyledClasses } from './popperUnstyledClasses'; diff --git a/packages/mui-material/src/Popper/Popper.tsx b/packages/mui-material/src/Popper/Popper.tsx index e62ece2792ba94..bee3462980d8f5 100644 --- a/packages/mui-material/src/Popper/Popper.tsx +++ b/packages/mui-material/src/Popper/Popper.tsx @@ -168,6 +168,10 @@ Popper.propTypes /* remove-proptypes */ = { * If `true`, the component is shown. */ open: PropTypes.bool.isRequired, + /** + * @ignore + */ + ownerState: PropTypes.any, /** * Popper placement. * @default 'bottom' From e0e0d6910a39ea166cd43b8b106fb3e176c53ec1 Mon Sep 17 00:00:00 2001 From: ekusiadadus Date: Wed, 21 Dec 2022 17:59:47 +0900 Subject: [PATCH 44/88] [docs] Remove empty tags on the TransferList demos (#33127) --- .../material/components/transfer-list/SelectAllTransferList.js | 1 - .../material/components/transfer-list/SelectAllTransferList.tsx | 1 - docs/data/material/components/transfer-list/TransferList.js | 1 - docs/data/material/components/transfer-list/TransferList.tsx | 1 - 4 files changed, 4 deletions(-) diff --git a/docs/data/material/components/transfer-list/SelectAllTransferList.js b/docs/data/material/components/transfer-list/SelectAllTransferList.js index 6aaa3be8dae837..96d88831cca504 100644 --- a/docs/data/material/components/transfer-list/SelectAllTransferList.js +++ b/docs/data/material/components/transfer-list/SelectAllTransferList.js @@ -121,7 +121,6 @@ export default function TransferList() { ); })} - ); diff --git a/docs/data/material/components/transfer-list/SelectAllTransferList.tsx b/docs/data/material/components/transfer-list/SelectAllTransferList.tsx index 7ef0ae913598a8..c192984def22f5 100644 --- a/docs/data/material/components/transfer-list/SelectAllTransferList.tsx +++ b/docs/data/material/components/transfer-list/SelectAllTransferList.tsx @@ -122,7 +122,6 @@ export default function TransferList() { ); })} - ); diff --git a/docs/data/material/components/transfer-list/TransferList.js b/docs/data/material/components/transfer-list/TransferList.js index 8a5537527d3c7b..dcf7843b087567 100644 --- a/docs/data/material/components/transfer-list/TransferList.js +++ b/docs/data/material/components/transfer-list/TransferList.js @@ -86,7 +86,6 @@ export default function TransferList() { ); })} - ); diff --git a/docs/data/material/components/transfer-list/TransferList.tsx b/docs/data/material/components/transfer-list/TransferList.tsx index 1ce1acba0049c0..f631eb7f2279fa 100644 --- a/docs/data/material/components/transfer-list/TransferList.tsx +++ b/docs/data/material/components/transfer-list/TransferList.tsx @@ -86,7 +86,6 @@ export default function TransferList() { ); })} - ); From 44c0f5d824ca10e49003c13e555d94fa32dc2110 Mon Sep 17 00:00:00 2001 From: Jason Sturges Date: Wed, 21 Dec 2022 06:32:02 -0600 Subject: [PATCH 45/88] [docs] Fix typo in `Progress` docs (#35553) Signed-off-by: Jason Sturges --- docs/data/material/components/progress/progress.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/material/components/progress/progress.md b/docs/data/material/components/progress/progress.md index ef5b5f22b8cf0a..4e1f726723ac16 100644 --- a/docs/data/material/components/progress/progress.md +++ b/docs/data/material/components/progress/progress.md @@ -69,7 +69,7 @@ The progress components accept a value in the range 0 - 100. This simplifies thi ```jsx // MIN = Minimum expected value -// MAX = Maximium expected value +// MAX = Maximum expected value // Function to normalise the values (MIN / MAX could be integrated) const normalise = (value) => ((value - MIN) * 100) / (MAX - MIN); From fa62bf35e109ede215cb8cbae66ba4d6a30fef60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Wed, 21 Dec 2022 15:08:24 +0100 Subject: [PATCH 46/88] [core] Exclude documentation of Base props not used in styled libraries (#35562) --- docs/pages/material-ui/api/popper.json | 22 ++++++++--- .../ApiBuilders/ComponentApiBuilder.ts | 38 ------------------- packages/mui-material/src/Popper/Popper.tsx | 4 +- 3 files changed, 19 insertions(+), 45 deletions(-) diff --git a/docs/pages/material-ui/api/popper.json b/docs/pages/material-ui/api/popper.json index f8dbd99d5a1b72..292d8c77a6a0d0 100644 --- a/docs/pages/material-ui/api/popper.json +++ b/docs/pages/material-ui/api/popper.json @@ -8,9 +8,13 @@ } }, "children": { "type": { "name": "union", "description": "node
| func" } }, - "components": { "type": { "name": "shape", "description": "{ Root?: elementType }" } }, + "components": { + "type": { "name": "shape", "description": "{ Root?: elementType }" }, + "default": "{}" + }, "componentsProps": { - "type": { "name": "shape", "description": "{ root?: func
| object }" } + "type": { "name": "shape", "description": "{ root?: func
| object }" }, + "default": "{}" }, "container": { "type": { "name": "union", "description": "HTML element
| func" } }, "disablePortal": { "type": { "name": "bool" } }, @@ -25,19 +29,25 @@ "type": { "name": "enum", "description": "'auto-end'
| 'auto-start'
| 'auto'
| 'bottom-end'
| 'bottom-start'
| 'bottom'
| 'left-end'
| 'left-start'
| 'left'
| 'right-end'
| 'right-start'
| 'right'
| 'top-end'
| 'top-start'
| 'top'" - } + }, + "default": "'bottom'" }, "popperOptions": { "type": { "name": "shape", "description": "{ modifiers?: array, onFirstUpdate?: func, placement?: 'auto-end'
| 'auto-start'
| 'auto'
| 'bottom-end'
| 'bottom-start'
| 'bottom'
| 'left-end'
| 'left-start'
| 'left'
| 'right-end'
| 'right-start'
| 'right'
| 'top-end'
| 'top-start'
| 'top', strategy?: 'absolute'
| 'fixed' }" - } + }, + "default": "{}" }, "popperRef": { "type": { "name": "custom", "description": "ref" } }, "slotProps": { - "type": { "name": "shape", "description": "{ root?: func
| object }" } + "type": { "name": "shape", "description": "{ root?: func
| object }" }, + "default": "{}" + }, + "slots": { + "type": { "name": "shape", "description": "{ root?: elementType }" }, + "default": "{}" }, - "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, "sx": { "type": { "name": "union", diff --git a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts index d4885204cca893..9e1f78dd9ba3ba 100644 --- a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts +++ b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts @@ -12,7 +12,6 @@ import { Link } from 'mdast'; import { defaultHandlers, parse as docgenParse, ReactDocgenApi } from 'react-docgen'; import { unstable_generateUtilityClass as generateUtilityClass } from '@mui/utils'; import { renderInline as renderMarkdownInline } from '@mui/markdown'; -import { getUnstyledFilename } from '@mui-internal/docs-utilities'; import * as ttp from 'typescript-to-proptypes'; import { LANGUAGES } from 'docs/config'; @@ -575,43 +574,6 @@ const generateComponentApi = async (componentInfo: ComponentInfo, program: ttp.t reactApi = docgenParse(src, null, defaultHandlers.concat(muiDefaultPropsHandler), { filename }); } - // === Handle unstyled component === - const unstyledFileName = getUnstyledFilename(filename); - let unstyledSrc; - - // Try to get data for the unstyled component - try { - unstyledSrc = readFileSync(unstyledFileName, 'utf8'); - } catch (err) { - // Unstyled component does not exist - } - - if (unstyledSrc) { - const unstyledReactAPI = docgenParse( - unstyledSrc, - null, - defaultHandlers.concat(muiDefaultPropsHandler), - { - filename: unstyledFileName, - }, - ); - - Object.keys(unstyledReactAPI.props).forEach((prop) => { - if ( - unstyledReactAPI.props[prop].defaultValue && - reactApi.props && - (!reactApi.props[prop] || !reactApi.props[prop].defaultValue) - ) { - if (reactApi.props[prop]) { - reactApi.props[prop].defaultValue = unstyledReactAPI.props[prop].defaultValue; - reactApi.props[prop].jsdocDefaultValue = unstyledReactAPI.props[prop].jsdocDefaultValue; - } else { - reactApi.props[prop] = unstyledReactAPI.props[prop]; - } - } - }); - } // ================================ - // Ignore what we might have generated in `annotateComponentDefinition` const annotatedDescriptionMatch = reactApi.description.match(/(Demos|API):\r?\n\r?\n/); if (annotatedDescriptionMatch !== null) { diff --git a/packages/mui-material/src/Popper/Popper.tsx b/packages/mui-material/src/Popper/Popper.tsx index bee3462980d8f5..41c74334673a85 100644 --- a/packages/mui-material/src/Popper/Popper.tsx +++ b/packages/mui-material/src/Popper/Popper.tsx @@ -49,11 +49,13 @@ const Popper = React.forwardRef(function Popper( ref: React.ForwardedRef, ) { const theme = useTheme<{ direction?: Direction }>(); - const { components, componentsProps, slots, slotProps, ...other } = useThemeProps({ + const props = useThemeProps({ props: inProps, name: 'MuiPopper', }); + const { components, componentsProps, slots, slotProps, ...other } = props; + const RootComponent = slots?.root ?? components?.Root; return ( From 1c4cf6b338518cee6683452615d1f77acf99caea Mon Sep 17 00:00:00 2001 From: Mibiki <108873902+MickaelAustoni@users.noreply.github.com> Date: Wed, 21 Dec 2022 18:29:42 +0100 Subject: [PATCH 47/88] [docs] Fix ListItem button deprecated use (#33970) --- .../components/dialogs/SimpleDialog.js | 36 +++++++++++-------- .../components/dialogs/SimpleDialog.tsx | 34 +++++++++++------- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/docs/data/material/components/dialogs/SimpleDialog.js b/docs/data/material/components/dialogs/SimpleDialog.js index e9a56e8f42494f..0d3e0baafb7937 100644 --- a/docs/data/material/components/dialogs/SimpleDialog.js +++ b/docs/data/material/components/dialogs/SimpleDialog.js @@ -5,6 +5,7 @@ import Avatar from '@mui/material/Avatar'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemAvatar from '@mui/material/ListItemAvatar'; +import ListItemButton from '@mui/material/ListItemButton'; import ListItemText from '@mui/material/ListItemText'; import DialogTitle from '@mui/material/DialogTitle'; import Dialog from '@mui/material/Dialog'; @@ -31,23 +32,30 @@ function SimpleDialog(props) { Set backup account {emails.map((email) => ( - handleListItemClick(email)} key={email}> - - - - - - + + handleListItemClick(email)} key={email}> + + + + + + + ))} - handleListItemClick('addAccount')}> - - - - - - + + handleListItemClick('addAccount')} + > + + + + + + + diff --git a/docs/data/material/components/dialogs/SimpleDialog.tsx b/docs/data/material/components/dialogs/SimpleDialog.tsx index b536e59dca2fe7..7d850e55bf6cb8 100644 --- a/docs/data/material/components/dialogs/SimpleDialog.tsx +++ b/docs/data/material/components/dialogs/SimpleDialog.tsx @@ -4,6 +4,7 @@ import Avatar from '@mui/material/Avatar'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemAvatar from '@mui/material/ListItemAvatar'; +import ListItemButton from '@mui/material/ListItemButton'; import ListItemText from '@mui/material/ListItemText'; import DialogTitle from '@mui/material/DialogTitle'; import Dialog from '@mui/material/Dialog'; @@ -36,22 +37,29 @@ function SimpleDialog(props: SimpleDialogProps) { Set backup account {emails.map((email) => ( - handleListItemClick(email)} key={email}> + + handleListItemClick(email)} key={email}> + + + + + + + + + ))} + + handleListItemClick('addAccount')} + > - - + + - - - ))} - handleListItemClick('addAccount')}> - - - - - - + + From b8c0d39e0cf3224b98834b5f1706bca6436f5318 Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Thu, 22 Dec 2022 13:44:24 +0700 Subject: [PATCH 48/88] [Joy] Miscellaneous fixes (#35552) --- .../joy/components/aspect-ratio/CarouselRatio.js | 5 +---- .../joy/components/aspect-ratio/CarouselRatio.tsx | 5 +---- .../joy/components/css-baseline/css-baseline.md | 2 +- packages/mui-joy/src/AspectRatio/AspectRatio.tsx | 3 ++- packages/mui-joy/src/styles/extendTheme.ts | 8 +++++--- .../fixtures/AspectRatioJoy/AspectRatioRadius.js | 14 ++++++++++++++ 6 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 test/regressions/fixtures/AspectRatioJoy/AspectRatioRadius.js diff --git a/docs/data/joy/components/aspect-ratio/CarouselRatio.js b/docs/data/joy/components/aspect-ratio/CarouselRatio.js index efd08aff3c1d71..7f20d59d6eae4b 100644 --- a/docs/data/joy/components/aspect-ratio/CarouselRatio.js +++ b/docs/data/joy/components/aspect-ratio/CarouselRatio.js @@ -48,10 +48,7 @@ export default function CarouselRatio() { '--Card-padding': (theme) => theme.spacing(2), }} > - + theme.spacing(2), }} > - + `, but 16px is assumed (the browser default). You can learn more about the implications of changing the `` default font size in [the theme documentation](/material-ui/customization/typography/#html-font-size) page. -- Set the `theme.typography.body1` style on the `` element. +- Set the default `Typography`'s level (`body1`) style on the `` element. The style comes from `theme.typography.{default typography level prop}`. - Set the font-weight to `bold` for the `` and `` elements. - Custom font-smoothing is enabled for better display of the default font. diff --git a/packages/mui-joy/src/AspectRatio/AspectRatio.tsx b/packages/mui-joy/src/AspectRatio/AspectRatio.tsx index edaf4b39f82787..3173f05f4e7300 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatio.tsx +++ b/packages/mui-joy/src/AspectRatio/AspectRatio.tsx @@ -41,6 +41,7 @@ const AspectRatioRoot = styled('div', { maxHeight || '9999px' })` : `calc(100% / (${ownerState.ratio}))`, + borderRadius: 'var(--AspectRatio-radius)', flexDirection: 'column', margin: 'var(--AspectRatio-margin)', }; @@ -54,7 +55,7 @@ const AspectRatioContent = styled('div', { { flex: 1, position: 'relative', - borderRadius: 'var(--AspectRatio-radius)', + borderRadius: 'inherit', height: 0, paddingBottom: 'var(--AspectRatio-paddingBottom)', overflow: 'hidden', diff --git a/packages/mui-joy/src/styles/extendTheme.ts b/packages/mui-joy/src/styles/extendTheme.ts index a04427a97ac188..a5eb94c9971cee 100644 --- a/packages/mui-joy/src/styles/extendTheme.ts +++ b/packages/mui-joy/src/styles/extendTheme.ts @@ -546,16 +546,18 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme { margin: 'var(--Icon-margin)', ...(ownerState.fontSize && ownerState.fontSize !== 'inherit' && { - fontSize: `var(--Icon-fontSize, ${themeProp.fontSize[ownerState.fontSize]})`, + fontSize: `var(--Icon-fontSize, ${ + themeProp.vars.fontSize[ownerState.fontSize] + })`, }), ...(ownerState.color && ownerState.color !== 'inherit' && ownerState.color !== 'context' && themeProp.vars.palette[ownerState.color!] && { - color: themeProp.vars.palette[ownerState.color].plainColor, + color: `rgba(${themeProp.vars.palette[ownerState.color]?.mainChannel} / 1)`, }), ...(ownerState.color === 'context' && { - color: theme.variants.plain?.context?.color, + color: themeProp.vars.palette.text.secondary, }), ...(instanceFontSize && instanceFontSize !== 'inherit' && { diff --git a/test/regressions/fixtures/AspectRatioJoy/AspectRatioRadius.js b/test/regressions/fixtures/AspectRatioJoy/AspectRatioRadius.js new file mode 100644 index 00000000000000..7b9bc5669328c3 --- /dev/null +++ b/test/regressions/fixtures/AspectRatioJoy/AspectRatioRadius.js @@ -0,0 +1,14 @@ +import * as React from 'react'; +import { CssVarsProvider } from '@mui/joy/styles'; +import AspectRatio from '@mui/joy/AspectRatio'; +import Box from '@mui/joy/Box'; + +export default function VariantColorJoy() { + return ( + + + + + + ); +} From 94f8c9f1c2f189d302b11becde5490a0d7d40aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Souza=20Costa=20Ara=C3=BAjo?= Date: Thu, 22 Dec 2022 06:06:08 -0300 Subject: [PATCH 49/88] [SelectInput] Update menu to use select wrapper as anchor (#34229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Dudak --- packages/mui-material/src/Select/Select.test.js | 14 ++++++++------ packages/mui-material/src/Select/SelectInput.js | 10 ++++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/mui-material/src/Select/Select.test.js b/packages/mui-material/src/Select/Select.test.js index 8d8e7026f0547f..861657c9130449 100644 --- a/packages/mui-material/src/Select/Select.test.js +++ b/packages/mui-material/src/Select/Select.test.js @@ -851,27 +851,29 @@ describe(' Only , ); + const parentEl = container.querySelector('.MuiInputBase-root'); const button = getByRole('button'); - stub(button, 'clientWidth').get(() => 14); + stub(parentEl, 'clientWidth').get(() => 14); fireEvent.mouseDown(button); expect(getByTestId('paper').style).to.have.property('minWidth', '14px'); }); - it('should not take the triger width into account when autoWidth is true', () => { - const { getByRole, getByTestId } = render( + it('should not take the trigger parent element width into account when autoWidth is true', () => { + const { container, getByRole, getByTestId } = render( , ); + const parentEl = container.querySelector('.MuiInputBase-root'); const button = getByRole('button'); - stub(button, 'clientWidth').get(() => 14); + stub(parentEl, 'clientWidth').get(() => 14); fireEvent.mouseDown(button); expect(getByTestId('paper').style).to.have.property('minWidth', ''); diff --git a/packages/mui-material/src/Select/SelectInput.js b/packages/mui-material/src/Select/SelectInput.js index 81f92e9c4942d1..7b86a76f01bf44 100644 --- a/packages/mui-material/src/Select/SelectInput.js +++ b/packages/mui-material/src/Select/SelectInput.js @@ -158,6 +158,8 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { } }, []); + const anchorElement = displayNode?.parentNode; + React.useImperativeHandle( handleRef, () => ({ @@ -173,7 +175,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { // Resize menu on `defaultOpen` automatic toggle. React.useEffect(() => { if (defaultOpen && openState && displayNode && !isOpenControlled) { - setMenuMinWidthState(autoWidth ? null : displayNode.clientWidth); + setMenuMinWidthState(autoWidth ? null : anchorElement.clientWidth); displayRef.current.focus(); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -215,7 +217,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { } if (!isOpenControlled) { - setMenuMinWidthState(autoWidth ? null : displayNode.clientWidth); + setMenuMinWidthState(autoWidth ? null : anchorElement.clientWidth); setOpenState(open); } }; @@ -479,7 +481,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { let menuMinWidth = menuMinWidthState; if (!autoWidth && isOpenControlled && displayNode) { - menuMinWidth = displayNode.clientWidth; + menuMinWidth = anchorElement.clientWidth; } let tabIndex; @@ -546,7 +548,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {

Date: Thu, 22 Dec 2022 16:57:38 +0700 Subject: [PATCH 50/88] [Joy] Improve cursor pointer and add fallback for outlined variant (#35573) --- packages/mui-joy/src/Alert/Alert.tsx | 2 +- packages/mui-joy/src/Badge/Badge.tsx | 2 +- packages/mui-joy/src/Card/Card.tsx | 8 ++++---- packages/mui-joy/src/CardOverflow/CardOverflow.tsx | 2 +- packages/mui-joy/src/Chip/Chip.tsx | 7 ++++--- packages/mui-joy/src/ChipDelete/ChipDelete.tsx | 1 + .../mui-joy/src/CircularProgress/CircularProgress.tsx | 6 +++--- packages/mui-joy/src/IconButton/IconButton.tsx | 1 + packages/mui-joy/src/Input/Input.tsx | 7 +++---- packages/mui-joy/src/LinearProgress/LinearProgress.tsx | 2 +- packages/mui-joy/src/Link/Link.tsx | 2 +- packages/mui-joy/src/ListItemButton/ListItemButton.tsx | 3 ++- packages/mui-joy/src/ModalDialog/ModalDialog.tsx | 2 +- packages/mui-joy/src/Select/Select.tsx | 7 ++++--- packages/mui-joy/src/Slider/Slider.tsx | 6 +++--- packages/mui-joy/src/Switch/Switch.tsx | 4 ++-- packages/mui-joy/src/Textarea/Textarea.tsx | 4 ++-- packages/mui-joy/src/Tooltip/Tooltip.tsx | 2 +- packages/mui-joy/src/styles/extendTheme.test.js | 9 +++++++++ packages/mui-joy/src/styles/variantUtils.test.js | 6 ------ packages/mui-joy/src/styles/variantUtils.ts | 6 +++--- 21 files changed, 48 insertions(+), 41 deletions(-) diff --git a/packages/mui-joy/src/Alert/Alert.tsx b/packages/mui-joy/src/Alert/Alert.tsx index 9ae59c8135f0e3..ffe87c545874c7 100644 --- a/packages/mui-joy/src/Alert/Alert.tsx +++ b/packages/mui-joy/src/Alert/Alert.tsx @@ -34,7 +34,7 @@ const AlertRoot = styled('div', { })<{ ownerState: AlertProps }>(({ theme, ownerState }) => ({ '--Alert-radius': theme.vars.radius.sm, '--Alert-decorator-childRadius': - 'max((var(--Alert-radius) - var(--variant-borderWidth)) - var(--Alert-padding), min(var(--Alert-padding) / 2, (var(--Alert-radius) - var(--variant-borderWidth)) / 2))', + 'max((var(--Alert-radius) - var(--variant-borderWidth, 0px)) - var(--Alert-padding), min(var(--Alert-padding) / 2, (var(--Alert-radius) - var(--variant-borderWidth, 0px)) / 2))', '--Button-minHeight': 'var(--Alert-decorator-childHeight)', '--IconButton-size': 'var(--Alert-decorator-childHeight)', '--Button-radius': 'var(--Alert-decorator-childRadius)', diff --git a/packages/mui-joy/src/Badge/Badge.tsx b/packages/mui-joy/src/Badge/Badge.tsx index 39c25b6e2dbf15..2eaf531532fcaa 100644 --- a/packages/mui-joy/src/Badge/Badge.tsx +++ b/packages/mui-joy/src/Badge/Badge.tsx @@ -116,7 +116,7 @@ const BadgeBadge = styled('span', { fontWeight: theme.vars.fontWeight.md, lineHeight: 1, padding: - 'calc(var(--Badge-paddingX) / 2 - var(--variant-borderWidth)) calc(var(--Badge-paddingX) - var(--variant-borderWidth))', + 'calc(var(--Badge-paddingX) / 2 - var(--variant-borderWidth, 0px)) calc(var(--Badge-paddingX) - var(--variant-borderWidth, 0px))', minHeight: 'var(--Badge-minHeight)', minWidth: 'var(--Badge-minHeight)', borderRadius: 'var(--Badge-radius, var(--Badge-minHeight))', diff --git a/packages/mui-joy/src/Card/Card.tsx b/packages/mui-joy/src/Card/Card.tsx index 63e3f29830796f..73c9813f331fff 100644 --- a/packages/mui-joy/src/Card/Card.tsx +++ b/packages/mui-joy/src/Card/Card.tsx @@ -38,11 +38,11 @@ const CardRoot = styled('div', { { // a context variable for any child component '--Card-childRadius': - 'max((var(--Card-radius) - var(--variant-borderWidth)) - var(--Card-padding), min(var(--Card-padding) / 2, (var(--Card-radius) - var(--variant-borderWidth)) / 2))', + 'max((var(--Card-radius) - var(--variant-borderWidth, 0px)) - var(--Card-padding), min(var(--Card-padding) / 2, (var(--Card-radius) - var(--variant-borderWidth, 0px)) / 2))', // AspectRatio integration '--AspectRatio-radius': 'var(--Card-childRadius)', // Link integration - '--internal-action-margin': 'calc(-1 * var(--variant-borderWidth))', + '--internal-action-margin': 'calc(-1 * var(--variant-borderWidth, 0px))', // Link, Radio, Checkbox integration '--internal-action-radius': resolveSxValue( { theme, ownerState }, @@ -50,10 +50,10 @@ const CardRoot = styled('div', { 'var(--Card-radius)', ), // CardCover integration - '--CardCover-radius': 'calc(var(--Card-radius) - var(--variant-borderWidth))', + '--CardCover-radius': 'calc(var(--Card-radius) - var(--variant-borderWidth, 0px))', // CardOverflow integration '--CardOverflow-offset': `calc(-1 * var(--Card-padding))`, - '--CardOverflow-radius': 'calc(var(--Card-radius) - var(--variant-borderWidth))', + '--CardOverflow-radius': 'calc(var(--Card-radius) - var(--variant-borderWidth, 0px))', // Divider integration '--Divider-inset': 'calc(-1 * var(--Card-padding))', ...(ownerState.size === 'sm' && { diff --git a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx index a60fced418e944..bda7cd29a9d2da 100644 --- a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx +++ b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx @@ -34,7 +34,7 @@ const CardOverflowRoot = styled('div', { 'data-last-child'?: string; }; }>(({ theme, ownerState }) => { - const childRadius = 'calc(var(--CardOverflow-radius) - var(--variant-borderWidth))'; + const childRadius = 'calc(var(--CardOverflow-radius) - var(--variant-borderWidth, 0px))'; return [ ownerState.row ? { diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx index 5851c1552c62b6..a0eed3a96d5103 100644 --- a/packages/mui-joy/src/Chip/Chip.tsx +++ b/packages/mui-joy/src/Chip/Chip.tsx @@ -41,7 +41,7 @@ const ChipRoot = styled('div', { { // for controlling chip delete margin offset '--Chip-decorator-childOffset': - 'min(calc(var(--Chip-paddingInline) - (var(--_Chip-minHeight) - 2 * var(--variant-borderWidth) - var(--Chip-decorator-childHeight)) / 2), var(--Chip-paddingInline))', + 'min(calc(var(--Chip-paddingInline) - (var(--_Chip-minHeight) - 2 * var(--variant-borderWidth, 0px) - var(--Chip-decorator-childHeight)) / 2), var(--Chip-paddingInline))', '--Chip-decorator-childRadius': 'max(var(--_Chip-radius) - var(--_Chip-paddingBlock), min(var(--_Chip-paddingBlock) / 2, var(--_Chip-radius) / 2))', '--Chip-delete-radius': 'var(--Chip-decorator-childRadius)', @@ -54,7 +54,7 @@ const ChipRoot = styled('div', { '--Chip-gap': '0.25rem', '--Chip-paddingInline': '0.5rem', '--Chip-decorator-childHeight': - 'calc(min(1.125rem, var(--_Chip-minHeight)) - 2 * var(--variant-borderWidth))', + 'calc(min(1.125rem, var(--_Chip-minHeight)) - 2 * var(--variant-borderWidth, 0px))', '--Icon-fontSize': 'calc(var(--_Chip-minHeight) / 1.714)', // 0.875rem by default '--_Chip-minHeight': 'var(--Chip-minHeight, 1.5rem)', fontSize: theme.vars.fontSize.xs, @@ -77,7 +77,7 @@ const ChipRoot = styled('div', { }), '--_Chip-radius': 'var(--Chip-radius, 1.5rem)', '--_Chip-paddingBlock': - 'max((var(--_Chip-minHeight) - 2 * var(--variant-borderWidth) - var(--Chip-decorator-childHeight)) / 2, 0px)', + 'max((var(--_Chip-minHeight) - 2 * var(--variant-borderWidth, 0px) - var(--Chip-decorator-childHeight)) / 2, 0px)', minHeight: 'var(--_Chip-minHeight)', paddingInline: 'var(--Chip-paddingInline)', borderRadius: 'var(--_Chip-radius)', @@ -144,6 +144,7 @@ const ChipAction = styled('button', { bottom: 0, right: 0, border: 'none', + cursor: 'pointer', padding: 'initial', margin: 'initial', backgroundColor: 'initial', diff --git a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx index 3b91c645582bd6..19b5309dc467e9 100644 --- a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx +++ b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx @@ -34,6 +34,7 @@ const ChipDeleteRoot = styled('button', { { '--Icon-margin': 'initial', // prevent overrides from parent pointerEvents: 'visible', // force the ChipDelete to be hoverable because the decorator can have pointerEvents 'none' + cursor: 'pointer', width: 'var(--Chip-delete-size, 2rem)', height: 'var(--Chip-delete-size, 2rem)', borderRadius: 'var(--Chip-delete-radius, 50%)', diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx index 568d44ed94cf02..9477c90cdeadb5 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx +++ b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx @@ -90,7 +90,7 @@ const CircularProgressRoot = styled('span', { // internal variables '--_thickness-diff': 'calc(var(--CircularProgress-track-thickness) - var(--CircularProgress-progress-thickness))', - '--_inner-size': 'calc(var(--_root-size) - 2 * var(--variant-borderWidth))', + '--_inner-size': 'calc(var(--_root-size) - 2 * var(--variant-borderWidth, 0px))', '--_outlined-inset': 'max(var(--CircularProgress-track-thickness), var(--CircularProgress-progress-thickness))', width: 'var(--_root-size)', @@ -137,8 +137,8 @@ const CircularProgressSvg = styled('svg', { display: 'inherit', boxSizing: 'inherit', position: 'absolute', - top: 'calc(-1 * var(--variant-borderWidth))', // centered align - left: 'calc(-1 * var(--variant-borderWidth))', // centered align + top: 'calc(-1 * var(--variant-borderWidth, 0px))', // centered align + left: 'calc(-1 * var(--variant-borderWidth, 0px))', // centered align }); const CircularProgressTrack = styled('circle', { diff --git a/packages/mui-joy/src/IconButton/IconButton.tsx b/packages/mui-joy/src/IconButton/IconButton.tsx index 695253022424ce..5ba634b7627f70 100644 --- a/packages/mui-joy/src/IconButton/IconButton.tsx +++ b/packages/mui-joy/src/IconButton/IconButton.tsx @@ -66,6 +66,7 @@ export const StyledIconButton = styled('button')<{ ownerState: IconButtonOwnerSt border: 'none', boxSizing: 'border-box', backgroundColor: 'transparent', + cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', diff --git a/packages/mui-joy/src/Input/Input.tsx b/packages/mui-joy/src/Input/Input.tsx index d61580fe5a43bb..3d4c6c6ceb4eac 100644 --- a/packages/mui-joy/src/Input/Input.tsx +++ b/packages/mui-joy/src/Input/Input.tsx @@ -71,9 +71,9 @@ export const StyledInputRoot = styled('div')<{ ownerState: InputOwnerState }>( }), // variables for controlling child components '--Input-decorator-childOffset': - 'min(calc(var(--Input-paddingInline) - (var(--Input-minHeight) - 2 * var(--variant-borderWidth) - var(--Input-decorator-childHeight)) / 2), var(--Input-paddingInline))', + 'min(calc(var(--Input-paddingInline) - (var(--Input-minHeight) - 2 * var(--variant-borderWidth, 0px) - var(--Input-decorator-childHeight)) / 2), var(--Input-paddingInline))', '--_Input-paddingBlock': - 'max((var(--Input-minHeight) - 2 * var(--variant-borderWidth) - var(--Input-decorator-childHeight)) / 2, 0px)', + 'max((var(--Input-minHeight) - 2 * var(--variant-borderWidth, 0px) - var(--Input-decorator-childHeight)) / 2, 0px)', '--Input-decorator-childRadius': 'max(var(--Input-radius) - var(--_Input-paddingBlock), min(var(--_Input-paddingBlock) / 2, var(--Input-radius) / 2))', '--Button-minHeight': 'var(--Input-decorator-childHeight)', @@ -112,7 +112,7 @@ export const StyledInputRoot = styled('div')<{ ownerState: InputOwnerState }>( bottom: 0, zIndex: 1, borderRadius: 'inherit', - margin: 'calc(var(--variant-borderWidth) * -1)', // for outlined variant + margin: 'calc(var(--variant-borderWidth, 0px) * -1)', // for outlined variant }, }, { @@ -122,7 +122,6 @@ export const StyledInputRoot = styled('div')<{ ownerState: InputOwnerState }>( [`&:hover:not(.${inputClasses.focused})`]: { ...theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!], backgroundColor: null, // it is not common to change background on hover for Input - cursor: 'text', }, [`&.${inputClasses.disabled}`]: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx index b45a30ffe1f5ce..bfc14be306af41 100644 --- a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx +++ b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx @@ -99,7 +99,7 @@ const LinearProgressRoot = styled('div', { position: 'relative', ...theme.variants[ownerState.variant!]?.[ownerState.color!], '--_LinearProgress-padding': - 'max((var(--LinearProgress-thickness) - 2 * var(--variant-borderWidth) - var(--LinearProgress-progressThickness)) / 2, 0px)', + 'max((var(--LinearProgress-thickness) - 2 * var(--variant-borderWidth, 0px) - var(--LinearProgress-progressThickness)) / 2, 0px)', '&::before': { content: '""', display: 'block', diff --git a/packages/mui-joy/src/Link/Link.tsx b/packages/mui-joy/src/Link/Link.tsx index 35c3cdae18dfca..cf2336f9bba2c8 100644 --- a/packages/mui-joy/src/Link/Link.tsx +++ b/packages/mui-joy/src/Link/Link.tsx @@ -93,6 +93,7 @@ const LinkRoot = styled('a', { margin: 0, // Remove the margin in Safari borderRadius: theme.vars.radius.xs, padding: 0, // Remove the padding in Firefox + cursor: 'pointer', textDecorationColor: `rgba(${ theme.vars.palette[ownerState.color!]?.mainChannel } / var(--Link-underlineOpacity, 0.72))`, @@ -106,7 +107,6 @@ const LinkRoot = styled('a', { } : { color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)`, - cursor: 'pointer', [`&.${linkClasses.disabled}`]: { pointerEvents: 'none', color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 0.6)`, diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx index c2565af8fb3ecd..a80e2aab288491 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx +++ b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx @@ -56,6 +56,7 @@ export const StyledListItemButton = styled('div')<{ ownerState: ListItemButtonOw textAlign: 'initial', textDecoration: 'initial', // reset native anchor tag backgroundColor: 'initial', // reset button background + cursor: 'pointer', // In some cases, ListItemButton is a child of ListItem so the margin needs to be controlled by the ListItem. The value is negative to account for the ListItem's padding marginInline: 'var(--List-itemButton-marginInline)', marginBlock: 'var(--List-itemButton-marginBlock)', @@ -64,7 +65,7 @@ export const StyledListItemButton = styled('div')<{ ownerState: ListItemButtonOw marginBlockStart: ownerState.row ? undefined : 'var(--List-gap)', }), // account for the border width, so that all of the ListItemButtons content aligned horizontally - paddingBlock: 'calc(var(--List-item-paddingY) - var(--variant-borderWidth))', + paddingBlock: 'calc(var(--List-item-paddingY) - var(--variant-borderWidth, 0px))', // account for the border width, so that all of the ListItemButtons content aligned vertically paddingInlineStart: 'calc(var(--List-item-paddingLeft) + var(--List-item-startActionWidth, var(--internal-startActionWidth, 0px)))', // --internal variable makes it possible to customize the actionWidth from the top List diff --git a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx index 65db88c613face..078b57a1d372e2 100644 --- a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx +++ b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx @@ -38,7 +38,7 @@ const ModalDialogRoot = styled(SheetRoot, { // Divider integration '--Divider-inset': 'calc(-1 * var(--ModalDialog-padding))', '--ModalClose-radius': - 'max((var(--ModalDialog-radius) - var(--variant-borderWidth)) - var(--ModalClose-inset), min(var(--ModalClose-inset) / 2, (var(--ModalDialog-radius) - var(--variant-borderWidth)) / 2))', + 'max((var(--ModalDialog-radius) - var(--variant-borderWidth, 0px)) - var(--ModalClose-inset), min(var(--ModalClose-inset) / 2, (var(--ModalDialog-radius) - var(--variant-borderWidth, 0px)) / 2))', ...(ownerState.size === 'sm' && { '--ModalDialog-padding': theme.spacing(1.25), '--ModalDialog-radius': theme.vars.radius.sm, diff --git a/packages/mui-joy/src/Select/Select.tsx b/packages/mui-joy/src/Select/Select.tsx index a4049ab8b80bd5..f0537570ec656d 100644 --- a/packages/mui-joy/src/Select/Select.tsx +++ b/packages/mui-joy/src/Select/Select.tsx @@ -108,9 +108,9 @@ const SelectRoot = styled('div', { }), // variables for controlling child components '--Select-decorator-childOffset': - 'min(calc(var(--Select-paddingInline) - (var(--Select-minHeight) - 2 * var(--variant-borderWidth) - var(--Select-decorator-childHeight)) / 2), var(--Select-paddingInline))', + 'min(calc(var(--Select-paddingInline) - (var(--Select-minHeight) - 2 * var(--variant-borderWidth, 0px) - var(--Select-decorator-childHeight)) / 2), var(--Select-paddingInline))', '--_Select-paddingBlock': - 'max((var(--Select-minHeight) - 2 * var(--variant-borderWidth) - var(--Select-decorator-childHeight)) / 2, 0px)', + 'max((var(--Select-minHeight) - 2 * var(--variant-borderWidth, 0px) - var(--Select-decorator-childHeight)) / 2, 0px)', '--Select-decorator-childRadius': 'max(var(--Select-radius) - var(--_Select-paddingBlock), min(var(--_Select-paddingBlock) / 2, var(--Select-radius) / 2))', '--Button-minHeight': 'var(--Select-decorator-childHeight)', @@ -124,6 +124,7 @@ const SelectRoot = styled('div', { display: 'flex', alignItems: 'center', borderRadius: 'var(--Select-radius)', + cursor: 'pointer', ...(!variantStyle.backgroundColor && { backgroundColor: theme.vars.palette.background.surface, }), @@ -151,7 +152,7 @@ const SelectRoot = styled('div', { bottom: 0, zIndex: 1, borderRadius: 'inherit', - margin: 'calc(var(--variant-borderWidth) * -1)', // for outlined variant + margin: 'calc(var(--variant-borderWidth, 0px) * -1)', // for outlined variant }, [`&.${selectClasses.focusVisible}`]: { '--Select-indicator-color': 'var(--Select-focusedHighlight)', diff --git a/packages/mui-joy/src/Slider/Slider.tsx b/packages/mui-joy/src/Slider/Slider.tsx index 58806d92f68ac3..5b1647aa98a7a4 100644 --- a/packages/mui-joy/src/Slider/Slider.tsx +++ b/packages/mui-joy/src/Slider/Slider.tsx @@ -156,7 +156,7 @@ const SliderRail = styled('span', { : 'var(--Slider-rail-background)', border: ownerState.track === 'inverted' - ? 'var(--variant-borderWidth) solid var(--Slider-track-borderColor)' + ? 'var(--variant-borderWidth, 0px) solid var(--Slider-track-borderColor)' : 'initial', borderRadius: 'var(--Slider-track-radius)', ...(ownerState.orientation === 'horizontal' && { @@ -192,7 +192,7 @@ const SliderTrack = styled('span', { border: ownerState.track === 'inverted' ? 'initial' - : 'var(--variant-borderWidth) solid var(--Slider-track-borderColor)', + : 'var(--variant-borderWidth, 0px) solid var(--Slider-track-borderColor)', backgroundColor: ownerState.track === 'inverted' ? 'var(--Slider-rail-background)' @@ -232,7 +232,7 @@ const SliderThumb = styled('span', { justifyContent: 'center', width: 'var(--Slider-thumb-width)', height: 'var(--Slider-thumb-size)', - border: 'var(--variant-borderWidth) solid var(--Slider-track-borderColor)', + border: 'var(--variant-borderWidth, 0px) solid var(--Slider-track-borderColor)', borderRadius: 'var(--Slider-thumb-radius)', boxShadow: 'var(--Slider-thumb-shadow)', color: 'var(--Slider-thumb-color)', diff --git a/packages/mui-joy/src/Switch/Switch.tsx b/packages/mui-joy/src/Switch/Switch.tsx index 0b876244441051..23900cbec6adba 100644 --- a/packages/mui-joy/src/Switch/Switch.tsx +++ b/packages/mui-joy/src/Switch/Switch.tsx @@ -84,7 +84,7 @@ const SwitchRoot = styled('div', { '--Switch-thumb-size': '24px', '--Switch-gap': '12px', }), - '--internal-paddingBlock': `max((var(--Switch-track-height) - 2 * var(--variant-borderWidth) - var(--Switch-thumb-size)) / 2, 0px)`, + '--internal-paddingBlock': `max((var(--Switch-track-height) - 2 * var(--variant-borderWidth, 0px) - var(--Switch-thumb-size)) / 2, 0px)`, '--Switch-thumb-radius': `max(var(--Switch-track-radius) - var(--internal-paddingBlock), min(var(--internal-paddingBlock) / 2, var(--Switch-track-radius) / 2))`, '--Switch-thumb-width': 'var(--Switch-thumb-size)', '--Switch-thumb-offset': `max((var(--Switch-track-height) - var(--Switch-thumb-size)) / 2, 0px)`, @@ -156,7 +156,7 @@ const SwitchTrack = styled('span', { justifyContent: 'space-between', alignItems: 'center', boxSizing: 'border-box', - border: 'var(--variant-borderWidth) solid', + border: 'var(--variant-borderWidth, 0px) solid', borderColor: 'var(--Switch-track-borderColor)', backgroundColor: 'var(--Switch-track-background)', borderRadius: 'var(--Switch-track-radius)', diff --git a/packages/mui-joy/src/Textarea/Textarea.tsx b/packages/mui-joy/src/Textarea/Textarea.tsx index 65ffdfccabf76b..4f8f37ce00e619 100644 --- a/packages/mui-joy/src/Textarea/Textarea.tsx +++ b/packages/mui-joy/src/Textarea/Textarea.tsx @@ -77,7 +77,7 @@ const TextareaRoot = styled('div', { }), // variables for controlling child components '--_Textarea-paddingBlock': - 'max((var(--Textarea-minHeight) - 2 * var(--variant-borderWidth) - var(--Textarea-decorator-childHeight)) / 2, 0px)', + 'max((var(--Textarea-minHeight) - 2 * var(--variant-borderWidth, 0px) - var(--Textarea-decorator-childHeight)) / 2, 0px)', '--Textarea-decorator-childRadius': 'max(var(--Textarea-radius) - var(--_Textarea-paddingBlock), min(var(--_Textarea-paddingBlock) / 2, var(--Textarea-radius) / 2))', '--Button-minHeight': 'var(--Textarea-decorator-childHeight)', @@ -116,7 +116,7 @@ const TextareaRoot = styled('div', { bottom: 0, zIndex: 1, borderRadius: 'inherit', - margin: 'calc(var(--variant-borderWidth) * -1)', // for outlined variant + margin: 'calc(var(--variant-borderWidth, 0px) * -1)', // for outlined variant }, }, { diff --git a/packages/mui-joy/src/Tooltip/Tooltip.tsx b/packages/mui-joy/src/Tooltip/Tooltip.tsx index ed56ebaa61d076..09602baef96f43 100644 --- a/packages/mui-joy/src/Tooltip/Tooltip.tsx +++ b/packages/mui-joy/src/Tooltip/Tooltip.tsx @@ -147,7 +147,7 @@ const TooltipArrow = styled('span', { borderTopColor: variantStyle?.backgroundColor ?? theme.vars.palette.background.surface, borderRightColor: variantStyle?.backgroundColor ?? theme.vars.palette.background.surface, borderRadius: `0px 2px 0px 0px`, - boxShadow: `var(--variant-borderWidth) calc(-1 * var(--variant-borderWidth)) 0px 0px ${variantStyle.borderColor}`, + boxShadow: `var(--variant-borderWidth, 0px) calc(-1 * var(--variant-borderWidth, 0px)) 0px 0px ${variantStyle.borderColor}`, transformOrigin: 'center center', transform: 'rotate(calc(-45deg + 90deg * var(--unstable_Tooltip-arrow-rotation)))', }, diff --git a/packages/mui-joy/src/styles/extendTheme.test.js b/packages/mui-joy/src/styles/extendTheme.test.js index e72cf9f195005e..ede03ca6c5ca85 100644 --- a/packages/mui-joy/src/styles/extendTheme.test.js +++ b/packages/mui-joy/src/styles/extendTheme.test.js @@ -49,6 +49,15 @@ describe('extendTheme', () => { expect(theme.typography.body1.fontSize).to.equal('var(--fontSize-md)'); }); + it('should have custom --variant-borderWidth', () => { + const theme = extendTheme({ + variants: { outlined: { primary: { '--variant-borderWidth': '3px' } } }, + }); + expect(theme.variants.outlined.primary).to.contain({ + '--variant-borderWidth': '3px', + }); + }); + describe('theme.unstable_sx', () => { const { render } = createRenderer(); diff --git a/packages/mui-joy/src/styles/variantUtils.test.js b/packages/mui-joy/src/styles/variantUtils.test.js index 639f5fb3ff5432..268f378b7206b2 100644 --- a/packages/mui-joy/src/styles/variantUtils.test.js +++ b/packages/mui-joy/src/styles/variantUtils.test.js @@ -73,7 +73,6 @@ describe('variant utils', () => { anyHoverColor: 'var(--any-token)', }), ).to.deep.include({ - cursor: 'pointer', color: 'var(--any-token)', }); }); @@ -84,7 +83,6 @@ describe('variant utils', () => { anyHoverBg: 'var(--any-token)', }), ).to.deep.include({ - cursor: 'pointer', backgroundColor: 'var(--any-token)', }); }); @@ -95,7 +93,6 @@ describe('variant utils', () => { anyHoverBorder: 'var(--any-token)', }), ).to.deep.include({ - cursor: 'pointer', borderColor: 'var(--any-token)', }); }); @@ -229,7 +226,6 @@ describe('variant utils', () => { }, outlinedHover: { primary: { - cursor: 'pointer', color: 'var(--any-token)', borderColor: 'var(--any-token)', backgroundColor: 'var(--any-token)', @@ -280,7 +276,6 @@ describe('variant utils', () => { color: 'var(--joy-variant-plainColor)', }); expect(createVariantStyle('plainHover', vars)).to.deep.include({ - cursor: 'pointer', color: 'var(--joy-variant-plainHoverColor, var(--joy-variant-plainColor))', backgroundColor: 'var(--joy-variant-plainHoverBg)', }); @@ -329,7 +324,6 @@ describe('variant utils', () => { const softHoverResult = createVariant('softHover', theme); expect(softHoverResult.customColor).to.deep.include({ - cursor: 'pointer', color: 'var(--joy-palette-customColor-softHoverColor)', }); }); diff --git a/packages/mui-joy/src/styles/variantUtils.ts b/packages/mui-joy/src/styles/variantUtils.ts index 9438e19c59a79c..0ec88c162318a9 100644 --- a/packages/mui-joy/src/styles/variantUtils.ts +++ b/packages/mui-joy/src/styles/variantUtils.ts @@ -64,9 +64,6 @@ export const createVariantStyle = ( ([variantVar, value]) => { if (variantVar.match(new RegExp(`${name}(color|bg|border)`, 'i')) && !!value) { const cssVar = getCssVar ? getCssVar(variantVar) : value; - if (variantVar.includes('Hover')) { - result.cursor = 'pointer'; - } if (variantVar.includes('Disabled')) { result.pointerEvents = 'none'; result.cursor = 'default'; @@ -76,6 +73,9 @@ export const createVariantStyle = ( } else { // initial state if (!result['--variant-borderWidth']) { + // important to prevent inheritance, otherwise the children will have the wrong styles e.g. + // + // result['--variant-borderWidth'] = '0px'; } if (variantVar.includes('Border')) { From defd28e6dc56b136e7af38331c129a3d15006f43 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Thu, 22 Dec 2022 14:43:50 +0000 Subject: [PATCH 51/88] [core] Remove oudated pickers prop-type logic (#35571) --- scripts/generateProptypes.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/scripts/generateProptypes.ts b/scripts/generateProptypes.ts index 3f06821d90d4f6..2c625c564b8274 100644 --- a/scripts/generateProptypes.ts +++ b/scripts/generateProptypes.ts @@ -50,22 +50,6 @@ const useExternalDocumentation: Record = { // In DialogContentText we pass it through // Therefore it's considered "unused" in the actual component but we still want to document it. DialogContentText: ['classes'], - DatePicker: '*', - MobileDatePicker: '*', - StaticDatePicker: '*', - DesktopDatePicker: '*', - TimePicker: '*', - MobileTimePicker: '*', - StaticTimePicker: '*', - DesktopTimePicker: '*', - DateTimePicker: '*', - MobileDateTimePicker: '*', - StaticDateTimePicker: '*', - DesktopDateTimePicker: '*', - DateRangePicker: '*', - MobileDateRangePicker: '*', - StaticDateRangePicker: '*', - DesktopDateRangePicker: '*', FilledInput: useExternalPropsFromInputBase, IconButton: ['disableRipple'], Input: useExternalPropsFromInputBase, From 5b423bdd4f5a65c8ed1d8a4f9829ff428030f51a Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Thu, 22 Dec 2022 19:17:07 +0000 Subject: [PATCH 52/88] [core] Cleanup mention of test-utils (#35577) --- .../minimizing-bundle-size/minimizing-bundle-size-pt.md | 2 +- .../minimizing-bundle-size/minimizing-bundle-size-zh.md | 2 +- .../guides/minimizing-bundle-size/minimizing-bundle-size.md | 2 +- packages/eslint-plugin-material-ui/README.md | 4 ++-- packages/mui-lab/tsconfig.build.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size-pt.md b/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size-pt.md index e7f0aeb5c535ad..5e30e68f7368ff 100644 --- a/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size-pt.md +++ b/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size-pt.md @@ -78,7 +78,7 @@ If you're using `eslint` you can catch problematic imports with the [`no-restric "no-restricted-imports": [ "error", { - "patterns": ["@mui/*/*/*", "!@mui/material/test-utils/*"] + "patterns": ["@mui/*/*/*"] } ] } diff --git a/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size-zh.md b/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size-zh.md index ce04dfb7c42a4b..974981f086463b 100644 --- a/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size-zh.md +++ b/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size-zh.md @@ -78,7 +78,7 @@ If you're using `eslint` you can catch problematic imports with the [`no-restric "no-restricted-imports": [ "error", { - "patterns": ["@mui/*/*/*", "!@mui/material/test-utils/*"] + "patterns": ["@mui/*/*/*"] } ] } diff --git a/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size.md b/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size.md index 03d4e5e19010db..25a65465312370 100644 --- a/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size.md +++ b/docs/data/material/guides/minimizing-bundle-size/minimizing-bundle-size.md @@ -90,7 +90,7 @@ If you're using `eslint` you can catch problematic imports with the [`no-restric "no-restricted-imports": [ "error", { - "patterns": ["@mui/*/*/*", "!@mui/material/test-utils/*"] + "patterns": ["@mui/*/*/*"] } ] } diff --git a/packages/eslint-plugin-material-ui/README.md b/packages/eslint-plugin-material-ui/README.md index 8670c5cbced99b..8ee4945295b0a3 100644 --- a/packages/eslint-plugin-material-ui/README.md +++ b/packages/eslint-plugin-material-ui/README.md @@ -23,7 +23,7 @@ Enforce correct usage of `@ignore` in the prop-types block comments. ### no-hardcoded-labels Prevent the usage of hardcoded labels. -The docs are translated via crowdin, we prefer to use `t` from the redux store. +The docs are translated via Crowdin, we prefer to use `t` from the redux store. ### rules-of-use-theme-variants @@ -40,7 +40,7 @@ Removed in favor of [`no-restricted-imports`](https://eslint.org/docs/latest/rul "no-restricted-imports": [ "error", { - "patterns": ["@mui/*/*/*", "!@mui/material/test-utils/*"] + "patterns": ["@mui/*/*/*"] } ] } diff --git a/packages/mui-lab/tsconfig.build.json b/packages/mui-lab/tsconfig.build.json index 01364fe7009e03..f3c9573d726cf3 100644 --- a/packages/mui-lab/tsconfig.build.json +++ b/packages/mui-lab/tsconfig.build.json @@ -10,7 +10,7 @@ "emitDeclarationOnly": true }, "include": ["src/**/*.ts*"], - "exclude": ["src/**/*.d.ts", "src/**/*.test.*", "./**/*.spec.*", "**/test-utils.tsx"], + "exclude": ["src/**/*.d.ts", "src/**/*.test.*", "./**/*.spec.*"], "references": [ { "path": "../mui-material/tsconfig.build.json" }, { "path": "../mui-system/tsconfig.build.json" } From 3352b2cf60fa608bf86ddc02a78099044606d9e9 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Thu, 22 Dec 2022 19:45:06 +0000 Subject: [PATCH 53/88] [core] Fix a few title case (#35547) --- docs/data/base/pages.ts | 2 +- .../dark-mode-optimization.md | 2 +- .../perfect-dark-mode-pt.md | 43 ------------------- .../perfect-dark-mode-zh.md | 43 ------------------- docs/data/joy/pages.ts | 6 +-- .../components/tree-view/tree-view.md | 4 +- docs/data/material/pages.ts | 4 +- docs/pages/material-ui/api/tree-item.json | 2 +- docs/pages/material-ui/api/tree-view.json | 2 +- docs/src/pages.ts | 2 +- docs/translations/translations.json | 6 +-- packages/mui-lab/src/TreeItem/TreeItem.d.ts | 2 +- packages/mui-lab/src/TreeView/TreeView.d.ts | 2 +- 13 files changed, 17 insertions(+), 103 deletions(-) delete mode 100644 docs/data/joy/main-features/dark-mode-optimization/perfect-dark-mode-pt.md delete mode 100644 docs/data/joy/main-features/dark-mode-optimization/perfect-dark-mode-zh.md diff --git a/docs/data/base/pages.ts b/docs/data/base/pages.ts index f22c7bef79e113..9234b464b69437 100644 --- a/docs/data/base/pages.ts +++ b/docs/data/base/pages.ts @@ -80,7 +80,7 @@ const pages = [ }, { pathname: '/base/guides', - title: 'How To Guides', + title: 'How-to guides', icon: 'VisibilityIcon', children: [ { diff --git a/docs/data/joy/main-features/dark-mode-optimization/dark-mode-optimization.md b/docs/data/joy/main-features/dark-mode-optimization/dark-mode-optimization.md index 81e8b0644b97dd..15164b131faeae 100644 --- a/docs/data/joy/main-features/dark-mode-optimization/dark-mode-optimization.md +++ b/docs/data/joy/main-features/dark-mode-optimization/dark-mode-optimization.md @@ -27,7 +27,7 @@ Solving this problem required us to take a novel approach to styling and theming Thanks to Joy UI's built-in support for CSS variables, your app can render all of its color schemes at build time, so that the user's preference can be injected _before_ the DOM is rendered in the browser. -Joy UI provides the `getInitColorSchemeScript()` function to make this flash-free dark mode possible with React frameworks like Next.js, Gatsby, and Remix. +Joy UI provides the `getInitColorSchemeScript()` function to make this flash-free dark mode possible with React frameworks like Next.js, Remix, and Gatsby. This function must be placed before the main script so it can apply the correct stylesheet before your components are rendered. The code snippet below shows how this works with Next.js—see the [Applying dark mode](/joy-ui/customization/dark-mode/) page for more details on usage with other frameworks: diff --git a/docs/data/joy/main-features/dark-mode-optimization/perfect-dark-mode-pt.md b/docs/data/joy/main-features/dark-mode-optimization/perfect-dark-mode-pt.md deleted file mode 100644 index b6be5097217c44..00000000000000 --- a/docs/data/joy/main-features/dark-mode-optimization/perfect-dark-mode-pt.md +++ /dev/null @@ -1,43 +0,0 @@ -# Perfect dark mode - -

Joy UI's solution for perfect dark mode on server-side rendering.

- -## The current flickering problem - -In apps using SSR (server-side rendering) and SSG (static site generation-e.g. Jamstack), switching to dark mode and then refreshing the page will initially load the light mode to only then, after hydration, go back to the dark mode. - -This not only causes eye-fatigue to users that are in low-light settings as it also interrupts the browsing experience for those who interact with the website in the in-between of modes changing. - -Today's dark mode flickering in MUI's website. - -The above recording is taken from [MUI's website](https://mui.com/) when the page is hard refreshed. The root cause of this issue usually comes from the JavaScript runtime calculation to switch the stylesheet between light and dark modes. - -## The solution: CSS variables - -Ultimately, to solve this problem, we needed to think of a different styling and theming approach altogether. Joy UI comes with CSS variables support out-of-the-box which allows every color schemes to be rendered at build time, given we want to set the selected mode before the browser renders the DOM. - -Joy UI provides the `getInitColorSchemeScript()` function that enables you to integrate with various React frameworks, such as Next.js, Gatsby, and Remix. This function must be placed before the main script so it can apply the correct stylesheet before your components are rendered. - -```js -// Next.js example -import Document, { Html, Head, Main, NextScript } from 'next/document'; -import { getInitColorSchemeScript } from '@mui/joy/styles'; - -export default class MyDocument extends Document { - render() { - return ( - - ... - - {getInitColorSchemeScript()} -
- - - - ); - } -} -``` - -- Learn [how to apply dark mode](/joy-ui/customization/dark-mode/) in various frameworks by visiting the How To Guides. -- Check out our [RFC on CSS variables support](https://github.com/mui/material-ui/issues/27651) to get the full picture of its implementation in Joy UI. diff --git a/docs/data/joy/main-features/dark-mode-optimization/perfect-dark-mode-zh.md b/docs/data/joy/main-features/dark-mode-optimization/perfect-dark-mode-zh.md deleted file mode 100644 index b6be5097217c44..00000000000000 --- a/docs/data/joy/main-features/dark-mode-optimization/perfect-dark-mode-zh.md +++ /dev/null @@ -1,43 +0,0 @@ -# Perfect dark mode - -

Joy UI's solution for perfect dark mode on server-side rendering.

- -## The current flickering problem - -In apps using SSR (server-side rendering) and SSG (static site generation-e.g. Jamstack), switching to dark mode and then refreshing the page will initially load the light mode to only then, after hydration, go back to the dark mode. - -This not only causes eye-fatigue to users that are in low-light settings as it also interrupts the browsing experience for those who interact with the website in the in-between of modes changing. - -Today's dark mode flickering in MUI's website. - -The above recording is taken from [MUI's website](https://mui.com/) when the page is hard refreshed. The root cause of this issue usually comes from the JavaScript runtime calculation to switch the stylesheet between light and dark modes. - -## The solution: CSS variables - -Ultimately, to solve this problem, we needed to think of a different styling and theming approach altogether. Joy UI comes with CSS variables support out-of-the-box which allows every color schemes to be rendered at build time, given we want to set the selected mode before the browser renders the DOM. - -Joy UI provides the `getInitColorSchemeScript()` function that enables you to integrate with various React frameworks, such as Next.js, Gatsby, and Remix. This function must be placed before the main script so it can apply the correct stylesheet before your components are rendered. - -```js -// Next.js example -import Document, { Html, Head, Main, NextScript } from 'next/document'; -import { getInitColorSchemeScript } from '@mui/joy/styles'; - -export default class MyDocument extends Document { - render() { - return ( - - ... - - {getInitColorSchemeScript()} -
- - - - ); - } -} -``` - -- Learn [how to apply dark mode](/joy-ui/customization/dark-mode/) in various frameworks by visiting the How To Guides. -- Check out our [RFC on CSS variables support](https://github.com/mui/material-ui/issues/27651) to get the full picture of its implementation in Joy UI. diff --git a/docs/data/joy/pages.ts b/docs/data/joy/pages.ts index 7dee416d1f5d33..6376203aa10d79 100644 --- a/docs/data/joy/pages.ts +++ b/docs/data/joy/pages.ts @@ -59,8 +59,8 @@ const pages = [ subheader: 'feedback', children: [ { pathname: '/joy-ui/react-alert' }, - { pathname: '/joy-ui/react-circular-progress' }, - { pathname: '/joy-ui/react-linear-progress' }, + { pathname: '/joy-ui/react-circular-progress', title: 'Circular Progress' }, + { pathname: '/joy-ui/react-linear-progress', title: 'Linear Progress' }, { pathname: '/joy-ui/react-modal' }, ], }, @@ -101,7 +101,7 @@ const pages = [ }, { pathname: '/joy-ui/guides', - title: 'How To Guides', + title: 'How-to guides', icon: 'VisibilityIcon', children: [ { diff --git a/docs/data/material/components/tree-view/tree-view.md b/docs/data/material/components/tree-view/tree-view.md index bc5449407106bb..331160d41b354a 100644 --- a/docs/data/material/components/tree-view/tree-view.md +++ b/docs/data/material/components/tree-view/tree-view.md @@ -1,13 +1,13 @@ --- product: material-ui -title: Tree view React component +title: Tree View React component components: TreeView, TreeItem githubLabel: 'component: tree view' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ packageName: '@mui/lab' --- -# Tree view +# Tree View

A tree view widget presents a hierarchical list.

diff --git a/docs/data/material/pages.ts b/docs/data/material/pages.ts index f716a52483aac6..833fe51329c5a4 100644 --- a/docs/data/material/pages.ts +++ b/docs/data/material/pages.ts @@ -149,7 +149,7 @@ const pages: MuiPage[] = [ { pathname: '/material-ui/about-the-lab', title: 'About the lab 🧪' }, { pathname: '/material-ui/react-masonry' }, { pathname: '/material-ui/react-timeline' }, - { pathname: '/material-ui/react-tree-view' }, + { pathname: '/material-ui/react-tree-view', title: 'Tree View' }, ], }, ], @@ -187,7 +187,7 @@ const pages: MuiPage[] = [ }, { pathname: '/material-ui/guides', - title: 'How To Guides', + title: 'How-to guides', icon: 'VisibilityIcon', children: [ { pathname: '/material-ui/guides/api', title: 'API design approach' }, diff --git a/docs/pages/material-ui/api/tree-item.json b/docs/pages/material-ui/api/tree-item.json index 9288c4f8d5b305..d12c469900ef6a 100644 --- a/docs/pages/material-ui/api/tree-item.json +++ b/docs/pages/material-ui/api/tree-item.json @@ -49,6 +49,6 @@ "forwardsRefTo": "HTMLLIElement", "filename": "/packages/mui-lab/src/TreeItem/TreeItem.js", "inheritance": null, - "demos": "", + "demos": "", "cssComponent": false } diff --git a/docs/pages/material-ui/api/tree-view.json b/docs/pages/material-ui/api/tree-view.json index db718b6828da00..44c75e7da0cdce 100644 --- a/docs/pages/material-ui/api/tree-view.json +++ b/docs/pages/material-ui/api/tree-view.json @@ -38,6 +38,6 @@ "forwardsRefTo": "HTMLUListElement", "filename": "/packages/mui-lab/src/TreeView/TreeView.js", "inheritance": null, - "demos": "", + "demos": "", "cssComponent": false } diff --git a/docs/src/pages.ts b/docs/src/pages.ts index d773ed935c4c44..d852746a10f378 100644 --- a/docs/src/pages.ts +++ b/docs/src/pages.ts @@ -256,7 +256,7 @@ const pages: readonly MuiPage[] = [ }, { pathname: '/guides', - title: 'How To Guides', + title: 'How-to guides', icon: 'VisibilityIcon', children: [ { pathname: '/guides/api', title: 'API design approach' }, diff --git a/docs/translations/translations.json b/docs/translations/translations.json index 9c4f72bd77bb7a..1754dba3160af3 100644 --- a/docs/translations/translations.json +++ b/docs/translations/translations.json @@ -227,7 +227,7 @@ "/base/react-popper": "Popper", "/base/react-portal": "Portal", "/base/react-textarea-autosize": "Textarea Autosize", - "/base/guides": "How To Guides", + "/base/guides": "How-to guides", "/base/guides/working-with-tailwind-css": "Working with Tailwind CSS", "/material-ui/getting-started": "Getting started", "/material-ui/getting-started/overview": "Overview", @@ -308,7 +308,7 @@ "/material-ui/about-the-lab": "About the lab 🧪", "/material-ui/react-masonry": "Masonry", "/material-ui/react-timeline": "Timeline", - "/material-ui/react-tree-view": "Tree view", + "/material-ui/react-tree-view": "Tree View", "/material-ui/customization": "Customization", "/material-ui/customization/theme": "Theme", "/material-ui/customization/theming": "Theming", @@ -324,7 +324,7 @@ "/material-ui/customization/default-theme": "Default theme", "/material-ui/customization/how-to-customize": "How to customize", "/material-ui/customization/color": "Color", - "/material-ui/guides": "How To Guides", + "/material-ui/guides": "How-to guides", "/material-ui/guides/understand-mui-packages": "Understand MUI packages", "/material-ui/guides/typescript": "TypeScript", "/material-ui/guides/interoperability": "Style library interoperability", diff --git a/packages/mui-lab/src/TreeItem/TreeItem.d.ts b/packages/mui-lab/src/TreeItem/TreeItem.d.ts index 41e13fe3bb82e1..90776a253eff74 100644 --- a/packages/mui-lab/src/TreeItem/TreeItem.d.ts +++ b/packages/mui-lab/src/TreeItem/TreeItem.d.ts @@ -78,7 +78,7 @@ export interface TreeItemProps * * Demos: * - * - [Tree view](https://mui.com/material-ui/react-tree-view/) + * - [Tree View](https://mui.com/material-ui/react-tree-view/) * * API: * diff --git a/packages/mui-lab/src/TreeView/TreeView.d.ts b/packages/mui-lab/src/TreeView/TreeView.d.ts index 61dc4a42249063..2c7c39d8586b72 100644 --- a/packages/mui-lab/src/TreeView/TreeView.d.ts +++ b/packages/mui-lab/src/TreeView/TreeView.d.ts @@ -135,7 +135,7 @@ export type TreeViewProps = SingleSelectTreeViewProps | MultiSelectTreeViewProps * * Demos: * - * - [Tree view](https://mui.com/material-ui/react-tree-view/) + * - [Tree View](https://mui.com/material-ui/react-tree-view/) * * API: * From 727e06e062fb6cf8c537c189b614d73485e6f11d Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Thu, 22 Dec 2022 19:45:49 +0000 Subject: [PATCH 54/88] [core] Fix API demos callout spacing (#35579) --- docs/src/modules/components/ApiPage.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/src/modules/components/ApiPage.js b/docs/src/modules/components/ApiPage.js index 112aab9478ab1d..2584d74f46e62b 100644 --- a/docs/src/modules/components/ApiPage.js +++ b/docs/src/modules/components/ApiPage.js @@ -306,15 +306,13 @@ export default function ApiPage(props) { {disableAd ? null : } -
-

- -

+
For examples and details on the usage of this React component, visit the component demo pages:

+ ${demos}`, + }} + /> Date: Thu, 22 Dec 2022 16:08:01 -0500 Subject: [PATCH 55/88] [docs][Autocomplete] Fix GoogleMaps demo (#35545) --- .../components/autocomplete/GoogleMaps.js | 29 +++++++++-------- .../components/autocomplete/GoogleMaps.tsx | 31 +++++++++---------- .../autocomplete/autocomplete-pt.md | 2 +- .../autocomplete/autocomplete-zh.md | 2 +- .../components/autocomplete/autocomplete.md | 2 +- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/docs/data/material/components/autocomplete/GoogleMaps.js b/docs/data/material/components/autocomplete/GoogleMaps.js index 4d9029a1e77385..f310f265998076 100644 --- a/docs/data/material/components/autocomplete/GoogleMaps.js +++ b/docs/data/material/components/autocomplete/GoogleMaps.js @@ -6,7 +6,7 @@ import LocationOnIcon from '@mui/icons-material/LocationOn'; import Grid from '@mui/material/Grid'; import Typography from '@mui/material/Typography'; import parse from 'autosuggest-highlight/parse'; -import throttle from 'lodash/throttle'; +import { debounce } from '@mui/material/utils'; // This key was created specifically for the demo in mui.com. // You need to create a new one for your application. @@ -46,9 +46,9 @@ export default function GoogleMaps() { const fetch = React.useMemo( () => - throttle((request, callback) => { + debounce((request, callback) => { autocompleteService.current.getPlacePredictions(request, callback); - }, 200), + }, 400), [], ); @@ -102,6 +102,7 @@ export default function GoogleMaps() { includeInputInList filterSelectedOptions value={value} + noOptionsText="No locations" onChange={(event, newValue) => { setOptions(newValue ? [newValue, ...options] : options); setValue(newValue); @@ -113,7 +114,9 @@ export default function GoogleMaps() { )} renderOption={(props, option) => { - const matches = option.structured_formatting.main_text_matched_substrings; + const matches = + option.structured_formatting.main_text_matched_substrings || []; + const parts = parse( option.structured_formatting.main_text, matches.map((match) => [match.offset, match.offset + match.length]), @@ -122,22 +125,18 @@ export default function GoogleMaps() { return (
  • - - + + - + {parts.map((part, index) => ( - {part.text} - + ))} diff --git a/docs/data/material/components/autocomplete/GoogleMaps.tsx b/docs/data/material/components/autocomplete/GoogleMaps.tsx index 900033cb7fce18..269823b32bc4e8 100644 --- a/docs/data/material/components/autocomplete/GoogleMaps.tsx +++ b/docs/data/material/components/autocomplete/GoogleMaps.tsx @@ -6,7 +6,7 @@ import LocationOnIcon from '@mui/icons-material/LocationOn'; import Grid from '@mui/material/Grid'; import Typography from '@mui/material/Typography'; import parse from 'autosuggest-highlight/parse'; -import throttle from 'lodash/throttle'; +import { debounce } from '@mui/material/utils'; // This key was created specifically for the demo in mui.com. // You need to create a new one for your application. @@ -33,7 +33,7 @@ interface MainTextMatchedSubstrings { interface StructuredFormatting { main_text: string; secondary_text: string; - main_text_matched_substrings: readonly MainTextMatchedSubstrings[]; + main_text_matched_substrings?: readonly MainTextMatchedSubstrings[]; } interface PlaceType { description: string; @@ -60,7 +60,7 @@ export default function GoogleMaps() { const fetch = React.useMemo( () => - throttle( + debounce( ( request: { input: string }, callback: (results?: readonly PlaceType[]) => void, @@ -70,7 +70,7 @@ export default function GoogleMaps() { callback, ); }, - 200, + 400, ), [], ); @@ -126,6 +126,7 @@ export default function GoogleMaps() { includeInputInList filterSelectedOptions value={value} + noOptionsText="No locations" onChange={(event: any, newValue: PlaceType | null) => { setOptions(newValue ? [newValue, ...options] : options); setValue(newValue); @@ -137,7 +138,9 @@ export default function GoogleMaps() { )} renderOption={(props, option) => { - const matches = option.structured_formatting.main_text_matched_substrings; + const matches = + option.structured_formatting.main_text_matched_substrings || []; + const parts = parse( option.structured_formatting.main_text, matches.map((match: any) => [match.offset, match.offset + match.length]), @@ -146,22 +149,18 @@ export default function GoogleMaps() { return (
  • - - + + - + {parts.map((part, index) => ( - {part.text} - + ))} {option.structured_formatting.secondary_text} diff --git a/docs/data/material/components/autocomplete/autocomplete-pt.md b/docs/data/material/components/autocomplete/autocomplete-pt.md index 656c41b34efa10..7dab29002cf254 100644 --- a/docs/data/material/components/autocomplete/autocomplete-pt.md +++ b/docs/data/material/components/autocomplete/autocomplete-pt.md @@ -170,7 +170,7 @@ A customized UI for Google Maps Places Autocomplete. For this demo, we need to l {{"demo": "GoogleMaps.js"}} :::warning -⚠️ Before you can start using the Google Maps JavaScript API and Places API, you must sign up and create a billing account. +⚠️ Before you can start using the Google Maps JavaScript API and Places API, you need to get your own [API key](https://developers.google.com/maps/documentation/javascript/get-api-key). ::: ## Múltiplos valores diff --git a/docs/data/material/components/autocomplete/autocomplete-zh.md b/docs/data/material/components/autocomplete/autocomplete-zh.md index 845d649fcfb827..f1e0111fcda312 100644 --- a/docs/data/material/components/autocomplete/autocomplete-zh.md +++ b/docs/data/material/components/autocomplete/autocomplete-zh.md @@ -170,7 +170,7 @@ A customized UI for Google Maps Places Autocomplete. For this demo, we need to l {{"demo": "GoogleMaps.js"}} :::warning -⚠️ Before you can start using the Google Maps JavaScript API and Places API, you must sign up and create a billing account. +⚠️ Before you can start using the Google Maps JavaScript API and Places API, you need to get your own [API key](https://developers.google.com/maps/documentation/javascript/get-api-key). ::: ## 多个输入值 diff --git a/docs/data/material/components/autocomplete/autocomplete.md b/docs/data/material/components/autocomplete/autocomplete.md index 279591abd63210..0e102364be7224 100644 --- a/docs/data/material/components/autocomplete/autocomplete.md +++ b/docs/data/material/components/autocomplete/autocomplete.md @@ -188,7 +188,7 @@ For this demo, we need to load the [Google Maps JavaScript](https://developers.g {{"demo": "GoogleMaps.js"}} :::error -Before you can start using the Google Maps JavaScript API and Places API, you must sign up and create a billing account. +Before you can start using the Google Maps JavaScript API and Places API, you need to get your own [API key](https://developers.google.com/maps/documentation/javascript/get-api-key). ::: ## Multiple values From 68ef353897d97a26b6b4c7db567046ef3d2518fe Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Fri, 23 Dec 2022 12:00:21 +0700 Subject: [PATCH 56/88] [Joy] Apply color inversion to components (#34602) --- .../color-inversion/ColorInversionFooter.js | 226 ++++++++++++++++++ .../color-inversion/ColorInversionFooter.tsx | 225 +++++++++++++++++ .../color-inversion/ColorInversionHeader.js | 155 ++++++++++++ .../color-inversion/ColorInversionHeader.tsx | 154 ++++++++++++ .../ColorInversionMarketing.js | 89 +++++++ .../ColorInversionMarketing.tsx | 88 +++++++ .../ColorInversionMotivation.js | 61 +++++ .../ColorInversionMotivation.tsx | 60 +++++ .../ColorInversionNavigation.js | 197 +++++++++++++++ .../ColorInversionNavigation.tsx | 197 +++++++++++++++ .../color-inversion/ColorInversionOverview.js | 40 ++++ .../ColorInversionOverview.tsx | 40 ++++ .../color-inversion/ColorInversionPopup.js | 127 ++++++++++ .../color-inversion/ColorInversionPopup.tsx | 126 ++++++++++ .../color-inversion/color-inversion.md | 138 +++++++++++ docs/data/joy/pages.ts | 1 + .../experiments/joy/variant-overrides.tsx | 169 ------------- .../joy-ui/main-features/color-inversion.js | 7 + packages/mui-joy/src/Alert/Alert.test.js | 4 +- packages/mui-joy/src/Alert/Alert.tsx | 29 ++- packages/mui-joy/src/Alert/AlertProps.ts | 6 +- packages/mui-joy/src/Alert/alertClasses.ts | 3 + .../src/AspectRatio/AspectRatio.test.js | 7 +- .../mui-joy/src/AspectRatio/AspectRatio.tsx | 5 +- .../src/AspectRatio/AspectRatioProps.ts | 4 +- .../src/AspectRatio/aspectRatioClasses.ts | 3 + .../src/Autocomplete/Autocomplete.test.tsx | 7 + .../mui-joy/src/Autocomplete/Autocomplete.tsx | 79 +++--- .../src/Autocomplete/AutocompleteProps.ts | 4 +- .../src/Autocomplete/autocompleteClasses.ts | 3 + .../AutocompleteListbox.test.tsx | 7 +- .../AutocompleteListbox.tsx | 5 +- .../AutocompleteListboxProps.ts | 18 +- .../autocompleteListboxClasses.ts | 3 + .../AutocompleteOption.test.js | 4 +- .../AutocompleteOption/AutocompleteOption.tsx | 11 +- .../AutocompleteOptionProps.ts | 15 +- .../autocompleteOptionClasses.ts | 3 + packages/mui-joy/src/Avatar/Avatar.test.js | 9 +- packages/mui-joy/src/Avatar/Avatar.tsx | 4 +- packages/mui-joy/src/Avatar/AvatarProps.ts | 4 +- packages/mui-joy/src/Avatar/avatarClasses.ts | 3 + packages/mui-joy/src/Badge/Badge.test.js | 9 +- packages/mui-joy/src/Badge/Badge.tsx | 6 +- packages/mui-joy/src/Badge/BadgeProps.ts | 4 +- packages/mui-joy/src/Badge/badgeClasses.ts | 3 + packages/mui-joy/src/Button/Button.test.js | 4 +- packages/mui-joy/src/Button/Button.tsx | 5 +- packages/mui-joy/src/Button/ButtonProps.ts | 5 +- packages/mui-joy/src/Button/buttonClasses.ts | 3 + packages/mui-joy/src/Card/Card.test.js | 4 +- packages/mui-joy/src/Card/Card.tsx | 27 ++- packages/mui-joy/src/Card/CardProps.ts | 9 +- packages/mui-joy/src/Card/cardClasses.ts | 3 + .../src/CardOverflow/CardOverflow.test.js | 4 +- .../mui-joy/src/CardOverflow/CardOverflow.tsx | 15 +- .../src/CardOverflow/CardOverflowProps.ts | 4 +- .../src/CardOverflow/cardOverflowClasses.ts | 3 + .../mui-joy/src/Checkbox/Checkbox.test.js | 10 +- packages/mui-joy/src/Checkbox/Checkbox.tsx | 25 +- .../mui-joy/src/Checkbox/CheckboxProps.ts | 4 +- .../mui-joy/src/Checkbox/checkboxClasses.ts | 3 + packages/mui-joy/src/Chip/Chip.test.js | 9 +- packages/mui-joy/src/Chip/Chip.tsx | 11 +- packages/mui-joy/src/Chip/ChipProps.ts | 4 +- packages/mui-joy/src/Chip/chipClasses.ts | 3 + .../mui-joy/src/ChipDelete/ChipDelete.test.js | 10 +- .../mui-joy/src/ChipDelete/ChipDelete.tsx | 4 +- .../mui-joy/src/ChipDelete/ChipDeleteProps.ts | 4 +- .../src/ChipDelete/chipDeleteClasses.ts | 3 + .../CircularProgress.test.tsx | 4 +- .../src/CircularProgress/CircularProgress.tsx | 5 +- .../CircularProgress/CircularProgressProps.ts | 4 +- .../circularProgressClasses.ts | 3 + .../mui-joy/src/IconButton/IconButton.test.js | 4 +- .../mui-joy/src/IconButton/IconButton.tsx | 5 +- .../mui-joy/src/IconButton/IconButtonProps.ts | 4 +- .../src/IconButton/iconButtonClasses.ts | 3 + packages/mui-joy/src/Input/Input.test.js | 10 +- packages/mui-joy/src/Input/Input.tsx | 4 +- packages/mui-joy/src/Input/inputClasses.ts | 3 + .../LinearProgress/LinearProgress.test.tsx | 4 +- .../src/LinearProgress/LinearProgress.tsx | 5 +- .../src/LinearProgress/LinearProgressProps.ts | 4 +- .../LinearProgress/linearProgressClasses.ts | 3 + packages/mui-joy/src/Link/Link.test.js | 10 +- packages/mui-joy/src/Link/Link.tsx | 23 +- packages/mui-joy/src/Link/LinkProps.ts | 3 +- packages/mui-joy/src/Link/linkClasses.ts | 3 + packages/mui-joy/src/List/List.test.js | 4 +- packages/mui-joy/src/List/List.tsx | 5 +- packages/mui-joy/src/List/ListProps.ts | 4 +- packages/mui-joy/src/List/listClasses.ts | 3 + .../mui-joy/src/ListItem/ListItem.test.js | 4 +- packages/mui-joy/src/ListItem/ListItem.tsx | 13 +- .../mui-joy/src/ListItem/ListItemProps.ts | 4 +- .../mui-joy/src/ListItem/listItemClasses.ts | 3 + .../src/ListItemButton/ListItemButton.test.js | 10 +- .../src/ListItemButton/ListItemButton.tsx | 8 +- .../src/ListItemButton/ListItemButtonProps.ts | 4 +- .../src/ListSubheader/ListSubheader.test.tsx | 7 +- .../src/ListSubheader/ListSubheader.tsx | 12 +- .../src/ListSubheader/ListSubheaderProps.ts | 4 +- .../src/ListSubheader/listSubheaderClasses.ts | 3 + packages/mui-joy/src/Menu/Menu.test.js | 23 +- packages/mui-joy/src/Menu/Menu.tsx | 49 ++-- packages/mui-joy/src/Menu/MenuProps.ts | 4 +- packages/mui-joy/src/Menu/menuClasses.ts | 3 + .../mui-joy/src/MenuItem/MenuItem.test.js | 17 +- packages/mui-joy/src/MenuItem/MenuItem.tsx | 14 +- .../mui-joy/src/MenuItem/MenuItemProps.ts | 3 +- .../mui-joy/src/MenuItem/menuItemClasses.ts | 3 + .../mui-joy/src/MenuList/MenuList.test.js | 4 +- packages/mui-joy/src/MenuList/MenuList.tsx | 9 +- .../mui-joy/src/MenuList/MenuListProps.ts | 4 +- .../mui-joy/src/MenuList/menuListClasses.ts | 3 + packages/mui-joy/src/Modal/modalClasses.ts | 35 +-- .../src/ModalClose/ModalClose.test.tsx | 9 +- .../mui-joy/src/ModalClose/ModalClose.tsx | 10 +- .../mui-joy/src/ModalClose/ModalCloseProps.ts | 6 +- .../src/ModalClose/modalCloseClasses.ts | 3 + .../src/ModalDialog/ModalDialog.test.tsx | 4 +- .../mui-joy/src/ModalDialog/ModalDialog.tsx | 16 +- .../src/ModalDialog/ModalDialogProps.ts | 6 +- .../src/ModalDialog/modalDialogClasses.ts | 3 + packages/mui-joy/src/Option/Option.tsx | 20 +- packages/mui-joy/src/Option/OptionProps.ts | 6 +- packages/mui-joy/src/Radio/Radio.test.js | 10 +- packages/mui-joy/src/Radio/Radio.tsx | 18 +- packages/mui-joy/src/Radio/RadioProps.ts | 4 +- packages/mui-joy/src/Radio/radioClasses.ts | 3 + packages/mui-joy/src/Select/Select.test.tsx | 15 +- packages/mui-joy/src/Select/Select.tsx | 144 ++++++----- packages/mui-joy/src/Select/SelectProps.ts | 5 +- packages/mui-joy/src/Select/selectClasses.ts | 3 + packages/mui-joy/src/Sheet/Sheet.test.js | 4 +- packages/mui-joy/src/Sheet/Sheet.tsx | 6 +- packages/mui-joy/src/Sheet/SheetProps.ts | 4 +- packages/mui-joy/src/Sheet/sheetClasses.ts | 3 + packages/mui-joy/src/Slider/Slider.test.js | 4 +- packages/mui-joy/src/Slider/Slider.tsx | 6 +- packages/mui-joy/src/Slider/SliderProps.ts | 4 +- packages/mui-joy/src/Slider/sliderClasses.ts | 15 ++ packages/mui-joy/src/Switch/Switch.test.js | 11 +- packages/mui-joy/src/Switch/Switch.tsx | 24 +- packages/mui-joy/src/Switch/SwitchProps.ts | 4 +- packages/mui-joy/src/Switch/switchClasses.ts | 3 + packages/mui-joy/src/Tab/Tab.test.tsx | 8 +- packages/mui-joy/src/Tab/Tab.tsx | 9 +- packages/mui-joy/src/Tab/TabProps.ts | 4 +- packages/mui-joy/src/Tab/tabClasses.ts | 3 + packages/mui-joy/src/TabList/TabList.test.tsx | 8 +- packages/mui-joy/src/TabList/TabList.tsx | 5 +- packages/mui-joy/src/TabList/TabListProps.ts | 6 +- .../mui-joy/src/TabList/tabListClasses.ts | 3 + packages/mui-joy/src/Tabs/Tabs.test.tsx | 4 +- packages/mui-joy/src/Tabs/Tabs.tsx | 5 +- packages/mui-joy/src/Tabs/TabsProps.ts | 4 +- packages/mui-joy/src/Tabs/tabsClasses.ts | 3 + packages/mui-joy/src/TextField/TextField.tsx | 2 + .../mui-joy/src/Textarea/Textarea.test.tsx | 10 +- packages/mui-joy/src/Textarea/Textarea.tsx | 4 +- .../mui-joy/src/Textarea/textareaClasses.ts | 3 + packages/mui-joy/src/Tooltip/Tooltip.test.js | 14 +- packages/mui-joy/src/Tooltip/Tooltip.tsx | 26 +- packages/mui-joy/src/Tooltip/TooltipProps.ts | 6 +- .../mui-joy/src/Tooltip/tooltipClasses.ts | 3 + .../mui-joy/src/Typography/Typography.test.js | 4 +- .../mui-joy/src/Typography/Typography.tsx | 22 +- .../mui-joy/src/Typography/TypographyProps.ts | 3 +- .../src/Typography/typographyClasses.ts | 3 + .../mui-joy/src/styles/ColorInversion.tsx | 17 +- .../src/styles/CssVarsProvider.test.tsx | 64 ++++- .../mui-joy/src/styles/CssVarsProvider.tsx | 20 +- packages/mui-joy/src/styles/ThemeProvider.tsx | 18 +- packages/mui-joy/src/styles/defaultTheme.ts | 52 +--- .../mui-joy/src/styles/extendTheme.spec.ts | 10 + packages/mui-joy/src/styles/extendTheme.ts | 6 +- packages/mui-joy/src/styles/types/theme.ts | 4 +- packages/mui-joy/src/styles/types/variants.ts | 2 +- packages/mui-joy/src/styles/variantUtils.ts | 26 +- packages/mui-joy/src/utils/useSlot.test.tsx | 104 +++++++- packages/mui-joy/src/utils/useSlot.ts | 35 ++- .../JoyColorInversion/JoyColorInversion.js | 169 +++++++++++++ .../JoyColorInversionPortal.js | 125 ++++++++++ test/utils/describeJoyColorInversion.tsx | 125 ++++++++++ test/utils/index.js | 1 + 187 files changed, 3553 insertions(+), 661 deletions(-) create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionFooter.js create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionFooter.tsx create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionHeader.js create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionHeader.tsx create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionMarketing.js create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionMarketing.tsx create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionMotivation.js create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionMotivation.tsx create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionNavigation.js create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionNavigation.tsx create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionOverview.js create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionOverview.tsx create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionPopup.js create mode 100644 docs/data/joy/main-features/color-inversion/ColorInversionPopup.tsx create mode 100644 docs/data/joy/main-features/color-inversion/color-inversion.md delete mode 100644 docs/pages/experiments/joy/variant-overrides.tsx create mode 100644 docs/pages/joy-ui/main-features/color-inversion.js create mode 100644 test/regressions/fixtures/JoyColorInversion/JoyColorInversion.js create mode 100644 test/regressions/fixtures/JoyColorInversion/JoyColorInversionPortal.js create mode 100644 test/utils/describeJoyColorInversion.tsx diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionFooter.js b/docs/data/joy/main-features/color-inversion/ColorInversionFooter.js new file mode 100644 index 00000000000000..c3bf3ed2a24486 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionFooter.js @@ -0,0 +1,226 @@ +import * as React from 'react'; + +import AspectRatio from '@mui/joy/AspectRatio'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Card from '@mui/joy/Card'; +import CardContent from '@mui/joy/CardContent'; +import Chip from '@mui/joy/Chip'; +import Divider from '@mui/joy/Divider'; +import Input from '@mui/joy/Input'; +import List from '@mui/joy/List'; +import ListSubheader from '@mui/joy/ListSubheader'; +import ListItem from '@mui/joy/ListItem'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import ListItemButton from '@mui/joy/ListItemButton'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import FacebookRoundedIcon from '@mui/icons-material/FacebookRounded'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import SendIcon from '@mui/icons-material/Send'; + +export default function ColorInversionFooter() { + const [color, setColor] = React.useState('neutral'); + return ( + + + { + const colors = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + sx={{ borderRadius: '50%' }} + > + + + + + + + + + + + + + } + sx={{ ml: 'auto', display: { xs: 'none', md: 'flex' } }} + /> + + + + + + + + + Intro to the MUI ecosystem + + MUI blog + + + + + + Sitemap + + + Services + + + Blog + + + Contact us + + + + + Product + + + + + + + MUI Core + + + + + + + + MUI X + + + + + + + + MUI Toolpad + + BETA + + + + + + + + + Design kits + + + + + + + + Templates + + + + + + + + + by} + > + MUI + + + + Copyright 2022 + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionFooter.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionFooter.tsx new file mode 100644 index 00000000000000..a975030b428dcc --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionFooter.tsx @@ -0,0 +1,225 @@ +import * as React from 'react'; +import { ColorPaletteProp } from '@mui/joy/styles'; +import AspectRatio from '@mui/joy/AspectRatio'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Card from '@mui/joy/Card'; +import CardContent from '@mui/joy/CardContent'; +import Chip from '@mui/joy/Chip'; +import Divider from '@mui/joy/Divider'; +import Input from '@mui/joy/Input'; +import List from '@mui/joy/List'; +import ListSubheader from '@mui/joy/ListSubheader'; +import ListItem from '@mui/joy/ListItem'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import ListItemButton from '@mui/joy/ListItemButton'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import FacebookRoundedIcon from '@mui/icons-material/FacebookRounded'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import SendIcon from '@mui/icons-material/Send'; + +export default function ColorInversionFooter() { + const [color, setColor] = React.useState('neutral'); + return ( + + + { + const colors: ColorPaletteProp[] = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + sx={{ borderRadius: '50%' }} + > + + + + + + + + + + + + + } + sx={{ ml: 'auto', display: { xs: 'none', md: 'flex' } }} + /> + + + + + + + + + Intro to the MUI ecosystem + + MUI blog + + + + + + Sitemap + + + Services + + + Blog + + + Contact us + + + + + Product + + + + + + + MUI Core + + + + + + + + MUI X + + + + + + + + MUI Toolpad + + BETA + + + + + + + + + Design kits + + + + + + + + Templates + + + + + + + + + by} + > + MUI + + + + Copyright 2022 + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionHeader.js b/docs/data/joy/main-features/color-inversion/ColorInversionHeader.js new file mode 100644 index 00000000000000..266be34b164ae5 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionHeader.js @@ -0,0 +1,155 @@ +import * as React from 'react'; + +import Avatar from '@mui/joy/Avatar'; +import Badge from '@mui/joy/Badge'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Input from '@mui/joy/Input'; +import IconButton from '@mui/joy/IconButton'; +import ListDivider from '@mui/joy/ListDivider'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Menu from '@mui/joy/Menu'; +import MenuItem from '@mui/joy/MenuItem'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import Chip from '@mui/joy/Chip'; +import AddIcon from '@mui/icons-material/Add'; +import BubbleChartIcon from '@mui/icons-material/BubbleChart'; +import NotificationsIcon from '@mui/icons-material/Notifications'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + +export default function ColorInversionHeader() { + const [anchorEl, setAnchorEl] = React.useState(null); + const [color, setColor] = React.useState('primary'); + return ( + + `linear-gradient(to top, ${theme.vars.palette[color][600]}, ${theme.vars.palette[color][500]})`, + }), + }} + > + { + const colors = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + sx={{ borderRadius: '50%' }} + > + + + + { + setAnchorEl(event.currentTarget); + }} + endDecorator={} + > + Main + + setAnchorEl(null)} + placement="bottom-start" + disablePortal + size="sm" + sx={{ + '--List-decorator-size': '24px', + '--List-item-minHeight': '40px', + '--List-divider-gap': '4px', + minWidth: 200, + }} + > + setAnchorEl(null)}> + + + + Products + + + setAnchorEl(null)}>Pricing + setAnchorEl(null)}> + Case studies{' '} + + `rgba(${theme.vars.palette.primary.mainChannel} / 0.1)`, + }} + > + Beta + + + + + + + + + ⌘K + + } + sx={{ + '--Input-radius': '40px', + '--Input-paddingInline': '12px', + width: 160, + display: { xs: 'none', lg: 'flex' }, + }} + /> + + + + + + + + + + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionHeader.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionHeader.tsx new file mode 100644 index 00000000000000..eaf29e3a352e01 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionHeader.tsx @@ -0,0 +1,154 @@ +import * as React from 'react'; +import { ColorPaletteProp } from '@mui/joy/styles'; +import Avatar from '@mui/joy/Avatar'; +import Badge from '@mui/joy/Badge'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Input from '@mui/joy/Input'; +import IconButton from '@mui/joy/IconButton'; +import ListDivider from '@mui/joy/ListDivider'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Menu from '@mui/joy/Menu'; +import MenuItem from '@mui/joy/MenuItem'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import Chip from '@mui/joy/Chip'; +import AddIcon from '@mui/icons-material/Add'; +import BubbleChartIcon from '@mui/icons-material/BubbleChart'; +import NotificationsIcon from '@mui/icons-material/Notifications'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + +export default function ColorInversionHeader() { + const [anchorEl, setAnchorEl] = React.useState(null); + const [color, setColor] = React.useState('primary'); + return ( + + `linear-gradient(to top, ${theme.vars.palette[color][600]}, ${theme.vars.palette[color][500]})`, + }), + }} + > + { + const colors: ColorPaletteProp[] = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + sx={{ borderRadius: '50%' }} + > + + + + { + setAnchorEl(event.currentTarget); + }} + endDecorator={} + > + Main + + setAnchorEl(null)} + placement="bottom-start" + disablePortal + size="sm" + sx={{ + '--List-decorator-size': '24px', + '--List-item-minHeight': '40px', + '--List-divider-gap': '4px', + minWidth: 200, + }} + > + setAnchorEl(null)}> + + + + Products + + + setAnchorEl(null)}>Pricing + setAnchorEl(null)}> + Case studies{' '} + + `rgba(${theme.vars.palette.primary.mainChannel} / 0.1)`, + }} + > + Beta + + + + + + + + + ⌘K + + } + sx={{ + '--Input-radius': '40px', + '--Input-paddingInline': '12px', + width: 160, + display: { xs: 'none', lg: 'flex' }, + }} + /> + + + + + + + + + + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.js b/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.js new file mode 100644 index 00000000000000..c5172e7306d9b5 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.js @@ -0,0 +1,89 @@ +import * as React from 'react'; + +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +/** + * Credit: https://flutter.dev/ + */ +export default function ColorInversionMarketing() { + const [color, setColor] = React.useState('primary'); + return ( + + + Get started + + Instant access to the power of the React UI library + + *': { flexGrow: 1, fontWeight: 'lg' }, + }} + > + + + + + + { + const colors = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + > + 🎨 + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.tsx new file mode 100644 index 00000000000000..64d83a0aba8851 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.tsx @@ -0,0 +1,88 @@ +import * as React from 'react'; +import { ColorPaletteProp } from '@mui/joy/styles'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +/** + * Credit: https://flutter.dev/ + */ +export default function ColorInversionMarketing() { + const [color, setColor] = React.useState('primary'); + return ( + + + Get started + + Instant access to the power of the React UI library + + *': { flexGrow: 1, fontWeight: 'lg' }, + }} + > + + + + + + { + const colors: ColorPaletteProp[] = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + > + 🎨 + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.js b/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.js new file mode 100644 index 00000000000000..bfd8891c137a8b --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.js @@ -0,0 +1,61 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import Chip from '@mui/joy/Chip'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +export default function ColorInversionMotivation() { + const demo = ( + + + New + + + + + + Learn how to build super fast websites. + + + + ); + + return ( + + {/* Left: The global variants are applied to children only */} + + {demo} + + One layer
    global variants are applied only to the children. +
    +
    + + {/* Right: The global variants are applied to both parent and children */} + + {React.cloneElement(demo, { + variant: 'solid', + color: 'primary', + })} + + Two layers +
    global variants are applied to the card and children. +
    +
    +
    + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.tsx new file mode 100644 index 00000000000000..85a879983c1fb5 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import Chip from '@mui/joy/Chip'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +export default function ColorInversionMotivation() { + const demo = ( + + + New + + + + + + Learn how to build super fast websites. + + + + ); + return ( + + {/* Left: The global variants are applied to children only */} + + {demo} + + One layer
    global variants are applied only to the children. +
    +
    + + {/* Right: The global variants are applied to both parent and children */} + + {React.cloneElement(demo, { + variant: 'solid', + color: 'primary', + })} + + Two layers +
    global variants are applied to the card and children. +
    +
    +
    + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.js b/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.js new file mode 100644 index 00000000000000..af8b73ae0a42f0 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.js @@ -0,0 +1,197 @@ +import * as React from 'react'; +import Avatar from '@mui/joy/Avatar'; +import Badge, { badgeClasses } from '@mui/joy/Badge'; +import Box from '@mui/joy/Box'; +import Card from '@mui/joy/Card'; +import CardContent from '@mui/joy/CardContent'; +import CircularProgress from '@mui/joy/CircularProgress'; +import Chip from '@mui/joy/Chip'; +import Divider from '@mui/joy/Divider'; +import IconButton from '@mui/joy/IconButton'; +import List from '@mui/joy/List'; +import ListItem from '@mui/joy/ListItem'; +import ListSubheader from '@mui/joy/ListSubheader'; +import ListItemButton from '@mui/joy/ListItemButton'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Typography from '@mui/joy/Typography'; +import Select from '@mui/joy/Select'; +import Option from '@mui/joy/Option'; +import Sheet from '@mui/joy/Sheet'; +import PieChart from '@mui/icons-material/PieChart'; +import SmsIcon from '@mui/icons-material/Sms'; +import PersonIcon from '@mui/icons-material/Person'; +import BubbleChartIcon from '@mui/icons-material/BubbleChart'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import AddIcon from '@mui/icons-material/Add'; +import SettingsIcon from '@mui/icons-material/Settings'; + +export default function ColorInversionNavigation() { + return ( + + ({ + p: 2, + ml: -3, + my: -3, + background: `linear-gradient(to top, ${theme.vars.palette.info[700]}, ${theme.vars.palette.info[600]} 25%, ${theme.vars.palette.info[500]} 75%)`, + })} + > + + + + + + + Dashboard + + + + Overview + + + + + + Chat + + 5 + + + + + + + Team + + + Shortcuts + + Tasks + Reports + Settings + + + + + + 35% + + + Last update: 22/12/22 + + Active + + + + + + + Jerry Wilson + + + + + + ({ + p: 2, + mr: -3, + my: -3, + display: 'flex', + flexDirection: 'column', + gap: 2, + '& button': { + borderRadius: '50%', + padding: 0, + '&:hover': { + boxShadow: theme.shadow.md, + }, + }, + })} + > + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.tsx new file mode 100644 index 00000000000000..af8b73ae0a42f0 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.tsx @@ -0,0 +1,197 @@ +import * as React from 'react'; +import Avatar from '@mui/joy/Avatar'; +import Badge, { badgeClasses } from '@mui/joy/Badge'; +import Box from '@mui/joy/Box'; +import Card from '@mui/joy/Card'; +import CardContent from '@mui/joy/CardContent'; +import CircularProgress from '@mui/joy/CircularProgress'; +import Chip from '@mui/joy/Chip'; +import Divider from '@mui/joy/Divider'; +import IconButton from '@mui/joy/IconButton'; +import List from '@mui/joy/List'; +import ListItem from '@mui/joy/ListItem'; +import ListSubheader from '@mui/joy/ListSubheader'; +import ListItemButton from '@mui/joy/ListItemButton'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Typography from '@mui/joy/Typography'; +import Select from '@mui/joy/Select'; +import Option from '@mui/joy/Option'; +import Sheet from '@mui/joy/Sheet'; +import PieChart from '@mui/icons-material/PieChart'; +import SmsIcon from '@mui/icons-material/Sms'; +import PersonIcon from '@mui/icons-material/Person'; +import BubbleChartIcon from '@mui/icons-material/BubbleChart'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import AddIcon from '@mui/icons-material/Add'; +import SettingsIcon from '@mui/icons-material/Settings'; + +export default function ColorInversionNavigation() { + return ( + + ({ + p: 2, + ml: -3, + my: -3, + background: `linear-gradient(to top, ${theme.vars.palette.info[700]}, ${theme.vars.palette.info[600]} 25%, ${theme.vars.palette.info[500]} 75%)`, + })} + > + + + + + + + Dashboard + + + + Overview + + + + + + Chat + + 5 + + + + + + + Team + + + Shortcuts + + Tasks + Reports + Settings + + + + + + 35% + + + Last update: 22/12/22 + + Active + + + + + + + Jerry Wilson + + + + + + ({ + p: 2, + mr: -3, + my: -3, + display: 'flex', + flexDirection: 'column', + gap: 2, + '& button': { + borderRadius: '50%', + padding: 0, + '&:hover': { + boxShadow: theme.shadow.md, + }, + }, + })} + > + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionOverview.js b/docs/data/joy/main-features/color-inversion/ColorInversionOverview.js new file mode 100644 index 00000000000000..df58ca270575cb --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionOverview.js @@ -0,0 +1,40 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import Chip from '@mui/joy/Chip'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +export default function ColorInversionOverview() { + return ( + + + New + + + + + + Learn how to build super fast website. + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionOverview.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionOverview.tsx new file mode 100644 index 00000000000000..df58ca270575cb --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionOverview.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import Chip from '@mui/joy/Chip'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +export default function ColorInversionOverview() { + return ( + + + New + + + + + + Learn how to build super fast website. + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionPopup.js b/docs/data/joy/main-features/color-inversion/ColorInversionPopup.js new file mode 100644 index 00000000000000..0c9ad35decc01e --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionPopup.js @@ -0,0 +1,127 @@ +import * as React from 'react'; + +import Autocomplete from '@mui/joy/Autocomplete'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import IconButton from '@mui/joy/IconButton'; +import Menu from '@mui/joy/Menu'; +import MenuItem from '@mui/joy/MenuItem'; +import ListDivider from '@mui/joy/ListDivider'; +import Tooltip from '@mui/joy/Tooltip'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + +// disable flip for this demo +// https://popper.js.org/docs/v2/modifiers/flip/ +const modifiers = [ + { + name: 'flip', + options: { + fallbackPlacements: ['bottom'], + }, + }, +]; + +export default function ColorInversionPopup() { + const [color, setColor] = React.useState('danger'); + const [menuButton, setMenuButton] = React.useState(null); + return ( + + + + setMenuButton(null)} + > + New tab + New window + + Delete + + + + + + + { + const colors = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + > + 🎨 + + + ); +} + +const films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, +]; diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionPopup.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionPopup.tsx new file mode 100644 index 00000000000000..15a50684fb6abb --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionPopup.tsx @@ -0,0 +1,126 @@ +import * as React from 'react'; +import { ColorPaletteProp } from '@mui/joy/styles'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import IconButton from '@mui/joy/IconButton'; +import Menu from '@mui/joy/Menu'; +import MenuItem from '@mui/joy/MenuItem'; +import ListDivider from '@mui/joy/ListDivider'; +import Tooltip from '@mui/joy/Tooltip'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + +// disable flip for this demo +// https://popper.js.org/docs/v2/modifiers/flip/ +const modifiers = [ + { + name: 'flip', + options: { + fallbackPlacements: ['bottom'], + }, + }, +]; + +export default function ColorInversionPopup() { + const [color, setColor] = React.useState('danger'); + const [menuButton, setMenuButton] = React.useState(null); + return ( + + + + setMenuButton(null)} + > + New tab + New window + + Delete + + + + + + + { + const colors: ColorPaletteProp[] = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + > + 🎨 + + + ); +} + +const films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, +]; diff --git a/docs/data/joy/main-features/color-inversion/color-inversion.md b/docs/data/joy/main-features/color-inversion/color-inversion.md new file mode 100644 index 00000000000000..4a7aa26cc14465 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/color-inversion.md @@ -0,0 +1,138 @@ +# Color inversion + +

    Joy UI components can invert their colors to match with the parent's variant.

    + +## Motivation + +[Global variants](/joy-ui/main-features/global-variants/) provide a consistent `variant` prop that lets you control the hierarchy of importance within a group of Joy UI components. However, they are not working as expected when global variants are used in multiple layers. + +The example below (on your right-hand side) shows the problem when the interface has more than one layer that applies the global variants: + +{{"demo": "ColorInversionMotivation.js"}} + +On the **left**, the `Button`'s variant is `solid` which is the highest emphasis level compared to other components. This conforms to the visual appearance on the screen. + +On the **right**, the problem arises when the container's variant becomes `solid`. The button is no longer the highest emphasis element because the it has the same background as the container. Also, the text and the icon button don't have enough contrast to the parent's background. + +The color inversion is implemented to solves this issue, keeping the global variants meaningful when multiple layers of global variants are composed together. + +## Overview + +When color inversion is enabled on the parent component, the children with implicit color will invert their styles to match the parent's background by keeping the same hierarchy of importance based on their variants. + +{{"demo": "ColorInversionOverview.js"}} + +:::info +**Implicit** color refers to components that don't have the `color` prop specified. + +Color inversion has **no effect** on those children that have an **explicit** `color` prop. + +```js +// implicit color. The styles change when color inversion is enabled. + + +// explicit color. Color inversion has no effect. + +``` + +::: + +### Benefits + +- Color inversion reduces a significant amount of styling effort. It handles all of the visual states (hover, active, and focus) on all the children. +- It makes your interface scalable. New components added to the area will just work. +- It works for both client-side and server-side rendering. +- It works for both light and dark mode. +- It can be disabled at any time without impacting the structure of the components. +- It is an opt-in feature. If you don't use it, the extra CSS variables won't be included in the production style sheet. +- It doesn't alter the styles of the children if you explicitly specify the `color` prop on them. + +### Trade-offs + +- If the surface component contains just a few components, the style sheet size of the CSS variables might be **bigger** than customizing the styles of each individual child. +- It doesn't work with browsers that don't support [CSS variables](https://caniuse.com/css-variables). + +## Usage + +To enable the feature set `invertedColors` to true on the surface components: + +```js + + + +``` + +:::info + +- [`Sheet`](/joy-ui/react-sheet/) and [`Card`](/joy-ui/react-card/) are the only components that support this feature. +- The surface component should have `soft` or `solid` variant to enable this feature. + ::: + +### Portal popup + +By default, color inversion has no effect on the popup slot of `Autocomplete`, `Menu`, and `Tooltip`. + +To enable color inversion for those slots, set `disablePortal` to true. + +{{"demo": "ColorInversionPopup.js"}} + +:::info +The popup slot of the `Select` component has `disablePortal` set to true by default. +::: + +## Common examples + +### Header + +{{"demo": "ColorInversionHeader.js"}} + +### Footer + +{{"demo": "ColorInversionFooter.js"}} + +### Side navigation + +{{"demo": "ColorInversionNavigation.js"}} + +### Marketing section + +{{"demo": "ColorInversionMarketing.js"}} + +## How it works + +**Parent component** + +When `invertedColors` is set to true on the surface component, a set of CSS variables are applied to it. The values of those variables comes from `theme.colorInversion[variant][color]` where `variant` and `color` are the component's props. The surface component also creates a React context to tell the children to update their styles. + +```jsx + + +// The component style sheet +{ + // the values of these variables depends on the parent's variant and color. + --variant-softColor: …; + --variant-softBg: …; + --variant-softHoverColor: …; + --variant-softHoverBg: …; + --variant-softActiveBg: …; + … // other variants +} +``` + +**Child component** + +All Joy UI components that support global variants check the React context that contains the color inversion flag. If the flag is true and the child has an implicit color, the internal `color` value will switch to `context` and apply the styles from `theme.variants[variant].context`. + +The styles will match the `--variant-*` variables that the parent has. + +```jsx + + +// Component style sheet +{ + background-color: var(--variant-softBg); + color: var(--variant-softColor); +} +``` + +In summary, the parent creates a React context to tell the children that the feature is enabled, and generates CSS variables that will be used by the children. The children with an implicit color switch their default color value to `context` to get the styles from the theme. diff --git a/docs/data/joy/pages.ts b/docs/data/joy/pages.ts index 6376203aa10d79..cae4b30a0c11ee 100644 --- a/docs/data/joy/pages.ts +++ b/docs/data/joy/pages.ts @@ -14,6 +14,7 @@ const pages = [ subheader: 'main-features', children: [ { pathname: '/joy-ui/main-features/global-variants' }, + { pathname: '/joy-ui/main-features/color-inversion' }, { pathname: '/joy-ui/main-features/automatic-adjustment' }, { pathname: '/joy-ui/main-features/dark-mode-optimization' }, ], diff --git a/docs/pages/experiments/joy/variant-overrides.tsx b/docs/pages/experiments/joy/variant-overrides.tsx deleted file mode 100644 index a1c1ff8a88a4a7..00000000000000 --- a/docs/pages/experiments/joy/variant-overrides.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import * as React from 'react'; -import { GlobalStyles } from '@mui/system'; -import Box from '@mui/joy/Box'; -import Button from '@mui/joy/Button'; -import Divider from '@mui/joy/Divider'; -import Typography from '@mui/joy/Typography'; -import Sheet from '@mui/joy/Sheet'; -import { CssVarsProvider, useColorScheme } from '@mui/joy/styles'; -import Moon from '@mui/icons-material/DarkMode'; -import Sun from '@mui/icons-material/LightMode'; - -function ColorSchemePicker() { - const { mode, setMode } = useColorScheme(); - const [mounted, setMounted] = React.useState(false); - React.useEffect(() => { - setMounted(true); - }, []); - if (!mounted) { - return null; - } - - return ( - - ); -} - -export default function JoyVariant() { - return ( - - - - - - - div': { - display: 'flex', - flexDirection: 'column', - gap: 1, - p: 2.5, - boxShadow: 'md', - borderRadius: 'sm', - }, - }} - > - - - text.primary - text.secondary - - text.tertiary - - - - - - - - - - - - - - - - - - - - - - - - - - - text.primary - text.secondary - - text.tertiary - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/docs/pages/joy-ui/main-features/color-inversion.js b/docs/pages/joy-ui/main-features/color-inversion.js new file mode 100644 index 00000000000000..bd3ed4e7015ff5 --- /dev/null +++ b/docs/pages/joy-ui/main-features/color-inversion.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docs/data/joy/main-features/color-inversion/color-inversion.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/packages/mui-joy/src/Alert/Alert.test.js b/packages/mui-joy/src/Alert/Alert.test.js index da5e7bb67c5ecb..c5621ee4fbf6d1 100644 --- a/packages/mui-joy/src/Alert/Alert.test.js +++ b/packages/mui-joy/src/Alert/Alert.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Alert, { alertClasses as classes } from '@mui/joy/Alert'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -26,6 +26,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyAlert', classes }); + describe('prop: variant', () => { it('soft by default', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/Alert/Alert.tsx b/packages/mui-joy/src/Alert/Alert.tsx index ffe87c545874c7..1c70bcf9670a16 100644 --- a/packages/mui-joy/src/Alert/Alert.tsx +++ b/packages/mui-joy/src/Alert/Alert.tsx @@ -1,16 +1,17 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; -import clsx from 'clsx'; -import PropTypes from 'prop-types'; -import * as React from 'react'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import { getAlertUtilityClass } from './alertClasses'; -import { AlertProps, AlertTypeMap } from './AlertProps'; +import { AlertProps, AlertOwnerState, AlertTypeMap } from './AlertProps'; -const useUtilityClasses = (ownerState: AlertProps) => { +const useUtilityClasses = (ownerState: AlertOwnerState) => { const { variant, color, size } = ownerState; const slots = { @@ -31,7 +32,7 @@ const AlertRoot = styled('div', { name: 'JoyAlert', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: AlertProps }>(({ theme, ownerState }) => ({ +})<{ ownerState: AlertOwnerState }>(({ theme, ownerState }) => ({ '--Alert-radius': theme.vars.radius.sm, '--Alert-decorator-childRadius': 'max((var(--Alert-radius) - var(--variant-borderWidth, 0px)) - var(--Alert-padding), min(var(--Alert-padding) / 2, (var(--Alert-radius) - var(--variant-borderWidth, 0px)) / 2))', @@ -76,23 +77,27 @@ const AlertStartDecorator = styled('span', { name: 'JoyAlert', slot: 'StartDecorator', overridesResolver: (props, styles) => styles.startDecorator, -})<{ ownerState: AlertProps }>(({ theme, ownerState }) => ({ +})<{ ownerState: AlertOwnerState }>(({ theme, ownerState }) => ({ display: 'inherit', flex: 'none', marginInlineEnd: 'var(--Alert-gap)', - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + ...(ownerState.color !== 'context' && { + color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + }), })); const AlertEndDecorator = styled('span', { name: 'JoyAlert', slot: 'EndDecorator', overridesResolver: (props, styles) => styles.endDecorator, -})<{ ownerState: AlertProps }>(({ theme, ownerState }) => ({ +})<{ ownerState: AlertOwnerState }>(({ theme, ownerState }) => ({ display: 'inherit', flex: 'none', marginInlineStart: 'var(--Alert-gap)', marginLeft: 'auto', - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + ...(ownerState.color !== 'context' && { + color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + }), })); const Alert = React.forwardRef(function Alert(inProps, ref) { @@ -104,7 +109,7 @@ const Alert = React.forwardRef(function Alert(inProps, ref) { const { children, className, - color = 'primary', + color: colorProp = 'primary', role = 'alert', variant = 'soft', size = 'md', @@ -112,6 +117,8 @@ const Alert = React.forwardRef(function Alert(inProps, ref) { endDecorator, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/Alert/AlertProps.ts b/packages/mui-joy/src/Alert/AlertProps.ts index 34c00ca1da8bde..2f6ebbbf359153 100644 --- a/packages/mui-joy/src/Alert/AlertProps.ts +++ b/packages/mui-joy/src/Alert/AlertProps.ts @@ -1,6 +1,6 @@ -import { OverridableStringUnion, OverrideProps } from '@mui/types'; import * as React from 'react'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { OverridableStringUnion, OverrideProps } from '@mui/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type AlertSlot = 'root' | 'startDecorator' | 'endDecorator'; @@ -62,4 +62,4 @@ export type AlertProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface AlertOwnerState extends AlertProps {} +export interface AlertOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Alert/alertClasses.ts b/packages/mui-joy/src/Alert/alertClasses.ts index 64bdb5ba4c559d..d2ee0ef9063024 100644 --- a/packages/mui-joy/src/Alert/alertClasses.ts +++ b/packages/mui-joy/src/Alert/alertClasses.ts @@ -15,6 +15,8 @@ export interface AlertClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the endDecorator element if supplied. */ endDecorator: string; /** Styles applied to the root element if `size="sm"`. */ @@ -51,6 +53,7 @@ const alertClasses: AlertClasses = generateUtilityClasses('JoyAlert', [ 'colorNeutral', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/AspectRatio/AspectRatio.test.js b/packages/mui-joy/src/AspectRatio/AspectRatio.test.js index 8e4704581efd0d..8ec3c5aadfbcde 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatio.test.js +++ b/packages/mui-joy/src/AspectRatio/AspectRatio.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { ThemeProvider } from '@mui/joy/styles'; import AspectRatio, { aspectRatioClasses as classes } from '@mui/joy/AspectRatio'; @@ -25,6 +25,11 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion( + , + { muiName: 'JoyAlert', classes }, + ); + describe('prop: variant', () => { it('plain by default', () => { const { getByTestId } = render(Hello World); diff --git a/packages/mui-joy/src/AspectRatio/AspectRatio.tsx b/packages/mui-joy/src/AspectRatio/AspectRatio.tsx index 3173f05f4e7300..f23cf3746a34d0 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatio.tsx +++ b/packages/mui-joy/src/AspectRatio/AspectRatio.tsx @@ -6,6 +6,7 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import useThemeProps from '../styles/useThemeProps'; import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import { getAspectRatioUtilityClass } from './aspectRatioClasses'; import { AspectRatioProps, AspectRatioOwnerState, AspectRatioTypeMap } from './AspectRatioProps'; @@ -95,10 +96,12 @@ const AspectRatio = React.forwardRef(function AspectRatio(inProps, ref) { minHeight, maxHeight, objectFit = 'cover', - color = 'neutral', + color: colorProp = 'neutral', variant = 'soft', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts b/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts index fbd9c4418c3ac0..ffddaf3ec7b431 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts +++ b/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type AspectRatioSlot = 'root' | 'content'; @@ -65,4 +65,4 @@ export type AspectRatioProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface AspectRatioOwnerState extends AspectRatioProps {} +export interface AspectRatioOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts b/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts index e1b1034503222d..fb6bbd46be099f 100644 --- a/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts +++ b/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts @@ -17,6 +17,8 @@ export interface AspectRatioClasses { colorSuccess: string; /** Styles applied to the content element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the content element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the content element if `variant="outlined"`. */ @@ -42,6 +44,7 @@ const aspectRatioClasses: AspectRatioClasses = generateUtilityClasses('JoyAspect 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx index ad05a2d2f88889..b338d45dce7246 100644 --- a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx +++ b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx @@ -9,6 +9,7 @@ import { act, fireEvent, strictModeDoubleLoggingSupressed, + describeJoyColorInversion, } from 'test/utils'; import Autocomplete, { autocompleteClasses as classes, @@ -48,6 +49,12 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { + muiName: 'JoyAutocomplete', + classes, + portalSlot: 'listbox', + }); + it('should be customizable in the theme', () => { render( , 'onChange' | 'defaultValue'>; @@ -330,10 +332,11 @@ const Autocomplete = React.forwardRef(function Autocomplete( } = props; const other = excludeUseAutocompleteParams(otherProps); + const { getColor } = useColorInversion(variant); const formControl = React.useContext(FormControlContext); const error = inProps.error ?? formControl?.error ?? errorProp; const size = inProps.size ?? formControl?.size ?? sizeProp; - const color = error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp; + const color = getColor(inProps.color, error ? 'danger' : formControl?.color ?? colorProp); const disabled = disabledProp ?? formControl?.disabled ?? false; const { @@ -402,7 +405,7 @@ const Autocomplete = React.forwardRef(function Autocomplete( key={index} size={size} variant="soft" - color="neutral" + color={color === 'context' ? undefined : 'neutral'} endDecorator={} > {getOptionLabel(option)} @@ -537,7 +540,8 @@ const Autocomplete = React.forwardRef(function Autocomplete( getSlotOwnerState: (mergedProps) => ({ size: mergedProps.size || size, variant: mergedProps.variant || 'outlined', - color: mergedProps.variant || 'neutral', + color: mergedProps.color || 'neutral', + disableColorInversion: !mergedProps.disablePortal, }), additionalProps: { anchorEl, @@ -601,6 +605,7 @@ const Autocomplete = React.forwardRef(function Autocomplete( getSlotOwnerState: (mergedProps) => ({ variant: mergedProps.variant || 'plain', color: mergedProps.color || 'neutral', + disableColorInversion: !listboxProps.disablePortal, }), additionalProps: { as: 'li', @@ -637,6 +642,47 @@ const Autocomplete = React.forwardRef(function Autocomplete( [listboxProps.modifiers], ); + let popup = null; + if (anchorEl) { + popup = ( + + + {groupedOptions.map((option, index) => { + if (groupBy) { + const typedOption = option as AutocompleteGroupedOption; + return renderGroup({ + key: String(typedOption.key), + group: typedOption.group, + children: typedOption.options.map((option2, index2) => + renderListOption(option2, typedOption.index + index2), + ), + }); + } + return renderListOption(option, index); + })} + {loading && groupedOptions.length === 0 ? ( + {loadingText} + ) : null} + {groupedOptions.length === 0 && !freeSolo && !loading ? ( + {noOptionsText} + ) : null} + + + ); + + if (!listboxProps.disablePortal) { + // For portal popup, the children should not inherit color inversion from the upper parent. + popup = {popup}; + } + } + return ( @@ -656,32 +702,7 @@ const Autocomplete = React.forwardRef(function Autocomplete( {popupIcon} ) : null} - {anchorEl ? ( - // `nested` is for grouped options use case. - - - {groupedOptions.map((option, index) => { - if (groupBy) { - const typedOption = option as AutocompleteGroupedOption; - return renderGroup({ - key: String(typedOption.key), - group: typedOption.group, - children: typedOption.options.map((option2, index2) => - renderListOption(option2, typedOption.index + index2), - ), - }); - } - return renderListOption(option, index); - })} - {loading && groupedOptions.length === 0 ? ( - {loadingText} - ) : null} - {groupedOptions.length === 0 && !freeSolo && !loading ? ( - {noOptionsText} - ) : null} - - - ) : null} + {popup} ); }) as AutocompleteComponent; diff --git a/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts b/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts index d995f5e91ade04..7a817c92f7cbc8 100644 --- a/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts +++ b/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts @@ -9,7 +9,7 @@ import { } from '@mui/base/AutocompleteUnstyled'; import { PopperUnstyledOwnProps } from '@mui/base/PopperUnstyled'; import { OverridableStringUnion } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type AutocompleteSlot = @@ -305,7 +305,7 @@ export interface AutocompleteOwnerState< Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, -> extends AutocompleteOwnProps { +> extends ApplyColorInversion> { focused?: boolean; hasClearIcon?: boolean; hasPopupIcon?: boolean; diff --git a/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts index e48385d4651176..22ced03f1bc388 100644 --- a/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts +++ b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts @@ -53,6 +53,8 @@ export interface AutocompleteClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -102,6 +104,7 @@ const autocompleteClasses: AutocompleteClasses = generateUtilityClasses('JoyAuto 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx index c1dce2700792d3..d2c36e7be137a8 100644 --- a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx +++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import AutocompleteListbox, { autocompleteListboxClasses as classes, @@ -21,6 +21,11 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { + muiName: 'JoyAutocompleteListbox', + classes, + }); + it('should have ul tag', () => { const { container } = render(); expect(container.firstChild).to.have.tagName('ul'); diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx index bd58846f76264e..0aba3162179d35 100644 --- a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx +++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx @@ -14,6 +14,7 @@ import { import listItemClasses from '../ListItem/listItemClasses'; import listClasses from '../List/listClasses'; import { scopedVariables } from '../List/ListProvider'; +import { useColorInversion } from '../styles/ColorInversion'; const useUtilityClasses = (ownerState: AutocompleteListboxOwnerState) => { const { variant, color, size } = ownerState; @@ -93,11 +94,13 @@ const AutocompleteListbox = React.forwardRef(function AutocompleteListbox(inProp children, className, component, - color = 'neutral', + color: colorProp = 'neutral', variant = 'outlined', size = 'md', ...otherProps } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts index d48cfd219c1d0a..f14f02d67f74d7 100644 --- a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts +++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts @@ -1,27 +1,30 @@ import * as React from 'react'; -import { OverrideProps } from '@mui/types'; -import { ListProps } from '../List/ListProps'; -import { SxProps } from '../styles/types'; +import { OverrideProps, OverridableStringUnion } from '@mui/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type AutocompleteListboxSlot = 'root'; +export interface AutocompleteListboxPropsSizeOverrides {} +export interface AutocompleteListboxPropsColorOverrides {} +export interface AutocompleteListboxPropsVariantOverrides {} + export interface AutocompleteListboxTypeMap

    { props: P & { /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' */ - color?: ListProps['color']; + color?: OverridableStringUnion; /** * The variant to use. * @default 'outlined' */ - variant?: ListProps['variant']; + variant?: OverridableStringUnion; /** * The size of the component (affect other nested list* components). * @default 'md' */ - size?: ListProps['size']; + size?: OverridableStringUnion<'sm' | 'md' | 'lg', AutocompleteListboxPropsSizeOverrides>; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -37,4 +40,5 @@ export type AutocompleteListboxProps< }, > = OverrideProps, D>; -export interface AutocompleteListboxOwnerState extends AutocompleteListboxProps {} +export interface AutocompleteListboxOwnerState + extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts index d3fa27a8717fe7..a8a348abcaa19a 100644 --- a/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts +++ b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts @@ -21,6 +21,8 @@ export interface AutocompleteListboxClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -50,6 +52,7 @@ const autocompleteListboxClasses: AutocompleteListboxClasses = generateUtilityCl 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js index 205980e72aa61f..546fc62e96954f 100644 --- a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js +++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import AutocompleteOption, { autocompleteOptionClasses as classes, @@ -21,6 +21,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyAutocompleteOption', classes }); + it('should have li tag', () => { const { getByRole } = render(); expect(getByRole('option')).to.have.tagName('li'); diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx index b54098681d529d..7c8ad4f5b13397 100644 --- a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx +++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx @@ -10,6 +10,7 @@ import autocompleteOptionClasses, { getAutocompleteOptionUtilityClass, } from './autocompleteOptionClasses'; import { AutocompleteOptionOwnerState, AutocompleteOptionTypeMap } from './AutocompleteOptionProps'; +import { useColorInversion } from '../styles/ColorInversion'; const useUtilityClasses = (ownerState: AutocompleteOptionOwnerState) => { const { color, variant } = ownerState; @@ -33,8 +34,10 @@ export const StyledAutocompleteOption = styled(StyledListItemButton as unknown a }, '&[aria-disabled="true"]': theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], '&[aria-selected="true"]': { - color: theme.vars.palette.primary.softColor, - backgroundColor: theme.vars.palette.primary.softBg, + color: theme.variants.soft?.[ownerState.color === 'context' ? 'context' : 'primary']?.color, + backgroundColor: + theme.variants.soft?.[ownerState.color === 'context' ? 'context' : 'primary'] + ?.backgroundColor, fontWeight: theme.vars.fontWeight.md, }, [`&.${autocompleteOptionClasses.focused}:not([aria-selected="true"]):not(:hover)`]: { @@ -59,11 +62,13 @@ const AutocompleteOption = React.forwardRef(function AutocompleteOption(inProps, const { children, component = 'li', - color = 'neutral', + color: colorProp = 'neutral', variant = 'plain', className, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts b/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts index 4d2e672dc482b4..4146e121d861fa 100644 --- a/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts +++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts @@ -1,22 +1,24 @@ import * as React from 'react'; -import { OverrideProps } from '@mui/types'; -import { ListItemButtonProps } from '../ListItemButton/ListItemButtonProps'; -import { SxProps } from '../styles/types'; +import { OverrideProps, OverridableStringUnion } from '@mui/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type AutocompleteOptionSlot = 'root'; +export interface AutocompleteOptionPropsColorOverrides {} +export interface AutocompleteOptionPropsVariantOverrides {} + export interface AutocompleteOptionTypeMap

    { props: P & { /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' */ - color?: ListItemButtonProps['color']; + color?: OverridableStringUnion; /** * The variant to use. * @default 'plain' */ - variant?: ListItemButtonProps['variant']; + variant?: OverridableStringUnion; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -32,4 +34,5 @@ export type AutocompleteOptionProps< }, > = OverrideProps, D>; -export interface AutocompleteOptionOwnerState extends AutocompleteOptionProps {} +export interface AutocompleteOptionOwnerState + extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts index 711ee09fc73203..f393cf0e6e91c9 100644 --- a/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts +++ b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts @@ -19,6 +19,8 @@ export interface AutocompleteOptionClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `variant="plain"`. */ variantPlain: string; /** State class applied to the root element if `variant="soft"`. */ @@ -47,6 +49,7 @@ const autocompleteOptionClasses: AutocompleteOptionClasses = generateUtilityClas 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSoft', 'variantOutlined', diff --git a/packages/mui-joy/src/Avatar/Avatar.test.js b/packages/mui-joy/src/Avatar/Avatar.test.js index 1c3539c39bd6b6..166c290800ccd2 100644 --- a/packages/mui-joy/src/Avatar/Avatar.test.js +++ b/packages/mui-joy/src/Avatar/Avatar.test.js @@ -1,7 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { createRenderer, describeConformance, fireEvent } from 'test/utils'; +import { + createRenderer, + describeConformance, + describeJoyColorInversion, + fireEvent, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Avatar, { avatarClasses as classes } from '@mui/joy/Avatar'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -28,6 +33,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyAvatar', classes }); + describe('prop: variant', () => { it('soft by default', () => { const { getByTestId } = render(); diff --git a/packages/mui-joy/src/Avatar/Avatar.tsx b/packages/mui-joy/src/Avatar/Avatar.tsx index 8871021bfeed1d..ac99fdfed9a804 100644 --- a/packages/mui-joy/src/Avatar/Avatar.tsx +++ b/packages/mui-joy/src/Avatar/Avatar.tsx @@ -6,6 +6,7 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import { useThemeProps } from '../styles'; import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import Person from '../internal/svg-icons/Person'; import { getAvatarUtilityClass } from './avatarClasses'; import { AvatarProps, AvatarOwnerState, AvatarTypeMap } from './AvatarProps'; @@ -155,8 +156,9 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) { children: childrenProp, ...other } = props; - const color = inProps.color || groupContext?.color || colorProp; const variant = inProps.variant || groupContext?.variant || variantProp; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color || groupContext?.color, colorProp); const size = inProps.size || groupContext?.size || sizeProp; let children = null; diff --git a/packages/mui-joy/src/Avatar/AvatarProps.ts b/packages/mui-joy/src/Avatar/AvatarProps.ts index a1b43d176c93dd..8f211b7444e4ca 100644 --- a/packages/mui-joy/src/Avatar/AvatarProps.ts +++ b/packages/mui-joy/src/Avatar/AvatarProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type AvatarSlot = 'root' | 'img' | 'fallback'; @@ -76,7 +76,7 @@ export type AvatarProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface AvatarOwnerState extends AvatarProps { +export interface AvatarOwnerState extends ApplyColorInversion { /** * The avatar is wrapped by AvatarGroup component. */ diff --git a/packages/mui-joy/src/Avatar/avatarClasses.ts b/packages/mui-joy/src/Avatar/avatarClasses.ts index 4d42d4c9d2d8ea..2094f9c82dcb8e 100644 --- a/packages/mui-joy/src/Avatar/avatarClasses.ts +++ b/packages/mui-joy/src/Avatar/avatarClasses.ts @@ -15,6 +15,8 @@ export interface AvatarClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the fallback icon. */ fallback: string; /** Styles applied to the root element if `size="sm"`. */ @@ -47,6 +49,7 @@ const avatarClasses: AvatarClasses = generateUtilityClasses('JoyAvatar', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'fallback', 'sizeSm', 'sizeMd', diff --git a/packages/mui-joy/src/Badge/Badge.test.js b/packages/mui-joy/src/Badge/Badge.test.js index 0841b89083f6c7..c2fb3c81d51ebd 100644 --- a/packages/mui-joy/src/Badge/Badge.test.js +++ b/packages/mui-joy/src/Badge/Badge.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Badge, { badgeClasses as classes } from '@mui/joy/Badge'; @@ -42,6 +42,13 @@ describe('', () => { }), ); + describeJoyColorInversion( + + , { muiName: 'JoyButton', classes }); + it('by default, should render with the root, variantSolid, sizeMd and colorPrimary classes', () => { const { getByRole } = render(); const button = getByRole('button'); diff --git a/packages/mui-joy/src/Button/Button.tsx b/packages/mui-joy/src/Button/Button.tsx index e7c59022f78e64..af12b54e24d790 100644 --- a/packages/mui-joy/src/Button/Button.tsx +++ b/packages/mui-joy/src/Button/Button.tsx @@ -190,7 +190,10 @@ const Button = React.forwardRef(function Button(inProps, ref) { }); const loadingIndicator = loadingIndicatorProp ?? ( - + ); React.useImperativeHandle( diff --git a/packages/mui-joy/src/Button/ButtonProps.ts b/packages/mui-joy/src/Button/ButtonProps.ts index 898ce397d3552f..5046d6ad1b62ec 100644 --- a/packages/mui-joy/src/Button/ButtonProps.ts +++ b/packages/mui-joy/src/Button/ButtonProps.ts @@ -5,7 +5,7 @@ import { OverridableTypeMap, OverrideProps, } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; export type ButtonSlot = 'root' | 'startDecorator' | 'endDecorator' | 'loadingIndicatorCenter'; @@ -114,8 +114,7 @@ export type ButtonProps< }, > = OverrideProps, D>; -export interface ButtonOwnerState extends Omit { - color: ButtonProps['color'] | 'context'; +export interface ButtonOwnerState extends ApplyColorInversion { /** * If `true`, the button's focus is visible. */ diff --git a/packages/mui-joy/src/Button/buttonClasses.ts b/packages/mui-joy/src/Button/buttonClasses.ts index 405baa4fe22bb6..a47e6f895db806 100644 --- a/packages/mui-joy/src/Button/buttonClasses.ts +++ b/packages/mui-joy/src/Button/buttonClasses.ts @@ -15,6 +15,8 @@ export interface ButtonClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -59,6 +61,7 @@ const buttonClasses: ButtonClasses = generateUtilityClasses('JoyButton', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Card/Card.test.js b/packages/mui-joy/src/Card/Card.test.js index 08c0239c769001..c81d64825bab0c 100644 --- a/packages/mui-joy/src/Card/Card.test.js +++ b/packages/mui-joy/src/Card/Card.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Card, { cardClasses as classes } from '@mui/joy/Card'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -21,6 +21,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyCard', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByTestId } = render(Hello World); diff --git a/packages/mui-joy/src/Card/Card.tsx b/packages/mui-joy/src/Card/Card.tsx index 73c9813f331fff..1d35f6c933ff24 100644 --- a/packages/mui-joy/src/Card/Card.tsx +++ b/packages/mui-joy/src/Card/Card.tsx @@ -9,12 +9,13 @@ import { } from '@mui/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { ColorInversionProvider, useColorInversion } from '../styles/ColorInversion'; import { getCardUtilityClass } from './cardClasses'; -import { CardProps, CardTypeMap } from './CardProps'; +import { CardProps, CardOwnerState, CardTypeMap } from './CardProps'; import { resolveSxValue } from '../styles/styleUtils'; import { CardRowContext } from './CardContext'; -const useUtilityClasses = (ownerState: CardProps) => { +const useUtilityClasses = (ownerState: CardOwnerState) => { const { size, variant, color, row } = ownerState; const slots = { @@ -34,7 +35,7 @@ const CardRoot = styled('div', { name: 'JoyCard', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: CardProps }>(({ theme, ownerState }) => [ +})<{ ownerState: CardOwnerState }>(({ theme, ownerState }) => [ { // a context variable for any child component '--Card-childRadius': @@ -82,6 +83,9 @@ const CardRoot = styled('div', { flexDirection: ownerState.row ? 'row' : 'column', }, theme.variants[ownerState.variant!]?.[ownerState.color!], + ownerState.color !== 'context' && + ownerState.invertedColors && + theme.colorInversion[ownerState.variant!]?.[ownerState.color!], ]); const Card = React.forwardRef(function Card(inProps, ref) { @@ -92,14 +96,17 @@ const Card = React.forwardRef(function Card(inProps, ref) { const { className, - color = 'neutral', + color: colorProp = 'neutral', component = 'div', + invertedColors = false, size = 'md', variant = 'plain', children, row = false, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, @@ -112,7 +119,7 @@ const Card = React.forwardRef(function Card(inProps, ref) { const classes = useUtilityClasses(ownerState); - return ( + const result = ( ); + + if (invertedColors) { + return {result}; + } + return result; }) as OverridableComponent; Card.propTypes /* remove-proptypes */ = { @@ -173,6 +185,11 @@ Card.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, + /** + * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color. + * @default false + */ + invertedColors: PropTypes.bool, /** * If `true`, flex direction is set to 'row'. * @default false diff --git a/packages/mui-joy/src/Card/CardProps.ts b/packages/mui-joy/src/Card/CardProps.ts index 5aa19039932eb9..cd3619d30e5c60 100644 --- a/packages/mui-joy/src/Card/CardProps.ts +++ b/packages/mui-joy/src/Card/CardProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type CardSlot = 'root'; @@ -20,6 +20,11 @@ export interface CardTypeMap

    { * @default 'neutral' */ color?: OverridableStringUnion; + /** + * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color. + * @default false + */ + invertedColors?: boolean; /** * If `true`, flex direction is set to 'row'. * @default false @@ -49,4 +54,4 @@ export type CardProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface CardOwnerState extends CardProps {} +export interface CardOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Card/cardClasses.ts b/packages/mui-joy/src/Card/cardClasses.ts index 687062fdf18d50..6fae87bdfe765f 100644 --- a/packages/mui-joy/src/Card/cardClasses.ts +++ b/packages/mui-joy/src/Card/cardClasses.ts @@ -15,6 +15,8 @@ export interface CardClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -47,6 +49,7 @@ const cardClasses: CardClasses = generateUtilityClasses('JoyCard', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/CardOverflow/CardOverflow.test.js b/packages/mui-joy/src/CardOverflow/CardOverflow.test.js index f5433675915d12..3843b7c68adf01 100644 --- a/packages/mui-joy/src/CardOverflow/CardOverflow.test.js +++ b/packages/mui-joy/src/CardOverflow/CardOverflow.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { ThemeProvider } from '@mui/joy/styles'; import CardOverflow, { cardOverflowClasses as classes } from '@mui/joy/CardOverflow'; @@ -21,6 +21,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyCardOverflow', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByTestId } = render(Hello World); diff --git a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx index bda7cd29a9d2da..8e30b37b16b619 100644 --- a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx +++ b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx @@ -6,11 +6,16 @@ import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import { getCardOverflowUtilityClass } from './cardOverflowClasses'; -import { CardOverflowProps, CardOverflowTypeMap } from './CardOverflowProps'; +import { + CardOverflowProps, + CardOverflowOwnerState, + CardOverflowTypeMap, +} from './CardOverflowProps'; import { CardRowContext } from '../Card/CardContext'; -const useUtilityClasses = (ownerState: CardOverflowProps) => { +const useUtilityClasses = (ownerState: CardOverflowOwnerState) => { const { variant, color } = ownerState; const slots = { root: [ @@ -28,7 +33,7 @@ const CardOverflowRoot = styled('div', { slot: 'Root', overridesResolver: (props, styles) => styles.root, })<{ - ownerState: CardOverflowProps & { + ownerState: CardOverflowOwnerState & { row: boolean; 'data-first-child'?: string; 'data-last-child'?: string; @@ -95,10 +100,12 @@ const CardOverflow = React.forwardRef(function CardOverflow(inProps, ref) { className, component = 'div', children, - color = 'neutral', + color: colorProp = 'neutral', variant = 'plain', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts b/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts index 9063abb4b44a6a..7b69d67fa0545d 100644 --- a/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts +++ b/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type CardOverflowSlot = 'root'; @@ -37,4 +37,4 @@ export type CardOverflowProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface CardOverflowOwnerState extends CardOverflowProps {} +export interface CardOverflowOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts b/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts index dd445e4e83e4d2..14f85734a87023 100644 --- a/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts +++ b/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts @@ -15,6 +15,8 @@ export interface CardOverflowClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -39,6 +41,7 @@ const aspectRatioClasses: CardOverflowClasses = generateUtilityClasses('JoyCardO 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Checkbox/Checkbox.test.js b/packages/mui-joy/src/Checkbox/Checkbox.test.js index 31433c05d9ddaf..48731c34a5c7a9 100644 --- a/packages/mui-joy/src/Checkbox/Checkbox.test.js +++ b/packages/mui-joy/src/Checkbox/Checkbox.test.js @@ -1,6 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, act, createRenderer, fireEvent } from 'test/utils'; +import { + describeConformance, + act, + createRenderer, + fireEvent, + describeJoyColorInversion, +} from 'test/utils'; import Checkbox, { checkboxClasses as classes } from '@mui/joy/Checkbox'; import { ThemeProvider } from '@mui/joy/styles'; @@ -26,6 +32,8 @@ describe('', () => { skip: ['componentProp', 'componentsProp', 'classesRoot', 'propsSpread', 'themeVariants'], })); + describeJoyColorInversion(, { muiName: 'JoyCheckbox', classes }); + it('should have the classes required for Checkbox', () => { expect(classes).to.include.all.keys(['root', 'checked', 'disabled']); }); diff --git a/packages/mui-joy/src/Checkbox/Checkbox.tsx b/packages/mui-joy/src/Checkbox/Checkbox.tsx index 6852f48fd3e29b..f8a82b7786d8ef 100644 --- a/packages/mui-joy/src/Checkbox/Checkbox.tsx +++ b/packages/mui-joy/src/Checkbox/Checkbox.tsx @@ -5,6 +5,7 @@ import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui import { unstable_composeClasses as composeClasses } from '@mui/base'; import { useSwitch } from '@mui/base/SwitchUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import checkboxClasses, { getCheckboxUtilityClass } from './checkboxClasses'; import { CheckboxOwnerState, CheckboxTypeMap } from './CheckboxProps'; @@ -64,15 +65,15 @@ const CheckboxRoot = styled('span', { position: ownerState.overlay ? 'initial' : 'relative', display: 'inline-flex', fontFamily: theme.vars.fontFamily.body, - lineHeight: 'var(--Checkbox-size)', // prevent label from having larger height than the checkbox + lineHeight: 'var(--Checkbox-size)', color: theme.vars.palette.text.primary, [`&.${checkboxClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.plainDisabledColor, + color: theme.variants.plainDisabled?.[ownerState.color!]?.color, }, ...(ownerState.disableIcon && { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, [`&.${checkboxClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], + color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, }, }), })); @@ -204,7 +205,7 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { required, value, color: colorProp, - variant, + variant: variantProp, size: sizeProp = 'md', ...other } = props; @@ -212,7 +213,6 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { const formControl = React.useContext(FormControlContext); const disabledProp = inProps.disabled ?? formControl?.disabled ?? disabledExternalProp; const size = inProps.size ?? formControl?.size ?? sizeProp; - const color = formControl?.error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp; if (process.env.NODE_ENV !== 'production') { const registerEffect = formControl?.registerEffect; @@ -241,10 +241,17 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { const { getInputProps, checked, disabled, focusVisible } = useSwitch(useCheckboxProps); const isCheckboxActive = checked || indeterminate; + const activeVariant = variantProp || 'solid'; + const inactiveVariant = variantProp || 'outlined'; + const variant = isCheckboxActive ? activeVariant : inactiveVariant; + const { getColor } = useColorInversion(variant); + const color = getColor( + inProps.color, + formControl?.error ? 'danger' : formControl?.color ?? colorProp, + ); + const activeColor = color || 'primary'; const inactiveColor = color || 'neutral'; - const activeVariant = variant || 'solid'; - const inactiveVariant = variant || 'outlined'; const ownerState = { ...props, @@ -254,7 +261,7 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { overlay, focusVisible, color: isCheckboxActive ? activeColor : inactiveColor, - variant: isCheckboxActive ? activeVariant : inactiveVariant, + variant, size, }; diff --git a/packages/mui-joy/src/Checkbox/CheckboxProps.ts b/packages/mui-joy/src/Checkbox/CheckboxProps.ts index 70e493bbc92da4..f8908b4b2fc01a 100644 --- a/packages/mui-joy/src/Checkbox/CheckboxProps.ts +++ b/packages/mui-joy/src/Checkbox/CheckboxProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { UseSwitchParameters } from '@mui/base/SwitchUnstyled'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; export type CheckboxSlot = 'root' | 'checkbox' | 'action' | 'input' | 'label'; @@ -105,7 +105,7 @@ export type CheckboxProps< }, > = OverrideProps, D>; -export interface CheckboxOwnerState extends CheckboxProps { +export interface CheckboxOwnerState extends ApplyColorInversion { /** * If `true`, the checkbox's focus is visible. */ diff --git a/packages/mui-joy/src/Checkbox/checkboxClasses.ts b/packages/mui-joy/src/Checkbox/checkboxClasses.ts index 41c5a8725fdfcb..0d612a729a6759 100644 --- a/packages/mui-joy/src/Checkbox/checkboxClasses.ts +++ b/packages/mui-joy/src/Checkbox/checkboxClasses.ts @@ -31,6 +31,8 @@ export interface CheckboxClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -67,6 +69,7 @@ const checkboxClasses: CheckboxClasses = generateUtilityClasses('JoyCheckbox', [ 'colorNeutral', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/Chip/Chip.test.js b/packages/mui-joy/src/Chip/Chip.test.js index ba26272452aeb7..7e6548bd2cd823 100644 --- a/packages/mui-joy/src/Chip/Chip.test.js +++ b/packages/mui-joy/src/Chip/Chip.test.js @@ -1,7 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { createRenderer, describeConformance, fireEvent } from 'test/utils'; +import { + createRenderer, + describeConformance, + describeJoyColorInversion, + fireEvent, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Chip, { chipClasses as classes } from '@mui/joy/Chip'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -34,6 +39,8 @@ describe('', () => { }), ); + describeJoyColorInversion(, { muiName: 'JoyChip', classes }); + it('renders children', () => { const { getByText } = render( diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx index a0eed3a96d5103..291428b3771604 100644 --- a/packages/mui-joy/src/Chip/Chip.tsx +++ b/packages/mui-joy/src/Chip/Chip.tsx @@ -6,6 +6,7 @@ import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize, unstable_useId as useId } from '@mui/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import chipClasses, { getChipUtilityClass } from './chipClasses'; import { ChipProps, ChipOwnerState, ChipTypeMap } from './ChipProps'; import ChipContext from './ChipContext'; @@ -94,7 +95,7 @@ const ChipRoot = styled('div', { verticalAlign: 'middle', boxSizing: 'border-box', [`&.${chipClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], + color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, }, }, ...(!ownerState.clickable @@ -108,7 +109,7 @@ const ChipRoot = styled('div', { : [ { '--variant-borderWidth': '0px', - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, }, ]), ]; @@ -202,7 +203,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { const { children, className, - color = 'primary', + color: colorProp = 'primary', slotProps = {}, onClick, disabled = false, @@ -212,6 +213,8 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { endDecorator, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const clickable = !!onClick || !!slotProps.action; const ownerState: ChipOwnerState = { @@ -284,7 +287,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { }); const chipContextValue = React.useMemo( - () => ({ disabled, variant, color }), + () => ({ disabled, variant, color: color === 'context' ? undefined : color }), [color, disabled, variant], ); diff --git a/packages/mui-joy/src/Chip/ChipProps.ts b/packages/mui-joy/src/Chip/ChipProps.ts index 848c6aff25ca8e..4a39ec66b9615c 100644 --- a/packages/mui-joy/src/Chip/ChipProps.ts +++ b/packages/mui-joy/src/Chip/ChipProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type ChipSlot = 'root' | 'label' | 'action' | 'startDecorator' | 'endDecorator'; @@ -80,7 +80,7 @@ export type ChipProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface ChipOwnerState extends ChipProps { +export interface ChipOwnerState extends ApplyColorInversion { /** * If `true`, the chip is clickable. */ diff --git a/packages/mui-joy/src/Chip/chipClasses.ts b/packages/mui-joy/src/Chip/chipClasses.ts index 6c70e90450b5a0..53ecbf73b7d765 100644 --- a/packages/mui-joy/src/Chip/chipClasses.ts +++ b/packages/mui-joy/src/Chip/chipClasses.ts @@ -15,6 +15,8 @@ export interface ChipClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `disabled={true}`. */ disabled: string; /** Styles applied to the endDecorator element if supplied. */ @@ -62,6 +64,7 @@ const chipClasses: ChipClasses = generateUtilityClasses('JoyChip', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'disabled', 'endDecorator', 'focusVisible', diff --git a/packages/mui-joy/src/ChipDelete/ChipDelete.test.js b/packages/mui-joy/src/ChipDelete/ChipDelete.test.js index 55fd5647ce561c..ad332a90a290dc 100644 --- a/packages/mui-joy/src/ChipDelete/ChipDelete.test.js +++ b/packages/mui-joy/src/ChipDelete/ChipDelete.test.js @@ -1,7 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { createRenderer, describeConformance, act, fireEvent } from 'test/utils'; +import { + createRenderer, + describeConformance, + describeJoyColorInversion, + act, + fireEvent, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Chip from '@mui/joy/Chip'; import ChipDelete, { chipDeleteClasses as classes } from '@mui/joy/ChipDelete'; @@ -22,6 +28,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyChipDelete', classes }); + describe('Chip context', () => { it('disabled', () => { const { getByRole } = render( diff --git a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx index 19b5309dc467e9..afc5009e1dd622 100644 --- a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx +++ b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx @@ -6,6 +6,7 @@ import { unstable_composeClasses as composeClasses, useButton } from '@mui/base' import { useSlotProps } from '@mui/base/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import Cancel from '../internal/svg-icons/Cancel'; import chipDeleteClasses, { getChipDeleteUtilityClass } from './chipDeleteClasses'; import { ChipDeleteProps, ChipDeleteOwnerState, ChipDeleteTypeMap } from './ChipDeleteProps'; @@ -82,8 +83,9 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) { ...other } = props; const chipContext = React.useContext(ChipContext); - const color = colorProp || chipContext.color || 'primary'; const variant = variantProp || chipVariantMapping[chipContext.variant!] || 'solid'; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp || chipContext.color || 'primary'); const disabled = disabledProp ?? chipContext.disabled; const buttonRef = React.useRef(null); diff --git a/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts b/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts index f94220f58e1001..870dafd8db3405 100644 --- a/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts +++ b/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type ChipDeleteSlot = 'root'; @@ -49,7 +49,7 @@ export type ChipDeleteProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface ChipDeleteOwnerState extends ChipDeleteProps { +export interface ChipDeleteOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts b/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts index 6b44ed513cc84e..da156ca19badc4 100644 --- a/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts +++ b/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts @@ -19,6 +19,8 @@ export interface ChipDeleteClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="solid"`. */ @@ -43,6 +45,7 @@ const chipDeleteClasses: ChipDeleteClasses = generateUtilityClasses('JoyChipDele 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSolid', 'variantSoft', diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx index 99422dcd1fb5e1..9f1d8f49f646c4 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx +++ b/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import CircularProgress, { circularProgressClasses as classes } from '@mui/joy/CircularProgress'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -28,6 +28,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyCircularProgress', classes }); + describe('prop: determinate', () => { it('should render a determinate circular progress', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx index 9477c90cdeadb5..f239e32603fa20 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx +++ b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx @@ -7,6 +7,7 @@ import { unstable_composeClasses as composeClasses } from '@mui/base'; import { css, keyframes } from '@mui/system'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import { getCircularProgressUtilityClass } from './circularProgressClasses'; import { @@ -206,7 +207,7 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref const { children, className, - color = 'primary', + color: colorProp = 'primary', size = 'md', variant = 'soft', thickness, @@ -214,6 +215,8 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref value = determinate ? 0 : 25, // `25` is the 1/4 of the circle. ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts index f1e053c23dc860..215e3f7f25c41d 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts +++ b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type CircularProgressSlot = 'root' | 'svg' | 'track' | 'progress'; @@ -68,7 +68,7 @@ export type CircularProgressProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface CircularProgressOwnerState extends CircularProgressProps { +export interface CircularProgressOwnerState extends ApplyColorInversion { /** * @internal the explicit size on the instance: */ diff --git a/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts b/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts index fa3065de409a7a..deec0ccf079ea6 100644 --- a/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts +++ b/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts @@ -23,6 +23,8 @@ export interface CircularProgressClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -59,6 +61,7 @@ const circularProgressClasses: CircularProgressClasses = generateUtilityClasses( 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/IconButton/IconButton.test.js b/packages/mui-joy/src/IconButton/IconButton.test.js index 164e66081a72ec..033f9d85ec8657 100644 --- a/packages/mui-joy/src/IconButton/IconButton.test.js +++ b/packages/mui-joy/src/IconButton/IconButton.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import IconButton, { iconButtonClasses as classes } from '@mui/joy/IconButton'; import { ThemeProvider } from '@mui/joy/styles'; @@ -21,6 +21,8 @@ describe('Joy ', () => { skip: ['propsSpread', 'componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyIconButton', classes }); + it('by default, should render with the root, variantSolid, sizeMd and colorPrimary classes', () => { const { getByRole } = render(Hello World); const button = getByRole('button'); diff --git a/packages/mui-joy/src/IconButton/IconButton.tsx b/packages/mui-joy/src/IconButton/IconButton.tsx index 5ba634b7627f70..2afbe401ffd568 100644 --- a/packages/mui-joy/src/IconButton/IconButton.tsx +++ b/packages/mui-joy/src/IconButton/IconButton.tsx @@ -4,6 +4,7 @@ import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef } import { useButton } from '@mui/base/ButtonUnstyled'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import iconButtonClasses, { getIconButtonUtilityClass } from './iconButtonClasses'; import { IconButtonOwnerState, IconButtonTypeMap, ExtendIconButton } from './IconButtonProps'; @@ -102,11 +103,13 @@ const IconButton = React.forwardRef(function IconButton(inProps, ref) { children, action, component = 'button', - color = 'primary', + color: colorProp = 'primary', variant = 'soft', size = 'md', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const buttonRef = React.useRef(null); const handleRef = useForkRef(buttonRef, ref); diff --git a/packages/mui-joy/src/IconButton/IconButtonProps.ts b/packages/mui-joy/src/IconButton/IconButtonProps.ts index 3aab381f8f04f8..80929e799bd854 100644 --- a/packages/mui-joy/src/IconButton/IconButtonProps.ts +++ b/packages/mui-joy/src/IconButton/IconButtonProps.ts @@ -5,7 +5,7 @@ import { OverridableTypeMap, OverrideProps, } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type IconButtonSlot = 'root'; @@ -73,7 +73,7 @@ export type IconButtonProps< }, > = OverrideProps, D>; -export interface IconButtonOwnerState extends IconButtonProps { +export interface IconButtonOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/IconButton/iconButtonClasses.ts b/packages/mui-joy/src/IconButton/iconButtonClasses.ts index 2d89f71fc67376..3028400351d277 100644 --- a/packages/mui-joy/src/IconButton/iconButtonClasses.ts +++ b/packages/mui-joy/src/IconButton/iconButtonClasses.ts @@ -15,6 +15,8 @@ export interface IconButtonClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -49,6 +51,7 @@ const iconButtonClasses: IconButtonClasses = generateUtilityClasses('JoyIconButt 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Input/Input.test.js b/packages/mui-joy/src/Input/Input.test.js index 5f876f581a5302..77d37fa9f47e35 100644 --- a/packages/mui-joy/src/Input/Input.test.js +++ b/packages/mui-joy/src/Input/Input.test.js @@ -1,7 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { describeConformance, createRenderer, screen, act } from 'test/utils'; +import { + describeConformance, + describeJoyColorInversion, + createRenderer, + screen, + act, +} from 'test/utils'; import Input, { inputClasses as classes } from '@mui/joy/Input'; import { ThemeProvider } from '@mui/joy/styles'; @@ -26,6 +32,8 @@ describe('Joy ', () => { skip: ['propsSpread', 'componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyInput', classes }); + it('should have error classes', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.error); diff --git a/packages/mui-joy/src/Input/Input.tsx b/packages/mui-joy/src/Input/Input.tsx index 3d4c6c6ceb4eac..1bd0a414566767 100644 --- a/packages/mui-joy/src/Input/Input.tsx +++ b/packages/mui-joy/src/Input/Input.tsx @@ -5,6 +5,7 @@ import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; import { EventHandlers } from '@mui/base/utils'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import { InputTypeMap, InputProps, InputOwnerState } from './InputProps'; import inputClasses, { getInputUtilityClass } from './inputClasses'; @@ -289,7 +290,8 @@ const Input = React.forwardRef(function Input(inProps, ref) { const error = inProps.error ?? formControl?.error ?? errorProp; const size = inProps.size ?? formControl?.size ?? sizeProp; - const color = error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, error ? 'danger' : formControl?.color ?? colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/Input/inputClasses.ts b/packages/mui-joy/src/Input/inputClasses.ts index 8d11b6abbdcec8..e69e7b353ea00c 100644 --- a/packages/mui-joy/src/Input/inputClasses.ts +++ b/packages/mui-joy/src/Input/inputClasses.ts @@ -25,6 +25,8 @@ export interface InputClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -68,6 +70,7 @@ const inputClasses: InputClasses = generateUtilityClasses('JoyInput', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx index f5c135e0ecb978..721fd5912a9485 100644 --- a/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx +++ b/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import LinearProgress, { linearProgressClasses as classes } from '@mui/joy/LinearProgress'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -19,6 +19,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyLinearProgress', classes }); + describe('prop: determinate', () => { it('should render a determinate circular progress', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx index bfc14be306af41..66e897e7429416 100644 --- a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx +++ b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx @@ -7,6 +7,7 @@ import { unstable_composeClasses as composeClasses } from '@mui/base'; import { css, keyframes } from '@mui/system'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import { useColorInversion } from '../styles/ColorInversion'; import { getLinearProgressUtilityClass } from './linearProgressClasses'; import { LinearProgressOwnerState, @@ -146,7 +147,7 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { children, className, component, - color = 'primary', + color: colorProp = 'primary', size = 'md', variant = 'soft', thickness, @@ -155,6 +156,8 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { style, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts b/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts index b72f310b3000fe..090e9687da2dd0 100644 --- a/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts +++ b/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type LinearProgressSlot = 'root'; @@ -56,7 +56,7 @@ export type LinearProgressProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface LinearProgressOwnerState extends LinearProgressProps { +export interface LinearProgressOwnerState extends ApplyColorInversion { /** * @internal the explicit size on the instance: */ diff --git a/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts index 9df29535022d2e..0e1437aa8bd3ad 100644 --- a/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts +++ b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts @@ -17,6 +17,8 @@ export interface LinearProgressClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -48,6 +50,7 @@ const linearProgressClasses: LinearProgressClasses = generateUtilityClasses('Joy 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/Link/Link.test.js b/packages/mui-joy/src/Link/Link.test.js index 858c8875e34b89..e7bb048c3d4739 100644 --- a/packages/mui-joy/src/Link/Link.test.js +++ b/packages/mui-joy/src/Link/Link.test.js @@ -1,7 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { act, createRenderer, fireEvent, describeConformance } from 'test/utils'; +import { + act, + createRenderer, + fireEvent, + describeConformance, + describeJoyColorInversion, +} from 'test/utils'; import Link, { linkClasses as classes } from '@mui/joy/Link'; import Typography from '@mui/joy/Typography'; import { ThemeProvider } from '@mui/joy/styles'; @@ -40,6 +46,8 @@ describe('', () => { }), ); + describeJoyColorInversion(, { muiName: 'JoyLink', classes }); + it('should render children', () => { const { queryByText } = render(Home); diff --git a/packages/mui-joy/src/Link/Link.tsx b/packages/mui-joy/src/Link/Link.tsx index cf2336f9bba2c8..8b6e35cd0b8fee 100644 --- a/packages/mui-joy/src/Link/Link.tsx +++ b/packages/mui-joy/src/Link/Link.tsx @@ -10,6 +10,7 @@ import { import { unstable_extendSxProp as extendSxProp } from '@mui/system'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import linkClasses, { getLinkUtilityClass } from './linkClasses'; import { LinkProps, LinkOwnerState, LinkTypeMap } from './LinkProps'; @@ -94,9 +95,11 @@ const LinkRoot = styled('a', { borderRadius: theme.vars.radius.xs, padding: 0, // Remove the padding in Firefox cursor: 'pointer', - textDecorationColor: `rgba(${ - theme.vars.palette[ownerState.color!]?.mainChannel - } / var(--Link-underlineOpacity, 0.72))`, + ...(ownerState.color !== 'context' && { + textDecorationColor: `rgba(${ + theme.vars.palette[ownerState.color!]?.mainChannel + } / var(--Link-underlineOpacity, 0.72))`, + }), ...(ownerState.variant ? { paddingBlock: 'min(0.15em, 4px)', @@ -106,10 +109,14 @@ const LinkRoot = styled('a', { }), } : { - color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)`, + ...(ownerState.color !== 'context' && { + color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)`, + }), [`&.${linkClasses.disabled}`]: { pointerEvents: 'none', - color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 0.6)`, + ...(ownerState.color !== 'context' && { + color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 0.6)`, + }), }, }), userSelect: 'none', @@ -153,14 +160,17 @@ const LinkRoot = styled('a', { const Link = React.forwardRef(function Link(inProps, ref) { const { - color = 'primary', + color: colorProp = 'primary', textColor, + variant, ...themeProps } = useThemeProps({ props: inProps, name: 'JoyLink', }); + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const nested = React.useContext(TypographyContext); const props = extendSxProp({ ...themeProps, color: textColor }) as LinkProps; @@ -173,7 +183,6 @@ const Link = React.forwardRef(function Link(inProps, ref) { level: levelProp = 'body1', overlay = false, underline = 'hover', - variant, endDecorator, startDecorator, ...other diff --git a/packages/mui-joy/src/Link/LinkProps.ts b/packages/mui-joy/src/Link/LinkProps.ts index 9362780a4e396b..93e5ec8857cd2a 100644 --- a/packages/mui-joy/src/Link/LinkProps.ts +++ b/packages/mui-joy/src/Link/LinkProps.ts @@ -4,6 +4,7 @@ import { ColorPaletteProp, SxProps, SystemProps, + ApplyColorInversion, TypographySystem, VariantProp, } from '../styles/types'; @@ -91,7 +92,7 @@ export type LinkProps< }, > = OverrideProps, D>; -export interface LinkOwnerState extends LinkProps { +export interface LinkOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/Link/linkClasses.ts b/packages/mui-joy/src/Link/linkClasses.ts index 0fa388bdc91d78..bcde0b06ab16c3 100644 --- a/packages/mui-joy/src/Link/linkClasses.ts +++ b/packages/mui-joy/src/Link/linkClasses.ts @@ -15,6 +15,8 @@ export interface LinkClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `disabled={true}`. */ disabled: string; /** State class applied to the root element if the link is keyboard focused. */ @@ -73,6 +75,7 @@ const linkClasses: LinkClasses = generateUtilityClasses('JoyLink', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'focusVisible', 'variantPlain', 'variantOutlined', diff --git a/packages/mui-joy/src/List/List.test.js b/packages/mui-joy/src/List/List.test.js index 6ed684e29fa6ad..948f7d06762ade 100644 --- a/packages/mui-joy/src/List/List.test.js +++ b/packages/mui-joy/src/List/List.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import List, { listClasses as classes } from '@mui/joy/List'; import ListItem from '@mui/joy/ListItem'; @@ -24,6 +24,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyList', classes }); + it('should have root className', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.root); diff --git a/packages/mui-joy/src/List/List.tsx b/packages/mui-joy/src/List/List.tsx index b4af0a8cde9116..477b4d3d6f89a7 100644 --- a/packages/mui-joy/src/List/List.tsx +++ b/packages/mui-joy/src/List/List.tsx @@ -7,6 +7,7 @@ import composeClasses from '@mui/base/composeClasses'; import { MenuUnstyledContext } from '@mui/base/MenuUnstyled'; import { SelectUnstyledContext } from '@mui/base/SelectUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { ListProps, ListOwnerState, ListTypeMap } from './ListProps'; import { getListUtilityClass } from './listClasses'; import NestedListContext from './NestedListContext'; @@ -160,10 +161,12 @@ const List = React.forwardRef(function List(inProps, ref) { row = false, wrap = false, variant = 'plain', - color = 'neutral', + color: colorProp = 'neutral', role: roleProp, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); let role; if (menuContext || selectContext) { diff --git a/packages/mui-joy/src/List/ListProps.ts b/packages/mui-joy/src/List/ListProps.ts index 4eb6123163ee63..e0c5636694edc1 100644 --- a/packages/mui-joy/src/List/ListProps.ts +++ b/packages/mui-joy/src/List/ListProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type ListSlot = 'root'; @@ -56,7 +56,7 @@ export type ListProps< }, > = OverrideProps, D>; -export interface ListOwnerState extends ListProps { +export interface ListOwnerState extends ApplyColorInversion { /** * @internal * The explicit size specified on the element instance. diff --git a/packages/mui-joy/src/List/listClasses.ts b/packages/mui-joy/src/List/listClasses.ts index 574d3ce2c3220a..431c29214f62c3 100644 --- a/packages/mui-joy/src/List/listClasses.ts +++ b/packages/mui-joy/src/List/listClasses.ts @@ -27,6 +27,8 @@ export interface ListClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -57,6 +59,7 @@ const listClasses: ListClasses = generateUtilityClasses('JoyList', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/ListItem/ListItem.test.js b/packages/mui-joy/src/ListItem/ListItem.test.js index ec32e3140f1660..bc10d7db269953 100644 --- a/packages/mui-joy/src/ListItem/ListItem.test.js +++ b/packages/mui-joy/src/ListItem/ListItem.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import MenuList from '@mui/joy/MenuList'; import List from '@mui/joy/List'; @@ -27,6 +27,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyListItem', classes }); + it('should have root className', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.root); diff --git a/packages/mui-joy/src/ListItem/ListItem.tsx b/packages/mui-joy/src/ListItem/ListItem.tsx index b3f7dd57e38124..715af23dd0c435 100644 --- a/packages/mui-joy/src/ListItem/ListItem.tsx +++ b/packages/mui-joy/src/ListItem/ListItem.tsx @@ -9,8 +9,9 @@ import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; import { MenuUnstyledContext } from '@mui/base/MenuUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; -import { ListItemProps, ListItemOwnerState, ListItemTypeMap } from './ListItemProps'; +import { ListItemOwnerState, ListItemTypeMap } from './ListItemProps'; import { getListItemUtilityClass } from './listItemClasses'; import NestedListContext from '../List/NestedListContext'; import RowListContext from '../List/RowListContext'; @@ -112,7 +113,7 @@ const ListItemStartAction = styled('div', { name: 'JoyListItem', slot: 'StartAction', overridesResolver: (props, styles) => styles.startAction, -})<{ ownerState: ListItemProps }>(({ ownerState }) => ({ +})<{ ownerState: ListItemOwnerState }>(({ ownerState }) => ({ display: 'inherit', position: 'absolute', top: ownerState.nested ? 'calc(var(--List-item-minHeight) / 2)' : '50%', @@ -125,7 +126,7 @@ const ListItemEndAction = styled('div', { name: 'JoyListItem', slot: 'StartAction', overridesResolver: (props, styles) => styles.startAction, -})<{ ownerState: ListItemProps }>(({ ownerState }) => ({ +})<{ ownerState: ListItemOwnerState }>(({ ownerState }) => ({ display: 'inherit', position: 'absolute', top: ownerState.nested ? 'calc(var(--List-item-minHeight) / 2)' : '50%', @@ -153,12 +154,14 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { nested = false, sticky = false, variant = 'plain', - color = 'neutral', + color: colorProp = 'neutral', startAction, endAction, role: roleProp, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const [subheaderId, setSubheaderId] = React.useState(''); @@ -178,6 +181,7 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { } const ownerState = { + ...props, sticky, startAction, endAction, @@ -189,7 +193,6 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { nested, component, role, - ...props, }; const classes = useUtilityClasses(ownerState); diff --git a/packages/mui-joy/src/ListItem/ListItemProps.ts b/packages/mui-joy/src/ListItem/ListItemProps.ts index 171328792a7888..2dc941b5e8e9ba 100644 --- a/packages/mui-joy/src/ListItem/ListItemProps.ts +++ b/packages/mui-joy/src/ListItem/ListItemProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type ListItemSlot = 'root' | 'startAction' | 'endAction'; @@ -67,7 +67,7 @@ export type ListItemProps< }, > = OverrideProps, D>; -export interface ListItemOwnerState extends ListItemProps { +export interface ListItemOwnerState extends ApplyColorInversion { /** * If `true`, the element is rendered in a horizontal list. * @internal diff --git a/packages/mui-joy/src/ListItem/listItemClasses.ts b/packages/mui-joy/src/ListItem/listItemClasses.ts index f5feeaebd3fdb7..0dd5c74faa9c56 100644 --- a/packages/mui-joy/src/ListItem/listItemClasses.ts +++ b/packages/mui-joy/src/ListItem/listItemClasses.ts @@ -25,6 +25,8 @@ export interface ListItemClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `variant="plain"`. */ variantPlain: string; /** State class applied to the root element if `variant="soft"`. */ @@ -54,6 +56,7 @@ const listItemClasses: ListItemClasses = generateUtilityClasses('JoyListItem', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSoft', 'variantOutlined', diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.test.js b/packages/mui-joy/src/ListItemButton/ListItemButton.test.js index abacdc0b318c0d..b3073e17e32d43 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButton.test.js +++ b/packages/mui-joy/src/ListItemButton/ListItemButton.test.js @@ -1,6 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, act, fireEvent } from 'test/utils'; +import { + describeConformance, + describeJoyColorInversion, + createRenderer, + act, + fireEvent, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import ListItemButton, { listItemButtonClasses as classes } from '@mui/joy/ListItemButton'; @@ -19,6 +25,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyListItemButton', classes }); + it('should render with the selected class', () => { const { getByRole } = render(); expect(getByRole('button')).to.have.class(classes.selected); diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx index a80e2aab288491..1489bb92218028 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx +++ b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx @@ -5,6 +5,7 @@ import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef } import composeClasses from '@mui/base/composeClasses'; import { useButton } from '@mui/base/ButtonUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { ListItemButtonOwnerState, ExtendListItemButton, @@ -45,7 +46,7 @@ export const StyledListItemButton = styled('div')<{ ownerState: ListItemButtonOw }), ...(ownerState.disabled && { '--List-decorator-color': - theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], + theme.variants?.[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, }), WebkitTapHighlightColor: 'transparent', boxSizing: 'border-box', @@ -120,11 +121,14 @@ const ListItemButton = React.forwardRef(function ListItemButton(inProps, ref) { orientation = 'horizontal', role, selected = false, - color = selected ? 'primary' : 'neutral', + color: colorProp = selected ? 'primary' : 'neutral', variant = 'plain', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); + const buttonRef = React.useRef(null); const handleRef = useForkRef(buttonRef, ref); diff --git a/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts b/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts index ecbfdd857f73b5..853bd6a41b7036 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts +++ b/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts @@ -5,7 +5,7 @@ import { OverridableTypeMap, OverrideProps, } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; import { ListItemButtonClasses } from './listItemButtonClasses'; export type ListItemButtonSlot = 'root'; @@ -94,7 +94,7 @@ export type ListItemButtonProps< }, > = OverrideProps, D>; -export interface ListItemButtonOwnerState extends ListItemButtonProps { +export interface ListItemButtonOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx b/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx index e2624ce6d676cd..ff7eb82c9ef59e 100644 --- a/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx +++ b/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import ListSubheader, { listSubheaderClasses as classes } from '@mui/joy/ListSubheader'; import ListSubheaderDispatch from './ListSubheaderContext'; @@ -21,6 +21,11 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { + muiName: 'JoyListSubheader', + classes, + }); + it('should have root className', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.root); diff --git a/packages/mui-joy/src/ListSubheader/ListSubheader.tsx b/packages/mui-joy/src/ListSubheader/ListSubheader.tsx index 0ac1ce43fe88e0..1d34452f0b114c 100644 --- a/packages/mui-joy/src/ListSubheader/ListSubheader.tsx +++ b/packages/mui-joy/src/ListSubheader/ListSubheader.tsx @@ -5,6 +5,7 @@ import { OverridableComponent } from '@mui/types'; import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui/utils'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { ListSubheaderOwnerState, ListSubheaderTypeMap } from './ListSubheaderProps'; import { getListSubheaderUtilityClass } from './listSubheaderClasses'; import ListSubheaderDispatch from './ListSubheaderContext'; @@ -47,9 +48,10 @@ const ListSubheaderRoot = styled('div', { zIndex: 1, background: 'var(--List-item-stickyBackground)', }), - color: ownerState.color - ? `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)` - : theme.vars.palette.text.tertiary, + color: + ownerState.color && ownerState.color !== 'context' + ? `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)` + : theme.vars.palette.text.tertiary, ...theme.variants[ownerState.variant!]?.[ownerState.color!], })); @@ -66,9 +68,11 @@ const ListSubheader = React.forwardRef(function ListSubheader(inProps, ref) { id: idOverride, sticky = false, variant, - color, + color: colorProp, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const id = useId(idOverride); const setSubheaderId = React.useContext(ListSubheaderDispatch); diff --git a/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts b/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts index 049653316bd689..c0e5a2e18d33e2 100644 --- a/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts +++ b/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type ListSubheaderSlot = 'root'; @@ -42,4 +42,4 @@ export type ListSubheaderProps< }, > = OverrideProps, D>; -export interface ListSubheaderOwnerState extends ListSubheaderProps {} +export interface ListSubheaderOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts b/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts index 5662fa2210b053..de8f9045987e8a 100644 --- a/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts +++ b/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts @@ -17,6 +17,8 @@ export interface ListSubheaderClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `variant="plain"`. */ variantPlain: string; /** State class applied to the root element if `variant="soft"`. */ @@ -42,6 +44,7 @@ const listSubheaderClasses: ListSubheaderClasses = generateUtilityClasses('JoyLi 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSoft', 'variantOutlined', diff --git a/packages/mui-joy/src/Menu/Menu.test.js b/packages/mui-joy/src/Menu/Menu.test.js index 0af5376984aa75..5887bc3724a3e2 100644 --- a/packages/mui-joy/src/Menu/Menu.test.js +++ b/packages/mui-joy/src/Menu/Menu.test.js @@ -1,7 +1,14 @@ import * as React from 'react'; import { spy } from 'sinon'; import { expect } from 'chai'; -import { act, createRenderer, describeConformance, screen, fireEvent } from 'test/utils'; +import { + act, + createRenderer, + describeConformance, + screen, + fireEvent, + describeJoyColorInversion, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Menu, { menuClasses as classes } from '@mui/joy/Menu'; import MenuItem from '@mui/joy/MenuItem'; @@ -30,6 +37,20 @@ describe('Joy

    ', () => { ], })); + describeJoyColorInversion( + document.createElement('div')} + data-testid="test-element" + />, + { + muiName: 'JoyMenu', + classes, + portalSlot: 'root', + }, + ); + it('should pass onClose prop to Popover', () => { const handleClose = spy(); render( diff --git a/packages/mui-joy/src/Menu/Menu.tsx b/packages/mui-joy/src/Menu/Menu.tsx index 0f1f6dcff328bb..30d228154e76a1 100644 --- a/packages/mui-joy/src/Menu/Menu.tsx +++ b/packages/mui-joy/src/Menu/Menu.tsx @@ -9,10 +9,11 @@ import PopperUnstyled from '@mui/base/PopperUnstyled'; import { StyledList } from '../List/List'; import ListProvider, { scopedVariables } from '../List/ListProvider'; import { styled, useThemeProps } from '../styles'; -import { MenuTypeMap, MenuProps, MenuOwnerState } from './MenuProps'; +import ColorInversion, { useColorInversion } from '../styles/ColorInversion'; +import { MenuTypeMap, MenuOwnerState } from './MenuProps'; import { getMenuUtilityClass } from './menuClasses'; -const useUtilityClasses = (ownerState: MenuProps) => { +const useUtilityClasses = (ownerState: MenuOwnerState) => { const { open, variant, color, size } = ownerState; const slots = { root: [ @@ -62,7 +63,7 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { anchorEl, children, component, - color = 'neutral', + color: colorProp = 'neutral', disablePortal = false, keepMounted = false, id, @@ -73,6 +74,8 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { size = 'md', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = disablePortal ? getColor(inProps.color, colorProp) : colorProp; const { registerItem, @@ -128,17 +131,6 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { ownerState, }); - const contextValue: MenuUnstyledContextType = React.useMemo( - () => ({ - registerItem, - unregisterItem, - getItemState, - getItemProps, - open, - }), - [getItemProps, getItemState, open, registerItem, unregisterItem], - ); - const modifiers = React.useMemo( () => [ { @@ -152,13 +144,38 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { [modifiersProp], ); - return ( + const contextValue: MenuUnstyledContextType = React.useMemo( + () => ({ + registerItem, + unregisterItem, + getItemState, + getItemProps, + open, + }), + [getItemProps, getItemState, open, registerItem, unregisterItem], + ); + + const result = ( - {children} + + {disablePortal ? ( + children + ) : ( + // For portal popup, the children should not inherit color inversion from the upper parent. + {children} + )} + ); + + return disablePortal ? ( + result + ) : ( + // For portal popup, the children should not inherit color inversion from the upper parent. + {result} + ); }) as OverridableComponent; Menu.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/Menu/MenuProps.ts b/packages/mui-joy/src/Menu/MenuProps.ts index 40ec92b94d3553..737ad7697ca151 100644 --- a/packages/mui-joy/src/Menu/MenuProps.ts +++ b/packages/mui-joy/src/Menu/MenuProps.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { OverrideProps, OverridableStringUnion } from '@mui/types'; import { PopperUnstyledProps } from '@mui/base/PopperUnstyled'; import { MenuUnstyledActions } from '@mui/base/MenuUnstyled'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type MenuSlot = 'root'; @@ -56,4 +56,4 @@ export type MenuProps< P = {}, > = OverrideProps, D>; -export interface MenuOwnerState extends MenuProps {} +export interface MenuOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Menu/menuClasses.ts b/packages/mui-joy/src/Menu/menuClasses.ts index d5ca76e710c0bb..89ff38f2e5fc36 100644 --- a/packages/mui-joy/src/Menu/menuClasses.ts +++ b/packages/mui-joy/src/Menu/menuClasses.ts @@ -17,6 +17,8 @@ export interface MenuClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -48,6 +50,7 @@ const menuClasses: MenuClasses = generateUtilityClasses('JoyMenu', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/MenuItem/MenuItem.test.js b/packages/mui-joy/src/MenuItem/MenuItem.test.js index 9ddb0782e50ea7..b4bccbc0ae3297 100644 --- a/packages/mui-joy/src/MenuItem/MenuItem.test.js +++ b/packages/mui-joy/src/MenuItem/MenuItem.test.js @@ -1,7 +1,14 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { act, describeConformance, createRenderer, fireEvent, screen } from 'test/utils'; +import { + act, + describeConformance, + createRenderer, + fireEvent, + screen, + describeJoyColorInversion, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import { MenuUnstyledContext } from '@mui/base/MenuUnstyled'; import MenuItem, { menuItemClasses as classes } from '@mui/joy/MenuItem'; @@ -52,6 +59,14 @@ describe('Joy ', () => { skip: ['propsSpread', 'componentsProp', 'classesRoot', 'reactTestRenderer'], })); + describeJoyColorInversion(, { + muiName: 'JoyMenuItem', + classes, + wrapper: (node) => ( + {node} + ), + }); + it('should render with the variant class', () => { const { getByRole } = render(); expect(getByRole('menuitem')).to.have.class(classes.variantOutlined); diff --git a/packages/mui-joy/src/MenuItem/MenuItem.tsx b/packages/mui-joy/src/MenuItem/MenuItem.tsx index 5d7598cbf394f5..86cf9605281e91 100644 --- a/packages/mui-joy/src/MenuItem/MenuItem.tsx +++ b/packages/mui-joy/src/MenuItem/MenuItem.tsx @@ -6,16 +6,12 @@ import { useSlotProps } from '@mui/base/utils'; import { useMenuItem } from '@mui/base/MenuItemUnstyled'; import { StyledListItemButton } from '../ListItemButton/ListItemButton'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { getMenuItemUtilityClass } from './menuItemClasses'; -import { - MenuItemProps, - MenuItemOwnerState, - ExtendMenuItem, - MenuItemTypeMap, -} from './MenuItemProps'; +import { MenuItemOwnerState, ExtendMenuItem, MenuItemTypeMap } from './MenuItemProps'; import RowListContext from '../List/RowListContext'; -const useUtilityClasses = (ownerState: MenuItemProps & { focusVisible?: boolean }) => { +const useUtilityClasses = (ownerState: MenuItemOwnerState) => { const { focusVisible, disabled, selected, color, variant } = ownerState; const slots = { root: [ @@ -52,10 +48,12 @@ const MenuItem = React.forwardRef(function MenuItem(inProps, ref) { disabled: disabledProp = false, component = 'li', selected = false, - color = selected ? 'primary' : 'neutral', + color: colorProp = selected ? 'primary' : 'neutral', variant = 'plain', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const { getRootProps, disabled, focusVisible } = useMenuItem({ disabled: disabledProp, diff --git a/packages/mui-joy/src/MenuItem/MenuItemProps.ts b/packages/mui-joy/src/MenuItem/MenuItemProps.ts index 0a3c6e690b4c11..50c41b04f2466c 100644 --- a/packages/mui-joy/src/MenuItem/MenuItemProps.ts +++ b/packages/mui-joy/src/MenuItem/MenuItemProps.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import { OverridableComponent, OverridableTypeMap, OverrideProps } from '@mui/types'; +import { ApplyColorInversion } from '../styles/types'; import { ListItemButtonProps } from '../ListItemButton/ListItemButtonProps'; export type MenuItemSlot = 'root'; @@ -25,7 +26,7 @@ export type MenuItemProps< }, > = OverrideProps, D>; -export interface MenuItemOwnerState extends MenuItemProps { +export interface MenuItemOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/MenuItem/menuItemClasses.ts b/packages/mui-joy/src/MenuItem/menuItemClasses.ts index 603847d79d2a76..19d99c80a7144c 100644 --- a/packages/mui-joy/src/MenuItem/menuItemClasses.ts +++ b/packages/mui-joy/src/MenuItem/menuItemClasses.ts @@ -21,6 +21,8 @@ export interface MenuItemClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `variant="plain"`. */ variantPlain: string; /** State class applied to the root element if `variant="soft"`. */ @@ -48,6 +50,7 @@ const menuItemClasses: MenuItemClasses = generateUtilityClasses('JoyMenuItem', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSoft', 'variantOutlined', diff --git a/packages/mui-joy/src/MenuList/MenuList.test.js b/packages/mui-joy/src/MenuList/MenuList.test.js index a438cbedcaac3a..b51130624181fe 100644 --- a/packages/mui-joy/src/MenuList/MenuList.test.js +++ b/packages/mui-joy/src/MenuList/MenuList.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import MenuList, { menuListClasses as classes } from '@mui/joy/MenuList'; @@ -19,6 +19,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyMenuList', classes }); + it('should have root className', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.root); diff --git a/packages/mui-joy/src/MenuList/MenuList.tsx b/packages/mui-joy/src/MenuList/MenuList.tsx index 2187714c21ec02..a5ac9f13d38c5b 100644 --- a/packages/mui-joy/src/MenuList/MenuList.tsx +++ b/packages/mui-joy/src/MenuList/MenuList.tsx @@ -6,12 +6,13 @@ import composeClasses from '@mui/base/composeClasses'; import { useSlotProps } from '@mui/base/utils'; import { useMenu, MenuUnstyledContext, MenuUnstyledContextType } from '@mui/base/MenuUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { StyledList } from '../List/List'; import ListProvider, { scopedVariables } from '../List/ListProvider'; -import { MenuListProps, MenuListOwnerState, MenuListTypeMap } from './MenuListProps'; +import { MenuListOwnerState, MenuListTypeMap } from './MenuListProps'; import { getMenuListUtilityClass } from './menuListClasses'; -const useUtilityClasses = (ownerState: MenuListProps) => { +const useUtilityClasses = (ownerState: MenuListOwnerState) => { const { variant, color, size } = ownerState; const slots = { root: [ @@ -60,9 +61,11 @@ const MenuList = React.forwardRef(function MenuList(inProps, ref) { children, size = 'md', variant = 'outlined', - color = 'neutral', + color: colorProp = 'neutral', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const { registerItem, diff --git a/packages/mui-joy/src/MenuList/MenuListProps.ts b/packages/mui-joy/src/MenuList/MenuListProps.ts index 2d4b47180c585f..0c9ae59317953b 100644 --- a/packages/mui-joy/src/MenuList/MenuListProps.ts +++ b/packages/mui-joy/src/MenuList/MenuListProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { OverrideProps, OverridableStringUnion } from '@mui/types'; import { MenuUnstyledActions } from '@mui/base/MenuUnstyled'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type MenuListSlot = 'root'; @@ -47,4 +47,4 @@ export type MenuListProps< }, > = OverrideProps, D>; -export interface MenuListOwnerState extends MenuListProps {} +export interface MenuListOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/MenuList/menuListClasses.ts b/packages/mui-joy/src/MenuList/menuListClasses.ts index 3323c394b6651a..7d80b26fdbaeab 100644 --- a/packages/mui-joy/src/MenuList/menuListClasses.ts +++ b/packages/mui-joy/src/MenuList/menuListClasses.ts @@ -21,6 +21,8 @@ export interface MenuListClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -49,6 +51,7 @@ const menuClasses: MenuListClasses = generateUtilityClasses('JoyMenuList', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Modal/modalClasses.ts b/packages/mui-joy/src/Modal/modalClasses.ts index 9eec63c1a2ca20..8c89001fde5e54 100644 --- a/packages/mui-joy/src/Modal/modalClasses.ts +++ b/packages/mui-joy/src/Modal/modalClasses.ts @@ -5,26 +5,6 @@ export interface ModalClasses { root: string; /** Styles applied to the backdrop element. */ backdrop: string; - /** Styles applied to the root element if `color="primary"`. */ - colorPrimary: string; - /** Styles applied to the root element if `color="neutral"`. */ - colorNeutral: string; - /** Styles applied to the root element if `color="danger"`. */ - colorDanger: string; - /** Styles applied to the root element if `color="info"`. */ - colorInfo: string; - /** Styles applied to the root element if `color="success"`. */ - colorSuccess: string; - /** Styles applied to the root element if `color="warning"`. */ - colorWarning: string; - /** Styles applied to the root element if `variant="plain"`. */ - variantPlain: string; - /** Styles applied to the root element if `variant="outlined"`. */ - variantOutlined: string; - /** Styles applied to the root element if `variant="soft"`. */ - variantSoft: string; - /** Styles applied to the root element if `variant="solid"`. */ - variantSolid: string; } export type ModalClassKey = keyof ModalClasses; @@ -33,19 +13,6 @@ export function getModalUtilityClass(slot: string): string { return generateUtilityClass('JoyModal', slot); } -const modalClasses: ModalClasses = generateUtilityClasses('JoyModal', [ - 'root', - 'backdrop', - 'colorPrimary', - 'colorNeutral', - 'colorDanger', - 'colorInfo', - 'colorSuccess', - 'colorWarning', - 'variantPlain', - 'variantOutlined', - 'variantSoft', - 'variantSolid', -]); +const modalClasses: ModalClasses = generateUtilityClasses('JoyModal', ['root', 'backdrop']); export default modalClasses; diff --git a/packages/mui-joy/src/ModalClose/ModalClose.test.tsx b/packages/mui-joy/src/ModalClose/ModalClose.test.tsx index 5102ea7571143e..869942344145a8 100644 --- a/packages/mui-joy/src/ModalClose/ModalClose.test.tsx +++ b/packages/mui-joy/src/ModalClose/ModalClose.test.tsx @@ -1,7 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { createRenderer, describeConformance, fireEvent } from 'test/utils'; +import { + createRenderer, + describeConformance, + describeJoyColorInversion, + fireEvent, +} from 'test/utils'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Modal from '@mui/joy/Modal'; @@ -22,6 +27,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyModalClose', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/ModalClose/ModalClose.tsx b/packages/mui-joy/src/ModalClose/ModalClose.tsx index 48dd83236fca04..4431277dc69833 100644 --- a/packages/mui-joy/src/ModalClose/ModalClose.tsx +++ b/packages/mui-joy/src/ModalClose/ModalClose.tsx @@ -6,15 +6,16 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import { useSlotProps } from '@mui/base/utils'; import { useButton } from '@mui/base/ButtonUnstyled'; import { useThemeProps, styled } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { StyledIconButton } from '../IconButton/IconButton'; import { getModalCloseUtilityClass } from './modalCloseClasses'; -import { ModalCloseProps, ModalCloseTypeMap } from './ModalCloseProps'; +import { ModalCloseProps, ModalCloseOwnerState, ModalCloseTypeMap } from './ModalCloseProps'; import CloseIcon from '../internal/svg-icons/Close'; import CloseModalContext from '../Modal/CloseModalContext'; import ModalDialogSizeContext from '../ModalDialog/ModalDialogSizeContext'; import ModalDialogVariantColorContext from '../ModalDialog/ModalDialogVariantColorContext'; -const useUtilityClasses = (ownerState: ModalCloseProps & { focusVisible?: boolean }) => { +const useUtilityClasses = (ownerState: ModalCloseOwnerState) => { const { variant, color, disabled, focusVisible, size } = ownerState; const slots = { @@ -35,7 +36,7 @@ export const ModalCloseRoot = styled(StyledIconButton, { name: 'JoyModalClose', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: ModalCloseProps }>(({ ownerState, theme }) => ({ +})<{ ownerState: ModalCloseOwnerState }>(({ ownerState, theme }) => ({ ...(ownerState.size === 'sm' && { '--IconButton-size': '28px', }), @@ -79,9 +80,10 @@ const ModalClose = React.forwardRef(function ModalClose(inProps, ref) { const closeModalContext = React.useContext(CloseModalContext); const modalDialogVariantColor = React.useContext(ModalDialogVariantColorContext); - const color = inProps.color ?? modalDialogVariantColor?.color ?? colorProp; const variant = inProps.variant ?? modalDialogVariantMapping[modalDialogVariantColor?.variant!] ?? variantProp; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, modalDialogVariantColor?.color ?? colorProp); const modalDialogSize = React.useContext(ModalDialogSizeContext); const size = inProps.size ?? modalDialogSize ?? sizeProp; diff --git a/packages/mui-joy/src/ModalClose/ModalCloseProps.ts b/packages/mui-joy/src/ModalClose/ModalCloseProps.ts index 3c156fff66a8da..2ce8c7bd0b2734 100644 --- a/packages/mui-joy/src/ModalClose/ModalCloseProps.ts +++ b/packages/mui-joy/src/ModalClose/ModalCloseProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type ModalCloseSlot = 'root'; @@ -38,4 +38,6 @@ export type ModalCloseProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface ModalCloseOwnerState extends ModalCloseProps {} +export interface ModalCloseOwnerState extends ApplyColorInversion { + focusVisible?: boolean; +} diff --git a/packages/mui-joy/src/ModalClose/modalCloseClasses.ts b/packages/mui-joy/src/ModalClose/modalCloseClasses.ts index 6b2be6e619c8f1..7127b7989791f2 100644 --- a/packages/mui-joy/src/ModalClose/modalCloseClasses.ts +++ b/packages/mui-joy/src/ModalClose/modalCloseClasses.ts @@ -15,6 +15,8 @@ export interface ModalCloseClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -45,6 +47,7 @@ const modalCloseClasses: ModalCloseClasses = generateUtilityClasses('JoyModalClo 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/ModalDialog/ModalDialog.test.tsx b/packages/mui-joy/src/ModalDialog/ModalDialog.test.tsx index e6671c5eaafc34..856f392338d706 100644 --- a/packages/mui-joy/src/ModalDialog/ModalDialog.test.tsx +++ b/packages/mui-joy/src/ModalDialog/ModalDialog.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import ModalDialog, { modalDialogClasses as classes } from '@mui/joy/ModalDialog'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -20,6 +20,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyModalDialog', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx index 078b57a1d372e2..7caaab82668fb8 100644 --- a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx +++ b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx @@ -8,13 +8,14 @@ import { unstable_isMuiElement as isMuiElement, } from '@mui/utils'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { SheetRoot } from '../Sheet/Sheet'; import { getModalDialogUtilityClass } from './modalDialogClasses'; -import { ModalDialogProps, ModalDialogTypeMap } from './ModalDialogProps'; +import { ModalDialogProps, ModalDialogOwnerState, ModalDialogTypeMap } from './ModalDialogProps'; import ModalDialogSizeContext from './ModalDialogSizeContext'; import ModalDialogVariantColorContext from './ModalDialogVariantColorContext'; -const useUtilityClasses = (ownerState: ModalDialogProps) => { +const useUtilityClasses = (ownerState: ModalDialogOwnerState) => { const { variant, color, size, layout } = ownerState; const slots = { @@ -34,7 +35,7 @@ const ModalDialogRoot = styled(SheetRoot, { name: 'JoyModalDialog', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: ModalDialogProps }>(({ theme, ownerState }) => ({ +})<{ ownerState: ModalDialogOwnerState }>(({ theme, ownerState }) => ({ // Divider integration '--Divider-inset': 'calc(-1 * var(--ModalDialog-padding))', '--ModalClose-radius': @@ -90,13 +91,15 @@ const ModalDialog = React.forwardRef(function ModalDialog(inProps, ref) { const { className, children, - color = 'neutral', + color: colorProp = 'neutral', component = 'div', variant = 'outlined', size = 'md', layout = 'center', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, @@ -109,7 +112,10 @@ const ModalDialog = React.forwardRef(function ModalDialog(inProps, ref) { const classes = useUtilityClasses(ownerState); - const contextValue = React.useMemo(() => ({ variant, color }), [color, variant]); + const contextValue = React.useMemo( + () => ({ variant, color: color === 'context' ? undefined : color }), + [color, variant], + ); return ( diff --git a/packages/mui-joy/src/ModalDialog/ModalDialogProps.ts b/packages/mui-joy/src/ModalDialog/ModalDialogProps.ts index 26ed4589bb60ce..9ff71da92425dd 100644 --- a/packages/mui-joy/src/ModalDialog/ModalDialogProps.ts +++ b/packages/mui-joy/src/ModalDialog/ModalDialogProps.ts @@ -1,6 +1,6 @@ -import { OverridableStringUnion, OverrideProps } from '@mui/types'; import * as React from 'react'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { OverridableStringUnion, OverrideProps } from '@mui/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type ModalDialogSlot = 'root'; @@ -48,4 +48,4 @@ export type ModalDialogProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface ModalDialogOwnerState extends ModalDialogProps {} +export interface ModalDialogOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts b/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts index 6b83d66ae7fb85..409b21143c7bf0 100644 --- a/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts +++ b/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts @@ -15,6 +15,8 @@ export interface ModalDialogClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -49,6 +51,7 @@ const modalDialogClasses: ModalDialogClasses = generateUtilityClasses('JoyModalD 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Option/Option.tsx b/packages/mui-joy/src/Option/Option.tsx index c7a8ab4683f327..da68b786665160 100644 --- a/packages/mui-joy/src/Option/Option.tsx +++ b/packages/mui-joy/src/Option/Option.tsx @@ -6,6 +6,7 @@ import { useSlotProps } from '@mui/base/utils'; import { SelectUnstyledContext } from '@mui/base/SelectUnstyled'; import { StyledListItemButton } from '../ListItemButton/ListItemButton'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { OptionOwnerState, ExtendOption, OptionTypeMap } from './OptionProps'; import optionClasses, { getOptionUtilityClass } from './optionClasses'; import RowListContext from '../List/RowListContext'; @@ -24,11 +25,14 @@ const OptionRoot = styled(StyledListItemButton as unknown as 'button', { name: 'JoyOption', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: OptionOwnerState }>(({ theme, ownerState }) => ({ - [`&.${optionClasses.highlighted}`]: { - backgroundColor: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}HoverBg`], - }, -})); +})<{ ownerState: OptionOwnerState }>(({ theme, ownerState }) => { + const variantStyle = theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!]; + return { + [`&.${optionClasses.highlighted}`]: { + backgroundColor: variantStyle?.backgroundColor, + }, + }; +}); const Option = React.forwardRef(function Option(inProps, ref) { const props = useThemeProps({ @@ -64,10 +68,8 @@ const Option = React.forwardRef(function Option(inProps, ref) { const optionProps = selectContext.getOptionProps(selectOption); const listboxRef = selectContext.listboxRef; - let color = colorProp; - if (optionState.selected && !inProps.color) { - color = 'primary'; - } + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, optionState.selected ? 'primary' : colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/Option/OptionProps.ts b/packages/mui-joy/src/Option/OptionProps.ts index 384f067fc8158c..23fd1e214f3988 100644 --- a/packages/mui-joy/src/Option/OptionProps.ts +++ b/packages/mui-joy/src/Option/OptionProps.ts @@ -6,7 +6,7 @@ import { OverrideProps, } from '@mui/types'; import { OptionState } from '@mui/base/ListboxUnstyled'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type OptionSlot = 'root'; @@ -64,7 +64,9 @@ export type OptionProps< }, > = OverrideProps, D>; -export interface OptionOwnerState extends Omit, OptionState {} +export interface OptionOwnerState + extends ApplyColorInversion>, + OptionState {} export type ExtendOption = (( props: OverrideProps, 'a'>, diff --git a/packages/mui-joy/src/Radio/Radio.test.js b/packages/mui-joy/src/Radio/Radio.test.js index 05dc10742a9edc..a3460d781e2260 100644 --- a/packages/mui-joy/src/Radio/Radio.test.js +++ b/packages/mui-joy/src/Radio/Radio.test.js @@ -1,6 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, act, createRenderer, fireEvent } from 'test/utils'; +import { + describeConformance, + act, + createRenderer, + fireEvent, + describeJoyColorInversion, +} from 'test/utils'; import Radio, { radioClasses as classes } from '@mui/joy/Radio'; import { ThemeProvider } from '@mui/joy/styles'; @@ -27,6 +33,8 @@ describe('', () => { skip: ['componentProp', 'componentsProp', 'classesRoot', 'propsSpread'], })); + describeJoyColorInversion(, { muiName: 'JoyRadio', classes }); + it('should have the classes required for Radio', () => { expect(classes).to.include.all.keys(['root', 'checked', 'disabled']); }); diff --git a/packages/mui-joy/src/Radio/Radio.tsx b/packages/mui-joy/src/Radio/Radio.tsx index 2cf0c965c9d0ec..56e986738383a1 100644 --- a/packages/mui-joy/src/Radio/Radio.tsx +++ b/packages/mui-joy/src/Radio/Radio.tsx @@ -5,6 +5,7 @@ import { unstable_capitalize as capitalize, unstable_useId as useId } from '@mui import { unstable_composeClasses as composeClasses } from '@mui/base'; import { useSwitch } from '@mui/base/SwitchUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import radioClasses, { getRadioUtilityClass } from './radioClasses'; import { RadioOwnerState, RadioTypeMap } from './RadioProps'; @@ -80,12 +81,12 @@ const RadioRoot = styled('span', { lineHeight: 'var(--Radio-size)', // prevent label from having larger height than the checkbox color: theme.vars.palette.text.primary, [`&.${radioClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.plainDisabledColor, + color: theme.variants.plainDisabled?.[ownerState.color!]?.color, }, ...(ownerState.disableIcon && { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, [`&.${radioClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], + color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, }, }), ...(ownerState['data-parent'] === 'RadioGroup' && @@ -236,13 +237,14 @@ const Radio = React.forwardRef(function Radio(inProps, ref) { onFocusVisible, readOnly, required, - color, + color: colorProp, variant = 'outlined', size: sizeProp = 'md', uncheckedIcon, value, ...other } = props; + const { getColor } = useColorInversion(variant); const formControl = React.useContext(FormControlContext); @@ -262,10 +264,10 @@ const Radio = React.forwardRef(function Radio(inProps, ref) { const radioGroup = React.useContext(RadioGroupContext); const activeColor = formControl?.error ? 'danger' - : inProps.color ?? formControl?.color ?? color ?? 'primary'; + : inProps.color ?? formControl?.color ?? colorProp ?? 'primary'; const inactiveColor = formControl?.error ? 'danger' - : inProps.color ?? formControl?.color ?? color ?? 'neutral'; + : inProps.color ?? formControl?.color ?? colorProp ?? 'neutral'; const size = inProps.size || formControl?.size || radioGroup?.size || sizeProp; const name = inProps.name || radioGroup?.name || nameProp; const disableIcon = inProps.disableIcon || radioGroup?.disableIcon || disableIconProp; @@ -287,12 +289,14 @@ const Radio = React.forwardRef(function Radio(inProps, ref) { const { getInputProps, checked, disabled, focusVisible } = useSwitch(useRadioProps); + const color = getColor(inProps.color, checked ? activeColor : inactiveColor); + const ownerState = { ...props, checked, disabled, focusVisible, - color: checked ? activeColor : inactiveColor, + color, variant, size, disableIcon, diff --git a/packages/mui-joy/src/Radio/RadioProps.ts b/packages/mui-joy/src/Radio/RadioProps.ts index 79071df41384dc..8dab90b9e4eb65 100644 --- a/packages/mui-joy/src/Radio/RadioProps.ts +++ b/packages/mui-joy/src/Radio/RadioProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { UseSwitchParameters } from '@mui/base/SwitchUnstyled'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type RadioSlot = 'root' | 'radio' | 'icon' | 'action' | 'input' | 'label'; @@ -93,7 +93,7 @@ export type RadioProps< }, > = OverrideProps, D>; -export interface RadioOwnerState extends RadioProps { +export interface RadioOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/Radio/radioClasses.ts b/packages/mui-joy/src/Radio/radioClasses.ts index 1e7ed3a12deb13..876afacb826e33 100644 --- a/packages/mui-joy/src/Radio/radioClasses.ts +++ b/packages/mui-joy/src/Radio/radioClasses.ts @@ -31,6 +31,8 @@ export interface RadioClasses { colorSuccess: string; /** Class name applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Class name applied to the root element if `size="sm"`. */ sizeSm: string; /** Class name applied to the root element if `size="md"`. */ @@ -67,6 +69,7 @@ const radioClasses: RadioClasses = generateUtilityClasses('JoyRadio', [ 'colorNeutral', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/Select/Select.test.tsx b/packages/mui-joy/src/Select/Select.test.tsx index 1a262a6a4cd660..c0d030a576ac89 100644 --- a/packages/mui-joy/src/Select/Select.test.tsx +++ b/packages/mui-joy/src/Select/Select.test.tsx @@ -1,7 +1,14 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy, stub } from 'sinon'; -import { describeConformance, act, createRenderer, fireEvent, screen } from 'test/utils'; +import { + describeConformance, + describeJoyColorInversion, + act, + createRenderer, + fireEvent, + screen, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Select, { selectClasses as classes, SelectOption } from '@mui/joy/Select'; import Option from '@mui/joy/Option'; @@ -30,6 +37,12 @@ describe('Joy , { + muiName: 'JoySelect', + classes, + portalSlot: 'listbox', + }); + it('should be able to mount the component', () => { render( } diff --git a/packages/mui-joy/src/Select/SelectProps.ts b/packages/mui-joy/src/Select/SelectProps.ts index 33b6be5fc3e2ae..6aad3fd7f8e70f 100644 --- a/packages/mui-joy/src/Select/SelectProps.ts +++ b/packages/mui-joy/src/Select/SelectProps.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; import { PopperUnstyledOwnProps } from '@mui/base/PopperUnstyled'; import { SelectOption, SelectUnstyledCommonProps } from '@mui/base/SelectUnstyled'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type { SelectOption } from '@mui/base/SelectUnstyled'; @@ -127,7 +127,8 @@ export type SelectOwnProps = SelectStaticProps & value?: TValue | null; }; -export interface SelectOwnerState extends SelectOwnProps { +export interface SelectOwnerState + extends ApplyColorInversion> { /** * If `true`, the select button is active. */ diff --git a/packages/mui-joy/src/Select/selectClasses.ts b/packages/mui-joy/src/Select/selectClasses.ts index 3f50c14c13fe04..abee1ec07e1a77 100644 --- a/packages/mui-joy/src/Select/selectClasses.ts +++ b/packages/mui-joy/src/Select/selectClasses.ts @@ -27,6 +27,8 @@ export interface SelectClasses { colorSuccess: string; /** Styles applied to the root slot if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root slot if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root slot if `variant="outlined"`. */ @@ -69,6 +71,7 @@ const selectClasses: SelectClasses = generateUtilityClasses('JoySelect', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Sheet/Sheet.test.js b/packages/mui-joy/src/Sheet/Sheet.test.js index f8b08e15209bdd..a94817da4dd7e3 100644 --- a/packages/mui-joy/src/Sheet/Sheet.test.js +++ b/packages/mui-joy/src/Sheet/Sheet.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Sheet, { sheetClasses as classes } from '@mui/joy/Sheet'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -21,6 +21,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoySheet', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByTestId } = render(Hello World); diff --git a/packages/mui-joy/src/Sheet/Sheet.tsx b/packages/mui-joy/src/Sheet/Sheet.tsx index 7cdc3737c3692e..e5387555386955 100644 --- a/packages/mui-joy/src/Sheet/Sheet.tsx +++ b/packages/mui-joy/src/Sheet/Sheet.tsx @@ -11,7 +11,7 @@ import { getSheetUtilityClass } from './sheetClasses'; import { SheetProps, SheetOwnerState, SheetTypeMap } from './SheetProps'; import { ColorInversionProvider, useColorInversion } from '../styles/ColorInversion'; -const useUtilityClasses = (ownerState: SheetProps) => { +const useUtilityClasses = (ownerState: SheetOwnerState) => { const { variant, color } = ownerState; const slots = { @@ -50,7 +50,9 @@ export const SheetRoot = styled('div', { position: 'relative', }, variantStyle, - ownerState.invertedColors && theme.colorInversion[ownerState.variant!]?.[ownerState.color!], + ownerState.color !== 'context' && + ownerState.invertedColors && + theme.colorInversion[ownerState.variant!]?.[ownerState.color!], ]; }); diff --git a/packages/mui-joy/src/Sheet/SheetProps.ts b/packages/mui-joy/src/Sheet/SheetProps.ts index d00a6cff339621..92efe7faf452ac 100644 --- a/packages/mui-joy/src/Sheet/SheetProps.ts +++ b/packages/mui-joy/src/Sheet/SheetProps.ts @@ -1,6 +1,6 @@ import { OverridableStringUnion, OverrideProps } from '@mui/types'; import * as React from 'react'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type SheetSlot = 'root'; @@ -41,4 +41,4 @@ export type SheetProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface SheetOwnerState extends SheetProps {} +export interface SheetOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Sheet/sheetClasses.ts b/packages/mui-joy/src/Sheet/sheetClasses.ts index 7e255adfd4659e..55ced58d433eef 100644 --- a/packages/mui-joy/src/Sheet/sheetClasses.ts +++ b/packages/mui-joy/src/Sheet/sheetClasses.ts @@ -15,6 +15,8 @@ export interface SheetClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -39,6 +41,7 @@ const sheetClasses: SheetClasses = generateUtilityClasses('JoySheet', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Slider/Slider.test.js b/packages/mui-joy/src/Slider/Slider.test.js index aef1b287b00913..29ddaaf2f97ecb 100644 --- a/packages/mui-joy/src/Slider/Slider.test.js +++ b/packages/mui-joy/src/Slider/Slider.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import Slider, { sliderClasses as classes } from '@mui/joy/Slider'; import { ThemeProvider } from '@mui/joy/styles'; @@ -36,6 +36,8 @@ describe('', () => { }), ); + describeJoyColorInversion(, { muiName: 'JoySlider', classes }); + it('should render the rail as the first child of the Slider', () => { const { container: { firstChild: root }, diff --git a/packages/mui-joy/src/Slider/Slider.tsx b/packages/mui-joy/src/Slider/Slider.tsx index 5b1647aa98a7a4..771748831a9ce5 100644 --- a/packages/mui-joy/src/Slider/Slider.tsx +++ b/packages/mui-joy/src/Slider/Slider.tsx @@ -9,6 +9,7 @@ import { OverridableComponent } from '@mui/types'; import { useSlider } from '@mui/base/SliderUnstyled'; import { isHostComponent } from '@mui/base/utils'; import { useThemeProps, styled, Theme } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import sliderClasses, { getSliderUtilityClass } from './sliderClasses'; import { SliderTypeMap, SliderOwnerState } from './SliderProps'; @@ -251,6 +252,7 @@ const SliderThumb = styled('span', { }), '&::before': { // use pseudo element to create thumb's ring + boxSizing: 'border-box', content: '""', display: 'block', position: 'absolute', @@ -425,11 +427,13 @@ const Slider = React.forwardRef(function Slider(inProps, ref) { valueLabelDisplay = 'off', valueLabelFormat = Identity, isRtl = false, - color = 'primary', + color: colorProp = 'primary', size = 'md', variant = 'solid', ...other } = props; + const { getColor } = useColorInversion('solid'); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/Slider/SliderProps.ts b/packages/mui-joy/src/Slider/SliderProps.ts index 841fa06613cebc..7fb6fcd843503f 100644 --- a/packages/mui-joy/src/Slider/SliderProps.ts +++ b/packages/mui-joy/src/Slider/SliderProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { SliderUnstyledOwnProps } from '@mui/base/SliderUnstyled'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type SliderSlot = @@ -65,7 +65,7 @@ export type SliderProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface SliderOwnerState extends SliderProps { +export interface SliderOwnerState extends ApplyColorInversion { /** * If `true`, the thumb is in dragging state. */ diff --git a/packages/mui-joy/src/Slider/sliderClasses.ts b/packages/mui-joy/src/Slider/sliderClasses.ts index 6ef6a2c0d152fb..286229b5239959 100644 --- a/packages/mui-joy/src/Slider/sliderClasses.ts +++ b/packages/mui-joy/src/Slider/sliderClasses.ts @@ -45,6 +45,16 @@ export interface SliderClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; + /** Styles applied to the root element if `variant="plain"`. */ + variantPlain: string; + /** Styles applied to the root element if `variant="outlined"`. */ + variantOutlined: string; + /** Styles applied to the root element if `variant="soft"`. */ + variantSoft: string; + /** Styles applied to the root element if `variant="solid"`. */ + variantSolid: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -84,6 +94,11 @@ const sliderClasses: SliderClasses = generateUtilityClasses('JoySlider', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', + 'variantPlain', + 'variantOutlined', + 'variantSoft', + 'variantSolid', 'disabled', 'sizeSm', 'sizeMd', diff --git a/packages/mui-joy/src/Switch/Switch.test.js b/packages/mui-joy/src/Switch/Switch.test.js index 43f17439620928..89de7a679355a2 100644 --- a/packages/mui-joy/src/Switch/Switch.test.js +++ b/packages/mui-joy/src/Switch/Switch.test.js @@ -1,6 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, act, createRenderer, fireEvent, screen } from 'test/utils'; +import { + describeConformance, + describeJoyColorInversion, + act, + createRenderer, + fireEvent, + screen, +} from 'test/utils'; import Switch, { switchClasses as classes } from '@mui/joy/Switch'; import { ThemeProvider } from '@mui/joy/styles'; @@ -33,6 +40,8 @@ describe('', () => { skip: ['componentProp', 'componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoySwitch', classes }); + it('should pass `slotProps` down to slots', () => { const { container } = render( { const switchColorVariables = ({ theme, ownerState }: { theme: Theme; ownerState: SwitchOwnerState }) => (data: { state?: 'Hover' | 'Disabled' } = {}) => { - const variant = ownerState.variant; - const color = ownerState.color; + const styles = + theme.variants[`${ownerState.variant!}${data.state || ''}`]?.[ownerState.color!] || {}; return { - '--Switch-track-background': theme.vars.palette[color!]?.[`${variant!}${data.state || ''}Bg`], - '--Switch-track-color': theme.vars.palette[color!]?.[`${variant!}Color`], + '--Switch-track-background': styles.backgroundColor, + '--Switch-track-color': styles.color, '--Switch-track-borderColor': - variant === 'outlined' - ? theme.vars.palette[color!]?.[`${variant!}${data.state || ''}Border`] - : 'currentColor', - '--Switch-thumb-background': - theme.vars.palette[color!]?.[`${variant!}${data.state || ''}Color`], - '--Switch-thumb-color': theme.vars.palette[color!]?.[`${variant!}Bg`], + ownerState.variant === 'outlined' ? styles.borderColor : 'currentColor', + '--Switch-thumb-background': styles.color, + '--Switch-thumb-color': styles.backgroundColor, }; }; @@ -256,7 +254,11 @@ const Switch = React.forwardRef(function Switch(inProps, ref) { const disabledProp = inProps.disabled ?? formControl?.disabled ?? disabledExternalProp; const size = inProps.size ?? formControl?.size ?? sizeProp; - const color = formControl?.error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp; + const { getColor } = useColorInversion(variant); + const color = getColor( + inProps.color, + formControl?.error ? 'danger' : formControl?.color ?? colorProp, + ); const useSwitchProps = { checked: checkedProp, diff --git a/packages/mui-joy/src/Switch/SwitchProps.ts b/packages/mui-joy/src/Switch/SwitchProps.ts index b4a4dfa0eecc0b..b74a26de10f069 100644 --- a/packages/mui-joy/src/Switch/SwitchProps.ts +++ b/packages/mui-joy/src/Switch/SwitchProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { UseSwitchParameters } from '@mui/base/SwitchUnstyled'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type SwitchSlot = @@ -70,7 +70,7 @@ export type SwitchProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface SwitchOwnerState extends SwitchProps { +export interface SwitchOwnerState extends ApplyColorInversion { /** * If `true`, the switch's focus is visible. */ diff --git a/packages/mui-joy/src/Switch/switchClasses.ts b/packages/mui-joy/src/Switch/switchClasses.ts index fe503acf7b9803..35c8b26356fb1b 100644 --- a/packages/mui-joy/src/Switch/switchClasses.ts +++ b/packages/mui-joy/src/Switch/switchClasses.ts @@ -29,6 +29,8 @@ export interface SwitchClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -68,6 +70,7 @@ const switchClasses: SwitchClasses = generateUtilityClasses('JoySwitch', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/Tab/Tab.test.tsx b/packages/mui-joy/src/Tab/Tab.test.tsx index b1eb018ef31e3f..eb5b1585a47462 100644 --- a/packages/mui-joy/src/Tab/Tab.test.tsx +++ b/packages/mui-joy/src/Tab/Tab.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { TabsContext, useTabs, TabsUnstyledProps } from '@mui/base/TabsUnstyled'; import { ThemeProvider } from '@mui/joy/styles'; import Tab, { tabClasses as classes } from '@mui/joy/Tab'; @@ -26,6 +26,12 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot', 'reactTestRenderer'], })); + describeJoyColorInversion(, { + muiName: 'JoyTab', + classes, + wrapper: (node) => {node}, + }); + it('prop: variant', () => { render( diff --git a/packages/mui-joy/src/Tab/Tab.tsx b/packages/mui-joy/src/Tab/Tab.tsx index 883c4134e7f0f5..0281613caa9e72 100644 --- a/packages/mui-joy/src/Tab/Tab.tsx +++ b/packages/mui-joy/src/Tab/Tab.tsx @@ -8,6 +8,7 @@ import { useSlotProps } from '@mui/base/utils'; import { StyledListItemButton } from '../ListItemButton/ListItemButton'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import { getTabUtilityClass } from './tabClasses'; import { TabOwnerState, TabTypeMap } from './TabProps'; import RowListContext from '../List/RowListContext'; @@ -44,9 +45,9 @@ const TabRoot = styled(StyledListItemButton, { boxShadow: theme.shadow.sm, fontWeight: 'initial', ...(!variantStyle?.backgroundColor && { - backgroundColor: theme.vars.palette.background.body, + backgroundColor: theme.vars.palette.background.surface, '&:hover': { - backgroundColor: theme.vars.palette.background.body, + backgroundColor: theme.vars.palette.background.surface, }, }), }), @@ -72,9 +73,11 @@ const Tab = React.forwardRef(function Tab(inProps, ref) { component = 'button', orientation = 'horizontal', variant = 'plain', - color = 'neutral', + color: colorProp = 'neutral', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const tabRef = React.useRef(); const handleRef = useForkRef(tabRef, ref); diff --git a/packages/mui-joy/src/Tab/TabProps.ts b/packages/mui-joy/src/Tab/TabProps.ts index f8baf034183b6a..6a5463e7973a16 100644 --- a/packages/mui-joy/src/Tab/TabProps.ts +++ b/packages/mui-joy/src/Tab/TabProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type TabSlot = 'root'; @@ -56,7 +56,7 @@ export type TabProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface TabOwnerState extends TabProps { +export interface TabOwnerState extends ApplyColorInversion { /** * If `true`, the tab is activated by mouse or keyboard. */ diff --git a/packages/mui-joy/src/Tab/tabClasses.ts b/packages/mui-joy/src/Tab/tabClasses.ts index ae8cd8c5cf673f..0dea454def0396 100644 --- a/packages/mui-joy/src/Tab/tabClasses.ts +++ b/packages/mui-joy/src/Tab/tabClasses.ts @@ -25,6 +25,8 @@ export interface TabClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -54,6 +56,7 @@ const tabListClasses: TabClasses = generateUtilityClasses('JoyTab', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/TabList/TabList.test.tsx b/packages/mui-joy/src/TabList/TabList.test.tsx index da319153b72610..31ce745a4155f8 100644 --- a/packages/mui-joy/src/TabList/TabList.test.tsx +++ b/packages/mui-joy/src/TabList/TabList.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { TabsContext, useTabs, TabsUnstyledProps } from '@mui/base/TabsUnstyled'; import { ThemeProvider } from '@mui/joy/styles'; import Tabs from '@mui/joy/Tabs'; @@ -27,6 +27,12 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot', 'reactTestRenderer'], })); + describeJoyColorInversion(, { + muiName: 'JoyTabList', + classes, + wrapper: (node) => {node}, + }); + describe('size', () => { it('uses size from Tabs', () => { render( diff --git a/packages/mui-joy/src/TabList/TabList.tsx b/packages/mui-joy/src/TabList/TabList.tsx index 810c97ac88260a..8a497b9931c20d 100644 --- a/packages/mui-joy/src/TabList/TabList.tsx +++ b/packages/mui-joy/src/TabList/TabList.tsx @@ -7,6 +7,7 @@ import { useTabsList } from '@mui/base/TabsListUnstyled'; import { useSlotProps } from '@mui/base/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import { StyledList } from '../List/List'; import ListProvider, { scopedVariables } from '../List/ListProvider'; import SizeTabsContext from '../Tabs/SizeTabsContext'; @@ -54,10 +55,12 @@ const TabList = React.forwardRef(function TabList(inProps, ref) { component = 'div', children, variant = 'soft', - color = 'neutral', + color: colorProp = 'neutral', size: sizeProp, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const { isRtl, orientation, getRootProps, processChildren } = useTabsList({ ...props, ref }); diff --git a/packages/mui-joy/src/TabList/TabListProps.ts b/packages/mui-joy/src/TabList/TabListProps.ts index be0783ba9671bf..d0bb454301cbfa 100644 --- a/packages/mui-joy/src/TabList/TabListProps.ts +++ b/packages/mui-joy/src/TabList/TabListProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type TabListSlot = 'root'; @@ -44,7 +44,7 @@ export type TabListProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export type TabListOwnerState = TabListProps & { +export interface TabListOwnerState extends ApplyColorInversion { /** * If `true`, the Tabs' direction is "rtl". */ @@ -53,4 +53,4 @@ export type TabListOwnerState = TabListProps & { * The orientation of the Tabs. */ orientation: 'horizontal' | 'vertical'; -}; +} diff --git a/packages/mui-joy/src/TabList/tabListClasses.ts b/packages/mui-joy/src/TabList/tabListClasses.ts index 6f7d8dd4b76d34..083a6ba599a11d 100644 --- a/packages/mui-joy/src/TabList/tabListClasses.ts +++ b/packages/mui-joy/src/TabList/tabListClasses.ts @@ -15,6 +15,8 @@ export interface TabListClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -45,6 +47,7 @@ const tabListClasses: TabListClasses = generateUtilityClasses('JoyTabList', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Tabs/Tabs.test.tsx b/packages/mui-joy/src/Tabs/Tabs.test.tsx index c70cdde9ef1fb4..30aca601bf5b6b 100644 --- a/packages/mui-joy/src/Tabs/Tabs.test.tsx +++ b/packages/mui-joy/src/Tabs/Tabs.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Tabs, { tabsClasses as classes } from '@mui/joy/Tabs'; import SizeTabsContext from './SizeTabsContext'; @@ -20,6 +20,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot', 'reactTestRenderer'], })); + describeJoyColorInversion(, { muiName: 'JoyTabs', classes }); + it('prop: variant', () => { render(); expect(screen.getByLabelText('Tabs')).to.have.class(classes.variantOutlined); diff --git a/packages/mui-joy/src/Tabs/Tabs.tsx b/packages/mui-joy/src/Tabs/Tabs.tsx index 11b15e8d22e18d..a6526f4a7ca4a0 100644 --- a/packages/mui-joy/src/Tabs/Tabs.tsx +++ b/packages/mui-joy/src/Tabs/Tabs.tsx @@ -7,6 +7,7 @@ import { useTabs, TabsContext } from '@mui/base/TabsUnstyled'; import { useSlotProps } from '@mui/base/utils'; import { SheetRoot } from '../Sheet/Sheet'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import SizeTabsContext from './SizeTabsContext'; import { getTabsUtilityClass } from './tabsClasses'; import { TabsOwnerState, TabsTypeMap } from './TabsProps'; @@ -64,10 +65,12 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) { onChange, selectionFollowsFocus, variant = 'plain', - color = 'neutral', + color: colorProp = 'neutral', size = 'md', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const { tabsContextValue } = useTabs({ ...props, orientation }); diff --git a/packages/mui-joy/src/Tabs/TabsProps.ts b/packages/mui-joy/src/Tabs/TabsProps.ts index 5ff21ba0a7f391..ceb58f89920ab3 100644 --- a/packages/mui-joy/src/Tabs/TabsProps.ts +++ b/packages/mui-joy/src/Tabs/TabsProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; import { TabsUnstyledOwnProps } from '@mui/base/TabsUnstyled'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type TabsSlot = 'root'; @@ -41,4 +41,4 @@ export type TabsProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface TabsOwnerState extends TabsProps {} +export interface TabsOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Tabs/tabsClasses.ts b/packages/mui-joy/src/Tabs/tabsClasses.ts index d5b0b29481384c..b3db18a28400a1 100644 --- a/packages/mui-joy/src/Tabs/tabsClasses.ts +++ b/packages/mui-joy/src/Tabs/tabsClasses.ts @@ -19,6 +19,8 @@ export interface TabsClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -51,6 +53,7 @@ const tabListClasses: TabsClasses = generateUtilityClasses('JoyTabs', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/TextField/TextField.tsx b/packages/mui-joy/src/TextField/TextField.tsx index ca57f339c8efa1..4059e705590488 100644 --- a/packages/mui-joy/src/TextField/TextField.tsx +++ b/packages/mui-joy/src/TextField/TextField.tsx @@ -118,6 +118,7 @@ const TextField = React.forwardRef(function TextField(inProps, ref) { ref, className: clsx(classes.root, className), elementType: TextFieldRoot, + // @ts-ignore internal logic externalForwardedProps: { ...other, component, slots, slotProps }, ownerState, }); @@ -125,6 +126,7 @@ const TextField = React.forwardRef(function TextField(inProps, ref) { const Input = slots.input || JoyInput; return ( + // @ts-ignore neglect 'context' color {label && ( ', () => { skip: ['propsSpread', 'componentsProp', 'classesRoot'], })); + describeJoyColorInversion(