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

[system] Use cx instead of clsx #32067

Closed
wants to merge 3 commits into from
Closed
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: 7 additions & 4 deletions packages/mui-material/src/Button/Button.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { internal_resolveProps as resolveProps } from '@mui/utils';
import { unstable_composeClasses as composeClasses } from '@mui/base';
import { alpha } from '@mui/system';
import { useCx } from '@mui/styled-engine';
import styled, { rootShouldForwardProp } from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
import ButtonBase from '../ButtonBase';
Expand Down Expand Up @@ -322,7 +322,8 @@ const Button = React.forwardRef(function Button(inProps, ref) {
variant,
};

const classes = useUtilityClasses(ownerState);
// eslint-disable-next-line @typescript-eslint/naming-convention
const { root: classes_root, ...classes } = useUtilityClasses(ownerState);

const startIcon = startIconProp && (
<ButtonStartIcon className={classes.startIcon} ownerState={ownerState}>
Expand All @@ -336,14 +337,16 @@ const Button = React.forwardRef(function Button(inProps, ref) {
</ButtonEndIcon>
);

const cx = useCx();

return (
<ButtonRoot
ownerState={ownerState}
className={clsx(className, contextProps.className)}
className={cx(contextProps.className, classes_root, className)}
component={component}
disabled={disabled}
focusRipple={!disableFocusRipple}
focusVisibleClassName={clsx(classes.focusVisible, focusVisibleClassName)}
focusVisibleClassName={cx(classes.focusVisible, focusVisibleClassName)}
ref={ref}
type={type}
{...other}
Expand Down
6 changes: 4 additions & 2 deletions packages/mui-material/src/ButtonBase/ButtonBase.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { elementTypeAcceptingRef, refType } from '@mui/utils';
import composeClasses from '@mui/base/composeClasses';
import { useCx } from '@mui/styled-engine';
import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
import useForkRef from '../utils/useForkRef';
Expand Down Expand Up @@ -338,10 +338,12 @@ const ButtonBase = React.forwardRef(function ButtonBase(inProps, ref) {

const classes = useUtilityClasses(ownerState);

const cx = useCx();

return (
<ButtonBaseRoot
as={ComponentProp}
className={clsx(classes.root, className)}
className={cx(classes.root, className)}
ownerState={ownerState}
onBlur={handleBlur}
onClick={onClick}
Expand Down
6 changes: 6 additions & 0 deletions packages/mui-styled-engine-sc/src/cx.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { CxArg } from './tools/classnames';

export type { CxArg };
export declare type Cx = (...classNames: CxArg[]) => string;

export declare function useCx(): Cx;
8 changes: 8 additions & 0 deletions packages/mui-styled-engine-sc/src/cx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint-disable import/prefer-default-export */
import { classnames } from './tools/classnames';

const cx = (...args) => classnames(args);

export function useCx() {
return cx;
}
1 change: 1 addition & 0 deletions packages/mui-styled-engine-sc/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export { default as StyledEngineProvider } from './StyledEngineProvider';

export { default as GlobalStyles } from './GlobalStyles';
export * from './GlobalStyles';
export * from './cx';

// These are the same as the ones in @mui/styled-engine
// CSS.PropertiesFallback are necessary so that we support spreading of the mixins. For example:
Expand Down
1 change: 1 addition & 0 deletions packages/mui-styled-engine-sc/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ export default function styled(tag, options) {
export { ThemeContext, keyframes, css } from 'styled-components';
export { default as StyledEngineProvider } from './StyledEngineProvider';
export { default as GlobalStyles } from './GlobalStyles';
export * from './cx';
18 changes: 18 additions & 0 deletions packages/mui-styled-engine-sc/src/tools/classnames.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

/** Copy pasted from
* https://github.com/emotion-js/emotion/blob/23f43ab9f24d44219b0b007a00f4ac681fe8712e/packages/react/src/class-names.js#L9-L15
* (we use void instead of undefined because calling cx with no argument shouldn't be correct typewise)
* */
export declare type CxArg =
garronej marked this conversation as resolved.
Show resolved Hide resolved
| undefined
| null
| string
| boolean
| {
[className: string]: boolean | null | undefined;
}
| readonly CxArg[];
/** Copy pasted from
* https://github.com/emotion-js/emotion/blob/23f43ab9f24d44219b0b007a00f4ac681fe8712e/packages/react/src/class-names.js#L17-L63
* */
export declare const classnames: (args: CxArg[]) => string;
48 changes: 48 additions & 0 deletions packages/mui-styled-engine-sc/src/tools/classnames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable no-continue */
/* eslint-disable no-plusplus */
/* eslint-disable import/prefer-default-export */
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable no-restricted-syntax */

/** Copy pasted from
* https://github.com/emotion-js/emotion/blob/23f43ab9f24d44219b0b007a00f4ac681fe8712e/packages/react/src/class-names.js#L17-L63
* */
export const classnames = (args) => {
const len = args.length;
let i = 0;
let cls = '';
for (; i < len; i++) {
const arg = args[i];
if (arg == null) {
continue;
}

let toAdd;
switch (typeof arg) {
case 'boolean':
break;
case 'object': {
if (Array.isArray(arg)) {
toAdd = classnames(arg);
} else {
toAdd = '';
for (const k in arg) {
if (arg[k] && k) {
toAdd && (toAdd += ' ');
toAdd += k;
}
}
}
break;
}
default: {
toAdd = arg;
}
}
if (toAdd) {
cls && (cls += ' ');
cls += toAdd;
}
}
return cls;
};
6 changes: 6 additions & 0 deletions packages/mui-styled-engine/src/cx.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { CxArg } from './tools/classnames';

export type { CxArg };
export declare type Cx = (...classNames: CxArg[]) => string;

export declare function useCx(): Cx;
58 changes: 58 additions & 0 deletions packages/mui-styled-engine/src/cx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable import/prefer-default-export */
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable import/first */
/* eslint-disable @typescript-eslint/no-use-before-define */
/* eslint-disable no-labels */
/* eslint-disable no-restricted-syntax */
/* eslint-disable @typescript-eslint/no-shadow */
import { useMemo } from 'react';
import { serializeStyles } from '@emotion/serialize';
import { insertStyles, getRegisteredStyles } from '@emotion/utils';
import { __unsafe_useEmotionCache as useEmotionCache } from '@emotion/react';
import createCache from '@emotion/cache';
import { classnames } from './tools/classnames';

const { createCx } = (() => {
function merge(registered, css, className) {
const registeredStyles = [];

const rawClassName = getRegisteredStyles(registered, registeredStyles, className);

if (registeredStyles.length < 2) {
return className;
}

return rawClassName + css(registeredStyles);
}

function createCx(params) {
const { cache } = params;

const css = (...args) => {
const serialized = serializeStyles(args, cache.registered);
insertStyles(cache, serialized, false);
const className = `${cache.key}-${serialized.name}`;

return className;
};

const cx = (...args) =>
merge(cache.registered, css, classnames(args));

return { cx };
}

return { createCx };
})();

/** Will pickup the contextual cache if any */
export function useCx() {
const cache = useEmotionCache();

const { cx } = useMemo(
() => createCx({ cache: cache ?? createCache({ key: 'never' }) }),
[cache],
);

return cx;
}
1 change: 1 addition & 0 deletions packages/mui-styled-engine/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { default as StyledEngineProvider } from './StyledEngineProvider';

export { default as GlobalStyles } from './GlobalStyles';
export * from './GlobalStyles';
export * from './cx';

export interface SerializedStyles {
name: string;
Expand Down
1 change: 1 addition & 0 deletions packages/mui-styled-engine/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ export default function styled(tag, options) {
export { ThemeContext, keyframes, css } from '@emotion/react';
export { default as StyledEngineProvider } from './StyledEngineProvider';
export { default as GlobalStyles } from './GlobalStyles';
export * from './cx';
13 changes: 13 additions & 0 deletions packages/mui-styled-engine/src/tools/classnames.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export declare type CxArg =
| undefined
| null
| string
| boolean
| {
[className: string]: boolean | null | undefined;
}
| readonly CxArg[];
/** Copy pasted from
* https://github.com/emotion-js/emotion/blob/23f43ab9f24d44219b0b007a00f4ac681fe8712e/packages/react/src/class-names.js#L17-L63
* */
export declare const classnames: (args: CxArg[]) => string;
58 changes: 58 additions & 0 deletions packages/mui-styled-engine/src/tools/classnames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable no-continue */
/* eslint-disable no-plusplus */
/* eslint-disable import/prefer-default-export */
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable no-restricted-syntax */

/** Copy pasted from
* https://github.com/emotion-js/emotion/blob/23f43ab9f24d44219b0b007a00f4ac681fe8712e/packages/react/src/class-names.js#L17-L63
* */
export const classnames = (args) => {
const len = args.length;
let i = 0;
let cls = '';
for (; i < len; i++) {
const arg = args[i];
if (arg == null) {
continue;
}

let toAdd;
switch (typeof arg) {
case 'boolean':
break;
case 'object': {
if (Array.isArray(arg)) {
toAdd = classnames(arg);
} else {
if (
process.env.NODE_ENV !== 'production' &&
arg.styles !== undefined &&
arg.name !== undefined
) {
console.error(
'You have passed styles created with `css` from `@emotion/react` package to the `cx`.\n' +
'`cx` is meant to compose class names (strings) so you should convert those styles to a class name by passing them to the `css` received from <ClassNames/> component.',
);
}
toAdd = '';
for (const k in arg) {
if (arg[k] && k) {
toAdd && (toAdd += ' ');
toAdd += k;
}
}
}
break;
}
default: {
toAdd = arg;
}
}
if (toAdd) {
cls && (cls += ' ');
cls += toAdd;
}
}
return cls;
};
24 changes: 24 additions & 0 deletions test/regressions/fixtures/Cx/Cx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react';
import { ClassNames } from '@emotion/react';
import Button from '@mui/material/Button';

export default function Cx() {
return (
<ClassNames>
{({ css }) => (
<React.Fragment>
<Button color="primary" classes={{ root: css({ color: 'pink' }) }}>
This text should be pink
</Button>
<Button
color="primary"
className={css({ color: 'red' })}
classes={{ root: css({ color: 'pink' }) }}
>
This text should be red
</Button>
</React.Fragment>
)}
</ClassNames>
);
}