├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── README.md ├── banner.jpg ├── lib ├── Article.js ├── Block.js ├── Display.js ├── Flex.js ├── FlexItem.js ├── Heading.js ├── Rule.js ├── Section.js ├── Span.js ├── SvgIcon.js ├── THEME.js ├── Text.js ├── WindowSize.js ├── index.js ├── utils.js └── withWindow.js ├── package.json ├── src ├── App.js ├── ComponentDemo.js ├── __tests__ │ ├── Display.test.js │ ├── SvgIcon.test.js │ └── utils │ │ ├── cssStringUtils.test.js │ │ ├── resolveUnits.test.js │ │ ├── withMediaColumns.test.js │ │ ├── withMediaGutters.test.js │ │ └── withStyles.test.js ├── assets │ ├── img │ │ └── favicon.ico │ └── logo.ai ├── demoData.js ├── index.html ├── index.js └── lib │ ├── Article.js │ ├── Block.js │ ├── Display.js │ ├── Flex.js │ ├── FlexItem.js │ ├── Heading.js │ ├── Rule.js │ ├── Section.js │ ├── Span.js │ ├── SvgIcon.js │ ├── THEME.js │ ├── Text.js │ ├── WindowSize.js │ ├── index.js │ ├── utils.js │ └── withWindow.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "react-hot-loader/babel", 4 | "transform-es2015-modules-commonjs", 5 | ["styled-components", { "ssr": false, "displayName": true, "preprocess": false } ] 6 | ], 7 | "presets": [ 8 | "env", 9 | "stage-0", 10 | "react" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { "browser": true }, // do not trip on browser globals, e.g. window, document.. 3 | "extends": ["airbnb", "prettier", "prettier/react"], 4 | "parser": "babel-eslint", 5 | "plugins": ["prettier"], // apply prettier through ESLint 6 | "rules": { 7 | "prettier/prettier": [ 8 | "warn", 9 | { 10 | "printWidth": 100, 11 | "singleQuote": true, 12 | "trailingComma": "es5" 13 | } 14 | ], 15 | "strict": 0, // for babel-eslint 16 | "no-nested-ternary": 0, 17 | "no-param-reassign": 0, 18 | 19 | "jsx-quotes": [2, "prefer-double"], 20 | "react/destructuring-assignment": "off", 21 | "react/jsx-filename-extension": "off", 22 | "react/require-default-props": "off" 23 | }, 24 | "globals": { 25 | "window": false, 26 | "document": false, 27 | "afterEach": true, 28 | "beforeEach": true, 29 | "describe": true, 30 | "expect": true, 31 | "it": true, 32 | "jest": true, 33 | "test": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | demo/ 3 | npm-debug.log 4 | yarn-error.log 5 | .idea 6 | .idea/ 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | banner.jpg 2 | demo/ 3 | src/ 4 | pages/ 5 | yarn.lock 6 | webpack.config.js 7 | .babelrc 8 | .eslintrc 9 | _config.yml 10 | .idea 11 | .idea/ 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | React Super Styled 3 | ================== 4 | 5 | ![RSS](banner.jpg) 6 | 7 | [![style: styled-components](https://img.shields.io/badge/style-%F0%9F%92%85%20styled--components-orange.svg?colorB=daa357&colorA=db748e)](https://github.com/styled-components/styled-components) 8 | [![js-standard-style](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](http://https://prettier.io/) 9 | 10 | ### Responsive JSX layouts with [Styled Components](https://www.styled-components.com/) 11 | 12 | > Try the [DEMO](https://moarwick.github.io/react-super-styled/) 13 | 14 | RSS is a small React component library which aims to accelerate authoring of JSX layouts and improve their readability: 15 | 16 | * Semantic component and prop naming 17 | * Handy boolean props for common styling rules 18 | * Media breakpoint support for styles, grid, and display (show/hide) 19 | * Flexbox and flex-based grid (arbitrary columns) 20 | * Spacing "shorthands" for margin, padding 21 | * Customizable theme, breakpoints 22 | * Plus: A highly-configurable SVG icon wrapper, utilities 23 | 24 | > Some breaking "improvements" in 0.5.0, 0.7.0 -- see [Releases](https://github.com/moarwick/react-super-styled/releases) 25 | 26 | ### Installation 27 | ``` 28 | npm install react-super-styled --save 29 | ``` 30 | or 31 | ``` 32 | yarn add react-super-styled 33 | ``` 34 | 35 | Your React project should be using Styled Components v4.0+ as a dependency. If not, [install it](https://www.styled-components.com/docs/basics#installation). 36 | 37 | 38 | ### Usage Example 39 | ``` 40 | import { css } from 'styled-components'; 41 | import { Article, Heading, Text } from 'react-super-styled' 42 | 43 | function MyArticle({ text, title }) { 44 | return ( 45 |
46 | {title} 47 | {text} 48 |
49 | ) 50 | } 51 | ``` 52 | 53 | ### Interactive Docs 54 | Try out *React Super Styled* "live" in the [DEMO](https://moarwick.github.io/react-super-styled/). The intent behind *RSS* is to be intuitive and readable. Experiment with all listed props and inspect the results! :) 55 | 56 | > RSS is intended for building layouts, prioritizing dev speed and code readability. Dynamic prop parsing adds some "overhead", so RSS may be inappropriate for complex components requiring lots of custom styling, ultra dense layouts, tables, recursive or iterative applications, or whenever maximum performance is critical. Don't build Reddit with it! :) 57 | 58 | 59 | ### Responsive 60 | Nearly all *RSS* components accept a `styles` prop, with responsive support. Styles can be passed in as a basic string of CSS, e.g. `"color: red; font-size: 2rem"` or an array of CSS interpolations from Styled Components' `css` helper. To specify styles per breakpoint, pass in an object with any of the following supported breakpoint keys: 61 | 62 | ```styles = {{ xs: "...", sm: "...", md: "...", lg: "...", xl: "..." }}``` 63 | 64 | 65 | ### Grid 66 | The `Flex` (container) and `FlexItem` components support all valid Flexbox props, plus an arbitrary-sized grid implementation. 67 | 68 | `FlexItem` supports `col` and an optional `offset`, expecting width values as decimal percentages `0 - 1`. For instance, a third of a 12-column grid, offset to the center: 69 | 70 | ``` 71 | 72 | Column Content 73 | 74 | ``` 75 | 76 | `Flex` accepts an optional `gutter`, which is passed down to any direct `FlexItem` children. Gutters are specified in `rems` (default) or other valid units, e.g. `px`. If specified, negative margins are applied to the `Flex` container to ensure flush alignment of the outer `FlexItem` columns with the container. 77 | 78 | As with styles, the grid props will also accept object values, per breakpoint: 79 | 80 | ``` 81 | 82 | Column Content 83 | 84 | ``` 85 | 86 | ### Spacing Shorthands 87 | Web layouts involve frequent tweaking of margins and padding, so most *RSS* components accept "shorthand" `margin` and `padding` props. Passing in numbers defaults to `rem` units. 88 | 89 | 90 | ### Typography 91 | The *RSS* theme does not come with any predefined font sizing. You can specify browser-interpreted sizing, e.g. `small`, `medium` (matches 100%), `large`, `xLarge`, `xxLarge`, as well as relative sizing & weights, e.g. `smaller`, `larger`, `lighter`, `bolder`. Explicit sizing can be set via the `size` prop, which accepts numbers (`rem`) or strings with any valid units. 92 | 93 | Per "best practices", it's recommended to use `rems`. Setting the following resets on your document tends to work well, establishing `1rem` as `10px`: 94 | 95 | ``` 96 | html { font-size: 62.5%; } // 1rem 97 | body { font-size: 1.4rem; } // ~14px 98 | ``` 99 | 100 | ### Theme 101 | *RSS* components rely on a built-in [default theme](https://github.com/moarwick/react-super-styled/blob/master/src/lib/THEME.js). Being a layout-oriented library, the theme is "design neutral" and contains primarily (Bootstrap compatible) breakpoint values. 102 | 103 | Should you want to override any of those values, you can pass in your own theme (or a subset thereof) to any *RSS* component directly via the `theme` prop. Using Styled Components' `ThemeProvider` [wrapper](https://www.styled-components.com/docs/advanced#theming) should also work. The passed-in theme will be "extended over" the defaults, so it can be used to override existing values or to add more variables in case you decide to [extend](#extending-styling) any *RSS* components further. 104 | 105 | 106 | ### Extending Styling 107 | Majority of *RSS* components are functional native Styled Components, so their styling can be extended further using the [styled(Component)](https://www.styled-components.com/docs/basics#extending-styles) constructor. As of SC v4, you can also pass in a tag name via the "as" prop. 108 | 109 | ### Changelog 110 | * [Releases](https://github.com/moarwick/react-super-styled/releases) 111 | -------------------------------------------------------------------------------- /banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moarwick/react-super-styled/5b447d4dd5f11dae82ea4570457db5edd34622aa/banner.jpg -------------------------------------------------------------------------------- /lib/Article.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _utils = require('./utils'); 10 | 11 | var _Block = require('./Block'); 12 | 13 | var _Block2 = _interopRequireDefault(_Block); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var propTypes = _extends({}, _utils.basePropTypes, _utils.containerPropTypes, _utils.justifyPropTypes, _utils.spacingPropTypes, _utils.mediaStylesPropTypes); 18 | 19 | /** 20 | * Article block wrapper 21 | * Duplicates Block, renders
tag 22 | */ 23 | var Article = _Block2.default.withComponent('article'); 24 | Article.propTypes = propTypes; 25 | exports.default = Article; -------------------------------------------------------------------------------- /lib/Block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _templateObject = _taggedTemplateLiteral(['\n ', '\n ', '\n ', '\n ', '\n'], ['\n ', '\n ', '\n ', '\n ', '\n']), 10 | _templateObject2 = _taggedTemplateLiteral(['\n ', ';\n'], ['\n ', ';\n']); 11 | 12 | var _styledComponents = require('styled-components'); 13 | 14 | var _styledComponents2 = _interopRequireDefault(_styledComponents); 15 | 16 | var _utils = require('./utils'); 17 | 18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 19 | 20 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 21 | 22 | /** 23 | * Block wrapper 24 | * Renders
tag 25 | */ 26 | var propTypes = _extends({}, _utils.basePropTypes, _utils.containerPropTypes, _utils.justifyPropTypes, _utils.spacingPropTypes, _utils.mediaStylesPropTypes); 27 | 28 | // prettier-ignore 29 | var getCss = function getCss(props) { 30 | return (0, _styledComponents.css)(_templateObject, (0, _utils.withContainer)(props), (0, _utils.withJustify)(props), (0, _utils.withSpacing)(props), (0, _utils.withMediaStyles)(props)); 31 | }; 32 | 33 | var Block = _styledComponents2.default.div(_templateObject2, function (props) { 34 | return getCss((0, _utils.addTheme)(props)); 35 | }); 36 | Block.propTypes = propTypes; 37 | exports.default = Block; -------------------------------------------------------------------------------- /lib/Display.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 8 | 9 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 10 | 11 | var _templateObject = _taggedTemplateLiteral(['\n ', '\n ', '\n ', '\n ', '\n ', '\n '], ['\n ', '\n ', '\n ', '\n ', '\n ', '\n ']), 12 | _templateObject2 = _taggedTemplateLiteral(['\n ', ';\n'], ['\n ', ';\n']); 13 | 14 | exports.getCss = getCss; 15 | 16 | var _styledComponents = require('styled-components'); 17 | 18 | var _styledComponents2 = _interopRequireDefault(_styledComponents); 19 | 20 | var _utils = require('./utils'); 21 | 22 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 23 | 24 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 25 | 26 | function toDisplayCss(hide, show) { 27 | if (hide) return 'display: none;'; 28 | if (typeof show === 'boolean' || !show) show = 'inline'; 29 | return 'display: ' + show + ';'; 30 | } 31 | 32 | /** 33 | * Wrapper to show/hide contents based on media breakpoints 34 | * Renders tag 35 | */ 36 | var propTypes = _extends({}, _utils.basePropTypes, _utils.displayPropTypes); 37 | 38 | function getCss(_ref) { 39 | var hide = _ref.hide, 40 | show = _ref.show, 41 | theme = _ref.theme; 42 | 43 | var _toMediaObj = (0, _utils.toMediaObj)(hide || false), 44 | xsHide = _toMediaObj.xs, 45 | smHide = _toMediaObj.sm, 46 | mdHide = _toMediaObj.md, 47 | lgHide = _toMediaObj.lg, 48 | xlHide = _toMediaObj.xl; 49 | 50 | var _toMediaObj2 = (0, _utils.toMediaObj)(show || false), 51 | xsShow = _toMediaObj2.xs, 52 | smShow = _toMediaObj2.sm, 53 | mdShow = _toMediaObj2.md, 54 | lgShow = _toMediaObj2.lg, 55 | xlShow = _toMediaObj2.xl; 56 | 57 | var breakpoints = [[smHide, smShow], [mdHide, mdShow], [lgHide, lgShow], [xlHide, xlShow]]; 58 | 59 | var isHideFirst = Boolean(xsHide); 60 | if (!isHideFirst) { 61 | breakpoints.some(function (_ref2) { 62 | var _ref3 = _slicedToArray(_ref2, 2), 63 | bHide = _ref3[0], 64 | bShow = _ref3[1]; 65 | 66 | if (bHide || bShow) { 67 | isHideFirst = !!bShow; 68 | return true; 69 | } 70 | return false; 71 | }); 72 | } 73 | 74 | return (0, _styledComponents.css)(_templateObject, xsShow ? toDisplayCss(false, xsShow) : isHideFirst ? 'display: none;' : null, (smHide || smShow) && theme.MEDIA_SM_UP + ' { ' + toDisplayCss(smHide, smShow) + ' }', (mdHide || mdShow) && theme.MEDIA_MD_UP + ' { ' + toDisplayCss(mdHide, mdShow) + ' }', (lgHide || lgShow) && theme.MEDIA_LG_UP + ' { ' + toDisplayCss(lgHide, lgShow) + ' }', (xlHide || xlShow) && theme.MEDIA_XL_UP + ' { ' + toDisplayCss(xlHide, xlShow) + ' }'); 75 | } 76 | 77 | var Display = _styledComponents2.default.span(_templateObject2, function (props) { 78 | return getCss((0, _utils.addTheme)(props)); 79 | }); 80 | Display.propTypes = propTypes; 81 | exports.default = Display; -------------------------------------------------------------------------------- /lib/Flex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _templateObject = _taggedTemplateLiteral(['\n display: ', ';\n flex-direction: ', ';\n flex-wrap: ', ';\n justify-content: ', ';\n align-items: ', ';\n align-content: ', ';\n ', '\n ', '\n ', ' // apply gutters last (overrides any prior left/right margins)\n'], ['\n display: ', ';\n flex-direction: ', ';\n flex-wrap: ', ';\n justify-content: ', ';\n align-items: ', ';\n align-content: ', ';\n ', '\n ', '\n ', ' // apply gutters last (overrides any prior left/right margins)\n']), 10 | _templateObject2 = _taggedTemplateLiteral(['\n ', ';\n'], ['\n ', ';\n']); 11 | 12 | var _react = require('react'); 13 | 14 | var _react2 = _interopRequireDefault(_react); 15 | 16 | var _propTypes = require('prop-types'); 17 | 18 | var _propTypes2 = _interopRequireDefault(_propTypes); 19 | 20 | var _styledComponents = require('styled-components'); 21 | 22 | var _styledComponents2 = _interopRequireDefault(_styledComponents); 23 | 24 | var _utils = require('./utils'); 25 | 26 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 27 | 28 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 29 | 30 | /** 31 | * Flex "container", to wrap FlexItems 32 | * Renders
33 | * https://scotch.io/tutorials/a-visual-guide-to-css3-flexbox-properties 34 | * 35 | * CSS Defaults: 36 | * flex-direction: row; 37 | * flex-wrap: nowrap; 38 | * justify-content: flex-start; 39 | * align-items: stretch; 40 | * align-content: stretch; 41 | */ 42 | var propTypes = _extends({}, _utils.basePropTypes, { 43 | inline: _propTypes2.default.bool, 44 | flexDirection: _propTypes2.default.oneOf(['row', 'row-reverse', 'column', 'column-reverse']), 45 | flexWrap: _propTypes2.default.oneOf(['nowrap', 'wrap', 'wrap-reverse']), 46 | justifyContent: _propTypes2.default.oneOf(['flex-start', 'flex-end', 'center', 'space-between', 'space-around']), 47 | alignItems: _propTypes2.default.oneOf(['stretch', 'center', 'flex-start', 'flex-end', 'baseline']), 48 | alignContent: _propTypes2.default.oneOf(['stretch', 'center', 'flex-start', 'flex-end', 'space-between', 'space-around']) 49 | }, _utils.spacingPropTypes, _utils.gutterPropTypes, _utils.mediaStylesPropTypes); 50 | 51 | // change flexWrap default for more grid-like behavior 52 | var defaultProps = { 53 | flexWrap: 'wrap' 54 | }; 55 | 56 | // prettier-ignore 57 | var getCss = function getCss(props) { 58 | return (0, _styledComponents.css)(_templateObject, props.inline ? 'inline-flex' : 'flex', props.flexDirection, props.flexWrap, props.justifyContent, props.alignItems, props.alignContent, (0, _utils.withSpacing)(props), (0, _utils.withMediaStyles)(props), (0, _utils.withMediaGutters)(props)); 59 | }; 60 | 61 | var FlexStyled = _styledComponents2.default.div(_templateObject2, function (props) { 62 | return getCss((0, _utils.addTheme)(props)); 63 | }); 64 | 65 | function Flex(props) { 66 | var children = props.children, 67 | gutter = props.gutter, 68 | smGutter = props.smGutter, 69 | mdGutter = props.mdGutter, 70 | lgGutter = props.lgGutter; 71 | 72 | // pass gutter props to any FlexItem children 73 | 74 | var childrenWithGutterProps = _react2.default.Children.map(children, function (child) { 75 | return child && child.type && child.type.displayName === 'FlexItem' ? _react2.default.cloneElement(child, { gutter: gutter, smGutter: smGutter, mdGutter: mdGutter, lgGutter: lgGutter }) : child; 76 | }); 77 | 78 | return _react2.default.createElement( 79 | FlexStyled, 80 | props, 81 | childrenWithGutterProps 82 | ); 83 | } 84 | 85 | Flex.propTypes = propTypes; 86 | Flex.defaultProps = defaultProps; 87 | exports.default = Flex; -------------------------------------------------------------------------------- /lib/FlexItem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _templateObject = _taggedTemplateLiteral(['\n box-sizing: border-box;\n flex: ', ';\n align-self: ', ';\n order: ', '; \n ', '\n ', '\n ', '\n '], ['\n box-sizing: border-box;\n flex: ', ';\n align-self: ', ';\n order: ', '; \n ', '\n ', '\n ', '\n ']), 10 | _templateObject2 = _taggedTemplateLiteral(['\n ', ';\n'], ['\n ', ';\n']); 11 | 12 | var _propTypes = require('prop-types'); 13 | 14 | var _propTypes2 = _interopRequireDefault(_propTypes); 15 | 16 | var _styledComponents = require('styled-components'); 17 | 18 | var _styledComponents2 = _interopRequireDefault(_styledComponents); 19 | 20 | var _utils = require('./utils'); 21 | 22 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 23 | 24 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 25 | 26 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 27 | 28 | /** 29 | * Flex item wrapper, with 12-column support, media breakpoints 30 | * Renders
31 | * https://scotch.io/tutorials/a-visual-guide-to-css3-flexbox-properties 32 | * https://www.w3.org/TR/styles-flexbox/#flex-common 33 | * 34 | * CSS Defaults: 35 | * order: 0; 36 | * align-self: auto; 37 | * flex: 0 1 auto; <-- recommended 'shorthand' for below props 38 | * 39 | * flex-grow: 0; <-- if any long-hands supplied, expects all three (do not mix) 40 | * flex-shrink: 1; 41 | * flex-basis: auto; 42 | */ 43 | var propTypes = _extends({}, _utils.basePropTypes, { 44 | alignSelf: _propTypes2.default.oneOf(['auto', 'flex-start', 'flex-end', 'center', 'baseline', 'stretch']), 45 | flex: _propTypes2.default.string, 46 | flexBasis: _propTypes2.default.string, 47 | flexGrow: _propTypes2.default.number, 48 | flexShrink: _propTypes2.default.number, 49 | order: _propTypes2.default.number 50 | }, _utils.columnPropTypes, _utils.spacingPropTypes, _utils.mediaStylesPropTypes); 51 | 52 | function getCss(_ref) { 53 | var flex = _ref.flex, 54 | flexGrow = _ref.flexGrow, 55 | flexShrink = _ref.flexShrink, 56 | flexBasis = _ref.flexBasis, 57 | props = _objectWithoutProperties(_ref, ['flex', 'flexGrow', 'flexShrink', 'flexBasis']); 58 | 59 | if (!flex) { 60 | flex = flexGrow || flexShrink || flexBasis ? (flexGrow || 0) + ' ' + (flexShrink || 1) + ' ' + (flexBasis || 'auto') : 'initial'; // 0 1 auto 61 | } 62 | 63 | // prettier-ignore 64 | return (0, _styledComponents.css)(_templateObject, flex, props.alignSelf || 'auto', props.order || 0, (0, _utils.withMediaColumns)(props), (0, _utils.withSpacing)(props), (0, _utils.withMediaStyles)(props)); 65 | } 66 | 67 | var FlexItem = _styledComponents2.default.div(_templateObject2, function (props) { 68 | return getCss((0, _utils.addTheme)(props)); 69 | }); 70 | FlexItem.propTypes = propTypes; 71 | FlexItem.displayName = 'FlexItem'; 72 | exports.default = FlexItem; -------------------------------------------------------------------------------- /lib/Heading.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _templateObject = _taggedTemplateLiteral(['\n color: ', ';\n line-height: ', ';\n ', ' \n ', '\n ', '\n ', '\n '], ['\n color: ', ';\n line-height: ', ';\n ', ' \n ', '\n ', '\n ', '\n ']), 10 | _templateObject2 = _taggedTemplateLiteral(['\n ', ';\n'], ['\n ', ';\n']); 11 | 12 | var _react = require('react'); 13 | 14 | var _react2 = _interopRequireDefault(_react); 15 | 16 | var _propTypes = require('prop-types'); 17 | 18 | var _propTypes2 = _interopRequireDefault(_propTypes); 19 | 20 | var _styledComponents = require('styled-components'); 21 | 22 | var _styledComponents2 = _interopRequireDefault(_styledComponents); 23 | 24 | var _utils = require('./utils'); 25 | 26 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 27 | 28 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 29 | 30 | /** 31 | * Heading 32 | * Renders

tag 33 | */ 34 | var propTypes = _extends({}, _utils.basePropTypes, { 35 | color: _propTypes2.default.string, 36 | lineHeight: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 37 | margin: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]) 38 | }, _utils.fontPropTypes, _utils.justifyPropTypes, _utils.mediaStylesPropTypes); 39 | 40 | var getCss = function getCss(props) { 41 | return ( 42 | // prettier-ignore 43 | (0, _styledComponents.css)(_templateObject, props.color, props.lineHeight, props.margin !== undefined && (0, _utils.cssSpacing)('margin', props), (0, _utils.withFont)(props), (0, _utils.withJustify)(props), (0, _utils.withMediaStyles)(props)) 44 | ); 45 | }; 46 | 47 | var Heading = _styledComponents2.default.h1(_templateObject2, function (props) { 48 | return getCss((0, _utils.addTheme)(props)); 49 | }); 50 | 51 | Heading.propTypes = propTypes; 52 | exports.default = Heading; -------------------------------------------------------------------------------- /lib/Rule.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _templateObject = _taggedTemplateLiteral(['\n border-top: ', ';\n ', ' \n ', ';\n '], ['\n border-top: ', ';\n ', ' \n ', ';\n ']), 10 | _templateObject2 = _taggedTemplateLiteral(['\n background: ', ';\n height: ', '; \n ', '\n ', ';\n '], ['\n background: ', ';\n height: ', '; \n ', '\n ', ';\n ']), 11 | _templateObject3 = _taggedTemplateLiteral(['\n ', ';\n'], ['\n ', ';\n']); 12 | 13 | var _propTypes = require('prop-types'); 14 | 15 | var _propTypes2 = _interopRequireDefault(_propTypes); 16 | 17 | var _styledComponents = require('styled-components'); 18 | 19 | var _styledComponents2 = _interopRequireDefault(_styledComponents); 20 | 21 | var _utils = require('./utils'); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 24 | 25 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 26 | 27 | /** 28 | * A "smarter"
29 | * Renders
30 | */ 31 | var propTypes = _extends({}, _utils.basePropTypes, { 32 | borderStyle: _propTypes2.default.string, 33 | color: _propTypes2.default.string, 34 | colorTo: _propTypes2.default.string, 35 | margin: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 36 | height: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]) 37 | }, _utils.mediaStylesPropTypes); 38 | 39 | var defaultProps = { 40 | borderStyle: 'solid', 41 | color: '#000', 42 | height: 0.1 43 | }; 44 | 45 | var getCss = function getCss(props) { 46 | var borderStyle = props.borderStyle, 47 | color = props.color, 48 | height = props.height, 49 | margin = props.margin; 50 | 51 | 52 | var borderCss = borderStyle !== 'solid' ? (0, _utils.toCssUnits)(height) + ' ' + borderStyle + ' ' + color : ''; 53 | 54 | if (borderCss) { 55 | // prettier-ignore 56 | return (0, _styledComponents.css)(_templateObject, borderCss, margin && (0, _utils.cssSpacing)('margin', props), (0, _utils.withMediaStyles)(props)); 57 | } 58 | 59 | var colorTo = props.colorTo || color; 60 | var background = 'linear-gradient(to right, ' + color + ', ' + colorTo + ')'; 61 | 62 | // prettier-ignore 63 | return (0, _styledComponents.css)(_templateObject2, background, (0, _utils.toCssUnits)(height), margin && (0, _utils.cssSpacing)('margin', margin), (0, _utils.withMediaStyles)(props)); 64 | }; 65 | 66 | var Rule = _styledComponents2.default.div(_templateObject3, function (props) { 67 | return getCss((0, _utils.addTheme)(props)); 68 | }); 69 | Rule.propTypes = propTypes; 70 | Rule.defaultProps = defaultProps; 71 | exports.default = Rule; -------------------------------------------------------------------------------- /lib/Section.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _utils = require('./utils'); 10 | 11 | var _Block = require('./Block'); 12 | 13 | var _Block2 = _interopRequireDefault(_Block); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var propTypes = _extends({}, _utils.basePropTypes, _utils.containerPropTypes, _utils.justifyPropTypes, _utils.spacingPropTypes, _utils.mediaStylesPropTypes); 18 | 19 | /** 20 | * Section block wrapper 21 | * Duplicates Block, renders
tag 22 | */ 23 | var Section = _Block2.default.withComponent('section'); 24 | Section.propTypes = propTypes; 25 | exports.default = Section; -------------------------------------------------------------------------------- /lib/Span.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _templateObject = _taggedTemplateLiteral(['\n color: ', ';\n ', '\n ', '\n ', '\n ', '\n ', '\n ', '\n'], ['\n color: ', ';\n ', '\n ', '\n ', '\n ', '\n ', '\n ', '\n']), 10 | _templateObject2 = _taggedTemplateLiteral(['\n ', ';\n'], ['\n ', ';\n']); 11 | 12 | var _propTypes = require('prop-types'); 13 | 14 | var _propTypes2 = _interopRequireDefault(_propTypes); 15 | 16 | var _styledComponents = require('styled-components'); 17 | 18 | var _styledComponents2 = _interopRequireDefault(_styledComponents); 19 | 20 | var _utils = require('./utils'); 21 | 22 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 23 | 24 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 25 | 26 | /** 27 | * Non-block wrapper 28 | * Renders tag 29 | */ 30 | var propTypes = _extends({}, _utils.basePropTypes, { 31 | color: _propTypes2.default.string, 32 | block: _propTypes2.default.bool, 33 | inlineBlock: _propTypes2.default.bool 34 | }, _utils.fontPropTypes, _utils.justifyPropTypes, _utils.spacingPropTypes, _utils.mediaStylesPropTypes); 35 | 36 | // prettier-ignore 37 | var getCss = function getCss(props) { 38 | return (0, _styledComponents.css)(_templateObject, props.color, props.block && 'display: block;', props.inlineBlock && 'display: inline-block;', (0, _utils.withFont)(props), (0, _utils.withJustify)(props), (0, _utils.withSpacing)(props), (0, _utils.withMediaStyles)(props)); 39 | }; 40 | 41 | var Span = _styledComponents2.default.span(_templateObject2, function (props) { 42 | return getCss((0, _utils.addTheme)(props)); 43 | }); 44 | Span.propTypes = propTypes; 45 | exports.default = Span; -------------------------------------------------------------------------------- /lib/SvgIcon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.DEFAULT_ICON_SIZE = exports.DEFAULT_VIEWBOX = exports.DEFAULT_VIEWBOX_SIZE = undefined; 7 | 8 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 9 | 10 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 11 | 12 | var _templateObject = _taggedTemplateLiteral(['\n display: inline-block;\n position: relative;\n font-size: 0;\n height: ', ';\n width: ', ';\n ', ';\n'], ['\n display: inline-block;\n position: relative;\n font-size: 0;\n height: ', ';\n width: ', ';\n ', ';\n']), 13 | _templateObject2 = _taggedTemplateLiteral(['\n display: inline-block;\n position: absolute;\n left: ', ';\n top: ', ';\n background-color: ', ';\n ', '\n ', '\n box-sizing: border-box;\n ', '\n height: ', ';\n width: ', ';\n transition: all 0.25s ease;\n z-index: 1;\n'], ['\n display: inline-block;\n position: absolute;\n left: ', ';\n top: ', ';\n background-color: ', ';\n ', '\n ', '\n box-sizing: border-box;\n ', '\n height: ', ';\n width: ', ';\n transition: all 0.25s ease;\n z-index: 1;\n']), 14 | _templateObject3 = _taggedTemplateLiteral(['\n position: absolute;\n left: ', ';\n top: ', ';\n cursor: ', ';\n pointer-events: ', ';\n transition: all 0.25s ease;\n z-index: 2;\n'], ['\n position: absolute;\n left: ', ';\n top: ', ';\n cursor: ', ';\n pointer-events: ', ';\n transition: all 0.25s ease;\n z-index: 2;\n']), 15 | _templateObject4 = _taggedTemplateLiteral(['\n ', ';\n'], ['\n ', ';\n']); 16 | 17 | exports.parseViewBoxRatio = parseViewBoxRatio; 18 | exports.parseDimensions = parseDimensions; 19 | 20 | var _react = require('react'); 21 | 22 | var React = _interopRequireWildcard(_react); 23 | 24 | var _propTypes = require('prop-types'); 25 | 26 | var _propTypes2 = _interopRequireDefault(_propTypes); 27 | 28 | var _styledComponents = require('styled-components'); 29 | 30 | var _styledComponents2 = _interopRequireDefault(_styledComponents); 31 | 32 | var _utils = require('./utils'); 33 | 34 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 35 | 36 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 37 | 38 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 39 | 40 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 41 | 42 | // ----- CONSTANTS ----- 43 | 44 | var DEFAULT_VIEWBOX_SIZE = exports.DEFAULT_VIEWBOX_SIZE = 24; 45 | var DEFAULT_VIEWBOX = exports.DEFAULT_VIEWBOX = '0 0 ' + DEFAULT_VIEWBOX_SIZE + ' ' + DEFAULT_VIEWBOX_SIZE; 46 | var DEFAULT_ICON_SIZE = exports.DEFAULT_ICON_SIZE = 1.4; 47 | var XMLNS = 'http://www.w3.org/2000/svg'; 48 | 49 | // ----- HELPER UTILS ----- 50 | 51 | /** 52 | * Calculate width-to-height aspect ratio from viewBox (default 1) 53 | */ 54 | function parseViewBoxRatio(viewBox) { 55 | var coords = (viewBox || DEFAULT_VIEWBOX).split(' ').map(function (val) { 56 | return (0, _utils.toNum)(val); 57 | }); 58 | if (coords.length !== 4) return 1; 59 | 60 | var _coords = _slicedToArray(coords, 4), 61 | x1 = _coords[0], 62 | y1 = _coords[1], 63 | x2 = _coords[2], 64 | y2 = _coords[3]; 65 | 66 | return (x2 - x1) / (y2 - y1) || 1; 67 | } 68 | 69 | /** 70 | * Derive height & width of SVG per its viewBox ratio (default DEFAULT_ICON_SIZE) 71 | */ 72 | function parseDimensions(width, height, viewBox) { 73 | var ratio = parseViewBoxRatio(viewBox); 74 | 75 | if (!width && !height) height = DEFAULT_ICON_SIZE; 76 | if (height && !width) width = (0, _utils.toNum)(height) * ratio; 77 | if (width && !height) height = (0, _utils.toNum)(width) / ratio; 78 | 79 | return [width, height]; 80 | } 81 | 82 | // ----- SUB-COMPONENTS ----- 83 | 84 | var getWrapperCss = function getWrapperCss(props) { 85 | return (0, _styledComponents.css)(_templateObject, props.outerHeight, props.outerWidth, (0, _utils.withMediaStyles)(props)); 86 | }; 87 | 88 | var getBackgroundCss = function getBackgroundCss(props) { 89 | return (0, _styledComponents.css)(_templateObject2, props.left, props.top, props.bgColor || 'transparent', props.border ? 'border: ' + props.border + ';' : '', props.radius ? 'border-radius: ' + props.radius + ';' : '', props.onClick ? 'cursor: pointer;' : '', props.sizeX, props.sizeY); 90 | }; 91 | 92 | var getSvgCss = function getSvgCss(props) { 93 | return (0, _styledComponents.css)(_templateObject3, props.left, props.top, props.cursor, props.pointerEvents); 94 | }; 95 | 96 | var BasicSvg = _styledComponents2.default.svg(_templateObject4, function (props) { 97 | return (0, _utils.withMediaStyles)(props); 98 | }); 99 | 100 | var Wrapper = _styledComponents2.default.span(_templateObject4, function (props) { 101 | return getWrapperCss(props); 102 | }); 103 | 104 | var Background = _styledComponents2.default.span(_templateObject4, function (props) { 105 | return getBackgroundCss(props); 106 | }); 107 | 108 | var Svg = _styledComponents2.default.svg(_templateObject4, function (props) { 109 | return getSvgCss(props); 110 | }); 111 | 112 | // ----- MAIN COMPONENT ----- 113 | 114 | /** 115 | * SvgIcon 116 | * 117 | * An highly-configurable SVG content wrapper 118 | * Adapted from https://gist.github.com/moarwick/1229e9bd73ad52be73d54975cdac0d1e 119 | */ 120 | var propTypes = _extends({}, _utils.basePropTypes, { 121 | innerRef: _propTypes2.default.oneOfType([_propTypes2.default.func, _propTypes2.default.object]), 122 | color: _propTypes2.default.string, 123 | height: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 124 | width: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 125 | stroke: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 126 | viewBox: _propTypes2.default.string, 127 | 128 | // complex, span-wrapped svg 129 | border: _propTypes2.default.string, 130 | bgColor: _propTypes2.default.string, 131 | inset: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 132 | offsetX: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 133 | offsetY: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 134 | onClick: _propTypes2.default.func, 135 | radius: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]) 136 | }, _utils.mediaStylesPropTypes); 137 | 138 | var defaultProps = { 139 | color: '#000', 140 | inset: 0, 141 | height: 0, 142 | width: 0, 143 | viewBox: DEFAULT_VIEWBOX, 144 | theme: {} 145 | }; 146 | 147 | var SvgIcon = function SvgIcon(props) { 148 | props = (0, _utils.addTheme)(props); 149 | 150 | var _props = props, 151 | innerRef = _props.innerRef, 152 | inset = _props.inset, 153 | offsetX = _props.offsetX, 154 | offsetY = _props.offsetY, 155 | radius = _props.radius, 156 | border = _props.border, 157 | bgColor = _props.bgColor, 158 | children = _props.children, 159 | color = _props.color, 160 | onClick = _props.onClick, 161 | stroke = _props.stroke, 162 | viewBox = _props.viewBox, 163 | styles = _props.styles, 164 | attribs = _objectWithoutProperties(_props, ['innerRef', 'inset', 'offsetX', 'offsetY', 'radius', 'border', 'bgColor', 'children', 'color', 'onClick', 'stroke', 'viewBox', 'styles']); 165 | 166 | var units = (0, _utils.resolveUnits)(props.width + ' ' + props.height + ' ' + inset + ' ' + offsetX + ' ' + offsetY); 167 | 168 | var _parseDimensions = parseDimensions((0, _utils.toNum)(props.width), (0, _utils.toNum)(props.height), viewBox), 169 | _parseDimensions2 = _slicedToArray(_parseDimensions, 2), 170 | width = _parseDimensions2[0], 171 | height = _parseDimensions2[1]; 172 | 173 | var widthPx = units === 'px' ? width : width * 10; // assume 1rem === 10px 174 | var heightPx = units === 'px' ? height : height * 10; // assume 1rem === 10px 175 | 176 | var isAdvanced = border || bgColor || radius || inset || offsetX || offsetY || onClick; 177 | 178 | // if "advanced" functionality not required, render a "basic" svg-only version 179 | if (!isAdvanced) { 180 | return React.createElement( 181 | BasicSvg, 182 | _extends({}, attribs, { 183 | ref: innerRef, 184 | height: heightPx, 185 | width: widthPx, 186 | viewBox: viewBox, 187 | xmlns: XMLNS, 188 | styles: styles 189 | }), 190 | React.createElement( 191 | 'g', 192 | { fill: color, stroke: color, strokeWidth: (0, _utils.toCssUnits)(stroke) }, 193 | children 194 | ) 195 | ); 196 | } 197 | 198 | // otherwise, render the full wrapper... 199 | inset = (0, _utils.toNum)(inset); 200 | offsetX = (0, _utils.toNum)(offsetX); 201 | offsetY = (0, _utils.toNum)(offsetY); 202 | 203 | var widthUnits = '' + width + units; 204 | var heightUnits = '' + height + units; 205 | var innerHeight = Math.max(0, height - inset * 2); 206 | var innerWidth = Math.max(0, width - inset * 2); 207 | var innerHeightPx = units === 'px' ? innerHeight : innerHeight * 10; 208 | var innerWidthPx = units === 'px' ? innerWidth : innerWidth * 10; 209 | 210 | if (radius) { 211 | var radiusUnits = (0, _utils.resolveUnits)(String(radius || '')); 212 | radius = '' + (0, _utils.toNum)(radius) + radiusUnits; 213 | } 214 | 215 | var isBackground = !!(bgColor || border || inset); 216 | 217 | return React.createElement( 218 | Wrapper, 219 | _extends({}, attribs, { 220 | ref: innerRef, 221 | outerHeight: heightUnits, 222 | outerWidth: widthUnits, 223 | styles: styles 224 | }), 225 | React.createElement('svg', { viewBox: viewBox, height: heightPx, width: widthPx, xmlns: XMLNS }), 226 | isBackground && React.createElement(Background, { 227 | bgColor: bgColor, 228 | border: border, 229 | sizeY: heightUnits, 230 | sizeX: widthUnits, 231 | left: '' + offsetX + units, 232 | top: '' + offsetY + units, 233 | onClick: onClick, 234 | radius: radius 235 | }), 236 | React.createElement( 237 | Svg, 238 | { 239 | height: innerHeightPx, 240 | width: innerWidthPx, 241 | viewBox: viewBox, 242 | xmlns: XMLNS, 243 | left: '' + (offsetX + inset) + units, 244 | top: '' + (offsetY + inset) + units, 245 | cursor: onClick && !isBackground ? 'pointer' : 'default', 246 | pointerEvents: onClick && isBackground ? 'none' : 'auto' 247 | }, 248 | React.createElement( 249 | 'g', 250 | { fill: color, stroke: color, strokeWidth: (0, _utils.toCssUnits)(stroke) }, 251 | children 252 | ) 253 | ) 254 | ); 255 | }; 256 | 257 | SvgIcon.displayName = 'SvgIcon'; 258 | SvgIcon.propTypes = propTypes; 259 | SvgIcon.defaultProps = defaultProps; 260 | exports.default = SvgIcon; -------------------------------------------------------------------------------- /lib/THEME.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | /** 7 | * Media Breakpoints 8 | */ 9 | var MEDIA_SM = 576; // SM range: 576 - 767 10 | var MEDIA_MD = 768; // MD range: 768 - 991 11 | var MEDIA_LG = 992; // LG range: 992 - 1199 12 | var MEDIA_XL = 1200; // XL range: 1200 - Infinity 13 | 14 | /** 15 | * Theming Variables 16 | */ 17 | var THEME = { 18 | CONTAINER_SMALL: 740, 19 | CONTAINER_MEDIUM: 960, 20 | CONTAINER_LARGE: 980, 21 | 22 | MEDIA_SM: MEDIA_SM, 23 | MEDIA_MD: MEDIA_MD, 24 | MEDIA_LG: MEDIA_LG, 25 | MEDIA_XL: MEDIA_XL, 26 | 27 | // applies at and above given breakpoint 28 | MEDIA_SM_UP: "@media only screen and (min-width: " + MEDIA_SM + "px)", 29 | MEDIA_MD_UP: "@media only screen and (min-width: " + MEDIA_MD + "px)", 30 | MEDIA_LG_UP: "@media only screen and (min-width: " + MEDIA_LG + "px)", 31 | MEDIA_XL_UP: "@media only screen and (min-width: " + MEDIA_XL + "px)", 32 | 33 | // applies below given breakpoint 34 | MEDIA_XS_DOWN: "@media only screen and (max-width: " + (MEDIA_SM - 1) + "px)", 35 | MEDIA_SM_DOWN: "@media only screen and (max-width: " + (MEDIA_MD - 1) + "px)", 36 | MEDIA_MD_DOWN: "@media only screen and (max-width: " + (MEDIA_LG - 1) + "px)", 37 | MEDIA_LG_DOWN: "@media only screen and (max-width: " + (MEDIA_XL - 1) + "px)" 38 | }; 39 | 40 | exports.default = THEME; -------------------------------------------------------------------------------- /lib/Text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _templateObject = _taggedTemplateLiteral(['\n color: ', ';\n line-height: ', ';\n ', ' \n ', '\n ', '\n ', '\n'], ['\n color: ', ';\n line-height: ', ';\n ', ' \n ', '\n ', '\n ', '\n']), 10 | _templateObject2 = _taggedTemplateLiteral(['\n ', ';\n'], ['\n ', ';\n']); 11 | 12 | var _propTypes = require('prop-types'); 13 | 14 | var _propTypes2 = _interopRequireDefault(_propTypes); 15 | 16 | var _styledComponents = require('styled-components'); 17 | 18 | var _styledComponents2 = _interopRequireDefault(_styledComponents); 19 | 20 | var _utils = require('./utils'); 21 | 22 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 23 | 24 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 25 | 26 | /** 27 | * Text paragraph 28 | * Renders

tag 29 | */ 30 | var propTypes = _extends({}, _utils.basePropTypes, { 31 | color: _propTypes2.default.string, 32 | lineHeight: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 33 | margin: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]) 34 | }, _utils.fontPropTypes, _utils.justifyPropTypes, _utils.mediaStylesPropTypes); 35 | 36 | // prettier-ignore 37 | var getCss = function getCss(props) { 38 | return (0, _styledComponents.css)(_templateObject, props.color, props.lineHeight, props.margin !== undefined && (0, _utils.cssSpacing)('margin', props), (0, _utils.withFont)(props), (0, _utils.withJustify)(props), (0, _utils.withMediaStyles)(props)); 39 | }; 40 | 41 | var Text = _styledComponents2.default.p(_templateObject2, function (props) { 42 | return getCss((0, _utils.addTheme)(props)); 43 | }); 44 | Text.propTypes = propTypes; 45 | exports.default = Text; -------------------------------------------------------------------------------- /lib/WindowSize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 12 | 13 | var _react = require('react'); 14 | 15 | var _react2 = _interopRequireDefault(_react); 16 | 17 | var _propTypes = require('prop-types'); 18 | 19 | var _propTypes2 = _interopRequireDefault(_propTypes); 20 | 21 | var _utils = require('./utils'); 22 | 23 | var _THEME2 = require('./THEME'); 24 | 25 | var _THEME3 = _interopRequireDefault(_THEME2); 26 | 27 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 28 | 29 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 30 | 31 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 32 | 33 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 34 | 35 | var MEDIA_DEFAULT = 'xs'; 36 | 37 | function getCurrentMedia(ranges) { 38 | var width = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 39 | 40 | var media = MEDIA_DEFAULT; 41 | Object.keys(ranges).some(function (key) { 42 | var _ranges$key = _slicedToArray(ranges[key], 2), 43 | min = _ranges$key[0], 44 | max = _ranges$key[1]; 45 | 46 | if (width >= min && width <= max) { 47 | media = key; 48 | return true; 49 | } 50 | return false; 51 | }); 52 | return media; 53 | } 54 | 55 | function getWindowSize() { 56 | var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 57 | var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; 58 | return { width: width, height: height }; 59 | } 60 | 61 | /** 62 | * WindowSize 63 | * 64 | * Wrapper component to supply current 'window' sizing and 'media' breakpoint to the wrapped component 65 | * Value of 'media' will be one of: 'xs' (default), 'sm', 'md', 'lg', 'xl' 66 | * Supports "children as a function" 67 | */ 68 | var propTypes = { 69 | children: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.func]).isRequired, 70 | theme: _propTypes2.default.object // optional theme media overrides 71 | }; 72 | 73 | var WindowSize = function (_React$Component) { 74 | _inherits(WindowSize, _React$Component); 75 | 76 | function WindowSize(props) { 77 | _classCallCheck(this, WindowSize); 78 | 79 | var _this = _possibleConstructorReturn(this, (WindowSize.__proto__ || Object.getPrototypeOf(WindowSize)).call(this, props)); 80 | 81 | _this.handleResize = function () { 82 | var _getWindowSize = getWindowSize(), 83 | width = _getWindowSize.width, 84 | height = _getWindowSize.height; 85 | 86 | var media = getCurrentMedia(_this.mediaRanges, width); 87 | _this.setState({ width: width, height: height, media: media }); 88 | }; 89 | 90 | _this.state = { 91 | media: MEDIA_DEFAULT, 92 | height: 0, 93 | width: 0 94 | }; 95 | 96 | var _THEME = _extends({}, _THEME3.default, props.theme || {}), 97 | MEDIA_SM = _THEME.MEDIA_SM, 98 | MEDIA_MD = _THEME.MEDIA_MD, 99 | MEDIA_LG = _THEME.MEDIA_LG, 100 | MEDIA_XL = _THEME.MEDIA_XL; 101 | 102 | _this.mediaRanges = { 103 | xs: [0, MEDIA_SM - 1], 104 | sm: [MEDIA_SM, MEDIA_MD - 1], 105 | md: [MEDIA_MD, MEDIA_LG - 1], 106 | lg: [MEDIA_LG, MEDIA_XL - 1], 107 | xl: [MEDIA_XL, Infinity] 108 | }; 109 | return _this; 110 | } 111 | 112 | _createClass(WindowSize, [{ 113 | key: 'componentDidMount', 114 | value: function componentDidMount() { 115 | if (window) { 116 | (0, _utils.throttleEvent)('resize', 'throttledWindowResize', window); 117 | window.addEventListener('throttledWindowResize', this.handleResize); 118 | this.handleResize(); 119 | } 120 | } 121 | }, { 122 | key: 'componentWillUnmount', 123 | value: function componentWillUnmount() { 124 | if (window) { 125 | window.removeEventListener('throttledWindowResize', this.handleResize); 126 | } 127 | } 128 | }, { 129 | key: 'render', 130 | value: function render() { 131 | var children = this.props.children; 132 | var _state = this.state, 133 | width = _state.width, 134 | height = _state.height, 135 | media = _state.media; 136 | 137 | 138 | var windowProps = { 139 | media: media, 140 | windowSize: { width: width, height: height } 141 | }; 142 | 143 | if (typeof children === 'function') { 144 | return children(windowProps); 145 | } 146 | 147 | return _react2.default.Children.map(children, function (child) { 148 | return _react2.default.cloneElement(child, windowProps); 149 | }); 150 | } 151 | }]); 152 | 153 | return WindowSize; 154 | }(_react2.default.Component); 155 | 156 | WindowSize.propTypes = propTypes; 157 | exports.default = WindowSize; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _Article = require('./Article'); 8 | 9 | Object.defineProperty(exports, 'Article', { 10 | enumerable: true, 11 | get: function get() { 12 | return _interopRequireDefault(_Article).default; 13 | } 14 | }); 15 | 16 | var _Block = require('./Block'); 17 | 18 | Object.defineProperty(exports, 'Block', { 19 | enumerable: true, 20 | get: function get() { 21 | return _interopRequireDefault(_Block).default; 22 | } 23 | }); 24 | 25 | var _Display = require('./Display'); 26 | 27 | Object.defineProperty(exports, 'Display', { 28 | enumerable: true, 29 | get: function get() { 30 | return _interopRequireDefault(_Display).default; 31 | } 32 | }); 33 | 34 | var _Flex = require('./Flex'); 35 | 36 | Object.defineProperty(exports, 'Flex', { 37 | enumerable: true, 38 | get: function get() { 39 | return _interopRequireDefault(_Flex).default; 40 | } 41 | }); 42 | 43 | var _FlexItem = require('./FlexItem'); 44 | 45 | Object.defineProperty(exports, 'FlexItem', { 46 | enumerable: true, 47 | get: function get() { 48 | return _interopRequireDefault(_FlexItem).default; 49 | } 50 | }); 51 | 52 | var _Heading = require('./Heading'); 53 | 54 | Object.defineProperty(exports, 'Heading', { 55 | enumerable: true, 56 | get: function get() { 57 | return _interopRequireDefault(_Heading).default; 58 | } 59 | }); 60 | 61 | var _Rule = require('./Rule'); 62 | 63 | Object.defineProperty(exports, 'Rule', { 64 | enumerable: true, 65 | get: function get() { 66 | return _interopRequireDefault(_Rule).default; 67 | } 68 | }); 69 | 70 | var _Section = require('./Section'); 71 | 72 | Object.defineProperty(exports, 'Section', { 73 | enumerable: true, 74 | get: function get() { 75 | return _interopRequireDefault(_Section).default; 76 | } 77 | }); 78 | 79 | var _Span = require('./Span'); 80 | 81 | Object.defineProperty(exports, 'Span', { 82 | enumerable: true, 83 | get: function get() { 84 | return _interopRequireDefault(_Span).default; 85 | } 86 | }); 87 | 88 | var _Text = require('./Text'); 89 | 90 | Object.defineProperty(exports, 'Text', { 91 | enumerable: true, 92 | get: function get() { 93 | return _interopRequireDefault(_Text).default; 94 | } 95 | }); 96 | 97 | var _SvgIcon = require('./SvgIcon'); 98 | 99 | Object.defineProperty(exports, 'SvgIcon', { 100 | enumerable: true, 101 | get: function get() { 102 | return _interopRequireDefault(_SvgIcon).default; 103 | } 104 | }); 105 | 106 | var _WindowSize = require('./WindowSize'); 107 | 108 | Object.defineProperty(exports, 'WindowSize', { 109 | enumerable: true, 110 | get: function get() { 111 | return _interopRequireDefault(_WindowSize).default; 112 | } 113 | }); 114 | 115 | var _withWindow = require('./withWindow'); 116 | 117 | Object.defineProperty(exports, 'withWindow', { 118 | enumerable: true, 119 | get: function get() { 120 | return _interopRequireDefault(_withWindow).default; 121 | } 122 | }); 123 | 124 | var _THEME = require('./THEME'); 125 | 126 | Object.defineProperty(exports, 'RSS_THEME', { 127 | enumerable: true, 128 | get: function get() { 129 | return _interopRequireDefault(_THEME).default; 130 | } 131 | }); 132 | 133 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.spacingPropTypes = exports.mediaStylesPropTypes = exports.gutterPropTypes = exports.justifyPropTypes = exports.fontPropTypes = exports.containerPropTypes = exports.displayPropTypes = exports.columnPropTypes = exports.basePropTypes = undefined; 7 | 8 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 9 | 10 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 11 | 12 | var _templateObject = _taggedTemplateLiteral(['\n ', ': ', ';\n '], ['\n ', ': ', ';\n ']), 13 | _templateObject2 = _taggedTemplateLiteral(['\n margin-left: auto;\n margin-right: auto;\n max-width: ', 'px;\n '], ['\n margin-left: auto;\n margin-right: auto;\n max-width: ', 'px;\n ']), 14 | _templateObject3 = _taggedTemplateLiteral(['\n margin-left: auto;\n margin-right: auto;\n max-width: ', 'px;\n\n ', ' { \n max-width: ', 'px;\n }\n \n ', ' { \n max-width: ', 'px;\n }\n '], ['\n margin-left: auto;\n margin-right: auto;\n max-width: ', 'px;\n\n ', ' { \n max-width: ', 'px;\n }\n \n ', ' { \n max-width: ', 'px;\n }\n ']), 15 | _templateObject4 = _taggedTemplateLiteral(['\n ', '\n ', '\n font-size: ', '; \n font-style: ', '; \n font-weight: ', ';\n '], ['\n ', '\n ', '\n font-size: ', '; \n font-style: ', '; \n font-weight: ', ';\n ']), 16 | _templateObject5 = _taggedTemplateLiteral(['\n ', ';\n '], ['\n ', ';\n ']), 17 | _templateObject6 = _taggedTemplateLiteral(['\n ', '\n ', '\n ', '\n ', '\n ', '\n '], ['\n ', '\n ', '\n ', '\n ', '\n ', '\n ']), 18 | _templateObject7 = _taggedTemplateLiteral(['', ''], ['', '']), 19 | _templateObject8 = _taggedTemplateLiteral(['\n ', ' \n ', '\n '], ['\n ', ' \n ', '\n ']); 20 | 21 | exports.addTheme = addTheme; 22 | exports.ensureSemi = ensureSemi; 23 | exports.resolveUnits = resolveUnits; 24 | exports.toCssString = toCssString; 25 | exports.toCssUnits = toCssUnits; 26 | exports.toMediaObj = toMediaObj; 27 | exports.toNum = toNum; 28 | exports.cssSpacing = cssSpacing; 29 | exports.toMediaGuttersCss = toMediaGuttersCss; 30 | exports.toMediaColumnCss = toMediaColumnCss; 31 | exports.throttleEvent = throttleEvent; 32 | exports.withContainer = withContainer; 33 | exports.withFont = withFont; 34 | exports.withJustify = withJustify; 35 | exports.withMediaGutters = withMediaGutters; 36 | exports.withMediaColumns = withMediaColumns; 37 | exports.withMediaStyles = withMediaStyles; 38 | exports.withSpacing = withSpacing; 39 | 40 | var _propTypes = require('prop-types'); 41 | 42 | var _propTypes2 = _interopRequireDefault(_propTypes); 43 | 44 | var _styledComponents = require('styled-components'); 45 | 46 | var _THEME = require('./THEME'); 47 | 48 | var _THEME2 = _interopRequireDefault(_THEME); 49 | 50 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 51 | 52 | function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } 53 | 54 | /** 55 | * Extend the theme prop onto the base THEME, and return all props 56 | */ 57 | function addTheme(props) { 58 | return _extends({}, props, { theme: props.theme ? _extends({}, _THEME2.default, props.theme) : _THEME2.default }); 59 | } 60 | 61 | /** 62 | * Accept string, trim, return terminated with a semicolon 63 | * If input string contains all spaces or only ';', return empty string 64 | */ 65 | function ensureSemi(val) { 66 | val = val.trim(); 67 | return val && val !== ';' ? val.slice(-1) === ';' ? val : val + ';' : ''; 68 | } 69 | 70 | /** 71 | * Parse value to determine units, make default assumptions based on type (unless default passed in) 72 | * Return string: px|rem|em|% 73 | */ 74 | function resolveUnits(val) { 75 | var defaultUnits = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; 76 | 77 | var match = String(val).match(/px|rem|em|%/g); 78 | return match ? match[0] : defaultUnits || 'rem'; 79 | } 80 | 81 | /** 82 | * Accept string or a list of strings (from SC), return a single string 83 | * Filter, trim, and ensure a semicolon delimiter 84 | */ 85 | function toCssString(val) { 86 | if (typeof val === 'string') return ensureSemi(val); 87 | if (!Array.isArray(val)) return ''; 88 | return val.map(function (el) { 89 | return ensureSemi(el); 90 | }).join('').trim(); 91 | } 92 | 93 | /** 94 | * Return strings as-is, coerce numbers to 'rem' (default '0rem') 95 | */ 96 | function toCssUnits(val) { 97 | var units = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'rem'; 98 | 99 | return typeof val === 'string' ? val : typeof val === 'number' ? '' + val + units : '0rem'; 100 | } 101 | 102 | /** 103 | * Helper to ensure that value is a (media) object 104 | */ 105 | function toMediaObj(val) { 106 | if (typeof val === 'number' || typeof val === 'string' || typeof val === 'boolean') return { xs: val }; 107 | if ((typeof val === 'undefined' ? 'undefined' : _typeof(val)) === 'object' && Object.keys(val).length) return val; 108 | return {}; 109 | } 110 | 111 | /** 112 | * Parse input value into a number, with optional decimal precision 113 | * Always return a number, 0 if value cannot be parsed 114 | */ 115 | function toNum(value) { 116 | var precision = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 117 | 118 | value = parseFloat(value) || 0; 119 | return precision > 0 ? Number(value.toFixed(precision)) : value; 120 | } 121 | 122 | /** 123 | * Deliver CSS 'padding' or 'margin' rules derived from passed in prop 124 | * Numbers are assumed to be 'rem' 125 | */ 126 | function cssSpacing(rule, value) { 127 | if (typeof value === 'number') value += 'rem'; 128 | return (0, _styledComponents.css)(_templateObject, rule, value); 129 | } 130 | 131 | /** 132 | * Deliver negative left/right margin rules for FlexRow 133 | * This is to ensure outer columns (with gutters) are flush with the container 134 | */ 135 | function toMediaGuttersCss(breakpoint, gutter) { 136 | var units = resolveUnits(gutter); 137 | gutter = toNum(gutter); 138 | var rule = gutter ? 'margin-left: -' + gutter / 2 + units + '; margin-right: -' + gutter / 2 + units + ';' : 'margin-left: 0; margin-right: 0;'; 139 | return breakpoint ? breakpoint + ' { ' + rule + ' }' : rule; 140 | } 141 | 142 | /** 143 | * Deliver correct column width and left/right margins, per the supplied props 144 | */ 145 | function toMediaColumnCss(breakpoint, col, offset, gutter) { 146 | var units = resolveUnits(gutter); 147 | gutter = toNum(gutter); 148 | col = toNum(col) * 100; 149 | offset = toNum(offset) * 100; 150 | var width = gutter ? 'calc(' + col + '% - ' + gutter + units + ')' : col + '%'; 151 | var marginRight = gutter ? '' + gutter / 2 + units : '0'; 152 | var marginLeft = offset && gutter ? 'calc(' + offset + '% + ' + gutter / 2 + units + ')' : offset && !gutter ? offset + '%' : gutter ? '' + gutter / 2 + units : '0'; 153 | 154 | var rule = 'margin-left: ' + marginLeft + '; margin-right: ' + marginRight + '; width: ' + width + ';'; 155 | return breakpoint ? breakpoint + ' { ' + rule + ' }' : rule; 156 | } 157 | 158 | /** 159 | * throttleEvent 160 | */ 161 | function throttleEvent(type, name, obj) { 162 | var isTriggered = false; 163 | var func = function func() { 164 | if (isTriggered) return; 165 | isTriggered = true; 166 | requestAnimationFrame(function () { 167 | obj.dispatchEvent(new CustomEvent(name)); 168 | isTriggered = false; 169 | }); 170 | }; 171 | obj = obj || window; 172 | obj.addEventListener(type, func); 173 | } 174 | 175 | /* ----- PROP TYPES & CSS RULE SETS ----- */ 176 | 177 | var basePropTypes = { 178 | children: _propTypes2.default.node, 179 | theme: _propTypes2.default.object 180 | }; 181 | exports.basePropTypes = basePropTypes; 182 | var columnPropTypes = exports.columnPropTypes = { 183 | col: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.object]), 184 | offset: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.object]), 185 | gutter: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string, _propTypes2.default.object]) 186 | }; 187 | 188 | var displayPropTypes = { 189 | hide: _propTypes2.default.oneOfType([_propTypes2.default.bool, _propTypes2.default.object]), 190 | show: _propTypes2.default.oneOfType([_propTypes2.default.bool, _propTypes2.default.string, _propTypes2.default.object]) 191 | }; 192 | exports.displayPropTypes = displayPropTypes; 193 | 194 | /** 195 | * withContainer 196 | */ 197 | 198 | var containerPropTypes = { 199 | container: _propTypes2.default.oneOfType([_propTypes2.default.bool, _propTypes2.default.number]) 200 | }; 201 | exports.containerPropTypes = containerPropTypes; 202 | function withContainer(props) { 203 | if (!props.container) return ''; 204 | 205 | if (typeof props.container === 'number') return (0, _styledComponents.css)(_templateObject2, props.container); 206 | 207 | // passing in a 'boolean' behaves "just like Bootstrap" 208 | // prettier-ignore 209 | return (0, _styledComponents.css)(_templateObject3, props.theme.CONTAINER_SMALL, props.theme.MEDIA_MD_UP, props.theme.CONTAINER_MEDIUM, props.theme.MEDIA_LG_UP, props.theme.CONTAINER_LARGE); 210 | } 211 | 212 | /** 213 | * withFont 214 | */ 215 | var fontPropTypes = { 216 | inline: _propTypes2.default.bool, 217 | 218 | roman: _propTypes2.default.bool, 219 | italic: _propTypes2.default.bool, 220 | oblique: _propTypes2.default.bool, 221 | 222 | light: _propTypes2.default.bool, 223 | lighter: _propTypes2.default.bool, 224 | normal: _propTypes2.default.bool, 225 | bold: _propTypes2.default.bool, 226 | bolder: _propTypes2.default.bool, 227 | 228 | xxSmall: _propTypes2.default.bool, 229 | xSmall: _propTypes2.default.bool, 230 | small: _propTypes2.default.bool, 231 | medium: _propTypes2.default.bool, 232 | large: _propTypes2.default.bool, 233 | xLarge: _propTypes2.default.bool, 234 | xxLarge: _propTypes2.default.bool, 235 | larger: _propTypes2.default.bool, 236 | smaller: _propTypes2.default.bool, 237 | 238 | size: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 239 | underline: _propTypes2.default.bool 240 | }; 241 | var FONT_SIZING = { 242 | xxSmall: 'xx-small', 243 | xSmall: 'x-small', 244 | small: 'small', 245 | medium: 'medium', 246 | large: 'large', 247 | xLarge: 'x-large', 248 | xxLarge: 'xx-large', 249 | larger: 'larger', 250 | smaller: 'smaller' 251 | }; 252 | exports.fontPropTypes = fontPropTypes; 253 | function withFont(props) { 254 | var fontStyle = ['roman', 'italic', 'oblique'].find(function (style) { 255 | return style in props; 256 | }) || ''; 257 | if (fontStyle === 'roman') fontStyle = 'normal'; 258 | 259 | var fontWeight = ['light', 'lighter', 'normal', 'bold', 'bolder'].find(function (weight) { 260 | return weight in props; 261 | }) || ''; 262 | 263 | var fontSize = props.size ? toCssUnits(props.size) : FONT_SIZING[Object.keys(FONT_SIZING).find(function (size) { 264 | return size in props; 265 | })] || ''; 266 | 267 | // prettier-ignore 268 | return (0, _styledComponents.css)(_templateObject4, props.inline && 'display: inline;', props.underline && 'text-decoration: underline;', fontSize, fontStyle, fontWeight); 269 | } 270 | 271 | /** 272 | * withJustify 273 | */ 274 | var justifyPropTypes = { 275 | left: _propTypes2.default.bool, 276 | center: _propTypes2.default.bool, 277 | right: _propTypes2.default.bool 278 | }; 279 | exports.justifyPropTypes = justifyPropTypes; 280 | function withJustify(props) { 281 | var justify = ['center', 'right', 'left'].find(function (dir) { 282 | return dir in props; 283 | }); 284 | return (0, _styledComponents.css)(_templateObject5, justify && 'text-align: ' + justify + ';'); 285 | } 286 | 287 | /** 288 | * withMediaGutters 289 | */ 290 | var gutterPropTypes = { 291 | gutter: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.object]) 292 | }; 293 | exports.gutterPropTypes = gutterPropTypes; 294 | function withMediaGutters(_ref) { 295 | var gutter = _ref.gutter, 296 | theme = _ref.theme; 297 | 298 | gutter = toMediaObj(gutter); 299 | 300 | // prettier-ignore 301 | var result = (0, _styledComponents.css)(_templateObject6, 'xs' in gutter && toMediaGuttersCss(null, gutter.xs), 'sm' in gutter && toMediaGuttersCss(theme.MEDIA_SM_UP, gutter.sm), 'md' in gutter && toMediaGuttersCss(theme.MEDIA_MD_UP, gutter.md), 'lg' in gutter && toMediaGuttersCss(theme.MEDIA_LG_UP, gutter.lg), 'xl' in gutter && toMediaGuttersCss(theme.MEDIA_XL_UP, gutter.xl)); 302 | 303 | return result; 304 | } 305 | 306 | /** 307 | * withMediaColumns 308 | */ 309 | function withMediaColumns(_ref2) { 310 | var col = _ref2.col, 311 | _ref2$offset = _ref2.offset, 312 | offset = _ref2$offset === undefined ? 0 : _ref2$offset, 313 | _ref2$gutter = _ref2.gutter, 314 | gutter = _ref2$gutter === undefined ? 0 : _ref2$gutter, 315 | theme = _ref2.theme; 316 | 317 | col = toMediaObj(col); 318 | offset = toMediaObj(offset); 319 | gutter = toMediaObj(gutter); 320 | 321 | // prettier-ignore 322 | return (0, _styledComponents.css)(_templateObject6, 'xs' in col && toMediaColumnCss(null, col.xs, offset.xs, gutter.xs), 'sm' in col && toMediaColumnCss(theme.MEDIA_SM_UP, col.sm, offset.sm, gutter.sm), 'md' in col && toMediaColumnCss(theme.MEDIA_MD_UP, col.md, offset.md, gutter.md), 'lg' in col && toMediaColumnCss(theme.MEDIA_LG_UP, col.lg, offset.lg, gutter.lg), 'xl' in col && toMediaColumnCss(theme.MEDIA_XL_UP, col.xl, offset.xl, gutter.xl)); 323 | } 324 | 325 | /** 326 | * withMediaStyles 327 | */ 328 | var mediaStylesPropTypes = { 329 | styles: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.arrayOf(_propTypes2.default.string), _propTypes2.default.object]) 330 | }; 331 | exports.mediaStylesPropTypes = mediaStylesPropTypes; 332 | function withMediaStyles(_ref3) { 333 | var styles = _ref3.styles, 334 | theme = _ref3.theme; 335 | 336 | if (Array.isArray(styles)) return styles; 337 | if (typeof styles === 'string') return (0, _styledComponents.css)(_templateObject7, toCssString(styles)); // prettier-ignore 338 | if ((typeof styles === 'undefined' ? 'undefined' : _typeof(styles)) === 'object') { 339 | // prettier-ignore 340 | return (0, _styledComponents.css)(_templateObject6, styles.xs && toCssString(styles.xs), styles.sm && theme.MEDIA_SM_UP + ' { ' + toCssString(styles.sm) + ' }', styles.md && theme.MEDIA_MD_UP + ' { ' + toCssString(styles.md) + ' }', styles.lg && theme.MEDIA_LG_UP + ' { ' + toCssString(styles.lg) + ' }', styles.xl && theme.MEDIA_XL_UP + ' { ' + toCssString(styles.xl) + ' }'); 341 | } 342 | 343 | return []; 344 | } 345 | 346 | /** 347 | * withSpacing 348 | */ 349 | var spacingPropTypes = { 350 | margin: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), 351 | padding: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]) 352 | }; 353 | exports.spacingPropTypes = spacingPropTypes; 354 | function withSpacing(props) { 355 | // prettier-ignore 356 | return (0, _styledComponents.css)(_templateObject8, props.margin && cssSpacing('margin', props.margin), props.padding && cssSpacing('padding', props.padding)); 357 | } -------------------------------------------------------------------------------- /lib/withWindow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 10 | 11 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 12 | 13 | var _react = require('react'); 14 | 15 | var _react2 = _interopRequireDefault(_react); 16 | 17 | var _utils = require('./utils'); 18 | 19 | var _THEME = require('./THEME'); 20 | 21 | var _THEME2 = _interopRequireDefault(_THEME); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 24 | 25 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 26 | 27 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 28 | 29 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 30 | 31 | var defaultMedia = 'xs'; 32 | 33 | function getCurrentMedia(ranges) { 34 | var width = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 35 | 36 | var media = defaultMedia; 37 | Object.keys(ranges).some(function (key) { 38 | var _ranges$key = _slicedToArray(ranges[key], 2), 39 | min = _ranges$key[0], 40 | max = _ranges$key[1]; 41 | 42 | if (width >= min && width <= max) { 43 | media = key; 44 | return true; 45 | } 46 | return false; 47 | }); 48 | return media; 49 | } 50 | 51 | function getWindowSize() { 52 | var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 53 | var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; 54 | return { width: width, height: height }; 55 | } 56 | 57 | /** 58 | * withWindow 59 | * HOC to supply current 'media' breakpoint and 'window' sizing the enhanced component 60 | * Value of 'media' will be one of: 'xs' (default), 'sm', 'md', 'lg', 'xl' 61 | */ 62 | var withWindow = function withWindow(EnhancedComponent) { 63 | var userTheme = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 64 | 65 | var _THEME$userTheme = _extends({}, _THEME2.default, userTheme), 66 | MEDIA_SM = _THEME$userTheme.MEDIA_SM, 67 | MEDIA_MD = _THEME$userTheme.MEDIA_MD, 68 | MEDIA_LG = _THEME$userTheme.MEDIA_LG, 69 | MEDIA_XL = _THEME$userTheme.MEDIA_XL; 70 | 71 | var mediaRanges = { 72 | xs: [0, MEDIA_SM - 1], 73 | sm: [MEDIA_SM, MEDIA_MD - 1], 74 | md: [MEDIA_MD, MEDIA_LG - 1], 75 | lg: [MEDIA_LG, MEDIA_XL - 1], 76 | xl: [MEDIA_XL, Infinity] 77 | }; 78 | 79 | return function (_React$Component) { 80 | _inherits(_class2, _React$Component); 81 | 82 | function _class2() { 83 | var _ref; 84 | 85 | var _temp, _this, _ret; 86 | 87 | _classCallCheck(this, _class2); 88 | 89 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 90 | args[_key] = arguments[_key]; 91 | } 92 | 93 | return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = _class2.__proto__ || Object.getPrototypeOf(_class2)).call.apply(_ref, [this].concat(args))), _this), _this.state = { 94 | media: defaultMedia, 95 | window: { width: 0, height: 0 } 96 | }, _this.handleResize = function () { 97 | var window = getWindowSize(); 98 | var media = getCurrentMedia(mediaRanges, window.width); 99 | _this.setState({ media: media, window: window }); 100 | }, _temp), _possibleConstructorReturn(_this, _ret); 101 | } 102 | 103 | _createClass(_class2, [{ 104 | key: 'componentDidMount', 105 | value: function componentDidMount() { 106 | if (window) { 107 | (0, _utils.throttleEvent)('resize', 'throttledWindowResize', window); 108 | window.addEventListener('throttledWindowResize', this.handleResize); 109 | this.handleResize(); 110 | } 111 | } 112 | }, { 113 | key: 'componentWillUnmount', 114 | value: function componentWillUnmount() { 115 | if (window) { 116 | window.removeEventListener('throttledWindowResize', this.handleResize); 117 | } 118 | } 119 | }, { 120 | key: 'render', 121 | value: function render() { 122 | return _react2.default.createElement(EnhancedComponent, _extends({ 123 | media: this.state.media, 124 | windowSize: this.state.window 125 | }, this.props)); 126 | } 127 | }]); 128 | 129 | return _class2; 130 | }(_react2.default.Component); 131 | }; 132 | 133 | exports.default = withWindow; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-super-styled", 3 | "version": "0.7.1", 4 | "description": "Responsive JSX layouts with Styled Components", 5 | "author": "moarwick", 6 | "license": "MIT", 7 | "keywords": [ 8 | "react", 9 | "react-component", 10 | "styled-components", 11 | "layout", 12 | "library", 13 | "css" 14 | ], 15 | "main": "lib/index.js", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/moarwick/react-super-styled" 19 | }, 20 | "bugs": "https://github.com/moarwick/react-super-styled/issues", 21 | "browserify": { 22 | "transform": [ 23 | "reactify" 24 | ] 25 | }, 26 | "scripts": { 27 | "build": "webpack --mode production", 28 | "start": "webpack-dev-server --open --hot", 29 | "predeploy": "npm run build", 30 | "deploy": "gh-pages -d demo", 31 | "precommit": "lint-staged", 32 | "postcommit": "git reset", 33 | "_dist": "rimraf lib && babel src/lib --out-dir lib", 34 | "dist": "rimraf lib && babel src/lib --out-dir lib --no-babelrc --presets=env,stage-0,react --plugins=transform-es2015-modules-commonjs", 35 | "prepublishOnly": "npm run dist", 36 | "test": "jest --watch --no-cache" 37 | }, 38 | "devDependencies": { 39 | "babel-cli": "^6.26.0", 40 | "babel-core": "^6.26.3", 41 | "babel-eslint": "^8.2.5", 42 | "babel-loader": "^7.1.4", 43 | "babel-plugin-styled-components": "^1.5.1", 44 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", 45 | "babel-preset-env": "^1.7.0", 46 | "babel-preset-react": "^6.24.1", 47 | "babel-preset-stage-0": "^6.24.1", 48 | "clean-webpack-plugin": "^0.1.19", 49 | "eslint": "^5.0.1", 50 | "eslint-config-airbnb": "^17.0.0", 51 | "eslint-config-prettier": "^2.6.0", 52 | "eslint-plugin-import": "^2.13.0", 53 | "eslint-plugin-jsx-a11y": "^6.0.3", 54 | "eslint-plugin-node": "^6.0.1", 55 | "eslint-plugin-prettier": "^2.6.1", 56 | "eslint-plugin-promise": "^3.8.0", 57 | "eslint-plugin-react": "^7.10.0", 58 | "gh-pages": "^1.2.0", 59 | "html-webpack-plugin": "^3.2.0", 60 | "husky": "^0.14.3", 61 | "jest": "^23.2.0", 62 | "lint-staged": "^7.2.0", 63 | "prettier": "^1.13.7", 64 | "progress-bar-webpack-plugin": "^1.11.0", 65 | "prop-types": "^15.6.2", 66 | "react": "^16.6.3", 67 | "react-dom": "^16.6.3", 68 | "react-hot-loader": "^4.3.12", 69 | "react-live": "^1.10.1", 70 | "rimraf": "^2.6.2", 71 | "styled-components": "^4.1.2", 72 | "webpack": "^4.14.0", 73 | "webpack-cli": "^3.0.8", 74 | "webpack-dev-server": "3.1.4" 75 | }, 76 | "peerDependencies": { 77 | "react": ">= 16.0", 78 | "react-dom": ">= 16.0", 79 | "styled-components": ">= 4.0" 80 | }, 81 | "lint-staged": { 82 | "src/**/*.js": [ 83 | "prettier --single-quote --print-width 100 --trailing-comma es5 --write", 84 | "git add" 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { css } from 'styled-components'; 3 | import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live'; 4 | 5 | import { 6 | Block, 7 | Display, 8 | Flex, 9 | FlexItem, 10 | Heading, 11 | Rule, 12 | Section, 13 | Span, 14 | Text, 15 | WindowSize, 16 | SvgIcon, 17 | } from './lib/index'; 18 | import ComponentDemo, { renderPropTypesColumns, styles as editorStyles } from './ComponentDemo'; 19 | import DEMO, { getPropTypes } from './demoData'; 20 | import { version } from '../package.json'; 21 | 22 | const CONTAINER_WIDTH = 1280; 23 | 24 | const RssLogo = () => ( 25 | 26 | 27 | 28 | 32 | 36 | 37 | 38 | ); 39 | 40 | const GitHubLogo = () => ( 41 | 42 | 51 | 52 | ); 53 | 54 | const RssLogoWrapper = styled.span` 55 | position: absolute; 56 | left: 0; 57 | top: 12px; 58 | `; 59 | 60 | const GitHubLogoWrapper = styled.span` 61 | position: absolute; 62 | right: 0; 63 | top: 12px; 64 | `; 65 | 66 | const Header = styled.a` 67 | color: #676; 68 | display: block; 69 | text-align: center; 70 | text-decoration: none; 71 | `; 72 | 73 | export const Code = styled.code` 74 | color: ${props => props.color || 'firebrick'}; 75 | font-size: 1.6rem; 76 | `; 77 | 78 | const styles = { 79 | section: css` 80 | background-color: #fff; 81 | box-shadow: 1px 1px 10px 0 rgba(0, 100, 0, 0.2); 82 | margin-bottom: 20px; 83 | `, 84 | title: { 85 | xs: css` 86 | font-family: 'Racing Sans One', cursive; 87 | font-size: 3.2rem; 88 | margin: 0; 89 | `, 90 | md: css` 91 | font-size: 4.8rem; 92 | `, 93 | }, 94 | }; 95 | 96 | const IconClose = props => ( 97 | 98 | 99 | 100 | ); 101 | 102 | const SvgIconCode = ` 103 | 104 | 105 | 106 | 107 | `; 108 | 109 | export default class App extends React.Component { 110 | constructor() { 111 | super(); 112 | console.time('Mounted In'); 113 | } 114 | 115 | componentDidMount() { 116 | console.timeEnd('Mounted In'); 117 | } 118 | 119 | render() { 120 | return ( 121 |

122 | 123 |
124 | 125 | < ReactSuperStyled /> 126 | 127 | 128 | 129 | 130 |    131 | {version} 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Responsive JSX layouts with Styled Components 142 | 143 |
144 |
145 | 146 | 147 | 148 | 149 | 150 | Wrappers » 151 | 152 | {DEMO.WRAPPERS.map((demoProps, index) => ( 153 | 154 | ))} 155 | 156 | 157 | Grid » 158 | 159 | {DEMO.GRID.map((demoProps, index) => ( 160 | 161 | ))} 162 | 163 | 164 | Typography » 165 | 166 | {DEMO.TYPOGRAPHY.map((demoProps, index) => ( 167 | 168 | ))} 169 | 170 | 171 | Misc » 172 | 173 | {DEMO.MISC.map((demoProps, index) => ( 174 | 175 | ))} 176 | 177 |
178 | 179 | 180 | SvgIcon   181 | 182 | A highly-configurable SVG wrapper for icon components. 183 | 184 | 185 | 186 | 187 | {renderPropTypesColumns(getPropTypes(SvgIcon))} 188 | 189 | 190 | 191 | 192 | 193 | Renders a simple SVG or a more complex SPAN-wrapped structure (depending on props), 194 | expecting inner SVG content as children. To create an icon component, wrap any SVG 195 | content with SvgIcon and pass in viewBox (if different 196 | from the default viewBox="0 0 24 24"). 197 | 198 |
199 |                 {`const IconClose = props => (
200 |   
201 |     
202 |   
203 | );`}
204 |               
205 |
206 | 207 | 208 | 209 | 210 | 211 | 212 | 217 | 218 | 219 | 220 | 221 |
222 | 223 | {/* WindowSize */} 224 | 225 |
226 | 227 | 228 | WindowSize   229 | 230 | 231 | Wrapper to to supply the current windowSize (object) and{' '} 232 | media breakpoint (string) to any component as props. 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | {renderPropTypesColumns(getPropTypes(WindowSize))} 241 | 242 | 243 | 244 | 245 | 246 | Can be applied through "render props" (children as a function), or as a 247 | "cloneElement" wrapper 248 | 249 | 250 |
251 |                 {`
252 |    { ({ media, windowSize }) =>  }
253 | 
254 | 
255 | 
256 |   
257 | `}
258 |               
259 | 260 | 261 | {({ media, windowSize }) => ( 262 |
263 | 264 | media: {media}     265 | windowSize:{' '} 266 | {JSON.stringify(windowSize, null, 2)} 267 | 268 |
269 | )} 270 |
271 |
272 |
273 |
274 |
275 | ); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/ComponentDemo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { css } from 'styled-components'; 3 | import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live'; 4 | import { Block, Flex, FlexItem, Heading, Rule, Section, Text, Span } from './lib/index'; 5 | 6 | export const Code = styled.code` 7 | color: firebrick; 8 | font-weight: bold; 9 | font-size: 1.4rem; 10 | `; 11 | 12 | export const styles = { 13 | section: css` 14 | background-color: #fff; 15 | box-shadow: 1px 1px 10px 0 rgba(0, 100, 0, 0.2); 16 | margin-bottom: 2rem; 17 | `, 18 | propNames: css` 19 | background: linear-gradient(to right, #eee, #bbb); 20 | `, 21 | editor: { 22 | overflowX: 'hidden', 23 | height: '100%', 24 | }, 25 | preview: css` 26 | background-color: #1d1f21; 27 | `, 28 | }; 29 | 30 | export function renderPropTypesColumns(list) { 31 | return list.map(propValPair => ( 32 | 33 | 34 | {propValPair[0]}:  35 | 36 | {propValPair[1]} 37 | 38 | 39 | 40 | )); 41 | } 42 | 43 | const ComponentDemo = props => { 44 | if (!Object.keys(props).length) return null; 45 | 46 | const { code, name, description, propTypesList, scope } = props; 47 | const renderedPropTypesList = renderPropTypesColumns(propTypesList); 48 | 49 | return ( 50 |
51 | 52 | 53 | {name}   54 | 55 | {description} 56 | 57 | 58 | 59 | 60 | {renderedPropTypesList} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 74 | 75 | 76 | 77 | 78 |
79 | ); 80 | }; 81 | 82 | export default ComponentDemo; 83 | -------------------------------------------------------------------------------- /src/__tests__/Display.test.js: -------------------------------------------------------------------------------- 1 | import { getCss } from '../lib/Display'; 2 | import theme from '../../lib/THEME'; 3 | 4 | const filterOutEmpties = list => list.filter(el => el.trim()); 5 | 6 | describe('getCss', () => { 7 | let props; 8 | let expected; 9 | 10 | beforeEach(() => { 11 | props = { theme }; 12 | }); 13 | 14 | it('returns default rules', () => { 15 | expected = []; 16 | expect(filterOutEmpties(getCss(props))).toEqual(expected); 17 | }); 18 | 19 | it('returns initial (xs) hide rule', () => { 20 | props = { ...props, hide: true }; 21 | expected = ['display: none;']; 22 | expect(filterOutEmpties(getCss(props))).toEqual(expected); 23 | }); 24 | 25 | it('returns initial (xs) show rule', () => { 26 | props = { ...props, show: 'block' }; 27 | expected = ['display: block;']; 28 | expect(filterOutEmpties(getCss(props))).toEqual(expected); 29 | }); 30 | 31 | it('returns "delayed" hide rule', () => { 32 | props = { ...props, hide: { xl: true } }; 33 | expected = ['@media only screen and (min-width: 1200px) { display: none; }']; 34 | expect(filterOutEmpties(getCss(props))).toEqual(expected); 35 | }); 36 | 37 | it('returns "delayed" show rule', () => { 38 | props = { ...props, show: { sm: true } }; 39 | expected = ['display: none;', '@media only screen and (min-width: 576px) { display: inline; }']; 40 | expect(filterOutEmpties(getCss(props))).toEqual(expected); 41 | }); 42 | 43 | it('returns multiple show -> hide -> show rules', () => { 44 | props = { ...props, hide: { sm: true }, show: { md: true } }; 45 | expected = [ 46 | '@media only screen and (min-width: 576px) { display: none; }', 47 | '@media only screen and (min-width: 768px) { display: inline; }', 48 | ]; 49 | expect(filterOutEmpties(getCss(props))).toEqual(expected); 50 | }); 51 | 52 | it('returns multiple hide -> show -> hide rules', () => { 53 | props = { ...props, show: { sm: 'block' }, hide: { lg: true } }; 54 | expected = [ 55 | 'display: none;', 56 | '@media only screen and (min-width: 576px) { display: block; }', 57 | '@media only screen and (min-width: 992px) { display: none; }', 58 | ]; 59 | expect(filterOutEmpties(getCss(props))).toEqual(expected); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/__tests__/SvgIcon.test.js: -------------------------------------------------------------------------------- 1 | import { parseDimensions, parseViewBoxRatio } from '../lib/SvgIcon'; 2 | 3 | describe('parseViewBoxRatio', () => { 4 | it('derives correct ratio', () => { 5 | expect(parseViewBoxRatio()).toEqual(1); 6 | expect(parseViewBoxRatio('0 0 foo bar')).toBe(1); 7 | expect(parseViewBoxRatio('0 0 100 100')).toBe(1); 8 | expect(parseViewBoxRatio('0 0 10 100')).toBe(0.1); 9 | expect(parseViewBoxRatio('0 0 100 10')).toBe(10); 10 | }); 11 | }); 12 | 13 | describe('parseDimensions', () => { 14 | it('derives correct dimensions, with 1:1 viewBox', () => { 15 | expect(parseDimensions(undefined, undefined, '0 0 100 100')).toEqual([1.4, 1.4]); // default 16 | expect(parseDimensions(undefined, 10, '0 0 100 100')).toEqual([10, 10]); 17 | expect(parseDimensions(10, undefined, '0 0 100 100')).toEqual([10, 10]); 18 | }); 19 | 20 | it('derives correct dimensions, with arbitrary viewBox', () => { 21 | expect(parseDimensions(undefined, undefined, '0 0 100 50')).toEqual([2.8, 1.4]); // default 22 | expect(parseDimensions(undefined, 10, '0 0 100 50')).toEqual([20, 10]); 23 | expect(parseDimensions(10, undefined, '0 0 100 50')).toEqual([10, 5]); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/utils/cssStringUtils.test.js: -------------------------------------------------------------------------------- 1 | import { ensureSemi, toCssString } from '../../lib/utils'; 2 | 3 | describe('ensureSemi', () => { 4 | it('ensures string is trimmed, terminated with semicolon', () => { 5 | expect(ensureSemi('foo; ')).toBe('foo;'); 6 | expect(ensureSemi(' foo ')).toBe('foo;'); 7 | }); 8 | 9 | it('returns empty string when only spaces or semi', () => { 10 | expect(ensureSemi(' ')).toBe(''); 11 | expect(ensureSemi(' ; ')).toBe(''); 12 | }); 13 | }); 14 | 15 | describe('toCssString', () => { 16 | it('handles strings, ensures terminated with semi', () => { 17 | expect(toCssString('foo; ')).toBe('foo;'); 18 | expect(toCssString(' foo ')).toBe('foo;'); 19 | }); 20 | 21 | it('handles arrays of strings, ensures all (actual) rules terminated with semi', () => { 22 | expect(toCssString([' foo ', ' ', 'bar; ', ' ; '])).toBe('foo;bar;'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/__tests__/utils/resolveUnits.test.js: -------------------------------------------------------------------------------- 1 | import { resolveUnits } from '../../lib/utils'; 2 | 3 | describe('resolveUnits', () => { 4 | it('parses recognized units', () => { 5 | expect(resolveUnits('10px')).toBe('px'); 6 | expect(resolveUnits('2.5rem')).toBe('rem'); 7 | expect(resolveUnits('24%')).toBe('%'); 8 | }); 9 | 10 | it('defaults to rem when value is a number', () => { 11 | expect(resolveUnits(100)).toBe('rem'); 12 | }); 13 | 14 | it('defaults to rem on other types', () => { 15 | expect(resolveUnits('10')).toBe('rem'); 16 | expect(resolveUnits()).toBe('rem'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/__tests__/utils/withMediaColumns.test.js: -------------------------------------------------------------------------------- 1 | import { toMediaColumnCss, withMediaColumns } from '../../lib/utils'; 2 | import THEME from '../../lib/THEME'; 3 | 4 | const filterOutEmpties = list => list.filter(el => el.trim()); 5 | const withMediaColumnsFiltered = props => filterOutEmpties(withMediaColumns(props)); 6 | 7 | describe('toMediaColumnCss', () => { 8 | let breakpoint; 9 | let col; 10 | let offset; 11 | let gutter; 12 | 13 | beforeEach(() => { 14 | breakpoint = null; 15 | col = 12 / 12; 16 | offset = 0; 17 | gutter = 0; 18 | }); 19 | 20 | it('delivers correct rules per col', () => { 21 | expect(toMediaColumnCss(breakpoint, col)).toBe('margin-left: 0; margin-right: 0; width: 100%;'); 22 | 23 | col = 6 / 12; 24 | expect(toMediaColumnCss(breakpoint, col)).toBe('margin-left: 0; margin-right: 0; width: 50%;'); 25 | 26 | col = 3 / 12; 27 | expect(toMediaColumnCss(breakpoint, col)).toBe('margin-left: 0; margin-right: 0; width: 25%;'); 28 | }); 29 | 30 | it('delivers correct rules per offset', () => { 31 | col = 3 / 12; 32 | offset = 6 / 12; 33 | expect(toMediaColumnCss(breakpoint, col, offset)).toBe( 34 | 'margin-left: 50%; margin-right: 0; width: 25%;' 35 | ); 36 | }); 37 | 38 | it('delivers correct rules per gutter, rem units, no offset', () => { 39 | col = 12 / 12; 40 | offset = 0; 41 | gutter = 3; 42 | expect(toMediaColumnCss(breakpoint, col, offset, gutter)).toBe( 43 | 'margin-left: 1.5rem; margin-right: 1.5rem; width: calc(100% - 3rem);' 44 | ); 45 | }); 46 | 47 | it('delivers correct rules per gutter, px units, no offset', () => { 48 | col = 12 / 12; 49 | offset = 0; 50 | gutter = '20px'; 51 | expect(toMediaColumnCss(breakpoint, col, offset, gutter)).toBe( 52 | 'margin-left: 10px; margin-right: 10px; width: calc(100% - 20px);' 53 | ); 54 | }); 55 | 56 | it('delivers correct media rules with breakpoint', () => { 57 | breakpoint = THEME.MEDIA_SM_UP; 58 | col = 12 / 12; 59 | expect(toMediaColumnCss(breakpoint, col, offset, gutter)).toBe( 60 | '@media only screen and (min-width: 576px) { margin-left: 0; margin-right: 0; width: 100%; }' 61 | ); 62 | }); 63 | }); 64 | 65 | describe('withMediaColumns', () => { 66 | let props; 67 | let expected; 68 | 69 | beforeEach(() => { 70 | props = { 71 | col: 12 / 12, 72 | offset: 0, 73 | gutter: 0, 74 | theme: THEME, 75 | }; 76 | }); 77 | 78 | it('delivers correct rules, no grid props', () => { 79 | props = { theme: THEME }; 80 | expect(withMediaColumnsFiltered(props)).toEqual([]); 81 | }); 82 | 83 | it('delivers correct rules, base props (no media)', () => { 84 | expected = ['margin-left: 0; margin-right: 0; width: 100%;']; 85 | expect(withMediaColumnsFiltered(props)).toEqual(expected); 86 | 87 | props = { ...props, col: 6 / 12, offset: 3 / 12, gutter: '2rem' }; 88 | expected = ['margin-left: calc(25% + 1rem); margin-right: 1rem; width: calc(50% - 2rem);']; 89 | expect(withMediaColumnsFiltered(props)).toEqual(expected); 90 | }); 91 | 92 | it('delivers correct rules, with media', () => { 93 | props = { ...props, col: { xs: 12 / 12, sm: 6 / 12 } }; 94 | expected = [ 95 | 'margin-left: 0; margin-right: 0; width: 100%;', 96 | '@media only screen and (min-width: 576px) { margin-left: 0; margin-right: 0; width: 50%; }', 97 | ]; 98 | expect(withMediaColumnsFiltered(props)).toEqual(expected); 99 | 100 | props = { 101 | ...props, 102 | col: { xs: 12 / 12, sm: 6 / 12, lg: 3 / 12 }, 103 | offset: { sm: 3 / 12, lg: 4.5 / 12 }, 104 | }; 105 | expected = [ 106 | 'margin-left: 0; margin-right: 0; width: 100%;', 107 | '@media only screen and (min-width: 576px) { margin-left: 25%; margin-right: 0; width: 50%; }', 108 | '@media only screen and (min-width: 992px) { margin-left: 37.5%; margin-right: 0; width: 25%; }', 109 | ]; 110 | expect(withMediaColumnsFiltered(props)).toEqual(expected); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /src/__tests__/utils/withMediaGutters.test.js: -------------------------------------------------------------------------------- 1 | import { toMediaGuttersCss, withMediaGutters } from '../../lib/utils'; 2 | import THEME from '../../lib/THEME'; 3 | 4 | const filterOutEmpties = list => list.filter(el => el.trim()); 5 | const withMediaGuttersFiltered = props => filterOutEmpties(withMediaGutters(props)); 6 | 7 | describe('toMediaGuttersCss', () => { 8 | let breakpoint; 9 | let gutter; 10 | let expected; 11 | 12 | beforeEach(() => { 13 | breakpoint = null; 14 | gutter = 0; 15 | }); 16 | 17 | it('delivers correct rules with no/zero breakpoint, gutter', () => { 18 | expected = 'margin-left: 0; margin-right: 0;'; 19 | expect(toMediaGuttersCss()).toBe(expected); 20 | expect(toMediaGuttersCss(breakpoint)).toBe(expected); 21 | expect(toMediaGuttersCss(breakpoint, gutter)).toBe(expected); 22 | }); 23 | 24 | it('delivers correct rules with gutter (different units)', () => { 25 | gutter = 1; // nums interpreted as rems 26 | expect(toMediaGuttersCss(breakpoint, gutter)).toBe( 27 | 'margin-left: -0.5rem; margin-right: -0.5rem;' 28 | ); 29 | 30 | gutter = '10px'; 31 | expect(toMediaGuttersCss(breakpoint, gutter)).toBe('margin-left: -5px; margin-right: -5px;'); 32 | }); 33 | 34 | it('delivers correct rules with breakpoints', () => { 35 | breakpoint = THEME.MEDIA_SM_UP; 36 | expect(toMediaGuttersCss(breakpoint, gutter)).toBe( 37 | '@media only screen and (min-width: 576px) { margin-left: 0; margin-right: 0; }' 38 | ); 39 | 40 | breakpoint = THEME.MEDIA_LG_UP; 41 | gutter = 2; 42 | expect(toMediaGuttersCss(breakpoint, gutter)).toBe( 43 | '@media only screen and (min-width: 992px) { margin-left: -1rem; margin-right: -1rem; }' 44 | ); 45 | }); 46 | }); 47 | 48 | describe('withMediaGutters', () => { 49 | let props; 50 | let expected; 51 | 52 | beforeEach(() => { 53 | props = { 54 | gutter: 0, 55 | theme: THEME, 56 | }; 57 | }); 58 | 59 | it('delivers correct rules, no media', () => { 60 | expected = ['margin-left: 0; margin-right: 0;']; 61 | expect(withMediaGuttersFiltered(props)).toEqual(expected); 62 | 63 | props = { ...props, gutter: 2 }; 64 | expected = ['margin-left: -1rem; margin-right: -1rem;']; 65 | expect(withMediaGuttersFiltered(props)).toEqual(expected); 66 | }); 67 | 68 | it('delivers correct rules, with media', () => { 69 | props = { ...props, gutter: { xs: 0 } }; 70 | expected = ['margin-left: 0; margin-right: 0;']; 71 | expect(withMediaGuttersFiltered(props)).toEqual(expected); 72 | 73 | props = { ...props, gutter: { xs: 0, sm: '30px' } }; 74 | expected = [ 75 | 'margin-left: 0; margin-right: 0;', 76 | '@media only screen and (min-width: 576px) { margin-left: -15px; margin-right: -15px; }', 77 | ]; 78 | expect(withMediaGuttersFiltered(props)).toEqual(expected); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /src/__tests__/utils/withStyles.test.js: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components'; 2 | import { withMediaStyles } from '../../lib/utils'; 3 | import theme from '../../lib/THEME'; 4 | 5 | const filterOutEmpties = list => list.filter(el => el.trim()); 6 | 7 | describe('withMediaStyles', () => { 8 | let props; 9 | let styles; 10 | let expected; 11 | 12 | beforeEach(() => { 13 | props = { styles: {}, theme }; 14 | }); 15 | 16 | it('handles styles as an empty string', () => { 17 | props = { ...props, styles: '' }; 18 | expect(withMediaStyles(props)).toEqual([]); 19 | }); 20 | 21 | it('handles styles as an empty array', () => { 22 | props = { ...props, styles: [] }; 23 | expect(withMediaStyles(props)).toEqual([]); 24 | }); 25 | 26 | it('handles styles as a string of CSS', () => { 27 | props = { ...props, styles: 'display: block; color: red;' }; 28 | expect(withMediaStyles(props)).toEqual(['display: block; color: red;']); 29 | }); 30 | 31 | it(`handles styles as an array of SC's rules`, () => { 32 | // prettier-ignore 33 | styles = css` 34 | display: block; 35 | color: red; 36 | ${true && 'cursor: pointer;'} 37 | `; 38 | props = { ...props, styles }; 39 | expect(withMediaStyles(props)).toEqual(['display:block;color:red;', 'cursor: pointer;']); 40 | }); 41 | 42 | it('handles styles as an empty object', () => { 43 | expect(withMediaStyles(props)).toEqual([' ', ' ', ' ', ' ']); 44 | }); 45 | 46 | it('handles styles as an object with media keys', () => { 47 | styles = { 48 | sm: css` 49 | display: block; 50 | cursor: pointer; 51 | `, // can be arrays (from SC's css method) 52 | lg: 'display: inline-block; color: blue', // or strings 53 | xs: css` 54 | display: inline; 55 | `, // keys out of order 56 | xl: undefined, // some empty 57 | md: 'color: red;', // some semis, some not.. 58 | gah: 'whoops!', // unsupported keys 59 | }; 60 | expected = [ 61 | 'display:inline;', 62 | '@media only screen and (min-width: 576px) { display:block;cursor:pointer; }', 63 | '@media only screen and (min-width: 768px) { color: red; }', 64 | '@media only screen and (min-width: 992px) { display: inline-block; color: blue; }', 65 | ]; 66 | props = { ...props, styles }; 67 | expect(filterOutEmpties(withMediaStyles(props))).toEqual(expected); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /src/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moarwick/react-super-styled/5b447d4dd5f11dae82ea4570457db5edd34622aa/src/assets/img/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moarwick/react-super-styled/5b447d4dd5f11dae82ea4570457db5edd34622aa/src/assets/logo.ai -------------------------------------------------------------------------------- /src/demoData.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { css } from 'styled-components'; 3 | import * as Components from './lib/index'; 4 | 5 | export const Code = styled.code` 6 | color: ${props => props.color || 'firebrick'}; 7 | font-size: 1.6rem; 8 | `; 9 | 10 | const TYPES = { 11 | ARRAY: 'Array', 12 | BOOL: 'Boolean', 13 | BOOL_OR_NUMBER: 'Boolean|Number', 14 | BOOL_OR_STRING: 'Boolean|String', 15 | FUNC: 'Function|Object', 16 | FUNC_OR_OBJECT: 'Function|Object', 17 | NODE: 'React Elem(s)', 18 | NUMBER: 'Number', 19 | NUMBER_OR_OBJECT: 'Number|Object', 20 | OBJECT: 'Object', 21 | STRING: 'String', 22 | STRING_OR_NUMBER: 'String|Number', 23 | STRING_OR_ARRAY_OF_CSS: 'String|Array (css)', 24 | STYLES: 'String|Array (css)|Object', 25 | }; 26 | 27 | const PROP_TYPES = { 28 | children: TYPES.NODE, 29 | theme: TYPES.OBJECT, 30 | 31 | hide: TYPES.BOOL, 32 | smHide: TYPES.BOOL, 33 | mdHide: TYPES.BOOL, 34 | lgHide: TYPES.BOOL, 35 | xlHide: TYPES.BOOL, 36 | 37 | show: TYPES.BOOL_OR_STRING, 38 | smShow: TYPES.BOOL_OR_STRING, 39 | mdShow: TYPES.BOOL_OR_STRING, 40 | lgShow: TYPES.BOOL_OR_STRING, 41 | xlShow: TYPES.BOOL_OR_STRING, 42 | 43 | container: TYPES.BOOL_OR_NUMBER, 44 | 45 | margin: TYPES.STRING_OR_NUMBER, 46 | padding: TYPES.STRING_OR_NUMBER, 47 | 48 | block: TYPES.BOOL, 49 | inline: TYPES.BOOL, 50 | inlineBlock: TYPES.BOOL, 51 | 52 | color: TYPES.STRING, 53 | lineHeight: TYPES.STRING_OR_NUMBER, 54 | 55 | roman: TYPES.BOOL, 56 | italic: TYPES.BOOL, 57 | oblique: TYPES.BOOL, 58 | underline: TYPES.BOOL, 59 | 60 | light: TYPES.BOOL, 61 | lighter: TYPES.BOOL, 62 | normal: TYPES.BOOL, 63 | bold: TYPES.BOOL, 64 | bolder: TYPES.BOOL, 65 | 66 | xxSmall: TYPES.BOOL, 67 | xSmall: TYPES.BOOL, 68 | small: TYPES.BOOL, 69 | smaller: TYPES.BOOL, 70 | medium: TYPES.BOOL, 71 | large: TYPES.BOOL, 72 | larger: TYPES.BOOL, 73 | xLarge: TYPES.BOOL, 74 | xxLarge: TYPES.BOOL, 75 | 76 | size: TYPES.STRING_OR_NUMBER, 77 | 78 | left: TYPES.BOOL, 79 | center: TYPES.BOOL, 80 | right: TYPES.BOOL, 81 | 82 | flexDirection: TYPES.STRING, 83 | flexWrap: TYPES.STRING, 84 | justifyContent: TYPES.STRING, 85 | alignItems: TYPES.STRING, 86 | alignContent: TYPES.STRING, 87 | 88 | alignSelf: TYPES.STRING, 89 | flex: TYPES.STRING, 90 | flexBasis: TYPES.STRING, 91 | flexGrow: TYPES.NUMBER, 92 | flexShrink: TYPES.NUMBER, 93 | order: TYPES.NUMBER, 94 | 95 | gutter: TYPES.NUMBER_OR_OBJECT, 96 | col: TYPES.NUMBER_OR_OBJECT, 97 | offset: TYPES.NUMBER_OR_OBJECT, 98 | 99 | styles: TYPES.STYLES, 100 | 101 | borderStyle: TYPES.STRING, 102 | colorTo: TYPES.STRING, 103 | height: TYPES.STRING_OR_NUMBER, 104 | 105 | innerRef: TYPES.FUNC_OR_OBJECT, 106 | width: TYPES.STRING_OR_NUMBER, 107 | stroke: TYPES.STRING_OR_NUMBER, 108 | inset: TYPES.STRING_OR_NUMBER, 109 | offsetX: TYPES.STRING_OR_NUMBER, 110 | offsetY: TYPES.STRING_OR_NUMBER, 111 | radius: TYPES.STRING_OR_NUMBER, 112 | border: TYPES.STRING, 113 | bgColor: TYPES.STRING, 114 | viewBox: TYPES.STRING, 115 | onClick: TYPES.FUNC, 116 | }; 117 | 118 | /** 119 | * Code examples and meta 120 | */ 121 | const DEMO = { 122 | BLOCK: { 123 | DESCRIPTION: 'Block wrapper, renders DIV tag.', 124 | CODE: ` 125 | 131 | I'm using margin & padding "shorthands". 132 | `, 133 | }, 134 | 135 | SECTION: { 136 | DESCRIPTION: 'Block variant, renders SECTION tag.', 137 | CODE: ` 138 |
148 | Watch me change styles at different <—> breakpoints! 149 |
`, 150 | }, 151 | 152 | ARTICLE: { 153 | DESCRIPTION: 'Block variant, renders ARTICLE tag.', 154 | CODE: ` 155 |
156 | I'm also just like Block, but more "semantic" ¯\\_(ツ)_/¯ 157 |
`, 158 | }, 159 | 160 | SPAN: { 161 | DESCRIPTION: SPAN wrapper, allows for typography and display controls., 162 | EXTRA_SCOPE: ['Block'], 163 | CODE: ` 164 | 165 | I will build a GREAT wall, 166 | and nobody builds walls better than me! 167 | – D. Trump 168 | `, 169 | }, 170 | 171 | FLEX: { 172 | DESCRIPTION: ( 173 | 174 | Flex "container", renders DIV tag. Supports standard flex props, plus a grid gutter. 175 | Defaults to 'wrap'. 176 | 177 | ), 178 | EXTRA_SCOPE: ['Text'], 179 | CODE: ` 180 | 181 | By gosh, I'm centered, even vertically! 182 | `, 183 | }, 184 | 185 | FLEXITEM: { 186 | DESCRIPTION: ( 187 | 188 | Flex "item" wrapper, renders DIV tag. Supports standard flex props, plus grid column props. 189 | 190 | ), 191 | EXTRA_SCOPE: ['Block', 'Flex'], 192 | CODE: ` 193 | 194 | 195 | 196 | 8 col - 10px gutter 197 | 198 | 199 | 4 col - 10px gutter 200 | 201 | 202 | 203 | 204 | 210 | 12 col (xs) → 2 col offset, 8 col (lg) 211 | 212 | 213 | 214 | 215 | 216 | 4 col (xs) → 4 col (lg) 217 | 218 | 219 | 8 col (xs) → 4 col (lg) 220 | 221 | 222 | 12 col (xs) → 4 col (lg) 223 | 224 | 225 | `, 226 | }, 227 | 228 | HEADING: { 229 | DESCRIPTION: ( 230 | 231 | Renders H1 tag. Use the as prop to apply H2, H3... 232 | 233 | ), 234 | CODE: ` 235 | 236 | Super Styled 237 | `, 238 | }, 239 | 240 | TEXT: { 241 | DESCRIPTION: 'Text paragraph, renders P tag.', 242 | CODE: ` 243 | 244 | Pack my box with five dozen liquor jugs. 245 | `, 246 | }, 247 | 248 | DISPLAY: { 249 | DESCRIPTION: 'Wrapper to show or hide children based on media breakpoints. Renders SPAN tag.', 250 | EXTRA_SCOPE: ['Span'], 251 | CODE: ` 252 | 253 | I'm shown as "inline" by default, up until LG. 254 | I appear at MD as "block". 255 | #MeToo! 256 | See me go hide at SM, back at XL! 257 | `, 258 | }, 259 | 260 | RULE: { 261 | DESCRIPTION: 'A "smarter" HR, just for fun. Apply border styles or gradients. Renders DIV tag.', 262 | EXTRA_SCOPE: ['Span'], 263 | CODE: ` 264 | 265 | dotted 266 | 267 | 268 | dashed 269 | 270 | 271 | gradient 272 | 273 | `, 274 | }, 275 | }; 276 | 277 | /** 278 | * Deliver component's propTypes as a list of [propName, propType] value pairs 279 | */ 280 | export function getPropTypes(Component) { 281 | return Object.keys((Component && Component.propTypes) || {}).map(propName => [ 282 | propName, 283 | PROP_TYPES[propName] || '???', 284 | ]); 285 | } 286 | 287 | /** 288 | * Deliver scope object, as required by React Live's LiveProvider 289 | * Always include the name'd component, adding any "EXTRA" components if specified in DEMO meta 290 | */ 291 | function getScopeForReactLive(name) { 292 | const baseScope = { [name]: Components[name] }; 293 | return (DEMO[name.toUpperCase()].EXTRA_SCOPE || []).reduce( 294 | (accum, compName) => ({ ...accum, [compName]: Components[compName] }), 295 | baseScope 296 | ); 297 | } 298 | 299 | /** 300 | * Deliver a data object for a given ComponentDemo component 301 | */ 302 | function getDemoComponentData(name) { 303 | const key = name.toUpperCase(); 304 | 305 | if (!DEMO[key]) return null; 306 | 307 | const Component = Components[name]; 308 | 309 | return { 310 | code: DEMO[key].CODE.trim(), 311 | name, 312 | description: DEMO[key].DESCRIPTION, 313 | propTypesList: getPropTypes(Component), 314 | scope: getScopeForReactLive(name), 315 | }; 316 | } 317 | 318 | export default { 319 | WRAPPERS: ['Block', 'Section', 'Article', 'Span'].map(name => getDemoComponentData(name)), 320 | TYPOGRAPHY: ['Heading', 'Text'].map(name => getDemoComponentData(name)), 321 | GRID: ['Flex', 'FlexItem'].map(name => getDemoComponentData(name)), 322 | MISC: ['Display', 'Rule'].map(name => getDemoComponentData(name)), 323 | }; 324 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React Styled Kit 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 | 22 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | import App from './App'; 5 | 6 | const render = Component => { 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | }; 14 | 15 | render(App); 16 | 17 | // Webpack Hot Module Replacement API 18 | if (module.hot) { 19 | module.hot.accept('./App', () => { 20 | render(require('./App')); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/Article.js: -------------------------------------------------------------------------------- 1 | import { 2 | basePropTypes, 3 | containerPropTypes, 4 | justifyPropTypes, 5 | spacingPropTypes, 6 | mediaStylesPropTypes, 7 | } from './utils'; 8 | 9 | import Block from './Block'; 10 | 11 | const propTypes = { 12 | ...basePropTypes, 13 | ...containerPropTypes, 14 | ...justifyPropTypes, 15 | ...spacingPropTypes, 16 | ...mediaStylesPropTypes, 17 | }; 18 | 19 | /** 20 | * Article block wrapper 21 | * Duplicates Block, renders
tag 22 | */ 23 | const Article = Block.withComponent('article'); 24 | Article.propTypes = propTypes; 25 | export default Article; 26 | -------------------------------------------------------------------------------- /src/lib/Block.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import { 3 | addTheme, 4 | basePropTypes, 5 | containerPropTypes, 6 | withContainer, 7 | justifyPropTypes, 8 | withJustify, 9 | spacingPropTypes, 10 | withSpacing, 11 | mediaStylesPropTypes, 12 | withMediaStyles, 13 | } from './utils'; 14 | 15 | /** 16 | * Block wrapper 17 | * Renders
tag 18 | */ 19 | const propTypes = { 20 | ...basePropTypes, 21 | ...containerPropTypes, 22 | ...justifyPropTypes, 23 | ...spacingPropTypes, 24 | ...mediaStylesPropTypes, 25 | }; 26 | 27 | // prettier-ignore 28 | const getCss = props => css` 29 | ${withContainer(props)} 30 | ${withJustify(props)} 31 | ${withSpacing(props)} 32 | ${withMediaStyles(props)} 33 | `; 34 | 35 | const Block = styled.div` 36 | ${props => getCss(addTheme(props))}; 37 | `; 38 | Block.propTypes = propTypes; 39 | export default Block; 40 | -------------------------------------------------------------------------------- /src/lib/Display.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import { addTheme, basePropTypes, displayPropTypes, toMediaObj } from './utils'; 3 | 4 | function toDisplayCss(hide, show) { 5 | if (hide) return 'display: none;'; 6 | if (typeof show === 'boolean' || !show) show = 'inline'; 7 | return `display: ${show};`; 8 | } 9 | 10 | /** 11 | * Wrapper to show/hide contents based on media breakpoints 12 | * Renders tag 13 | */ 14 | const propTypes = { 15 | ...basePropTypes, 16 | ...displayPropTypes, 17 | }; 18 | 19 | export function getCss({ hide, show, theme }) { 20 | const { xs: xsHide, sm: smHide, md: mdHide, lg: lgHide, xl: xlHide } = toMediaObj(hide || false); 21 | const { xs: xsShow, sm: smShow, md: mdShow, lg: lgShow, xl: xlShow } = toMediaObj(show || false); 22 | const breakpoints = [[smHide, smShow], [mdHide, mdShow], [lgHide, lgShow], [xlHide, xlShow]]; 23 | 24 | let isHideFirst = Boolean(xsHide); 25 | if (!isHideFirst) { 26 | breakpoints.some(([bHide, bShow]) => { 27 | if (bHide || bShow) { 28 | isHideFirst = !!bShow; 29 | return true; 30 | } 31 | return false; 32 | }); 33 | } 34 | 35 | return css` 36 | ${xsShow ? toDisplayCss(false, xsShow) : isHideFirst ? 'display: none;' : null} 37 | ${(smHide || smShow) && `${theme.MEDIA_SM_UP} { ${toDisplayCss(smHide, smShow)} }`} 38 | ${(mdHide || mdShow) && `${theme.MEDIA_MD_UP} { ${toDisplayCss(mdHide, mdShow)} }`} 39 | ${(lgHide || lgShow) && `${theme.MEDIA_LG_UP} { ${toDisplayCss(lgHide, lgShow)} }`} 40 | ${(xlHide || xlShow) && `${theme.MEDIA_XL_UP} { ${toDisplayCss(xlHide, xlShow)} }`} 41 | `; 42 | } 43 | 44 | const Display = styled.span` 45 | ${props => getCss(addTheme(props))}; 46 | `; 47 | Display.propTypes = propTypes; 48 | export default Display; 49 | -------------------------------------------------------------------------------- /src/lib/Flex.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled, { css } from 'styled-components'; 4 | import { 5 | addTheme, 6 | basePropTypes, 7 | gutterPropTypes, 8 | withMediaGutters, 9 | mediaStylesPropTypes, 10 | withMediaStyles, 11 | spacingPropTypes, 12 | withSpacing, 13 | } from './utils'; 14 | 15 | /** 16 | * Flex "container", to wrap FlexItems 17 | * Renders
18 | * https://scotch.io/tutorials/a-visual-guide-to-css3-flexbox-properties 19 | * 20 | * CSS Defaults: 21 | * flex-direction: row; 22 | * flex-wrap: nowrap; 23 | * justify-content: flex-start; 24 | * align-items: stretch; 25 | * align-content: stretch; 26 | */ 27 | const propTypes = { 28 | ...basePropTypes, 29 | inline: PropTypes.bool, 30 | flexDirection: PropTypes.oneOf(['row', 'row-reverse', 'column', 'column-reverse']), 31 | flexWrap: PropTypes.oneOf(['nowrap', 'wrap', 'wrap-reverse']), 32 | justifyContent: PropTypes.oneOf([ 33 | 'flex-start', 34 | 'flex-end', 35 | 'center', 36 | 'space-between', 37 | 'space-around', 38 | ]), 39 | alignItems: PropTypes.oneOf(['stretch', 'center', 'flex-start', 'flex-end', 'baseline']), 40 | alignContent: PropTypes.oneOf([ 41 | 'stretch', 42 | 'center', 43 | 'flex-start', 44 | 'flex-end', 45 | 'space-between', 46 | 'space-around', 47 | ]), 48 | ...spacingPropTypes, 49 | ...gutterPropTypes, 50 | ...mediaStylesPropTypes, 51 | }; 52 | 53 | // change flexWrap default for more grid-like behavior 54 | const defaultProps = { 55 | flexWrap: 'wrap', 56 | }; 57 | 58 | // prettier-ignore 59 | const getCss = props => css` 60 | display: ${props.inline ? 'inline-flex' : 'flex'}; 61 | flex-direction: ${props.flexDirection}; 62 | flex-wrap: ${props.flexWrap}; 63 | justify-content: ${props.justifyContent}; 64 | align-items: ${props.alignItems}; 65 | align-content: ${props.alignContent}; 66 | ${withSpacing(props)} 67 | ${withMediaStyles(props)} 68 | ${withMediaGutters(props)} // apply gutters last (overrides any prior left/right margins) 69 | `; 70 | 71 | const FlexStyled = styled.div` 72 | ${props => getCss(addTheme(props))}; 73 | `; 74 | 75 | function Flex(props) { 76 | const { children, gutter, smGutter, mdGutter, lgGutter } = props; 77 | 78 | // pass gutter props to any FlexItem children 79 | const childrenWithGutterProps = React.Children.map( 80 | children, 81 | child => 82 | child && child.type && child.type.displayName === 'FlexItem' 83 | ? React.cloneElement(child, { gutter, smGutter, mdGutter, lgGutter }) 84 | : child 85 | ); 86 | 87 | return {childrenWithGutterProps}; 88 | } 89 | 90 | Flex.propTypes = propTypes; 91 | Flex.defaultProps = defaultProps; 92 | export default Flex; 93 | -------------------------------------------------------------------------------- /src/lib/FlexItem.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled, { css } from 'styled-components'; 3 | import { 4 | addTheme, 5 | basePropTypes, 6 | columnPropTypes, 7 | withMediaColumns, 8 | spacingPropTypes, 9 | withSpacing, 10 | mediaStylesPropTypes, 11 | withMediaStyles, 12 | } from './utils'; 13 | 14 | /** 15 | * Flex item wrapper, with 12-column support, media breakpoints 16 | * Renders
17 | * https://scotch.io/tutorials/a-visual-guide-to-css3-flexbox-properties 18 | * https://www.w3.org/TR/styles-flexbox/#flex-common 19 | * 20 | * CSS Defaults: 21 | * order: 0; 22 | * align-self: auto; 23 | * flex: 0 1 auto; <-- recommended 'shorthand' for below props 24 | * 25 | * flex-grow: 0; <-- if any long-hands supplied, expects all three (do not mix) 26 | * flex-shrink: 1; 27 | * flex-basis: auto; 28 | */ 29 | const propTypes = { 30 | ...basePropTypes, 31 | alignSelf: PropTypes.oneOf(['auto', 'flex-start', 'flex-end', 'center', 'baseline', 'stretch']), 32 | flex: PropTypes.string, 33 | flexBasis: PropTypes.string, 34 | flexGrow: PropTypes.number, 35 | flexShrink: PropTypes.number, 36 | order: PropTypes.number, 37 | ...columnPropTypes, 38 | ...spacingPropTypes, 39 | ...mediaStylesPropTypes, 40 | }; 41 | 42 | function getCss({ flex, flexGrow, flexShrink, flexBasis, ...props }) { 43 | if (!flex) { 44 | flex = 45 | flexGrow || flexShrink || flexBasis 46 | ? `${flexGrow || 0} ${flexShrink || 1} ${flexBasis || 'auto'}` 47 | : 'initial'; // 0 1 auto 48 | } 49 | 50 | // prettier-ignore 51 | return css` 52 | box-sizing: border-box; 53 | flex: ${flex}; 54 | align-self: ${props.alignSelf || 'auto'}; 55 | order: ${props.order || 0}; 56 | ${withMediaColumns(props)} 57 | ${withSpacing(props)} 58 | ${withMediaStyles(props)} 59 | ` 60 | } 61 | 62 | const FlexItem = styled.div` 63 | ${props => getCss(addTheme(props))}; 64 | `; 65 | FlexItem.propTypes = propTypes; 66 | FlexItem.displayName = 'FlexItem'; 67 | export default FlexItem; 68 | -------------------------------------------------------------------------------- /src/lib/Heading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled, { css } from 'styled-components'; 4 | import { 5 | addTheme, 6 | cssSpacing, 7 | basePropTypes, 8 | fontPropTypes, 9 | withFont, 10 | justifyPropTypes, 11 | withJustify, 12 | mediaStylesPropTypes, 13 | withMediaStyles, 14 | } from './utils'; 15 | 16 | /** 17 | * Heading 18 | * Renders

tag 19 | */ 20 | const propTypes = { 21 | ...basePropTypes, 22 | color: PropTypes.string, 23 | lineHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 24 | margin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 25 | ...fontPropTypes, 26 | ...justifyPropTypes, 27 | ...mediaStylesPropTypes, 28 | }; 29 | 30 | const getCss = props => 31 | // prettier-ignore 32 | css` 33 | color: ${props.color}; 34 | line-height: ${props.lineHeight}; 35 | ${props.margin !== undefined && cssSpacing('margin', props)} 36 | ${withFont(props)} 37 | ${withJustify(props)} 38 | ${withMediaStyles(props)} 39 | `; 40 | 41 | const Heading = styled.h1` 42 | ${props => getCss(addTheme(props))}; 43 | `; 44 | 45 | Heading.propTypes = propTypes; 46 | export default Heading; 47 | -------------------------------------------------------------------------------- /src/lib/Rule.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled, { css } from 'styled-components'; 3 | import { 4 | addTheme, 5 | basePropTypes, 6 | cssSpacing, 7 | toCssUnits, 8 | mediaStylesPropTypes, 9 | withMediaStyles, 10 | } from './utils'; 11 | 12 | /** 13 | * A "smarter"
14 | * Renders
15 | */ 16 | const propTypes = { 17 | ...basePropTypes, 18 | borderStyle: PropTypes.string, 19 | color: PropTypes.string, 20 | colorTo: PropTypes.string, 21 | margin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 22 | height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 23 | ...mediaStylesPropTypes, 24 | }; 25 | 26 | const defaultProps = { 27 | borderStyle: 'solid', 28 | color: '#000', 29 | height: 0.1, 30 | }; 31 | 32 | const getCss = props => { 33 | const { borderStyle, color, height, margin } = props; 34 | 35 | const borderCss = borderStyle !== 'solid' ? `${toCssUnits(height)} ${borderStyle} ${color}` : ''; 36 | 37 | if (borderCss) { 38 | // prettier-ignore 39 | return css` 40 | border-top: ${borderCss}; 41 | ${margin && cssSpacing('margin', props)} 42 | ${withMediaStyles(props)}; 43 | ` 44 | } 45 | 46 | const colorTo = props.colorTo || color; 47 | const background = `linear-gradient(to right, ${color}, ${colorTo})`; 48 | 49 | // prettier-ignore 50 | return css` 51 | background: ${background}; 52 | height: ${toCssUnits(height)}; 53 | ${margin && cssSpacing('margin', margin)} 54 | ${withMediaStyles(props)}; 55 | ` 56 | }; 57 | 58 | const Rule = styled.div` 59 | ${props => getCss(addTheme(props))}; 60 | `; 61 | Rule.propTypes = propTypes; 62 | Rule.defaultProps = defaultProps; 63 | export default Rule; 64 | -------------------------------------------------------------------------------- /src/lib/Section.js: -------------------------------------------------------------------------------- 1 | import { 2 | basePropTypes, 3 | containerPropTypes, 4 | justifyPropTypes, 5 | spacingPropTypes, 6 | mediaStylesPropTypes, 7 | } from './utils'; 8 | 9 | import Block from './Block'; 10 | 11 | const propTypes = { 12 | ...basePropTypes, 13 | ...containerPropTypes, 14 | ...justifyPropTypes, 15 | ...spacingPropTypes, 16 | ...mediaStylesPropTypes, 17 | }; 18 | 19 | /** 20 | * Section block wrapper 21 | * Duplicates Block, renders
tag 22 | */ 23 | const Section = Block.withComponent('section'); 24 | Section.propTypes = propTypes; 25 | export default Section; 26 | -------------------------------------------------------------------------------- /src/lib/Span.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled, { css } from 'styled-components'; 3 | import { 4 | addTheme, 5 | basePropTypes, 6 | fontPropTypes, 7 | withFont, 8 | justifyPropTypes, 9 | withJustify, 10 | spacingPropTypes, 11 | withSpacing, 12 | mediaStylesPropTypes, 13 | withMediaStyles, 14 | } from './utils'; 15 | 16 | /** 17 | * Non-block wrapper 18 | * Renders tag 19 | */ 20 | const propTypes = { 21 | ...basePropTypes, 22 | color: PropTypes.string, 23 | block: PropTypes.bool, 24 | inlineBlock: PropTypes.bool, 25 | ...fontPropTypes, 26 | ...justifyPropTypes, 27 | ...spacingPropTypes, 28 | ...mediaStylesPropTypes, 29 | }; 30 | 31 | // prettier-ignore 32 | const getCss = props => css` 33 | color: ${props.color}; 34 | ${props.block && 'display: block;'} 35 | ${props.inlineBlock && 'display: inline-block;'} 36 | ${withFont(props)} 37 | ${withJustify(props)} 38 | ${withSpacing(props)} 39 | ${withMediaStyles(props)} 40 | `; 41 | 42 | const Span = styled.span` 43 | ${props => getCss(addTheme(props))}; 44 | `; 45 | Span.propTypes = propTypes; 46 | export default Span; 47 | -------------------------------------------------------------------------------- /src/lib/SvgIcon.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled, { css } from 'styled-components'; 4 | 5 | import { 6 | addTheme, 7 | basePropTypes, 8 | resolveUnits, 9 | toCssUnits, 10 | toNum, 11 | mediaStylesPropTypes, 12 | withMediaStyles, 13 | } from './utils'; 14 | 15 | // ----- CONSTANTS ----- 16 | 17 | export const DEFAULT_VIEWBOX_SIZE = 24; 18 | export const DEFAULT_VIEWBOX = `0 0 ${DEFAULT_VIEWBOX_SIZE} ${DEFAULT_VIEWBOX_SIZE}`; 19 | export const DEFAULT_ICON_SIZE = 1.4; 20 | const XMLNS = 'http://www.w3.org/2000/svg'; 21 | 22 | // ----- HELPER UTILS ----- 23 | 24 | /** 25 | * Calculate width-to-height aspect ratio from viewBox (default 1) 26 | */ 27 | export function parseViewBoxRatio(viewBox) { 28 | const coords = (viewBox || DEFAULT_VIEWBOX).split(' ').map(val => toNum(val)); 29 | if (coords.length !== 4) return 1; 30 | const [x1, y1, x2, y2] = coords; 31 | return (x2 - x1) / (y2 - y1) || 1; 32 | } 33 | 34 | /** 35 | * Derive height & width of SVG per its viewBox ratio (default DEFAULT_ICON_SIZE) 36 | */ 37 | export function parseDimensions(width, height, viewBox) { 38 | const ratio = parseViewBoxRatio(viewBox); 39 | 40 | if (!width && !height) height = DEFAULT_ICON_SIZE; 41 | if (height && !width) width = toNum(height) * ratio; 42 | if (width && !height) height = toNum(width) / ratio; 43 | 44 | return [width, height]; 45 | } 46 | 47 | // ----- SUB-COMPONENTS ----- 48 | 49 | const getWrapperCss = props => css` 50 | display: inline-block; 51 | position: relative; 52 | font-size: 0; 53 | height: ${props.outerHeight}; 54 | width: ${props.outerWidth}; 55 | ${withMediaStyles(props)}; 56 | `; 57 | 58 | const getBackgroundCss = props => css` 59 | display: inline-block; 60 | position: absolute; 61 | left: ${props.left}; 62 | top: ${props.top}; 63 | background-color: ${props.bgColor || 'transparent'}; 64 | ${props.border ? `border: ${props.border};` : ''} 65 | ${props.radius ? `border-radius: ${props.radius};` : ''} 66 | box-sizing: border-box; 67 | ${props.onClick ? 'cursor: pointer;' : ''} 68 | height: ${props.sizeX}; 69 | width: ${props.sizeY}; 70 | transition: all 0.25s ease; 71 | z-index: 1; 72 | `; 73 | 74 | const getSvgCss = props => css` 75 | position: absolute; 76 | left: ${props.left}; 77 | top: ${props.top}; 78 | cursor: ${props.cursor}; 79 | pointer-events: ${props.pointerEvents}; 80 | transition: all 0.25s ease; 81 | z-index: 2; 82 | `; 83 | 84 | const BasicSvg = styled.svg` 85 | ${props => withMediaStyles(props)}; 86 | `; 87 | 88 | const Wrapper = styled.span` 89 | ${props => getWrapperCss(props)}; 90 | `; 91 | 92 | const Background = styled.span` 93 | ${props => getBackgroundCss(props)}; 94 | `; 95 | 96 | const Svg = styled.svg` 97 | ${props => getSvgCss(props)}; 98 | `; 99 | 100 | // ----- MAIN COMPONENT ----- 101 | 102 | /** 103 | * SvgIcon 104 | * 105 | * An highly-configurable SVG content wrapper 106 | * Adapted from https://gist.github.com/moarwick/1229e9bd73ad52be73d54975cdac0d1e 107 | */ 108 | const propTypes = { 109 | // basic svg 110 | ...basePropTypes, 111 | innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), 112 | color: PropTypes.string, 113 | height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 114 | width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 115 | stroke: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 116 | viewBox: PropTypes.string, 117 | 118 | // complex, span-wrapped svg 119 | border: PropTypes.string, 120 | bgColor: PropTypes.string, 121 | inset: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 122 | offsetX: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 123 | offsetY: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 124 | onClick: PropTypes.func, 125 | radius: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 126 | ...mediaStylesPropTypes, 127 | }; 128 | 129 | const defaultProps = { 130 | color: '#000', 131 | inset: 0, 132 | height: 0, 133 | width: 0, 134 | viewBox: DEFAULT_VIEWBOX, 135 | theme: {}, 136 | }; 137 | 138 | const SvgIcon = props => { 139 | props = addTheme(props); 140 | let { 141 | innerRef, 142 | inset, 143 | offsetX, 144 | offsetY, 145 | radius, 146 | border, 147 | bgColor, 148 | children, 149 | color, 150 | onClick, 151 | stroke, 152 | viewBox, 153 | styles, 154 | ...attribs 155 | } = props; 156 | 157 | const units = resolveUnits(`${props.width} ${props.height} ${inset} ${offsetX} ${offsetY}`); 158 | const [width, height] = parseDimensions(toNum(props.width), toNum(props.height), viewBox); 159 | const widthPx = units === 'px' ? width : width * 10; // assume 1rem === 10px 160 | const heightPx = units === 'px' ? height : height * 10; // assume 1rem === 10px 161 | 162 | const isAdvanced = border || bgColor || radius || inset || offsetX || offsetY || onClick; 163 | 164 | // if "advanced" functionality not required, render a "basic" svg-only version 165 | if (!isAdvanced) { 166 | return ( 167 | 176 | 177 | {children} 178 | 179 | 180 | ); 181 | } 182 | 183 | // otherwise, render the full wrapper... 184 | inset = toNum(inset); 185 | offsetX = toNum(offsetX); 186 | offsetY = toNum(offsetY); 187 | 188 | const widthUnits = `${width}${units}`; 189 | const heightUnits = `${height}${units}`; 190 | const innerHeight = Math.max(0, height - inset * 2); 191 | const innerWidth = Math.max(0, width - inset * 2); 192 | const innerHeightPx = units === 'px' ? innerHeight : innerHeight * 10; 193 | const innerWidthPx = units === 'px' ? innerWidth : innerWidth * 10; 194 | 195 | if (radius) { 196 | const radiusUnits = resolveUnits(String(radius || '')); 197 | radius = `${toNum(radius)}${radiusUnits}`; 198 | } 199 | 200 | const isBackground = !!(bgColor || border || inset); 201 | 202 | return ( 203 | 210 | 211 | {/* sizing placeholder */} 212 | 213 | {isBackground && ( 214 | 224 | )} 225 | 235 | 236 | {children} 237 | 238 | 239 | 240 | ); 241 | }; 242 | 243 | SvgIcon.displayName = 'SvgIcon'; 244 | SvgIcon.propTypes = propTypes; 245 | SvgIcon.defaultProps = defaultProps; 246 | export default SvgIcon; 247 | -------------------------------------------------------------------------------- /src/lib/THEME.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Media Breakpoints 3 | */ 4 | const MEDIA_SM = 576; // SM range: 576 - 767 5 | const MEDIA_MD = 768; // MD range: 768 - 991 6 | const MEDIA_LG = 992; // LG range: 992 - 1199 7 | const MEDIA_XL = 1200; // XL range: 1200 - Infinity 8 | 9 | /** 10 | * Theming Variables 11 | */ 12 | const THEME = { 13 | CONTAINER_SMALL: 740, 14 | CONTAINER_MEDIUM: 960, 15 | CONTAINER_LARGE: 980, 16 | 17 | MEDIA_SM, 18 | MEDIA_MD, 19 | MEDIA_LG, 20 | MEDIA_XL, 21 | 22 | // applies at and above given breakpoint 23 | MEDIA_SM_UP: `@media only screen and (min-width: ${MEDIA_SM}px)`, 24 | MEDIA_MD_UP: `@media only screen and (min-width: ${MEDIA_MD}px)`, 25 | MEDIA_LG_UP: `@media only screen and (min-width: ${MEDIA_LG}px)`, 26 | MEDIA_XL_UP: `@media only screen and (min-width: ${MEDIA_XL}px)`, 27 | 28 | // applies below given breakpoint 29 | MEDIA_XS_DOWN: `@media only screen and (max-width: ${MEDIA_SM - 1}px)`, 30 | MEDIA_SM_DOWN: `@media only screen and (max-width: ${MEDIA_MD - 1}px)`, 31 | MEDIA_MD_DOWN: `@media only screen and (max-width: ${MEDIA_LG - 1}px)`, 32 | MEDIA_LG_DOWN: `@media only screen and (max-width: ${MEDIA_XL - 1}px)`, 33 | }; 34 | 35 | export default THEME; 36 | -------------------------------------------------------------------------------- /src/lib/Text.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled, { css } from 'styled-components'; 3 | import { 4 | addTheme, 5 | cssSpacing, 6 | basePropTypes, 7 | fontPropTypes, 8 | withFont, 9 | justifyPropTypes, 10 | withJustify, 11 | mediaStylesPropTypes, 12 | withMediaStyles, 13 | } from './utils'; 14 | 15 | /** 16 | * Text paragraph 17 | * Renders

tag 18 | */ 19 | const propTypes = { 20 | ...basePropTypes, 21 | color: PropTypes.string, 22 | lineHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 23 | margin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 24 | ...fontPropTypes, 25 | ...justifyPropTypes, 26 | ...mediaStylesPropTypes, 27 | }; 28 | 29 | // prettier-ignore 30 | const getCss = props => css` 31 | color: ${props.color}; 32 | line-height: ${props.lineHeight}; 33 | ${props.margin !== undefined && cssSpacing('margin', props)} 34 | ${withFont(props)} 35 | ${withJustify(props)} 36 | ${withMediaStyles(props)} 37 | ` 38 | 39 | const Text = styled.p` 40 | ${props => getCss(addTheme(props))}; 41 | `; 42 | Text.propTypes = propTypes; 43 | export default Text; 44 | -------------------------------------------------------------------------------- /src/lib/WindowSize.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { throttleEvent } from './utils'; 4 | import THEME from './THEME'; 5 | 6 | const MEDIA_DEFAULT = 'xs'; 7 | 8 | function getCurrentMedia(ranges, width = 0) { 9 | let media = MEDIA_DEFAULT; 10 | Object.keys(ranges).some(key => { 11 | const [min, max] = ranges[key]; 12 | if (width >= min && width <= max) { 13 | media = key; 14 | return true; 15 | } 16 | return false; 17 | }); 18 | return media; 19 | } 20 | 21 | function getWindowSize() { 22 | const width = 23 | window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 24 | const height = 25 | window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; 26 | return { width, height }; 27 | } 28 | 29 | /** 30 | * WindowSize 31 | * 32 | * Wrapper component to supply current 'window' sizing and 'media' breakpoint to the wrapped component 33 | * Value of 'media' will be one of: 'xs' (default), 'sm', 'md', 'lg', 'xl' 34 | * Supports "children as a function" 35 | */ 36 | const propTypes = { 37 | children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, 38 | theme: PropTypes.object, // optional theme media overrides 39 | }; 40 | 41 | export default class WindowSize extends React.Component { 42 | static propTypes = propTypes; 43 | 44 | constructor(props) { 45 | super(props); 46 | 47 | this.state = { 48 | media: MEDIA_DEFAULT, 49 | height: 0, 50 | width: 0, 51 | }; 52 | 53 | const { MEDIA_SM, MEDIA_MD, MEDIA_LG, MEDIA_XL } = { ...THEME, ...(props.theme || {}) }; 54 | 55 | this.mediaRanges = { 56 | xs: [0, MEDIA_SM - 1], 57 | sm: [MEDIA_SM, MEDIA_MD - 1], 58 | md: [MEDIA_MD, MEDIA_LG - 1], 59 | lg: [MEDIA_LG, MEDIA_XL - 1], 60 | xl: [MEDIA_XL, Infinity], 61 | }; 62 | } 63 | 64 | componentDidMount() { 65 | if (window) { 66 | throttleEvent('resize', 'throttledWindowResize', window); 67 | window.addEventListener('throttledWindowResize', this.handleResize); 68 | this.handleResize(); 69 | } 70 | } 71 | 72 | componentWillUnmount() { 73 | if (window) { 74 | window.removeEventListener('throttledWindowResize', this.handleResize); 75 | } 76 | } 77 | 78 | handleResize = () => { 79 | const { width, height } = getWindowSize(); 80 | const media = getCurrentMedia(this.mediaRanges, width); 81 | this.setState({ width, height, media }); 82 | }; 83 | 84 | render() { 85 | const { children } = this.props; 86 | const { width, height, media } = this.state; 87 | 88 | const windowProps = { 89 | media, 90 | windowSize: { width, height }, 91 | }; 92 | 93 | if (typeof children === 'function') { 94 | return children(windowProps); 95 | } 96 | 97 | return React.Children.map(children, child => React.cloneElement(child, windowProps)); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | export { default as Article } from './Article'; 2 | export { default as Block } from './Block'; 3 | export { default as Display } from './Display'; 4 | export { default as Flex } from './Flex'; 5 | export { default as FlexItem } from './FlexItem'; 6 | export { default as Heading } from './Heading'; 7 | export { default as Rule } from './Rule'; 8 | export { default as Section } from './Section'; 9 | export { default as Span } from './Span'; 10 | export { default as Text } from './Text'; 11 | export { default as SvgIcon } from './SvgIcon'; 12 | export { default as WindowSize } from './WindowSize'; 13 | export { default as withWindow } from './withWindow'; 14 | 15 | export { default as RSS_THEME } from './THEME'; 16 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import { css } from 'styled-components'; 3 | import THEME from './THEME'; 4 | 5 | /** 6 | * Extend the theme prop onto the base THEME, and return all props 7 | */ 8 | export function addTheme(props) { 9 | return { ...props, theme: props.theme ? { ...THEME, ...props.theme } : THEME }; 10 | } 11 | 12 | /** 13 | * Accept string, trim, return terminated with a semicolon 14 | * If input string contains all spaces or only ';', return empty string 15 | */ 16 | export function ensureSemi(val) { 17 | val = val.trim(); 18 | return val && val !== ';' ? (val.slice(-1) === ';' ? val : `${val};`) : ''; 19 | } 20 | 21 | /** 22 | * Parse value to determine units, make default assumptions based on type (unless default passed in) 23 | * Return string: px|rem|em|% 24 | */ 25 | export function resolveUnits(val, defaultUnits = '') { 26 | const match = String(val).match(/px|rem|em|%/g); 27 | return match ? match[0] : defaultUnits || 'rem'; 28 | } 29 | 30 | /** 31 | * Accept string or a list of strings (from SC), return a single string 32 | * Filter, trim, and ensure a semicolon delimiter 33 | */ 34 | export function toCssString(val) { 35 | if (typeof val === 'string') return ensureSemi(val); 36 | if (!Array.isArray(val)) return ''; 37 | return val 38 | .map(el => ensureSemi(el)) 39 | .join('') 40 | .trim(); 41 | } 42 | 43 | /** 44 | * Return strings as-is, coerce numbers to 'rem' (default '0rem') 45 | */ 46 | export function toCssUnits(val, units = 'rem') { 47 | return typeof val === 'string' ? val : typeof val === 'number' ? `${val}${units}` : '0rem'; 48 | } 49 | 50 | /** 51 | * Helper to ensure that value is a (media) object 52 | */ 53 | export function toMediaObj(val) { 54 | if (typeof val === 'number' || typeof val === 'string' || typeof val === 'boolean') 55 | return { xs: val }; 56 | if (typeof val === 'object' && Object.keys(val).length) return val; 57 | return {}; 58 | } 59 | 60 | /** 61 | * Parse input value into a number, with optional decimal precision 62 | * Always return a number, 0 if value cannot be parsed 63 | */ 64 | export function toNum(value, precision = 0) { 65 | value = parseFloat(value) || 0; 66 | return precision > 0 ? Number(value.toFixed(precision)) : value; 67 | } 68 | 69 | /** 70 | * Deliver CSS 'padding' or 'margin' rules derived from passed in prop 71 | * Numbers are assumed to be 'rem' 72 | */ 73 | export function cssSpacing(rule, value) { 74 | if (typeof value === 'number') value += 'rem'; 75 | return css` 76 | ${rule}: ${value}; 77 | `; 78 | } 79 | 80 | /** 81 | * Deliver negative left/right margin rules for FlexRow 82 | * This is to ensure outer columns (with gutters) are flush with the container 83 | */ 84 | export function toMediaGuttersCss(breakpoint, gutter) { 85 | const units = resolveUnits(gutter); 86 | gutter = toNum(gutter); 87 | const rule = gutter 88 | ? `margin-left: -${gutter / 2}${units}; margin-right: -${gutter / 2}${units};` 89 | : 'margin-left: 0; margin-right: 0;'; 90 | return breakpoint ? `${breakpoint} { ${rule} }` : rule; 91 | } 92 | 93 | /** 94 | * Deliver correct column width and left/right margins, per the supplied props 95 | */ 96 | export function toMediaColumnCss(breakpoint, col, offset, gutter) { 97 | const units = resolveUnits(gutter); 98 | gutter = toNum(gutter); 99 | col = toNum(col) * 100; 100 | offset = toNum(offset) * 100; 101 | const width = gutter ? `calc(${col}% - ${gutter}${units})` : `${col}%`; 102 | const marginRight = gutter ? `${gutter / 2}${units}` : '0'; 103 | const marginLeft = 104 | offset && gutter 105 | ? `calc(${offset}% + ${gutter / 2}${units})` 106 | : offset && !gutter 107 | ? `${offset}%` 108 | : gutter 109 | ? `${gutter / 2}${units}` 110 | : '0'; 111 | 112 | const rule = `margin-left: ${marginLeft}; margin-right: ${marginRight}; width: ${width};`; 113 | return breakpoint ? `${breakpoint} { ${rule} }` : rule; 114 | } 115 | 116 | /** 117 | * throttleEvent 118 | */ 119 | export function throttleEvent(type, name, obj) { 120 | let isTriggered = false; 121 | const func = () => { 122 | if (isTriggered) return; 123 | isTriggered = true; 124 | requestAnimationFrame(() => { 125 | obj.dispatchEvent(new CustomEvent(name)); 126 | isTriggered = false; 127 | }); 128 | }; 129 | obj = obj || window; 130 | obj.addEventListener(type, func); 131 | } 132 | 133 | /* ----- PROP TYPES & CSS RULE SETS ----- */ 134 | 135 | const basePropTypes = { 136 | children: PropTypes.node, 137 | theme: PropTypes.object, 138 | }; 139 | export { basePropTypes }; 140 | 141 | export const columnPropTypes = { 142 | col: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), 143 | offset: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), 144 | gutter: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.object]), 145 | }; 146 | 147 | const displayPropTypes = { 148 | hide: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), 149 | show: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.object]), 150 | }; 151 | export { displayPropTypes }; 152 | 153 | /** 154 | * withContainer 155 | */ 156 | const containerPropTypes = { 157 | container: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]), 158 | }; 159 | export { containerPropTypes }; 160 | export function withContainer(props) { 161 | if (!props.container) return ''; 162 | 163 | if (typeof props.container === 'number') 164 | return css` 165 | margin-left: auto; 166 | margin-right: auto; 167 | max-width: ${props.container}px; 168 | `; 169 | 170 | // passing in a 'boolean' behaves "just like Bootstrap" 171 | // prettier-ignore 172 | return css` 173 | margin-left: auto; 174 | margin-right: auto; 175 | max-width: ${props.theme.CONTAINER_SMALL}px; 176 | 177 | ${props.theme.MEDIA_MD_UP} { 178 | max-width: ${props.theme.CONTAINER_MEDIUM}px; 179 | } 180 | 181 | ${props.theme.MEDIA_LG_UP} { 182 | max-width: ${props.theme.CONTAINER_LARGE}px; 183 | } 184 | ` 185 | } 186 | 187 | /** 188 | * withFont 189 | */ 190 | const fontPropTypes = { 191 | inline: PropTypes.bool, 192 | 193 | roman: PropTypes.bool, 194 | italic: PropTypes.bool, 195 | oblique: PropTypes.bool, 196 | 197 | light: PropTypes.bool, 198 | lighter: PropTypes.bool, 199 | normal: PropTypes.bool, 200 | bold: PropTypes.bool, 201 | bolder: PropTypes.bool, 202 | 203 | xxSmall: PropTypes.bool, 204 | xSmall: PropTypes.bool, 205 | small: PropTypes.bool, 206 | medium: PropTypes.bool, 207 | large: PropTypes.bool, 208 | xLarge: PropTypes.bool, 209 | xxLarge: PropTypes.bool, 210 | larger: PropTypes.bool, 211 | smaller: PropTypes.bool, 212 | 213 | size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 214 | underline: PropTypes.bool, 215 | }; 216 | const FONT_SIZING = { 217 | xxSmall: 'xx-small', 218 | xSmall: 'x-small', 219 | small: 'small', 220 | medium: 'medium', 221 | large: 'large', 222 | xLarge: 'x-large', 223 | xxLarge: 'xx-large', 224 | larger: 'larger', 225 | smaller: 'smaller', 226 | }; 227 | export { fontPropTypes }; 228 | export function withFont(props) { 229 | let fontStyle = ['roman', 'italic', 'oblique'].find(style => style in props) || ''; 230 | if (fontStyle === 'roman') fontStyle = 'normal'; 231 | 232 | const fontWeight = 233 | ['light', 'lighter', 'normal', 'bold', 'bolder'].find(weight => weight in props) || ''; 234 | 235 | const fontSize = props.size 236 | ? toCssUnits(props.size) 237 | : FONT_SIZING[Object.keys(FONT_SIZING).find(size => size in props)] || ''; 238 | 239 | // prettier-ignore 240 | return css` 241 | ${props.inline && `display: inline;`} 242 | ${props.underline && 'text-decoration: underline;'} 243 | font-size: ${fontSize}; 244 | font-style: ${fontStyle}; 245 | font-weight: ${fontWeight}; 246 | `; 247 | } 248 | 249 | /** 250 | * withJustify 251 | */ 252 | const justifyPropTypes = { 253 | left: PropTypes.bool, 254 | center: PropTypes.bool, 255 | right: PropTypes.bool, 256 | }; 257 | export { justifyPropTypes }; 258 | 259 | export function withJustify(props) { 260 | const justify = ['center', 'right', 'left'].find(dir => dir in props); 261 | return css` 262 | ${justify && `text-align: ${justify};`}; 263 | `; 264 | } 265 | 266 | /** 267 | * withMediaGutters 268 | */ 269 | const gutterPropTypes = { 270 | gutter: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), 271 | }; 272 | export { gutterPropTypes }; 273 | export function withMediaGutters({ gutter, theme }) { 274 | gutter = toMediaObj(gutter); 275 | 276 | // prettier-ignore 277 | const result = css` 278 | ${'xs' in gutter && toMediaGuttersCss(null, gutter.xs)} 279 | ${'sm' in gutter && toMediaGuttersCss(theme.MEDIA_SM_UP, gutter.sm)} 280 | ${'md' in gutter && toMediaGuttersCss(theme.MEDIA_MD_UP, gutter.md)} 281 | ${'lg' in gutter && toMediaGuttersCss(theme.MEDIA_LG_UP, gutter.lg)} 282 | ${'xl' in gutter && toMediaGuttersCss(theme.MEDIA_XL_UP, gutter.xl)} 283 | `; 284 | 285 | return result; 286 | } 287 | 288 | /** 289 | * withMediaColumns 290 | */ 291 | export function withMediaColumns({ col, offset = 0, gutter = 0, theme }) { 292 | col = toMediaObj(col); 293 | offset = toMediaObj(offset); 294 | gutter = toMediaObj(gutter); 295 | 296 | // prettier-ignore 297 | return css` 298 | ${'xs' in col && toMediaColumnCss(null, col.xs, offset.xs, gutter.xs)} 299 | ${'sm' in col && toMediaColumnCss(theme.MEDIA_SM_UP, col.sm, offset.sm, gutter.sm)} 300 | ${'md' in col && toMediaColumnCss(theme.MEDIA_MD_UP, col.md, offset.md, gutter.md)} 301 | ${'lg' in col && toMediaColumnCss(theme.MEDIA_LG_UP, col.lg, offset.lg, gutter.lg)} 302 | ${'xl' in col && toMediaColumnCss(theme.MEDIA_XL_UP, col.xl, offset.xl, gutter.xl)} 303 | `; 304 | } 305 | 306 | /** 307 | * withMediaStyles 308 | */ 309 | const mediaStylesPropTypes = { 310 | styles: PropTypes.oneOfType([ 311 | PropTypes.string, 312 | PropTypes.arrayOf(PropTypes.string), 313 | PropTypes.object, 314 | ]), 315 | }; 316 | export { mediaStylesPropTypes }; 317 | export function withMediaStyles({ styles, theme }) { 318 | if (Array.isArray(styles)) return styles; 319 | if (typeof styles === 'string') return css`${toCssString(styles)}`; // prettier-ignore 320 | if (typeof styles === 'object') { 321 | // prettier-ignore 322 | return css` 323 | ${styles.xs && toCssString(styles.xs)} 324 | ${styles.sm && `${theme.MEDIA_SM_UP} { ${toCssString(styles.sm)} }`} 325 | ${styles.md && `${theme.MEDIA_MD_UP} { ${toCssString(styles.md)} }`} 326 | ${styles.lg && `${theme.MEDIA_LG_UP} { ${toCssString(styles.lg)} }`} 327 | ${styles.xl && `${theme.MEDIA_XL_UP} { ${toCssString(styles.xl)} }`} 328 | `; 329 | } 330 | 331 | return []; 332 | } 333 | 334 | /** 335 | * withSpacing 336 | */ 337 | const spacingPropTypes = { 338 | margin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 339 | padding: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 340 | }; 341 | export { spacingPropTypes }; 342 | export function withSpacing(props) { 343 | // prettier-ignore 344 | return css` 345 | ${props.margin && cssSpacing('margin', props.margin)} 346 | ${props.padding && cssSpacing('padding', props.padding)} 347 | ` 348 | } 349 | -------------------------------------------------------------------------------- /src/lib/withWindow.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { throttleEvent } from './utils'; 3 | import THEME from './THEME'; 4 | 5 | const defaultMedia = 'xs'; 6 | 7 | function getCurrentMedia(ranges, width = 0) { 8 | let media = defaultMedia; 9 | Object.keys(ranges).some(key => { 10 | const [min, max] = ranges[key]; 11 | if (width >= min && width <= max) { 12 | media = key; 13 | return true; 14 | } 15 | return false; 16 | }); 17 | return media; 18 | } 19 | 20 | function getWindowSize() { 21 | const width = 22 | window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 23 | const height = 24 | window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; 25 | return { width, height }; 26 | } 27 | 28 | /** 29 | * withWindow 30 | * HOC to supply current 'media' breakpoint and 'window' sizing the enhanced component 31 | * Value of 'media' will be one of: 'xs' (default), 'sm', 'md', 'lg', 'xl' 32 | */ 33 | const withWindow = (EnhancedComponent, userTheme = {}) => { 34 | const { MEDIA_SM, MEDIA_MD, MEDIA_LG, MEDIA_XL } = { ...THEME, ...userTheme }; 35 | 36 | const mediaRanges = { 37 | xs: [0, MEDIA_SM - 1], 38 | sm: [MEDIA_SM, MEDIA_MD - 1], 39 | md: [MEDIA_MD, MEDIA_LG - 1], 40 | lg: [MEDIA_LG, MEDIA_XL - 1], 41 | xl: [MEDIA_XL, Infinity], 42 | }; 43 | 44 | return class extends React.Component { 45 | state = { 46 | media: defaultMedia, 47 | window: { width: 0, height: 0 }, 48 | }; 49 | 50 | componentDidMount() { 51 | if (window) { 52 | throttleEvent('resize', 'throttledWindowResize', window); 53 | window.addEventListener('throttledWindowResize', this.handleResize); 54 | this.handleResize(); 55 | } 56 | } 57 | 58 | componentWillUnmount() { 59 | if (window) { 60 | window.removeEventListener('throttledWindowResize', this.handleResize); 61 | } 62 | } 63 | 64 | handleResize = () => { 65 | const window = getWindowSize(); 66 | const media = getCurrentMedia(mediaRanges, window.width); 67 | this.setState({ media, window }); 68 | }; 69 | 70 | render() { 71 | return ( 72 | 77 | ); 78 | } 79 | }; 80 | }; 81 | 82 | export default withWindow; 83 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const ProgressBarPlugin = require('progress-bar-webpack-plugin'); 6 | 7 | const isProd = process.argv.includes('production'); 8 | const isDev = !isProd; 9 | const ENV = isProd ? 'production' : 'development'; 10 | const DEV_PORT = '8080'; 11 | 12 | const srcFolder = path.resolve(__dirname, 'src'); 13 | const outFolder = path.resolve(__dirname, 'demo'); 14 | 15 | module.exports = function() { 16 | console.log(`Building for ${ENV}...`); 17 | 18 | /* ----- PLUGINS ----- */ 19 | 20 | const plugins = [ 21 | new HtmlWebpackPlugin({ 22 | template: 'src/index.html', 23 | hash: true, 24 | inject: 'body', 25 | favicon: 'src/assets/img/favicon.ico', 26 | }), 27 | new webpack.DefinePlugin({ 28 | 'process.env.NODE_ENV': JSON.stringify(ENV), 29 | }), 30 | new webpack.NamedModulesPlugin(), 31 | new ProgressBarPlugin(), 32 | ]; 33 | 34 | // if (isDev) { 35 | // plugins.push(new webpack.HotModuleReplacementPlugin()); 36 | // } 37 | 38 | if (isProd) { 39 | plugins.unshift(new CleanWebpackPlugin(['demo'])); // clear folder first! 40 | } 41 | 42 | /* ----- ENTRY ----- */ 43 | 44 | const entry = ['babel-polyfill']; 45 | 46 | if (isDev) { 47 | entry.push('react-hot-loader/patch'); 48 | entry.push(`webpack-dev-server/client?http://localhost:${DEV_PORT}`); 49 | entry.push('webpack/hot/dev-server'); // or 'webpack/hot/only-dev-server' to reload on success only 50 | } 51 | 52 | entry.push(path.join(srcFolder, 'index.js')); 53 | 54 | /* ----- FINAL CONFIG ----- */ 55 | 56 | return { 57 | devtool: isDev ? 'eval-source-map' : 'source-map', 58 | mode: isDev ? 'development' : 'production', 59 | entry: entry, 60 | output: { 61 | filename: 'bundle.js', 62 | path: outFolder, 63 | pathinfo: isDev, 64 | publicPath: '', 65 | }, 66 | resolve: { 67 | modules: [path.resolve('./src'), 'node_modules'], 68 | extensions: ['.js', '.jsx'], 69 | }, 70 | plugins: plugins, 71 | module: { 72 | rules: [ 73 | { 74 | test: /\.(js|jsx)$/, 75 | loader: 'babel-loader', 76 | exclude: /node_modules/, 77 | include: srcFolder, 78 | }, 79 | ], 80 | }, 81 | devServer: { 82 | contentBase: outFolder, 83 | historyApiFallback: true, 84 | hot: true, 85 | hotOnly: true, 86 | stats: 'minimal', 87 | }, 88 | }; 89 | }; 90 | --------------------------------------------------------------------------------