Skip to content

Commit

Permalink
[Box] Add sx prop (#23053)
Browse files Browse the repository at this point in the history
  • Loading branch information
mnajdova authored Oct 22, 2020
1 parent d0c2a16 commit a3ce82a
Show file tree
Hide file tree
Showing 22 changed files with 526 additions and 47 deletions.
26 changes: 26 additions & 0 deletions benchmark/browser/scenarios/sx-prop-box-material-ui/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';

export default function BoxSxPropMaterialUI() {
return (
<React.Fragment>
{new Array(1000).fill().map(() => (
<Box
sx={{
width: 200,
height: 200,
backgroundColor: [undefined, 'primary.light', 'primary.main', 'primary.dark'],
borderWidth: '3px',
borderColor: 'white',
borderStyle: [undefined, 'dashed', 'solid', 'dotted'],
':hover': {
backgroundColor: (theme) => theme.palette.secondary.dark,
},
}}
>
material-ui
</Box>
))}
</React.Fragment>
);
}
1 change: 1 addition & 0 deletions benchmark/browser/scripts/benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ async function run() {
await runMeasures(browser, 'Chakra-UI box component', './box-chakra-ui/index.js', 10);
await runMeasures(browser, 'Theme-UI box sx prop', './sx-prop-box-theme-ui/index.js', 10);
await runMeasures(browser, 'Theme-UI div sx prop', './sx-prop-div-theme-ui/index.js', 10);
await runMeasures(browser, 'Material-UI box sx prop', './sx-prop-box-material-ui/index.js', 10);
} finally {
await Promise.all([browser.close(), server.close()]);
}
Expand Down
11 changes: 11 additions & 0 deletions docs/src/pages/components/box/BoxClone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react';
import Button from '@material-ui/core/Button';
import Box from '@material-ui/core/Box';

export default function BoxClone() {
return (
<Box border="1px dashed grey" clone>
<Button>Save</Button>
</Box>
);
}
11 changes: 11 additions & 0 deletions docs/src/pages/components/box/BoxClone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react';
import Button from '@material-ui/core/Button';
import Box from '@material-ui/core/Box';

export default function BoxClone() {
return (
<Box border="1px dashed grey" clone>
<Button>Save</Button>
</Box>
);
}
11 changes: 11 additions & 0 deletions docs/src/pages/components/box/BoxComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';

export default function BoxComponent() {
return (
<Box component="span" p={2} border="1px dashed grey">
<Button>Save</Button>
</Box>
);
}
11 changes: 11 additions & 0 deletions docs/src/pages/components/box/BoxComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';

export default function BoxComponent() {
return (
<Box component="span" p={2} border="1px dashed grey">
<Button>Save</Button>
</Box>
);
}
11 changes: 11 additions & 0 deletions docs/src/pages/components/box/BoxRenderProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react';
import Button from '@material-ui/core/Button';
import Box from '@material-ui/core/Box';

export default function BoxClone() {
return (
<Box border="1px dashed grey">
{(props) => <Button {...props}>Save</Button>}
</Box>
);
}
11 changes: 11 additions & 0 deletions docs/src/pages/components/box/BoxRenderProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react';
import Button from '@material-ui/core/Button';
import Box from '@material-ui/core/Box';

export default function BoxClone() {
return (
<Box border="1px dashed grey">
{(props: { className: string }) => <Button {...props}>Save</Button>}
</Box>
);
}
18 changes: 18 additions & 0 deletions docs/src/pages/components/box/BoxSx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';

export default function BoxSx() {
return (
<Box
sx={{
width: 300,
height: 300,
bgcolor: 'primary.dark',
':hover': {
backgroundColor: 'primary.main',
opacity: [0.9, 0.8, 0.7],
},
}}
/>
);
}
18 changes: 18 additions & 0 deletions docs/src/pages/components/box/BoxSx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';

export default function BoxSx() {
return (
<Box
sx={{
width: 300,
height: 300,
bgcolor: 'primary.dark',
':hover': {
backgroundColor: 'primary.main',
opacity: [0.9, 0.8, 0.7],
},
}}
/>
);
}
22 changes: 9 additions & 13 deletions docs/src/pages/components/box/box.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ The Box component wraps your component.
It creates a new DOM element, a `<div>` by default that can be changed with the `component` prop.
Let's say you want to use a `<span>` instead:

```jsx
<Box component="span" m={1}>
<Button />
</Box>
```
{{"demo": "pages/components/box/BoxComponent.js", "defaultCodeOpen": true }}

This works great when the changes can be isolated to a new DOM element.
For instance, you can change the margin this way.
Expand All @@ -40,23 +36,23 @@ To workaround the problem, you have two options:

The Box component has a `clone` prop to enable the use of the clone element method of React.

```jsx
<Box color="text.primary" clone>
<Button />
</Box>
```
{{"demo": "pages/components/box/BoxClone.js", "defaultCodeOpen": true }}

2. Use render props

The Box children accepts a render props function. You can pull out the `className`.

```jsx
<Box color="text.primary">{(props) => <Button {...props} />}</Box>
```
{{"demo": "pages/components/box/BoxRenderProps.js", "defaultCodeOpen": true }}

> ⚠️ The CSS specificity relies on the import order.
> If you want the guarantee that the wrapped component's style will be overridden, you need to import the Box last.
## The sx prop

Sometimes, the props on the Box component are not enough to style the component. To solve this, `Box` supports the `sx` prop. This allows you to specify any CSS rules you want, in addition to the ones already available using system props. Here is an example of how you can use it:

{{"demo": "pages/components/box/BoxSx.js", "defaultCodeOpen": true }}

## API

```jsx
Expand Down
8 changes: 7 additions & 1 deletion packages/material-ui-system/src/breakpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ export function handleBreakpoints(props, propValue, styleFromPropValue) {
if (typeof propValue === 'object') {
const themeBreakpoints = props.theme.breakpoints || defaultBreakpoints;
return Object.keys(propValue).reduce((acc, breakpoint) => {
acc[themeBreakpoints.up(breakpoint)] = styleFromPropValue(propValue[breakpoint]);
// key is breakpoint
if (Object.keys(themeBreakpoints.values || values).indexOf(breakpoint) !== -1) {
acc[themeBreakpoints.up(breakpoint)] = styleFromPropValue(propValue[breakpoint]);
} else {
const cssKey = breakpoint;
acc[cssKey] = propValue[cssKey];
}
return acc;
}, {});
}
Expand Down
6 changes: 6 additions & 0 deletions packages/material-ui-system/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export type BordersProps = PropsFor<typeof borders>;
// breakpoints.js
type DefaultBreakPoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl';

export function handleBreakpoints<Props, Breakpoints extends string = DefaultBreakPoints>(
props: Props,
propValue: any,
styleFromPropValue: (value: any) => any
): any;

/**
* @returns An enhanced stylefunction that considers breakpoints
*/
Expand Down
1 change: 1 addition & 0 deletions packages/material-ui-system/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { default as borders } from './borders';
export * from './borders';
export { default as breakpoints } from './breakpoints';
export { handleBreakpoints } from './breakpoints';
export { default as compose } from './compose';
export { default as css } from './css';
export { default as display } from './display';
Expand Down
7 changes: 6 additions & 1 deletion packages/material-ui-system/src/palette.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export const bgcolor = style({
themeKey: 'palette',
});

const palette = compose(color, bgcolor);
export const backgroundColor = style({
prop: 'backgroundColor',
themeKey: 'palette',
});

const palette = compose(color, bgcolor, backgroundColor);

export default palette;
13 changes: 12 additions & 1 deletion packages/material-ui/src/Box/Box.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {
typography,
PropsFor,
} from '@material-ui/system';
import { Theme } from '../styles/createMuiTheme';
import { CSSObject } from '../styles/experimentalStyled';
import { Omit } from '..';

type BoxStyleFunction = ComposedStyleFunction<
export type BoxStyleFunction = ComposedStyleFunction<
[
typeof borders,
typeof display,
Expand All @@ -32,14 +34,23 @@ type BoxStyleFunction = ComposedStyleFunction<

type SystemProps = PropsFor<BoxStyleFunction>;
type ElementProps = Omit<React.HTMLAttributes<HTMLElement>, keyof SystemProps>;
type SxPropsValue = Omit<CSSObject, keyof SystemProps> & SystemProps;
type SxProps = {
[Name in keyof SxPropsValue]?:
| SxPropsValue[Name]
| ((theme: Theme) => CSSObject | SxPropsValue[Name])
| SxProps;
};

export interface BoxProps extends ElementProps, SystemProps {
children?: React.ReactNode | ((props: ElementProps) => React.ReactNode);
// styled API
component?: React.ElementType;
clone?: boolean;
ref?: React.Ref<unknown>;
// workaround for https://github.com/mui-org/material-ui/pull/15611
css?: SystemProps;
sx?: SxProps;
}

declare const Box: React.ComponentType<BoxProps>;
Expand Down
32 changes: 2 additions & 30 deletions packages/material-ui/src/Box/Box.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,9 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import {
borders,
compose,
display,
flexbox,
grid,
palette,
positions,
shadows,
sizing,
spacing,
typography,
css,
} from '@material-ui/system';
import clsx from 'clsx';
import styleFunction from './styleFunction';
import styled from '../styles/experimentalStyled';

export const styleFunction = css(
compose(
borders,
display,
flexbox,
grid,
positions,
palette,
shadows,
sizing,
spacing,
typography,
),
);

function omit(input, fields) {
const output = {};

Expand Down Expand Up @@ -71,7 +43,7 @@ const BoxRoot = React.forwardRef(function StyledComponent(props, ref) {
});

BoxRoot.propTypes = {
children: PropTypes.node,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
className: PropTypes.string,
clone: PropTypes.bool,
component: PropTypes.elementType,
Expand Down
2 changes: 2 additions & 0 deletions packages/material-ui/src/Box/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { default } from './Box';
export * from './Box';
export { default as styleFunction } from './styleFunction';
export * from './styleFunction';
3 changes: 2 additions & 1 deletion packages/material-ui/src/Box/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default, styleFunction } from './Box';
export { default } from './Box';
export { default as styleFunction } from './styleFunction';
4 changes: 4 additions & 0 deletions packages/material-ui/src/Box/styleFunction.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { BoxStyleFunction } from './Box';

declare const styleFunction: BoxStyleFunction;
export default styleFunction;
Loading

0 comments on commit a3ce82a

Please sign in to comment.