From e29ffb2ffeaa4d0f4159f9ae829c4edd101b161f Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sun, 15 Aug 2021 19:10:43 +0200 Subject: [PATCH 1/2] [styled-engine] Drop withComponent support --- .../modules/components/AppNavDrawerItem.js | 65 +++++++++---------- .../material-ui-system/src/createStyled.d.ts | 12 +--- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/docs/src/modules/components/AppNavDrawerItem.js b/docs/src/modules/components/AppNavDrawerItem.js index 59d2ca85eb0359..943cd5e573b429 100644 --- a/docs/src/modules/components/AppNavDrawerItem.js +++ b/docs/src/modules/components/AppNavDrawerItem.js @@ -6,39 +6,36 @@ import ButtonBase from '@material-ui/core/ButtonBase'; import ArrowRightIcon from '@material-ui/icons/ArrowRight'; import Link from 'docs/src/modules/components/Link'; -const Item = styled('div', { - shouldForwardProp: - // disable `as` prop - () => true, -})(({ theme }) => { - return { - ...theme.typography.body2, - display: 'flex', - borderRadius: theme.shape.borderRadius, - outline: 0, - width: '100%', - paddingTop: 8, - paddingBottom: 8, - justifyContent: 'flex-start', - fontWeight: theme.typography.fontWeightMedium, - transition: theme.transitions.create(['color', 'background-color'], { - duration: theme.transitions.duration.shortest, - }), - '&:hover': { - color: theme.palette.text.primary, - backgroundColor: alpha(theme.palette.text.primary, theme.palette.action.hoverOpacity), - }, - '&.Mui-focusVisible': { - backgroundColor: theme.palette.action.focus, - }, - [theme.breakpoints.up('md')]: { - paddingTop: 6, - paddingBottom: 6, - }, - }; -}); +const Item = styled(({ component: Component = 'div', ...props }) => , { + // disable `as` prop + shouldForwardProp: () => true, +})(({ theme }) => ({ + ...theme.typography.body2, + display: 'flex', + borderRadius: theme.shape.borderRadius, + outline: 0, + width: '100%', + paddingTop: 8, + paddingBottom: 8, + justifyContent: 'flex-start', + fontWeight: theme.typography.fontWeightMedium, + transition: theme.transitions.create(['color', 'background-color'], { + duration: theme.transitions.duration.shortest, + }), + '&:hover': { + color: theme.palette.text.primary, + backgroundColor: alpha(theme.palette.text.primary, theme.palette.action.hoverOpacity), + }, + '&.Mui-focusVisible': { + backgroundColor: theme.palette.action.focus, + }, + [theme.breakpoints.up('md')]: { + paddingTop: 6, + paddingBottom: 6, + }, +})); -const ItemLink = styled(Item.withComponent(Link), { +const ItemLink = styled(Item, { shouldForwardProp: (prop) => prop !== 'depth', })(({ depth, theme }) => { return { @@ -78,7 +75,7 @@ const ItemButtonIcon = styled(ArrowRightIcon, { }; }); -const ItemButton = styled(Item.withComponent(ButtonBase), { +const ItemButton = styled(Item, { shouldForwardProp: (prop) => prop !== 'depth', })(({ depth, theme }) => { return { @@ -120,6 +117,7 @@ export default function AppNavDrawerItem(props) { return ( = Pick>; export interface StyledComponent extends React.FunctionComponent, - ComponentSelector { - /** - * @desc this method is type-unsafe - */ - withComponent( - tag: NewTag, - ): StyledComponent; - withComponent>( - tag: Tag, - ): StyledComponent, StyleProps, Theme>; -} + ComponentSelector {} export interface StyledOptions { label?: string; From be825e1575c7f9bc1acd62f9df59afa91b18cbbe Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sun, 15 Aug 2021 15:46:15 +0200 Subject: [PATCH 2/2] [styled-engine] Add support for goober --- .codesandbox/ci.json | 4 +- babel.config.js | 3 + docs/babel.config.js | 4 +- docs/package.json | 2 + docs/src/modules/components/ThemeContext.js | 2 +- .../README.md | 23 +++++ .../package.json | 54 ++++++++++++ .../src/GlobalStyles/GlobalStyles.ts | 29 +++++++ .../src/GlobalStyles/index.ts | 2 + .../StyledEngineProvider.tsx | 3 + .../src/StyledEngineProvider/index.ts | 2 + .../src/index.tsx | 83 +++++++++++++++++++ .../tsconfig.json | 4 + .../material-ui-system/src/createStyled.js | 2 +- test/karma.tests.js | 7 ++ tsconfig.json | 2 + webpackBaseConfig.js | 4 + yarn.lock | 5 ++ 18 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 packages/material-ui-styled-engine-goober/README.md create mode 100644 packages/material-ui-styled-engine-goober/package.json create mode 100644 packages/material-ui-styled-engine-goober/src/GlobalStyles/GlobalStyles.ts create mode 100644 packages/material-ui-styled-engine-goober/src/GlobalStyles/index.ts create mode 100644 packages/material-ui-styled-engine-goober/src/StyledEngineProvider/StyledEngineProvider.tsx create mode 100644 packages/material-ui-styled-engine-goober/src/StyledEngineProvider/index.ts create mode 100644 packages/material-ui-styled-engine-goober/src/index.tsx create mode 100644 packages/material-ui-styled-engine-goober/tsconfig.json diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 6dae83fb6a85b5..78dff475afaed9 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -14,7 +14,8 @@ "packages/material-ui-utils", "packages/material-ui-unstyled", "packages/material-ui-styled-engine", - "packages/material-ui-styled-engine-sc" + "packages/material-ui-styled-engine-sc", + "packages/material-ui-styled-engine-goober" ], "publishDirectory": { "@material-ui/codemod": "packages/material-ui-codemod/build", @@ -24,6 +25,7 @@ "@material-ui/styles": "packages/material-ui-styles/build", "@material-ui/styled-engine": "packages/material-ui-styled-engine/build", "@material-ui/styled-engine-sc": "packages/material-ui-styled-engine-sc/build", + "@material-ui/styled-engine-goober": "packages/material-ui-styled-engine-goober/build", "@material-ui/system": "packages/material-ui-system/build", "@material-ui/private-theming": "packages/material-ui-private-theming/build", "@material-ui/types": "packages/material-ui-types/build", diff --git a/babel.config.js b/babel.config.js index 7c3ba507ed0bee..09f50bfceb0fb3 100644 --- a/babel.config.js +++ b/babel.config.js @@ -15,6 +15,9 @@ const defaultAlias = { '@material-ui/lab': resolveAliasPath('./packages/material-ui-lab/src'), '@material-ui/styled-engine': resolveAliasPath('./packages/material-ui-styled-engine/src'), '@material-ui/styled-engine-sc': resolveAliasPath('./packages/material-ui-styled-engine-sc/src'), + '@material-ui/styled-engine-goober': resolveAliasPath( + './packages/material-ui-styled-engine-goober/src', + ), '@material-ui/styles': resolveAliasPath('./packages/material-ui-styles/src'), '@material-ui/system': resolveAliasPath('./packages/material-ui-system/src'), '@material-ui/private-theming': resolveAliasPath('./packages/material-ui-private-theming/src'), diff --git a/docs/babel.config.js b/docs/babel.config.js index c9e98edcca5209..843ba61d0debde 100644 --- a/docs/babel.config.js +++ b/docs/babel.config.js @@ -19,10 +19,10 @@ const alias = { '@material-ui/icons': '../packages/material-ui-icons/lib', '@material-ui/lab': '../packages/material-ui-lab/src', '@material-ui/styles': '../packages/material-ui-styles/src', - '@material-ui/styled-engine-sc': '../packages/material-ui-styled-engine-sc/src', // Swap the comments on the next two lines for using the styled-components as style engine - '@material-ui/styled-engine': '../packages/material-ui-styled-engine/src', + // '@material-ui/styled-engine': '../packages/material-ui-styled-engine/src', // '@material-ui/styled-engine': '../packages/material-ui-styled-engine-sc/src', + '@material-ui/styled-engine': '../packages/material-ui-styled-engine-goober/src', '@material-ui/system': '../packages/material-ui-system/src', '@material-ui/private-theming': '../packages/material-ui-private-theming/src', '@material-ui/utils': '../packages/material-ui-utils/src', diff --git a/docs/package.json b/docs/package.json index 0a82208bcbee35..f6e26674f5a33b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -37,6 +37,7 @@ "@material-ui/lab": "5.0.0-alpha.43", "@material-ui/styled-engine": "5.0.0-beta.4", "@material-ui/styled-engine-sc": "5.0.0-beta.1", + "@material-ui/styled-engine-goober": "5.0.0-beta.1", "@material-ui/styles": "5.0.0-beta.4", "@material-ui/system": "5.0.0-beta.4", "@material-ui/types": "6.0.2", @@ -77,6 +78,7 @@ "final-form": "^4.18.5", "flexsearch": "^0.7.0", "fs-extra": "^10.0.0", + "goober": "^2.0.0", "json2mq": "^0.2.0", "jss": "^10.0.3", "jss-plugin-template": "^10.0.3", diff --git a/docs/src/modules/components/ThemeContext.js b/docs/src/modules/components/ThemeContext.js index ee9259f5dcfbab..3516b290a2c3a9 100644 --- a/docs/src/modules/components/ThemeContext.js +++ b/docs/src/modules/components/ThemeContext.js @@ -216,7 +216,7 @@ export function ThemeProvider(props) { components: { MuiCssBaseline: { styleOverrides: { - body: paletteMode === 'dark' ? darkScrollbar() : null, + body: paletteMode === 'dark' ? darkScrollbar() : {}, }, }, }, diff --git a/packages/material-ui-styled-engine-goober/README.md b/packages/material-ui-styled-engine-goober/README.md new file mode 100644 index 00000000000000..384d65d45011e2 --- /dev/null +++ b/packages/material-ui-styled-engine-goober/README.md @@ -0,0 +1,23 @@ +# @material-ui/styled-engine-goober + +This package is a wrapper around the `goober` package. +It is created to be used as alias for the `@material-ui/styled-engine` package, for the developers who would like to use `goober` as a styled engine instead of `@emotion/styled`. + +## Installation + +The installation of the dependency in your package is slightly different from the usual. +You need to alias the default `emotion` implementation to the `goober` one. +Depending on the bundler you are using, you made add it like this: + +### webpack + +```js +module.exports = { + //... + resolve: { + alias: { + '@material-ui/styled-engine': '@material-ui/styled-engine-goober', + }, + }, +}; +``` diff --git a/packages/material-ui-styled-engine-goober/package.json b/packages/material-ui-styled-engine-goober/package.json new file mode 100644 index 00000000000000..30d281c12f1179 --- /dev/null +++ b/packages/material-ui-styled-engine-goober/package.json @@ -0,0 +1,54 @@ +{ + "name": "@material-ui/styled-engine-goober", + "version": "5.0.0-beta.1", + "private": false, + "author": "Material-UI Team", + "description": "styled() API wrapper package for goober.", + "main": "./src/index.js", + "keywords": [ + "react", + "react-component", + "material-ui", + "goober" + ], + "repository": { + "type": "git", + "url": "https://github.com/mui-org/material-ui.git", + "directory": "packages/material-ui-styled-engine-goober" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/mui-org/material-ui/issues" + }, + "homepage": "https://next.material-ui.com/guides/styled-engine/", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "scripts": { + "build": "yarn build:legacy && yarn build:modern && yarn build:node && yarn build:stable && yarn build:copy-files", + "build:legacy": "node ../../scripts/build legacy", + "build:modern": "node ../../scripts/build modern", + "build:node": "node ../../scripts/build node", + "build:stable": "node ../../scripts/build stable", + "build:copy-files": "node ../../scripts/copy-files.js", + "prebuild": "rimraf build", + "release": "yarn build && npm publish build --tag next", + "test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/material-ui-styled-engine-goober/**/*.test.{js,ts,tsx}'", + "typescript": "tslint -p tsconfig.json \"{src,test}/**/*.{spec,d}.{ts,tsx}\" && tsc -p tsconfig.json" + }, + "dependencies": { + "@emotion/unitless": "^0.7.5", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "goober": "^5.0.0" + }, + "sideEffects": false, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=12.0.0" + } +} diff --git a/packages/material-ui-styled-engine-goober/src/GlobalStyles/GlobalStyles.ts b/packages/material-ui-styled-engine-goober/src/GlobalStyles/GlobalStyles.ts new file mode 100644 index 00000000000000..34725b2535a337 --- /dev/null +++ b/packages/material-ui-styled-engine-goober/src/GlobalStyles/GlobalStyles.ts @@ -0,0 +1,29 @@ +import PropTypes from 'prop-types'; +import { deepmerge } from '@material-ui/utils'; +import { createGlobalStyles } from 'goober/global'; + +function isEmpty(obj) { + return obj === undefined || obj === null || Object.keys(obj).length === 0; +} + +const GlobalStyles = createGlobalStyles((props) => { + const { styles, defaultTheme = {} } = props; + + if (typeof styles === 'function') { + const globalStyles = styles(isEmpty(props.theme) ? defaultTheme : props.theme); + const mergeStyles = Array.isArray(globalStyles) + ? globalStyles.reduce((acc, item) => deepmerge(acc, item), {}) + : globalStyles; + + return mergeStyles; + } + + return styles; +}); + +GlobalStyles.propTypes = { + defaultTheme: PropTypes.object, + styles: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.func]), +}; + +export default GlobalStyles; diff --git a/packages/material-ui-styled-engine-goober/src/GlobalStyles/index.ts b/packages/material-ui-styled-engine-goober/src/GlobalStyles/index.ts new file mode 100644 index 00000000000000..a815f5c4a572b2 --- /dev/null +++ b/packages/material-ui-styled-engine-goober/src/GlobalStyles/index.ts @@ -0,0 +1,2 @@ +export { default } from './GlobalStyles'; +export * from './GlobalStyles'; diff --git a/packages/material-ui-styled-engine-goober/src/StyledEngineProvider/StyledEngineProvider.tsx b/packages/material-ui-styled-engine-goober/src/StyledEngineProvider/StyledEngineProvider.tsx new file mode 100644 index 00000000000000..087e736373a426 --- /dev/null +++ b/packages/material-ui-styled-engine-goober/src/StyledEngineProvider/StyledEngineProvider.tsx @@ -0,0 +1,3 @@ +export default function StyledEngineProvider(props) { + return props.children; +} diff --git a/packages/material-ui-styled-engine-goober/src/StyledEngineProvider/index.ts b/packages/material-ui-styled-engine-goober/src/StyledEngineProvider/index.ts new file mode 100644 index 00000000000000..7fdd272b28e6ca --- /dev/null +++ b/packages/material-ui-styled-engine-goober/src/StyledEngineProvider/index.ts @@ -0,0 +1,2 @@ +export { default } from './StyledEngineProvider'; +export * from './StyledEngineProvider'; diff --git a/packages/material-ui-styled-engine-goober/src/index.tsx b/packages/material-ui-styled-engine-goober/src/index.tsx new file mode 100644 index 00000000000000..7be9304407e14a --- /dev/null +++ b/packages/material-ui-styled-engine-goober/src/index.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { styled as styledGoober, setup } from 'goober'; +import unitless from '@emotion/unitless'; +import { prefix } from 'goober/prefixer'; + +export const ThemeContext = React.createContext({}); + +// TODO drop hard coded function +function shouldForwardProp(prop: string) { + return prop !== 'styleProps' && prop !== 'theme' && prop !== 'sx' && prop !== 'as'; +} + +const useTheme = () => React.useContext(ThemeContext); + +function forwardProps(props: object) { + Object.keys(props).forEach((prop) => { + // Or any other conditions. + // This could also check if this is a dev build and not remove the props + if (!shouldForwardProp(prop)) { + // @ts-ignore + delete props[prop]; + } + }); +} + +function isCustomProperty(property: string) { + // 45 is - + return property.charCodeAt(1) === 45; +} + +function camelize(str: string) { + return str.replace(/-./g, (chunk) => chunk[1].toUpperCase()); +} + +function plugins(property: string, inValue: any) { + // Add default px unit when needed + let value = inValue; + const key = camelize(property); + if ( + unitless[key] !== 1 && + !isCustomProperty(property) && + typeof value === 'number' && + value !== 0 + ) { + value = `${value}px`; + } + + return prefix(property, value); +} + +// @ts-expect-error Wrong type https://github.com/cristianbote/goober/pull/364 +setup(React.createElement, plugins, useTheme, forwardProps); + +// TODO hanle options +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export default function styled(tag: any, options: any) { + const stylesFactory = styledGoober(tag, React.forwardRef); + + return (...styles: any[]) => { + if (process.env.NODE_ENV !== 'production') { + const component = typeof tag === 'string' ? `"${tag}"` : 'component'; + if (styles.length === 0) { + console.error( + [ + `Material-UI: Seems like you called \`styled(${component})()\` without a \`style\` argument.`, + 'You must provide a `styles` argument: `styled("div")(styleYouForgotToPass)`.', + ].join('\n'), + ); + } else if (styles.some((style) => style === undefined)) { + console.error( + `Material-UI: the styled(${component})(...args) API requires all its args to be defined.`, + ); + } + } + + // @ts-ignore + return stylesFactory(styles); + }; +} + +export { keyframes, css } from 'goober'; +export { default as StyledEngineProvider } from './StyledEngineProvider'; +export { default as GlobalStyles } from './GlobalStyles'; diff --git a/packages/material-ui-styled-engine-goober/tsconfig.json b/packages/material-ui-styled-engine-goober/tsconfig.json new file mode 100644 index 00000000000000..57220954971e1e --- /dev/null +++ b/packages/material-ui-styled-engine-goober/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig", + "include": ["src/**/*", "test/**/*"] +} diff --git a/packages/material-ui-system/src/createStyled.js b/packages/material-ui-system/src/createStyled.js index 892d08d8d77c7a..75d9106c588d9d 100644 --- a/packages/material-ui-system/src/createStyled.js +++ b/packages/material-ui-system/src/createStyled.js @@ -5,7 +5,7 @@ import styleFunctionSx from './styleFunctionSx'; import propsToClassKey from './propsToClassKey'; function isEmpty(obj) { - return Object.keys(obj).length === 0; + return obj == null || Object.keys(obj).length === 0; } const getStyleOverrides = (name, theme) => { diff --git a/test/karma.tests.js b/test/karma.tests.js index 0d9e3477dd9e56..bc9638dd454160 100644 --- a/test/karma.tests.js +++ b/test/karma.tests.js @@ -36,6 +36,13 @@ const styledEngineSCContext = require.context( ); styledEngineSCContext.keys().forEach(styledEngineSCContext); +const styledEngineGooberContext = require.context( + '../packages/material-ui-styled-engine-goober/src/', + true, + /\.test\.(js|ts|tsx)$/, +); +styledEngineGooberContext.keys().forEach(styledEngineGooberContext); + const systemContext = require.context( '../packages/material-ui-system/src/', true, diff --git a/tsconfig.json b/tsconfig.json index ded72cb01a3a2d..0563e0f195dc41 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,8 @@ "@material-ui/styled-engine/*": ["./packages/material-ui-styled-engine/src/*"], "@material-ui/styled-engine-sc": ["./packages/material-ui-styled-engine-sc/src"], "@material-ui/styled-engine-sc/*": ["./packages/material-ui-styled-engine-sc/src/*"], + "@material-ui/styled-engine-goober": ["./packages/material-ui-styled-engine-goober/src"], + "@material-ui/styled-engine-goober/*": ["./packages/material-ui-styled-engine-goober/src/*"], "@material-ui/styles": ["./packages/material-ui-styles/src"], "@material-ui/styles/*": ["./packages/material-ui-styles/src/*"], "@material-ui/system": ["./packages/material-ui-system/src"], diff --git a/webpackBaseConfig.js b/webpackBaseConfig.js index d41bffde4cd26a..d7f0151cb04bb3 100644 --- a/webpackBaseConfig.js +++ b/webpackBaseConfig.js @@ -21,6 +21,10 @@ module.exports = { __dirname, './packages/material-ui-styled-engine-sc/src', ), + '@material-ui/styled-engine-goober': path.resolve( + __dirname, + './packages/material-ui-styled-engine-goober/src', + ), '@material-ui/styles': path.resolve(__dirname, './packages/material-ui-styles/src'), '@material-ui/system': path.resolve(__dirname, './packages/material-ui-system/src'), '@material-ui/private-theming': path.resolve( diff --git a/yarn.lock b/yarn.lock index d8fd2b5940d645..93d1ced622c350 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8674,6 +8674,11 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" +goober@^2.0.0: + version "2.0.40" + resolved "https://registry.yarnpkg.com/goober/-/goober-2.0.40.tgz#7f3d4cafdf6854da55460a639e9b3a15848aaaf3" + integrity sha512-LnYclgtz+t2Q9fDEIEdMKCoh5+JyBwG6ORJme52Av18ZrajhqEtzACXWhL/+IIB0SegvwXLcGG7FH+BJ/Z3Ljw== + good-listener@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"