Skip to content

Commit 3e82f16

Browse files
author
Dobromir Hristov
authored
Support the @Row directive (#409)
closes rdar://97715742
1 parent 3283805 commit 3e82f16

File tree

6 files changed

+256
-1
lines changed

6 files changed

+256
-1
lines changed

src/components/ContentNode.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import Table from './ContentNode/Table.vue';
2323
import StrikeThrough from './ContentNode/StrikeThrough.vue';
2424
import Small from './ContentNode/Small.vue';
2525
import BlockVideo from './ContentNode/BlockVideo.vue';
26+
import Column from './ContentNode/Column.vue';
27+
import Row from './ContentNode/Row.vue';
2628
2729
const BlockType = {
2830
aside: 'aside',
@@ -37,6 +39,7 @@ const BlockType = {
3739
dictionaryExample: 'dictionaryExample',
3840
small: 'small',
3941
video: 'video',
42+
row: 'row',
4043
};
4144
4245
const InlineType = {
@@ -299,6 +302,15 @@ function renderNode(createElement, references) {
299302
})
300303
) : null;
301304
}
305+
case BlockType.row: {
306+
return createElement(
307+
Row, { props: { columns: node.numberOfColumns } }, node.columns.map(col => (
308+
createElement(
309+
Column, { props: { span: col.size } }, renderChildren(col.content),
310+
)
311+
)),
312+
);
313+
}
302314
case InlineType.codeVoice:
303315
return createElement(CodeVoice, {}, (
304316
node.code

src/components/ContentNode/Column.vue

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!--
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2022 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
-->
10+
<template>
11+
<div class="column" :style="style">
12+
<slot />
13+
</div>
14+
</template>
15+
16+
<script>
17+
export default {
18+
name: 'Column',
19+
props: {
20+
span: {
21+
type: Number,
22+
default: null,
23+
},
24+
},
25+
computed: {
26+
style: ({ span }) => ({ '--col-span': span }),
27+
},
28+
};
29+
</script>
30+
31+
<style scoped lang='scss'>
32+
@import 'docc-render/styles/_core.scss';
33+
34+
.column {
35+
grid-column: span var(--col-span);
36+
min-width: 0;
37+
@include breakpoint(small) {
38+
grid-column: span 1;
39+
}
40+
}
41+
</style>

src/components/ContentNode/Row.vue

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<!--
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2022 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
-->
10+
<template>
11+
<div class="row" :style="style" :class="{ 'with-columns': columns }">
12+
<slot />
13+
</div>
14+
</template>
15+
16+
<script>
17+
/**
18+
* A Row component that accepts an optional `columns` prop.
19+
* When columns is passed, the grid will have that exact number of columns.
20+
* If no `columns` provided, width is split up equally across each cell.
21+
*/
22+
export default {
23+
name: 'Row',
24+
props: {
25+
columns: {
26+
type: Number,
27+
default: null,
28+
required: false,
29+
validator: v => v > 0,
30+
},
31+
gap: {
32+
type: Number,
33+
required: false,
34+
},
35+
},
36+
computed: {
37+
style: ({ columns, gap }) => ({
38+
'--col-count': columns,
39+
'--col-gap': gap && `${gap}px`,
40+
}),
41+
},
42+
};
43+
</script>
44+
45+
<style scoped lang='scss'>
46+
@import 'docc-render/styles/_core.scss';
47+
48+
.row {
49+
display: grid;
50+
grid-auto-flow: column;
51+
grid-auto-columns: 1fr;
52+
grid-gap: var(--col-gap, #{$article-stacked-margin-small});
53+
54+
&.with-columns {
55+
grid-template-columns: repeat(var(--col-count), 1fr);
56+
grid-auto-flow: row;
57+
58+
@include breakpoint(small) {
59+
grid-template-columns: 1fr;
60+
}
61+
}
62+
63+
@include breakpoint(small) {
64+
grid-template-columns: 1fr;
65+
grid-auto-flow: row;
66+
}
67+
68+
/deep/ + * {
69+
margin-top: $stacked-margin-large;
70+
}
71+
}
72+
</style>

tests/unit/components/ContentNode.spec.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import LinkableHeading from 'docc-render/components/ContentNode/LinkableHeading.
2424
import StrikeThrough from 'docc-render/components/ContentNode/StrikeThrough.vue';
2525
import Small from '@/components/ContentNode/Small.vue';
2626
import BlockVideo from '@/components/ContentNode/BlockVideo.vue';
27+
import Row from '@/components/ContentNode/Row.vue';
28+
import Column from '@/components/ContentNode/Column.vue';
2729

2830
const { TableHeaderStyle } = ContentNode.constants;
2931

@@ -342,6 +344,54 @@ describe('ContentNode', () => {
342344
});
343345
});
344346

347+
describe('with type="row"', () => {
348+
it('renders a `<Row>` and `<Column>`', () => {
349+
const wrapper = mountWithItem({
350+
type: 'row',
351+
numberOfColumns: 4,
352+
columns: [
353+
{
354+
size: 2,
355+
content: [
356+
{
357+
type: 'paragraph',
358+
inlineContent: [
359+
{
360+
type: 'text',
361+
text: 'foo',
362+
},
363+
],
364+
},
365+
],
366+
},
367+
{
368+
content: [
369+
{
370+
type: 'paragraph',
371+
inlineContent: [
372+
{
373+
type: 'text',
374+
text: 'bar',
375+
},
376+
],
377+
},
378+
],
379+
},
380+
],
381+
});
382+
const grid = wrapper.find(Row);
383+
expect(grid.props()).toEqual({
384+
columns: 4,
385+
});
386+
const columns = grid.findAll(Column);
387+
expect(columns).toHaveLength(2);
388+
expect(columns.at(0).props()).toEqual({ span: 2 });
389+
expect(columns.at(0).find('p').text()).toBe('foo');
390+
expect(columns.at(1).props()).toEqual({ span: null });
391+
expect(columns.at(1).find('p').text()).toBe('bar');
392+
});
393+
});
394+
345395
describe('with type="codeVoice"', () => {
346396
it('renders a `CodeVoice`', () => {
347397
const wrapper = mountWithItem({
@@ -1227,7 +1277,8 @@ describe('ContentNode', () => {
12271277

12281278
const content = wrapper.find(StrikeThrough);
12291279
// assert the `strong` tag is rendered
1230-
expect(content.html()).toBe('<strikethrough-stub>2<strong>strong</strong></strikethrough-stub>');
1280+
expect(content.html())
1281+
.toBe('<strikethrough-stub>2<strong>strong</strong></strikethrough-stub>');
12311282
});
12321283
});
12331284

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* This source file is part of the Swift.org open source project
3+
*
4+
* Copyright (c) 2022 Apple Inc. and the Swift project authors
5+
* Licensed under Apache License v2.0 with Runtime Library Exception
6+
*
7+
* See https://swift.org/LICENSE.txt for license information
8+
* See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Column from '@/components/ContentNode/Column.vue';
12+
import { shallowMount } from '@vue/test-utils';
13+
14+
const createWrapper = props => shallowMount(Column, {
15+
slots: {
16+
default: 'Default Content',
17+
},
18+
...props,
19+
});
20+
21+
describe('Column', () => {
22+
it('renders the Column', () => {
23+
const wrapper = createWrapper();
24+
expect(wrapper.classes()).toContain('column');
25+
expect(wrapper.text()).toBe('Default Content');
26+
expect(wrapper.vm.style).toHaveProperty('--col-span', null);
27+
wrapper.setProps({
28+
span: 5,
29+
});
30+
expect(wrapper.vm.style).toHaveProperty('--col-span', 5);
31+
});
32+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* This source file is part of the Swift.org open source project
3+
*
4+
* Copyright (c) 2022 Apple Inc. and the Swift project authors
5+
* Licensed under Apache License v2.0 with Runtime Library Exception
6+
*
7+
* See https://swift.org/LICENSE.txt for license information
8+
* See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Row from '@/components/ContentNode/Row.vue';
12+
import { shallowMount } from '@vue/test-utils';
13+
14+
const createWrapper = props => shallowMount(Row, {
15+
slots: {
16+
default: 'Slot Content',
17+
},
18+
...props,
19+
});
20+
21+
describe('Row', () => {
22+
it('renders the Row', () => {
23+
const wrapper = createWrapper();
24+
expect(wrapper.classes()).toContain('row');
25+
expect(wrapper.classes()).not.toContain('with-columns');
26+
expect(wrapper.text()).toContain('Slot Content');
27+
});
28+
29+
it('renders with columns in mind', () => {
30+
const wrapper = createWrapper({
31+
propsData: {
32+
columns: 4,
33+
},
34+
});
35+
expect(wrapper.classes()).toContain('with-columns');
36+
expect(wrapper.vm.style).toHaveProperty('--col-count', 4);
37+
});
38+
39+
it('provides a --col-gap', () => {
40+
const wrapper = createWrapper({
41+
propsData: {
42+
gap: 10,
43+
},
44+
});
45+
expect(wrapper.vm.style).toHaveProperty('--col-gap', '10px');
46+
});
47+
});

0 commit comments

Comments
 (0)