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 |
38 | );
39 |
40 | const GitHubLogo = () => (
41 |
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 |
213 | {isBackground && (
214 |
224 | )}
225 |
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 |
--------------------------------------------------------------------------------