diff --git a/src-next/col/README.md b/src-next/col/README.md
new file mode 100644
index 000000000..d6be11217
--- /dev/null
+++ b/src-next/col/README.md
@@ -0,0 +1,119 @@
+# Layout
+
+### Intro
+
+Quickly and easily create layouts with `van-row` and `van-col`
+
+### Install
+
+```js
+import Vue from 'vue';
+import { Col, Row } from 'vant';
+
+Vue.use(Col);
+Vue.use(Row);
+```
+
+## Usage
+
+### Basic Usage
+
+Layout are based on 24-column. The attribute `span` in `Col` means the number of column the grid spans. Of course, You can use `offset` attribute to set number of spacing on the left side of the grid.
+
+```html
+
+ span: 8
+ span: 8
+ span: 8
+
+
+
+ span: 4
+ offset: 4, span: 10
+ span: 6
+
+
+
+ offset: 12, span: 12
+
+```
+
+### Column Spacing
+
+Set grid spacing using `gutter` attribute. The default value is 0
+
+```html
+
+ span: 8
+ span: 8
+ span: 8
+
+```
+
+### Flex Layout
+
+Setting `type` to `flex` to enable flex layout
+
+```html
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+```
+
+## API
+
+### Row Props
+
+| Attribute | Description | Type | Default |
+| --- | --- | --- | --- |
+| type | Layout type, can be set to `flex` | _string_ | - |
+| gutter | Grid spacing(px) | _number \| string_ | - |
+| tag | Custom element tag | _string_ | `div` |
+| justify | Flex main axis,can be set to end/center/space-around/space-between | _string_ | `start` |
+| align | Flex cross axis, be set to center/bottom | _string_ | `top` |
+
+### Col Props
+
+| Attribute | Description | Type | Default |
+| --- | --- | --- | --- |
+| span | number of column the grid spans | _number \| string_ | - |
+| offset | number of spacing on the left side of the grid | _number \| string_ | - |
+| tag | Custom element tag | _string_ | `div` |
+
+### Row Events
+
+| Event | Description | Arguments |
+| ----- | ------------------------ | -------------- |
+| click | Triggered when click row | _event: Event_ |
+
+### Col Events
+
+| Event | Description | Arguments |
+| ----- | ------------------------ | -------------- |
+| click | Triggered when click col | _event: Event_ |
diff --git a/src-next/col/README.zh-CN.md b/src-next/col/README.zh-CN.md
new file mode 100644
index 000000000..7eb405993
--- /dev/null
+++ b/src-next/col/README.zh-CN.md
@@ -0,0 +1,124 @@
+# Layout 布局
+
+### 介绍
+
+Layout 提供了`van-row`和`van-col`两个组件来进行行列布局
+
+### 引入
+
+```js
+import Vue from 'vue';
+import { Col, Row } from 'vant';
+
+Vue.use(Col);
+Vue.use(Row);
+```
+
+## 代码演示
+
+### 基础用法
+
+Layout 组件提供了`24列栅格`,通过在`Col`上添加`span`属性设置列所占的宽度百分比
+此外,添加`offset`属性可以设置列的偏移宽度,计算方式与 span 相同
+
+```html
+
+ span: 8
+ span: 8
+ span: 8
+
+
+
+ span: 4
+ offset: 4, span: 10
+
+
+
+ offset: 12, span: 12
+
+```
+
+### 设置列元素间距
+
+通过`gutter`属性可以设置列元素之间的间距,默认间距为 0
+
+```html
+
+ span: 8
+ span: 8
+ span: 8
+
+```
+
+### Flex 布局
+
+将 `type` 属性设置为 flex 可以启用 flex 布局,便于进行灵活的对齐
+
+```html
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+```
+
+## API
+
+### Row Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| type | 布局方式,可选值为`flex` | _string_ | - |
+| gutter | 列元素之间的间距(单位为 px) | _number \| string_ | - |
+| tag | 自定义元素标签 | _string_ | `div` |
+| justify | Flex 主轴对齐方式,可选值为 `end` `center`
`space-around` `space-between` | _string_ | `start` |
+| align | Flex 交叉轴对齐方式,可选值为 `center` `bottom` | _string_ | `top` |
+
+### Col Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| ------ | -------------- | ------------------ | ------ |
+| span | 列元素宽度 | _number \| string_ | - |
+| offset | 列元素偏移距离 | _number \| string_ | - |
+| tag | 自定义元素标签 | _string_ | `div` |
+
+### Row Events
+
+| 事件名 | 说明 | 回调参数 |
+| ------ | ---------- | -------------- |
+| click | 点击时触发 | _event: Event_ |
+
+### Col Events
+
+| 事件名 | 说明 | 回调参数 |
+| ------ | ---------- | -------------- |
+| click | 点击时触发 | _event: Event_ |
diff --git a/src-next/col/demo/index.vue b/src-next/col/demo/index.vue
new file mode 100644
index 000000000..5b396922a
--- /dev/null
+++ b/src-next/col/demo/index.vue
@@ -0,0 +1,112 @@
+
+
+
+
+ span: 8
+ span: 8
+ span: 8
+
+
+
+ span: 4
+
+ offset: 4, span: 10
+
+
+
+
+
+ offset: 12, span: 12
+
+
+
+
+
+
+ span: 8
+ span: 8
+ span: 8
+
+
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+ span: 6
+ span: 6
+ span: 6
+
+
+
+
+
+
+
+
diff --git a/src-next/col/index.js b/src-next/col/index.js
new file mode 100644
index 000000000..99f0c3e23
--- /dev/null
+++ b/src-next/col/index.js
@@ -0,0 +1,51 @@
+import { createNamespace } from '../utils';
+import { ChildrenMixin } from '../mixins/relation';
+
+const [createComponent, bem] = createNamespace('col');
+
+export default createComponent({
+ mixins: [ChildrenMixin('vanRow')],
+
+ props: {
+ span: [Number, String],
+ offset: [Number, String],
+ tag: {
+ type: String,
+ default: 'div',
+ },
+ },
+
+ computed: {
+ style() {
+ const { index } = this;
+ const { spaces } = this.parent || {};
+
+ if (spaces && spaces[index]) {
+ const { left, right } = spaces[index];
+ return {
+ paddingLeft: left ? `${left}px` : null,
+ paddingRight: right ? `${right}px` : null,
+ };
+ }
+ },
+ },
+
+ methods: {
+ onClick(event) {
+ this.$emit('click', event);
+ },
+ },
+
+ render() {
+ const { span, offset } = this;
+ return (
+
+ {this.$slots.default?.()}
+
+ );
+ },
+});
diff --git a/src-next/col/index.less b/src-next/col/index.less
new file mode 100644
index 000000000..35fa2f4f5
--- /dev/null
+++ b/src-next/col/index.less
@@ -0,0 +1,20 @@
+@import '../style/var';
+
+.van-col {
+ float: left;
+ box-sizing: border-box;
+ min-height: 1px;
+}
+
+.generate-col(24);
+.generate-col(@n, @i: 1) when (@i =< @n) {
+ .van-col--@{i} {
+ width: @i * 100% / 24;
+ }
+
+ .van-col--offset-@{i} {
+ margin-left: @i * 100% / 24;
+ }
+
+ .generate-col(@n, (@i + 1));
+}
diff --git a/src-next/col/test/__snapshots__/demo.spec.js.snap b/src-next/col/test/__snapshots__/demo.spec.js.snap
new file mode 100644
index 000000000..7c04f9b40
--- /dev/null
+++ b/src-next/col/test/__snapshots__/demo.spec.js.snap
@@ -0,0 +1,58 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders demo correctly 1`] = `
+
+
+
+
span: 8
+
span: 8
+
span: 8
+
+
+
span: 4
+
+ offset: 4, span: 10
+
+
+
+
+ offset: 12, span: 12
+
+
+
+
+
+
span: 8
+
span: 8
+
span: 8
+
+
+
+
+
span: 6
+
span: 6
+
span: 6
+
+
+
span: 6
+
span: 6
+
span: 6
+
+
+
span: 6
+
span: 6
+
span: 6
+
+
+
span: 6
+
span: 6
+
span: 6
+
+
+
span: 6
+
span: 6
+
span: 6
+
+
+
+`;
diff --git a/src-next/col/test/__snapshots__/index.spec.js.snap b/src-next/col/test/__snapshots__/index.spec.js.snap
new file mode 100644
index 000000000..2d4588095
--- /dev/null
+++ b/src-next/col/test/__snapshots__/index.spec.js.snap
@@ -0,0 +1,22 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`gutter prop 1`] = `
+
+
24
+
12
+
12
+
8
+
8
+
8
+
6
+
6
+
6
+
6
+
7
+
6
+
5
+
4
+
3
+
2
+
+`;
diff --git a/src-next/col/test/demo.spec.js b/src-next/col/test/demo.spec.js
new file mode 100644
index 000000000..5c70922b5
--- /dev/null
+++ b/src-next/col/test/demo.spec.js
@@ -0,0 +1,4 @@
+import Demo from '../demo';
+import { snapshotDemo } from '../../../test/demo';
+
+snapshotDemo(Demo);
diff --git a/src-next/col/test/index.spec.js b/src-next/col/test/index.spec.js
new file mode 100644
index 000000000..dfe0a20ac
--- /dev/null
+++ b/src-next/col/test/index.spec.js
@@ -0,0 +1,48 @@
+import Col from '..';
+import Row from '../../row';
+import { mount } from '../../../test';
+
+test('Col click event', () => {
+ const wrapper = mount(Col);
+ wrapper.trigger('click');
+
+ expect(wrapper.emitted('click')).toBeTruthy();
+});
+
+test('Row click event', () => {
+ const wrapper = mount(Row);
+ wrapper.trigger('click');
+
+ expect(wrapper.emitted('click')).toBeTruthy();
+});
+
+test('gutter prop', () => {
+ const wrapper = mount({
+ template: `
+
+ 24
+
+ 12
+ 12
+
+ 8
+ 8
+ 8
+
+ 6
+ 6
+ 6
+ 6
+
+ 7
+ 6
+ 5
+ 4
+ 3
+ 2
+
+ `,
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src-next/row/index.js b/src-next/row/index.js
new file mode 100644
index 000000000..9500641c6
--- /dev/null
+++ b/src-next/row/index.js
@@ -0,0 +1,87 @@
+import { createNamespace } from '../utils';
+import { ParentMixin } from '../mixins/relation';
+
+const [createComponent, bem] = createNamespace('row');
+
+export default createComponent({
+ mixins: [ParentMixin('vanRow')],
+
+ props: {
+ type: String,
+ align: String,
+ justify: String,
+ tag: {
+ type: String,
+ default: 'div',
+ },
+ gutter: {
+ type: [Number, String],
+ default: 0,
+ },
+ },
+
+ computed: {
+ spaces() {
+ const gutter = Number(this.gutter);
+
+ if (!gutter) {
+ return;
+ }
+
+ const spaces = [];
+ const groups = [[]];
+
+ let totalSpan = 0;
+ this.children.forEach((item, index) => {
+ totalSpan += Number(item.span);
+
+ if (totalSpan > 24) {
+ groups.push([index]);
+ totalSpan -= 24;
+ } else {
+ groups[groups.length - 1].push(index);
+ }
+ });
+
+ groups.forEach((group) => {
+ const averagePadding = (gutter * (group.length - 1)) / group.length;
+
+ group.forEach((item, index) => {
+ if (index === 0) {
+ spaces.push({ right: averagePadding });
+ } else {
+ const left = gutter - spaces[item - 1].right;
+ const right = averagePadding - left;
+ spaces.push({ left, right });
+ }
+ });
+ });
+
+ return spaces;
+ },
+ },
+
+ methods: {
+ onClick(event) {
+ this.$emit('click', event);
+ },
+ },
+
+ render() {
+ const { align, justify } = this;
+ const flex = this.type === 'flex';
+
+ return (
+
+ {this.$slots.default?.()}
+
+ );
+ },
+});
diff --git a/src-next/utils/vnodes.ts b/src-next/utils/vnodes.ts
index 471d2ea38..34cc6bf37 100644
--- a/src-next/utils/vnodes.ts
+++ b/src-next/utils/vnodes.ts
@@ -1,33 +1,34 @@
-import { VNode } from 'vue';
+// import { VNode } from 'vue';
-function flattenVNodes(vnodes: VNode[]) {
- const result: VNode[] = [];
+// function flattenVNodes(vnodes: VNode[]) {
+// const result: VNode[] = [];
- function traverse(vnodes: VNode[]) {
- vnodes.forEach((vnode) => {
- result.push(vnode);
+// function traverse(vnodes: VNode[]) {
+// vnodes.forEach((vnode) => {
+// result.push(vnode);
- if (vnode.componentInstance) {
- traverse(vnode.componentInstance.$children.map((item) => item.$vnode));
- }
+// if (vnode.componentInstance) {
+// traverse(vnode.componentInstance.$children.map((item) => item.$vnode));
+// }
- if (vnode.children) {
- traverse(vnode.children);
- }
- });
- }
+// if (vnode.children) {
+// traverse(vnode.children);
+// }
+// });
+// }
- traverse(vnodes);
- return result;
-}
+// traverse(vnodes);
+// return result;
+// }
+// TODO
// sort children instances by vnodes order
export function sortChildren(children: Vue[], parent: Vue) {
- const { componentOptions } = parent.$vnode;
- if (!componentOptions || !componentOptions.children) {
- return;
- }
+ // const { componentOptions } = parent.$vnode;
+ // if (!componentOptions || !componentOptions.children) {
+ // return;
+ // }
- const vnodes = flattenVNodes(componentOptions.children);
- children.sort((a, b) => vnodes.indexOf(a.$vnode) - vnodes.indexOf(b.$vnode));
+ // const vnodes = flattenVNodes(componentOptions.children);
+ // children.sort((a, b) => vnodes.indexOf(a.$vnode) - vnodes.indexOf(b.$vnode));
}
diff --git a/vant.config.js b/vant.config.js
index 9f64456b3..67ce5e06d 100644
--- a/vant.config.js
+++ b/vant.config.js
@@ -94,10 +94,10 @@ module.exports = {
path: 'image',
title: 'Image 图片',
},
- // {
- // path: 'col',
- // title: 'Layout 布局',
- // },
+ {
+ path: 'col',
+ title: 'Layout 布局',
+ },
// {
// path: 'popup',
// title: 'Popup 弹出层',
@@ -441,10 +441,10 @@ module.exports = {
path: 'image',
title: 'Image',
},
- // {
- // path: 'col',
- // title: 'Layout',
- // },
+ {
+ path: 'col',
+ title: 'Layout',
+ },
// {
// path: 'popup',
// title: 'Popup',