学习Element Plus组件库(二):Button组件

212 阅读1分钟

首先构建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;
      }
    }
  }
}

最终效果

基础用法: image.png 加载状态: image.png 禁用状态: image.png