0.1.0 • Published 5 years ago

mui-styling v0.1.0

Weekly downloads
1
License
MIT
Repository
-
Last release
5 years ago

MUI Styling

Utilities for building sustainable React Material-UI components

The Problem

Material-UI is a great react-component library. it has a lot of base components and styling pattern is all about javascript. Jss means you don't need to write .css, .scss or do the compiling stuffs (it is the part I love the most). But! there will be the day that you need customized components or very specific ones. Theses are problems that I found.

  1. What is the pattern for building a new one.
  2. Overriding components is hard
  3. How to change some styles in the nested component?
  4. If I want to change the default styles into a whole new one, how can I do that?

I spend a lot of time solving these questions, and the solution lies in mui-styling

Benefits

mui-styling will help you unleash the potential of Material-UI without external library (because it is based on @material-ui/styles)

  • Able to create reusable components
  • Follow styling best practice
  • Styling with confidence

Install

npm install mui-styling

// or

yarn add mui-styling

API

withStyles(styleCreator: fn, options?: object)

this is the same withStyles from @material-ui/styles. it is exported here for convenient.

Example
import React from 'react';
import { withStyles } from 'mui-styling';

const createStyles = () => ({
  childRoot: {},
  childDot: { fontSize: 32, lineHeight: 1 },
});

const Child = withStyles(createStyles)(({ classes }) => {
  // withStyles will inject an object(named "classes") as props
  // classes = { childRoot: '{autogen className}', childDot: '{autogen className}' }
  return (
    <div className={css.childRoot}>
      <span className={css.childDot}>•</span>
    </div>
  );
});

expot default Child;

attachStylingParams(Component: React.Component, styleCreator: fn, options: object)

This fn will attach these properties stylesAttrs, pickClasses and getOverrides to the Component

Example;
import React from 'react';
import PropTypes from 'prop-types';
import { attachStylingParams, withStyles } from 'mui-styling';

const createStyles = () => ({
  childRoot: {},
  childDot: { fontSize: 32, lineHeight: 1 },
});

const options = { name: 'Child' };

const Child = withStyles(createStyles, options)(({ classes, overrides }) => {
  const css = overrides || classes;
  return (
    <div className={css.childRoot}>
      <span className={css.childDot}>•</span>
    </div>
  );
});

Child.propTypes = {
  overrides: PropTypes.shape({}),
  classes: PropTypes.shape({}),
};
Child.defaultProps = {
  overrides: undefined,
  classes: undefined,
};

attachStylingParams(Child, createStyles, options);
// Now 'Child' have these properties that will be used in other components
// Child.styleAttrs: ['childRoot', 'childDot'];
// Child.pickClasses: fn;
// Child.getOverrides: fn;

export default Child;
styleAttrs is an array of attributes that come from createStyles result
pickClasses is a fn that will pick only attributes that are defined in styleAttrs
getOverrides is a fn that return overrides props to the component

See implementation

mergeStyleCreators(...creators: arrayOf(styleCreator))

This fn is used when you create complex component that consist of multiple components it returns a new styleCreator

Example
import { mergeStyleCreators } from 'mui-styling';

const createChildStyles = () => ({
  childRoot: {
    color: 'red',
  },
})

const createParentStyles = mergeStyleCreators(
  createChildStyles,
  () => ({
    parentRoot: {
      display: 'flex',
    },
  })
)

The benefit of this fn is that it warns** you when it find attributes collision.

Example of attributes collision
import { mergeStyleCreators } from 'mui-styling';

const createChildStyles = () => ({
  childRoot: {
    color: 'red',
  },
  general: { fontSize: 16 },
})

const createParentStyles = mergeStyleCreators(
  createChildStyles,
  () => ({
    parentRoot: {
      display: 'flex',
    },
    general: { fontSize: 16 },
  })
)

In console
> mui-styling: Styles collision alert! attributes: [general]. This might cause bugs in styling, please check these style attributes

Building Components

this example show how to create nested components with mui-styling

Child.styles.js
export default () => ({
  childRoot: {},
  childDot: { fontSize: 32, lineHeight: 1 },
});
Child.js
import React from 'react';
import PropTypes from 'prop-types';
import { attachStylingParams, withStyles } from 'mui-styling';
import createStyles from './Child.styles';

const options = { name: 'Child' };

const Child = withStyles(createStyles, options)(({ classes, overrides }) => {
  const css = overrides || classes;
  return (
    <div className={css.childRoot}>
      <span className={css.childDot}>•</span>
    </div>
  );
});

attachStylingParams(Child, createStyles, options);

export default Child;
Parent.styles.js
import { mergeStyleCreators } from 'mui-styling';
import { createStyles as createChildStyles } from '../Child';

export default mergeStyleCreators(createChildStyles, () => ({
  parentRoot: {
    display: 'flex',
    alignItems: 'center',
    minHeight: 32,
    padding: '0 16px',
    '&:hover': {
      backgroundColor: '#f7f7f7',
    },
  },
  parentLabel: {
    margin: '0 auto 0 0',
  },
}));
Parent.js
import React from 'react';
import PropTypes from 'prop-types';
import Child from './Child';
import { attachStylingParams, withStyles } from 'mui-styling';
import createStyles from './Parent.styles';

const options = { name: 'Parent' };

const Parent = withStyles(createStyles, options)(props => {
  const { classes, overrides, label } = props;
  const css = overrides || classes;
  return (
    <div className={css.parentRoot}>
      <p className={css.parentLabel}>{label}</p>
      <Child {...Child.getOverrides(css, props)} />
    </div>
  );
});

attachStylingParams(Parent, createStyles, options);

export default Parent;
Root.styles.js
import { mergeStyleCreators } from 'mui-styling';
import { createStyles as createParentStyles } from '../Parent';

export default mergeStyleCreators(createParentStyles, () => ({
  root: {},
}));
Root.js
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Parent from './Parent';
import { attachStylingParams } from 'mui-styling';
import createStyles from './Root.styles';

const options = { name: 'Root' };

const Root = withStyles(createStyles, options)(props => {
  const { items, classes, overrides, childOverrides } = props;
  const css = overrides || classes;
  return (
    <div className={css.root}>
      {items.map(label => (
        <Parent
          key={label}
          label={label}
          childOverrides={childOverrides}
          {...Parent.getOverrides(css, props)}
        />
      ))}
    </div>
  );
});

Root.displayName = 'Root';

attachStylingParams(Root, createStyles, options);

export default Root;

Page.js

This is where we use the Root component and try to customize it

import { makeStyles } from '@material-ui/styles';
import Root from './Root';
import Parent from './Parent';
import createParentStyles from './Parent.styles';

const useCustomStyles = makeStyles(({ palette }) => ({
  // as you can see, we can access every css api of the components
  // there is no magic, I just found the way to make it works
  // my code is easy as it looks, see in my repo
  parentRoot: {
    '&:hover': {
      '& $parentLabel, $childDot': {
        color: palette.primary.main,
      },
    },
  },
  parentLabel: {},
  childDot: {},
}));

const useOverrideStyles = makeStyles(theme => ({
  root: {
    boxShadow: '0 0 4px 0 rgba(0,0,0,0.32)',
  },
  parentRoot: {
    ...createParentStyles(theme).parentRoot,
    '&:hover': {
      backgroundColor: theme.palette.primary.main,
      '& $parentLabel, $childDot': {
        // as you can see, you can override nested component with this approach
        color: '#fff',
      },
    },
  },
  parentLabel: {},
  childRoot: {
    marginLeft: 'auto',
  },
  childDot: {},
  test: {}, // this will not effect styling since it is not related to style attrs defined in Root
}));

const ITEMS = ['Siriwat', 'Kotchakorn', 'Hello World', 'Programming'];

const CustomRoot1 = () => {
  const styles = useCustomStyles();
  return <Root items={ITEMS} classes={styles} />;
};

const OverrideRoot1 = () => {
  const styles = useOverrideStyles();
  return <Root items={ITEMS} overrides={styles} />;
};

See Full Demo

Test

use jest & enzyme

yarn test

License

MIT