@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
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {mount} from "enzyme";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
MEDIA_DEFAULT_SPEC,
|
|
8
|
+
MediaLayoutContext,
|
|
9
|
+
} from "@khanacademy/wonder-blocks-layout";
|
|
10
|
+
import Row from "../row.js";
|
|
11
|
+
import Cell from "../cell.js";
|
|
12
|
+
|
|
13
|
+
describe("Row", () => {
|
|
14
|
+
describe("large", () => {
|
|
15
|
+
it("should render Cells with largeCols and cols", () => {
|
|
16
|
+
// Arrange
|
|
17
|
+
|
|
18
|
+
// Act
|
|
19
|
+
const wrapper = mount(
|
|
20
|
+
<div>
|
|
21
|
+
<MediaLayoutContext.Provider
|
|
22
|
+
value={{
|
|
23
|
+
overrideSize: undefined,
|
|
24
|
+
ssrSize: "large",
|
|
25
|
+
mediaSpec: MEDIA_DEFAULT_SPEC,
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<Row>
|
|
29
|
+
<Cell cols={1}>cols</Cell>
|
|
30
|
+
<Cell largeCols={1}>largeCols</Cell>
|
|
31
|
+
<Cell mediumCols={1}>mediumCols</Cell>
|
|
32
|
+
<Cell smallCols={1}>smallCols</Cell>
|
|
33
|
+
</Row>
|
|
34
|
+
</MediaLayoutContext.Provider>
|
|
35
|
+
</div>,
|
|
36
|
+
);
|
|
37
|
+
const text = wrapper.text();
|
|
38
|
+
|
|
39
|
+
// Assert
|
|
40
|
+
expect(text).toEqual("colslargeCols");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should throw if there are too many columns", async () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
const render = () => {
|
|
46
|
+
mount(
|
|
47
|
+
<div>
|
|
48
|
+
<MediaLayoutContext.Provider
|
|
49
|
+
value={{
|
|
50
|
+
overrideSize: undefined,
|
|
51
|
+
ssrSize: "large",
|
|
52
|
+
mediaSpec: MEDIA_DEFAULT_SPEC,
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<Row>
|
|
56
|
+
<Cell cols={13}>cols</Cell>
|
|
57
|
+
</Row>
|
|
58
|
+
</MediaLayoutContext.Provider>
|
|
59
|
+
</div>,
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Act, Assert
|
|
64
|
+
expect(render).toThrow();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("medium", () => {
|
|
69
|
+
it("should render Cells with largeCols and cols", async () => {
|
|
70
|
+
// Arrange
|
|
71
|
+
|
|
72
|
+
// Act
|
|
73
|
+
const wrapper = mount(
|
|
74
|
+
<div>
|
|
75
|
+
<MediaLayoutContext.Provider
|
|
76
|
+
value={{
|
|
77
|
+
overrideSize: undefined,
|
|
78
|
+
ssrSize: "medium",
|
|
79
|
+
mediaSpec: MEDIA_DEFAULT_SPEC,
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<Row>
|
|
83
|
+
<Cell cols={1}>cols</Cell>
|
|
84
|
+
<Cell largeCols={1}>largeCols</Cell>
|
|
85
|
+
<Cell mediumCols={1}>mediumCols</Cell>
|
|
86
|
+
<Cell smallCols={1}>smallCols</Cell>
|
|
87
|
+
</Row>
|
|
88
|
+
</MediaLayoutContext.Provider>
|
|
89
|
+
</div>,
|
|
90
|
+
);
|
|
91
|
+
const text = wrapper.text();
|
|
92
|
+
|
|
93
|
+
// Assert
|
|
94
|
+
expect(text).toEqual("colsmediumCols");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should throw if there are too many columns", () => {
|
|
98
|
+
// Arrange
|
|
99
|
+
const render = () => {
|
|
100
|
+
mount(
|
|
101
|
+
<div>
|
|
102
|
+
<MediaLayoutContext.Provider
|
|
103
|
+
value={{
|
|
104
|
+
overrideSize: undefined,
|
|
105
|
+
ssrSize: "medium",
|
|
106
|
+
mediaSpec: MEDIA_DEFAULT_SPEC,
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<Row>
|
|
110
|
+
<Cell cols={9}>cols</Cell>
|
|
111
|
+
</Row>
|
|
112
|
+
</MediaLayoutContext.Provider>
|
|
113
|
+
</div>,
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Act, Assert
|
|
118
|
+
expect(render).toThrow();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("small", () => {
|
|
123
|
+
it("should render Cells with largeCols and cols", () => {
|
|
124
|
+
// Arrange
|
|
125
|
+
|
|
126
|
+
// Act
|
|
127
|
+
const wrapper = mount(
|
|
128
|
+
<div>
|
|
129
|
+
<MediaLayoutContext.Provider
|
|
130
|
+
value={{
|
|
131
|
+
overrideSize: undefined,
|
|
132
|
+
ssrSize: "small",
|
|
133
|
+
mediaSpec: MEDIA_DEFAULT_SPEC,
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
<Row>
|
|
137
|
+
<Cell cols={1}>cols</Cell>
|
|
138
|
+
<Cell largeCols={1}>largeCols</Cell>
|
|
139
|
+
<Cell mediumCols={1}>mediumCols</Cell>
|
|
140
|
+
<Cell smallCols={1}>smallCols</Cell>
|
|
141
|
+
</Row>
|
|
142
|
+
</MediaLayoutContext.Provider>
|
|
143
|
+
</div>,
|
|
144
|
+
);
|
|
145
|
+
const text = wrapper.text();
|
|
146
|
+
|
|
147
|
+
// Assert
|
|
148
|
+
expect(text).toEqual("colssmallCols");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should throw if there are too many columns", () => {
|
|
152
|
+
// Arrange
|
|
153
|
+
const render = () => {
|
|
154
|
+
mount(
|
|
155
|
+
<div>
|
|
156
|
+
<MediaLayoutContext.Provider
|
|
157
|
+
value={{
|
|
158
|
+
overrideSize: undefined,
|
|
159
|
+
ssrSize: "small",
|
|
160
|
+
mediaSpec: MEDIA_DEFAULT_SPEC,
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
<Row>
|
|
164
|
+
<Cell cols={5}>cols</Cell>
|
|
165
|
+
</Row>
|
|
166
|
+
</MediaLayoutContext.Provider>
|
|
167
|
+
</div>,
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Act, Assert
|
|
172
|
+
expect(render).toThrow();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import {MediaLayout} from "@khanacademy/wonder-blocks-layout";
|
|
5
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import type {MediaSize} from "@khanacademy/wonder-blocks-layout";
|
|
7
|
+
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
8
|
+
|
|
9
|
+
import styles from "../util/styles.js";
|
|
10
|
+
import {flexBasis} from "../util/utils.js";
|
|
11
|
+
|
|
12
|
+
type Props = {|
|
|
13
|
+
/** The number of columns this cell should span on a Small Grid. */
|
|
14
|
+
smallCols: number,
|
|
15
|
+
|
|
16
|
+
/** The number of columns this cell should span on a Medium Grid. */
|
|
17
|
+
mediumCols: number,
|
|
18
|
+
|
|
19
|
+
/** The number of columns this cell should span on a Large Grid. */
|
|
20
|
+
largeCols: number,
|
|
21
|
+
|
|
22
|
+
/** The number of columns this should should span by default. */
|
|
23
|
+
cols: number | ((mediaSize: MediaSize) => number),
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The child components to populate inside the cell. Can also accept a
|
|
27
|
+
* function which receives the `mediaSize`, `totalColumns`, and cell
|
|
28
|
+
* `width` and should return some React Nodes to render.
|
|
29
|
+
*/
|
|
30
|
+
children:
|
|
31
|
+
| React.Node
|
|
32
|
+
| (({|
|
|
33
|
+
mediaSize: MediaSize,
|
|
34
|
+
totalColumns: number,
|
|
35
|
+
cols: number,
|
|
36
|
+
|}) => React.Node),
|
|
37
|
+
|
|
38
|
+
/** The styling to apply to the cell. */
|
|
39
|
+
style?: StyleType,
|
|
40
|
+
|};
|
|
41
|
+
|
|
42
|
+
type DefaultProps = {|
|
|
43
|
+
smallCols: $PropertyType<Props, "smallCols">,
|
|
44
|
+
mediumCols: $PropertyType<Props, "mediumCols">,
|
|
45
|
+
largeCols: $PropertyType<Props, "largeCols">,
|
|
46
|
+
cols: $PropertyType<Props, "cols">,
|
|
47
|
+
|};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A Cell is a container whose width is set based on the width of the
|
|
51
|
+
* specified columns at the current grid size. You will specify the number
|
|
52
|
+
* of columns that you want this component to span at each grid size.
|
|
53
|
+
* This component should only be used as a child of a [Row](#row).
|
|
54
|
+
*
|
|
55
|
+
* This component renders a [View](#view) that
|
|
56
|
+
* uses Flex Box and has a [flex-basis](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis)
|
|
57
|
+
* of the specified "width" and [flex-shrink](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink)
|
|
58
|
+
* of 0.
|
|
59
|
+
*
|
|
60
|
+
* By default (with no properties specified) it will display at all
|
|
61
|
+
* grid sizes. If you specify the `smallCols`, `mediumCols`, `largeCols`, or
|
|
62
|
+
* `cols` props then the component will only be shown at those grid sizes and
|
|
63
|
+
* using the specified column width.
|
|
64
|
+
*/
|
|
65
|
+
export default class Cell extends React.Component<Props> {
|
|
66
|
+
static isClassOf(instance: React.Element<any>): boolean {
|
|
67
|
+
return instance && instance.type && instance.type.__IS_CELL__;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static getCols(props: Props, mediaSize: MediaSize): ?number {
|
|
71
|
+
// If no option was specified then we just return undefined,
|
|
72
|
+
// components may handle this case differently.
|
|
73
|
+
// We go through all the ways in which a fixed width can be
|
|
74
|
+
// specified and find the one that matches our current grid size.
|
|
75
|
+
if (
|
|
76
|
+
!props.smallCols &&
|
|
77
|
+
!props.mediumCols &&
|
|
78
|
+
!props.largeCols &&
|
|
79
|
+
!props.cols
|
|
80
|
+
) {
|
|
81
|
+
return undefined;
|
|
82
|
+
} else if (props.smallCols && mediaSize === "small") {
|
|
83
|
+
return props.smallCols;
|
|
84
|
+
} else if (props.mediumCols && mediaSize === "medium") {
|
|
85
|
+
return props.mediumCols;
|
|
86
|
+
} else if (props.largeCols && mediaSize === "large") {
|
|
87
|
+
return props.largeCols;
|
|
88
|
+
} else if (typeof props.cols === "function") {
|
|
89
|
+
return props.cols(mediaSize);
|
|
90
|
+
} else if (props.cols) {
|
|
91
|
+
return props.cols;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// If nothing applies then we return null (usually resulting
|
|
95
|
+
// in the component not being rendered)
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// HACK(kevinb): we use a stack method here because we can't get a ref to
|
|
100
|
+
// each cell in a row without cloning them all.
|
|
101
|
+
static shouldDisplay(props: Props, mediaSize: MediaSize): boolean {
|
|
102
|
+
const cols = Cell.getCols(props, mediaSize);
|
|
103
|
+
return cols !== null && cols !== 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static defaultProps: DefaultProps = {
|
|
107
|
+
smallCols: 0,
|
|
108
|
+
mediumCols: 0,
|
|
109
|
+
largeCols: 0,
|
|
110
|
+
cols: 0,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
static __IS_CELL__: boolean = true;
|
|
114
|
+
|
|
115
|
+
render(): React.Node {
|
|
116
|
+
const {children, style} = this.props;
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<MediaLayout>
|
|
120
|
+
{({mediaSize, mediaSpec}) => {
|
|
121
|
+
const spec = mediaSpec[mediaSize];
|
|
122
|
+
if (!spec) {
|
|
123
|
+
throw new Error(`mediaSpec.${mediaSize} is undefined`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Get the settings for this particular size of grid
|
|
127
|
+
const {totalColumns, gutterWidth, marginWidth} = spec;
|
|
128
|
+
|
|
129
|
+
const cols = Cell.getCols(this.props, mediaSize);
|
|
130
|
+
|
|
131
|
+
// If no columns are specified then we just don't render this cell
|
|
132
|
+
if (cols === undefined || cols === null || cols === 0) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (cols > totalColumns) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Specified columns ${cols} is greater than the maximum ` +
|
|
139
|
+
`${totalColumns} at the ${mediaSize} grid size.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// We need to start by calculating the total width of all the "content"
|
|
144
|
+
// We do this by starting with the full width (100%) and then
|
|
145
|
+
// subtracting all of the gutter spaces inbetween the cells
|
|
146
|
+
// (gutterWidth * (totalColumns - 1)) and the width of the two margins
|
|
147
|
+
// (marginWidth * 2).
|
|
148
|
+
const contentWidth = `(100% - ${
|
|
149
|
+
gutterWidth * (totalColumns - 1)
|
|
150
|
+
}px - ${marginWidth * 2}px)`;
|
|
151
|
+
|
|
152
|
+
// Now that we have the full width we can calculate the width of this
|
|
153
|
+
// particular cell by multiplying the full width (allCellWidth) by
|
|
154
|
+
// the ratio of this cell (cols / totalColumns). But we then need to
|
|
155
|
+
// add back in the missing gutter widths:
|
|
156
|
+
// (gutterWidth * (cols - 1)). This gives us to full width of
|
|
157
|
+
// this particular cell.
|
|
158
|
+
const calcWidth = `calc(${contentWidth} * ${
|
|
159
|
+
cols / totalColumns
|
|
160
|
+
} + ${gutterWidth * (cols - 1)}px)`;
|
|
161
|
+
|
|
162
|
+
// If the contents are a function then we call it with the mediaSize,
|
|
163
|
+
// totalColumns, and cols properties and render the return value.
|
|
164
|
+
const contents =
|
|
165
|
+
typeof children === "function"
|
|
166
|
+
? children({mediaSize, totalColumns, cols})
|
|
167
|
+
: children;
|
|
168
|
+
|
|
169
|
+
// Render a fixed-width cell (flex-basis: size, flex-shrink: 0)
|
|
170
|
+
// that matches the intended width of the cell
|
|
171
|
+
return (
|
|
172
|
+
<View
|
|
173
|
+
style={[
|
|
174
|
+
styles.cellFixed,
|
|
175
|
+
flexBasis(calcWidth),
|
|
176
|
+
style,
|
|
177
|
+
]}
|
|
178
|
+
>
|
|
179
|
+
{contents}
|
|
180
|
+
</View>
|
|
181
|
+
);
|
|
182
|
+
}}
|
|
183
|
+
</MediaLayout>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
**Example:**
|
|
2
|
+
|
|
3
|
+
A row inside of a grid containing many Cells. Each cell has a column width of 1 and will display at different sizes of the viewport (always matching the number of available columns).
|
|
4
|
+
|
|
5
|
+
```jsx
|
|
6
|
+
import {Row} from "@khanacademy/wonder-blocks-grid";
|
|
7
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
8
|
+
import {View, Text} from "@khanacademy/wonder-blocks-core";
|
|
9
|
+
import {MediaLayout} from "@khanacademy/wonder-blocks-layout";
|
|
10
|
+
import {StyleSheet} from "aphrodite";
|
|
11
|
+
|
|
12
|
+
const styleSheets = {
|
|
13
|
+
all: StyleSheet.create({
|
|
14
|
+
background: {
|
|
15
|
+
background: Color.offBlack,
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
cell: {
|
|
19
|
+
height: 100,
|
|
20
|
+
padding: "5px 0",
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
small: StyleSheet.create({
|
|
24
|
+
cell: {
|
|
25
|
+
background: Color.blue,
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
medium: StyleSheet.create({
|
|
29
|
+
cell: {
|
|
30
|
+
background: Color.green,
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
large: StyleSheet.create({
|
|
34
|
+
cell: {
|
|
35
|
+
background: Color.gold,
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
<MediaLayout styleSheets={styleSheets}>
|
|
41
|
+
{({styles}) => <View style={styles.background}>
|
|
42
|
+
<Row>
|
|
43
|
+
<Cell cols={1} style={styles.cell}>1</Cell>
|
|
44
|
+
<Cell cols={1} style={styles.cell}>1</Cell>
|
|
45
|
+
<Cell cols={1} style={styles.cell}>1</Cell>
|
|
46
|
+
<Cell cols={1} style={styles.cell}>1</Cell>
|
|
47
|
+
<Cell mediumCols={1} largeCols={1} style={styles.cell}>1</Cell>
|
|
48
|
+
<Cell mediumCols={1} largeCols={1} style={styles.cell}>1</Cell>
|
|
49
|
+
<Cell mediumCols={1} largeCols={1} style={styles.cell}>1</Cell>
|
|
50
|
+
<Cell mediumCols={1} largeCols={1} style={styles.cell}>1</Cell>
|
|
51
|
+
<Cell largeCols={1} style={styles.cell}>1</Cell>
|
|
52
|
+
<Cell largeCols={1} style={styles.cell}>1</Cell>
|
|
53
|
+
<Cell largeCols={1} style={styles.cell}>1</Cell>
|
|
54
|
+
<Cell largeCols={1} style={styles.cell}>1</Cell>
|
|
55
|
+
</Row>
|
|
56
|
+
</View>}
|
|
57
|
+
</MediaLayout>;
|
|
58
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {
|
|
4
|
+
MediaLayout,
|
|
5
|
+
Strut,
|
|
6
|
+
queryMatchesSize,
|
|
7
|
+
} from "@khanacademy/wonder-blocks-layout";
|
|
8
|
+
import type {MediaQuery} from "@khanacademy/wonder-blocks-layout";
|
|
9
|
+
|
|
10
|
+
type Props = {|
|
|
11
|
+
/**
|
|
12
|
+
* Which media should this cell be renderer on. Defaults to all.
|
|
13
|
+
*/
|
|
14
|
+
mediaQuery: MediaQuery,
|
|
15
|
+
|};
|
|
16
|
+
|
|
17
|
+
type DefaultProps = {|
|
|
18
|
+
mediaQuery: $PropertyType<Props, "mediaQuery">,
|
|
19
|
+
|};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gutter is a component whose width is set based on the size of grid currently
|
|
23
|
+
* being displayed. Used for spacing out cells from each other. The gutter
|
|
24
|
+
* itself doesn't hold any content, it just spaces it out.
|
|
25
|
+
*
|
|
26
|
+
* Gutters are inserted automatically inside of a [Row](#row) in-between Cells.
|
|
27
|
+
* You may only need to use Gutters if you're manually building your own
|
|
28
|
+
* sub-grid, or some-such (this should be relatively rare).
|
|
29
|
+
*
|
|
30
|
+
* By default (with no properties specified) it will display at all
|
|
31
|
+
* grid sizes. If you specify the `small`, `medium`, or `large`
|
|
32
|
+
* props then the component will only be shown at those grid sizes.
|
|
33
|
+
*/
|
|
34
|
+
export default class Gutter extends React.Component<Props> {
|
|
35
|
+
static defaultProps: DefaultProps = {
|
|
36
|
+
mediaQuery: "all",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
render(): React.Node {
|
|
40
|
+
return (
|
|
41
|
+
<MediaLayout>
|
|
42
|
+
{({mediaSize, mediaSpec}) => {
|
|
43
|
+
const spec = mediaSpec[mediaSize];
|
|
44
|
+
if (!spec) {
|
|
45
|
+
throw new Error(`mediaSpec.${mediaSize} is undefined`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const {gutterWidth} = spec;
|
|
49
|
+
|
|
50
|
+
if (!queryMatchesSize(this.props.mediaQuery, mediaSize)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return <Strut size={gutterWidth} />;
|
|
55
|
+
}}
|
|
56
|
+
</MediaLayout>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
4
|
+
import {
|
|
5
|
+
MediaLayout,
|
|
6
|
+
Strut,
|
|
7
|
+
queryMatchesSize,
|
|
8
|
+
} from "@khanacademy/wonder-blocks-layout";
|
|
9
|
+
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
10
|
+
import type {MediaQuery, MediaSize} from "@khanacademy/wonder-blocks-layout";
|
|
11
|
+
|
|
12
|
+
import styles from "../util/styles.js";
|
|
13
|
+
import Gutter from "./gutter.js";
|
|
14
|
+
import Cell from "./cell.js";
|
|
15
|
+
|
|
16
|
+
type Props = {|
|
|
17
|
+
/**
|
|
18
|
+
* Which media should this cell be renderer on. Defaults to all.
|
|
19
|
+
*/
|
|
20
|
+
mediaQuery: MediaQuery,
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The child components to populate inside the row. Typically this will be
|
|
24
|
+
* a [Cell](#cell), but it can also include any elements
|
|
25
|
+
* that could fit in a [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox).
|
|
26
|
+
* Can also accept a function which receives the `mediaSize` and
|
|
27
|
+
* `totalColumns` and should return some React Nodes to render.
|
|
28
|
+
*/
|
|
29
|
+
children:
|
|
30
|
+
| React.Node
|
|
31
|
+
| (({|
|
|
32
|
+
mediaSize: MediaSize,
|
|
33
|
+
totalColumns: number,
|
|
34
|
+
|}) => React.Node),
|
|
35
|
+
|
|
36
|
+
/** The styling to apply to the row. */
|
|
37
|
+
style?: StyleType,
|
|
38
|
+
|};
|
|
39
|
+
|
|
40
|
+
type DefaultProps = {|
|
|
41
|
+
mediaQuery: $PropertyType<Props, "mediaQuery">,
|
|
42
|
+
|};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A Row holds all of the Cells that make up the contents of the grid. A row
|
|
46
|
+
* also provides the margins on the sides and inserts the gutter spacing
|
|
47
|
+
* in-between the cells. Typically this component will hold a [Cell](#cell),
|
|
48
|
+
* but it can also include any elements that could fit in a
|
|
49
|
+
* [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox).
|
|
50
|
+
*
|
|
51
|
+
* This component will automatically attempt to insert [Gutters](#gutter)
|
|
52
|
+
* in-between all child elements. Additionally, it'll perform some basic checks
|
|
53
|
+
* to ensure that no impossible layouts are accidentally generated.
|
|
54
|
+
*
|
|
55
|
+
* Typically this component will be used as a child of a [Grid](#grid-1),
|
|
56
|
+
* but it's not a requirement, you can use it as a descendant, as well.
|
|
57
|
+
*
|
|
58
|
+
* By default (with no properties specified) it will display at all
|
|
59
|
+
* grid sizes. If you specify the `small`, `medium`, or `large`
|
|
60
|
+
* props then the component will only be shown at those grid sizes.
|
|
61
|
+
*/
|
|
62
|
+
export default class Row extends React.Component<Props> {
|
|
63
|
+
static defaultProps: DefaultProps = {
|
|
64
|
+
mediaQuery: "all",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
render(): React.Node {
|
|
68
|
+
const {style, children} = this.props;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<MediaLayout>
|
|
72
|
+
{({mediaSize, mediaSpec}) => {
|
|
73
|
+
const spec = mediaSpec[mediaSize];
|
|
74
|
+
if (!spec) {
|
|
75
|
+
throw new Error(`mediaSpec.${mediaSize} is undefined`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const {marginWidth, maxWidth, totalColumns} = spec;
|
|
79
|
+
const shouldDisplay = queryMatchesSize(
|
|
80
|
+
this.props.mediaQuery,
|
|
81
|
+
mediaSize,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Don't render the row if it's been disabled at this size
|
|
85
|
+
if (!shouldDisplay) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// If the contents are a function then we call it with the mediaSize and
|
|
90
|
+
// totalColumns properties and render the return value.
|
|
91
|
+
const contents =
|
|
92
|
+
typeof children === "function"
|
|
93
|
+
? children({mediaSize, totalColumns})
|
|
94
|
+
: children;
|
|
95
|
+
|
|
96
|
+
const filteredContents: Array<React.Node> = (React.Children.toArray(
|
|
97
|
+
contents,
|
|
98
|
+
): Array<React.Node>)
|
|
99
|
+
// Go through all of the contents and pre-emptively remove anything
|
|
100
|
+
// that shouldn't be rendered.
|
|
101
|
+
.filter(
|
|
102
|
+
// Flow doesn't let us check .type on a non-null React.Node so
|
|
103
|
+
// we have to cast it to any.
|
|
104
|
+
(item: any) =>
|
|
105
|
+
Cell.isClassOf(item)
|
|
106
|
+
? Cell.shouldDisplay(item.props, mediaSize)
|
|
107
|
+
: true,
|
|
108
|
+
)
|
|
109
|
+
// Intersperse Gutter elements between the cells.
|
|
110
|
+
.reduce(
|
|
111
|
+
(acc, elem, index) => [
|
|
112
|
+
...acc,
|
|
113
|
+
elem,
|
|
114
|
+
<Gutter key={`gutter-${index}`} />,
|
|
115
|
+
],
|
|
116
|
+
[],
|
|
117
|
+
)
|
|
118
|
+
// We only want gutters between each cell in the row. The reduce
|
|
119
|
+
// adds a gutter after every cell so we need to remove the last
|
|
120
|
+
// element which is an unnecessary gutteer.
|
|
121
|
+
.slice(0, -1);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<View
|
|
125
|
+
style={[
|
|
126
|
+
styles.row,
|
|
127
|
+
!!maxWidth && styles.rowMaxWidth,
|
|
128
|
+
!!maxWidth && {maxWidth},
|
|
129
|
+
style,
|
|
130
|
+
]}
|
|
131
|
+
>
|
|
132
|
+
<Strut size={marginWidth} />
|
|
133
|
+
{filteredContents}
|
|
134
|
+
<Strut size={marginWidth} />
|
|
135
|
+
</View>
|
|
136
|
+
);
|
|
137
|
+
}}
|
|
138
|
+
</MediaLayout>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|