主题方案和基础组件:如何设计组件库的主题方案?

注:此文章为杨文坚老师的课程笔记,非作者原创

什么是"可定制化主题"呢?

如果你在电商企业中进行业务功能的前端页面开发,原有使用的组件库是蓝色风格的样式,但是想在节假日里快速转变成红色风格的组件样式,再比如,如果你开发的页面是亮色系的效果,哪天产品经理需要前端快速实现暗色系的黑夜效果,提升用户夜间的使用体验,那么,你会怎么做前端页面的改造呢?

这些场景,都要处理前端页面整体颜色以及视觉风格的变化,这类"变化"在前端开发中一般定义为"主题"的控制,也就是"可定制化主题"。

组件库的主题方案设计需要做什么准备?

既然是方案设计,首先要做的是方案的规范准备,这里主题的方案设计需要准备以下两种规范:

  • 颜色的设计规范
  • CSS 的开发规范

前面我们提到,页面主题的变化主要是整体颜色视觉风格的变化。而且,使用组件库开发,业务功能页面控制主题是通过组件库的内置主题系统来控制的。所以,我们主题方案设计的第一步就是需要设计好组件库的颜色规范

这里要明确一点,颜色规范设计通常不是前端开发者的职责工作,而是设计师的工作。但是前端作为设计稿和实现代码之间的"桥梁",需要做好设计稿的沟通和讨论。不过,设计师一般对颜色规范设计的流程比较严格,也有很多讲究。所以,作为前端开发者,我们有必要简单了解这些颜色规范设计的过程。

第一步是颜色种类的选择。一般设计师会选择几种大类型的颜色,例如红色、蓝色和绿色等。然后根据业务需要挑选这几类型中的一个基准色号。

第二步,基于上一步选择好的基准色号,进行颜色梯度的处理,例如颜色亮度和饱和度从浅到深的梯度处理。举个例子,灰色的颜色梯度处理如下图片所示:

最后一步,也就是第三步,根据不同颜色的颜色梯度,进行语义化使用处理。我们拿上述灰色每个梯度的颜色来"语义化处理",将"灰色 1 号"作为页面背景颜色,"灰色 10 号"作为页面的字体颜色。效果如下述所示:

有很多开源组件库都提供了现成的颜色设计规范。例如Ant Design 官方团队的颜色规范Element Plus 官方团队的颜色规范都可以直接参考。

前端开发中对组件库的主题开发和控制主要基于 CSS 来处理的,所以在开发之前,我们要制定 CSS 的开发规范。

首先,我们要选择 CSS 的预处理器语言来开发,主要利用 CSS 预处理器语言的"可编程的逻辑语法"来编写 CSS。目前主流的 CSS 的预处理器语言有 Less 和 Sass,两者语法比较类似。

  • Less,对 CSS 原始语法增加了少许方便的扩展,例如函数、运算等语法,学习更容易
  • 预处理器 Sass,语法更加丰富和全面。

两种预处理器语言都有很多开源组件库都在使用,例如 Ant Design 使用了 Less,Element Plus 选择了 Sass。这里我们主要选择 Less 来开发组件库的 CSS 代码,主要是考虑到 Less 简单易用,能满足绝大部分的开发需要。

我们可以用 Less 里的变量语法来管理所有的颜色梯度,如下述代码所示:

less 复制代码
@gray-1: #f5f5f5;
@gray-2: #f0f0f0;
@gray-3: #d9d9d9;
@gray-4: #bfbfbf;
@gray-5: #8c8c8c;
@gray-6: #595959;
@gray-7: #434343;
@gray-8: #262626;
@gray-9: #1f1f1f;
@gray-10: #141414;

不过,虽然我们已经有了 Less 作为预处理器语法来管理 CSS 代码,例如主题颜色梯度都用了 Less 变量语法来管理,但接下来我们还要管理具体组件的主题样式上面,而且还要语义化控制到具体某个组件的某个维度的颜色

什么是语义化颜色呢?一个按钮的背景颜色是"蓝色 1 号",语义化颜色就是将"蓝色 1 号"语义化给了按钮背景颜色。但是实际中,组件语义化的内容维度是有很多层次的。

一个按钮的颜色,有背景颜色、字体颜色和边框颜色这一整套颜色体系,在按钮默认状态、点击状态和禁用状态时,背景、字体和边框颜色又需要独立的一套新的颜色体系。这个时候,就有三套颜色体系。如果再叠加一个按钮类型,例如是"实心颜色"的按钮和"空心颜色"的按钮,就演变成 3x2 的 6 种颜色体系。

你想,就这一个按钮组件,都有 6 种颜色体系,那如何做好 CSS 的语义化代码管理和维护呢?如果再来一个"白天"和"黑夜"模式的主题切换颜色功能,那要如何管理呢?

这个时候就需要用到 CSS Variable 来管理语义化的组件颜色了。

什么是 CSS Variable 呢?就是 CSS 自定义属性,也可以称作 CSS 变量或者级联变量,主要是 CSS 代码在浏览器中,定义一个 CSS 属性(或者称为"变量")后,这个 CSS 属性就可以在页面中全局其它 CSS 代码中使用,甚至是覆盖重写。例如下面代码所示:

less 复制代码
@prefix-name: my-vue;

@white: #ffffff;
@black: #222222;
// ...
@gray-4: #bfbfbf;
// ...

:root {
  // 页面背景颜色
  --@{prefix-name}-page-bg-color: @white;
  // 页面字体颜色
  --@{prefix-name}-page-text-color: @black;
  // 页面通用边框颜色
  --@{prefix-name}-page-border-color: @gray-4;
}

.@{prefix-name}-box {
  background: ~'var(--@{prefix-name}-page-bg-color)';
  color: ~'var(--@{prefix-name}-page-text-color)';
}

这里的代码是用 Less 来管理颜色梯度,再用 CSS Variable 来使用颜色梯度的 Less 变量,同时语义化来管理不同维度的语义化颜色

我们现在有了颜色设计规范和 CSS 开发规范,最后要面临的问题就是"如何实现组件库的主题方案"了,我下面就用一个最简单的案例来实现一个主题方案,示范一下。

如何实现组件库主题方案?

在讲解组件库的主题方案前,我们先看看效果:

在这张动图中,我实现了一个组件 Box,设置了组件语义化的背景色和字体色,切换主题的时候,就用 className 的优先级来控制语义化的 CSS Variable,也就是用新主题的颜色样式覆盖掉原有的默认主题的颜色样式,达到动态切换主题。

具体代码如下述所示。Box 组件的 Less 代码:

less 复制代码
@prefix-name: my-vue;

@white: #ffffff;
@black: #222222;
// ...
@gray-4: #bfbfbf;
// ...

// 默认明亮主题颜色
:root {
  // 页面背景颜色
  --@{prefix-name}-page-bg-color: @white;
  // 页面字体颜色
  --@{prefix-name}-page-text-color: @black;
}

:root {
  // 暗黑主题颜色
  &.@{prefix-name}-theme-dark {
    // 页面背景颜色
    --@{prefix-name}-page-bg-color: @black;
    // 页面字体颜色
    --@{prefix-name}-page-text-color: @white;
  }
}

.@{prefix-name}-box {
  background: ~'var(--@{prefix-name}-page-bg-color)';
  color: ~'var(--@{prefix-name}-page-text-color)';
}

Box 组件的 Vue 代码:

xml 复制代码
<template>
  <div :class="{ [baseClassName]: true }">
    <slot v-if="$slots.default"></slot>
  </div>
</template>

<script setup lang="ts">
import { prefixName } from '../theme/index';
const baseClassName = `${prefixName}-box`;
</script>

Box 组件的使用代码:

xml 复制代码
<template>
  <Box class="example">
    <div :style="{ padding: 10, fontSize: 24 }">这是一个主题演示案例</div>
    <button @click="onClick">点击换主题色</button>
  </Box>
</template>

<script setup lang="ts">
import { Box } from '../src';
import { prefixName } from '../src/theme/index';

const onClick = () => {
  const darkThemeName = `${prefixName}-theme-dark`;
  const html = document.querySelector('html');
  if (html?.classList.contains(darkThemeName)) {
    html?.classList.remove(darkThemeName);
  } else {
    html?.classList.add(darkThemeName);
  }
};
</script>

<style>
html,
body {
  height: 100%;
  width: 100%;
}
.example {
  height: 100%;
  padding: 100px;
  box-sizing: border-box;
  text-align: center;
}
</style>

通过上述"明亮主题"和"暗黑主题"的切换代码和演示案例,你会发现,组件库的主题方案设计核心就是颜色规范 + CSS Variable 控制。是不是很简单?

其实,理论上是可以这么简单理解的,但是实际在组件库的每个组件开发过程中,我们要做好组件内部的主题方案的实现,还是有点"复杂度"的。

这个"复杂度"体现在不同组件的使用场景不同,不同组件的主题方案都要"因地制宜"来实现。具体情况,我给你演示一个基础的组件实现,你就知道了。接下来我就给你演示一下,如何开发一个常见的多状态的按钮组件。

如何开发一个多状态的按钮组件?

在开发组件前,还是先演示一下最终的效果:

上面动图中,演示的是常见组件库里实现的按钮组件,具备多种组件的状态维度。

这里维度分成:

  • 按钮类型
  • 按钮变种(种类)
  • 按钮禁用状态。

其中按钮类型有五类:

  • Default
  • Primary
  • Success
  • Warn
  • Danger

按钮变种有两类:

  • Contented
  • Outlined

按钮禁用状态有两类。

  • Enabled
  • Disabled

总的来讲,上述按钮有这三种维度状态,也就是类型、变种和是否禁用。那么,如何实现这个三种维度的叠加管理呢?分解成这三个步骤:

  • 第一步,基础按钮组件样式的开发;
  • 第二步,实现按钮不同维度组合的样式;
  • 第三步,组件的使用状态叠加。

第一步,基础按钮组件的样式开发,也就是实现一个按钮的"底座",后续可以基于这个底座做各类维度叠加的样式开发,具体代码如下述所示。

less 复制代码
.@{prefix-name}-button {
  position: relative;
  display: inline-block;
  font-weight: 400;
  white-space: nowrap;
  text-align: center;
  cursor: pointer;
  user-select: none;
  touch-action: manipulation;
  height: 32px;
  padding: 4px 15px;
  font-size: 14px;
  border-radius: 2px;
  box-sizing: border-box;
  border-width: 1px;
}

Vue 代码:

xml 复制代码
<template>
  <button
    :class="{
      [baseClassName]: true,
    }"
  >
    <slot v-if="$slots.default"></slot>
  </button>
</template>

<script setup lang="ts">
import { prefixName } from '../theme/index';
const baseClassName = `${prefixName}-button`;
</script>

第二步,实现按钮不同维度组合的样式,也就是我们要根据不同状态维度的组合来实现样式的叠加。

我先将按钮的不同维度的样式的 className 做个统一的管理,其中按钮类型和按钮变种统一管理,形成 5 x 3 的 15 个基础按钮样式。

具体 CSS Variable 语义化,如下面所示:

less 复制代码
// variable.less
:root {

  // 按钮 default-contained: 默认状态
  --@{prefix-name}-btn-default-contained-color: @gray-1;
  --@{prefix-name}-btn-default-contained-border-color: @gray-6;
  --@{prefix-name}-btn-default-contained-bg-color: @gray-6;

  // 按钮 primary-contained: 默认状态
  --@{prefix-name}-btn-primary-contained-color: @blue-1;
  --@{prefix-name}-btn-primary-contained-border-color: @blue-6;
  --@{prefix-name}-btn-primary-contained-bg-color: @blue-6;

  // 按钮 success-contained: 默认状态
  --@{prefix-name}-btn-success-contained-color: @green-1;
  --@{prefix-name}-btn-success-contained-border-color: @green-6;
  --@{prefix-name}-btn-success-contained-bg-color: @green-6;

  // 按钮 warning-contained: 默认状态
  --@{prefix-name}-btn-warning-contained-color: @gold-1;
  --@{prefix-name}-btn-warning-contained-border-color: @gold-6;
  --@{prefix-name}-btn-warning-contained-bg-color: @gold-6;

  // 按钮 danger-contained: 默认状态
  --@{prefix-name}-btn-danger-contained-color: @red-1;
  --@{prefix-name}-btn-danger-contained-border-color: @red-6;
  --@{prefix-name}-btn-danger-contained-bg-color: @red-6;

  // 按钮 default-outlined: 默认状态
  --@{prefix-name}-btn-default-outlined-color: @gray-6;
  --@{prefix-name}-btn-default-outlined-border-color: @gray-6;
  --@{prefix-name}-btn-default-outlined-bg-color: @gray-1;

  // 按钮 primary-outlined: 默认状态
  --@{prefix-name}-btn-primary-outlined-color: @blue-6;
  --@{prefix-name}-btn-primary-outlined-border-color: @blue-6;
  --@{prefix-name}-btn-primary-outlined-bg-color: @blue-1;

  // 按钮 success-outlined: 默认状态
  --@{prefix-name}-btn-success-outlined-color: @green-6;
  --@{prefix-name}-btn-success-outlined-border-color: @green-6;
  --@{prefix-name}-btn-success-outlined-bg-color: @green-1;

  // 按钮 warning-outlined: 默认状态
  --@{prefix-name}-btn-warning-outlined-color: @gold-6;
  --@{prefix-name}-btn-warning-outlined-border-color: @gold-6;
  --@{prefix-name}-btn-warning-outlined-bg-color: @gold-1;

  // 按钮 danger-outlined: 默认状态
  --@{prefix-name}-btn-danger-outlined-color: @red-6;
  --@{prefix-name}-btn-danger-outlined-border-color: @red-6;
  --@{prefix-name}-btn-danger-outlined-bg-color: @red-1;
}

具体 className 组合实现如下述所示:

less 复制代码
@import '../../theme/variable.less';

.@{prefix-name}-button {
  // ....

  // contented
  &.@{prefix-name}-button-default-contained {
    background: ~'var(--@{prefix-name}-btn-default-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-default-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-default-contained-border-color)';
  }
  &.@{prefix-name}-button-primary-contained {
    background: ~'var(--@{prefix-name}-btn-primary-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-primary-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-primary-contained-border-color)';
  }
  &.@{prefix-name}-button-success-contained {
    background: ~'var(--@{prefix-name}-btn-success-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-success-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-success-contained-border-color)';
  }
  &.@{prefix-name}-button-warning-contained {
    background: ~'var(--@{prefix-name}-btn-warning-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-warning-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-warning-contained-border-color)';
  }
  &.@{prefix-name}-button-danger-contained {
    background: ~'var(--@{prefix-name}-btn-danger-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-danger-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-danger-contained-border-color)';
  }
  // outlined
  &.@{prefix-name}-button-default-outlined {
    background: ~'var(--@{prefix-name}-btn-default-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-default-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-default-outlined-border-color)';
  }
  &.@{prefix-name}-button-primary-outlined {
    background: ~'var(--@{prefix-name}-btn-primary-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-primary-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-primary-outlined-border-color)';
  }
  &.@{prefix-name}-button-success-outlined {
    background: ~'var(--@{prefix-name}-btn-success-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-success-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-success-outlined-border-color)';
  }
  &.@{prefix-name}-button-warning-outlined {
    background: ~'var(--@{prefix-name}-btn-warning-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-warning-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-warning-outlined-border-color)';
  }
  &.@{prefix-name}-button-danger-outlined {
    background: ~'var(--@{prefix-name}-btn-danger-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-danger-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-danger-outlined-border-color)';
  }
}

Vue 代码实现如下:

vue 复制代码
<template>
  <button
    :class="{
      [baseClassName]: true,
      [btnClassName]: true
    }"
  >
    <slot v-if="$slots.default"></slot>
  </button>
</template>

<script setup lang="ts">
import { prefixName } from '../theme/index';
import type { ButtonType, ButtonVariant } from './types';
const props = withDefaults(
  defineProps<{
    type?: ButtonType;
    variant?: ButtonVariant;
  }>(),
  {
    type: 'default',
    variant: 'contained',
    disabled: false
  }
);

const baseClassName = `${prefixName}-button`;
const btnClassName = `${baseClassName}-${props.type}-${props.variant}`;
</script>

实现后的效果如下:

接下来实现按钮禁用的样式,主要也是通过添加 className,利用其添加在后面,优先级更高来覆盖样式,具体实现代码如下:

css 复制代码
@import '../../theme/variable.less';

.@{prefix-name}-button {

  // ...
  &.@{prefix-name}-button-disabled {
    opacity: 0.5;
    cursor: not-allowed;
    &:hover {
      opacity: 0.5;
    }
  }
}

Vue 代码改造后如下:

xml 复制代码
<template>
  <button
    :class="{
      [baseClassName]: true,
      [btnClassName]: true,
      [disabledClassName]: props.disabled
    }"
  >
    <slot v-if="$slots.default"></slot>
  </button>
</template>

<script setup lang="ts">
import { prefixName } from '../theme/index';
import type { ButtonType, ButtonVariant } from './types';
const props = withDefaults(
  defineProps<{
    type?: ButtonType;
    variant?: ButtonVariant;
    disabled?: boolean;
  }>(),
  {
    type: 'default',
    variant: 'contained',
    disabled: false
  }
);

const baseClassName = `${prefixName}-button`;
const btnClassName = `${baseClassName}-${props.type}-${props.variant}`;
const disabledClassName = `${baseClassName}-disabled`;
</script>

实现上述代码后,使用该按钮组件枚举所有按钮状态叠加效果如下图所示:

到了这一步,我们就已经实现了一个多状态的 Vue.js 3.x 按钮组件,可以通过传入不同的 Props 来控制显示不同的状态样式和状态叠加的样式。

讲到这,你是不是已经觉得"复杂度"有所提升了?其实这个按钮组件的复杂度基本就到此为止了,也就是说这个按钮的"基础底座"已经实现了。

我们接下来要做的就是基于已有的"底座",也就是已经实现好的按钮语义化的 className 和 CSS Variable,来配置主题控制和样式主题的切换效果。

如何对多状态的按钮组件进行主题控制?

通过上述的规范设计阶段,我们应该知道,主题核心就是颜色梯度的控制,我们在处理按钮组件的不同颜色的时候,只是选择某个颜色的某个梯度号。也就是说,当我们想按钮主题风格时候,只需要控制"颜色"和"梯度"就行了,再覆盖对应的 CSS Variable,就能实现主题快速切换,不需要关注其他。

我这里具体拆解成两步:

  • 第一步,对按钮不同状态维度组合选择对应色板的颜色梯度;
  • 第二步,将选好的颜色用新 className 来覆盖原来的 CSS Variable。

刚刚实现按钮组件是默认的"明亮"模式的主题,我现在针对按钮组件按钮的所有维度状态,快速实现一个"暗黑"主题的颜色效果。这里可以直接切换颜色"梯度",直接从取梯度号的镜像号数,例如"蓝色 2 号"的就换成"蓝色 8 号"来替换。

然后按照第二步的操作,全部替换到对应的 CSS Variable,根据 className 优先级操作,来覆盖主题样式,具体代码如下述所示:

明亮主题 Less 代码:

scss 复制代码
@import "./variable.less";

:root {
  // 按钮 default-contained: 默认状态
  --@{prefix-name}-btn-default-contained-color: @gray-1;
  --@{prefix-name}-btn-default-contained-border-color: @gray-6;
  --@{prefix-name}-btn-default-contained-bg-color: @gray-6;
  // 按钮 default-contained: Hover状态
  --@{prefix-name}-btn-default-contained-color-hover: @gray-2;
  --@{prefix-name}-btn-default-contained-border-color-hover: @gray-8;
  --@{prefix-name}-btn-default-contained-bg-color-hover: @gray-8;

  // 按钮 primary-contained: 默认状态
  --@{prefix-name}-btn-primary-contained-color: @blue-1;
  --@{prefix-name}-btn-primary-contained-border-color: @blue-6;
  --@{prefix-name}-btn-primary-contained-bg-color: @blue-6;
  // 按钮 primary-contained: Hover状态
  --@{prefix-name}-btn-primary-contained-color-hover: @blue-2;
  --@{prefix-name}-btn-primary-contained-border-color-hover: @blue-8;
  --@{prefix-name}-btn-primary-contained-bg-color-hover: @blue-8;

  // 按钮 success-contained: 默认状态
  --@{prefix-name}-btn-success-contained-color: @green-1;
  --@{prefix-name}-btn-success-contained-border-color: @green-6;
  --@{prefix-name}-btn-success-contained-bg-color: @green-6;
  // 按钮 success-contained: Hover状态
  --@{prefix-name}-btn-success-contained-color-hover: @green-2;
  --@{prefix-name}-btn-success-contained-border-color-hover: @green-8;
  --@{prefix-name}-btn-success-contained-bg-color-hover: @green-8;

  // 按钮 warning-contained: 默认状态
  --@{prefix-name}-btn-warning-contained-color: @gold-1;
  --@{prefix-name}-btn-warning-contained-border-color: @gold-6;
  --@{prefix-name}-btn-warning-contained-bg-color: @gold-6;
  // 按钮 warning-contained: Hover状态
  --@{prefix-name}-btn-warning-contained-color-hover: @gold-2;
  --@{prefix-name}-btn-warning-contained-border-color-hover: @gold-8;
  --@{prefix-name}-btn-warning-contained-bg-color-hover: @gold-8;

  // 按钮 danger-contained: 默认状态
  --@{prefix-name}-btn-danger-contained-color: @red-1;
  --@{prefix-name}-btn-danger-contained-border-color: @red-6;
  --@{prefix-name}-btn-danger-contained-bg-color: @red-6;
  // 按钮 danger-contained: Hover状态
  --@{prefix-name}-btn-danger-contained-color-hover: @red-2;
  --@{prefix-name}-btn-danger-contained-border-color-hover: @red-8;
  --@{prefix-name}-btn-danger-contained-bg-color-hover: @red-8;

  // 按钮 default-outlined: 默认状态
  --@{prefix-name}-btn-default-outlined-color: @gray-6;
  --@{prefix-name}-btn-default-outlined-border-color: @gray-6;
  --@{prefix-name}-btn-default-outlined-bg-color: @gray-1;
  // 按钮 default-outlined: Hover状态
  --@{prefix-name}-btn-default-outlined-color-hover: @gray-8;
  --@{prefix-name}-btn-default-outlined-border-color-hover: @gray-8;
  --@{prefix-name}-btn-default-outlined-bg-color-hover: @gray-2;

  // 按钮 primary-outlined: 默认状态
  --@{prefix-name}-btn-primary-outlined-color: @blue-6;
  --@{prefix-name}-btn-primary-outlined-border-color: @blue-6;
  --@{prefix-name}-btn-primary-outlined-bg-color: @blue-1;
  // 按钮 primary-outlined: Hover状态
  --@{prefix-name}-btn-primary-outlined-color-hover: @blue-8;
  --@{prefix-name}-btn-primary-outlined-border-color-hover: @blue-8;
  --@{prefix-name}-btn-primary-outlined-bg-color-hover: @blue-2;

  // 按钮 success-outlined: 默认状态
  --@{prefix-name}-btn-success-outlined-color: @green-6;
  --@{prefix-name}-btn-success-outlined-border-color: @green-6;
  --@{prefix-name}-btn-success-outlined-bg-color: @green-1;
  // 按钮 success-outlined: Hover状态
  --@{prefix-name}-btn-success-outlined-color-hover: @green-8;
  --@{prefix-name}-btn-success-outlined-border-color-hover: @green-8;
  --@{prefix-name}-btn-success-outlined-bg-color-hover: @green-2;

  // 按钮 warning-outlined: 默认状态
  --@{prefix-name}-btn-warning-outlined-color: @gold-6;
  --@{prefix-name}-btn-warning-outlined-border-color: @gold-6;
  --@{prefix-name}-btn-warning-outlined-bg-color: @gold-1;
  // 按钮 warning-outlined: Hover状态
  --@{prefix-name}-btn-warning-outlined-color-hover: @gold-8;
  --@{prefix-name}-btn-warning-outlined-border-color-hover: @gold-8;
  --@{prefix-name}-btn-warning-outlined-bg-color-hover: @gold-2;

  // 按钮 danger-outlined: 默认状态
  --@{prefix-name}-btn-danger-outlined-color: @red-6;
  --@{prefix-name}-btn-danger-outlined-border-color: @red-6;
  --@{prefix-name}-btn-danger-outlined-bg-color: @red-1;
  // 按钮 danger-outlined: Hover状态
  --@{prefix-name}-btn-danger-outlined-color-hover: @red-8;
  --@{prefix-name}-btn-danger-outlined-border-color-hover: @red-8;
  --@{prefix-name}-btn-danger-outlined-bg-color-hover: @red-2
}

暗黑主题 Less 代码:

scss 复制代码
@import "../variable.less";

:root {
  &.@{prefix-name}-theme-dark {
    // 按钮 default-contained: 默认状态
    --@{prefix-name}-btn-default-contained-color: @gray-9;
    --@{prefix-name}-btn-default-contained-border-color: @gray-4;
    --@{prefix-name}-btn-default-contained-bg-color: @gray-4;
    // 按钮 default-contained: Hover状态
    --@{prefix-name}-btn-default-contained-color-hover: @gray-8;
    --@{prefix-name}-btn-default-contained-border-color-hover: @gray-2;
    --@{prefix-name}-btn-default-contained-bg-color-hover: @gray-2;

    // 按钮 primary-contained: 默认状态
    --@{prefix-name}-btn-primary-contained-color: @blue-9;
    --@{prefix-name}-btn-primary-contained-border-color: @blue-4;
    --@{prefix-name}-btn-primary-contained-bg-color: @blue-4;
    // 按钮 primary-contained: Hover状态
    --@{prefix-name}-btn-primary-contained-color-hover: @blue-8;
    --@{prefix-name}-btn-primary-contained-border-color-hover: @blue-2;
    --@{prefix-name}-btn-primary-contained-bg-color-hover: @blue-2;

    // 按钮 success-contained: 默认状态
    --@{prefix-name}-btn-success-contained-color: @green-9;
    --@{prefix-name}-btn-success-contained-border-color: @green-4;
    --@{prefix-name}-btn-success-contained-bg-color: @green-4;
    // 按钮 success-contained: Hover状态
    --@{prefix-name}-btn-success-contained-color-hover: @green-8;
    --@{prefix-name}-btn-success-contained-border-color-hover: @green-2;
    --@{prefix-name}-btn-success-contained-bg-color-hover: @green-2;

    // 按钮 warning-contained: 默认状态
    --@{prefix-name}-btn-warning-contained-color: @gold-9;
    --@{prefix-name}-btn-warning-contained-border-color: @gold-4;
    --@{prefix-name}-btn-warning-contained-bg-color: @gold-4;
    // 按钮 warning-contained: Hover状态
    --@{prefix-name}-btn-warning-contained-color-hover: @gold-8;
    --@{prefix-name}-btn-warning-contained-border-color-hover: @gold-2;
    --@{prefix-name}-btn-warning-contained-bg-color-hover: @gold-2;

    // 按钮 danger-contained: 默认状态
    --@{prefix-name}-btn-danger-contained-color: @red-9;
    --@{prefix-name}-btn-danger-contained-border-color: @red-4;
    --@{prefix-name}-btn-danger-contained-bg-color: @red-4;
    // 按钮 danger-contained: Hover状态
    --@{prefix-name}-btn-danger-contained-color-hover: @red-8;
    --@{prefix-name}-btn-danger-contained-border-color-hover: @red-2;
    --@{prefix-name}-btn-danger-contained-bg-color-hover: @red-2;

    // 按钮 default-outlined: 默认状态
    --@{prefix-name}-btn-default-outlined-color: @gray-4;
    --@{prefix-name}-btn-default-outlined-border-color: @gray-4;
    --@{prefix-name}-btn-default-outlined-bg-color: @gray-9;
    // 按钮 default-outlined: Hover状态
    --@{prefix-name}-btn-default-outlined-color-hover: @gray-2;
    --@{prefix-name}-btn-default-outlined-border-color-hover: @gray-2;
    --@{prefix-name}-btn-default-outlined-bg-color-hover: @gray-8;

    // 按钮 primary-outlined: 默认状态
    --@{prefix-name}-btn-primary-outlined-color: @blue-4;
    --@{prefix-name}-btn-primary-outlined-border-color: @blue-4;
    --@{prefix-name}-btn-primary-outlined-bg-color: @blue-9;
    // 按钮 primary-outlined: Hover状态
    --@{prefix-name}-btn-primary-outlined-color-hover: @blue-2;
    --@{prefix-name}-btn-primary-outlined-border-color-hover: @blue-2;
    --@{prefix-name}-btn-primary-outlined-bg-color-hover: @blue-8;

    // 按钮 success-outlined: 默认状态
    --@{prefix-name}-btn-success-outlined-color: @green-4;
    --@{prefix-name}-btn-success-outlined-border-color: @green-4;
    --@{prefix-name}-btn-success-outlined-bg-color: @green-9;
    // 按钮 success-outlined: Hover状态
    --@{prefix-name}-btn-success-outlined-color-hover: @green-2;
    --@{prefix-name}-btn-success-outlined-border-color-hover: @green-2;
    --@{prefix-name}-btn-success-outlined-bg-color-hover: @green-8;

    // 按钮 warning-outlined: 默认状态
    --@{prefix-name}-btn-warning-outlined-color: @gold-4;
    --@{prefix-name}-btn-warning-outlined-border-color: @gold-4;
    --@{prefix-name}-btn-warning-outlined-bg-color: @gold-9;
    // 按钮 warning-outlined: Hover状态
    --@{prefix-name}-btn-warning-outlined-color-hover: @gold-2;
    --@{prefix-name}-btn-warning-outlined-border-color-hover: @gold-2;
    --@{prefix-name}-btn-warning-outlined-bg-color-hover: @gold-8;

    // 按钮 danger-outlined: 默认状态
    --@{prefix-name}-btn-danger-outlined-color: @red-4;
    --@{prefix-name}-btn-danger-outlined-border-color: @red-4;
    --@{prefix-name}-btn-danger-outlined-bg-color: @red-9;
    // 按钮 danger-outlined: Hover状态
    --@{prefix-name}-btn-danger-outlined-color-hover: @red-2;
    --@{prefix-name}-btn-danger-outlined-border-color-hover: @red-2;
    --@{prefix-name}-btn-danger-outlined-bg-color-hover: @red-8;

  }
}

最终效果下述所示:

好了,至此,我们就基于主题方案实现了 Vue.js 3.x 组件库的一个基础组件------按钮组件的功能和主题效果。

完整代码在这里

相关推荐
ziyue757517 分钟前
vue修改element-ui的默认的class
前端·vue.js·ui
程序定小飞2 小时前
基于springboot的在线商城系统设计与开发
java·数据库·vue.js·spring boot·后端
BumBle2 小时前
uniapp 用css实现圆形进度条组件
前端·vue.js·uni-app
Komorebi_99993 小时前
Vue3 + TypeScript provide/inject 小白学习笔记
前端·javascript·vue.js
二十雨辰4 小时前
vite性能优化
前端·vue.js
明月与玄武4 小时前
浅谈 富文本编辑器
前端·javascript·vue.js
FuckPatience5 小时前
Vue 与.Net Core WebApi交互时路由初探
前端·javascript·vue.js
aklry5 小时前
elpis之学习总结
前端·vue.js
FuckPatience7 小时前
Vue ASP.Net Core WebApi 前后端传参
前端·javascript·vue.js
Komorebi_99997 小时前
Vue3 provide/inject 详细组件关系说明
前端·javascript·vue.js