diff --git a/packages/theme-generator/CONTRIBUTING_zh-CN.md b/packages/theme-generator/CONTRIBUTING_zh-CN.md new file mode 100644 index 00000000..2189ee30 --- /dev/null +++ b/packages/theme-generator/CONTRIBUTING_zh-CN.md @@ -0,0 +1,76 @@ +# CONTRIBUTING + +**如果你想要新增特性或修复 Bug,建议阅读本文档,以便更好地理解代码的实现** + +## 📁 目录结构 + +项目 UI 层由一个浮动入口 `dock` 和一个包含多个功能面板的侧边 `panel` 组成: + +```mermaid +graph TB + A[Generator] --> B[float-dock] + A[Generator] --> C[panel-drawer] + + C[panel-drawer] --> D[color-panel] + C[panel-drawer] --> E[font-panel] + C[panel-drawer] --> F[shadow-panel] + C[panel-drawer] --> G[radius-panel] + C[panel-drawer] --> H[size-panel] + + classDef panelFill fill:#e6fffb,stroke:#13c2c2,stroke-width:2px; + class D,E,F,G,H panelFill; +``` + +项目还包含两个文件夹: + +```mermaid +graph TB + A[styles] --> B["*.min.css 样式文件"] + + C[common] --> D[components 通用组件] + C[common] --> E[i18n 国际化] + C[common] --> F[theme 核心逻辑
(包括内置主题模板和颜色算法等实现)] + C[common] --> G[utils 工具函数] + + classDef themeFill fill:#e6f7ff,stroke:#1890ff,stroke-width:2px; + class F themeFill +``` + +`*.min.css` 文件来自 `tdesign-vue` 且经过二次压缩 + +- 额外添加了 `:host` 选择器,确保 Web Components 打包后样式正常 +- 移除 `td-brand-color-x` 系列颜色 Token,使主题生成器能与外部主题色直接同步 + +## 🧚 交互逻辑 + +### 数据持久化 + +1. `custom-theme-options`: + +| Key | 类型 | 示例 | +| ----------------------------- | -------------------------- | --------------------- | +| `theme` | 主题 | `TCloud` | +| `color` | 主题色 | `#45C58B` | +| `gray/success/warning/danger` | 功能色的品牌色 | `#2ba471` | +| `font/radius/shadow` | 预设步骤中的位置 | `1` | +| `line-height` | 固定或递增模式及其参数 | `plus_9` / `time_1.5` | +| `recommend` | 智能推荐 | `true` | +| `neutral` | 关联主题色 | `true` | + +2. `custom-theme-tokens`:各种 `--td-*` token,主要来自用户手动修改单个变量的操作 + +### 执行流程 + +1. **初始化**:根据本地存储的 `theme`(或默认主题),进行 CSS 模板挂载 + +2. **配置恢复**:读取本地存储的 `tokens`,并还原用户的自定义修改 + +3. **面板渲染**:各个 Panel 根据 `options` 确定初始化数值 + +## 😈 开发规范 + +1. **常量管理**:使用 `UPPER_CASE` 命名方式,独立存放在 JS 文件中,与 Vue 逻辑分离 + +2. **全局状态管理**:避免通过 props 层层传递共享变量,优先使用 `inject`、`mixin` 或 `store` 等 + +3. **动态编码管理**:避免硬编码数值,特别是 Token 相关的映射,优先从 CSS 中获取配置 diff --git a/packages/theme-generator/README-zh_CN.md b/packages/theme-generator/README-zh_CN.md index ce668b7d..d5f5fd79 100644 --- a/packages/theme-generator/README-zh_CN.md +++ b/packages/theme-generator/README-zh_CN.md @@ -1,16 +1,10 @@ -# tdesign-theme-generator +

TDesign Logo TDesign Theme Generator

-- TDesign 主题配置生成器挂件 支持任意框架使用。 +[English](./README.md) | 简体中文 -## 🏗️ Develop +TDesign 主题生成器挂件,专为组件库的文档站点量身打造,用于实时预览配色和样式的调整。 -- `npm run dev` - -- `npm run build:watch` 配合 npm link 进入站点热更新预览 - -- `npm run build` 构建 web component - -## 📦 Usage +## 🔨 基础使用 1. `npm i tdesign-theme-generator` @@ -27,3 +21,10 @@ generator.setAttribute('device', 'mobile'); document.body.appendChild(generator); ``` + +## 🏗️ 快速开发 + +```bash +npm link +npm run build:watch # 进入组件库站点热更新预览 +``` \ No newline at end of file diff --git a/packages/theme-generator/README.md b/packages/theme-generator/README.md index 0b23d39e..659d5d8e 100644 --- a/packages/theme-generator/README.md +++ b/packages/theme-generator/README.md @@ -1,18 +1,10 @@ -## TDesign Theme Generator Plugin +

TDesign Logo TDesign Theme Generator

-- TDesign theme generator plugin, which is available in any web application. +English | [简体中文](./README_zh-CN.md) -### 🏗️ Develop +TDesign theme generator plugin, which is tailored for the component library documentation site. It is used for real-time preview of color and style adjustments. -- `npm run dev` - -- `npm run build:watch` execute `npm link` as well to preview in application project - -### ⚙️ Build - -- `npm run build` build web-component - -### 📦 Usage +## 🔨 Usage 1. `npm i tdesign-theme-generator` @@ -29,3 +21,10 @@ generator.setAttribute('device', 'mobile'); document.body.appendChild(generator); ``` + +## 🏗️ Develop + +```bash +npm link +npm run build:watch # open the component library site with hot-reload preview +``` diff --git a/packages/theme-generator/package.json b/packages/theme-generator/package.json index 845be81e..6ec58bc1 100644 --- a/packages/theme-generator/package.json +++ b/packages/theme-generator/package.json @@ -31,7 +31,7 @@ "cssbeautify": "^0.3.1", "lodash": "^4.17.21", "tdesign-icons-vue": "^0.0.8", - "tdesign-vue": "1.9.4-naruto", + "tdesign-vue": "1.13.1-naruto", "tvision-color": "^1.6.0", "vue": "2.7.14" }, diff --git a/packages/theme-generator/src/Generator.vue b/packages/theme-generator/src/Generator.vue index 976a31a5..930c074b 100644 --- a/packages/theme-generator/src/Generator.vue +++ b/packages/theme-generator/src/Generator.vue @@ -1,51 +1,34 @@ diff --git a/packages/theme-generator/src/color-panel/components/ColorContent/index.vue b/packages/theme-generator/src/color-panel/components/ColorContent/index.vue deleted file mode 100644 index 17e01f91..00000000 --- a/packages/theme-generator/src/color-panel/components/ColorContent/index.vue +++ /dev/null @@ -1,886 +0,0 @@ - - - diff --git a/packages/theme-generator/src/color-panel/index.vue b/packages/theme-generator/src/color-panel/index.vue new file mode 100644 index 00000000..cf6631a5 --- /dev/null +++ b/packages/theme-generator/src/color-panel/index.vue @@ -0,0 +1,769 @@ + + + + + diff --git a/packages/theme-generator/src/color-panel/assets/AgapanthusPurple b/packages/theme-generator/src/color-panel/svg/AgapanthusPurple similarity index 100% rename from packages/theme-generator/src/color-panel/assets/AgapanthusPurple rename to packages/theme-generator/src/color-panel/svg/AgapanthusPurple diff --git a/packages/theme-generator/src/color-panel/assets/DavidiaGreen b/packages/theme-generator/src/color-panel/svg/DavidiaGreen similarity index 100% rename from packages/theme-generator/src/color-panel/assets/DavidiaGreen rename to packages/theme-generator/src/color-panel/svg/DavidiaGreen diff --git a/packages/theme-generator/src/color-panel/assets/GovRed b/packages/theme-generator/src/color-panel/svg/GovRed similarity index 100% rename from packages/theme-generator/src/color-panel/assets/GovRed rename to packages/theme-generator/src/color-panel/svg/GovRed diff --git a/packages/theme-generator/src/color-panel/assets/HyacinthBlue b/packages/theme-generator/src/color-panel/svg/HyacinthBlue similarity index 100% rename from packages/theme-generator/src/color-panel/assets/HyacinthBlue rename to packages/theme-generator/src/color-panel/svg/HyacinthBlue diff --git a/packages/theme-generator/src/color-panel/assets/MarigoldOrange b/packages/theme-generator/src/color-panel/svg/MarigoldOrange similarity index 100% rename from packages/theme-generator/src/color-panel/assets/MarigoldOrange rename to packages/theme-generator/src/color-panel/svg/MarigoldOrange diff --git a/packages/theme-generator/src/color-panel/assets/MultifloraRed b/packages/theme-generator/src/color-panel/svg/MultifloraRed similarity index 100% rename from packages/theme-generator/src/color-panel/assets/MultifloraRed rename to packages/theme-generator/src/color-panel/svg/MultifloraRed diff --git a/packages/theme-generator/src/color-panel/assets/PetelotiiYellow b/packages/theme-generator/src/color-panel/svg/PetelotiiYellow similarity index 100% rename from packages/theme-generator/src/color-panel/assets/PetelotiiYellow rename to packages/theme-generator/src/color-panel/svg/PetelotiiYellow diff --git a/packages/theme-generator/src/color-panel/assets/PlumbagoBlue b/packages/theme-generator/src/color-panel/svg/PlumbagoBlue similarity index 100% rename from packages/theme-generator/src/color-panel/assets/PlumbagoBlue rename to packages/theme-generator/src/color-panel/svg/PlumbagoBlue diff --git a/packages/theme-generator/src/color-panel/assets/TCloudBlack b/packages/theme-generator/src/color-panel/svg/TCloudBlack similarity index 100% rename from packages/theme-generator/src/color-panel/assets/TCloudBlack rename to packages/theme-generator/src/color-panel/svg/TCloudBlack diff --git a/packages/theme-generator/src/color-panel/assets/TaxusRed b/packages/theme-generator/src/color-panel/svg/TaxusRed similarity index 100% rename from packages/theme-generator/src/color-panel/assets/TaxusRed rename to packages/theme-generator/src/color-panel/svg/TaxusRed diff --git a/packages/theme-generator/src/color-panel/assets/TencentBlue b/packages/theme-generator/src/color-panel/svg/TencentBlue similarity index 100% rename from packages/theme-generator/src/color-panel/assets/TencentBlue rename to packages/theme-generator/src/color-panel/svg/TencentBlue diff --git a/packages/theme-generator/src/color-panel/assets/TourismPurple b/packages/theme-generator/src/color-panel/svg/TourismPurple similarity index 100% rename from packages/theme-generator/src/color-panel/assets/TourismPurple rename to packages/theme-generator/src/color-panel/svg/TourismPurple diff --git a/packages/theme-generator/src/color-panel/assets/ViridifloraGreen b/packages/theme-generator/src/color-panel/svg/ViridifloraGreen similarity index 100% rename from packages/theme-generator/src/color-panel/assets/ViridifloraGreen rename to packages/theme-generator/src/color-panel/svg/ViridifloraGreen diff --git a/packages/theme-generator/src/color-panel/assets/WeChatGreen b/packages/theme-generator/src/color-panel/svg/WeChatGreen similarity index 100% rename from packages/theme-generator/src/color-panel/assets/WeChatGreen rename to packages/theme-generator/src/color-panel/svg/WeChatGreen diff --git a/packages/theme-generator/src/color-panel/assets/YulaniaPink b/packages/theme-generator/src/color-panel/svg/YulaniaPink similarity index 100% rename from packages/theme-generator/src/color-panel/assets/YulaniaPink rename to packages/theme-generator/src/color-panel/svg/YulaniaPink diff --git a/packages/theme-generator/src/color-panel/utils/const.js b/packages/theme-generator/src/color-panel/utils/const.js deleted file mode 100644 index c3fbda7e..00000000 --- a/packages/theme-generator/src/color-panel/utils/const.js +++ /dev/null @@ -1,379 +0,0 @@ -import AgapanthusPurple from '!raw-loader!../assets/AgapanthusPurple'; // 百子莲紫 -import DavidiaGreen from '!raw-loader!../assets/DavidiaGreen'; // 珙桐绿 -import GovRed from '!raw-loader!../assets/GovRed'; // 政务红 -import HyacinthBlue from '!raw-loader!../assets/HyacinthBlue'; //风信子蓝 -import MarigoldOrange from '!raw-loader!../assets/MarigoldOrange'; // 万寿菊橙 -import MultifloraRed from '!raw-loader!../assets/MultifloraRed'; // 蔷薇红 -import PetelotiiYellow from '!raw-loader!../assets/PetelotiiYellow'; //金茶花黄 -import PlumbagoBlue from '!raw-loader!../assets/PlumbagoBlue'; // 花丹蓝 -import TaxusRed from '!raw-loader!../assets/TaxusRed'; // 豆衫红 -import TCloudBlack from '!raw-loader!../assets/TCloudBlack'; // 腾云黑 -import TencentBlue from '!raw-loader!../assets/TencentBlue'; //腾讯蓝 -import TourismPurple from '!raw-loader!../assets/TourismPurple'; // 文旅紫 -import ViridifloraGreen from '!raw-loader!../assets/ViridifloraGreen'; // 鸢尾绿 -import WeChatGreen from '!raw-loader!../assets/WeChatGreen'; //微信绿 -import YulaniaPink from '!raw-loader!../assets/YulaniaPink'; - -export const DEFAULT_COLOR = [ - { - name: '腾讯蓝', - subtitle: TencentBlue, - value: '#0052D9', - }, - { - name: '风信子蓝', - subtitle: HyacinthBlue, - value: '#0894FA', - }, - { - name: '金花茶黄', - subtitle: PetelotiiYellow, - value: '#F3B814', - }, -]; - -export const RECOMMEND_COLOR = [ - { - name: '珙桐绿', - subtitle: DavidiaGreen, - value: '#45C58B', - }, - { - name: '鸢尾绿', - subtitle: ViridifloraGreen, - value: '#0ED6CA', - }, - { - name: '花丹蓝', - subtitle: PlumbagoBlue, - value: '#53B1FD', - }, - { - name: '百子莲紫', - subtitle: AgapanthusPurple, - value: '#7A5AF8', - }, - { - name: '', - subtitle: YulaniaPink, - value: '#EF45B3', - }, - { - name: '豆衫红', - subtitle: TaxusRed, - value: '#F54343', - }, - - { - name: '蔷薇红', - subtitle: MultifloraRed, - value: '#FF5479', - }, - { - name: '万寿菊橙', - subtitle: MarigoldOrange, - value: '#FD853A', - }, -]; - -export const SCENE_COLOR = [ - { - name: '微信绿', - subtitle: WeChatGreen, - value: '#07C160', - }, - { - name: '腾云黑', - subtitle: TCloudBlack, - value: '#262626', - }, - { - name: '文旅紫', - subtitle: TourismPurple, - value: '#623BFF', - }, - { - name: '政务红', - subtitle: GovRed, - value: '#EE1C25', - }, -]; - -export const BRAND_COLOR_MAP = [ - { name: '--td-brand-color-light', type: 'light', idx: 0 }, - { name: '--td-brand-color-focus', type: 'focus', idx: 1 }, - { name: '--td-brand-color-disabled', type: 'disabled', idx: 2 }, -]; - -export const ERROR_COLOR_MAP = [ - { name: '--td-error-color-light', type: 'light', idx: 0 }, - { name: '--td-error-color-focus', type: 'focus', idx: 1 }, - { name: '--td-error-color-disabled', type: 'disabled', idx: 2 }, - { name: '--td-error-color-hover', type: 'hover', idx: 4 }, - { name: '--td-error-color', type: 'main', idx: 5 }, - { name: '--td-error-color-active', type: 'active', idx: 6 }, -]; - -export const WARNING_COLOR_MAP = [ - { name: '--td-warning-color-light', type: 'light', idx: 0 }, - { name: '--td-warning-color-focus', type: 'focus', idx: 1 }, - { name: '--td-warning-color-disabled', type: 'disabled', idx: 2 }, - { name: '--td-warning-color-hover', type: 'hover', idx: 3 }, - { name: '--td-warning-color', type: 'main', idx: 4 }, - { name: '--td-warning-color-active', type: 'active', idx: 5 }, -]; - -export const SUCCESS_COLOR_MAP = [ - { name: '--td-success-color-light', type: 'light', idx: 0 }, - { name: '--td-success-color-focus', type: 'focus', idx: 1 }, - { name: '--td-success-color-disabled', type: 'disabled', idx: 2 }, - { name: '--td-success-color-hover', type: 'hover', idx: 3 }, - { name: '--td-success-color', type: 'main', idx: 4 }, - { name: '--td-success-color-active', type: 'active', idx: 5 }, -]; - -export const GRAY_COLOR_MAP = [ - { name: '--td-bg-color-container-hover', type: '', idx: 0 }, - { name: '--td-bg-color-secondarycontainer', type: '', idx: 0 }, - { name: '--td-bg-color-secondarycontainer-hover', type: '', idx: 1 }, - { name: '--td-bg-color-component-disabled', type: '', idx: 1 }, - { name: '--td-bg-color-page', type: '', idx: 1 }, - { name: '--td-bg-color-container-active', type: '', idx: 2 }, - { name: '--td-bg-color-component', type: '', idx: 2 }, - { name: '--td-component-stroke', type: '', idx: 2 }, - { name: '--td-bg-color-secondarycontainer-active', type: 'main', idx: 3 }, - { name: '--td-bg-color-component-hover', type: '', idx: 3 }, - { name: '--td-component-border', type: '', idx: 3 }, - { name: '--td-bg-color-component-active', type: '', idx: 5 }, -]; - -export const FONT_COLOR_MAP = [ - { name: '--td-text-color-primary', from: '--td-font-gray-1' }, - { name: '--td-text-color-secondary', from: '--td-font-gray-2' }, - { name: '--td-text-color-placeholder', from: '--td-font-gray-3' }, - { name: '--td-text-color-disabled', from: '--td-font-gray-4' }, - { name: '--td-text-color-anti', value: '#fff' }, - { name: '--td-text-color-brand', from: '--td-brand-color' }, - { name: '--td-text-color-link', from: '--td-brand-color' }, -]; - -// 亮色模式下的功能色、中性色等 -const LIGHT_WARNING_COLOR = `--td-warning-color-1: #fef3e6; ---td-warning-color-2: #f9e0c7; ---td-warning-color-3: #f7c797; ---td-warning-color-4: #f2995f; ---td-warning-color-5: #ed7b2f; ---td-warning-color-6: #d35a21; ---td-warning-color-7: #ba431b; ---td-warning-color-8: #9e3610; ---td-warning-color-9: #842b0b; ---td-warning-color-10: #5a1907; ---td-warning-color: var(--td-warning-color-5); ---td-warning-color-hover: var(--td-warning-color-4); ---td-warning-color-focus: var(--td-warning-color-2); ---td-warning-color-active: var(--td-warning-color-6); ---td-warning-color-disabled: var(--td-warning-color-3); ---td-warning-color-light: var(--td-warning-color-1);`; - -const LIGHT_ERROR_COLOR = `--td-error-color-1: #fdecee; ---td-error-color-2: #f9d7d9; ---td-error-color-3: #f8b9be; ---td-error-color-4: #f78d94; ---td-error-color-5: #f36d78; ---td-error-color-6: #e34d59; ---td-error-color-7: #c9353f; ---td-error-color-8: #b11f26; ---td-error-color-9: #951114; ---td-error-color-10: #680506; ---td-error-color: var(--td-error-color-6); ---td-error-color-hover: var(--td-error-color-5); ---td-error-color-focus: var(--td-error-color-2); ---td-error-color-active: var(--td-error-color-7); ---td-error-color-disabled: var(--td-error-color-3); ---td-error-color-light: var(--td-error-color-1);`; - -const LIGHT_SUCCESS_COLOR = `--td-success-color-1: #e8f8f2; ---td-success-color-2: #bcebdc; ---td-success-color-3: #85dbbe; ---td-success-color-4: #48c79c; ---td-success-color-5: #00a870; ---td-success-color-6: #078d5c; ---td-success-color-7: #067945; ---td-success-color-8: #056334; ---td-success-color-9: #044f2a; ---td-success-color-10: #033017; ---td-success-color: var(--td-success-color-5); ---td-success-color-hover: var(--td-success-color-4); ---td-success-color-focus: var(--td-success-color-2); ---td-success-color-active: var(--td-success-color-6); ---td-success-color-disabled: var(--td-success-color-3); ---td-success-color-light: var(--td-success-color-1);`; - -const LIGHT_GRAY_COLOR = `--td-gray-color-1: #f3f3f3; ---td-gray-color-2: #eee; ---td-gray-color-3: #e7e7e7; ---td-gray-color-4: #dcdcdc; ---td-gray-color-5: #c5c5c5; ---td-gray-color-6: #a6a6a6; ---td-gray-color-7: #8b8b8b; ---td-gray-color-8: #777; ---td-gray-color-9: #5e5e5e; ---td-gray-color-10: #4b4b4b; ---td-gray-color-11: #383838; ---td-gray-color-12: #2c2c2c; ---td-gray-color-13: #242424; ---td-gray-color-14: #181818; ---td-bg-color-container: #fff; ---td-bg-color-container-select: #fff; ---td-bg-color-page: var(--td-gray-color-2); ---td-bg-color-container-hover: var(--td-gray-color-1); ---td-bg-color-container-active: var(--td-gray-color-3); ---td-bg-color-secondarycontainer: var(--td-gray-color-1); ---td-bg-color-secondarycontainer-hover: var(--td-gray-color-2); ---td-bg-color-secondarycontainer-active: var(--td-gray-color-4); ---td-bg-color-component: var(--td-gray-color-3); ---td-bg-color-component-hover: var(--td-gray-color-4); ---td-bg-color-component-active: var(--td-gray-color-6); ---td-bg-color-component-disabled: var(--td-gray-color-2); ---td-component-stroke: var(--td-gray-color-3); ---td-component-border: var(--td-gray-color-4);`; - -const LIGHT_FONT_COLOR = `--td-font-white-1: #ffffff; ---td-font-white-2: rgba(255, 255, 255, 0.55); ---td-font-white-3: rgba(255, 255, 255, 0.35); ---td-font-white-4: rgba(255, 255, 255, 0.22); ---td-font-gray-1: rgba(0, 0, 0, 0.9); ---td-font-gray-2: rgba(0, 0, 0, 0.6); ---td-font-gray-3: rgba(0, 0, 0, 0.4); ---td-font-gray-4: rgba(0, 0, 0, 0.26); ---td-text-color-primary: var(--td-font-gray-1); ---td-text-color-secondary: var(--td-font-gray-2); ---td-text-color-placeholder: var(--td-font-gray-3); ---td-text-color-disabled: var(--td-font-gray-4); ---td-text-color-anti: #fff; ---td-text-color-brand: var(--td-brand-color); ---td-text-color-link: var(--td-brand-color);`; - -const LIGHT_SCROLLBAR_COLOR = `--td-brand-color-light-hover: var(--td-brand-color-2); ---td-warning-color-light-hover: var(--td-warning-color-2); ---td-error-color-light-hover: var(--td-error-color-2); ---td-success-color-light-hover: var(--td-success-color-2); ---td-bg-color-secondarycomponent: var(--td-gray-color-4); ---td-bg-color-secondarycomponent-hover: var(--td-gray-color-5); ---td-bg-color-secondarycomponent-active: var(--td-gray-color-6); ---td-table-shadow-color: rgba(0, 0, 0, 8%); ---td-scrollbar-color: rgba(0, 0, 0, 10%); ---td-scrollbar-hover-color: rgba(0, 0, 0, 30%); ---td-scroll-track-color: #fff; ---td-bg-color-specialcomponent: #fff; ---td-border-level-1-color: var(--td-gray-color-3); ---td-border-level-2-color: var(--td-gray-color-4); ---td-shadow-1: 0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%); ---td-shadow-2: 0 3px 14px 2px rgba(0, 0, 0, 5%), 0 8px 10px 1px rgba(0, 0, 0, 6%), 0 5px 5px -3px rgba(0, 0, 0, 10%); ---td-shadow-3: 0 6px 30px 5px rgba(0, 0, 0, 5%), 0 16px 24px 2px rgba(0, 0, 0, 4%), 0 8px 10px -5px rgba(0, 0, 0, 8%); ---td-shadow-inset-top: inset 0 0.5px 0 #dcdcdc; ---td-shadow-inset-right: inset 0.5px 0 0 #dcdcdc; ---td-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc; ---td-shadow-inset-left: inset -0.5px 0 0 #dcdcdc; ---td-mask-active: rgba(0, 0, 0, 0.6); ---td-mask-disabled: rgba(255, 255, 255, 0.6);`; - -export const LIGHT_FUNCTION_COLOR = `${LIGHT_WARNING_COLOR} ${LIGHT_ERROR_COLOR} ${LIGHT_SUCCESS_COLOR} ${LIGHT_GRAY_COLOR} ${LIGHT_FONT_COLOR} ${LIGHT_SCROLLBAR_COLOR}`; - -// 暗色模式下的功能色、中性色等 -const DARK_ERROR_COLOR = `--td-error-color-1: #472324; ---td-error-color-2: #5e2a2d; ---td-error-color-3: #703439; ---td-error-color-4: #83383e; ---td-error-color-5: #a03f46; ---td-error-color-6: #c64751; ---td-error-color-7: #de6670; ---td-error-color-8: #ec888e; ---td-error-color-9: #edb1b6; ---td-error-color-10: #eeced0;`; - -const DARK_SUCCESS_COLOR = `--td-success-color-1: #193a2a; ---td-success-color-2: #1a4230; ---td-success-color-3: #17533d; ---td-success-color-4: #0d7a55; ---td-success-color-5: #059465; ---td-success-color-6: #43af8a; ---td-success-color-7: #46bf96; ---td-success-color-8: #80d2b6; ---td-success-color-9: #b4e1d3; ---td-success-color-10: #deede8;`; - -const DARK_WARNING_COLOR = `--td-warning-color-1: #4f2a1d; ---td-warning-color-2: #582f21; ---td-warning-color-3: #733c23; ---td-warning-color-4: #a75d2b; ---td-warning-color-5: #cf6e2d; ---td-warning-color-6: #dc7633; ---td-warning-color-7: #e8935c; ---td-warning-color-8: #ecbf91; ---td-warning-color-9: #eed7bf; ---td-warning-color-10: #f3e9dc;`; - -const DARK_GRAY_COLOR = `--td-gray-color-1: #f3f3f3; ---td-gray-color-2: #eee; ---td-gray-color-3: #e7e7e7; ---td-gray-color-4: #dcdcdc; ---td-gray-color-5: #c5c5c5; ---td-gray-color-6: #a6a6a6; ---td-gray-color-7: #8b8b8b; ---td-gray-color-8: #777; ---td-gray-color-9: #5e5e5e; ---td-gray-color-10: #4b4b4b; ---td-gray-color-11: #383838; ---td-gray-color-12: #2c2c2c; ---td-gray-color-13: #242424; ---td-gray-color-14: #181818; ---td-bg-color-page: var(--td-gray-color-14); ---td-bg-color-container: var(--td-gray-color-13); ---td-bg-color-container-hover: var(--td-gray-color-12); ---td-bg-color-container-active: var(--td-gray-color-10); ---td-bg-color-container-select: var(--td-gray-color-9); ---td-bg-color-secondarycontainer: var(--td-gray-color-12); ---td-bg-color-secondarycontainer-hover: var(--td-gray-color-11); ---td-bg-color-secondarycontainer-active: var(--td-gray-color-9); ---td-bg-color-component: var(--td-gray-color-11); ---td-bg-color-component-hover: var(--td-gray-color-10); ---td-bg-color-component-active: var(--td-gray-color-9); ---td-bg-color-component-disabled: var(--td-gray-color-12); ---td-component-stroke: var(--td-gray-color-11); ---td-component-border: var(--td-gray-color-9);`; - -const DARK_FONT_COLOR = `--td-font-white-1: rgba(255, 255, 255, 0.9); ---td-font-white-2: rgba(255, 255, 255, 0.55); ---td-font-white-3: rgba(255, 255, 255, 0.35); ---td-font-white-4: rgba(255, 255, 255, 0.22); ---td-font-gray-1: rgba(0, 0, 0, 0.9); ---td-font-gray-2: rgba(0, 0, 0, 0.6); ---td-font-gray-3: rgba(0, 0, 0, 0.4); ---td-font-gray-4: rgba(0, 0, 0, 0.26); ---td-text-color-primary: var(--td-font-white-1); ---td-text-color-secondary: var(--td-font-white-2); ---td-text-color-placeholder: var(--td-font-white-3); ---td-text-color-disabled: var(--td-font-white-4); ---td-text-color-anti: #fff; ---td-text-color-brand: var(--td-brand-color); ---td-text-color-link: var(--td-brand-color);`; - -const DARK_SCROLLBAR_COLOR = `--td-shadow-1: 0 4px 6px rgba(0, 0, 0, 0.06), 0 1px 10px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12); - --td-shadow-2: 0 8px 10px rgba(0, 0, 0, 0.12), 0 3px 14px rgba(0, 0, 0, 0.10), 0 5px 5px rgba(0, 0, 0, 0.16); - --td-shadow-3: 0 16px 24px rgba(0, 0, 0, 0.14), 0 6px 30px rgba(0, 0, 0, 0.12), 0 8px 10px rgba(0, 0, 0, 0.20); - --td-shadow-inset-top: inset 0 0.5px 0 #5e5e5e; - --td-shadow-inset-right: inset 0.5px 0 0 #5e5e5e; - --td-shadow-inset-bottom: inset 0 -0.5px 0 #5e5e5e; - --td-shadow-inset-left: inset -0.5px 0 0 #5e5e5e; - --td-table-shadow-color: rgba(0, 0, 0, 55%); - --td-scrollbar-color: rgba(255, 255, 255, 10%); - --td-scrollbar-hover-color: rgba(255, 255, 255, 30%); - --td-scroll-track-color: #333; - --td-bg-color-specialcomponent: transparent; - --td-border-level-1-color: var(--td-gray-color-11); - --td-border-level-2-color: var(--td-gray-color-9); - --td-mask-active: rgba(0, 0, 0, 0.4); - --td-mask-disabled: rgba(0, 0, 0, 0.6);`; - -export const DARK_FUNCTION_COLOR = `${DARK_WARNING_COLOR} ${DARK_ERROR_COLOR} ${DARK_SUCCESS_COLOR} ${DARK_GRAY_COLOR} ${DARK_FONT_COLOR} ${DARK_SCROLLBAR_COLOR}`; diff --git a/packages/theme-generator/src/common/ColorPicker/Alpha.vue b/packages/theme-generator/src/common/ColorPicker/Alpha.vue deleted file mode 100644 index e65671a6..00000000 --- a/packages/theme-generator/src/common/ColorPicker/Alpha.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/theme-generator/src/common/ColorPicker/Saturation.vue b/packages/theme-generator/src/common/ColorPicker/Saturation.vue deleted file mode 100644 index 76bbe535..00000000 --- a/packages/theme-generator/src/common/ColorPicker/Saturation.vue +++ /dev/null @@ -1,227 +0,0 @@ - - - diff --git a/packages/theme-generator/src/common/ColorPicker/Slider.vue b/packages/theme-generator/src/common/ColorPicker/Slider.vue deleted file mode 100644 index 540fe9df..00000000 --- a/packages/theme-generator/src/common/ColorPicker/Slider.vue +++ /dev/null @@ -1,78 +0,0 @@ - - - diff --git a/packages/theme-generator/src/common/ColorPicker/index.vue b/packages/theme-generator/src/common/ColorPicker/index.vue deleted file mode 100644 index 088bee95..00000000 --- a/packages/theme-generator/src/common/ColorPicker/index.vue +++ /dev/null @@ -1,139 +0,0 @@ - - - diff --git a/packages/theme-generator/src/common/Themes/built-in/css/mobile/common/radius.css b/packages/theme-generator/src/common/Themes/built-in/css/mobile/common/radius.css deleted file mode 100644 index c6d85725..00000000 --- a/packages/theme-generator/src/common/Themes/built-in/css/mobile/common/radius.css +++ /dev/null @@ -1,8 +0,0 @@ -:root { - --td-radius-small: 3px; - --td-radius-default: 6px; - --td-radius-large: 9px; - --td-radius-extraLarge: 12px; - --td-radius-round: 999px; - --td-radius-circle: 50%; -} \ No newline at end of file diff --git a/packages/theme-generator/src/common/Themes/built-in/css/web/common/radius.css b/packages/theme-generator/src/common/Themes/built-in/css/web/common/radius.css deleted file mode 100644 index 205a8312..00000000 --- a/packages/theme-generator/src/common/Themes/built-in/css/web/common/radius.css +++ /dev/null @@ -1,9 +0,0 @@ -:root { - --td-radius-small: 2px; - --td-radius-default: 3px; - --td-radius-medium: 6px; - --td-radius-large: 9px; - --td-radius-extraLarge: 12px; - --td-radius-round: 999px; - --td-radius-circle: 50%; -} \ No newline at end of file diff --git a/packages/theme-generator/src/common/Themes/built-in/css/web/common/size.css b/packages/theme-generator/src/common/Themes/built-in/css/web/common/size.css deleted file mode 100644 index 7adb5df1..00000000 --- a/packages/theme-generator/src/common/Themes/built-in/css/web/common/size.css +++ /dev/null @@ -1,57 +0,0 @@ -:root { - --td-size-1: 2px; - --td-size-2: 4px; - --td-size-3: 6px; - --td-size-4: 8px; - --td-size-5: 12px; - --td-size-6: 16px; - --td-size-7: 20px; - --td-size-8: 24px; - --td-size-9: 28px; - --td-size-10: 32px; - --td-size-11: 36px; - --td-size-12: 40px; - --td-size-13: 48px; - --td-size-14: 56px; - --td-size-15: 64px; - --td-size-16: 72px; - --td-comp-size-xxxs: var(--td-size-6); - --td-comp-size-xxs: var(--td-size-7); - --td-comp-size-xs: var(--td-size-8); - --td-comp-size-s: var(--td-size-9); - --td-comp-size-m: var(--td-size-10); - --td-comp-size-l: var(--td-size-11); - --td-comp-size-xl: var(--td-size-12); - --td-comp-size-xxl: var(--td-size-13); - --td-comp-size-xxxl: var(--td-size-14); - --td-comp-size-xxxxl: var(--td-size-15); - --td-comp-size-xxxxxl: var(--td-size-16); - --td-pop-padding-s: var(--td-size-2); - --td-pop-padding-m: var(--td-size-3); - --td-pop-padding-l: var(--td-size-4); - --td-pop-padding-xl: var(--td-size-5); - --td-pop-padding-xxl: var(--td-size-6); - --td-comp-paddingLR-xxs: var(--td-size-1); - --td-comp-paddingLR-xs: var(--td-size-2); - --td-comp-paddingLR-s: var(--td-size-4); - --td-comp-paddingLR-m: var(--td-size-5); - --td-comp-paddingLR-l: var(--td-size-6); - --td-comp-paddingLR-xl: var(--td-size-8); - --td-comp-paddingLR-xxl: var(--td-size-10); - --td-comp-paddingTB-xxs: var(--td-size-1); - --td-comp-paddingTB-xs: var(--td-size-2); - --td-comp-paddingTB-s: var(--td-size-4); - --td-comp-paddingTB-m: var(--td-size-5); - --td-comp-paddingTB-l: var(--td-size-6); - --td-comp-paddingTB-xl: var(--td-size-8); - --td-comp-paddingTB-xxl: var(--td-size-10); - --td-comp-margin-xxs: var(--td-size-1); - --td-comp-margin-xs: var(--td-size-2); - --td-comp-margin-s: var(--td-size-4); - --td-comp-margin-m: var(--td-size-5); - --td-comp-margin-l: var(--td-size-6); - --td-comp-margin-xl: var(--td-size-7); - --td-comp-margin-xxl: var(--td-size-8); - --td-comp-margin-xxxl: var(--td-size-10); - --td-comp-margin-xxxxl: var(--td-size-12); -} \ No newline at end of file diff --git a/packages/theme-generator/src/common/Themes/index.js b/packages/theme-generator/src/common/Themes/index.js deleted file mode 100644 index 37ee2b24..00000000 --- a/packages/theme-generator/src/common/Themes/index.js +++ /dev/null @@ -1,341 +0,0 @@ -export * from './iframe'; -export * from './preset'; -export * from './token'; - -import cssbeautify from 'cssbeautify'; -import { Color } from 'tvision-color'; - -import { appendStyleSheet, clearLocalItem, downloadFile, extractRootContent, removeCssProperties } from '../utils'; -import { BUILT_IN_THEMES, DEFAULT_THEME, RECOMMEND_THEMES } from './preset'; -import { DARK_FUNCTION_COLOR, LIGHT_FUNCTION_COLOR, MOBILE_MISSING_TOKENS } from './token'; - -/* stylesheet 的 ID */ -export const CUSTOM_THEME_ID = 'custom-theme'; -export const CUSTOM_DARK_ID = `${CUSTOM_THEME_ID}-dark`; -export const CUSTOM_EXTRA_ID = `${CUSTOM_THEME_ID}-extra`; -export const CUSTOM_COMMON_ID_PREFIX = `${CUSTOM_THEME_ID}-common`; -/* localStorage 的 key */ -export const CUSTOM_OPTIONS_ID = `${CUSTOM_THEME_ID}-options`; -export const CUSTOM_TOKEN_ID = `${CUSTOM_THEME_ID}-tokens`; - -export const isMiniProgram = (device) => device === 'mini-program'; -export const isMobile = (device) => device === 'mobile' || isMiniProgram(device); - -export function normalizeDeviceType(device) { - return isMobile(device) ? 'mobile' : 'web'; -} - -export function getBuiltInThemes(device = 'web', hex = undefined) { - const themeCopy = JSON.parse(JSON.stringify(RECOMMEND_THEMES)); - - const filtered = themeCopy - .map((group) => { - group.options = group.options.filter((theme) => { - const deviceType = normalizeDeviceType(device); - const availableCss = BUILT_IN_THEMES[deviceType]?.[theme.enName]; - - /* 这里的 && 不能简写为 hex.?,有时初始化不需要传入 hex,但是需要继续执行 - 可选链生成 `undefined !== theme.value.toLocaleLowerCase()` 为 true,会直接返回 */ - if (hex && hex.toLocaleLowerCase() !== theme.value.toLocaleLowerCase()) return false; - - if (availableCss) { - theme.css = { - light: availableCss.light, - dark: availableCss.dark, - ...(availableCss.extra && { extra: availableCss.extra }), - }; - return true; - } - return false; - }); - - return group; - }) - .filter((group) => group.options.length > 0); - - return filtered; -} - -export function generateNewTheme(hex, remainInput = true, device = 'web') { - updateLocalOption('color', hex, hex !== DEFAULT_THEME.value); - - const styleSheet = appendStyleSheet(CUSTOM_THEME_ID); - const darkStyleSheet = appendStyleSheet(CUSTOM_DARK_ID); - - const { brandColorIdx, colorPalette, styleSheetString } = generateTokenList(hex, false, 10, remainInput); - - const builtInTheme = getBuiltInThemes(device, hex); - const hasBuiltInTheme = builtInTheme.length > 0; - - generateCommonTheme(device, hasBuiltInTheme); - if (hasBuiltInTheme) { - // 内置主题 - const theme = builtInTheme[0].options[0]; // 条件筛选后只有一个 - const { light, dark, extra } = theme.css; - - styleSheet.textContent = light; - darkStyleSheet.textContent = dark; - - if (extra) { - const extraStyleSheet = appendStyleSheet(CUSTOM_EXTRA_ID); - extraStyleSheet.textContent = extra; - // 有自己一套特有样式的,不应用 Common Token - document.querySelectorAll(`[id^="${CUSTOM_COMMON_ID_PREFIX}-"]`).forEach((sheet) => { - sheet.remove(); - }); - } else { - document.getElementById(CUSTOM_EXTRA_ID)?.remove(); - } - } else { - document.getElementById(CUSTOM_EXTRA_ID)?.remove(); - - // 动态生成 - const darkCssTokenString = generateTokenList(hex, true).styleSheetString; - styleSheet.textContent = styleSheetString; - darkStyleSheet.textContent = darkCssTokenString; - } - - document.documentElement.setAttribute('theme-color', CUSTOM_THEME_ID); - document.documentElement.style.setProperty('--brand-main', hex); - return { brandColorIdx, colorPalette }; -} - -export const generateCommonTheme = (() => { - let previousDevice = 'web'; // 闭包保存 - - return function (device = 'web', forceRefresh = false) { - const deviceType = normalizeDeviceType(device); - const commonThemes = BUILT_IN_THEMES[deviceType]?.common; - if (!commonThemes) return; - - // device 变化时,清除之前的样式 - if (previousDevice !== deviceType) { - const existingStyles = Array.from(document.querySelectorAll(`[id^="${CUSTOM_COMMON_ID_PREFIX}-"]`)); - existingStyles.forEach((style) => { - style.parentNode.removeChild(style); - }); - } - - Object.entries(commonThemes).forEach(([key, theme]) => { - const commonId = `${CUSTOM_COMMON_ID_PREFIX}-${key}`; - if (document.getElementById(commonId) && !forceRefresh) return; // 不覆盖之前的内容 - const commonStyleSheet = appendStyleSheet(commonId); - commonStyleSheet.textContent = theme; - }); - - previousDevice = deviceType; - }; -})(); - -export function generateTokenList(hex, isDark = false, step = 10, remainInput = true) { - const lowCaseHex = hex.toLocaleLowerCase(); - const root = isDark ? `:root[theme-mode="dark"]` : `:root,:root[theme-mode="light"]`; - - let colorPalette; - let brandColorIdx; - - const [{ colors, primary }] = Color.getColorGradations({ - colors: [lowCaseHex], - step: step, - remainInput, - }); - - colorPalette = colors; - brandColorIdx = primary; - - if (lowCaseHex === DEFAULT_THEME.value.toLocaleLowerCase()) { - brandColorIdx = 8; - } - if (isDark) { - if (lowCaseHex === DEFAULT_THEME.value.toLocaleLowerCase()) { - colorPalette = [ - '#1b2f51', - '#173463', - '#143975', - '#103d88', - '#0d429a', - '#054bbe', - '#2667d4', - '#4582e6', - '#699ef5', - '#96bbf8', - ]; - brandColorIdx = 8; - } else { - colorPalette.reverse().map((color) => { - const [h, s, l] = Color.colorTransform(color, 'hex', 'hsl'); - return Color.colorTransform([h, Number(s) - 4, l], 'hsl', 'hex'); - }); - brandColorIdx = 5; - } - - colorPalette[0] = `${colorPalette[brandColorIdx]}20`; - } - - // TODO: 功能色、中性色未通过t-vision生成 先固定住 - const styleSheetString = `${root}{ - --td-brand-color-1: ${colorPalette[0]}; - --td-brand-color-2: ${colorPalette[1]}; - --td-brand-color-3: ${colorPalette[2]}; - --td-brand-color-4: ${colorPalette[3]}; - --td-brand-color-5: ${colorPalette[4]}; - --td-brand-color-6: ${colorPalette[5]}; - --td-brand-color-7: ${colorPalette[6]}; - --td-brand-color-8: ${colorPalette[7]}; - --td-brand-color-9: ${colorPalette[8]}; - --td-brand-color-10: ${colorPalette[9]}; - --td-brand-color-light: var(--td-brand-color-1); - --td-brand-color-focus: var(--td-brand-color-2); - --td-brand-color-disabled: var(--td-brand-color-3); - --td-brand-color-hover: var(--td-brand-color-${brandColorIdx > 0 ? brandColorIdx : brandColorIdx + 1}); - --td-brand-color: var(--td-brand-color-${brandColorIdx + 1}); - --td-brand-color-active:var(--td-brand-color-${brandColorIdx > 8 ? brandColorIdx + 1 : brandColorIdx + 2}); - ${isDark ? DARK_FUNCTION_COLOR : LIGHT_FUNCTION_COLOR} - }`; - - return { styleSheetString, brandColorIdx, colorPalette }; -} - -export function exportCustomTheme(device = 'web') { - const styleSheet = document.getElementById(CUSTOM_THEME_ID); - const darkStyleSheet = document.getElementById(CUSTOM_DARK_ID); - const extraStyleSheet = document.getElementById(CUSTOM_EXTRA_ID); - const commonStyleSheet = document.querySelectorAll(`[id^="${CUSTOM_COMMON_ID_PREFIX}-"]`); - - const cssString = extractRootContent(styleSheet?.innerText); - const darkCssString = extractRootContent(darkStyleSheet?.innerText); - const commonCssString = Array.from(commonStyleSheet) - .map((sheet) => extractRootContent(sheet.innerText)) - .join('\n'); - const extraCssString = extraStyleSheet?.innerText || ''; - - let finalCssString; - if (isMiniProgram(device)) { - finalCssString = ` - @media (prefers-color-scheme: light) { - page, .page { - ${cssString} - } - } - @media (prefers-color-scheme: dark) { - page, .page { - ${darkCssString} - } - } - ${commonCssString ? `page {\n${commonCssString}\n}` : ''} - ${extraCssString} - `; - } else { - finalCssString = ` - :root, :root[theme-mode="light"] { - ${cssString} - } - :root[theme-mode="dark"] { - ${darkCssString} - } - ${commonCssString ? `:root {\n${commonCssString}\n}` : ''} - ${extraCssString} - `; - } - - if (isMobile(device)) { - finalCssString = removeCssProperties(finalCssString, MOBILE_MISSING_TOKENS); - } - - const beautifyCssString = cssbeautify(finalCssString.trim()); - const blob = new Blob([beautifyCssString], { type: 'text' }); - const fileSuffix = isMiniProgram(device) ? 'wxss' : 'css'; - downloadFile(blob, `theme.${fileSuffix}`); -} - -export function modifyToken(tokenName, newVal, saveToLocal = true) { - // 获取所有可能包含 token 的样式表 - const styleSheets = document.querySelectorAll( - `#${CUSTOM_THEME_ID}, #${CUSTOM_DARK_ID}, #${CUSTOM_EXTRA_ID}, [id^="${CUSTOM_COMMON_ID_PREFIX}-"]`, - ); - - let tokenFound = false; - - styleSheets.forEach((styleSheet) => { - const reg = new RegExp(`${tokenName}:\\s*(.*?);`); - const match = styleSheet.innerText.match(reg); - - if (!match) return; - if (match[1] === newVal) { - tokenFound = true; - return; - } - - const currentVal = match[1]; - styleSheet.innerText = styleSheet.innerText.replace(`${tokenName}: ${currentVal}`, `${tokenName}: ${newVal}`); - tokenFound = true; - - if (saveToLocal) { - storeTokenToLocal(tokenName, newVal); - } else { - // 确保没有遗留的 Token - clearLocalItem(CUSTOM_TOKEN_ID, tokenName); - } - }); - - if (!tokenFound) { - console.warn(`CSS variable: ${tokenName} is not exist`); - } -} - -export function applyMainColorFromLocal(device) { - const options = localStorage.getItem(CUSTOM_OPTIONS_ID); - if (!options) return; - const typeObj = JSON.parse(options); - const hex = typeObj.color; - if (!hex) return; - generateNewTheme(hex, false, device); -} - -export function updateLocalOption(optionName, value, storeToLocal = true) { - if (storeToLocal) { - const options = localStorage.getItem(CUSTOM_OPTIONS_ID) || '{}'; - const optionObj = JSON.parse(options); - optionObj[optionName] = value; - localStorage.setItem(CUSTOM_OPTIONS_ID, JSON.stringify(optionObj)); - } else { - // 一般如果选择了默认选项,则清除掉之前的存储 - clearLocalItem(CUSTOM_OPTIONS_ID, optionName); - } -} - -export function getOptionFromLocal(optionName) { - const options = localStorage.getItem(CUSTOM_OPTIONS_ID); - if (!options) return; - const optionObj = JSON.parse(options); - return optionObj[optionName]; -} - -export function storeTokenToLocal(tokenName, newVal) { - const tokens = localStorage.getItem(CUSTOM_TOKEN_ID) || '{}'; - const tokenObj = JSON.parse(tokens); - tokenObj[tokenName] = newVal; - localStorage.setItem(CUSTOM_TOKEN_ID, JSON.stringify(tokenObj)); -} - -export function applyTokenFromLocal() { - const token = localStorage.getItem(CUSTOM_TOKEN_ID); - if (!token) return; - - const tokenObj = JSON.parse(token); - Object.entries(tokenObj).forEach(([key, value]) => { - modifyToken(key, value, false); - }); -} - -export function applyThemeFromLocal(device) { - // 先应用颜色 - applyMainColorFromLocal(device); - // 再应用 token -> 避免修改过的颜色被覆盖掉 - applyTokenFromLocal(); -} - -export function clearLocalTheme() { - localStorage.removeItem(CUSTOM_OPTIONS_ID); - localStorage.removeItem(CUSTOM_TOKEN_ID); -} diff --git a/packages/theme-generator/src/common/Themes/preset.js b/packages/theme-generator/src/common/Themes/preset.js deleted file mode 100644 index 1dc77b2a..00000000 --- a/packages/theme-generator/src/common/Themes/preset.js +++ /dev/null @@ -1,140 +0,0 @@ -import TDesignOriginal from '!raw-loader!./built-in/svg/TDesignOriginal'; -import TencentCloud from '!raw-loader!./built-in/svg/TencentCloud'; -// import TencentQuestionnaire from '!raw-loader!./built-in/svg/TencentQuestionnaire'; -// import GovService from '!raw-loader!./built-in/svg/GovService'; -// import SmartRetail from '!raw-loader!./built-in/svg/SmartRetail'; -// import WeChatPay from '!raw-loader!./built-in/svg/WeChatPay'; -// import CulturalTourism from '!raw-loader!./built-in/svg/CulturalTourism'; -// import TencentEducation from '!raw-loader!./built-in/svg/TencentEducation'; -// import TencentHealthcare from '!raw-loader!./built-in/svg/TencentHealthcare'; -// import TencentGames from '!raw-loader!./built-in/svg/TencentGames'; -// import TencentSafe from '!raw-loader!./built-in/svg/TencentSafe'; - -import WebTCloudDark from '!raw-loader!./built-in/css/web/TCloud/dark.css'; -import WebTCloudExtra from '!raw-loader!./built-in/css/web/TCloud/extra.css'; -import WebTCloudLight from '!raw-loader!./built-in/css/web/TCloud/light.css'; -import WebTDesignDark from '!raw-loader!./built-in/css/web/TDesign/dark.css'; -import WebTDesignLight from '!raw-loader!./built-in/css/web/TDesign/light.css'; -import WebFont from '!raw-loader!./built-in/css/web/common/font.css'; -import WebRadius from '!raw-loader!./built-in/css/web/common/radius.css'; -import WebSize from '!raw-loader!./built-in/css/web/common/size.css'; - -import MobileTDesignDark from '!raw-loader!./built-in/css/mobile/TDesign/dark.css'; -import MobileTDesignLight from '!raw-loader!./built-in/css/mobile/TDesign/light.css'; -import MobileFont from '!raw-loader!./built-in/css/mobile/common/font.css'; -import MobileRadius from '!raw-loader!./built-in/css/mobile/common/radius.css'; - -export const CUSTOM_THEME_TEXT = { - name: '自定义主题', - enName: 'Custom', - subtitleText: 'Custom Theme', -}; - -export const DEFAULT_THEME = { - name: '默认主题', - enName: 'TDesign', - subtitle: TDesignOriginal, - subtitleText: 'TDesign Original', - value: '#0052D9', -}; - -export const RECOMMEND_THEMES = [ - { - title: '官方推荐', - options: [ - DEFAULT_THEME, - { - name: '腾讯云', - enName: 'TCloud', - subtitle: TencentCloud, - subtitleText: 'Tencent Cloud', - value: '#006EFF', - }, - // { - // name: "微信支付", - // subtitle: WeChatPay, - // subtitleText:'WeChat Pay', - // value: "#07C160", - // }, - // { - // name: "腾讯问卷", - // subtitle: TencentQuestionnaire, - // subtitleText: 'Tencent Questionnaire', - // value: "#53B1FD", - // }, - // { - // name: "腾讯教育", - // subtitle: TencentEducation, - // value: "#0DB282", - // }, - // { - // name: "腾讯医疗", - // subtitle: TencentHealthcare, - // value: "#0077FF", - // }, - // { - // name: "腾讯游戏", - // subtitle: TencentGames, - // value: "#FD853A", - // }, - // { - // name: "腾讯安全", - // subtitle: TencentSafe, - // value: "#005AFF", - // }, - ], - }, - // { - // title: "业务推荐", - // options: [ - // { - // name: "政务", - // subtitle: GovService, - // value: "#EE1C25", - // }, - - // { - // name: "文旅", - // subtitle: CulturalTourism, - // value: "#262626", - // }, - // { - // name: "智慧零售", - // subtitle: SmartRetail, - // value: "#623BFF", - // }, - // ], - // }, -]; - -/** - * 主题文件夹名字需要和 `RECOMMEND_THEMES` 中的 `enName` 对应 - */ -export const BUILT_IN_THEMES = { - web: { - TDesign: { - light: WebTDesignLight, - dark: WebTDesignDark, - }, - TCloud: { - light: WebTCloudLight, - dark: WebTCloudDark, - extra: WebTCloudExtra, - }, - common: { - font: WebFont, - radius: WebRadius, - size: WebSize, - }, - }, - mobile: { - TDesign: { - light: MobileTDesignLight, - dark: MobileTDesignDark, - }, - common: { - font: MobileFont, - radius: MobileRadius, - }, - }, -}; diff --git a/packages/theme-generator/src/common/Themes/token.js b/packages/theme-generator/src/common/Themes/token.js deleted file mode 100644 index 3227c697..00000000 --- a/packages/theme-generator/src/common/Themes/token.js +++ /dev/null @@ -1,239 +0,0 @@ -/* --- 亮色模式 --- */ -const LIGHT_WARNING_COLOR = `--td-warning-color-1: #fef3e6; ---td-warning-color-2: #f9e0c7; ---td-warning-color-3: #f7c797; ---td-warning-color-4: #f2995f; ---td-warning-color-5: #ed7b2f; ---td-warning-color-6: #d35a21; ---td-warning-color-7: #ba431b; ---td-warning-color-8: #9e3610; ---td-warning-color-9: #842b0b; ---td-warning-color-10: #5a1907; ---td-warning-color: var(--td-warning-color-5); ---td-warning-color-hover: var(--td-warning-color-4); ---td-warning-color-focus: var(--td-warning-color-2); ---td-warning-color-active: var(--td-warning-color-6); ---td-warning-color-disabled: var(--td-warning-color-3); ---td-warning-color-light: var(--td-warning-color-1);`; - -const LIGHT_ERROR_COLOR = `--td-error-color-1: #fdecee; ---td-error-color-2: #f9d7d9; ---td-error-color-3: #f8b9be; ---td-error-color-4: #f78d94; ---td-error-color-5: #f36d78; ---td-error-color-6: #e34d59; ---td-error-color-7: #c9353f; ---td-error-color-8: #b11f26; ---td-error-color-9: #951114; ---td-error-color-10: #680506; ---td-error-color: var(--td-error-color-6); ---td-error-color-hover: var(--td-error-color-5); ---td-error-color-focus: var(--td-error-color-2); ---td-error-color-active: var(--td-error-color-7); ---td-error-color-disabled: var(--td-error-color-3); ---td-error-color-light: var(--td-error-color-1);`; - -const LIGHT_SUCCESS_COLOR = `--td-success-color-1: #e8f8f2; ---td-success-color-2: #bcebdc; ---td-success-color-3: #85dbbe; ---td-success-color-4: #48c79c; ---td-success-color-5: #00a870; ---td-success-color-6: #078d5c; ---td-success-color-7: #067945; ---td-success-color-8: #056334; ---td-success-color-9: #044f2a; ---td-success-color-10: #033017; ---td-success-color: var(--td-success-color-5); ---td-success-color-hover: var(--td-success-color-4); ---td-success-color-focus: var(--td-success-color-2); ---td-success-color-active: var(--td-success-color-6); ---td-success-color-disabled: var(--td-success-color-3); ---td-success-color-light: var(--td-success-color-1);`; - -const LIGHT_GRAY_COLOR = `--td-gray-color-1: #f3f3f3; ---td-gray-color-2: #eee; ---td-gray-color-3: #e7e7e7; ---td-gray-color-4: #dcdcdc; ---td-gray-color-5: #c5c5c5; ---td-gray-color-6: #a6a6a6; ---td-gray-color-7: #8b8b8b; ---td-gray-color-8: #777; ---td-gray-color-9: #5e5e5e; ---td-gray-color-10: #4b4b4b; ---td-gray-color-11: #383838; ---td-gray-color-12: #2c2c2c; ---td-gray-color-13: #242424; ---td-gray-color-14: #181818; ---td-bg-color-container: #fff; ---td-bg-color-container-select: #fff; ---td-bg-color-page: var(--td-gray-color-2); ---td-bg-color-container-hover: var(--td-gray-color-1); ---td-bg-color-container-active: var(--td-gray-color-3); ---td-bg-color-secondarycontainer: var(--td-gray-color-1); ---td-bg-color-secondarycontainer-hover: var(--td-gray-color-2); ---td-bg-color-secondarycontainer-active: var(--td-gray-color-4); ---td-bg-color-component: var(--td-gray-color-3); ---td-bg-color-component-hover: var(--td-gray-color-4); ---td-bg-color-component-active: var(--td-gray-color-6); ---td-bg-color-component-disabled: var(--td-gray-color-2); ---td-component-stroke: var(--td-gray-color-3); ---td-component-border: var(--td-gray-color-4);`; - -const LIGHT_FONT_COLOR = `--td-font-white-1: #ffffff; ---td-font-white-2: rgba(255, 255, 255, 0.55); ---td-font-white-3: rgba(255, 255, 255, 0.35); ---td-font-white-4: rgba(255, 255, 255, 0.22); ---td-font-gray-1: rgba(0, 0, 0, 0.9); ---td-font-gray-2: rgba(0, 0, 0, 0.6); ---td-font-gray-3: rgba(0, 0, 0, 0.4); ---td-font-gray-4: rgba(0, 0, 0, 0.26); ---td-text-color-primary: var(--td-font-gray-1); ---td-text-color-secondary: var(--td-font-gray-2); ---td-text-color-placeholder: var(--td-font-gray-3); ---td-text-color-disabled: var(--td-font-gray-4); ---td-text-color-anti: #fff; ---td-text-color-brand: var(--td-brand-color); ---td-text-color-link: var(--td-brand-color);`; - -const LIGHT_SCROLLBAR_COLOR = `--td-brand-color-light-hover: var(--td-brand-color-2); ---td-warning-color-light-hover: var(--td-warning-color-2); ---td-error-color-light-hover: var(--td-error-color-2); ---td-success-color-light-hover: var(--td-success-color-2); ---td-bg-color-secondarycomponent: var(--td-gray-color-4); ---td-bg-color-secondarycomponent-hover: var(--td-gray-color-5); ---td-bg-color-secondarycomponent-active: var(--td-gray-color-6); ---td-table-shadow-color: rgba(0, 0, 0, 8%); ---td-scrollbar-color: rgba(0, 0, 0, 10%); ---td-scrollbar-hover-color: rgba(0, 0, 0, 30%); ---td-scroll-track-color: #fff; ---td-bg-color-specialcomponent: #fff; ---td-border-level-1-color: var(--td-gray-color-3); ---td-border-level-2-color: var(--td-gray-color-4); ---td-shadow-1: 0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%); ---td-shadow-2: 0 3px 14px 2px rgba(0, 0, 0, 5%), 0 8px 10px 1px rgba(0, 0, 0, 6%), 0 5px 5px -3px rgba(0, 0, 0, 10%); ---td-shadow-3: 0 6px 30px 5px rgba(0, 0, 0, 5%), 0 16px 24px 2px rgba(0, 0, 0, 4%), 0 8px 10px -5px rgba(0, 0, 0, 8%); ---td-shadow-inset-top: inset 0 0.5px 0 #dcdcdc; ---td-shadow-inset-right: inset 0.5px 0 0 #dcdcdc; ---td-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc; ---td-shadow-inset-left: inset -0.5px 0 0 #dcdcdc; ---td-mask-active: rgba(0, 0, 0, 0.6); ---td-mask-disabled: rgba(255, 255, 255, 0.6);`; - -export const LIGHT_FUNCTION_COLOR = `${LIGHT_WARNING_COLOR} ${LIGHT_ERROR_COLOR} ${LIGHT_SUCCESS_COLOR} ${LIGHT_GRAY_COLOR} ${LIGHT_FONT_COLOR} ${LIGHT_SCROLLBAR_COLOR}`; - -/* --- 暗色模式 --- */ -const DARK_ERROR_COLOR = `--td-error-color-1: #472324; ---td-error-color-2: #5e2a2d; ---td-error-color-3: #703439; ---td-error-color-4: #83383e; ---td-error-color-5: #a03f46; ---td-error-color-6: #c64751; ---td-error-color-7: #de6670; ---td-error-color-8: #ec888e; ---td-error-color-9: #edb1b6; ---td-error-color-10: #eeced0;`; - -const DARK_SUCCESS_COLOR = `--td-success-color-1: #193a2a; ---td-success-color-2: #1a4230; ---td-success-color-3: #17533d; ---td-success-color-4: #0d7a55; ---td-success-color-5: #059465; ---td-success-color-6: #43af8a; ---td-success-color-7: #46bf96; ---td-success-color-8: #80d2b6; ---td-success-color-9: #b4e1d3; ---td-success-color-10: #deede8;`; - -const DARK_WARNING_COLOR = `--td-warning-color-1: #4f2a1d; ---td-warning-color-2: #582f21; ---td-warning-color-3: #733c23; ---td-warning-color-4: #a75d2b; ---td-warning-color-5: #cf6e2d; ---td-warning-color-6: #dc7633; ---td-warning-color-7: #e8935c; ---td-warning-color-8: #ecbf91; ---td-warning-color-9: #eed7bf; ---td-warning-color-10: #f3e9dc;`; - -const DARK_GRAY_COLOR = `--td-gray-color-1: #f3f3f3; ---td-gray-color-2: #eee; ---td-gray-color-3: #e7e7e7; ---td-gray-color-4: #dcdcdc; ---td-gray-color-5: #c5c5c5; ---td-gray-color-6: #a6a6a6; ---td-gray-color-7: #8b8b8b; ---td-gray-color-8: #777; ---td-gray-color-9: #5e5e5e; ---td-gray-color-10: #4b4b4b; ---td-gray-color-11: #383838; ---td-gray-color-12: #2c2c2c; ---td-gray-color-13: #242424; ---td-gray-color-14: #181818; ---td-bg-color-page: var(--td-gray-color-14); ---td-bg-color-container: var(--td-gray-color-13); ---td-bg-color-container-hover: var(--td-gray-color-12); ---td-bg-color-container-active: var(--td-gray-color-10); ---td-bg-color-container-select: var(--td-gray-color-9); ---td-bg-color-secondarycontainer: var(--td-gray-color-12); ---td-bg-color-secondarycontainer-hover: var(--td-gray-color-11); ---td-bg-color-secondarycontainer-active: var(--td-gray-color-9); ---td-bg-color-component: var(--td-gray-color-11); ---td-bg-color-component-hover: var(--td-gray-color-10); ---td-bg-color-component-active: var(--td-gray-color-9); ---td-bg-color-component-disabled: var(--td-gray-color-12); ---td-component-stroke: var(--td-gray-color-11); ---td-component-border: var(--td-gray-color-9);`; - -const DARK_FONT_COLOR = `--td-font-white-1: rgba(255, 255, 255, 0.9); ---td-font-white-2: rgba(255, 255, 255, 0.55); ---td-font-white-3: rgba(255, 255, 255, 0.35); ---td-font-white-4: rgba(255, 255, 255, 0.22); ---td-font-gray-1: rgba(0, 0, 0, 0.9); ---td-font-gray-2: rgba(0, 0, 0, 0.6); ---td-font-gray-3: rgba(0, 0, 0, 0.4); ---td-font-gray-4: rgba(0, 0, 0, 0.26); ---td-text-color-primary: var(--td-font-white-1); ---td-text-color-secondary: var(--td-font-white-2); ---td-text-color-placeholder: var(--td-font-white-3); ---td-text-color-disabled: var(--td-font-white-4); ---td-text-color-anti: #fff; ---td-text-color-brand: var(--td-brand-color); ---td-text-color-link: var(--td-brand-color);`; - -const DARK_SCROLLBAR_COLOR = `--td-shadow-1: 0 4px 6px rgba(0, 0, 0, 0.06), 0 1px 10px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12); - --td-shadow-2: 0 8px 10px rgba(0, 0, 0, 0.12), 0 3px 14px rgba(0, 0, 0, 0.10), 0 5px 5px rgba(0, 0, 0, 0.16); - --td-shadow-3: 0 16px 24px rgba(0, 0, 0, 0.14), 0 6px 30px rgba(0, 0, 0, 0.12), 0 8px 10px rgba(0, 0, 0, 0.20); - --td-shadow-inset-top: inset 0 0.5px 0 #5e5e5e; - --td-shadow-inset-right: inset 0.5px 0 0 #5e5e5e; - --td-shadow-inset-bottom: inset 0 -0.5px 0 #5e5e5e; - --td-shadow-inset-left: inset -0.5px 0 0 #5e5e5e; - --td-table-shadow-color: rgba(0, 0, 0, 55%); - --td-scrollbar-color: rgba(255, 255, 255, 10%); - --td-scrollbar-hover-color: rgba(255, 255, 255, 30%); - --td-scroll-track-color: #333; - --td-bg-color-specialcomponent: transparent; - --td-border-level-1-color: var(--td-gray-color-11); - --td-border-level-2-color: var(--td-gray-color-9); - --td-mask-active: rgba(0, 0, 0, 0.4); - --td-mask-disabled: rgba(0, 0, 0, 0.6);`; - -export const DARK_FUNCTION_COLOR = `${DARK_WARNING_COLOR} ${DARK_ERROR_COLOR} ${DARK_SUCCESS_COLOR} ${DARK_GRAY_COLOR} ${DARK_FONT_COLOR} ${DARK_SCROLLBAR_COLOR}`; - -/** - * 移动端缺失的 Token - */ -export const MOBILE_MISSING_TOKENS = [ - '--td-brand-color-hover', - '--td-warning-color-hover', - '--td-error-color-hover', - '--td-success-color-hover', - '--td-bg-color-container-hover', - '--td-bg-color-secondarycontainer-hover', - '--td-bg-color-component-hover', - '--td-brand-color-light-hover', - '--td-warning-color-light-hover', - '--td-error-color-light-hover', - '--td-success-color-light-hover', - '--td-bg-color-secondarycomponent-hover', - '--td-bg-color-container-select', -]; diff --git a/packages/theme-generator/src/common/Collapse/index.vue b/packages/theme-generator/src/common/components/Collapse/index.vue similarity index 96% rename from packages/theme-generator/src/common/Collapse/index.vue rename to packages/theme-generator/src/common/components/Collapse/index.vue index 4ed6f4ac..49dd3842 100644 --- a/packages/theme-generator/src/common/Collapse/index.vue +++ b/packages/theme-generator/src/common/components/Collapse/index.vue @@ -29,10 +29,10 @@ + + diff --git a/packages/theme-generator/src/common/SegmentSelection/index.vue b/packages/theme-generator/src/common/components/SegmentSelection/index.vue similarity index 95% rename from packages/theme-generator/src/common/SegmentSelection/index.vue rename to packages/theme-generator/src/common/components/SegmentSelection/index.vue index cee0900a..f177a624 100644 --- a/packages/theme-generator/src/common/SegmentSelection/index.vue +++ b/packages/theme-generator/src/common/components/SegmentSelection/index.vue @@ -5,8 +5,9 @@
+
device === 'mini-program'; +export const isMobile = (device) => device === 'mobile' || isMiniProgram(device); + +export function normalizeDevice(device) { + return isMobile(device) ? 'mobile' : 'web'; +} + +/** + * 初始化给生成器本身使用的变量,避免部分样式在用户调整主题时产生冲突 + */ +export function initGeneratorVars() { + const siteStylesheet = appendStyleSheet(GENERATOR_ID); + siteStylesheet.textContent = GENERATOR_VARIABLES; +} + +export function getDefaultTheme(device) { + return isMobile(device) ? TDESIGN_MOBILE_THEME : TDESIGN_WEB_THEME; +} + +export function getRecommendThemes(device) { + return isMobile(device) ? MOBILE_RECOMMEND_THEMES : WEB_RECOMMEND_THEMES; +} + +/** + * 同步 site 的 亮暗模式给主题生成器 Web Component + */ +export function syncModeToGenerator() { + setUpModeObserver((theme) => { + const generator = document.querySelector('td-theme-generator'); + if (!generator) return; + generator.setAttribute('theme-mode', theme); + }); +} + +export function findThemeByEnName(device, enName) { + const themes = getRecommendThemes(device); + for (const category of themes) { + const theme = category.options.find((t) => t.enName === enName); + if (theme) return theme; + } + return getDefaultTheme(device); +} + +/** + * 初始化当前主题对应的样式表 + */ +export function initThemeStyleSheet(themeName, device) { + const deviceType = normalizeDevice(device); + const theme = findThemeByEnName(deviceType, themeName); + + const styleSheet = appendStyleSheet(CUSTOM_THEME_ID); + const darkStyleSheet = appendStyleSheet(CUSTOM_DARK_ID); + const extraStyleSheet = appendStyleSheet(CUSTOM_EXTRA_ID); + + const { light, dark, extra } = theme.css; + + styleSheet.textContent = light; + darkStyleSheet.textContent = dark; + extraStyleSheet.textContent = extra; + + return theme; +} + +export function exportCustomStyleSheet(device) { + const styleSheet = document.getElementById(CUSTOM_THEME_ID); + const darkStyleSheet = document.getElementById(CUSTOM_DARK_ID); + const extraStyleSheet = document.getElementById(CUSTOM_EXTRA_ID); + + const cssString = extractRootContent(styleSheet?.textContent); + const darkCssString = extractRootContent(darkStyleSheet?.textContent); + const extraCssString = extraStyleSheet?.textContent || ''; + + let finalCssString; + if (isMiniProgram(device)) { + finalCssString = ` + @media (prefers-color-scheme: light) { + page, .page { + ${cssString} + } + } + @media (prefers-color-scheme: dark) { + page, .page { + ${darkCssString} + } + } + ${extraCssString} + `; + } else { + finalCssString = ` + :root, :root[theme-mode="light"] { + ${cssString} + } + :root[theme-mode="dark"] { + ${darkCssString} + } + ${extraCssString} + `; + } + + const beautifyCssString = cssbeautify(finalCssString.trim()); + const blob = new Blob([beautifyCssString], { type: 'text' }); + const fileSuffix = isMiniProgram(device) ? 'wxss' : 'css'; + downloadFile(blob, `theme.${fileSuffix}`); +} + +export function modifyToken(tokenName, newVal, saveToLocal = true) { + // 获取所有可能包含 token 的样式表 + const styleSheets = document.querySelectorAll(`#${CUSTOM_THEME_ID}, #${CUSTOM_DARK_ID}, #${CUSTOM_EXTRA_ID}`); + + let tokenFound = false; + styleSheets.forEach((styleSheet) => { + const reg = new RegExp(`${tokenName}:\\s*(.*?);`); + const match = styleSheet.textContent.match(reg); + + if (!match) return; + if (match[1] === newVal) { + tokenFound = true; + return; + } + + const currentVal = match[1]; + styleSheet.textContent = styleSheet.textContent.replace(`${tokenName}: ${currentVal}`, `${tokenName}: ${newVal}`); + tokenFound = true; + + updateLocalToken(tokenName, saveToLocal ? newVal : null); + }); + + if (!tokenFound) { + console.warn(`CSS variable: ${tokenName} is not exist`); + } +} + +export function getOptionFromLocal(optionName) { + const options = localStorage.getItem(CUSTOM_OPTIONS_ID); + if (!options) return; + const optionObj = JSON.parse(options); + return optionObj[optionName]; +} + +/** + * 如果不传入 `tokenName`,则返回所有的 `token` 对象 + */ +export function getTokenFromLocal(tokenName) { + const tokens = localStorage.getItem(CUSTOM_TOKEN_ID); + if (!tokens) return; + const tokenObj = JSON.parse(tokens); + if (!tokenName) return tokenObj; + return tokenObj[tokenName]; +} + +/** + * @param {*} value 传入 `null` 或 `undefined`,则表示清除掉之前的存储 + */ +export function updateLocalOption(optionName, value) { + if (value) { + const options = localStorage.getItem(CUSTOM_OPTIONS_ID) || '{}'; + const optionObj = JSON.parse(options); + optionObj[optionName] = value; + localStorage.setItem(CUSTOM_OPTIONS_ID, JSON.stringify(optionObj)); + } else { + clearLocalItem(CUSTOM_OPTIONS_ID, optionName); + } +} + +/** + * @param {*} value 传入 `null` 或 `undefined`,则表示清除掉之前的存储 + */ +export function updateLocalToken(tokenName, value) { + if (value) { + const tokens = localStorage.getItem(CUSTOM_TOKEN_ID) || '{}'; + const tokenObj = JSON.parse(tokens); + tokenObj[tokenName] = value; + localStorage.setItem(CUSTOM_TOKEN_ID, JSON.stringify(tokenObj)); + } else { + clearLocalItem(CUSTOM_TOKEN_ID, tokenName); + } +} + +export function applyTokenFromLocal() { + const token = localStorage.getItem(CUSTOM_TOKEN_ID); + if (!token) return; + + const tokenObj = JSON.parse(token); + Object.entries(tokenObj).forEach(([key, value]) => { + modifyToken(key, value); + }); +} + +export function clearLocalTheme() { + localStorage.removeItem(CUSTOM_OPTIONS_ID); + localStorage.removeItem(CUSTOM_TOKEN_ID); +} + +export function convertFromHex(color, format) { + return `(${Color.colorTransform(color, 'hex', format).join(',')})`; +} + +export function generateBrandPalette(hex, remainInput = false) { + const lowCaseHex = hex.toLowerCase(); + + const [{ colors, primary }] = Color.getColorGradations({ + colors: [lowCaseHex], + step: 10, + remainInput, + }); + + const isTencentBlue = lowCaseHex === TENCENT_BLUE.toLowerCase(); + const validPrimary = typeof primary === 'number' && !isNaN(primary) ? primary : 6; + + const lightBrandIdx = isTencentBlue ? 7 : validPrimary + 1; + const lightPalette = [...colors]; + + const darkPalette = isTencentBlue ? TENCENT_BLUE_DARK_PALETTE : [...colors].reverse(); + const darkBrandIdx = isTencentBlue ? 8 : 6; + + return { lightPalette, lightBrandIdx, darkPalette, darkBrandIdx }; +} + +export function generateFunctionalPalette(hex, step = 10) { + const lowCaseHex = hex.toLowerCase(); + const [{ colors }] = Color.getColorGradations({ + colors: [lowCaseHex], + step, + }); + + const lightPalette = [...colors]; + const darkPalette = [...colors].reverse(); + + return { lightPalette, darkPalette }; +} + +export function generateNeutralPalette(hex, isRelatedTheme) { + if (isRelatedTheme) { + return Color.getNeutralColor(hex); + } else { + return generateFunctionalPalette(hex, 14).lightPalette; + } +} + +/** + * @param {'init' | 'update'} trigger - 触发类型 + */ +export function updateStyleSheetColor(type, lightPalette, darkPalette, trigger) { + const styleSheet = appendStyleSheet(CUSTOM_THEME_ID); + const darkStyleSheet = appendStyleSheet(CUSTOM_DARK_ID); + const updateColorTokens = (styleSheet, palette) => { + palette.forEach((color, index) => { + const tokenName = `--td-${type}-color-${index + 1}`; + const regExp = new RegExp(`${tokenName}:.*?;`, 'g'); + let replacement = `${tokenName}: ${color};`; + if (trigger === 'init') { + // 确保不覆盖用户本地自定义的值 + replacement = `${tokenName}: ${getTokenFromLocal(tokenName) || color};`; + } + if (trigger === 'update') { + updateLocalToken(tokenName, null); // 清除本地存储的颜色 Token + } + styleSheet.textContent = styleSheet.textContent.replace(regExp, replacement); + }); + }; + + updateColorTokens(styleSheet, lightPalette); + updateColorTokens(darkStyleSheet, darkPalette); +} + +export function syncColorTokensToStyle(lightTokenMap, darkTokenMap) { + const styleSheet = appendStyleSheet(CUSTOM_THEME_ID); + const darkStyleSheet = appendStyleSheet(CUSTOM_DARK_ID); + const updateColorTokens = (styleSheet, tokenMap) => { + tokenMap.forEach(({ name, idx }) => { + const regExp = new RegExp(`${name}:.*?;`, 'g'); + const replacement = `${name}: var(--td-brand-color-${idx});`; + styleSheet.textContent = styleSheet.textContent.replace(regExp, replacement); + }); + }; + + updateColorTokens(styleSheet, lightTokenMap); + updateColorTokens(darkStyleSheet, darkTokenMap); +} + +/** + * 根据 token 名称获取对应的索引 + * 例如 `--td-brand-focus` -> 2 + */ +export function collectTokenIndexes(tokenArr) { + const isDarkMode = document.documentElement.getAttribute('theme-mode') === 'dark'; + const targetCss = document.querySelector(isDarkMode ? `#${CUSTOM_DARK_ID}` : `#${CUSTOM_THEME_ID}`); + + return tokenArr + .map((token) => { + const reg = new RegExp(`${token}:\\s*var\\((--td-[\\w-]+)\\)`, 'i'); + const match = targetCss?.textContent.match(reg); + if (match) { + return { + name: token, + idx: parseInt(match[1].match(/(\d+)$/)?.[1], 10), + }; + } + return null; + }) + .filter(Boolean) + .sort((a, b) => a.idx - b.idx); +} diff --git a/packages/theme-generator/src/common/Themes/iframe.js b/packages/theme-generator/src/common/themes/iframe.js similarity index 91% rename from packages/theme-generator/src/common/Themes/iframe.js rename to packages/theme-generator/src/common/themes/iframe.js index 84b5ecb2..05287a43 100644 --- a/packages/theme-generator/src/common/Themes/iframe.js +++ b/packages/theme-generator/src/common/themes/iframe.js @@ -1,5 +1,5 @@ -import { extractRootContent } from '../utils'; -import { CUSTOM_DARK_ID, CUSTOM_THEME_ID, isMiniProgram, isMobile } from './'; +import { extractRootContent, getThemeMode, setUpModeObserver } from '../utils'; +import { CUSTOM_DARK_ID, CUSTOM_THEME_ID, isMiniProgram, isMobile } from './core'; /* ----- 同步亮暗模式 ----- */ function handleMobileModeChange(iframe, mode) { @@ -90,23 +90,11 @@ function watchThemeModeChange(iframe) { }; // 初始化 - const mode = document.documentElement.getAttribute('theme-mode'); - handleModeChange(mode); + handleModeChange(getThemeMode()); - const observer = new MutationObserver((mutationsList) => { - for (const mutation of mutationsList) { - if (mutation.type === 'attributes' && mutation.attributeName === 'theme-mode') { - const newMode = document.documentElement.getAttribute('theme-mode'); - handleModeChange(newMode); - } - } + const observer = setUpModeObserver((newMode) => { + handleModeChange(newMode); }); - - observer.observe(document.documentElement, { - attributes: true, - attributeFilter: ['theme-mode'], - }); - return observer; } diff --git a/packages/theme-generator/src/common/themes/index.js b/packages/theme-generator/src/common/themes/index.js new file mode 100644 index 00000000..b5870fcf --- /dev/null +++ b/packages/theme-generator/src/common/themes/index.js @@ -0,0 +1,4 @@ +export * from './built-in'; +export * from './core'; +export * from './iframe'; +export * from './store'; diff --git a/packages/theme-generator/src/common/themes/store.js b/packages/theme-generator/src/common/themes/store.js new file mode 100644 index 00000000..630713f7 --- /dev/null +++ b/packages/theme-generator/src/common/themes/store.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; + +import { DEFAULT_THEME_META, TDESIGN_WEB_THEME } from './built-in'; +import { clearLocalTheme, getDefaultTheme, getOptionFromLocal, initThemeStyleSheet, updateLocalOption } from './core'; + +export const themeStore = Vue.observable({ + device: 'web', + theme: TDESIGN_WEB_THEME, + brandColor: getOptionFromLocal('color') || DEFAULT_THEME_META.value, + refreshId: 0, // 用于强制刷新绑定了 key 的组件 UI + updateDevice(device) { + this.device = device; + this.theme = getInitialTheme(device); + }, + updateTheme(theme) { + this.theme = theme; + initThemeStyleSheet(theme.enName, this.device); + clearLocalTheme(); + updateLocalOption('theme', theme.enName !== DEFAULT_THEME_META.enName ? theme.enName : null); + this.updateBrandColor(theme.value); + this.incrementRefreshId(); + }, + resetTheme() { + this.updateTheme(getDefaultTheme(this.device)); + }, + updateBrandColor(color) { + this.brandColor = color; + document.documentElement.style.setProperty('--brand-main', color); + }, + incrementRefreshId() { + this.refreshId++; + }, +}); + +function getInitialTheme(device = 'web') { + const localThemeName = getOptionFromLocal('theme') || DEFAULT_THEME_META.enName; + const theme = initThemeStyleSheet(localThemeName, device); + return theme; +} diff --git a/packages/theme-generator/src/common/utils/index.js b/packages/theme-generator/src/common/utils/index.js index 98e9efd1..cf8e0f8f 100644 --- a/packages/theme-generator/src/common/utils/index.js +++ b/packages/theme-generator/src/common/utils/index.js @@ -1,27 +1,35 @@ -export * from 'tdesign-vue/es/_common/js/color-picker'; +export * from './animation'; -import GENERATOR_VARIABLES from '!raw-loader!./vars.css'; -const GENERATOR_ID = 'TDESIGN_GENERATOR_SYMBOL'; +/** + * 获取指定 CSS Token 对应的数值 + */ +export function getTokenValue(name) { + const isDarkMode = document.documentElement.getAttribute('theme-mode') === 'dark'; + const rootElement = isDarkMode ? document.querySelector('[theme-mode="dark"]') : document.documentElement; + return window.getComputedStyle(rootElement).getPropertyValue(name).toLowerCase().trim(); +} /** - * 初始化给生成器本身使用的样式变量,避免和 TDesign 冲突 + * 获取当前亮暗模式 (light / dark) */ -export function initGeneratorVars() { - const siteStylesheet = appendStyleSheet(GENERATOR_ID); - siteStylesheet.textContent = GENERATOR_VARIABLES; +export function getThemeMode() { + return document.documentElement.getAttribute('theme-mode') || 'light'; } /** - * 同步亮暗模式给 Web Component + * 创建亮暗变化监听器 */ -export function syncThemeToGenerator() { +export function setUpModeObserver(handler) { + let mode = getThemeMode(); + const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { - if (mutation.type === 'attributes' && mutation.attributeName === 'theme-mode') { - const generator = document.querySelector('td-theme-generator'); - if (!generator) return; - const themeMode = document.documentElement.getAttribute('theme-mode'); - generator.setAttribute('theme-mode', themeMode); + if (mutation.type === 'attributes') { + const newMode = getThemeMode(); + if (newMode !== mode) { + mode = newMode; + handler(mode); + } } } }); @@ -30,6 +38,8 @@ export function syncThemeToGenerator() { attributes: true, attributeFilter: ['theme-mode'], }); + + return observer; } /** @@ -72,23 +82,6 @@ export function downloadFile(blob, fileName) { a.click(); } -/** - * 从 CSS 文本中移除指定的 Token 字符串 / 数组 - * - e.g. `"--td-xxx-1"` or `["--td-xxx-2", "--td-xxx-3"]` - */ -export function removeCssProperties(cssText, properties) { - if (!Array.isArray(properties)) { - properties = [properties]; - } - - properties.forEach((property) => { - const reg = new RegExp(`${property}:\\s*.*?;`, 'g'); - cssText = cssText.replace(reg, ''); - }); - - return cssText; -} - /** * 从 CSS 文本中提取 `:root` 中的内容 */ diff --git a/packages/theme-generator/src/dock/svg/AdjustSvg.vue b/packages/theme-generator/src/dock/svg/AdjustSvg.vue deleted file mode 100644 index 4d5b81cb..00000000 --- a/packages/theme-generator/src/dock/svg/AdjustSvg.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/packages/theme-generator/src/recommend-themes/PickedSvg.vue b/packages/theme-generator/src/float-dock/components/RecommendThemes/PickedSvg.vue similarity index 100% rename from packages/theme-generator/src/recommend-themes/PickedSvg.vue rename to packages/theme-generator/src/float-dock/components/RecommendThemes/PickedSvg.vue diff --git a/packages/theme-generator/src/recommend-themes/index.vue b/packages/theme-generator/src/float-dock/components/RecommendThemes/index.vue similarity index 77% rename from packages/theme-generator/src/recommend-themes/index.vue rename to packages/theme-generator/src/float-dock/components/RecommendThemes/index.vue index 8c184b13..8e638b82 100644 --- a/packages/theme-generator/src/recommend-themes/index.vue +++ b/packages/theme-generator/src/float-dock/components/RecommendThemes/index.vue @@ -1,11 +1,12 @@ +