├── .gitignore ├── .lvimrc ├── definition.js ├── media.js ├── .editorconfig ├── definition.test.js ├── .babelrc ├── media.test.js ├── rollup.config.js ├── examples ├── index.html ├── glamor.js ├── emotion.js └── aphrodite.js ├── index.js ├── package.json ├── index.test.js ├── README.md └── GUIDE.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .cache 4 | -------------------------------------------------------------------------------- /.lvimrc: -------------------------------------------------------------------------------- 1 | let g:ale_fixers = { 2 | \ 'javascript': ['prettier_standard'], 3 | \} 4 | 5 | let g:ale_linters = {'javascript': ['']} 6 | -------------------------------------------------------------------------------- /definition.js: -------------------------------------------------------------------------------- 1 | // Helper to render a definition given a value and a property name 2 | export default function definition (value, property) { 3 | return typeof value === 'object' ? value : { [property]: value } 4 | } 5 | -------------------------------------------------------------------------------- /media.js: -------------------------------------------------------------------------------- 1 | // Helper to write down a media query definition 2 | export default function media (value, feature = 'min-width', unit = 'em') { 3 | return `@media screen and (${feature}: ${value}${value > 0 ? unit : ''})` 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /definition.test.js: -------------------------------------------------------------------------------- 1 | import definition from './definition' 2 | 3 | test('Should be able to render a definition given a property and a value', () => { 4 | expect(definition('red', 'color')).toEqual({ color: 'red' }) 5 | }) 6 | 7 | test('Should be able to render a definition given a plain object as value', () => { 8 | const value = { 9 | fontSize: 16, 10 | letterSpacing: 0 11 | } 12 | 13 | expect(definition(value)).toEqual(value) 14 | }) 15 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "loose": true, 6 | "targets": { 7 | "browsers": ["last 1 version"] 8 | } 9 | }] 10 | ], 11 | "env": { 12 | "test": { 13 | "plugins": ["transform-es2015-modules-commonjs"] 14 | }, 15 | "development":{ 16 | "plugins": ["transform-react-jsx"] 17 | } 18 | }, 19 | "plugins": [ 20 | "transform-object-rest-spread" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /media.test.js: -------------------------------------------------------------------------------- 1 | import media from './media' 2 | 3 | test('Defaults should just work', () => { 4 | expect(media(42)).toEqual('@media screen and (min-width: 42em)') 5 | }) 6 | 7 | test('If value is zero, there should not be a unit', () => { 8 | expect(media(0)).toEqual('@media screen and (min-width: 0)') 9 | }) 10 | 11 | test('If unit is specified it should be respected', () => { 12 | expect(media('1024px')).toEqual('@media screen and (min-width: 1024px)') 13 | }) 14 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import pkg from './package.json' 3 | 4 | const name = 'responsiveStyles' 5 | 6 | const output = { 7 | umd: pkg.main, 8 | es: pkg.module 9 | } 10 | 11 | export default { 12 | input: 'index.js', 13 | output: [ 14 | { 15 | file: output.umd, 16 | format: 'umd', 17 | name 18 | }, 19 | { 20 | file: output.es, 21 | format: 'es' 22 | } 23 | ], 24 | plugins: [babel()] 25 | } 26 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import media from './media' 2 | import definition from './definition' 3 | 4 | export default function responsive (propertyOrValues, maybeValues, breaks) { 5 | const values = 6 | typeof propertyOrValues === 'string' ? maybeValues : propertyOrValues 7 | const initial = values[0] 8 | 9 | return values 10 | .slice(1) 11 | .map((value, index) => { 12 | return ( 13 | value !== null && { 14 | [media(breaks[index])]: definition(value, propertyOrValues) 15 | } 16 | ) 17 | }) 18 | .reduce( 19 | (accumulator, value) => ({ 20 | ...accumulator, 21 | ...value 22 | }), 23 | initial !== null ? definition(initial, propertyOrValues) : {} 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /examples/glamor.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { css } from 'glamor' 4 | import combine from 'combine-same-keys' 5 | import responsiveStyles from '../' 6 | 7 | const breaks = [48, 64, 80] 8 | const r = (props, values) => responsiveStyles(props, values, breaks) 9 | 10 | const root = css({ 11 | width: '100%', 12 | fontFamily: 'SF Mono, monospace' 13 | }) 14 | 15 | const fcss = css({ 16 | padding: 75, 17 | ':before': { 18 | ...r('content', ['"Small"', '"Medium"', '"Large"', '"Extra Large"']) 19 | } 20 | }) 21 | 22 | const withCombine = css({ 23 | ...combine( 24 | r('color', ['#FFF', '#005782', '#820005', '#16160B']), 25 | r('backgroundColor', ['#FF0066', '#27D88E', '#FFF5C3', '#E1E1E1']) 26 | ) 27 | }) 28 | 29 | const App = () => ( 30 |139 | Buy me a ☕ 140 |
141 | -------------------------------------------------------------------------------- /GUIDE.md: -------------------------------------------------------------------------------- 1 | [combine-same-keys]: https://github.com/rafaelrinaldi/combine-same-keys 2 | [fcss]: https://github.com/chibicode/react-functional-css-protips 3 | [styled-system]: https://github.com/jxnblk/styled-system 4 | 5 | # Guide 6 | > Detailed guide on how to get the most out of `responsive-styles` 7 | 8 | ## Defining breakpoints 9 | 10 | In order for the library to work you must always specify a list of breakpoints you want to support. This can become very repetitive, so our suggestion is that you simply create a function that composes on `responsive-styles` so you can reference it instead: 11 | 12 | ```js 13 | // your-project/src/responsive.js 14 | import responsiveStyles from 'responsive-styles' 15 | 16 | // A list with the breakpoints you wish to support 17 | const breaks = [48, 64, 80] 18 | 19 | // Compose a new function on top of `responsiveStyles` passing down your breakpoints 20 | const responsive = (props, values) => responsiveStyles(props, values, breaks) 21 | 22 | export default responsive 23 | ``` 24 | 25 | For the sake of brevity, moving forward in the next code snippets we will assume you have a `responsive.js` file living within your own project and that you chose Aphrodite. 26 | 27 | ### Mobile first 28 | 29 | One of the assumptions is that you approach design using a mobile first mindset, so the first item you pass to the list would be applied by default to whatever property you're defining values for. That's the reason why the first value on the breakpoints list should not be zero, since it would be redundant. 30 | 31 | ### Use `null` to bypass definitions 32 | 33 | You can use `null` to bypass definitions for specific breakpoints. Example: 34 | 35 | ```js 36 | import responsive './responsive' 37 | 38 | responsive('color', [null, 'green', 'blue']) 39 | 40 | // Outputs: 41 | // { 42 | // '@media screen and (min-width: 48em)': { color: 'green' }, 43 | // '@media screen and (min-width: 64em)': { color: 'blue' }, 44 | // } 45 | ``` 46 | 47 | ### Functional CSS 48 | 49 | `responsive-styles` plays really well with the concept of [Functional CSS][fcss], which favors creating one CSS class per property definition. 50 | 51 | Creating classes is cheap, gives us more control, separate visual concerns and can potentially improve reusability. 52 | 53 | Even though the Functional CSS approach is encouraged, the API is flexible enough to allow for all sorts of different use cases: 54 | 55 | ### What’s encouraged 56 | 57 | ```js 58 | // Every rule has its own class name 59 | import { StyleSheet } from 'aphrodite' 60 | import responsive from './responsive' 61 | 62 | const styles = StyleSheet.create({ 63 | color: responsive('color', ['red', 'green', 'blue']), 64 | opacity: responsive('opacity', [0, 0.5, 1]), 65 | width: responsive('width', ['25vw', '50vw', '100vw']) 66 | }) 67 | ``` 68 | 69 | ### What’s possible 70 | 71 | You can combine multiple rules within the same media break by utilizing the [`combine-same-keys`][combine-same-keys] library: 72 | 73 | ```js 74 | import { StyleSheet } from 'aphrodite' 75 | import combineSameKeys from 'combine-same-keys' 76 | import responsive from './responsive' 77 | 78 | const styles = StyleSheet.create({ 79 | root: combineSameKeys( 80 | responsive('color', ['red', 'green', 'blue']), 81 | responsive('opacity', [0, 0.5, 1]), 82 | responsive('width', ['25vw', '50vw', '100vw']) 83 | ) 84 | }) 85 | ``` 86 | 87 | #### “It’s just JavaScript” 88 | 89 | We can benefit from having things isolated in small functions by exploring different possibilities when composing styles. For instance, you can use partial application for reducing boilerplate and making things look more concise: 90 | 91 | ```js 92 | import { StyleSheet } from 'aphrodite' 93 | import responsive from './responsive' 94 | 95 | const color = values => responsive('color', values) 96 | const opacity = values => responsive('opacity', values) 97 | 98 | const styles = StyleSheet.create({ 99 | color: color(['red', 'green', 'blue']), 100 | opacity: opacity([0, 0.5, 1]) 101 | }) 102 | ``` 103 | 104 | ### Passing down plain objects 105 | 106 | It's encouraged that you split up your styling into classes instead of a group of rules – in a more functional fashion – but you can also pass down plain objects for each breakpoint, which can be useful sometimes (specially for intereoperability with other libraries): 107 | 108 | ```js 109 | import { StyleSheet } from 'aphrodite' 110 | import responsive from './responsive' 111 | 112 | const textVariations = [ 113 | { fontSize: 16, letterSpacing: 0 }, 114 | { fontSize: 18, letterSpacing: 0.5 }, 115 | { fontSize: 22, letterSpacing: 1, fontWeight: 'bold' } 116 | ] 117 | 118 | const styles = StyleSheet.create({ 119 | root: { 120 | color: '#FFF', 121 | backgroundColor: '#FF0066', 122 | ...responsive(textVariations) 123 | } 124 | }) 125 | ``` 126 | 127 | ## Related projects 128 | 129 | - [styled-system][styled-system] 130 | --------------------------------------------------------------------------------