@khanacademy/wonder-blocks-grid 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/es/index.js +276 -0
- package/dist/index.js +787 -0
- package/dist/index.js.flow +2 -0
- package/docs.md +260 -0
- package/package.json +31 -0
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +2247 -0
- package/src/__tests__/generated-snapshot.test.js +559 -0
- package/src/components/__tests__/row.test.js +175 -0
- package/src/components/cell.js +186 -0
- package/src/components/cell.md +58 -0
- package/src/components/gutter.js +59 -0
- package/src/components/row.js +141 -0
- package/src/components/row.md +105 -0
- package/src/index.js +3 -0
- package/src/util/styles.js +29 -0
- package/src/util/utils.js +11 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Khan Academy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/es/index.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { Component, createElement, Children } from 'react';
|
|
2
|
+
import { MediaLayout, queryMatchesSize, Strut } from '@khanacademy/wonder-blocks-layout';
|
|
3
|
+
import { View } from '@khanacademy/wonder-blocks-core';
|
|
4
|
+
import { StyleSheet } from 'aphrodite';
|
|
5
|
+
|
|
6
|
+
const WIDE_SCREEN = "@media (min-width: 1168px)";
|
|
7
|
+
const styles = StyleSheet.create({
|
|
8
|
+
row: {
|
|
9
|
+
flexDirection: "row",
|
|
10
|
+
alignItems: "center",
|
|
11
|
+
width: "100%"
|
|
12
|
+
},
|
|
13
|
+
rowMaxWidth: {
|
|
14
|
+
[WIDE_SCREEN]: {
|
|
15
|
+
margin: "0 auto"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
cellGrow: {
|
|
19
|
+
flexGrow: 1
|
|
20
|
+
},
|
|
21
|
+
cellFixed: {
|
|
22
|
+
flexShrink: 0
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const flexBasis = size => {
|
|
27
|
+
return {
|
|
28
|
+
MsFlexBasis: size,
|
|
29
|
+
MsFlexPreferredSize: size,
|
|
30
|
+
WebkitFlexBasis: size,
|
|
31
|
+
flexBasis: size
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A Cell is a container whose width is set based on the width of the
|
|
37
|
+
* specified columns at the current grid size. You will specify the number
|
|
38
|
+
* of columns that you want this component to span at each grid size.
|
|
39
|
+
* This component should only be used as a child of a [Row](#row).
|
|
40
|
+
*
|
|
41
|
+
* This component renders a [View](#view) that
|
|
42
|
+
* uses Flex Box and has a [flex-basis](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis)
|
|
43
|
+
* of the specified "width" and [flex-shrink](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink)
|
|
44
|
+
* of 0.
|
|
45
|
+
*
|
|
46
|
+
* By default (with no properties specified) it will display at all
|
|
47
|
+
* grid sizes. If you specify the `smallCols`, `mediumCols`, `largeCols`, or
|
|
48
|
+
* `cols` props then the component will only be shown at those grid sizes and
|
|
49
|
+
* using the specified column width.
|
|
50
|
+
*/
|
|
51
|
+
class Cell extends Component {
|
|
52
|
+
static isClassOf(instance) {
|
|
53
|
+
return instance && instance.type && instance.type.__IS_CELL__;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static getCols(props, mediaSize) {
|
|
57
|
+
// If no option was specified then we just return undefined,
|
|
58
|
+
// components may handle this case differently.
|
|
59
|
+
// We go through all the ways in which a fixed width can be
|
|
60
|
+
// specified and find the one that matches our current grid size.
|
|
61
|
+
if (!props.smallCols && !props.mediumCols && !props.largeCols && !props.cols) {
|
|
62
|
+
return undefined;
|
|
63
|
+
} else if (props.smallCols && mediaSize === "small") {
|
|
64
|
+
return props.smallCols;
|
|
65
|
+
} else if (props.mediumCols && mediaSize === "medium") {
|
|
66
|
+
return props.mediumCols;
|
|
67
|
+
} else if (props.largeCols && mediaSize === "large") {
|
|
68
|
+
return props.largeCols;
|
|
69
|
+
} else if (typeof props.cols === "function") {
|
|
70
|
+
return props.cols(mediaSize);
|
|
71
|
+
} else if (props.cols) {
|
|
72
|
+
return props.cols;
|
|
73
|
+
} // If nothing applies then we return null (usually resulting
|
|
74
|
+
// in the component not being rendered)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
return null;
|
|
78
|
+
} // HACK(kevinb): we use a stack method here because we can't get a ref to
|
|
79
|
+
// each cell in a row without cloning them all.
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
static shouldDisplay(props, mediaSize) {
|
|
83
|
+
const cols = Cell.getCols(props, mediaSize);
|
|
84
|
+
return cols !== null && cols !== 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
render() {
|
|
88
|
+
const {
|
|
89
|
+
children,
|
|
90
|
+
style
|
|
91
|
+
} = this.props;
|
|
92
|
+
return /*#__PURE__*/createElement(MediaLayout, null, ({
|
|
93
|
+
mediaSize,
|
|
94
|
+
mediaSpec
|
|
95
|
+
}) => {
|
|
96
|
+
const spec = mediaSpec[mediaSize];
|
|
97
|
+
|
|
98
|
+
if (!spec) {
|
|
99
|
+
throw new Error(`mediaSpec.${mediaSize} is undefined`);
|
|
100
|
+
} // Get the settings for this particular size of grid
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
const {
|
|
104
|
+
totalColumns,
|
|
105
|
+
gutterWidth,
|
|
106
|
+
marginWidth
|
|
107
|
+
} = spec;
|
|
108
|
+
const cols = Cell.getCols(this.props, mediaSize); // If no columns are specified then we just don't render this cell
|
|
109
|
+
|
|
110
|
+
if (cols === undefined || cols === null || cols === 0) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (cols > totalColumns) {
|
|
115
|
+
throw new Error(`Specified columns ${cols} is greater than the maximum ` + `${totalColumns} at the ${mediaSize} grid size.`);
|
|
116
|
+
} // We need to start by calculating the total width of all the "content"
|
|
117
|
+
// We do this by starting with the full width (100%) and then
|
|
118
|
+
// subtracting all of the gutter spaces inbetween the cells
|
|
119
|
+
// (gutterWidth * (totalColumns - 1)) and the width of the two margins
|
|
120
|
+
// (marginWidth * 2).
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
const contentWidth = `(100% - ${gutterWidth * (totalColumns - 1)}px - ${marginWidth * 2}px)`; // Now that we have the full width we can calculate the width of this
|
|
124
|
+
// particular cell by multiplying the full width (allCellWidth) by
|
|
125
|
+
// the ratio of this cell (cols / totalColumns). But we then need to
|
|
126
|
+
// add back in the missing gutter widths:
|
|
127
|
+
// (gutterWidth * (cols - 1)). This gives us to full width of
|
|
128
|
+
// this particular cell.
|
|
129
|
+
|
|
130
|
+
const calcWidth = `calc(${contentWidth} * ${cols / totalColumns} + ${gutterWidth * (cols - 1)}px)`; // If the contents are a function then we call it with the mediaSize,
|
|
131
|
+
// totalColumns, and cols properties and render the return value.
|
|
132
|
+
|
|
133
|
+
const contents = typeof children === "function" ? children({
|
|
134
|
+
mediaSize,
|
|
135
|
+
totalColumns,
|
|
136
|
+
cols
|
|
137
|
+
}) : children; // Render a fixed-width cell (flex-basis: size, flex-shrink: 0)
|
|
138
|
+
// that matches the intended width of the cell
|
|
139
|
+
|
|
140
|
+
return /*#__PURE__*/createElement(View, {
|
|
141
|
+
style: [styles.cellFixed, flexBasis(calcWidth), style]
|
|
142
|
+
}, contents);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
Cell.defaultProps = {
|
|
148
|
+
smallCols: 0,
|
|
149
|
+
mediumCols: 0,
|
|
150
|
+
largeCols: 0,
|
|
151
|
+
cols: 0
|
|
152
|
+
};
|
|
153
|
+
Cell.__IS_CELL__ = true;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Gutter is a component whose width is set based on the size of grid currently
|
|
157
|
+
* being displayed. Used for spacing out cells from each other. The gutter
|
|
158
|
+
* itself doesn't hold any content, it just spaces it out.
|
|
159
|
+
*
|
|
160
|
+
* Gutters are inserted automatically inside of a [Row](#row) in-between Cells.
|
|
161
|
+
* You may only need to use Gutters if you're manually building your own
|
|
162
|
+
* sub-grid, or some-such (this should be relatively rare).
|
|
163
|
+
*
|
|
164
|
+
* By default (with no properties specified) it will display at all
|
|
165
|
+
* grid sizes. If you specify the `small`, `medium`, or `large`
|
|
166
|
+
* props then the component will only be shown at those grid sizes.
|
|
167
|
+
*/
|
|
168
|
+
class Gutter extends Component {
|
|
169
|
+
render() {
|
|
170
|
+
return /*#__PURE__*/createElement(MediaLayout, null, ({
|
|
171
|
+
mediaSize,
|
|
172
|
+
mediaSpec
|
|
173
|
+
}) => {
|
|
174
|
+
const spec = mediaSpec[mediaSize];
|
|
175
|
+
|
|
176
|
+
if (!spec) {
|
|
177
|
+
throw new Error(`mediaSpec.${mediaSize} is undefined`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const {
|
|
181
|
+
gutterWidth
|
|
182
|
+
} = spec;
|
|
183
|
+
|
|
184
|
+
if (!queryMatchesSize(this.props.mediaQuery, mediaSize)) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return /*#__PURE__*/createElement(Strut, {
|
|
189
|
+
size: gutterWidth
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
}
|
|
195
|
+
Gutter.defaultProps = {
|
|
196
|
+
mediaQuery: "all"
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A Row holds all of the Cells that make up the contents of the grid. A row
|
|
201
|
+
* also provides the margins on the sides and inserts the gutter spacing
|
|
202
|
+
* in-between the cells. Typically this component will hold a [Cell](#cell),
|
|
203
|
+
* but it can also include any elements that could fit in a
|
|
204
|
+
* [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox).
|
|
205
|
+
*
|
|
206
|
+
* This component will automatically attempt to insert [Gutters](#gutter)
|
|
207
|
+
* in-between all child elements. Additionally, it'll perform some basic checks
|
|
208
|
+
* to ensure that no impossible layouts are accidentally generated.
|
|
209
|
+
*
|
|
210
|
+
* Typically this component will be used as a child of a [Grid](#grid-1),
|
|
211
|
+
* but it's not a requirement, you can use it as a descendant, as well.
|
|
212
|
+
*
|
|
213
|
+
* By default (with no properties specified) it will display at all
|
|
214
|
+
* grid sizes. If you specify the `small`, `medium`, or `large`
|
|
215
|
+
* props then the component will only be shown at those grid sizes.
|
|
216
|
+
*/
|
|
217
|
+
class Row extends Component {
|
|
218
|
+
render() {
|
|
219
|
+
const {
|
|
220
|
+
style,
|
|
221
|
+
children
|
|
222
|
+
} = this.props;
|
|
223
|
+
return /*#__PURE__*/createElement(MediaLayout, null, ({
|
|
224
|
+
mediaSize,
|
|
225
|
+
mediaSpec
|
|
226
|
+
}) => {
|
|
227
|
+
const spec = mediaSpec[mediaSize];
|
|
228
|
+
|
|
229
|
+
if (!spec) {
|
|
230
|
+
throw new Error(`mediaSpec.${mediaSize} is undefined`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const {
|
|
234
|
+
marginWidth,
|
|
235
|
+
maxWidth,
|
|
236
|
+
totalColumns
|
|
237
|
+
} = spec;
|
|
238
|
+
const shouldDisplay = queryMatchesSize(this.props.mediaQuery, mediaSize); // Don't render the row if it's been disabled at this size
|
|
239
|
+
|
|
240
|
+
if (!shouldDisplay) {
|
|
241
|
+
return null;
|
|
242
|
+
} // If the contents are a function then we call it with the mediaSize and
|
|
243
|
+
// totalColumns properties and render the return value.
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
const contents = typeof children === "function" ? children({
|
|
247
|
+
mediaSize,
|
|
248
|
+
totalColumns
|
|
249
|
+
}) : children;
|
|
250
|
+
const filteredContents = Children.toArray(contents).filter( // Flow doesn't let us check .type on a non-null React.Node so
|
|
251
|
+
// we have to cast it to any.
|
|
252
|
+
item => Cell.isClassOf(item) ? Cell.shouldDisplay(item.props, mediaSize) : true) // Intersperse Gutter elements between the cells.
|
|
253
|
+
.reduce((acc, elem, index) => [].concat(acc, [elem, /*#__PURE__*/createElement(Gutter, {
|
|
254
|
+
key: `gutter-${index}`
|
|
255
|
+
})]), []) // We only want gutters between each cell in the row. The reduce
|
|
256
|
+
// adds a gutter after every cell so we need to remove the last
|
|
257
|
+
// element which is an unnecessary gutteer.
|
|
258
|
+
.slice(0, -1);
|
|
259
|
+
return /*#__PURE__*/createElement(View, {
|
|
260
|
+
style: [styles.row, !!maxWidth && styles.rowMaxWidth, !!maxWidth && {
|
|
261
|
+
maxWidth
|
|
262
|
+
}, style]
|
|
263
|
+
}, /*#__PURE__*/createElement(Strut, {
|
|
264
|
+
size: marginWidth
|
|
265
|
+
}), filteredContents, /*#__PURE__*/createElement(Strut, {
|
|
266
|
+
size: marginWidth
|
|
267
|
+
}));
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
Row.defaultProps = {
|
|
273
|
+
mediaQuery: "all"
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export { Cell, Row };
|