在开发组件之前,我们需要规划好我们的全局样式变量,比如主题色以及不同状态的颜色(警告色、错误颜色、信息颜色等),以及是否你需要规划暗夜模式,这些东西都建议提前考虑。
主题色设置
我建议将 packages/theme-chalk/index.less
文件作为样式的入口文件,然后将基础的样式以及我们后面组件的样式导入其中。 我们可以创建如下结构来管理我们的样式(这个根据自己的喜好来规范),我们在 index.less
中导入我们的各个文件
less
@import url(./common/base.less);
@import url(./common/theme.less);
base.less
中我存放的是需要全局注册的各种主题变量,我设置了主要这几种状态 default、primary、success、info、warning、danger
less
:root {
--t-default: #172b4d;
--t-primary: #5e72e4;
--t-success: #2dce89;
--t-info: #11cdef;
--t-warning: #fb6340;
--t-danger: #f5365c;
--t-border-color: #dcdfe6;
--t-bg-color: #fff;
--t-text-color: #606266;
--t-placeholder-color: #bfc3d6;
--t-hover-color: #f2f2f2;
--t-icon-fill-color: #ccc;
--t-shadow: rgba(0, 0, 0, 0.12);
--t-primary-lighten: lighten(#5e72e4, 23%);
--t-success-lighten: lighten(#2dce89, 45%);
--t-info-lighten: lighten(#11cdef, 39%);
--t-warning-lighten: lighten(#fb6340, 35%);
--t-danger-lighten: lighten(#f5365c, 35%);
--t-success-border: lighten(#2dce89, 30%);
--t-info-border: lighten(#11cdef, 30%);
--t-warning-border: lighten(#fb6340, 30%);
--t-danger-border: lighten(#f5365c, 30%);
}
theme.less
中存放明暗模式基础的一些颜色变量
less
:root,
[data-color-mode="light"] {
--background-color: #fff;
--title: #292d35;
--icon: #9da2ac;
--border: #e5e5e5;
--borderColor: #dee2e6;
--fontColor: #333;
--iconColor: #bdc0d3;
--disabled: #b1b1b1;
}
[data-color-mode="dark"] {
--background-color: #1a1d24;
--fontColor: #f2f2f2;
...;
}
ps. 提前定义好文字、背景、图标等很常见且用的最多的变量,一定要记得在开发组件的时候使用他们,免得回头定义暗夜模式的时候折腾
fonts
文件夹我们准备存放图标的 font 文件,你也可以存放你的字体等
以上的仅供参考,你们只要理解思路就行。
Button 组件开发场景以及需求分析
又是一个看腻的组件,但是很多组件需要用到 button,所以需要提前开发,请保持耐心
先从 button 本身的特性开始,button 是用来和用户点击交互的,且不同的交互会有不同的样式,有 hover(悬停)、acitve(按下去)、focus(选中)我们需要给不同的情况设置不同的颜色。
此外,button 需要支持一些我们常见的功能:
- 默认按钮以及不同状态
- 不同尺寸
- 是否需要圆角
- 是否携带图标
- loading
- disabled
当然,你可以根据自己需求来实现你的组件所支持的功能。
实现
默认按钮以及不同状态、不同尺寸、是否需要圆角、禁用
默认情况下的按钮很简单,我们需要吧 button 标签进行封装和包装,至于不同状态我们可以设置 type 属性来控制其颜色以及对应的交互颜色
我们在 components/button/src/button.vue
的同级创建一个 button.js
用户存放 props、emit、枚举等
button.js
js
const BUTTON_TYPE = ["default", "primary", "success", "warning", "info", "danger"];
const BUTTON_SIZE = ["", "small", "mini"];
export const ButtonProps = {
// 类型
type: {
type: String,
default: "default",
validator(value) {
return BUTTON_TYPE.includes(value);
},
},
// 尺寸
size: {
type: String,
default: "",
validator(value) {
return BUTTON_SIZE.includes(value);
},
},
// 圆角
round: {
type: Boolean,
default: false,
},
// 是否禁用
disabled: {
type: Boolean,
default: false,
},
};
html
<template>
<button
class="t-button"
:class="[`t-button__${type}`,`${size && 't-button--' + size}`,{ 'is-round': round },]"
:disabled="disabled"
>
<div class="t-button__inner">
<span v-if="$slots.default">
<slot />
</span>
</div>
</button>
</template>
<script setup>
import { ButtonProps } from "./button";
defineOptions({
name: "t-button",
});
const BUTTON_TYPE = ["primary", "success", "warning", "info", "danger"];
const BUTTON_SIZE = ["", "small", "mini"];
defineProps(ButtonProps);
</script>
less
.t-button {
display: inline-block;
white-space: nowrap;
cursor: pointer;
background-color: #fff;
border: 1px solid #fff;
text-align: center;
box-sizing: border-box;
outline: none;
font-weight: 500;
line-height: 1;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
min-width: 98px;
padding: 12px 18px;
font-size: 14px;
border-radius: 4px;
box-shadow: 0 4px 6px rgb(50 50 93 / 11%), 0 1px 3px rgb(0 0 0 / 8%);
&:hover:not(:disabled) {
box-shadow: 0 7px 14px rgb(50 50 93 / 10%), 0 3px 6px rgb(0 0 0 / 8%);
}
}
.t-button > .button__inner {
display: flex;
align-items: center;
}
.t-button.t-button--small {
min-width: 92px;
padding: 10px 18px;
}
.t-button.t-button--mini {
min-width: 80px;
padding: 9px 15px;
font-size: 12px;
border-radius: 3px;
}
.t-button.t-button--icon {
min-width: 40px;
padding: 8px 15px;
font-size: 18px;
border-radius: 4px;
}
.t-button.is-round {
box-sizing: border-box;
border-radius: 20px;
}
.t-button.is-disabled {
opacity: 0.6;
cursor: not-allowed;
}
.t-button__default {
color: var(--t-primary);
background-color: transparent;
&:active:not(:disabled) {
background: #e6e6e6;
border-color: #e6e6e6;
color: var(--t-default);
box-shadow: 0 0 0 transparent;
}
}
.t-button__primary {
color: #fff;
background-color: var(--t-primary);
border-color: var(--t-primary);
&:active:not(:disabled) {
background: #324cdd;
}
}
.t-button__success {
color: #fff;
background-color: var(--t-success);
border-color: var(--t-success);
&:active:not(:disabled) {
background: #24a46d;
}
}
.t-button__warning {
color: #fff;
background-color: var(--t-warning);
border-color: var(--t-warning);
&:active:not(:disabled) {
background: #fa3a0e;
}
}
.t-button__info {
color: #fff;
background-color: var(--t-info);
border-color: var(--t-info);
&:active:not(:disabled) {
background: #0da5c0;
}
}
.t-button__danger {
color: #fff;
background-color: var(--t-danger);
border-color: var(--t-danger);
&:active:not(:disabled) {
background: #ec0c38;
}
}
我们来在 examples 中测试一下看看
图标按钮
在此之前,我们需要准备好我们组件库的所有图标,建议使用 font class,这样我们可以根据指定类名来给按钮添加图表,我这边是在阿里巴巴矢量图库中找的,我们可以创建一个项目,找到自己想要的保存至自己项目,然后选择 Font class,下载至本地
我们将 font 文件放置 /packages/theme-chalk/fonts
中,然后在 /packages/theme-chalk/components/icon.less
中使用,记得在样式的入口文件中导入 icon.less
less
@font-face {
font-family: "test-ui-icons"; /* Project id 4465617 */
src: url("../fonts/iconfont.woff2") format("woff2"), url("../fonts/iconfont.woff") format("woff"),
url("../fonts/iconfont.ttf") format("truetype");
}
.t-icon {
font-family: "test-ui-icons" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: inline-block;
}
.icon-3column:before {
content: "\e663";
}
.icon-column-4:before {
content: "\e664";
}
我们回到 button 组件,添加 icon 属性,接收一个字符串,然后我们在 button 中创建一个 i 标签用于显示图标
js
export const ButtonProps = {
...
// 图标
icon: {
type: String,
default: "",
},
};
html
<template>
<button
class="t-button"
:class="[
`${size && 't-button--' + size}`,
`t-button__${type}`,
{ 't-button--icon': icon },
{ 'is-disabled': disabled },
{ 'is-round': round },
]"
:disabled="disabled"
>
<div class="t-button__inner">
<i v-if="icon" :class="['t-icon', `icon-${icon}`]"></i>
<span v-if="$slots.default">
<slot />
</span>
</div>
</button>
</template>
我们写几个来看看效果
html
<div>
<t-button type="primary" icon="chart-bar">primary</t-button>
<t-button type="success" icon="calendar">success</t-button>
<t-button type="info" icon="data-view"></t-button>
<t-button type="primary" icon="download"></t-button>
</div>
按钮 loading
loading 可以当做 icon 和 disabled 的结合,我们先定义 loading 属性
js
export const ButtonProps = {
...
// 加载
loading: {
type: Boolean,
default: false,
},
};
html
<button
class="t-button"
:class="[
`${size && 't-button--' + size}`,
`t-button__${type}`,
{ 't-button--icon': icon },
{ 'is-disabled': disabled || loading },
{ 'is-round': round },
]"
:disabled="disabled || loading"
>
<div class="t-button__inner">
<i v-if="loading" class="t-icon icon-loading"></i>
<i v-if="icon" :class="['t-icon', `icon-${icon}`]"></i>
<span v-if="$slots.default">
<slot />
</span>
</div>
</button>
css
@keyframes rotating {
0% {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
.t-button .icon-loading {
animation: rotating 2s linear infinite;
}
✨ 本专栏源码地址