Skip to content

Instantly share code, notes, and snippets.

@rosskevin
Last active December 11, 2022 18:15
Show Gist options
  • Save rosskevin/0e484f45fa95e5c7f20acfccb21f6d60 to your computer and use it in GitHub Desktop.
Save rosskevin/0e484f45fa95e5c7f20acfccb21f6d60 to your computer and use it in GitHub Desktop.
material-ui `next` responsive layout/grid using JSS (originally based on flexboxgrid.com)
// @flow
export function capitalizeFirstLetter (string: string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
// @flow
import React, {Component, Element, PropTypes} from 'react'
import classNames from 'classnames'
import pure from 'recompose/pure'
import merge from 'lodash/merge'
import {createStyleSheet} from 'jss-theme-reactor'
import Logger from '../../util/Logger'
import {capitalizeFirstLetter} from '../../util/strings'
type DefaultProps = {
component: string
}
type Props = {
children: Element<any>,
component: string,
className: ?string,
//
xs: ?(number | boolean),
sm: ?(number | boolean),
md: ?(number | boolean),
lg: ?(number | boolean),
xsOffset: ?number,
smOffset: ?number,
mdOffset: ?number,
lgOffset: ?number,
reverse: ?boolean,
first: ?Breakpoints,
last: ?Breakpoints
}
function generateClasses (breakpoint) {
const bp = capitalizeFirstLetter(breakpoint)
return {
col: {
// flex: '0 0 auto'
// paddingRight: 'var(--half-gutter-width, 0.5rem)',
// paddingLeft: 'var(--half-gutter-width, 0.5rem)'
},
[`col${bp}`]: {
extend: 'col',
flexGrow: '1',
flexBasis: '0',
maxWidth: '100%'
},
[`col${bp}1`]: {
extend: 'col',
flexBasis: '8.33333333%',
maxWidth: '8.33333333%'
},
[`col${bp}2`]: {
extend: 'col',
flexBasis: '16.66666667%',
maxWidth: '16.66666667%'
},
[`col${bp}3`]: {
extend: 'col',
flexBasis: '25%',
maxWidth: '25%'
},
[`col${bp}4`]: {
extend: 'col',
flexBasis: '33.33333333%',
maxWidth: '33.33333333%'
},
[`col${bp}5`]: {
extend: 'col',
flexBasis: '41.66666667%',
maxWidth: '41.66666667%'
},
[`col${bp}6`]: {
extend: 'col',
flexBasis: '50%',
maxWidth: '50%'
},
[`col${bp}7`]: {
extend: 'col',
flexBasis: '58.33333333%',
maxWidth: '58.33333333%'
},
[`col${bp}8`]: {
extend: 'col',
flexBasis: '66.66666667%',
maxWidth: '66.66666667%'
},
[`col${bp}9`]: {
extend: 'col',
flexBasis: '75%',
maxWidth: '75%'
},
[`col${bp}10`]: {
extend: 'col',
flexBasis: '83.33333333%',
maxWidth: '83.33333333%'
},
[`col${bp}11`]: {
extend: 'col',
flexBasis: '91.66666667%',
maxWidth: '91.66666667%'
},
[`col${bp}12`]: {
extend: 'col',
flexBasis: '100%',
maxWidth: '100%'
},
[`col${bp}Offset0`]: {
marginLeft: '0'
},
[`col${bp}Offset1`]: {
marginLeft: '8.33333333%'
},
[`col${bp}Offset2`]: {
marginLeft: '16.66666667%'
},
[`col${bp}Offset3`]: {
marginLeft: '25%'
},
[`col${bp}Offset4`]: {
marginLeft: '33.33333333%'
},
[`col${bp}Offset5`]: {
marginLeft: '41.66666667%'
},
[`col${bp}Offset6`]: {
marginLeft: '50%'
},
[`col${bp}Offset7`]: {
marginLeft: '58.33333333%'
},
[`col${bp}Offset8`]: {
marginLeft: '66.66666667%'
},
[`col${bp}Offset9`]: {
marginLeft: '75%'
},
[`col${bp}Offset10`]: {
marginLeft: '83.33333333%'
},
[`col${bp}Offset11`]: {
marginLeft: '91.66666667%'
},
[`col${bp}First`]: {
order: '-1'
},
[`col${bp}Last`]: {
order: '1'
}
}
}
export const styleSheet = createStyleSheet('AfCol', (theme) => {
const { breakpoints } = theme
const classes = {
col: {
boxSizing: 'border-box'
// flex: '0 0 auto'
},
reverse: {
flexDirection: 'column-reverse'
}
}
const generated = generateClasses('xs')
generated[ breakpoints.up('sm') ] = generateClasses('sm')
generated[ breakpoints.up('md') ] = generateClasses('md')
generated[ breakpoints.up('lg') ] = generateClasses('lg')
return merge(generated, classes)
})
const MODIFIERS = [ 'xs', 'sm', 'md', 'lg', 'xsOffset', 'smOffset', 'mdOffset', 'lgOffset', 'first', 'last' ]
class Col extends Component<DefaultProps, Props, void> {
static contextTypes = {
styleManager: PropTypes.object.isRequired
}
static defaultProps: DefaultProps = {
component: 'div'
}
props: Props
render (): Element<any> {
const classes = this.context.styleManager.render(styleSheet)
const {
children, component, className: classNameProp, reverse,
xs, sm, md, lg, xsOffset, smOffset, mdOffset, lgOffset, first, last, // eslint-disable-line no-unused-vars
...rest
} = this.props
// accumulate all modifier classes
const modifierClasses = []
for (const key of MODIFIERS) {
const value = this.props[ key ]
if (value) {
let name
if (Number.isInteger(value)) {
// number case e.g. xs={3} colXs3
name = `col${capitalizeFirstLetter(key)}${String(value)}`
} else if (typeof value === 'string') {
// string case e.g. start='xs' colXsStart
name = `col${capitalizeFirstLetter(value)}${capitalizeFirstLetter(key)}`
} else if (typeof value === 'boolean') {
// boolean case e.g. xs @see auto width example
name = `col${capitalizeFirstLetter(key)}`
} else {
throw new Error(`Unknown handling of key[${key}] and value[${value}]`)
}
// log.debug(`${key}=${value}: [${name}]`, Number.isInteger(value), typeof value === 'string', typeof value === 'boolean')
modifierClasses.push(classes[ name ])
}
}
// log.debug(modifierClasses)
const className = classNames(...modifierClasses, {
[classes.reverse]: reverse
}, classNameProp)
return React.createElement(component, { className, ...rest }, children)
}
}
const log = Logger.get(Col) // eslint-disable-line
export default pure(Col)
// @flow
import React, {Component, Element, PropTypes} from 'react'
import classNames from 'classnames'
import pure from 'recompose/pure'
import {createStyleSheet} from 'jss-theme-reactor'
type DefaultProps = {
component: string
}
type Props = {
children: Element<any>,
component: string,
className: ?string,
//
fixed: boolean,
}
/*
':root': {
'--gutter-width': '1rem',
'--outer-margin': '2rem',
'--gutter-compensation': 'calc((var(--gutter-width) * 0.5) * -1)',
'--half-gutter-width': 'calc((var(--gutter-width) * 0.5))',
'--xs-min': '30',
'--sm-min': '48',
'--md-min': '64',
'--lg-min': '75',
'--screen-xs-min': 'var(--xs-min)em',
'--screen-sm-min': 'var(--sm-min)em',
'--screen-md-min': 'var(--md-min)em',
'--screen-lg-min': 'var(--lg-min)em',
'--container-sm': 'calc(var(--sm-min) + var(--gutter-width))',
'--container-md': 'calc(var(--md-min) + var(--gutter-width))',
'--container-lg': 'calc(var(--lg-min) + var(--gutter-width))'
},
*/
export const styleSheet = createStyleSheet('AfContainer', (theme) => {
const { breakpoints } = theme
return {
// NOTE: we currently use ZERO padding/gutters so that developer is in control of those.
container: {
marginRight: 'auto',
marginLeft: 'auto'
},
containerFixed: {
// paddingRight: '2rem)',
// paddingLeft: '2rem)'
},
[breakpoints.up('sm')]: {
// containerFixed: {
// width: 'var(--container-sm, 46rem)'
// }
},
[breakpoints.up('md')]: {
// containerFixed: {
// width: 'var(--container-md, 61rem)'
// }
},
[breakpoints.up('lg')]: {
// containerFixed: {
// width: 'var(--container-lg, 71rem)'
// }
}
}
})
/**
* This component is not necessary to be used with Row/Col. It is ported from the original code.
*/
class Container extends Component<DefaultProps, Props, void> {
static contextTypes = {
styleManager: PropTypes.object.isRequired
}
static defaultProps: DefaultProps = {
component: 'div',
fixed: false
}
props: Props
render (): Element<any> {
const classes = this.context.styleManager.render(styleSheet)
const { children, component, className: classNameProp, fixed, ...rest } = this.props
const className = classNames(classes.container, { [classes.containerFluid]: fixed }, classNameProp)
return React.createElement(component, { className, ...rest }, children)
}
}
export default pure(Container)
// @flow
import Container from './Container'
import Row from './Row'
import Col from './Col'
export {Container, Row, Col} // https://github.com/facebook/flow/issues/940
// @flow
import React, {Component, Element, PropTypes} from 'react'
import classNames from 'classnames'
import pure from 'recompose/pure'
import {createStyleSheet} from 'jss-theme-reactor'
import {capitalizeFirstLetter} from '../../util/strings'
import merge from 'lodash/merge'
import Logger from '../../util/Logger'
type DefaultProps = {
component: string
}
type Props = {
children: Element<any>,
component: string,
className: ?string,
//
reverse: ?boolean,
start: ?Breakpoints,
center: ?Breakpoints,
end: ?Breakpoints,
top: ?Breakpoints,
middle: ?Breakpoints,
bottom: ?Breakpoints,
around: ?Breakpoints,
between: ?Breakpoints,
first: ?Breakpoints,
last: ?Breakpoints
}
function generateClasses (breakpoint) {
const bp = capitalizeFirstLetter(breakpoint)
return {
[`row${bp}Start`]: {
justifyContent: 'flex-start',
textAlign: 'start'
},
[`row${bp}Center`]: {
justifyContent: 'center',
textAlign: 'center'
},
[`row${bp}End`]: {
justifyContent: 'flex-end',
textAlign: 'end'
},
[`row${bp}Top`]: {
alignItems: 'flex-start'
},
[`row${bp}Middle`]: {
alignItems: 'center'
},
[`row${bp}Bottom`]: {
alignItems: 'flex-end'
},
[`row${bp}Around`]: {
justifyContent: 'space-around'
},
[`row${bp}Between`]: {
justifyContent: 'space-between'
},
[`row${bp}First`]: {
order: '-1'
},
[`row${bp}Last`]: {
order: '1'
}
}
}
export const styleSheet = createStyleSheet('AfRow', (theme) => {
const { breakpoints } = theme
const classes = {
row: {
boxSizing: 'border-box',
display: 'flex',
flex: '0 1 auto',
flexDirection: 'row',
flexWrap: 'wrap'
// marginRight: 'var(--gutter-compensation, -0.5rem)',
// marginLeft: 'var(--gutter-compensation, -0.5rem)'
},
reverse: {
flexDirection: 'row-reverse'
}
}
const generated = generateClasses('xs')
generated[ breakpoints.up('sm') ] = generateClasses('xs')
generated[ breakpoints.up('md') ] = generateClasses('md')
generated[ breakpoints.up('lg') ] = generateClasses('lg')
return merge(generated, classes)
})
const MODIFIERS = [ 'start', 'center', 'end', 'top', 'middle', 'bottom', 'around', 'between', 'first', 'last' ]
class Row extends Component<DefaultProps, Props, void> {
static contextTypes = {
styleManager: PropTypes.object.isRequired
}
static defaultProps: DefaultProps = {
component: 'div'
}
props: Props
render (): Element<any> {
const classes = this.context.styleManager.render(styleSheet)
const {
children, component, className: classNameProp, reverse,
start, center, end, top, middle, bottom, around, between, first, last, // eslint-disable-line no-unused-vars
...rest
} = this.props
// accumulate all modifier classes
const modifierClasses = []
for (const key of MODIFIERS) {
const value = this.props[ key ]
let name
if (typeof value === 'boolean') {
// allow <Row center>, don't require <Row center='xs'>
name = `rowXs${capitalizeFirstLetter(key)}`
} else if (value) {
name = `row${capitalizeFirstLetter(value)}${capitalizeFirstLetter(key)}`
}
// log.debug('value needs defaulted?', !value, value, name)
// e.g. rowXsEnd
modifierClasses.push(classes[ name ])
}
const className = classNames(classes.row, ...modifierClasses, {
[classes.reverse]: reverse
}, classNameProp)
return React.createElement(component, { className, ...rest }, children)
}
}
const log = Logger.get(Row) // eslint-disable-line
export default pure(Row)
/* eslint-disable */
// make flow Breakpoints available by adding this to the .flowconfig i.e.
// [libs]
// ./src/flow
type Breakpoints = 'xs' | 'sm' | 'md' | 'lg'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment