首先构建Button组件的目录结构:
├── packages
│ ├── components
│ │ ├── button
│ │ │ ├── src # 组件入口目录
│ │ │ │ ├── button.ts # Ts类型和组件属性
│ │ │ │ └── button.vue # 组件代码
│ │ │ └── index.ts # 组件入口文件
Ts类型声明和组件属性定义
// 组件大小
export type Size = 'small' | 'default' | 'large'
// 组件类型
export type Type = 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'default'
// 组件原始类型
export type NativeType = 'button' | 'submit' | 'reset'
export const buttonProps = {
size: {
type: String as PropType<Size>,
default: ''
},
type: {
type: String as PropType<Type>,
default: 'default'
},
round: Boolean,
loading: Boolean,
disabled: Boolean,
icon: {
type: iconPropType
},
nativeType: {
type: String as PropType<NativeType>,
default: 'button'
}
}
export const buttonEmits = {
click: (e: MouseEvent) => e instanceof MouseEvent
}
// 提供外部使用
export type ButtonProps = ExtractPropTypes<typeof buttonProps>
export type ButtonEmits = typeof buttonEmits
在 packages/utils/index.ts文件中定义一个类型专门供icon使用:
import { Component, PropType } from 'vue'
export const iconPropType = [String, Object, Function] as PropType<string | Component>
组件实现
<template>
<button
:class="[
bem.b(),
bem.m(type),
bem.m(size),
bem.is('round', round),
bem.is('loading', loading),
bem.is('disabled', disabled),
]"
:type="nativeType"
:disabled="disabled || loading"
@click="handleClick"
>
<!-- loading -->
<template v-if="loading">
<s-icon :class="bem.is('loading', loading)">
<slot name="loading" v-if="$slots.loading" />
<LoadingComponent v-else />
</s-icon>
</template>
<!-- icon -->
<s-icon v-else-if="icon || $slots.icon">
<component :is="icon" v-if="icon" />
<slot name="icon" v-else />
</s-icon>
<!-- default slot -->
<span v-if="$slots.default">
<slot />
</span>
</button>
</template>
入口文件
import { withInstall } from '@storm/utils'
import _Button from './src/button.vue'
// 添加install方法
export const Button = withInstall(_Button)
export default Button
export * from './src/button'
定义公共的样式变量
在 packages/theme-chalk/src/common/var.scss 中定义一些公共的样式变量:
$color-black: #27243f;
$color-white: #ffffff;
$color-text: #616998;
$color-primary: #2970f6;
$color-success: #67c23a;
$color-warning: #ff8047;
$color-danger: #f0667f;
$color-info: #50a9eb;
$color-primary-active: #104fcd;
$color-success-active: #71ab54;
$color-warning-active: #e07d51;
$color-danger-active: #c9687a;
$color-info-active: #60a2d3;
$color-primary-hover-shadow: 0 4px 10px 0 rgba(41, 112, 246, 0.3);
$color-success-hover-shadow: 0 4px 10px 0 rgba(103, 194, 58, 0.3);
$color-warning-hover-shadow: 0 4px 10px 0 rgba(255, 128, 71, 0.3);
$color-danger-hover-shadow: 0 4px 10px 0 rgba(240, 102, 127, 0.3);
$color-info-hover-shadow: 0 4px 10px 0 rgba(80, 169, 235, 0.3);
// 占位和文本禁用后的颜色
$color-placeholder-text: #ccd1de;
$color-border: #b5bdc7;
// 禁用的背景色
$color-bg-disabled: #f5f7fa;
$font-size-base: 14px;
样式文件
可以根据不同的按钮type,使用不同的变量;is-loading 来表示loading状态;is-disabled 表示禁用状态。
@use "./mixins/mixins.scss" as *;
@use "common/var.scss" as *;
@include b(button) {
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
height: 32px;
line-height: 1;
padding: 8px 15px;
font-size: $font-size-base;
font-weight: 500;
color: $color-text;
border: 1px solid $color-border;
border-radius: 4px;
background-color: $color-white;
outline: none;
white-space: nowrap;
user-select: none;
cursor: pointer;
-webkit-appearance: none;
& + .s-button {
margin-left: 12px;
}
[class*="s-icon"] + span {
margin-left: 6px;
}
span {
display: inline-flex;
align-items: center;
}
@include when(round) {
border-radius: 20px;
}
@include m(small) {
height: 24px;
padding: 5px 11px;
font-size: 12px;
border-radius: 3px;
}
@include m(large) {
height: 40px;
padding: 12px 19px;
}
@include when(disabled) {
&,
&:hover,
&:focus {
opacity: 0.7;
cursor: not-allowed;
}
}
@include when(loading) {
position: relative;
pointer-events: none;
// -1px 遮住边框
&:before {
content: "";
position: absolute;
left: -1px;
right: -1px;
top: -1px;
bottom: -1px;
background-color: rgba(255, 255, 255, 0.3);
}
}
@include m(default) {
&:not(.is-disabled) {
&:hover {
color: $color-primary;
border-color: $color-primary;
}
}
}
@include m(primary) {
@include button-variant($color-white, $color-primary, $color-primary);
&:not(.is-disabled) {
&:hover {
box-shadow: $color-primary-hover-shadow;
}
&:active {
border-color: $color-primary-active;
background-color: $color-primary-active;
}
}
}
@include m(success) {
@include button-variant($color-white, $color-success, $color-success);
&:not(.is-disabled) {
&:hover {
box-shadow: $color-success-hover-shadow;
}
&:active {
border-color: $color-success-active;
background-color: $color-success-active;
}
}
}
@include m(warning) {
@include button-variant($color-white, $color-warning, $color-warning);
&:not(.is-disabled) {
&:hover {
box-shadow: $color-warning-hover-shadow;
}
&:active {
border-color: $color-warning-active;
background-color: $color-warning-active;
}
}
}
@include m(danger) {
@include button-variant($color-white, $color-danger, $color-danger);
&:not(.is-disabled) {
&:hover {
box-shadow: $color-danger-hover-shadow;
}
&:active {
border-color: $color-danger-active;
background-color: $color-danger-active;
}
}
}
@include m(info) {
@include button-variant($color-white, $color-info, $color-info);
&:not(.is-disabled) {
&:hover {
box-shadow: $color-info-hover-shadow;
}
&:active {
border-color: $color-info-active;
background-color: $color-info-active;
}
}
}
}
最终效果
基础用法:
加载状态:
禁用状态: