diff --git a/src-next/cell/README.md b/src-next/cell/README.md
new file mode 100644
index 000000000..63e9248b5
--- /dev/null
+++ b/src-next/cell/README.md
@@ -0,0 +1,164 @@
+# Cell
+
+### Install
+
+```js
+import Vue from 'vue';
+import { Cell, CellGroup } from 'vant';
+
+Vue.use(Cell);
+Vue.use(CellGroup);
+```
+
+## Usage
+
+### Basic Usage
+
+```html
+
+
+
+
+```
+
+### Size
+
+```html
+
+
+
+
+```
+
+### Left Icon
+
+```html
+
+
+
+```
+
+### Value only
+
+```html
+
+
+
+```
+
+### Link
+
+```html
+
+
+
+
+
+```
+
+### Router
+
+```html
+
+
+
+
+```
+
+### Group Title
+
+```html
+
+
+
+
+
+
+```
+
+### Use Slots
+
+```html
+
+
+
+ 单元格
+ 标签
+
+
+
+
+
+
+
+
+
+```
+
+### Vertical Center
+
+```html
+
+```
+
+## API
+
+### CellGroup Props
+
+| Attribute | Description | Type | Default |
+| --------- | ---------------------------- | --------- | ------- |
+| title | Group title | _string_ | - |
+| border | Whether to show outer border | _boolean_ | `true` |
+
+### Cell Props
+
+| Attribute | Description | Type | Default |
+| --- | --- | --- | --- |
+| title | Title | _number \| string_ | - |
+| value | Right text | _number \| string_ | - |
+| label | Description below the title | _string_ | - |
+| size | Size,can be set to `large` | _string_ | - |
+| icon | Left Icon | _string_ | - |
+| icon-prefix `v2.5.3` | Icon className prefix | _string_ | `van-icon` |
+| border | Whether to show inner border | _boolean_ | `true` |
+| center | Whether to center content vertically | _boolean_ | `true` |
+| url | Link URL | _string_ | - |
+| to | Target route of the link, same as to of vue-router | _string \| object_ | - |
+| replace | If true, the navigation will not leave a history record | _boolean_ | `false` |
+| clickable | Whether to show click feedback when clicked | _boolean_ | `false` |
+| is-link | Whether to show link icon | _boolean_ | `false` |
+| required | Whether to show required mark | _boolean_ | `false` |
+| arrow-direction | Can be set to `left` `up` `down` | _string_ | `right` |
+| title-style | Title style | _any_ | - |
+| title-class | Title className | _any_ | - |
+| value-class | Value className | _any_ | - |
+| label-class | Label className | _any_ | - |
+
+### Cell Events
+
+| Event | Description | Arguments |
+| ----- | ------------------------- | -------------- |
+| click | Triggered when click cell | _event: Event_ |
+
+### CellGroup Slots
+
+| Name | Description |
+| ------- | ------------ |
+| default | Default slot |
+| title | Custom title |
+
+### Cell Slots
+
+| Name | Description |
+| ---------- | --------------------------------- |
+| default | Custom value |
+| icon | Custom icon |
+| title | Custom title |
+| label | Custom label |
+| right-icon | Custom right icon |
+| extra | Custom extra content on the right |
diff --git a/src-next/cell/README.zh-CN.md b/src-next/cell/README.zh-CN.md
new file mode 100644
index 000000000..d02da1172
--- /dev/null
+++ b/src-next/cell/README.zh-CN.md
@@ -0,0 +1,167 @@
+# Cell 单元格
+
+### 引入
+
+```js
+import Vue from 'vue';
+import { Cell, CellGroup } from 'vant';
+
+Vue.use(Cell);
+Vue.use(CellGroup);
+```
+
+## 代码演示
+
+### 基础用法
+
+`Cell`可以单独使用,也可以与`CellGroup`搭配使用。`CellGroup`可以为`Cell`提供上下外边框
+
+```html
+
+
+
+
+```
+
+### 单元格大小
+
+通过`size`属性可以控制单元格的大小
+
+```html
+
+
+```
+
+### 展示图标
+
+通过`icon`属性在标题左侧展示图标
+
+```html
+
+```
+
+### 只设置 value
+
+只设置`value`时,内容会靠左对齐
+
+```html
+
+```
+
+### 展示箭头
+
+设置`is-link`属性后会在单元格右侧显示箭头,并且可以通过`arrow-direction`属性控制箭头方向
+
+```html
+
+
+
+```
+
+### 页面导航
+
+可以通过`url`属性进行 URL 跳转,或通过`to`属性进行路由跳转
+
+```html
+
+
+```
+
+### 分组标题
+
+通过`CellGroup`的`title`属性可以指定分组标题
+
+```html
+
+
+
+
+
+
+```
+
+### 使用插槽
+
+如以上用法不能满足你的需求,可以使用插槽来自定义内容
+
+```html
+
+
+
+ 单元格
+ 标签
+
+
+
+
+
+
+
+
+
+```
+
+### 垂直居中
+
+通过`center`属性可以让`Cell`的左右内容都垂直居中
+
+```html
+
+```
+
+## API
+
+### CellGroup Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| ------ | -------------- | --------- | ------ |
+| title | 分组标题 | _string_ | `-` |
+| border | 是否显示外边框 | _boolean_ | `true` |
+
+### Cell Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| title | 左侧标题 | _number \| string_ | - |
+| value | 右侧内容 | _number \| string_ | - |
+| label | 标题下方的描述信息 | _string_ | - |
+| size | 单元格大小,可选值为 `large` | _string_ | - |
+| icon | 左侧[图标名称](#/zh-CN/icon)或图片链接 | _string_ | - |
+| icon-prefix `v2.5.3` | 图标类名前缀,同 Icon 组件的 [class-prefix 属性](#/zh-CN/icon#props) | _string_ | `van-icon` |
+| url | 点击后跳转的链接地址 | _string_ | - |
+| to | 点击后跳转的目标路由对象,同 vue-router 的 [to 属性](https://router.vuejs.org/zh/api/#to) | _string \| object_ | - |
+| border | 是否显示内边框 | _boolean_ | `true` |
+| replace | 是否在跳转时替换当前页面历史 | _boolean_ | `false` |
+| clickable | 是否开启点击反馈 | _boolean_ | `false` |
+| is-link | 是否展示右侧箭头并开启点击反馈 | _boolean_ | `false` |
+| required | 是否显示表单必填星号 | _boolean_ | `false` |
+| center | 是否使内容垂直居中 | _boolean_ | `false` |
+| arrow-direction | 箭头方向,可选值为 `left` `up` `down` | _string_ | `right` |
+| title-style | 左侧标题额外样式 | _any_ | - |
+| title-class | 左侧标题额外类名 | _any_ | - |
+| value-class | 右侧内容额外类名 | _any_ | - |
+| label-class | 描述信息额外类名 | _any_ | - |
+
+### Cell Events
+
+| 事件名 | 说明 | 回调参数 |
+| ------ | ---------------- | -------------- |
+| click | 点击单元格时触发 | _event: Event_ |
+
+### CellGroup Slots
+
+| 名称 | 说明 |
+| ------- | -------------- |
+| default | 默认插槽 |
+| title | 自定义分组标题 |
+
+### Cell Slots
+
+| 名称 | 说明 |
+| ---------- | ----------------------------- |
+| default | 自定义右侧 value 的内容 |
+| title | 自定义左侧 title 的内容 |
+| label | 自定义标题下方 label 的内容 |
+| icon | 自定义左侧图标 |
+| right-icon | 自定义右侧按钮,默认为`arrow` |
+| extra | 自定义单元格最右侧的额外内容 |
diff --git a/src-next/cell/demo/index.vue b/src-next/cell/demo/index.vue
new file mode 100644
index 000000000..f7b47398a
--- /dev/null
+++ b/src-next/cell/demo/index.vue
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('cell') }}
+ {{ t('tag') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src-next/cell/index.js b/src-next/cell/index.js
new file mode 100644
index 000000000..4f4eb4964
--- /dev/null
+++ b/src-next/cell/index.js
@@ -0,0 +1,130 @@
+// Utils
+import { createNamespace, isDef } from '../utils';
+import { routeProps } from '../utils/router';
+import { cellProps } from './shared';
+
+// Components
+import Icon from '../icon';
+
+const [createComponent, bem] = createNamespace('cell');
+
+export default createComponent({
+ props: {
+ ...cellProps,
+ ...routeProps,
+ },
+
+ setup(props, { slots, emit }) {
+ const { icon, size, title, label, value, isLink } = props;
+ const showTitle = slots.title || isDef(title);
+
+ function Label() {
+ const showLabel = slots.label || isDef(label);
+
+ if (showLabel) {
+ return (
+
+ {slots.label ? slots.label() : label}
+
+ );
+ }
+ }
+
+ function Title() {
+ if (showTitle) {
+ return (
+
+ {slots.title ? slots.title() : {title}}
+ {Label()}
+
+ );
+ }
+ }
+
+ function Value() {
+ const showValue = slots.default || isDef(value);
+
+ if (showValue) {
+ return (
+
+ {slots.default ? slots.default() : {value}}
+
+ );
+ }
+ }
+
+ function LeftIcon() {
+ if (slots.icon) {
+ return slots.icon();
+ }
+
+ if (icon) {
+ return (
+
+ );
+ }
+ }
+
+ function RightIcon() {
+ const rightIconSlot = slots['right-icon'];
+
+ if (rightIconSlot) {
+ return rightIconSlot();
+ }
+
+ if (isLink) {
+ const { arrowDirection } = props;
+
+ return (
+
+ );
+ }
+ }
+
+ function onClick(event) {
+ emit('click', event);
+ // TODO
+ // functionalRoute(ctx);
+ }
+
+ const clickable = isLink || props.clickable;
+
+ const classes = {
+ clickable,
+ center: props.center,
+ required: props.required,
+ borderless: !props.border,
+ };
+
+ if (size) {
+ classes[size] = size;
+ }
+
+ return function () {
+ return (
+
+ {LeftIcon()}
+ {Title()}
+ {Value()}
+ {RightIcon()}
+ {slots.extra?.()}
+
+ );
+ };
+ },
+});
diff --git a/src-next/cell/index.less b/src-next/cell/index.less
new file mode 100644
index 000000000..8bcb4878a
--- /dev/null
+++ b/src-next/cell/index.less
@@ -0,0 +1,104 @@
+@import '../style/var';
+@import '../style/mixins/hairline';
+
+.van-cell {
+ position: relative;
+ display: flex;
+ box-sizing: border-box;
+ width: 100%;
+ padding: @cell-vertical-padding @cell-horizontal-padding;
+ overflow: hidden;
+ color: @cell-text-color;
+ font-size: @cell-font-size;
+ line-height: @cell-line-height;
+ background-color: @cell-background-color;
+
+ &::after {
+ .hairline-bottom(@cell-border-color, @padding-md, @padding-md);
+ }
+
+ &:last-child::after,
+ &--borderless::after {
+ display: none;
+ }
+
+ &__label {
+ margin-top: @cell-label-margin-top;
+ color: @cell-label-color;
+ font-size: @cell-label-font-size;
+ line-height: @cell-label-line-height;
+ }
+
+ &__title,
+ &__value {
+ flex: 1;
+ }
+
+ &__value {
+ position: relative;
+ overflow: hidden;
+ color: @cell-value-color;
+ text-align: right;
+ vertical-align: middle;
+ word-wrap: break-word;
+
+ &--alone {
+ color: @text-color;
+ text-align: left;
+ }
+ }
+
+ &__left-icon,
+ &__right-icon {
+ min-width: 1em;
+ height: @cell-line-height;
+ font-size: @cell-icon-size;
+ line-height: @cell-line-height;
+ }
+
+ &__left-icon {
+ margin-right: 5px;
+ }
+
+ &__right-icon {
+ margin-left: 5px;
+ color: @cell-right-icon-color;
+ }
+
+ &--clickable {
+ cursor: pointer;
+
+ &:active {
+ background-color: @cell-active-color;
+ }
+ }
+
+ &--required {
+ overflow: visible;
+
+ &::before {
+ position: absolute;
+ left: @padding-xs;
+ color: @cell-required-color;
+ font-size: @cell-font-size;
+ content: '*';
+ }
+ }
+
+ &--center {
+ align-items: center;
+ }
+
+ &--large {
+ padding-top: @cell-large-vertical-padding;
+ padding-bottom: @cell-large-vertical-padding;
+
+ .van-cell__title {
+ font-size: @cell-large-title-font-size;
+ }
+
+ .van-cell__label {
+ font-size: @cell-large-label-font-size;
+ }
+ }
+}
diff --git a/src-next/cell/shared.ts b/src-next/cell/shared.ts
new file mode 100644
index 000000000..f3c2ec949
--- /dev/null
+++ b/src-next/cell/shared.ts
@@ -0,0 +1,40 @@
+export type SharedCellProps = {
+ icon?: string;
+ size?: string;
+ border: boolean;
+ center?: boolean;
+ isLink?: boolean;
+ required?: boolean;
+ clickable?: boolean;
+ iconPrefix?: string;
+ titleStyle?: any;
+ titleClass?: any;
+ valueClass?: any;
+ labelClass?: any;
+ title?: string | number;
+ value?: string | number;
+ label?: string | number;
+ arrowDirection?: 'up' | 'down' | 'left' | 'right';
+};
+
+export const cellProps = {
+ icon: String,
+ size: String,
+ center: Boolean,
+ isLink: Boolean,
+ required: Boolean,
+ clickable: Boolean,
+ iconPrefix: String,
+ titleStyle: null as any,
+ titleClass: null as any,
+ valueClass: null as any,
+ labelClass: null as any,
+ title: [Number, String],
+ value: [Number, String],
+ label: [Number, String],
+ arrowDirection: String,
+ border: {
+ type: Boolean,
+ default: true,
+ },
+};
diff --git a/src-next/cell/test/__snapshots__/demo.spec.js.snap b/src-next/cell/test/__snapshots__/demo.spec.js.snap
new file mode 100644
index 000000000..20a039dac
--- /dev/null
+++ b/src-next/cell/test/__snapshots__/demo.spec.js.snap
@@ -0,0 +1,110 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders demo correctly 1`] = `
+
+`;
diff --git a/src-next/cell/test/__snapshots__/index.spec.js.snap b/src-next/cell/test/__snapshots__/index.spec.js.snap
new file mode 100644
index 000000000..cd68daa26
--- /dev/null
+++ b/src-next/cell/test/__snapshots__/index.spec.js.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CellGroup title slot 1`] = `
+
+`;
+
+exports[`arrow direction 1`] = `
+
+
+`;
+
+exports[`icon-prefix prop 1`] = `
+
+
+`;
+
+exports[`render slot 1`] = `
+Custom Icon
Custom Title
Custom Label
+
Custom Extra
+`;
+
+exports[`title-style prop 1`] = `
+
+`;
diff --git a/src-next/cell/test/demo.spec.js b/src-next/cell/test/demo.spec.js
new file mode 100644
index 000000000..5c70922b5
--- /dev/null
+++ b/src-next/cell/test/demo.spec.js
@@ -0,0 +1,4 @@
+import Demo from '../demo';
+import { snapshotDemo } from '../../../test/demo';
+
+snapshotDemo(Demo);
diff --git a/src-next/cell/test/index.spec.js b/src-next/cell/test/index.spec.js
new file mode 100644
index 000000000..0fd6b240d
--- /dev/null
+++ b/src-next/cell/test/index.spec.js
@@ -0,0 +1,80 @@
+import Cell from '..';
+import CellGroup from '../../cell-group';
+import { mount } from '../../../test';
+
+test('click event', () => {
+ const click = jest.fn();
+ const wrapper = mount(Cell, {
+ context: {
+ on: {
+ click,
+ },
+ },
+ });
+
+ wrapper.trigger('click');
+ expect(click).toHaveBeenCalled();
+});
+
+test('arrow direction', () => {
+ const wrapper = mount(Cell, {
+ propsData: {
+ isLink: true,
+ arrowDirection: 'down',
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('render slot', () => {
+ const wrapper = mount({
+ template: `
+
+ Custom Icon
+ Custom Title
+ Custom Label
+ Custom Extra
+ |
+ `,
+ components: {
+ Cell,
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('title-style prop', () => {
+ const wrapper = mount(Cell, {
+ propsData: {
+ title: 'title',
+ titleStyle: {
+ color: 'red',
+ },
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('CellGroup title slot', () => {
+ const wrapper = mount(CellGroup, {
+ scopedSlots: {
+ title: () => 'CustomTitle',
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('icon-prefix prop', () => {
+ const wrapper = mount(Cell, {
+ propsData: {
+ iconPrefix: 'my-icon',
+ icon: 'success',
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src-next/utils/functional.ts b/src-next/utils/functional.ts
deleted file mode 100644
index b2ef48688..000000000
--- a/src-next/utils/functional.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import Vue, { RenderContext, VNodeData } from 'vue';
-import { ObjectIndex } from './types';
-
-type Context = RenderContext & { data: VNodeData & ObjectIndex };
-
-type InheritContext = Partial & ObjectIndex;
-
-const inheritKey = [
- 'ref',
- 'style',
- 'class',
- 'attrs',
- 'nativeOn',
- 'directives',
- 'staticClass',
- 'staticStyle',
-];
-
-const mapInheritKey: ObjectIndex = { nativeOn: 'on' };
-
-// inherit partial context, map nativeOn to on
-export function inherit(
- context: Context,
- inheritListeners?: boolean
-): InheritContext {
- const result = inheritKey.reduce((obj, key) => {
- if (context.data[key]) {
- obj[mapInheritKey[key] || key] = context.data[key];
- }
- return obj;
- }, {} as InheritContext);
-
- if (inheritListeners) {
- result.on = result.on || {};
- Object.assign(result.on, context.data.on);
- }
-
- return result;
-}
-
-// emit event
-export function emit(context: Context, eventName: string, ...args: any[]) {
- const listeners = context.listeners[eventName];
- if (listeners) {
- if (Array.isArray(listeners)) {
- listeners.forEach((listener) => {
- listener(...args);
- });
- } else {
- listeners(...args);
- }
- }
-}
-
-// mount functional component
-export function mount(Component: any, data?: VNodeData) {
- const instance = new Vue({
- el: document.createElement('div'),
- props: Component.props,
- render(h) {
- return h(Component, {
- props: this.$props,
- ...data,
- });
- },
- });
-
- document.body.appendChild(instance.$el);
-
- return instance;
-}
diff --git a/vant.config.js b/vant.config.js
index e2f7a50d8..34735e591 100644
--- a/vant.config.js
+++ b/vant.config.js
@@ -82,10 +82,10 @@ module.exports = {
path: 'button',
title: 'Button 按钮',
},
- // {
- // path: 'cell',
- // title: 'Cell 单元格',
- // },
+ {
+ path: 'cell',
+ title: 'Cell 单元格',
+ },
{
path: 'icon',
title: 'Icon 图标',
@@ -102,10 +102,10 @@ module.exports = {
// path: 'popup',
// title: 'Popup 弹出层',
// },
- // {
- // path: 'style',
- // title: 'Style 内置样式',
- // },
+ {
+ path: 'style',
+ title: 'Style 内置样式',
+ },
// {
// path: 'toast',
// title: 'Toast 轻提示',
@@ -360,20 +360,7 @@ module.exports = {
// title: 'Sku 商品规格',
// },
],
- },
- {
- title: '废弃',
- items: [
- // {
- // path: 'panel',
- // title: 'Panel 面板',
- // },
- // {
- // path: 'switch-cell',
- // title: 'SwitchCell 开关单元格',
- // },
- ],
- },
+ }
],
},
'en-US': {
@@ -429,10 +416,10 @@ module.exports = {
path: 'button',
title: 'Button',
},
- // {
- // path: 'cell',
- // title: 'Cell',
- // },
+ {
+ path: 'cell',
+ title: 'Cell',
+ },
{
path: 'icon',
title: 'Icon',
@@ -449,10 +436,10 @@ module.exports = {
// path: 'popup',
// title: 'Popup',
// },
- // {
- // path: 'style',
- // title: 'Built-in style',
- // },
+ {
+ path: 'style',
+ title: 'Built-in style',
+ },
// {
// path: 'toast',
// title: 'Toast',
@@ -519,10 +506,6 @@ module.exports = {
// title: 'Switch',
// },
// {
- // path: 'switch-cell',
- // title: 'SwitchCell',
- // },
- // {
// path: 'uploader',
// title: 'Uploader',
// },
@@ -711,20 +694,7 @@ module.exports = {
// title: 'Sku',
// },
],
- },
- {
- title: 'Deprecated',
- items: [
- // {
- // path: 'panel',
- // title: 'Panel',
- // },
- // {
- // path: 'switch-cell',
- // title: 'SwitchCell',
- // },
- ],
- },
+ }
],
},
},