├── .babelrc
├── .editorconfig
├── .gitignore
├── .lvimrc
├── GUIDE.md
├── README.md
├── definition.js
├── definition.test.js
├── examples
├── aphrodite.js
├── emotion.js
├── glamor.js
└── index.html
├── index.js
├── index.test.js
├── media.js
├── media.test.js
├── package-lock.json
├── package.json
└── rollup.config.js
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [aphrodite]: https://github.com/Khan/aphrodite
2 | [brent]: http://jxnblk.com
3 | [combine-same-keys]: https://github.com/rafaelrinaldi/combine-same-keys
4 | [emotion]: https://github.com/emotion-js/emotion
5 | [glamor]: https://github.com/threepointone/glamor
6 | [react]: http://reactjs.org
7 | [rebass]: http://jxnblk.com/rebass
8 | [url]: https://rinaldi.io
9 |
10 | # Responsive Styles [](https://semaphoreci.com/rafaelrinaldi/responsive-styles)
11 | > Use arrays as values to specify mobile-first responsive styles for CSS-in-JS projects
12 |
13 | The main idea of this library is to provide a framework agnostic way to easily enable any property to become responsive.
14 |
15 | This is **100%** inspired by the awesome work done by [Brent Jackson][brent] on [Rebass][rebass].
16 |
17 | ## Install
18 |
19 | ```sh
20 | npm i responsive-styles
21 | ```
22 |
23 | ## Usage
24 |
25 | This library was tested against [Aphrodite][aphrodite], [glamor][glamor] and [emotion][emotion] so far and they all seem to work nicely.
26 |
27 | Aphrodite and React
29 |
30 | ```js
31 | import React from 'react'
32 | import { render } from 'react-dom'
33 | import { StyleSheet, css } from 'aphrodite/no-important'
34 | import combineSameKeys from 'combine-same-keys'
35 | import responsiveStyles from 'responsive-styles'
36 |
37 | const breaks = [48, 64, 80]
38 | const r = (props, values) => responsiveStyles(props, values, breaks)
39 |
40 | const styles = StyleSheet.create({
41 | // A la functional CSS
42 | padding: r('padding', [8, 24, 48]),
43 | fontSize: r('fontSize', [16, 24, 36]),
44 |
45 | // Combine multiple definitions into a single class name
46 | colors: combineSameKeys(
47 | r('color', ['#FFF', '#005782', '#820005', '#16160B']),
48 | r('backgroundColor', ['#FF0066', '#27D88E', '#FFF5C3', '#E1E1E1'])
49 | )
50 | })
51 |
52 | const className = css(styles.padding, styles.fontSize, styles.colors)
53 |
54 | const App = () => Aphrodite
glamor and React
62 |
63 | ```js
64 | import React from 'react'
65 | import { render } from 'react-dom'
66 | import { css } from 'glamor' // The API is exactly the same for emotion
67 | import combineSameKeys from 'combine-same-keys'
68 | import responsiveStyles from 'responsive-styles'
69 |
70 | const breaks = [48, 64, 80]
71 | const r = (props, values) => responsiveStyles(props, values, breaks)
72 |
73 | // A la functional CSS
74 | const padding = css({
75 | ...r('padding', [8, 24, 48]),
76 | })
77 |
78 | const fontSize = css({
79 | ...r('fontSize', [16, 24, 36]),
80 | })
81 |
82 | // Combine multiple definitions into a single class name
83 | const colors = css(
84 | combineSameKeys(
85 | r('color', ['#FFF', '#005782', '#820005', '#16160B']),
86 | r('backgroundColor', ['#FF0066', '#27D88E', '#FFF5C3', '#E1E1E1'])
87 | )
88 | )
89 |
90 | const className = `${padding} ${fontSize} ${colors}`
91 |
92 | const App = () => Glamor and Emotion
139 | Buy me a ☕ 140 |
141 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/aphrodite.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { StyleSheet, css } from 'aphrodite/no-important' 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 styles = StyleSheet.create({ 11 | root: { 12 | width: '100%', 13 | fontFamily: 'SF Mono, monospace' 14 | }, 15 | 16 | fcss: { 17 | padding: 75, 18 | ':before': { 19 | ...r('content', ['"Small"', '"Medium"', '"Large"', '"Extra Large"']) 20 | } 21 | }, 22 | 23 | withCombine: { 24 | ...combine( 25 | r('color', ['#FFF', '#005782', '#820005', '#16160B']), 26 | r('backgroundColor', ['#FF0066', '#27D88E', '#FFF5C3', '#E1E1E1']) 27 | ) 28 | } 29 | }) 30 | 31 | const App = () => ( 32 |