TinyVue 主题定制指南

适用场景

  • 为新组件编写样式
  • 修改现有组件的视觉效果
  • 创建自定义主题
  • 调整响应式断点

核心原则

1. 主题架构

TinyVue 支持多主题系统:

packages/
├── theme/
│   └── src/
│       ├── base /             # 公共变量和混入
│       │   ├── vars.less      # 全局的CSS 变量定义
│       │   └── reset.less    # rest 样式定义
│       │   └── transition.less    # 全局动画 样式定义
│       │   └── aurora-theme.less    # vars.less 中的一个变体, aurora风格
│       │   └── dark-theme.less    # vars.less 中的一个变体, 暗黑风格
│       │   └── motion-theme.less    # vars.less 中的一个变体, motion 风格
│       │   └── old-theme.less    # vars.less 中的一个变体, 原来的主题风格
│       ├── svgs /            # 所有图标的原始 svg 文件
│       └── <component>/      # 各组件样式
│           └── vars.less      #  组件级的 CSS 变量定义
│           └── index.less     #  组件的 CSS 样式
└── theme-saas/         # SAAS 主题
    └── src/
        └── ...         # 类似结构,不同变量值

2. CSS 变量系统

Tinyvue的样式系统设计了一套多级CSS 变量系统,base CSS 变量common CSS 变量 的定义都在 packages/theme/src/base/vars.less中;组件的CSS 变量的定义在各个组件的文件夹下面。

  1. base CSS 变量 定义一套规范的颜色值和数字值, 以 --tv-base-* 打头。
  2. common CSS 变量 定义一套通用的CSS 变量值,有明确的使用场景约束, 更好的名称可读性。所有的值必须从base CSS 变量里面选取, 以 --tv-小写字母 打头, 比如 --tv-color-* 。
  3. 组件的CSS 变量 定义在组件级别的CSS 变量值,方便用户定制组件样式。 它必须使用 common CSS 变量 ,并且不允许使用 base CSS 变量的值。 以 --tv-组件名首字母大写 打头,比如: --tv-Button-*。 每一个组件级CSS 变量的上面必须是对应的注释,写明它的使用的位置。
// base CSS 变量
:root {
  --tv-base-color-brand: #191919;
  --tv-base-color-brand-1: #f0f7ff;
  --tv-base-color-brand-2: #deecff;
  --tv-base-color-brand-3: #b3d6ff;
  --tv-base-color-brand-4: #7eb7fc;
  --tv-base-color-brand-5: #4191fa;
  --tv-base-color-brand-6: #1476ff;
  --tv-base-color-brand-7: #0f5ed4;
  --tv-base-color-brand-8: #0845a6;
  --tv-base-color-brand-9: #022e7a;
  --tv-base-color-brand-10: #001a4a;
  --tv-base-color-brand-11: #3d6899;
  --tv-base-color-brand-12: #7fa6d4;
  --tv-base-color-brand-13: #b6d4f2;
}
// common CSS 变量
:root {
  --tv-color-success-text: var(--tv-base-color-success-6); // #5cb300 成功-文本色 tag的light、plain类型
  --tv-color-success-text-primary: var(--tv-base-color-common-11); // #191919 常规一级文本色(非主题色)
  --tv-color-success-bg: var(--tv-base-color-success-6); //#5cb300 成功-背景色(深) tag的dark类型/tooltip/badge
  --tv-color-success-bg-light: var(--tv-base-color-success-14); // #e6f2d5 成功-背景色(浅)
  --tv-color-success-bg-1: var(--tv-base-color-success-14); // #e6f2d5 tag的light类型
  --tv-color-success-border: var(--tv-base-color-success-6); // #5cb300 成功-边框色(深)
  --tv-color-success-border-light: var(--tv-base-color-success-14); // #e6f2d5 成功-边框色(浅) 型
  --tv-color-success-border-1: var(--tv-base-color-success-14); // #e6f2d5 tag的light类型
  --tv-color-success-icon: var(--tv-base-color-success-6); // #5cb300 成功-图标色
}

// 组件级 CSS 变量
.inject-Button-vars() {
  // 默认时按钮字重
  --tv-Button-font-weight: var(--tv-font-weight-regular, 400);
  // 默认时按钮边框宽度
  --tv-Button-border-width: var(--tv-border-width, 1px);
  // 按钮的文本行高
  --tv-Button-line-height: var(--tv-line-height-number, 1.5);
}

重要约束

目前组件库已经稳定,在开发组件时,尽量复用已经存在的 common CSS 变量,不要添加新变量。

3. BEM 命名规范

在模板中,为节点添加类名时,使用 Block__Element--Modifier 命名的规则:

// Block: 组件名
.tiny-button {
  // Element: 组成部分(双下划线)
  &__icon {
    margin-right: @spacing-xs;
  }

  &__text {
    font-weight: bold;
  }

  // Modifier: 状态变体(双横线)
  &--primary {
    background-color: @color-brand;
  }

  &--disabled {
    opacity: @opacity-disabled;
  }
}
@button-prefix-cls: ~'@{css-prefix}button';

.@{button-prefix-cls} {
  .inject-Button-vars();

  &.@{button-prefix-cls}--large {
    .size-mixin(-large);
  }
  &.@{button-prefix-cls}--medium {
    .size-mixin(-medium);
  }
  &.@{button-prefix-cls}--small {
    .size-mixin(-small);
  }
  &.@{button-prefix-cls}--mini {
    .size-mixin(-mini);
  }

4. 组件的样式开发规范

一个组件通常都有三个样式文件:

  1. vars.less: 组件级 CSS 变量。 通过分析组件中,哪些地方的样式需要适配不同主题,以及它们的值在 common CSS 变量中存在的,就需要找出来定义为组件级变量。 每一个组件级变量上面必须写注释指示该变量的使用场景。示例如下:
.inject-Button-vars() {
  // 默认时按钮字重
  --tv-Button-font-weight: var(--tv-font-weight-regular, 400);
  // 默认时按钮边框宽度
  --tv-Button-border-width: var(--tv-border-width, 1px);
  // 按钮的文本行高
  --tv-Button-line-height: var(--tv-line-height-number, 1.5);
  // 默认时按钮圆角
  --tv-Button-border-radius: var(--tv-border-radius-md, 6px); // 默认还原为6px
  // 大圆角时按钮圆角
  --tv-Button-border-radius-round: var(--tv-border-radius-round, 999px);
  // 圆形时按钮圆角
  --tv-Button-border-radius-circle: var(--tv-border-radius-round, 999px);
}
  1. index.less: 组件的样式编写。每个组件的根节点,

首先要注入一下自己的组件级 CSS 变量,以便这些变量生效。如果组件有多个根节点,或者有弹出层等场景,需要给所有的根节点或弹出层的根节点添加这个注入。

其次,尽量使用 less嵌套结构父选择器 & 的能力进行编写。

最后,需要使用组件级的CSS变量,避免使用 common CSS 变量base CSS 变量

示例如下:

@alert-prefix-cls: ~'@{css-prefix}alert';

.@{alert-prefix-cls} {
  .inject-Alert-vars();

  position: relative;
  display: flex;
  border: none;
  border-radius: var(--tv-Alert-border-radius);
  padding: var(--tv-Alert-padding-y) var(--tv-Alert-padding-x);
  margin: var(--tv-Alert-margin-y) var(--tv-Alert-margin-x);
  line-height: 1.5;

  &.is-center {
    justify-content: center;
    align-items: center;
  }

  /** alert-icon 场景 */
  .@{alert-prefix-cls}__icon:not(.@{alert-prefix-cls}__close) {
    font-size: var(--tv-Alert-icon-size);
    margin-right: var(--tv-Alert-icon-margin-right);
    flex-shrink: 0;
    margin-top: 2px;
  }
}
  1. 组件的响应式设计

组件在不同的屏幕尺寸时,有不同的规范要求时,就需要添加 responsive.less 样式文件,编写在指定的屏幕大小时应该呈现的样式。开发规范同 index.ts 一致。示例如下:

@import '../custom.less';
@import './vars.less';
@alert-prefix-cls: ~'@{css-prefix}alert';

@media screen and (max-width: 1280px) {
  .@{alert-prefix-cls} {
    .inject-Alert-responsive-vars();

    .@{alert-prefix-cls}__content {
      .@{alert-prefix-cls}__title {
        font-size: var(--tv-Alert-title-responsive-font-size);
      }

      .@{alert-prefix-cls}__description {
        color: var(--tv-Alert-title-responsive-text-color);
      }
    }
  }
}

5 移动优先的多端模板的开发规范

组件的多端模板 mobile-first.vue文件,不使用传统的 BEM样式规范,而是使用 tailwind css进行开发,tailwind css的配置文件在..\packages\theme-saas\tailwind.config.js 中,其中有定制的颜色值,断点值,布局等配置信息。

多端模板不需要引入任何的 css 文件,在模板中,只需要添加tailwind的类名即可,示例如下:

<template>
  <div
    data-tag="tiny-alert"
    v-if="state.show"
    :class="
      m(
        'min-h-min flex py-2 sm:py-3 px-4 my-2 rounded box-border font-light sm:font-normal text-color-text-primary',
        { 'bg-color-info-primary-subtler': type === 'info' || !type },
        { 'bg-color-error-subtler': type === 'error' },
        { 'bg-color-warning-subtler': type === 'warning' },
        { 'bg-color-success-subtler': type === 'success' },
        { 'text-center': center },
        customClass
      )
    "
  >
    <span
      v-else-if="closeText && closable"
      data-tag="tiny-alert-close-text"
      @click="handleClose"
      class="leading-6 text-sm cursor-pointer"
      >{{ closeText }}</span
    >
  </div>
</template>

上面的 m函数是适配层注入的tailwind merge函数,用于合并类名。 如果元素上的类名太长,也可以将类名抽取为变量,集中编写到 token.ts文件中。

export const classes = {
  'button': 'inline-block.....',
  'size-default': 'h-10 text-sm sm:h-7'
  // ....
}

Tailwind CSS 集成(Mobile First)

<!-- 使用 Tailwind 工具类 -->
<template>
  <div
    :class="
      m(
        'flex items-center justify-between',
        'px-4 py-2 sm:px-6 sm:py-3',
        'bg-white dark:bg-gray-800',
        'border border-gray-200 rounded-lg',
        'hover:border-blue-500 transition-colors'
      )
    "
  >
    <!-- 内容 -->
  </div>
</template>

主题定制检查清单

编写样式时,确认:

变量使用

BEM 命名

响应式

无障碍

性能

兼容性

调试技巧

查看 CSS 变量

// 在浏览器控制台执行
getComputedStyle(document.documentElement).getPropertyValue('--tv-color-brand')

临时覆盖样式

<style scoped>
/* 仅用于调试,不要提交 */
.tiny-my-component {
  border: 1px solid red !important;
}
</style>

使用浏览器 DevTools

  1. 打开 Elements 面板
  2. 查看 Computed 样式
  3. 检查 CSS 变量值
  4. 实时修改测试效果

参考资源