Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[material-ui] Support theme scoping in useMediaQuery #44339

Merged
merged 4 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/mui-material/src/useMediaQuery/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
export { default } from '@mui/system/useMediaQuery';
import { UseMediaQueryOptions } from '@mui/system/useMediaQuery';
import { Theme } from '../styles/createTheme';

export * from '@mui/system/useMediaQuery';

declare function useMediaQuery(
queryInput: string | ((theme: Theme) => string),
options?: UseMediaQueryOptions,
): boolean;

export default useMediaQuery;
7 changes: 6 additions & 1 deletion packages/mui-material/src/useMediaQuery/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export { default } from '@mui/system/useMediaQuery';
import { unstable_createUseMediaQuery } from '@mui/system/useMediaQuery';
import THEME_ID from '../styles/identifier';

const useMediaQuery = unstable_createUseMediaQuery({ themeId: THEME_ID });

export default useMediaQuery;
36 changes: 35 additions & 1 deletion packages/mui-material/src/useMediaQuery/useMediaQuery.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import mediaQuery from 'css-mediaquery';
import { expect } from 'chai';
import { stub } from 'sinon';
import useMediaQuery from '@mui/material/useMediaQuery';
import { ThemeProvider } from '@mui/material/styles';
import { THEME_ID, ThemeProvider, createTheme } from '@mui/material/styles';

const usesUseSyncExternalStore = React.useSyncExternalStore !== undefined;
const matchMediaInstances = new Map();
Expand Down Expand Up @@ -344,6 +344,40 @@ describe('useMediaQuery', () => {
});
});

describe('theme scoping', () => {
it('should work with theme scoping', () => {
function MyComponent() {
const matches = useMediaQuery((theme) => theme.breakpoints.up('xl'));

return <span>{`${matches}`}</span>;
}

function Test() {
const ssrMatchMedia = (query) => ({
matches: mediaQuery.match(query, {
width: 3000,
}),
});

return (
<ThemeProvider
theme={{
[THEME_ID]: createTheme({
components: { MuiUseMediaQuery: { defaultProps: { ssrMatchMedia } } },
}),
}}
>
<MyComponent />
</ThemeProvider>
);
}

const { container } = renderToString(<Test />);

expect(container.firstChild).to.have.text('true');
});
});

describe('warnings', () => {
it('warns on invalid `query` argument', () => {
function MyComponent() {
Expand Down
105 changes: 58 additions & 47 deletions packages/mui-system/src/useMediaQuery/useMediaQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,53 +117,64 @@ function useMediaQueryNew(
return match;
}

export default function useMediaQuery<Theme = unknown>(
queryInput: string | ((theme: Theme) => string),
options: UseMediaQueryOptions = {},
): boolean {
const theme = useTheme<Theme>();
// Wait for jsdom to support the match media feature.
// All the browsers MUI support have this built-in.
// This defensive check is here for simplicity.
// Most of the time, the match media logic isn't central to people tests.
const supportMatchMedia =
typeof window !== 'undefined' && typeof window.matchMedia !== 'undefined';
const {
defaultMatches = false,
matchMedia = supportMatchMedia ? window.matchMedia : null,
ssrMatchMedia = null,
noSsr = false,
} = getThemeProps({ name: 'MuiUseMediaQuery', props: options, theme });

if (process.env.NODE_ENV !== 'production') {
if (typeof queryInput === 'function' && theme === null) {
console.error(
[
'MUI: The `query` argument provided is invalid.',
'You are providing a function without a theme in the context.',
'One of the parent elements needs to use a ThemeProvider.',
].join('\n'),
);
// eslint-disable-next-line @typescript-eslint/naming-convention
export function unstable_createUseMediaQuery(params: { themeId?: string } = {}) {
const { themeId } = params;
return function useMediaQuery<Theme = unknown>(
queryInput: string | ((theme: Theme) => string),
options: UseMediaQueryOptions = {},
): boolean {
let theme = useTheme<Theme>();
if (theme && themeId) {
theme = (theme as Record<string, any>)[themeId] || theme;
}
// Wait for jsdom to support the match media feature.
// All the browsers MUI support have this built-in.
// This defensive check is here for simplicity.
// Most of the time, the match media logic isn't central to people tests.
const supportMatchMedia =
typeof window !== 'undefined' && typeof window.matchMedia !== 'undefined';
const {
defaultMatches = false,
matchMedia = supportMatchMedia ? window.matchMedia : null,
ssrMatchMedia = null,
noSsr = false,
} = getThemeProps({ name: 'MuiUseMediaQuery', props: options, theme });

if (process.env.NODE_ENV !== 'production') {
if (typeof queryInput === 'function' && theme === null) {
console.error(
[
'MUI: The `query` argument provided is invalid.',
'You are providing a function without a theme in the context.',
'One of the parent elements needs to use a ThemeProvider.',
].join('\n'),
);
}
}
}

let query = typeof queryInput === 'function' ? queryInput(theme) : queryInput;
query = query.replace(/^@media( ?)/m, '');

const useMediaQueryImplementation =
maybeReactUseSyncExternalStore !== undefined ? useMediaQueryNew : useMediaQueryOld;
const match = useMediaQueryImplementation(
query,
defaultMatches,
matchMedia,
ssrMatchMedia,
noSsr,
);

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useDebugValue({ query, match });
}

return match;
let query = typeof queryInput === 'function' ? queryInput(theme) : queryInput;
query = query.replace(/^@media( ?)/m, '');

const useMediaQueryImplementation =
maybeReactUseSyncExternalStore !== undefined ? useMediaQueryNew : useMediaQueryOld;
const match = useMediaQueryImplementation(
query,
defaultMatches,
matchMedia,
ssrMatchMedia,
noSsr,
);

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useDebugValue({ query, match });
}

return match;
};
}

const useMediaQuery = unstable_createUseMediaQuery();

export default useMediaQuery;