feat: add Badge component #6573

This commit is contained in:
chenjiahan 2020-10-02 16:46:57 +08:00
parent f75c8231c6
commit 475e60b282
17 changed files with 336 additions and 46 deletions

View File

@ -18,7 +18,6 @@
} }
&__icon { &__icon {
position: relative;
width: 1em; width: 1em;
margin: 0 auto 5px; margin: 0 auto 5px;
color: @action-bar-icon-color; color: @action-bar-icon-color;

View File

@ -32,10 +32,9 @@ export default createComponent({
if (slots.icon) { if (slots.icon) {
return ( return (
<div class={bem('icon')}> <Badge dot={dot} content={badge} class={bem('icon')}>
{slots.icon()} {slots.icon()}
<Badge dot={dot} badge={badge} /> </Badge>
</div>
); );
} }

78
src/badge/README.md Normal file
View File

@ -0,0 +1,78 @@
# Badge
### Install
```js
import { createApp } from 'vue';
import { Badge } from 'vant';
const app = createApp();
app.use(Badge);
```
## Usage
### Basic Usage
```html
<van-badge :content="5">
<div class="child" />
</van-badge>
<van-badge dot>
<div class="child" />
</van-badge>
<style>
.child {
width: 36px;
height: 36px;
background: #f2f3f5;
border-radius: 4px;
}
</style>
```
### Max
```html
<van-badge :content="20" :max="9">
<div class="child" />
</van-badge>
<van-badge :content="200" :max="99">
<div class="child" />
</van-badge>
```
### Custom Color
```html
<van-badge :content="5" color="#1989fa">
<div class="child" />
</van-badge>
<van-badge dot color="#1989fa">
<div class="child" />
</van-badge>
```
### Standaline
```html
<van-badge :content="200" :max="99" />
```
## API
### Props
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| content | Badge content | _string \| number_ | - |
| color | Background color | _string_ | `#ee0a24` |
| dot | Whether to show dot | _boolean_ | `false` |
| max | Max valueshow `{max}+` when exceedonly works when content is number type | _number_ | - |
### Slots
| Name | Description |
| ------- | ------------ |
| default | Default slot |

91
src/badge/README.zh-CN.md Normal file
View File

@ -0,0 +1,91 @@
# Badge 徽标
### 介绍
在右上角展示徽标数字或小红点。
### 引入
```js
import { createApp } from 'vue';
import { Badge } from 'vant';
const app = createApp();
app.use(Badge);
```
## 代码演示
### 基础用法
设置 `content` 属性后Badge 会在子元素的右上角显示对应的徽标,也可以通过 `dot` 来显示小红点。
```html
<van-badge :content="5">
<div class="child" />
</van-badge>
<van-badge dot>
<div class="child" />
</van-badge>
<style>
.child {
width: 36px;
height: 36px;
background: #f2f3f5;
border-radius: 4px;
}
</style>
```
### 最大值
设置 `max` 属性后,当 `content` 的数值超过最大值时,会自动显示为 `{max}+`
```html
<van-badge :content="20" :max="9">
<div class="child" />
</van-badge>
<van-badge :content="200" :max="99">
<div class="child" />
</van-badge>
```
### 自定义颜色
通过 `color` 属性来设置徽标的颜色。
```html
<van-badge :content="5" color="#1989fa">
<div class="child" />
</van-badge>
<van-badge dot color="#1989fa">
<div class="child" />
</van-badge>
```
### 独立展示
当 Badge 没有子元素时,会作为一个独立的元素进行展示。
```html
<van-badge :content="200" :max="99" />
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| content | 徽标内容 | _string \| number_ | - |
| color | 徽标背景颜色 | _string_ | `#ee0a24` |
| dot | 是否展示为小红点 | _boolean_ | `false` |
| max | 最大值,超过最大值会显示 `{max}+`,仅当 content 为 number 类型时有效 | _number_ | - |
### Slots
| 名称 | 说明 |
| ------- | ---------------- |
| default | 徽标包裹的子元素 |
| content | 自定义徽标内容 |

78
src/badge/demo/index.vue Normal file
View File

@ -0,0 +1,78 @@
<template>
<demo-section>
<demo-block :title="t('basicUsage')">
<van-badge :content="5">
<div class="child" />
</van-badge>
<van-badge dot>
<div class="child" />
</van-badge>
</demo-block>
<demo-block :title="t('max')">
<van-badge :content="20" :max="9">
<div class="child" />
</van-badge>
<van-badge :content="200" :max="99">
<div class="child" />
</van-badge>
</demo-block>
<demo-block :title="t('customColor')">
<van-badge :content="5" :color="BLUE">
<div class="child" />
</van-badge>
<van-badge dot :color="BLUE">
<div class="child" />
</van-badge>
</demo-block>
<demo-block :title="t('standalone')">
<van-badge :content="200" :max="99" />
</demo-block>
</demo-section>
</template>
<script>
import { BLUE } from '../../utils/constant';
export default {
i18n: {
'zh-CN': {
max: '最大值',
standalone: '独立展示',
customColor: '自定义颜色',
},
'en-US': {
max: 'Max',
standalone: 'Standalone',
customColor: 'Custom Color',
},
},
data() {
return {
BLUE,
};
},
};
</script>
<style lang="less">
@import '../../style/var';
.demo-badge {
background-color: @white;
.van-badge__wrapper {
margin-left: @padding-md;
}
.child {
width: 36px;
height: 36px;
background: @gray-2;
border-radius: 4px;
}
}
</style>

View File

@ -1,9 +1,7 @@
@import '../style/var'; @import '../style/var';
.van-badge { .van-badge {
position: absolute; display: inline-block;
top: 0;
right: 0;
box-sizing: border-box; box-sizing: border-box;
min-width: @badge-size; min-width: @badge-size;
padding: @badge-padding; padding: @badge-padding;
@ -15,9 +13,15 @@
text-align: center; text-align: center;
background-color: @badge-background-color; background-color: @badge-background-color;
border: @badge-border-width solid @white; border: @badge-border-width solid @white;
border-radius: @badge-size; border-radius: @border-radius-max;
transform: translate(50%, -50%);
transform-origin: 100%; &--fixed {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
transform-origin: 100%;
}
&--dot { &--dot {
width: @badge-dot-size; width: @badge-dot-size;
@ -26,4 +30,9 @@
background-color: @badge-dot-color; background-color: @badge-dot-color;
border-radius: 100%; border-radius: 100%;
} }
&__wrapper {
position: relative;
display: inline-block;
}
} }

View File

@ -1,21 +1,65 @@
import type { PropType } from 'vue';
import { isDef, createNamespace } from '../utils'; import { isDef, createNamespace } from '../utils';
const [createComponent, bem] = createNamespace('badge'); const [createComponent, bem] = createNamespace('badge');
export default createComponent({ export default createComponent({
props: { props: {
max: Number,
dot: Boolean, dot: Boolean,
badge: [Number, String], color: String,
content: [Number, String],
tag: {
type: String as PropType<keyof HTMLElementTagNameMap>,
default: 'div',
},
}, },
setup(props) { setup(props, { slots }) {
return () => { const hasContent = () =>
const { dot, badge } = props; !!(slots.default || (isDef(props.content) && props.content !== ''));
const hasBadge = isDef(badge) && badge !== '';
if (dot || hasBadge) { const renderContent = () => {
return <div class={bem({ dot })}>{dot ? '' : badge}</div>; const { dot, max, content } = props;
if (!dot && hasContent()) {
if (slots.content) {
return slots.content();
}
if (isDef(max) && typeof content === 'number' && content > max) {
return `${max}+`;
}
return content;
} }
}; };
const renderBadge = () => {
if (hasContent() || props.dot) {
return (
<div
class={bem({ dot: props.dot, fixed: !!slots.default })}
style={{ background: props.color }}
>
{renderContent()}
</div>
);
}
};
return () => {
if (slots.default) {
const { tag } = props;
return (
<tag class={bem('wrapper')}>
{slots.default()}
{renderBadge()}
</tag>
);
}
return renderBadge();
};
}, },
}); });

View File

@ -66,10 +66,9 @@ export default createComponent({
const renderIcon = () => { const renderIcon = () => {
if (slots.icon) { if (slots.icon) {
return ( return (
<div class={bem('icon-wrapper')}> <Badge dot={props.dot} content={props.badge}>
{slots.icon()} {slots.icon()}
<Badge dot={props.dot} badge={props.badge} /> </Badge>
</div>
); );
} }

View File

@ -12,10 +12,6 @@
font-size: @grid-item-icon-size; font-size: @grid-item-icon-size;
} }
&__icon-wrapper {
position: relative;
}
&__text { &__text {
color: @grid-item-text-color; color: @grid-item-text-color;
font-size: @grid-item-text-font-size; font-size: @grid-item-text-font-size;

View File

@ -31,7 +31,10 @@ export default createComponent({
const isImageIcon = isImage(name); const isImageIcon = isImage(name);
return ( return (
<tag <Badge
dot={dot}
tag={tag}
content={badge}
class={[classPrefix, isImageIcon ? '' : `${classPrefix}-${name}`]} class={[classPrefix, isImageIcon ? '' : `${classPrefix}-${name}`]}
style={{ style={{
color, color,
@ -40,8 +43,7 @@ export default createComponent({
> >
{slots.default?.()} {slots.default?.()}
{isImageIcon && <img class={bem('image')} src={name} />} {isImageIcon && <img class={bem('image')} src={name} />}
<Badge dot={dot} badge={badge} /> </Badge>
</tag>
); );
}; };
}, },

View File

@ -38,10 +38,9 @@ export default createComponent({
return ( return (
<a class={bem({ select: selected, disabled })} onClick={onClick}> <a class={bem({ select: selected, disabled })} onClick={onClick}>
<div class={bem('text')}> <Badge dot={dot} content={badge} class={bem('text')}>
{slots.title ? slots.title() : title} {slots.title ? slots.title() : title}
<Badge dot={dot} badge={badge} class={bem('badge')} /> </Badge>
</div>
</a> </a>
); );
}; };

View File

@ -18,11 +18,6 @@
background-color: @sidebar-active-color; background-color: @sidebar-active-color;
} }
&__text {
position: relative;
display: inline-block;
}
&:not(:last-child)::after { &:not(:last-child)::after {
border-bottom-width: 1px; border-bottom-width: 1px;
} }

View File

@ -73,10 +73,9 @@ export default createComponent({
style={{ color }} style={{ color }}
onClick={onClick} onClick={onClick}
> >
<div class={bem('icon')}> <Badge dot={dot} content={badge} class={bem('icon')}>
{renderIcon()} {renderIcon()}
<Badge dot={dot} badge={badge} /> </Badge>
</div>
<div class={bem('text')}> <div class={bem('text')}>
{slots.default?.({ active: active.value })} {slots.default?.({ active: active.value })}
</div> </div>

View File

@ -12,7 +12,6 @@
cursor: pointer; cursor: pointer;
&__icon { &__icon {
position: relative;
margin-bottom: @tabbar-item-margin-bottom; margin-bottom: @tabbar-item-margin-bottom;
font-size: @tabbar-item-icon-size; font-size: @tabbar-item-icon-size;

View File

@ -63,10 +63,9 @@ export default createComponent({
if (props.dot || (isDef(props.badge) && props.badge !== '')) { if (props.dot || (isDef(props.badge) && props.badge !== '')) {
return ( return (
<span class={bem('text-wrapper')}> <Badge dot={props.dot} content={props.badge}>
{Text} {Text}
{<Badge dot={props.dot} badge={props.badge} />} </Badge>
</span>
); );
} }

View File

@ -31,10 +31,6 @@
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
} }
&__text-wrapper {
position: relative;
}
} }
.van-tabs { .van-tabs {

View File

@ -240,6 +240,10 @@ module.exports = {
{ {
title: '展示组件', title: '展示组件',
items: [ items: [
{
path: 'badge',
title: 'Badge 徽标',
},
{ {
path: 'circle', path: 'circle',
title: 'Circle 环形进度条', title: 'Circle 环形进度条',
@ -570,6 +574,10 @@ module.exports = {
{ {
title: 'Display Components', title: 'Display Components',
items: [ items: [
{
path: 'badge',
title: 'Badge',
},
{ {
path: 'circle', path: 'circle',
title: 'Circle', title: 'Circle',