UNPKG

react-inln

Version:

Define react component style for multiple breakpoints directly in props

91 lines (80 loc) 2.91 kB
import React, { Component } from 'react' class Element extends Component { state = { css: {} } breakpoints = [ { match: 'only screen and (max-width: 575px)', alias: 'xs' }, { match: 'only screen and (max-width: 768px)', alias: 's' }, { match: 'only screen and (max-width: 992px)', alias: 'm' }, { match: 'only screen and (max-width: 1200px)', alias: 'l' }, { match: 'only screen and (min-width: 1201px)', alias: 'xl' }, ] _breakpoints = [] ignoreAttributes = ['length', 'src'] cssAttributes = (() => { // this, could be simple Object.keys....filter but firefox and ie doesn't support const attrs = [] for (const attr in document.body.style) { if (['length', 'src'].includes(attr)) continue if (['string', 'number'].includes(typeof document.body.style[attr])) { attrs.push(attr) } } return attrs })() equalCss = (prev, next) => { return JSON.stringify(prev, this.cssAttributes) === JSON.stringify(next, this.cssAttributes) } componentDidMount() { this._breakpoints = this.breakpoints.map(({ match, alias }) => ( { alias, mql: window.matchMedia(match), listener: (mql) => this.matchedMedia(alias, mql) } )) this._breakpoints.forEach(({ mql, listener }) => mql.addListener(listener)) this._breakpoints.forEach(({ mql, listener }) => listener(mql)) } componentWillUnmount() { this._breakpoints.forEach(({ mql, listener }) => mql.removeListener(listener)) } componentDidUpdate(prevProps) { if (!this.equalCss(prevProps, this.props)) { this._breakpoints.forEach(({ mql, listener }) => listener(mql)) } } extractFromProps = (props) => { const { tag: Tag = 'div', children, ...rest } = props let cssRules = {} let otherProps = {} for (let prop in rest) { const value = rest[prop] let [ key, breakpoint = '' ] = prop.split('_') if (this.cssAttributes.includes(key)) { cssRules[breakpoint] = { ...cssRules[breakpoint], [key]: value } } else { otherProps = { ...otherProps, [key]: value } } } return { Tag, children, cssRules, props: otherProps } } matchedMedia = (breakpoint, mql) => { const { cssRules } = this.extractFromProps(this.props) let css = cssRules[''] || {} if (breakpoint in cssRules && mql.matches) { css = { ...css, ...cssRules[breakpoint] } } else { const brk = this._breakpoints.find(b => { return b.alias in cssRules && b.mql.matches }) if (brk) { css = { ...css, ...cssRules[brk.alias] } } } if (css && Object.keys(css).length) { if (!this.equalCss(this.state.css, css)) { this.setState({ css }) } } } render () { const { Tag, children, props } = this.extractFromProps(this.props) return <Tag {...props} style={this.state.css}>{children}</Tag> } } export default Element