├── .github
└── LICENSE.md
├── .gitignore
├── .npmignore
├── .travis.yml
├── README.md
├── __snapshots__
└── test.js.snap
├── docs
├── App.js
├── Banner.js
├── Border.js
├── Btn.js
├── Button.js
├── Card.js
├── Fill.js
├── Nav.js
├── Pre.js
├── Text.js
├── bundle.js
├── card.png
├── colors.js
├── entry.js
├── examples
│ ├── Align.jsx
│ ├── Column.jsx
│ ├── Directions.jsx
│ ├── FlexAuto.jsx
│ ├── Grid.jsx
│ ├── Hero.jsx
│ ├── MarginAuto.jsx
│ ├── Offset.jsx
│ ├── Padding.jsx
│ ├── Wrap.jsx
│ └── index.js
├── index.html
└── updaters.js
├── package.json
├── src
├── Box.js
├── Flex.js
├── ReflexProvider.js
├── config.js
├── context-types.js
├── css.js
├── index.js
├── reflex.js
└── sheet.js
├── test.js
└── webpack.config.js
/.github/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | # The MIT License (MIT)
3 | Copyright (c) 2016 Brent Jackson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | dist/*
4 | docs/dev.js
5 | package-lock.json
6 | yarn.lock
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxnblk/reflexbox/76c8f1bb1a15a5c35f0c16855273d5c2b67de0e3/.npmignore
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 8.0
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Reflexbox
3 |
4 | Responsive React Flexbox Grid System
5 |
6 | http://jxnblk.com/reflexbox
7 |
8 | [](https://travis-ci.org/jxnblk/reflexbox)
9 | [](https://badge.fury.io/js/reflexbox)
10 |
11 |
12 | ## Features
13 |
14 | - Simple API for quickly controlling layout
15 | - Helps promote composability and separation of concerns
16 | - CSS-in-JS built in - no external dependencies
17 | - Only generates the CSS needed to render
18 |
19 | ## Getting Started
20 |
21 | ```
22 | npm install reflexbox
23 | ```
24 |
25 | ```jsx
26 | import React from 'react'
27 | import { Flex, Box } from 'reflexbox'
28 |
29 | class Component extends React.Component {
30 | render() {
31 | return (
32 |
33 | Box A
34 | Box B
35 |
36 | )
37 | }
38 | }
39 | ```
40 |
41 | ## Usage
42 |
43 | ```jsx
44 | // Fractional width
45 |
46 |
47 | // Pixel width
48 |
49 |
50 | // Responsive widths
51 |
52 |
53 | // Padding
54 |
55 |
56 | // Responsive padding
57 |
58 |
59 | // Margin
60 |
61 |
62 | // Responsive margin
63 |
64 |
65 | // top, right, bottom, left
66 |
72 |
73 | // x-axis
74 |
75 |
76 | // y-axis
77 |
78 |
79 | // align-items: center
80 |
81 |
82 | // justify-content: space-between
83 |
84 |
85 | // Flex wrap
86 |
87 |
88 | // Flex direction column
89 |
90 |
91 | // Order
92 |
93 |
94 | // flex: 1 1 auto
95 |
96 | ```
97 |
98 | ## API
99 |
100 | ### ``
101 |
102 | Component primitive with `display: flex`
103 |
104 | ### ``
105 |
106 | Primitive for controlling width, margin, padding and more.
107 |
108 | ### Props
109 |
110 | Both `` and `` share the same props.
111 |
112 | - `w` (number|string) sets width, where numbers 0-1 are percentage values, larger numbers are pixel values, and strings are raw CSS values with units.
113 | - `flex` (boolean) sets `display: flex`
114 | - `wrap` (boolean) sets `flex-wrap: wrap`
115 | - `column` (boolean) sets `flex-direction: column`
116 | - `auto` (boolean) sets `flex: 1 1 auto`
117 | - `order` (number) sets `order`
118 | - `align` (string) sets `align-items`
119 | - `justify` (string) sets `justify-content`
120 |
121 | #### Margin and Padding
122 |
123 | Margin and padding props accept numbers `0-4` for values from the spacing scale `[ 0, 8, 16, 32, 64 ]`.
124 | Numbers greater than 4 will be used as pixel values.
125 | Negative values can be used for negative margins.
126 | Strings can be passed for other CSS values, e.g. `mx='auto'`
127 |
128 | - `m` (number|string) margin based on a scale from 0–4.
129 | - `mx` (number|string) x-axis margin based on a scale from 0–4.
130 | - `my` (number|string) y-axis margin based on a scale from 0–4.
131 | - `mt` (number|string) margin-top based on a scale from 0–4.
132 | - `mb` (number|string) margin-bottom based on a scale from 0–4.
133 | - `ml` (number|string) margin-left based on a scale from 0–4.
134 | - `mr` (number|string) margin-right based on a scale from 0–4.
135 | - `p` (number|string) padding based on a scale from 0–4.
136 | - `px` (number|string) x-axis padding based on a scale from 0–4.
137 | - `py` (number|string) y-axis padding based on a scale from 0–4.
138 | - `pt` (number|string) padding-top based on a scale from 0–4.
139 | - `pb` (number|string) padding-bottom based on a scale from 0–4.
140 | - `pl` (number|string) padding-left based on a scale from 0–4.
141 | - `pr` (number|string) padding-right based on a scale from 0–4.
142 |
143 | ### Responsive Array Prop Values
144 |
145 | All props accept arrays as values for mobile-first responsive styles.
146 |
147 | ```jsx
148 | // Set widths for different breakpoints, starting from smallest to largest
149 | // This example will be 100% width below the smallest breakpoint,
150 | // then 50% and 25% for the next two breakpoints respectively
151 |
152 | ```
153 |
154 | Null values can be passed to the array to skip a breakpoint.
155 |
156 | ```jsx
157 |
158 | ```
159 |
160 |
161 | ## Configuration
162 |
163 | Values for the breakpoints and space scale can be configured with
164 | [React Context](https://facebook.github.io/react/docs/context.html).
165 |
166 | Context can be set manually or with the `` component.
167 |
168 |
169 | ```jsx
170 | import React from 'react'
171 | import { ReflexProvider, Flex, Box } from 'reflexbox'
172 |
173 | const space = [ 0, 6, 12, 18, 24 ]
174 | const breakpoints = [ 32, 48, 64 ]
175 |
176 | class App extends React.Component {
177 | render () {
178 | return (
179 |
182 |
183 | Box
184 | Box
185 | Box
186 | Box
187 |
188 |
189 | )
190 | }
191 | }
192 | ```
193 |
194 | ## Higher Order Component
195 |
196 | The core Reflexbox higher-order component can be used on any element that accepts `className` as a prop.
197 |
198 | ```jsx
199 | import React from 'react'
200 | import { reflex } from 'reflexbox'
201 | import MyInput from './MyInput'
202 |
203 | const FlexInput = reflex(MyInput)
204 |
205 | const App = () => (
206 |
207 |
212 |
213 | )
214 | ```
215 |
216 | ### Caveats
217 |
218 | This currently *DOES NOT* work in Node.js server-side applications.
219 | If you need server-side support, see version `^2.2.0` or one of the related libraries below.
220 |
221 | ---
222 |
223 | ### Related
224 |
225 | - [Axs](http://jxnblk.com/axs)
226 | - [Grid Styled](http://jxnblk.com/grid-styled)
227 | - [Gx](http://jxnblk.com/gx)
228 | - [Rebass](http://jxnblk.com/rebass)
229 | - [Reline](http://jxnblk.com/reline)
230 |
231 | [MIT License](.github/LICENSE.md)
232 |
233 |
--------------------------------------------------------------------------------
/__snapshots__/test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`react context 1`] = `
4 |
7 | `;
8 |
9 | exports[`snapshot 1`] = `
10 |
13 | `;
14 |
15 | exports[`snapshot 2`] = `
16 |
19 | `;
20 |
--------------------------------------------------------------------------------
/docs/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createProvider } from 'funcup'
3 | import Nav from './Nav'
4 | import Banner from './Banner'
5 | import examples from './examples'
6 | import colors from './colors'
7 |
8 | const App = props => {
9 | const example = examples[Math.abs(props.index) % examples.length]
10 | const color = colors[Math.abs(props.index) % colors.length]
11 |
12 | return (
13 |
14 |
15 |
19 |
20 | )
21 | }
22 |
23 | const initialState = {
24 | index: 0,
25 | xray: false,
26 | }
27 |
28 | export default createProvider(initialState)(App)
29 |
--------------------------------------------------------------------------------
/docs/Banner.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'funcup'
3 | import { Flex, Box } from 'reflexbox'
4 | import XRay from 'react-x-ray'
5 | import {
6 | LiveProvider,
7 | LiveEditor,
8 | LiveError,
9 | LivePreview
10 | } from 'react-live'
11 | import { dark } from './colors'
12 | import Fill from './Fill'
13 | import Border from './Border'
14 | import Btn from './Btn'
15 | import Text from './Text'
16 | import Pre from './Pre'
17 | import { setCode } from './updaters'
18 |
19 | class ExampleBanner extends React.Component {
20 | update = code => {
21 | this.props.update(setCode(code))
22 | }
23 |
24 | render () {
25 | const {
26 | example,
27 | color,
28 | xray
29 | } = this.props
30 |
31 | const scope = {
32 | Flex,
33 | Box,
34 | Border,
35 | Fill,
36 | Btn,
37 | Text,
38 | Pre,
39 | color,
40 | }
41 |
42 | const textColor = dark(color) ? '#fff' : '#000'
43 |
44 | const sx = {
45 | root: {
46 | position: 'relative',
47 | },
48 | inner: {
49 | minHeight: '100vh'
50 | },
51 | top: {
52 | position: 'relative',
53 | height: '80vh',
54 | fontWeight: 'bold',
55 | color: textColor,
56 | backgroundColor: color,
57 | transitionProperty: 'background-color',
58 | transitionDuration: '.5s',
59 | transitionTimingFunction: 'ease-out',
60 | overflow: 'hidden'
61 | },
62 | bottom: {
63 | height: '50vh'
64 | },
65 | preview: {
66 | display: 'flex',
67 | alignItems: 'center',
68 | height: '80vh',
69 | paddingTop: 64,
70 | paddingBottom: 64,
71 | minHeight: 512,
72 | overflow: 'auto'
73 | },
74 | editor: {
75 | fontFamily: 'SF Mono, Menlo, monospace',
76 | fontSize: 12,
77 | padding: 16,
78 | height: '100%',
79 | overflow: 'auto',
80 | margin: 0,
81 | color: color,
82 | backgroundColor: '#000',
83 | outline: 'none'
84 | },
85 | error: {
86 | position: 'absolute',
87 | zIndex: 2,
88 | top: '50%',
89 | right: 0,
90 | left: 0,
91 | transform: 'TranslateY(-50%)',
92 | fontFamily: 'SF Mono, Menlo, monospace',
93 | fontSize: 12,
94 | margin: 0,
95 | padding: 16,
96 | color: '#fff',
97 | backgroundColor: '#f00'
98 | },
99 | credits: {
100 | position: 'absolute',
101 | right: 0,
102 | bottom: 0,
103 | marginRight: 16
104 | }
105 | }
106 |
107 | return (
108 |
109 |
113 |
114 |
115 |
116 |
120 |
123 |
124 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | )
137 | }
138 | }
139 |
140 | export default connect()(ExampleBanner)
141 |
--------------------------------------------------------------------------------
/docs/Border.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Box } from 'reflexbox'
3 |
4 | const Border = props => (
5 |
12 | )
13 |
14 | export default Border
15 |
--------------------------------------------------------------------------------
/docs/Btn.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Btn = props => (
4 |
22 | )
23 |
24 | export default Btn
25 |
--------------------------------------------------------------------------------
/docs/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Button = props => (
4 |
26 | )
27 |
28 | export default Button
29 |
--------------------------------------------------------------------------------
/docs/Card.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // const color = '#0eb'
4 | const color = '#07c'
5 |
6 | const Card = () => (
7 |
24 |
32 |
38 | Reflex
39 |
40 |
41 | Box
42 |
43 |
44 |
45 | )
46 |
47 | export default Card
48 |
--------------------------------------------------------------------------------
/docs/Fill.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Box } from 'reflexbox'
3 | import { dark } from './colors'
4 |
5 | const Fill = props => (
6 |
17 | )
18 |
19 | export default Fill
20 |
--------------------------------------------------------------------------------
/docs/Nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'funcup'
3 | import { Flex, Box } from 'reflexbox'
4 | import { Arrow } from 'reline'
5 | import {
6 | inc,
7 | dec,
8 | toggleXRay,
9 | cycleColor
10 | } from './updaters'
11 | import Button from './Button'
12 | import Btn from './Btn'
13 |
14 | const Nav = props => {
15 | const sx = {
16 | root: {
17 | position: 'fixed',
18 | zIndex: 2,
19 | top: 0,
20 | right: 0,
21 | left: 0,
22 | color: '#fff',
23 | backgroundColor: 'rgba(0, 0, 0, .75)',
24 | mixBlendMode: 'multiply'
25 | }
26 | }
27 |
28 | return (
29 |
52 | )
53 | }
54 |
55 | export default connect()(Nav)
56 |
--------------------------------------------------------------------------------
/docs/Pre.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Pre = props => (
4 |
13 | )
14 |
15 | export default Pre
16 |
--------------------------------------------------------------------------------
/docs/Text.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Text = props => (
4 | React.createElement(props.is,
5 | Object.assign({}, props, {
6 | style: {
7 | margin: 0,
8 | fontSize: scale[props.f]
9 | }
10 | })
11 | )
12 | )
13 |
14 | Text.defaultProps = {
15 | is: 'p',
16 | f: 4
17 | }
18 |
19 | const scale = [
20 | 48,
21 | 32,
22 | 24,
23 | 20,
24 | 16,
25 | 14,
26 | 12
27 | ]
28 |
29 | export default Text
30 |
--------------------------------------------------------------------------------
/docs/card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxnblk/reflexbox/76c8f1bb1a15a5c35f0c16855273d5c2b67de0e3/docs/card.png
--------------------------------------------------------------------------------
/docs/colors.js:
--------------------------------------------------------------------------------
1 | import chroma from 'chroma-js'
2 |
3 | export const dark = n => chroma(n).luminance() < 0.5
4 |
5 | const colors = [
6 | '#07c',
7 | '#50c',
8 | '#b0c',
9 | '#0eb',
10 | '#eb0',
11 | '#e07',
12 | ]
13 |
14 | export default colors
15 |
--------------------------------------------------------------------------------
/docs/entry.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import App from './App'
4 |
5 | console.log('hello')
6 | render(, app)
7 |
--------------------------------------------------------------------------------
/docs/examples/Align.jsx:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | Align
12 |
13 |
14 |
15 | Center
16 |
17 |
18 |
--------------------------------------------------------------------------------
/docs/examples/Column.jsx:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | Flex Direction
11 |
12 |
13 |
14 | Column
15 |
16 |
17 |
--------------------------------------------------------------------------------
/docs/examples/Directions.jsx:
--------------------------------------------------------------------------------
1 |
2 | pt
3 | pr
4 | pb
5 | pl
6 | px
7 | py
8 |
9 |
--------------------------------------------------------------------------------
/docs/examples/FlexAuto.jsx:
--------------------------------------------------------------------------------
1 |
2 | Auto
3 | Auto
4 | Auto
5 |
6 |
--------------------------------------------------------------------------------
/docs/examples/Grid.jsx:
--------------------------------------------------------------------------------
1 |
2 | 1/2
3 | 1/2
4 |
5 | 1/3
6 | 1/3
7 | 1/3
8 |
9 | 1/4
10 | 1/4
11 | 1/4
12 | 1/4
13 |
14 | 1/5
15 | 1/5
16 | 1/5
17 | 1/5
18 | 1/5
19 |
20 | 1/6
21 | 1/6
22 | 1/6
23 | 1/6
24 | 1/6
25 | 1/6
26 |
27 |
--------------------------------------------------------------------------------
/docs/examples/Hero.jsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
13 | Reflex
14 |
15 |
16 | Box
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/docs/examples/MarginAuto.jsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | Margin
4 |
5 |
6 | Auto
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/examples/Offset.jsx:
--------------------------------------------------------------------------------
1 |
6 | 1/4
7 | 1/4
8 | 1/4
9 | 1/4
10 |
11 | Offset 1/4
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/examples/Padding.jsx:
--------------------------------------------------------------------------------
1 |
6 |
7 | p4
8 |
9 |
10 | p3
11 |
12 |
13 | p2
14 |
15 |
16 | p1
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/examples/Wrap.jsx:
--------------------------------------------------------------------------------
1 |
9 |
10 | Wrap
11 |
12 |
13 | Wrap
14 |
15 |
16 | Wrap
17 |
18 |
19 | Wrap
20 |
21 |
22 | Wrap
23 |
24 |
25 | Wrap
26 |
27 |
28 |
--------------------------------------------------------------------------------
/docs/examples/index.js:
--------------------------------------------------------------------------------
1 | import Hero from './Hero.jsx'
2 | import FlexAuto from './FlexAuto.jsx'
3 | import Padding from './Padding.jsx'
4 | import Align from './Align.jsx'
5 | import MarginAuto from './MarginAuto.jsx'
6 | import Directions from './Directions.jsx'
7 | import Wrap from './Wrap.jsx'
8 | import Column from './Column.jsx'
9 | import Grid from './Grid.jsx'
10 | import Offset from './Offset.jsx'
11 |
12 | export default [
13 | Hero,
14 | Grid,
15 | FlexAuto,
16 | Padding,
17 | Align,
18 | MarginAuto,
19 | Directions,
20 | Wrap,
21 | Column,
22 | Offset,
23 | ]
24 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Reflexbox
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/docs/updaters.js:
--------------------------------------------------------------------------------
1 | import colors from './colors'
2 | import examples from './examples'
3 |
4 | export const setCode = code => state => ({ code })
5 | export const setColor = color => state => ({ color })
6 | export const setIndex = index => state => ({ index })
7 | export const dec = state => ({ index: state.index - 1 })
8 | export const inc = state => ({ index: state.index + 1 })
9 |
10 | export const cycleColor = state => {
11 | const i = (colors.indexOf(state.color) + 1) % colors.length
12 | return { color: colors[i] }
13 | }
14 |
15 | export const toggleXRay = state => ({ xray: !state.xray })
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reflexbox",
3 | "version": "3.0.1",
4 | "description": "Responsive React Flexbox Grid System",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "build": "NODE_ENV=production webpack -p",
8 | "start": "webpack-dev-server",
9 | "prepublish": "mkdir -p dist && babel src --out-dir dist",
10 | "test": "ava",
11 | "card": "repng docs/Card.js --dev"
12 | },
13 | "author": "Brent Jackson",
14 | "license": "MIT",
15 | "devDependencies": {
16 | "ava": "^0.19.1",
17 | "babel-cli": "^6.24.1",
18 | "babel-core": "^6.24.1",
19 | "babel-eslint": "^6.1.2",
20 | "babel-loader": "^6.2.3",
21 | "babel-preset-env": "^1.5.1",
22 | "babel-preset-react": "^6.24.1",
23 | "babel-preset-stage-0": "^6.24.1",
24 | "babel-register": "^6.24.1",
25 | "browser-env": "^3.2.4",
26 | "chroma-js": "^1.3.4",
27 | "funcup": "^1.0.0-0",
28 | "raw-loader": "^0.5.1",
29 | "react": "^15.5.4",
30 | "react-dom": "^15.5.4",
31 | "react-live": "^1.5.3",
32 | "react-test-renderer": "^15.5.4",
33 | "react-x-ray": "^1.0.0-1",
34 | "reline": "^1.0.0-beta.3",
35 | "repng": "^2.0.0-alpha.1",
36 | "webpack": "^2.6.1",
37 | "webpack-dev-server": "^2.4.5"
38 | },
39 | "repository": {
40 | "type": "git",
41 | "url": "git+https://github.com/jxnblk/reflexbox.git"
42 | },
43 | "keywords": [
44 | "react",
45 | "react-component",
46 | "flexbox",
47 | "layout",
48 | "grid",
49 | "css-in-js"
50 | ],
51 | "bugs": {
52 | "url": "https://github.com/jxnblk/reflexbox/issues"
53 | },
54 | "babel": {
55 | "presets": [
56 | "env",
57 | "stage-0",
58 | "react"
59 | ]
60 | },
61 | "ava": {
62 | "require": [
63 | "babel-register"
64 | ],
65 | "babel": "inherit"
66 | },
67 | "homepage": "https://github.com/jxnblk/reflexbox#readme",
68 | "dependencies": {
69 | "prop-types": "^15.5.10"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Box.js:
--------------------------------------------------------------------------------
1 | import reflex from './reflex'
2 |
3 | const Box = reflex('div')
4 |
5 | export default Box
6 |
--------------------------------------------------------------------------------
/src/Flex.js:
--------------------------------------------------------------------------------
1 | import { createElement } from 'react'
2 | import Box from './Box'
3 |
4 | const Flex = props => (
5 | createElement(Box, { ...props, flex: true })
6 | )
7 |
8 | export default Flex
9 |
--------------------------------------------------------------------------------
/src/ReflexProvider.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import contextTypes from './context-types'
3 |
4 | class ReflexProvider extends React.Component {
5 | getChildContext () {
6 | return {
7 | reflexbox: this.props
8 | }
9 | }
10 |
11 | render () {
12 | return React.Children.only(this.props.children)
13 | }
14 | }
15 |
16 | ReflexProvider.childContextTypes = contextTypes
17 |
18 | export default ReflexProvider
19 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export const breakpoints = [
2 | 40,
3 | 52,
4 | 64
5 | ]
6 |
7 | export const space = [
8 | 0, 8, 16, 32, 64
9 | ]
10 |
11 | export default {
12 | breakpoints,
13 | space
14 | }
15 |
--------------------------------------------------------------------------------
/src/context-types.js:
--------------------------------------------------------------------------------
1 | import { shape, arrayOf, number } from 'prop-types'
2 |
3 | const contextTypes = {
4 | reflexbox: shape({
5 | breakpoints: arrayOf(number),
6 | space: arrayOf(number)
7 | })
8 | }
9 |
10 | export default contextTypes
11 |
--------------------------------------------------------------------------------
/src/css.js:
--------------------------------------------------------------------------------
1 | import sheet from './sheet'
2 |
3 | const REG = /^([wmp][trblxy]?|flex|wrap|column|auto|align|justify|order)$/
4 | const cache = {}
5 |
6 | const css = config => props => {
7 | const next = {}
8 | const classNames = []
9 |
10 | const breaks = [ null, ...config.breakpoints ]
11 | const sx = stylers(config)
12 |
13 | for (let key in props) {
14 | const val = props[key]
15 | if (!REG.test(key)) {
16 | next[key] = val
17 | continue
18 | }
19 | const cx = createRule(breaks, sx)(key, val)
20 | cx.forEach(cn => classNames.push(cn))
21 | }
22 |
23 | next.className = join(next.className, ...classNames)
24 |
25 | return next
26 | }
27 |
28 | css.reset = () => {
29 | Object.keys(cache).forEach(key => {
30 | delete cache[key]
31 | })
32 | while (sheet.cssRules.length) {
33 | sheet.deleteRule(0)
34 | }
35 | }
36 |
37 | const createRule = (breaks, sx) => (key, val) => {
38 | const classNames = []
39 | const id = '_Rfx' + sheet.cssRules.length.toString(36)
40 | const k = key.charAt(0)
41 | const style = sx[key] || sx[k]
42 |
43 | const rules = toArr(val).map((v, i) => {
44 | const bp = breaks[i]
45 | const decs = style(key, v)
46 | const cn = id + '_' + (bp || '')
47 | const body = `.${cn}{${decs}}`
48 | const rule = media(bp, body)
49 |
50 | const _key = decs + (bp || '')
51 |
52 | if (cache[_key]) {
53 | classNames.push(cache[_key])
54 | return null
55 | } else {
56 | classNames.push(cn)
57 | cache[_key] = cn
58 | return rule
59 | }
60 | }).filter(r => r !== null)
61 |
62 | sheet.insert(rules)
63 |
64 | return classNames
65 | }
66 |
67 | const toArr = n => Array.isArray(n) ? n : [ n ]
68 | const num = n => typeof n === 'number' && !isNaN(n)
69 |
70 | const join = (...args) => args
71 | .filter(a => !!a)
72 | .join(' ')
73 |
74 | const dec = args => args.join(':')
75 | const rule = args => args.join(';')
76 | const media = (bp, body) => bp ? `@media screen and (min-width:${bp}em){${body}}` : body
77 |
78 | const width = (key, n) => dec([ 'width', !num(n) || n > 1 ? px(n) : (n * 100) + '%' ])
79 | const px = n => num(n) ? n + 'px' : n
80 |
81 | const space = scale => (key, n) => {
82 | const [ a, b ] = key.split('')
83 | const prop = a === 'm' ? 'margin' : 'padding'
84 | const dirs = directions[b] || ['']
85 | const neg = n < 0 ? -1 : 1
86 | const val = !num(n) ? n : px((scale[Math.abs(n)] || Math.abs(n)) * neg)
87 | return rule(dirs.map(d => dec([ prop + d, val ])))
88 | }
89 |
90 | const directions = {
91 | t: [ '-top' ],
92 | r: [ '-right' ],
93 | b: [ '-bottom' ],
94 | l: [ '-left' ],
95 | x: [ '-left', '-right' ],
96 | y: [ '-top', '-bottom' ],
97 | }
98 |
99 | const flex = (key, n) => dec([ 'display', n ? 'flex' : 'block' ])
100 | const wrap = (key, n) => dec([ 'flex-wrap', n ? 'wrap' : 'nowrap' ])
101 | const auto = (key, n) => dec([ 'flex', '1 1 auto' ])
102 | const column = (key, n) => dec([ 'flex-direction', n ? 'column' : 'row' ])
103 | const align = (key, n) => dec([ 'align-items', n ])
104 | const justify = (key, n) => dec([ 'justify-content', n ])
105 | const order = (key, n) => dec([ 'order', n ])
106 |
107 | const stylers = config => ({
108 | w: width,
109 | m: space(config.space),
110 | p: space(config.space),
111 | flex,
112 | wrap,
113 | auto,
114 | column,
115 | align,
116 | justify,
117 | order
118 | })
119 |
120 | export default css
121 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as config } from './config'
2 | export { default as reflex } from './reflex'
3 | export { default as sheet } from './sheet'
4 | export { default as css } from './css'
5 | export { default as Flex } from './Flex'
6 | export { default as Box } from './Box'
7 | export { default as ReflexProvider } from './ReflexProvider'
8 |
--------------------------------------------------------------------------------
/src/reflex.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import css from './css'
3 | import defaultConfig from './config'
4 | import contextTypes from './context-types'
5 |
6 | const reflex = Component => {
7 | const Reflex = (props, context) => {
8 | const config = Object.assign({}, defaultConfig, context.reflexbox)
9 | const next = css(config)(props)
10 |
11 | return React.createElement(Component, next)
12 | }
13 |
14 | Reflex.contextTypes = contextTypes
15 |
16 | return Reflex
17 | }
18 |
19 | export default reflex
20 |
--------------------------------------------------------------------------------
/src/sheet.js:
--------------------------------------------------------------------------------
1 | // todo: make node version
2 | const style = document.createElement('style')
3 | style.id = 'reflexbox'
4 | style.type = 'text/css'
5 | document.head.appendChild(style)
6 |
7 | const sheet = style.sheet
8 |
9 | sheet.insert = css => css.map(rule => {
10 | const l = sheet.cssRules.length
11 | sheet.insertRule(rule, l)
12 | })
13 |
14 | export default sheet
15 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | require('browser-env')()
2 | const test = require('ava')
3 | const React = require('react')
4 | const render = require('react-test-renderer').create
5 | const {
6 | sheet,
7 | css,
8 | config,
9 | ReflexProvider,
10 | Box,
11 | Flex
12 | } = require('./src')
13 |
14 | const props = {
15 | children: 'Hello',
16 | title: 'Boop',
17 | w: [ 1, 1/2, 1/4 ],
18 | m: 2,
19 | flex: true,
20 | px: 3
21 | }
22 |
23 | const cx = css(config)
24 |
25 | test.afterEach(t => {
26 | css.reset()
27 | })
28 |
29 | test('css returns props', t => {
30 | const a = cx(props)
31 | t.is(typeof a, 'object')
32 | })
33 |
34 | test('css passes props through', t => {
35 | const a = cx(props)
36 | t.is(a.children, 'Hello')
37 | t.is(a.title, 'Boop')
38 | })
39 |
40 | test('css strips style props', t => {
41 | const a = cx(props)
42 | t.is(a.w, undefined)
43 | t.is(a.m, undefined)
44 | t.is(a.flex, undefined)
45 | t.is(a.px, undefined)
46 | })
47 |
48 | test('css adds a className prop', t => {
49 | const a = cx(props)
50 | t.is(typeof a.className, 'string')
51 | })
52 |
53 | test('css inserts styles to sheet', t => {
54 | const a = cx({ m: 2 })
55 | t.is(sheet.cssRules.length, 1)
56 | const [ rule ] = sheet.cssRules
57 | t.is(rule.style.margin, '16px')
58 | })
59 |
60 | test('css inserts responsive styles', t => {
61 | const a = css(config)({ m: [ 1, 2 ] })
62 | t.is(sheet.cssRules.length, 2)
63 | const [ baseRule, mobile ] = sheet.cssRules
64 | const [ mobileRule ] = mobile.cssRules
65 | t.is(baseRule.style.margin, '8px')
66 | t.is(mobileRule.style.margin, '16px')
67 | })
68 |
69 | test('css dedupes repeated styles', t => {
70 | const a = cx({ p: 2 })
71 | const b = cx({ p: 2 })
72 | t.is(sheet.cssRules.length, 1)
73 | t.deepEqual(a, b)
74 | })
75 |
76 | test('css parses widths', t => {
77 | const a = cx({ w: 1 })
78 | const b = cx({ w: 1/2 })
79 | const c = cx({ w: 0 })
80 | const d = cx({ w: 24 })
81 | const e = cx({ w: 'auto' })
82 | const rules = sheet.cssRules
83 | t.is(rules[0].style.width, '100%')
84 | t.is(rules[1].style.width, '50%')
85 | t.is(rules[2].style.width, '0%')
86 | t.is(rules[3].style.width, '24px')
87 | t.is(rules[4].style.width, 'auto')
88 | })
89 |
90 | test('css parses margins', t => {
91 | cx({ m: 1 })
92 | cx({ m: -2 })
93 | cx({ m: 24 })
94 | cx({ m: -24 })
95 | cx({ m: 'auto' })
96 | const [ a, b, c, d, e ] = sheet.cssRules
97 | t.is(a.style.margin, '8px')
98 | t.is(b.style.margin, '-16px')
99 | t.is(c.style.margin, '24px')
100 | t.is(d.style.margin, '-24px')
101 | t.is(e.style.margin, 'auto')
102 | })
103 |
104 | test('css parses margin directions', t => {
105 | cx({ m: 1 })
106 | cx({ mt: 1 })
107 | cx({ mr: 1 })
108 | cx({ mb: 1 })
109 | cx({ ml: 1 })
110 | cx({ mx: 1 })
111 | cx({ my: 1 })
112 | const [ a, top, r, b, l, x, y ] = sheet.cssRules
113 | t.is(a.style.margin, '8px')
114 | t.is(top.style['margin-top'], '8px')
115 | t.is(r.style['margin-right'], '8px')
116 | t.is(b.style['margin-bottom'], '8px')
117 | t.is(l.style['margin-left'], '8px')
118 | t.is(x.style['margin-left'], '8px')
119 | t.is(x.style['margin-right'], '8px')
120 | t.is(y.style['margin-top'], '8px')
121 | t.is(y.style['margin-bottom'], '8px')
122 | })
123 |
124 | test('css parses paddings', t => {
125 | cx({ p: 1 })
126 | cx({ p: 24 })
127 | cx({ p: '20%' })
128 | const [ a, b, c ] = sheet.cssRules
129 | t.is(a.style.padding, '8px')
130 | t.is(b.style.padding, '24px')
131 | t.is(c.style.padding, '20%')
132 | })
133 |
134 | test('css parses padding directions', t => {
135 | cx({ p: 1 })
136 | cx({ pt: 1 })
137 | cx({ pr: 1 })
138 | cx({ pb: 1 })
139 | cx({ pl: 1 })
140 | cx({ px: 1 })
141 | cx({ py: 1 })
142 | const [ a, top, r, b, l, x, y ] = sheet.cssRules
143 | t.is(a.style.padding, '8px')
144 | t.is(top.style['padding-top'], '8px')
145 | t.is(r.style['padding-right'], '8px')
146 | t.is(b.style['padding-bottom'], '8px')
147 | t.is(l.style['padding-left'], '8px')
148 | t.is(x.style['padding-left'], '8px')
149 | t.is(x.style['padding-right'], '8px')
150 | t.is(y.style['padding-top'], '8px')
151 | t.is(y.style['padding-bottom'], '8px')
152 | })
153 |
154 | test('css parses flexbox styles', t => {
155 | cx({
156 | flex: true,
157 | wrap: true,
158 | column: true,
159 | auto: true,
160 | order: 5,
161 | align: 'center',
162 | justify: 'space-between'
163 | })
164 | const [ a, b, c, d, e, f, g ] = sheet.cssRules
165 | t.is(a.style.display, 'flex')
166 | t.is(b.style['flex-wrap'], 'wrap')
167 | t.is(c.style['flex-direction'], 'column')
168 | t.is(d.style.flex, '1 1 auto')
169 | t.is(e.style.order, '5')
170 | t.is(f.style['align-items'], 'center')
171 | t.is(g.style['justify-content'], 'space-between')
172 | })
173 |
174 | test('snapshot', t => {
175 | const box = render().toJSON()
176 | const flex = render().toJSON()
177 | t.snapshot(box)
178 | t.snapshot(flex)
179 | })
180 |
181 | test('react context', t => {
182 | const ctx = {
183 | space: [ 0, 6, 12, 18, 24, 30 ],
184 | breakpoints: [ 32, 48, 64 ]
185 | }
186 | const box = render(
187 |
188 |
189 |
190 | ).toJSON()
191 | const [ m, w1, w2 ] = sheet.cssRules
192 | t.snapshot(box)
193 | t.is(m.style.margin, '6px')
194 | t.is(w1.style.width, '100%')
195 | t.is(w2.media[0], 'screen and (min-width:32em)')
196 | })
197 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | entry: './docs/entry.js',
5 |
6 | output: {
7 | path: path.join(__dirname, 'docs'),
8 | filename: 'bundle.js'
9 | },
10 |
11 | resolve: {
12 | alias: {
13 | 'reflexbox': path.join(__dirname, 'dist')
14 | }
15 | },
16 |
17 | module: {
18 | rules: [
19 | {
20 | test: /\.js$/,
21 | exclude: /node_modules/,
22 | use: 'babel-loader'
23 | },
24 | {
25 | test: /\.jsx$/,
26 | exclude: /node_modules/,
27 | use: 'raw-loader'
28 | }
29 | ]
30 | },
31 |
32 | devServer: {
33 | contentBase: 'docs/',
34 | historyApiFallback: true
35 | }
36 | }
37 |
--------------------------------------------------------------------------------