diff --git a/src-next/grid-item/index.js b/src-next/grid-item/index.js new file mode 100644 index 000000000..c003d9dbb --- /dev/null +++ b/src-next/grid-item/index.js @@ -0,0 +1,151 @@ +// Utils +import { createNamespace, addUnit } from '../utils'; +import { BORDER } from '../utils/constant'; +import { route, routeProps } from '../utils/router'; + +// Mixins +import { ChildrenMixin } from '../mixins/relation'; + +// Components +import Info from '../info'; +import Icon from '../icon'; + +const [createComponent, bem] = createNamespace('grid-item'); + +export default createComponent({ + mixins: [ChildrenMixin('vanGrid')], + + props: { + ...routeProps, + dot: Boolean, + text: String, + icon: String, + iconPrefix: String, + badge: [Number, String], + }, + + emits: ['click'], + + computed: { + style() { + const { square, gutter, columnNum } = this.parent; + const percent = `${100 / columnNum}%`; + + const style = { + flexBasis: percent, + }; + + if (square) { + style.paddingTop = percent; + } else if (gutter) { + const gutterValue = addUnit(gutter); + style.paddingRight = gutterValue; + + if (this.index >= columnNum) { + style.marginTop = gutterValue; + } + } + + return style; + }, + + contentStyle() { + const { square, gutter } = this.parent; + + if (square && gutter) { + const gutterValue = addUnit(gutter); + + return { + right: gutterValue, + bottom: gutterValue, + height: 'auto', + }; + } + }, + }, + + methods: { + onClick(event) { + this.$emit('click', event); + route(this.$router, this); + }, + + genIcon() { + if (this.$slots.icon) { + return ( +
+ {this.$slots.icon()} + +
+ ); + } + + if (this.icon) { + return ( + + ); + } + }, + + getText() { + if (this.$slots.text) { + return this.$slots.text(); + } + + if (this.text) { + return {this.text}; + } + }, + + genContent() { + if (this.$slots.default) { + return this.$slots.default(); + } + + return [this.genIcon(), this.getText()]; + }, + }, + + render() { + const { + center, + border, + square, + gutter, + direction, + clickable, + } = this.parent; + + return ( +
+
+ {this.genContent()} +
+
+ ); + }, +}); diff --git a/src-next/grid-item/index.less b/src-next/grid-item/index.less new file mode 100644 index 000000000..322982c53 --- /dev/null +++ b/src-next/grid-item/index.less @@ -0,0 +1,78 @@ +@import '../style/var'; + +.van-grid-item { + position: relative; + box-sizing: border-box; + + &--square { + height: 0; + } + + &__icon { + font-size: @grid-item-icon-size; + } + + &__icon-wrapper { + position: relative; + } + + &__text { + color: @grid-item-text-color; + font-size: @grid-item-text-font-size; + line-height: 1.5; + word-wrap: break-word; + } + + &__icon + &__text { + margin-top: @padding-xs; + } + + &__content { + display: flex; + flex-direction: column; + box-sizing: border-box; + height: 100%; + padding: @grid-item-content-padding; + background-color: @grid-item-content-background-color; + + &::after { + z-index: 1; + border-width: 0 @border-width-base @border-width-base 0; + } + + &--square { + position: absolute; + top: 0; + right: 0; + left: 0; + } + + &--center { + align-items: center; + justify-content: center; + } + + &--horizontal { + flex-direction: row; + + .van-grid-item__icon + .van-grid-item__text { + margin-top: 0; + margin-left: @padding-xs; + } + } + + &--surround { + &::after { + border-width: @border-width-base; + } + } + + &--clickable { + cursor: pointer; + + &:active { + background-color: @grid-item-content-active-color; + } + } + } +} diff --git a/src-next/grid/README.md b/src-next/grid/README.md new file mode 100644 index 000000000..0ab19d44b --- /dev/null +++ b/src-next/grid/README.md @@ -0,0 +1,134 @@ +# Grid + +### Install + +```js +import Vue from 'vue'; +import { Grid, GridItem } from 'vant'; + +Vue.use(Grid); +Vue.use(GridItem); +``` + +## Usage + +### Basic Usage + +```html + + + + + + +``` + +### Column Num + +```html + + + +``` + +### Custom Content + +```html + + + + + + + + + + + +``` + +### Square + +```html + + + +``` + +### Gutter + +```html + + + +``` + +### Horizontal + +```html + + + + + +``` + +### Route + +```html + + + + +``` + +### Show Badge + +```html + + + + +``` + +## API + +### Grid Props + +| Attribute | Description | Type | Default | +| --- | --- | --- | --- | +| column-num `v2.0.4` | Column Num | _number \| string_ | `4` | +| icon-size `v2.2.6` | Icon size | _number \| string_ | `28px` | +| gutter | Gutter | _number \| string_ | `0` | +| border | Whether to show border | _boolean_ | `true` | +| center | Whether to center content | _boolean_ | `true` | +| square | Whether to be square shape | _boolean_ | `false` | +| clickable | Whether to show click feedback when clicked | _boolean_ | `false` | +| direction `v2.8.2` | Content arrangement direction, can be set to `horizontal` | _string_ | `vertical` | + +### GridItem Props + +| Attribute | Description | Type | Default | +| --- | --- | --- | --- | +| text | Text | _string_ | - | +| icon | Icon name or URL | _string_ | - | +| icon-prefix `v2.5.3` | Icon className prefix | _string_ | `van-icon` | +| dot `v2.2.1` | Whether to show red dot | _boolean_ | `false` | +| badge `v2.5.6` | Content of the badge | _number \| string_ | - | +| 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` | + +### GridItem Events + +| Event | Description | Arguments | +| ----- | ---------------------- | -------------- | +| click | Triggered when clicked | _event: Event_ | + +### GridItem Slots + +| Name | Description | +| ------- | -------------- | +| default | Custom content | +| icon | Custom icon | +| text | Custom text | diff --git a/src-next/grid/README.zh-CN.md b/src-next/grid/README.zh-CN.md new file mode 100644 index 000000000..1dd484706 --- /dev/null +++ b/src-next/grid/README.zh-CN.md @@ -0,0 +1,155 @@ +# Grid 宫格 + +### 介绍 + +宫格可以在水平方向上把页面分隔成等宽度的区块,用于展示内容或进行页面导航 + +### 引入 + +```js +import Vue from 'vue'; +import { Grid, GridItem } from 'vant'; + +Vue.use(Grid); +Vue.use(GridItem); +``` + +## 代码演示 + +### 基础用法 + +通过`icon`属性设置格子内的图标,`text`属性设置文字内容 + +```html + + + + + + +``` + +### 自定义列数 + +默认一行展示四个格子,可以通过`column-num`自定义列数 + +```html + + + +``` + +### 自定义内容 + +通过插槽可以自定义格子展示的内容 + +```html + + + + + + + + + + + +``` + +### 正方形格子 + +设置`square`属性后,格子的高度会和宽度保持一致 + +```html + + + +``` + +### 格子间距 + +通过`gutter`属性设置格子之间的距离 + +```html + + + +``` + +### 内容横排 + +将`direction`属性设置为`horizontal`,可以让宫格的内容呈横向排列 + +```html + + + + + +``` + +### 页面导航 + +通过`to`属性设置`vue-router`跳转链接,通过`url`属性设置 URL 跳转链接 + +```html + + + + +``` + +### 徽标提示 + +设置`dot`属性后,会在图标右上角展示一个小红点。设置`badge`属性后,会在图标右上角展示相应的徽标 + +```html + + + + +``` + +## API + +### Grid Props + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| column-num `v2.0.4` | 列数 | _number \| string_ | `4` | +| icon-size `v2.2.6` | 图标大小,默认单位为`px` | _number \| string_ | `28px` | +| gutter | 格子之间的间距,默认单位为`px` | _number \| string_ | `0` | +| border | 是否显示边框 | _boolean_ | `true` | +| center | 是否将格子内容居中显示 | _boolean_ | `true` | +| square | 是否将格子固定为正方形 | _boolean_ | `false` | +| clickable | 是否开启格子点击反馈 | _boolean_ | `false` | +| direction `v2.8.2` | 格子内容排列的方向,可选值为 `horizontal` | _string_ | `vertical` | + +### GridItem Props + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| text | 文字 | _string_ | - | +| icon | [图标名称](#/zh-CN/icon)或图片链接 | _string_ | - | +| icon-prefix `v2.5.3` | 图标类名前缀,同 Icon 组件的 [class-prefix 属性](#/zh-CN/icon#props) | _string_ | `van-icon` | +| dot `v2.2.1` | 是否显示图标右上角小红点 | _boolean_ | `false` | +| badge `v2.5.6` | 图标右上角徽标的内容 | _number \| string_ | - | +| info `2.2.1` | 图标右上角徽标的内容(已废弃,请使用 badge 属性) | _number \| string_ | - | +| url | 点击后跳转的链接地址 | _string_ | - | +| to | 点击后跳转的目标路由对象,同 vue-router 的 [to 属性](https://router.vuejs.org/zh/api/#to) | _string \| object_ | - | +| replace | 是否在跳转时替换当前页面历史 | _boolean_ | `false` | + +### GridItem Events + +| 事件名 | 说明 | 回调参数 | +| ------ | -------------- | -------------- | +| click | 点击格子时触发 | _event: Event_ | + +### GridItem Slots + +| 名称 | 说明 | +| ------- | -------------------- | +| default | 自定义宫格的所有内容 | +| icon | 自定义图标 | +| text | 自定义文字 | diff --git a/src-next/grid/demo/index.vue b/src-next/grid/demo/index.vue new file mode 100644 index 000000000..544f524f1 --- /dev/null +++ b/src-next/grid/demo/index.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src-next/grid/index.js b/src-next/grid/index.js new file mode 100644 index 000000000..5c0d7f62c --- /dev/null +++ b/src-next/grid/index.js @@ -0,0 +1,52 @@ +import { createNamespace, addUnit } from '../utils'; +import { BORDER_TOP } from '../utils/constant'; +import { ParentMixin } from '../mixins/relation'; + +const [createComponent, bem] = createNamespace('grid'); + +export default createComponent({ + mixins: [ParentMixin('vanGrid')], + + props: { + square: Boolean, + gutter: [Number, String], + iconSize: [Number, String], + direction: String, + clickable: Boolean, + columnNum: { + type: [Number, String], + default: 4, + }, + center: { + type: Boolean, + default: true, + }, + border: { + type: Boolean, + default: true, + }, + }, + + computed: { + style() { + const { gutter } = this; + + if (gutter) { + return { + paddingLeft: addUnit(gutter), + }; + } + }, + }, + + render() { + return ( +
+ {this.$slots.default?.()} +
+ ); + }, +}); diff --git a/src-next/grid/index.less b/src-next/grid/index.less new file mode 100644 index 000000000..47a890003 --- /dev/null +++ b/src-next/grid/index.less @@ -0,0 +1,6 @@ +@import '../style/var'; + +.van-grid { + display: flex; + flex-wrap: wrap; +} diff --git a/src-next/grid/test/__snapshots__/demo.spec.js.snap b/src-next/grid/test/__snapshots__/demo.spec.js.snap new file mode 100644 index 000000000..4b9a61ac3 --- /dev/null +++ b/src-next/grid/test/__snapshots__/demo.spec.js.snap @@ -0,0 +1,196 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders demo correctly 1`] = ` +
+
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+ 文字
+
+
+
+
+
+
+
+ 路由跳转
+
+
+
+ URL 跳转
+
+
+
+
+
+
+
+
+
文字
+
+
+
+
99+
+
文字
+
+
+
+
+`; diff --git a/src-next/grid/test/__snapshots__/index.spec.js.snap b/src-next/grid/test/__snapshots__/index.spec.js.snap new file mode 100644 index 000000000..e7d763ddc --- /dev/null +++ b/src-next/grid/test/__snapshots__/index.spec.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`icon-size prop 1`] = ` +
+
+
+
+
+
+`; + +exports[`render icon-slot 1`] = ` +
+
+
+
+
+
1
+
+
+
+
+`; + +exports[`sqaure and set gutter 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+`; diff --git a/src-next/grid/test/demo.spec.js b/src-next/grid/test/demo.spec.js new file mode 100644 index 000000000..5c70922b5 --- /dev/null +++ b/src-next/grid/test/demo.spec.js @@ -0,0 +1,4 @@ +import Demo from '../demo'; +import { snapshotDemo } from '../../../test/demo'; + +snapshotDemo(Demo); diff --git a/src-next/grid/test/index.spec.js b/src-next/grid/test/index.spec.js new file mode 100644 index 000000000..77377b69f --- /dev/null +++ b/src-next/grid/test/index.spec.js @@ -0,0 +1,60 @@ +import { mount } from '../../../test'; + +test('click grid item', () => { + const onClick = jest.fn(); + const wrapper = mount({ + template: ` + + + + `, + methods: { + onClick, + }, + }); + + const Item = wrapper.find('.van-grid-item__content'); + Item.trigger('click'); + + expect(onClick).toHaveBeenCalledTimes(1); +}); + +test('sqaure and set gutter', () => { + const wrapper = mount({ + template: ` + + + + + + `, + }); + + expect(wrapper).toMatchSnapshot(); +}); + +test('icon-size prop', () => { + const wrapper = mount({ + template: ` + + + + `, + }); + + expect(wrapper).toMatchSnapshot(); +}); + +test('render icon-slot', () => { + const wrapper = mount({ + template: ` + + +
+ + + `, + }); + + expect(wrapper).toMatchSnapshot(); +}); diff --git a/vant.config.js b/vant.config.js index 44c0a1233..d5eaad36d 100644 --- a/vant.config.js +++ b/vant.config.js @@ -286,10 +286,10 @@ module.exports = { { title: '导航组件', items: [ - // { - // path: 'grid', - // title: 'Grid 宫格', - // }, + { + path: 'grid', + title: 'Grid 宫格', + }, // { // path: 'index-bar', // title: 'IndexBar 索引栏', @@ -620,10 +620,10 @@ module.exports = { { title: 'Navigation Components', items: [ - // { - // path: 'grid', - // title: 'Grid', - // }, + { + path: 'grid', + title: 'Grid', + }, // { // path: 'index-bar', // title: 'IndexBar',