From e6b261da0c6377b31bc0e8962a325744f534a5d5 Mon Sep 17 00:00:00 2001 From: Kagol Date: Wed, 23 Feb 2022 00:07:18 +0800 Subject: [PATCH] feat: add devui-theme --- packages/devui-theme/.gitignore | 24 ++ packages/devui-theme/package.json | 27 ++ packages/devui-theme/src/index.ts | 2 + .../devui-theme/src/styles-var/devui-var.less | 1 + .../devui-theme/src/styles-var/devui-var.scss | 6 + .../src/styles-var/private/_animation.scss | 9 + .../src/styles-var/private/_color.scss | 100 ++++++ .../src/styles-var/private/_corner.scss | 5 + .../src/styles-var/private/_font.scss | 21 ++ .../src/styles-var/private/_shadow.scss | 12 + .../src/styles-var/private/_variables.scss | 43 +++ .../src/styles-var/private/_z-index.scss | 12 + .../src/theme-collection/extend-theme.scss | 334 ++++++++++++++++++ .../src/theme-collection/extend-theme.ts | 213 +++++++++++ .../devui-theme/src/theme-collection/index.ts | 1 + .../src/theme-collection/public-api.ts | 1 + packages/devui-theme/src/theme/index.ts | 1 + packages/devui-theme/src/theme/key-config.ts | 10 + packages/devui-theme/src/theme/media-query.ts | 54 +++ packages/devui-theme/src/theme/public-api.ts | 5 + packages/devui-theme/src/theme/theme-data.ts | 324 +++++++++++++++++ .../devui-theme/src/theme/theme-management.ts | 105 ++++++ .../devui-theme/src/theme/theme-service.ts | 189 ++++++++++ packages/devui-theme/src/theme/theme.ts | 43 +++ .../src/theme/utils/context-service.ts | 17 + .../devui-theme/src/theme/utils/event-bus.ts | 87 +++++ packages/devui-theme/src/theme/utils/index.ts | 4 + .../devui-theme/src/theme/utils/interface.ts | 15 + .../src/theme/utils/storage-service.ts | 17 + packages/devui-theme/tsconfig.json | 16 + packages/devui-theme/tsconfig.node.json | 8 + packages/devui-theme/vite.config.ts | 24 ++ .../devui-theme/components/NavBar.vue | 10 +- packages/devui-vue/docs/theme-guide/index.md | 149 ++++++++ packages/devui-vue/docs/vite.config.ts | 1 + packages/devui-vue/package.json | 3 +- pnpm-lock.yaml | 198 ++++++++++- 37 files changed, 2083 insertions(+), 8 deletions(-) create mode 100644 packages/devui-theme/.gitignore create mode 100644 packages/devui-theme/package.json create mode 100644 packages/devui-theme/src/index.ts create mode 100644 packages/devui-theme/src/styles-var/devui-var.less create mode 100644 packages/devui-theme/src/styles-var/devui-var.scss create mode 100644 packages/devui-theme/src/styles-var/private/_animation.scss create mode 100755 packages/devui-theme/src/styles-var/private/_color.scss create mode 100644 packages/devui-theme/src/styles-var/private/_corner.scss create mode 100644 packages/devui-theme/src/styles-var/private/_font.scss create mode 100644 packages/devui-theme/src/styles-var/private/_shadow.scss create mode 100755 packages/devui-theme/src/styles-var/private/_variables.scss create mode 100644 packages/devui-theme/src/styles-var/private/_z-index.scss create mode 100644 packages/devui-theme/src/theme-collection/extend-theme.scss create mode 100644 packages/devui-theme/src/theme-collection/extend-theme.ts create mode 100644 packages/devui-theme/src/theme-collection/index.ts create mode 100644 packages/devui-theme/src/theme-collection/public-api.ts create mode 100644 packages/devui-theme/src/theme/index.ts create mode 100644 packages/devui-theme/src/theme/key-config.ts create mode 100644 packages/devui-theme/src/theme/media-query.ts create mode 100644 packages/devui-theme/src/theme/public-api.ts create mode 100644 packages/devui-theme/src/theme/theme-data.ts create mode 100644 packages/devui-theme/src/theme/theme-management.ts create mode 100644 packages/devui-theme/src/theme/theme-service.ts create mode 100644 packages/devui-theme/src/theme/theme.ts create mode 100644 packages/devui-theme/src/theme/utils/context-service.ts create mode 100644 packages/devui-theme/src/theme/utils/event-bus.ts create mode 100644 packages/devui-theme/src/theme/utils/index.ts create mode 100644 packages/devui-theme/src/theme/utils/interface.ts create mode 100644 packages/devui-theme/src/theme/utils/storage-service.ts create mode 100644 packages/devui-theme/tsconfig.json create mode 100644 packages/devui-theme/tsconfig.node.json create mode 100644 packages/devui-theme/vite.config.ts create mode 100644 packages/devui-vue/docs/theme-guide/index.md diff --git a/packages/devui-theme/.gitignore b/packages/devui-theme/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/packages/devui-theme/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/devui-theme/package.json b/packages/devui-theme/package.json new file mode 100644 index 0000000000..c0866bd7f6 --- /dev/null +++ b/packages/devui-theme/package.json @@ -0,0 +1,27 @@ +{ + "name": "devui-theme", + "version": "0.0.1", + "main": "src/index.ts", + "module": "src/index.ts", + "files": [ + "dist", + "src" + ], + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "css-vars-ponyfill": "^2.4.7", + "enquirejs-ssr": "^2.1.7", + "rxjs": "^7.5.4", + "vue": "^3.2.25" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^2.2.0", + "typescript": "^4.5.4", + "vite": "^2.8.0", + "vue-tsc": "^0.29.8" + } +} \ No newline at end of file diff --git a/packages/devui-theme/src/index.ts b/packages/devui-theme/src/index.ts new file mode 100644 index 0000000000..de2843a78e --- /dev/null +++ b/packages/devui-theme/src/index.ts @@ -0,0 +1,2 @@ +export * from './theme'; +export * from './theme-collection'; diff --git a/packages/devui-theme/src/styles-var/devui-var.less b/packages/devui-theme/src/styles-var/devui-var.less new file mode 100644 index 0000000000..0f1fc7c91c --- /dev/null +++ b/packages/devui-theme/src/styles-var/devui-var.less @@ -0,0 +1 @@ +// empty file wait to fill in diff --git a/packages/devui-theme/src/styles-var/devui-var.scss b/packages/devui-theme/src/styles-var/devui-var.scss new file mode 100644 index 0000000000..7152d5fc56 --- /dev/null +++ b/packages/devui-theme/src/styles-var/devui-var.scss @@ -0,0 +1,6 @@ +@import './private/color'; +@import './private/font'; +@import './private/shadow'; +@import './private/corner'; +@import './private/animation'; +@import './private/z-index'; diff --git a/packages/devui-theme/src/styles-var/private/_animation.scss b/packages/devui-theme/src/styles-var/private/_animation.scss new file mode 100644 index 0000000000..0209cefaf7 --- /dev/null +++ b/packages/devui-theme/src/styles-var/private/_animation.scss @@ -0,0 +1,9 @@ +$devui-animation-duration-slow: var(--devui-animation-duration-slow, 300ms); +$devui-animation-duration-base: var(--devui-animation-duration-base, 200ms); +$devui-animation-duration-fast: var(--devui-animation-duration-fast, 100ms); + +$devui-animation-ease-in: var(--devui-animation-ease-in, cubic-bezier(0.5, 0, 0.84, 0.25)); +$devui-animation-ease-out: var(--devui-animation-ease-out, cubic-bezier(0.16, 0.75, 0.5, 1)); +$devui-animation-ease-in-out: var(--devui-animation-ease-in-out, cubic-bezier(0.5, 0.05, 0.5, 0.95)); +$devui-animation-ease-in-out-smooth: var(--devui-animation-ease-in-out-smooth, cubic-bezier(0.645, 0.045, 0.355, 1)); +$devui-animation-linear: var(--devui-animation-linear, cubic-bezier(0, 0, 1, 1)); diff --git a/packages/devui-theme/src/styles-var/private/_color.scss b/packages/devui-theme/src/styles-var/private/_color.scss new file mode 100755 index 0000000000..84cc2926a0 --- /dev/null +++ b/packages/devui-theme/src/styles-var/private/_color.scss @@ -0,0 +1,100 @@ +// 基础变量 +$devui-global-bg: var(--devui-global-bg, #f3f6f8); // 全局带底色背景 +$devui-global-bg-normal: var(--devui-global-bg-normal, #ffffff); // 全局白色背景 +$devui-base-bg: var(--devui-base-bg, #ffffff); // 基础区块背景白色 +$devui-base-bg-dark: var(--devui-base-bg-dark, #333854); // 基础区块背景深色(固定) +$devui-brand: var(--devui-brand, #5e7ce0); // 品牌色 +$devui-brand-foil: var(--devui-brand-foil, #859bff); // 品牌色辅助色、正衬色 +$devui-brand-hover: var(--devui-brand-hover, #7693f5); // 品牌色高亮色(加亮) +$devui-brand-active: var(--devui-brand-active, #526ecc); // 品牌色激活色(加深) +$devui-brand-active-focus: var(--devui-brand-active-focus, #344899); // 品牌色焦点色(重度加深) +$devui-contrast: var(--devui-contrast, #c7000b); // 品牌色撞色、对比色、反衬色、第二品牌色 +$devui-text: var(--devui-text, #252b3a); // 正文文本 +$devui-text-weak: var(--devui-text-weak, #575d6c); // 弱化的正文信息(手风琴子项,表头) +$devui-aide-text: var(--devui-aide-text, #8a8e99); // 辅助文本、帮助信息(面包屑) +$devui-aide-text-stress: var(--devui-aide-text-stress, #575d6c); // 辅助文本、帮助信息里的强调色 +$devui-placeholder: var(--devui-placeholder, #8a8e99); // 占位符 +$devui-light-text: var(--devui-light-text, #ffffff); // 有色深色背景下字体颜色(固定) +$devui-dark-text: var(--devui-dark-text, #252b3a); // 有色浅色背景下字体颜色(固定) +$devui-link: var(--devui-link, #526ecc); // 链接文本颜色 +$devui-link-active: var(--devui-link-active, #344899); // 链接文本悬停/激活颜色 +$devui-link-light: var(--devui-link-light, #96adfa); // 深色背景下链接文本颜色 +$devui-link-light-active: var(--devui-link-light-active, #beccfa); // 深色背景下链接文本悬停/激活颜色 +$devui-line: var(--devui-line, #adb0b8); // 边框分割线,仅用于边框 +$devui-dividing-line: var(--devui-dividing-line, #dfe1e6); // 内容分割线,用于内容之间的分割 +$devui-block: var(--devui-block, #ffffff); // 大面积的不可折叠区块的背景色(例如顶部导航背景色) +$devui-area: var(--devui-area, #f8f8f8); // 可折叠区块的背景色(展开区域颜色) +$devui-danger: var(--devui-danger, #f66f6a); // 失败、错误、告警 +$devui-warning: var(--devui-warning, #fac20a); // 警告、需注意类型提示 +$devui-waiting: var(--devui-waiting, #9faad7); // 等待中 +$devui-success: var(--devui-success, #50d4ab); // 成功、正确 +$devui-info: var(--devui-info, #5e7ce0); // 通知、一般提示、执行中 +$devui-initial: var(--devui-initial, #e9edfa); // 初始化、未执行 +$devui-unavailable: var(--devui-unavailable, #f5f5f6); // 不可用、禁用状态 +$devui-shadow: var(--devui-shadow, rgba(0, 0, 0, 0.2)); // 阴影色 +$devui-light-shadow: var(--devui-light-shadow, rgba(0, 0, 0, 0.1)); // 弱化阴影色 + +// 图标 +$devui-icon-text: var(--devui-icon-text, #252b3a); // 文字图标颜色,同 正文颜色 +$devui-icon-bg: var(--devui-icon-bg, #ffffff); // svg图标 背景色 +$devui-icon-fill: var(--devui-icon-fill, #d3d5d9); // svg图标 灰色填充色 +$devui-icon-fill-hover: var(--devui-icon-fill-hover, #adb5ce); // svg图标 灰色填充色悬停反馈色 +$devui-icon-fill-active: var(--devui-icon-fill-active, #5e7ce0); // svg图标 高亮填充色(激活状态) +$devui-icon-fill-active-hover: var(--devui-icon-fill-active-hover, #526ecc); // svg图标 高亮填充色悬停反馈色 +// 表单 +$devui-form-control-line: var(--devui-form-control-line, #adb0b8); // 表单控件边框色,同 边框分割线 +$devui-form-control-line-hover: var(--devui-form-control-line-hover, #575d6c); // 表单控件边框悬停反馈色 +$devui-form-control-line-active: var(--devui-form-control-line-active, #5e7ce0); // 表单控件边框激活色,用于获得焦点 +$devui-form-control-line-active-hover: var(--devui-form-control-line-active-hover, #344899); // 表单控件边框激活色,用于获得焦点且悬停 +// 列表 +$devui-list-item-active-bg: var(--devui-list-item-active-bg, #5e7ce0); // 列表类型单选选中背景 +$devui-list-item-active-text: var(--devui-list-item-active-text, #ffffff); // 列表类型单选选中背景搭配文本,同 有色深色背景下字体颜色(固定) +$devui-list-item-active-hover-bg: var(--devui-list-item-active-hover-bg, #526ecc); // 列表类型单选选中背景悬停反馈色(仅用于分页等) +$devui-list-item-hover-bg: var(--devui-list-item-hover-bg, #f2f5fc); // 列表类型普通选项悬停背景 +$devui-list-item-hover-text: var(--devui-list-item-hover-text, #526ecc); // 列表类型普通选项悬停背景搭配文本 +$devui-list-item-selected-bg: var(--devui-list-item-selected-bg, #e9edfa); // 列表类型多选被选中的行色,仅用于表格类 +$devui-list-item-strip-bg: var(--devui-list-item-strip-bg, #f2f5fc); // 列表类型斑马纹色,仅用于表格类 +// 禁用 +$devui-disabled-bg: var(--devui-disabled-bg, #f5f5f6); // disabled背景颜色 +$devui-disabled-line: var(--devui-disabled-line, #dfe1e6); // disabled边框颜色 +$devui-disabled-text: var(--devui-disabled-text, #adb0b8); // disabled文字颜色 +$devui-primary-disabled: var(--devui-primary-disabled, #beccfa); //主要按钮disabled状态文字颜色 +$devui-icon-fill-active-disabled: var(--devui-icon-fill-active-disabled, #beccfa); // svg图标激活状态禁用色 +// 特殊背景色 +$devui-label-bg: var(--devui-label-bg, #eef0f5); // 默认标签化选项背景色 +$devui-connected-overlay-bg: var(--devui-connected-overlay-bg, #ffffff); // 有连接点的弹出菜单层背景色 +$devui-connected-overlay-line: var(--devui-connected-overlay-line, #526ecc); // 有连接点的弹出菜单层边框色 +$devui-fullscreen-overlay-bg: var(--devui-fullscreen-overlay-bg, #ffffff); // 全屏类型的弹出内容层背景色(模态弹窗) +$devui-feedback-overlay-bg: var(--devui-feedback-overlay-bg, #464d6e); // 信息提示反馈类型的漂浮层背景色(toast、popover) +$devui-feedback-overlay-text: var(--devui-feedback-overlay-text, #dfe1e6); // 信息提示反馈类型的漂浮层背景色搭配文本色 +$devui-embed-search-bg: var(--devui-embed-search-bg, #f2f5fc); // 被嵌套的无边框搜索框背景色 +$devui-embed-search-bg-hover: var(--devui-embed-search-bg-hover, #eef0f5); // 被嵌套的无边框搜索框背景色 +$devui-float-block-shadow: var(--devui-float-block-shadow, rgba(94, 124, 224, 0.3)); // 特殊浮层背景色(待修正) +$devui-highlight-overlay: var(--devui-highlight-overlay, rgba(255, 255, 255, 0.8)); // 局部半透明全局浮层背景色(比如底部) +$devui-range-item-hover-bg: var(--devui-range-item-hover-bg, #e9edfa); // datepicker范围中的日期hover的反馈背景色 + +// 按钮 +$devui-primary: var(--devui-primary, #5e7ce0); // 主要按钮,同品牌色 +$devui-primary-hover: var(--devui-primary-hover, #7693f5); // 主要按钮悬停 +$devui-primary-active: var(--devui-primary-active, #344899); // 主要按钮激活 + +$devui-contrast-hover: var(--devui-contrast-hover, #d64a52); // 突出按钮悬停 +$devui-contrast-active: var(--devui-contrast-active, #b12220);// 突出按钮激活 + +// 状态 +$devui-danger-line: var(--devui-danger-line, #f66f6a); // 失败边框 +$devui-danger-bg: var(--devui-danger-bg, #ffeeed); // 失败底色 + +$devui-warning-line: var(--devui-warning-line, #fa9841);// 警告边框 +$devui-warning-bg: var(--devui-warning-bg, #fff3e8); // 警告底色 + +$devui-info-line: var(--devui-info-line, #5e7ce0); // 通知边框 +$devui-info-bg: var(--devui-info-bg, #f2f5fc); // 通知底色 + +$devui-success-line: var(--devui-success-line, #50d4ab);// 成功边框 +$devui-success-bg: var(--devui-success-bg, #edfff9); // 成功底色 +$devui-primary-line: var(--devui-primary-line, #5e7ce0);// 主要边框 +$devui-primary-bg: var(--devui-primary-bg, #f2f5fc); // 主要底色 + +$devui-default-line: var(--devui-default-line, #5e7ce0);// 默认边框 +$devui-default-bg: var(--devui-default-bg, #f3f6f8); // 默认底色 diff --git a/packages/devui-theme/src/styles-var/private/_corner.scss b/packages/devui-theme/src/styles-var/private/_corner.scss new file mode 100644 index 0000000000..517e64beef --- /dev/null +++ b/packages/devui-theme/src/styles-var/private/_corner.scss @@ -0,0 +1,5 @@ +//圆角变量 + +$devui-border-radius: var(--devui-border-radius, 2px); //一般圆角 +$devui-border-radius-feedback: var(--devui-border-radius-feedback, 4px); //反馈类圆角 +$devui-border-radius-card: var(--devui-border-radius-card, 6px); //卡片圆角 diff --git a/packages/devui-theme/src/styles-var/private/_font.scss b/packages/devui-theme/src/styles-var/private/_font.scss new file mode 100644 index 0000000000..d1acc6e835 --- /dev/null +++ b/packages/devui-theme/src/styles-var/private/_font.scss @@ -0,0 +1,21 @@ +// 字号大小变量 + +$devui-font-size: var(--devui-font-size, 12px); //正文、卡片副标题 +$devui-font-size-card-title: var(--devui-font-size-card-title, 14px); //卡片标题 +$devui-font-size-page-title: var(--devui-font-size-page-title, 16px); //页面标题 +$devui-font-size-modal-title: var(--devui-font-size-modal-title, 18px); //弹窗标题、数字 +$devui-font-size-price: var(--devui-font-size-price, 20px); //购买价格 +$devui-font-size-data-overview: var(--devui-font-size-data-overview, 24px); //数据总览 + +$devui-font-size-icon: var(--devui-font-size-icon, 16px); //图标大小 +$devui-font-size-sm: var(--devui-font-size-sm, 12px); //当组件size为'sm'时使用此字号大小 +$devui-font-size-md: var(--devui-font-size-md, 12px); //当组件size为''时使用此字号大小 +$devui-font-size-lg: var(--devui-font-size-lg, 14px); //当组件size为'lg'时使用此字号大小 + +$devui-font-title-weight: bold; //标题文字粗细 +$devui-font-content-weight: normal; //内容文字粗细 +$devui-line-height-base: 1.5; //规范行高 + +$font-title-weight: bold; +$font-content-weight: normal; +$line-height-base: 1.5; diff --git a/packages/devui-theme/src/styles-var/private/_shadow.scss b/packages/devui-theme/src/styles-var/private/_shadow.scss new file mode 100644 index 0000000000..a86b13c331 --- /dev/null +++ b/packages/devui-theme/src/styles-var/private/_shadow.scss @@ -0,0 +1,12 @@ +//阴影变量 + +$devui-shadow-length-base: var(--devui-shadow-length-base, 0 1px 4px 0); //直接铺陈在页面上方的元素 (card等) + +$devui-shadow-length-slide-left: var(--devui-shadow-length-slide-left, -2px 0 8px 0); //向左滑动时出现在右侧边缘的阴影 (dataTable固定右侧列向左滑动) +$devui-shadow-length-slide-right: var(--devui-shadow-length-slide-right, 2px 0 8px 0); //向右滑动时出现在左侧边缘的阴影 (dataTable固定左侧列向右滑动) +$devui-shadow-length-connected-overlay : var(--devui-shadow-connected-overlay, 0 2px 8px 0); //有连接点的弹出(覆盖)层 (DatePicker Cascader Select TagsInput Pagination的下拉菜单等) + +$devui-shadow-length-hover : var(--devui-shadow-length-hover, 0 4px 16px 0); //涉及到hover的地方 +$devui-shadow-length-feedback-overlay : var(--devui-shadow-length-feedback-overlay, 0 4px 16px 0); //信息提示反馈类 (PopOver Tooltip Toast StepsGuide等) + +$devui-shadow-length-fullscreen-overlay: var(--devui-shadow-fullscreen-overlay, 0 8px 40px 0); //无连接点的弹出(覆盖)层 (Drawer Modal ImagePreview等) diff --git a/packages/devui-theme/src/styles-var/private/_variables.scss b/packages/devui-theme/src/styles-var/private/_variables.scss new file mode 100755 index 0000000000..69c096e3b9 --- /dev/null +++ b/packages/devui-theme/src/styles-var/private/_variables.scss @@ -0,0 +1,43 @@ +@import './color'; +@import '../core/font'; +@import '../theme/font'; + +$text-color: $devui-text; +$font-family: 'HuaweiFont', 'Helvetica','Arial', 'PingFangSC-Regular', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑','Microsoft JhengHei'; +$font-variant-base: tabular-nums; +$body-background: $devui-base-bg; +$devui-font-size: $devui-font-size; // 12px +// $devui-font-size-modal-title: $devui-font-size-modal-title; // 18px +$font-size-page-title: $devui-font-size-page-title; // 16px +// $devui-font-size-card-title: $devui-font-size-card-title; // 14px +.devui-font-size-base { + font-size: $devui-font-size; // 12px +} + +.devui-font-base { + @include font-content(); // 12px normal 1.5 +} + +.devui-font-size-modal-title { + font-size: $devui-font-size-modal-title; // 18px +} + +.devui-font-modal-title { + @include font-title($devui-font-size-modal-title); // 18px bold 1.5 +} + +.devui-font-size-page-title { + font-size: $devui-font-size-page-title; // 16px +} + +.devui-font-page-title { + @include font-title(); // 16px bold 1.5 +} + +.devui-font-size-secondary-title { + font-size: $devui-font-size-card-title; // 14px +} + +.devui-font-secondary-title { + @include font-title($devui-font-size-card-title); // 14px bold 1.5 +} diff --git a/packages/devui-theme/src/styles-var/private/_z-index.scss b/packages/devui-theme/src/styles-var/private/_z-index.scss new file mode 100644 index 0000000000..7846b41414 --- /dev/null +++ b/packages/devui-theme/src/styles-var/private/_z-index.scss @@ -0,0 +1,12 @@ +// 临时层 +// 若存在遮罩,则遮罩基于对应z-index值-1 +$devui-z-index-full-page-overlay: var(--devui-z-index-full-page-overlay, 1080); // 全屏覆盖类元素 +$devui-z-index-dropdown: var(--devui-z-index-dropdown, 1052); // 下拉菜单,dropdown等 +$devui-z-index-pop-up: var(--devui-z-index-pop-up, 1060); // 提示类信息,popover,tooltip等 +$devui-z-index-modal: var(--devui-z-index-modal, 1050);// 弹窗, +$devui-z-index-drawer: var(--devui-z-index-drawer, 1040);// 抽屉板 +$devui-z-index-framework: var(--devui-z-index-framework, 1000);// 框架类元素,header,sideMenu等 + +// 内容层,根据需要设置,zIndex需小于临时层 + +// 背景层,根据需要设置,zIndex需小于内容层 diff --git a/packages/devui-theme/src/theme-collection/extend-theme.scss b/packages/devui-theme/src/theme-collection/extend-theme.scss new file mode 100644 index 0000000000..78a4f4e547 --- /dev/null +++ b/packages/devui-theme/src/theme-collection/extend-theme.scss @@ -0,0 +1,334 @@ +@import '../styles-var/devui-var.scss'; + +body[ui-theme='infinity-theme'], +body[ui-theme='sweet-theme'], +body[ui-theme='provence-theme'], +body[ui-theme='deep-theme'], +body[ui-theme='galaxy-theme'] { + // TODO: 组件支持全局配置默认尺寸参数后删除 + // button default size change to '32px' + .devui-btn:not(.devui-btn-xs):not(.devui-btn-sm):not(.devui-btn-lg) { + height: 32px; + line-height: 32px; + } + + d-tabs { + display: inline; + + --devui-font-size-card-title: 14px; + } + + d-tag { + --devui-font-size: var(--devui-font-size-sm, 12px); + } + // datepicker定制化处理高亮 + d-datepicker, + d-date-range-picker, + d-datepicker-range-single, + d-two-datepicker-single { + --devui-list-item-active-bg: var(--devui-brand, #5e7ce0); + --devui-list-item-active-hover-bg: var(--devui-brand, #526ecc); + --devui-list-item-active-text: var(--devui-light-text, #ffffff); + --devui-font-size: var(--devui-font-size-sm, 12px); + } + + // TODO: 表单底层统一使用一致的input,全局修改该input尺寸默认值即可 + // input default size change to '32px' + d-multi-auto-complete .multi-auto-complete label.multiple-label-auto-complete ul.devui-dropdown-origin { + padding: 3px 2px 0 2px; + min-height: 32px; + } + + d-search .devui-search { + .devui-input:not(.devui-input-sm):not(.devui-input-lg) { + height: 32px; + } + + .devui-search-icon { + line-height: 32px; + } + + .devui-search-clear:not(.devui-search-clear-lg):not(.devui-search-clear-sm) { + line-height: 32px; + } + } + + d-select .devui-form-group { + .devui-form-control { + &.devui-select-input { + height: 30px; + } + } + + .devui-select-list-wrapper.devui-form-control { + .devui-select-placeholder { + height: 28px; + line-height: 28px; + } + + ul.devui-select-tag-list { + height: 28px; + } + + .devui-select-tag-item { + margin: 3px 1px 0; + } + } + } + + d-tree-select { + .devui-tree-select .popper .popper-activator .devui-select-input.devui-tree-select-input { + padding: 4px 10px; + min-height: 32px; + max-height: 64px; + } + } + + input { + border-radius: $devui-border-radius; + } + + [dTextInput] { + height: 32px; + } + + .devui-form-controls input[type='text'], + .devui-form-controls input[type='password'], + [dTextInput] { + height: 32px; + } + + // select带有搜索存在两层ul + .devui-dropdown-menu > ul.devui-dropdown-menu-wrap { + // 存在搜索框时 + & > li { + padding: 12px 12px 0 12px; + } + + ul { + padding: 12px; + + li { + border-radius: $devui-border-radius; + } + } + } + + // TODO: 下拉专项整改增加offset设置 + .devui-dropdown-menu { + margin-top: 8px !important; + margin-bottom: 8px !important; + } + + // breadcrumb dropdown menu + div.devui-search-container { + padding: 12px 12px 0 12px; + } + // autoComplete等 + .devui-dropdown-menu > ul:not(.devui-dropdown-menu-wrap) { + padding: 12px; + + li { + border-radius: $devui-border-radius; + } + } + // dropdown + ul.devui-dropdown-menu { + padding: 12px; + + li .devui-dropdown-item { + border-radius: $devui-border-radius; + } + } + + d-tags-input { + .devui-tags-autocomplete .devui-suggestion-list { + padding: 12px !important; + } + } + + .devui-input-sm { + height: 24px; + } + + .devui-input-lg { + height: 46px; + } + + d-tabs .devui-nav { + display: block; + + &.devui-nav-tabs, + &.devui-nav-pills { + li a { + line-height: 32px !important; + } + } + } + + // 表单尺寸未统一,upload内部自定义了高度 + d-single-upload, + d-multiple-upload { + .devui-input-group .devui-form-control { + min-height: 32px !important; + + .devui-file-tag { + height: 26px !important; + } + } + } + + div.popper-container div.popper-container-scrollable div.devui-tree-select.devui-options-container { + padding: 12px; + } + + div.popper-container div.devui-tree-select span.devui-form-control-feedback { + top: 12px; + right: 12px; + } + + .table-row-selected { + td { + color: #ffffff !important; + } + } + + // pagination样式修改 + d-pagination { + div.devui-pagination ul.devui-pagination-list li:not(.disabled) { + cursor: pointer; + + a:hover, + span:hover, + a:focus, + span:focus { + background-color: transparent !important; + color: $devui-text !important; + border: 1px solid $devui-dividing-line !important; + box-shadow: 0 1px 3px 0 $devui-light-shadow !important; + } + + a:active { + background-color: transparent !important; + color: $devui-text !important; + } + + &.active a { + background-color: $devui-primary !important; + color: $devui-light-text !important; + cursor: pointer !important; + + &:hover { + background-color: $devui-primary !important; + color: $devui-light-text !important; + border: 1px solid transparent !important; + box-shadow: none !important; + } + } + + a.devui-pagination-link:hover:not(:active) svg g polygon { + fill: $devui-text !important; + } + + a.devui-pagination-link:active svg g polygon { + fill: $devui-text !important; + } + } + + ul.devui-pagination-list > li > a { + height: 28px !important; + min-width: 28px !important; + padding: 0 4px !important; + justify-content: center !important; + border: 1px solid transparent !important; + } + + ul.devui-pagination-sm > li > a { + height: 24px !important; + min-width: 24px !important; + } + + ul.devui-pagination-lg > li > a { + height: 40px !important; + min-width: 40px !important; + } + + .devui-pagination-list > li:first-child > a, + .devui-pagination-list > li:last-child > a { + padding: 0 !important; + height: 28px !important; + line-height: 28px !important; + } + + .devui-pagination-sm > li:first-child > a, + .devui-pagination-sm > li:last-child > a { + height: 24px !important; + line-height: 24px !important; + } + + .devui-pagination-lg > li:first-child > a, + .devui-pagination-lg > li:last-child > a { + height: 40px !important; + line-height: 40px !important; + } + + .devui-pagination-link { + height: 28px !important; + line-height: 28px !important; + } + + .devui-pagination-sm > li:first-child > a { + padding: 0 !important; + } + + .devui-pagination-sm > li:last-child > a { + padding: 0 !important; + } + } +} + +body[ui-theme='deep-theme'] { + .devui-tree-node__content.active { + .devui-tree-node__title { + color: #ffffff; + } + + svg.svg-icon rect { + stroke: #ffffff !important; + } + + svg.svg-icon rect:last-child { + fill: #ffffff !important; + } + } + + .table-row-selected { + td { + color: #ffffff !important; + } + } +} + +body[ui-theme='infinity-theme'] { + d-tabs .devui-nav { + --devui-brand: #252b3a; + --devui-brand-active: #252b3a; + } +} + +body[ui-theme='galaxy-theme'] { + d-tabs .devui-nav-slider { + --devui-tab-slider-bg: #313131; + --devui-text: #a3a3a3; + --devui-brand-active: #ffffff; + --devui-base-bg: #3f3f3f; + } + + d-button .devui-btn-common { + --devui-block: transparent; + } + + d-button .devui-btn-primary:disabled { + --devui-light-text: #838383; + } +} diff --git a/packages/devui-theme/src/theme-collection/extend-theme.ts b/packages/devui-theme/src/theme-collection/extend-theme.ts new file mode 100644 index 0000000000..f68888310c --- /dev/null +++ b/packages/devui-theme/src/theme-collection/extend-theme.ts @@ -0,0 +1,213 @@ +import { devuiDarkTheme, devuiLightTheme, Theme } from '../theme'; +export const infinityTheme: Theme = new Theme({ + id: 'infinity-theme', + name: '无限主题', + data: { ...devuiLightTheme.data, 'devui-brand-foil': '#F2F2F3', + 'devui-global-bg': '#F8F8FA', + 'devui-base-bg': '#ffffff', + 'devui-text': '#252B3A', + 'devui-aide-text': '#71757f', + 'devui-placeholder': '#babbc0', + 'devui-disabled-text': '#cfd0d3', + 'devui-disabled-bg': '#f5f5f6', + 'devui-line': '#D7D8DA', + 'devui-dividing-line': '#F2F2F3', + 'devui-list-item-hover-bg': '#F2F2F3', + 'devui-list-item-active-bg': '#F2F5FC', + 'devui-list-item-active-hover-bg': '#F2F5FC', + 'devui-list-item-selected-bg': '#F2F5FC', + 'devui-list-item-hover-text': '#252b3a', + 'devui-list-item-active-text': '#252B3A', + 'devui-form-control-line-hover': '#A3A6AC', + 'devui-form-control-line': '#D7D8DA', + 'devui-icon-text': '#babbc0', + 'devui-label-bg': '#E9EDFA', + 'devui-border-radius': '4px', + 'devui-font-size': '14px', + 'devui-font-size-md': '14px', + 'devui-font-size-card-title': '16px', + 'devui-shadow-length-fullscreen-overlay': '0 0 6px 0', + 'devui-border-radius-card': '4px'}, + extends: 'devui-light-theme', + isDark: false, +}); + +export const provenceTheme: Theme = new Theme({ + id: 'provence-theme', + name: '紫罗兰主题', + data: { + ...infinityTheme.data, + 'devui-brand': '#7B69EE', + 'devui-brand-foil': '#F5F5F9', + 'devui-brand-active-focus': '#7B69EE', + 'devui-primary-active': '#7B69EE', + 'devui-brand-hover': '#7B69EE', + 'devui-global-bg': '#f9fafb', + 'devui-base-bg': '#ffffff', + 'devui-text': '#070036', + 'devui-aide-text': '#717087', + 'devui-placeholder': '#babbc0', + 'devui-disabled-text': '#cfd0d3', + 'devui-disabled-bg': '#f5f5f6', + 'devui-line': '#E2E2E5', + 'devui-dividing-line': '#F2F2F3', + 'devui-list-item-hover-bg': '#F5F5F9', + 'devui-list-item-active-bg': '#7B69EE', + 'devui-list-item-active-hover-bg': '#7B69EE', + 'devui-list-item-selected-bg': '#F4F2FF', + 'devui-list-item-hover-text': '#252b3a', + 'devui-list-item-active-text': '#ffffff', + 'devui-form-control-line-hover': '#A3A6AC', + 'devui-form-control-line': '#D7D8DA', + 'devui-icon-text': '#babbc0', + 'devui-brand-active': '#7B69EE', + 'devui-primary': '#7B69EE', + 'devui-primary-hover': '#7B69EE', + 'devui-form-control-line-active': '#7B69EE', + 'devui-form-control-line-active-hover': '#7B69EE', + 'devui-icon-fill-active': '#7B69EE', + 'devui-icon-fill-active-hover': '#7B69EE', + 'devui-label-bg': '#F4F2FF', + 'devui-embed-search-bg': '#F4F2FF', + 'devui-connected-overlay-line': '#7B69EE', + 'devui-primary-disabled': '#d8d2fa', + 'devui-icon-fill-active-disabled': '#d8d2fa' + + }, + extends: 'infinity-theme', + isDark: false +}); + +export const sweetTheme: Theme = new Theme({ + id: 'sweet-theme', + name: '蜜糖主题', + data: { + ...infinityTheme.data, + 'devui-brand': '#ec66ab', + 'devui-brand-foil': '#f8f1f5', + 'devui-brand-active-focus': '#ec66ab', + 'devui-primary-active': '#ec66ab', + 'devui-brand-hover': '#ec66ab', + 'devui-global-bg': '#f9fafb', + 'devui-base-bg': '#ffffff', + 'devui-text': '#2f272f', + 'devui-aide-text': '#827d82', + 'devui-placeholder': '#bdb8bd', + 'devui-disabled-text': '#cbcacb', + 'devui-disabled-bg': '#f6f6f6', + 'devui-line': '#aea6ad', + 'devui-dividing-line': '#eae7e9', + 'devui-list-item-hover-bg': '#f8f1f5', + 'devui-list-item-active-bg': '#ffdcee', + 'devui-list-item-active-hover-bg': '#ffdcee', + 'devui-list-item-selected-bg': '#ffdcee', + 'devui-list-item-hover-text': '#252b3a', + 'devui-list-item-active-text': '#252b3a', + 'devui-form-control-line-hover': '#A3A6AC', + 'devui-form-control-line': '#D7D8DA', + 'devui-icon-text': '#babbc0', + 'devui-brand-active': '#ec66ab', + 'devui-primary': '#ec66ab', + 'devui-primary-hover': '#ec66ab', + 'devui-form-control-line-active': '#ec66ab', + 'devui-form-control-line-active-hover': '#ec66ab', + 'devui-icon-fill-active': '#ec66ab', + 'devui-icon-fill-active-hover': '#ec66ab', + 'devui-label-bg': '#ffdcee', + 'devui-embed-search-bg': '#ffdcee', + 'devui-connected-overlay-line': '#ec66ab', + 'devui-primary-disabled': '#fad1e6', + 'devui-icon-fill-active-disabled': '#fad1e6', + }, + extends: 'infinity-theme', + isDark: false +}); + +export const deepTheme: Theme = new Theme({ + id: 'deep-theme', + name: '深邃夜空主题', + data: { + ...infinityTheme.data, + 'devui-brand': '#252b3a', + 'devui-brand-foil': '#f3f4f7', + 'devui-brand-active-focus': '#252b3a', + 'devui-primary-active': '#252b3a', + 'devui-brand-active': '#252b3a', + 'devui-brand-hover': '#252b3a', + 'devui-global-bg': '#f7f8fa', + 'devui-base-bg': '#ffffff', + 'devui-text': '#252b3a', + 'devui-aide-text': '#505c7c', + 'devui-placeholder': '#9ba6bf', + 'devui-disabled-text': '#a8b1c7', + 'devui-disabled-bg': '#f7f8fa', + 'devui-line': '#cdd2df', + 'devui-dividing-line': '#e6e9ef', + 'devui-list-item-hover-bg': '#f3f4f7', + 'devui-list-item-active-bg': '#252b3a', + 'devui-list-item-active-hover-bg': '#252b3a', + 'devui-list-item-selected-bg': '#252b3a', + 'devui-list-item-hover-text': '#252b3a', + 'devui-list-item-active-text': '#ffffff', + 'devui-form-control-line-hover': '#A3A6AC', + 'devui-form-control-line': '#D7D8DA', + 'devui-icon-text': '#babbc0', + 'devui-primary': '#252b3a', + 'devui-primary-hover': '#252b3a', + 'devui-form-control-line-active': '#252b3a', + 'devui-form-control-line-active-hover': '#252b3a', + 'devui-icon-fill-active': '#252b3a', + 'devui-icon-fill-active-hover': '#252b3a', + 'devui-connected-overlay-line': '#252b3a', + 'devui-primary-disabled': '#bebfc4', + 'devui-icon-fill-active-disabled': '#bebfc4', + }, + extends: 'infinity-theme', + isDark: false +}); + +export const galaxyTheme: Theme = new Theme({ + id: 'galaxy-theme', + name: '追光主题', + data: { + ...devuiDarkTheme.data, + 'devui-brand-foil': '#F2F2F3', + 'devui-global-bg': '#000000', + 'devui-base-bg': '#1F1F1F', + 'devui-text': '#F5F5F5', + 'devui-aide-text': '#A3A3A3', + 'devui-placeholder': '#616161', + 'devui-disabled-text': '#838383', + 'devui-disabled-bg': '#3F3F3F', + 'devui-line': '#565656', + 'devui-dividing-line': '#303030', + 'devui-list-item-hover-bg': '#313131', + 'devui-list-item-active-bg': '#30333D', + 'devui-list-item-active-hover-bg': '#30333D', + 'devui-list-item-selected-bg': '#30333D', + 'devui-list-item-hover-text': '#F5F5F5', + 'devui-list-item-active-text': '#526ECC', + 'devui-primary-disabled': '#3f3f3f', + 'devui-form-control-line': '#565656', + 'devui-icon-text': '#A3A3A3', + 'devui-connected-overlay-bg': '#282828', + 'devui-fullscreen-overlay-bg': '#282828', + 'devui-warning-line': '#a2622a', + 'devui-warning-bg': '#4b2e14', + 'devui-success-line': '#27846b ', + 'devui-success-bg': '#123d32', + 'devui-danger-line': '#9f4844', + 'devui-danger-bg': '#4a2120', + 'devui-info-line': '#3c5091', + 'devui-info-bg': '#1c2543', + 'devui-default-bg': '#313131', + 'devui-border-radius': '4px', + 'devui-font-size': '14px', + 'devui-font-size-md': '14px', + 'devui-font-size-card-title': '16px', + 'devui-shadow-length-fullscreen-overlay': '0 0 6px 0', + 'devui-border-radius-card': '4px' + }, + extends: 'devui-dark-theme', + isDark: true +}); diff --git a/packages/devui-theme/src/theme-collection/index.ts b/packages/devui-theme/src/theme-collection/index.ts new file mode 100644 index 0000000000..7e1a213e3e --- /dev/null +++ b/packages/devui-theme/src/theme-collection/index.ts @@ -0,0 +1 @@ +export * from './public-api'; diff --git a/packages/devui-theme/src/theme-collection/public-api.ts b/packages/devui-theme/src/theme-collection/public-api.ts new file mode 100644 index 0000000000..dab9199773 --- /dev/null +++ b/packages/devui-theme/src/theme-collection/public-api.ts @@ -0,0 +1 @@ +export * from './extend-theme'; diff --git a/packages/devui-theme/src/theme/index.ts b/packages/devui-theme/src/theme/index.ts new file mode 100644 index 0000000000..7e1a213e3e --- /dev/null +++ b/packages/devui-theme/src/theme/index.ts @@ -0,0 +1 @@ +export * from './public-api'; diff --git a/packages/devui-theme/src/theme/key-config.ts b/packages/devui-theme/src/theme/key-config.ts new file mode 100644 index 0000000000..e55fcbdf06 --- /dev/null +++ b/packages/devui-theme/src/theme/key-config.ts @@ -0,0 +1,10 @@ +export const THEME_KEY = { + userLastPreferTheme : 'user-custom-theme', // localStorage 存储的上一次主题名字 + userLastPreferThemeData: 'user-custom-theme-data', // localStorage 存储的上一次主题的变量,用于骨架屏阶段快速恢复主题 + currentTheme: 'devuiCurrentTheme', // context(默认window) 当前主题名字 + themeCollection: 'devuiThemes', // context(默认window) 存储所有主题集合 + styleElementId: 'devuiThemeVariables', // DOM Style Element 的 id标识, 标记css变量声明的片段 + transitionStyleElementId: 'devuiThemeColorTransition', // DOM Style Element 的 id标识,标记临时使用的css颜色动画 + uiThemeAttributeName: 'ui-theme', // body 和 style元素标记用户数据 + themeService: 'devuiThemeService' // 全局window下的theme service实例 +}; diff --git a/packages/devui-theme/src/theme/media-query.ts b/packages/devui-theme/src/theme/media-query.ts new file mode 100644 index 0000000000..e159a33432 --- /dev/null +++ b/packages/devui-theme/src/theme/media-query.ts @@ -0,0 +1,54 @@ +import * as enquire from 'enquirejs-ssr'; +import { ReplaySubject } from 'rxjs'; + +export class PrefersColorSchemeMediaQuery { + static enquire = enquire; // prevent code optimization excluding enquire out + private prefersColorSchemeSubject = new ReplaySubject(1); + public prefersColorSchemeChange = this.prefersColorSchemeSubject.asObservable(); + + register() { + PrefersColorSchemeMediaQuery.enquire + .register.bind(enquire)(PrefersColorSchemeMediaQuery.Query.light, { + match: () => { + this.handleColorSchemeChange('light'); + } + }) + .register(PrefersColorSchemeMediaQuery.Query.dark, { + match: () => { + this.handleColorSchemeChange('dark'); + } + }); + this.prefersColorSchemeSubject.next(this.getInitValue()); + } + + unregister() { + PrefersColorSchemeMediaQuery.enquire + .unregister(PrefersColorSchemeMediaQuery.Query.light) + .unregister(PrefersColorSchemeMediaQuery.Query.dark); + this.prefersColorSchemeSubject.complete(); + } + + handleColorSchemeChange = (value: PrefersColorSchemeMediaQuery.Value) => { + this.prefersColorSchemeSubject.next(value); + }; + + getInitValue(): PrefersColorSchemeMediaQuery.Value { + if (typeof window === 'undefined') { + return 'light'; + } + + return window.matchMedia(PrefersColorSchemeMediaQuery.Query.light).matches && 'light' + || window.matchMedia(PrefersColorSchemeMediaQuery.Query.dark).matches && 'dark' + || 'no-preference'; + } +} + +/* eslint-disable-next-line @typescript-eslint/no-namespace */ +export namespace PrefersColorSchemeMediaQuery { + export type Value = 'light' | 'dark' | 'no-preference'; + export enum Query { + 'light' = 'screen and (prefers-color-scheme: light)', + 'dark' = 'screen and (prefers-color-scheme: dark)', + 'noPreferences' = 'screen and (prefers-color-scheme: light)', + } +} diff --git a/packages/devui-theme/src/theme/public-api.ts b/packages/devui-theme/src/theme/public-api.ts new file mode 100644 index 0000000000..e6396e2502 --- /dev/null +++ b/packages/devui-theme/src/theme/public-api.ts @@ -0,0 +1,5 @@ +export * from './theme'; +export * from './theme-management'; +export * from './theme-data'; +export * from './theme-service'; +export * from './utils/index'; diff --git a/packages/devui-theme/src/theme/theme-data.ts b/packages/devui-theme/src/theme/theme-data.ts new file mode 100644 index 0000000000..cd82585e08 --- /dev/null +++ b/packages/devui-theme/src/theme/theme-data.ts @@ -0,0 +1,324 @@ +import { Theme } from './theme'; +export const devuiLightTheme: Theme = new Theme({ + id: 'devui-light-theme', + name: 'Light Mode', + cnName: '浅色主题', + data: { + // 基础变量 + 'devui-global-bg': '#f3f6f8', + 'devui-global-bg-normal': '#ffffff', + 'devui-base-bg': '#ffffff', + 'devui-base-bg-dark': '#333854', + 'devui-brand': '#5e7ce0', + 'devui-brand-foil': '#859bff', + 'devui-brand-hover': '#7693f5', + 'devui-brand-active': '#526ecc', + 'devui-brand-active-focus': '#344899', + 'devui-contrast': '#c7000b', + 'devui-text': '#252b3a', + 'devui-text-weak': '#575d6c', + 'devui-aide-text': '#8a8e99', + 'devui-aide-text-stress': '#575d6c', + 'devui-placeholder': '#8a8e99', + 'devui-light-text': '#ffffff', + 'devui-dark-text': '#252b3a', + 'devui-link': '#526ecc', + 'devui-link-active': '#526ecc', + 'devui-link-light': '#96adfa', + 'devui-link-light-active': '#beccfa', + 'devui-line': '#adb0b8', + 'devui-dividing-line': '#dfe1e6', + 'devui-block': '#ffffff', + 'devui-area': '#f8f8f8', + 'devui-danger': '#f66f6a', + 'devui-warning': '#fac20a', + 'devui-waiting': '#9faad7', + 'devui-success': '#50d4ab', + 'devui-info': '#5e7ce0', + 'devui-initial': '#e9edfa', + 'devui-unavailable': '#f5f5f6', + 'devui-shadow': 'rgba(0, 0, 0, 0.2)', + 'devui-light-shadow': 'rgba(0, 0, 0, 0.1)', + // 图标 + 'devui-icon-text': '#252b3a', + 'devui-icon-bg': '#ffffff', + 'devui-icon-fill': '#d3d5d9', + 'devui-icon-fill-hover': '#adb5ce', + 'devui-icon-fill-active': '#5e7ce0', + 'devui-icon-fill-active-hover': '#526ecc', + // 表单 + 'devui-form-control-line': '#adb0b8', + 'devui-form-control-line-hover': '#575d6c', + 'devui-form-control-line-active': '#5e7ce0', + 'devui-form-control-line-active-hover': '#344899', + 'devui-list-item-active-bg': '#5e7ce0', + 'devui-list-item-active-text': '#ffffff', + 'devui-list-item-active-hover-bg': '#526ecc', + 'devui-list-item-hover-bg': '#f2f5fc', + 'devui-list-item-hover-text': '#526ecc', + 'devui-list-item-selected-bg': '#e9edfa', + 'devui-list-item-strip-bg': '#f2f5fc', + // 禁用 + 'devui-disabled-bg': '#f5f5f6', + 'devui-disabled-line': '#dfe1e6', + 'devui-disabled-text': '#adb0b8', + 'devui-primary-disabled': '#beccfa', + 'devui-icon-fill-active-disabled': '#beccfa', + // 特殊背景色 + 'devui-label-bg': '#eef0f5', + 'devui-connected-overlay-bg': '#ffffff', + 'devui-connected-overlay-line': '#526ecc', + 'devui-fullscreen-overlay-bg': '#ffffff', + 'devui-feedback-overlay-bg': '#464d6e', + 'devui-feedback-overlay-text': '#dfe1e6', + 'devui-embed-search-bg': '#f2f5fc', + 'devui-embed-search-bg-hover': '#eef0f5', + 'devui-float-block-shadow': 'rgba(94, 124, 224, 0.3)', + 'devui-highlight-overlay': 'rgba(255, 255, 255, 0.8)', + 'devui-range-item-hover-bg': '#e9edfa', + // 按钮 + 'devui-primary': '#5e7ce0', + 'devui-primary-hover': '#7693f5', + 'devui-primary-active': '#344899', + 'devui-contrast-hover': '#d64a52', + 'devui-contrast-active': '#b12220', + // 状态 + 'devui-danger-line': '#f66f6a', + 'devui-danger-bg': '#ffeeed', + 'devui-warning-line': '#fa9841', + 'devui-warning-bg': '#fff3e8', + 'devui-info-line': '#5e7ce0', + 'devui-info-bg': '#f2f5fc', + 'devui-success-line': '#50d4ab', + 'devui-success-bg': '#edfff9', + 'devui-primary-line': '#5e7ce0', + 'devui-primary-bg': '#f2f5fc', + 'devui-default-line': '#5e7ce0', + 'devui-default-bg': '#f3f6f8', + // 字体设置相关 + 'devui-font-size': '12px', + 'devui-font-size-card-title': '14px', + 'devui-font-size-page-title': '16px', + 'devui-font-size-modal-title': '18px', + 'devui-font-size-price': '20px', + 'devui-font-size-data-overview': '24px', + 'devui-font-size-icon': '16px', + 'devui-font-size-sm': '12px', + 'devui-font-size-md': '12px', + 'devui-font-size-lg': '14px', + 'devui-font-title-weight': 'bold', + 'devui-font-content-weight': 'normal', + 'devui-line-height-base': '1.5', + // 圆角 + 'devui-border-radius': '2px', + 'devui-border-radius-feedback': '4px', + 'devui-border-radius-card': '6px', + // 阴影 + 'devui-shadow-length-base': '0 1px 4px 0', + 'devui-shadow-length-slide-left': '-2px 0 8px 0', + 'devui-shadow-length-slide-right': '2px 0 8px 0', + 'devui-shadow-length-connected-overlay': '0 2px 8px 0', + 'devui-shadow-length-hover': '0 4px 16px 0', + 'devui-shadow-length-feedback-overlay': '0 4px 16px 0', + 'devui-shadow-fullscreen-overlay': '0 8px 40px 0', + // 动效 + 'devui-animation-duration-slow': '300ms', + 'devui-animation-duration-base': '200ms', + 'devui-animation-duration-fast': '100ms', + 'devui-animation-ease-in': 'cubic-bezier(0.5, 0, 0.84, 0.25)', + 'devui-animation-ease-out': 'cubic-bezier(0.16, 0.75, 0.5, 1)', + 'devui-animation-ease-in-out': 'cubic-bezier(0.5, 0.05, 0.5, 0.95)', + 'devui-animation-ease-in-out-smooth': 'cubic-bezier(0.645, 0.045, 0.355, 1)', + 'devui-animation-linear': 'cubic-bezier(0, 0, 1, 1)', + // zIndex + 'devui-z-index-full-page-overlay': '1080', + 'devui-z-index-pop-up': '1060', + 'devui-z-index-dropdown': '1052', + 'devui-z-index-modal': '1050', + 'devui-z-index-drawer': '1040', + 'devui-z-index-framework': '1000' + }, + isDark: false, +}); +export const devuiGreenTheme: Theme = new Theme({ + id: 'devui-green-theme', + name: 'Green - Light Mode', + cnName: '绿色主题', + data: { ...devuiLightTheme.data, 'devui-global-bg': '#f3f8f7', + 'devui-brand': '#3DCCA6', + 'devui-brand-foil': '#7fdac1', + 'devui-brand-hover': '#6DDEBB', + 'devui-brand-active': '#07c693', + 'devui-brand-active-focus': '#369676', + 'devui-link': '#07c693', + 'devui-link-active': '#07c693', + 'devui-link-light': '#96fac8', + 'devui-link-light-active': '#befade', + 'devui-info': '#079CCD', + 'devui-initial': '#CCCCCC', + 'devui-icon-fill-active': '#3DCCA6', + 'devui-icon-fill-active-hover': '#07c693', + 'devui-form-control-line-active': '#3DCCA6', + 'devui-form-control-line-active-hover': '#2EB28A', + 'devui-list-item-active-bg': '#3DCCA6', + 'devui-list-item-active-hover-bg': '#07c693', + 'devui-list-item-hover-bg': '#f3fef9', + 'devui-list-item-hover-text': '#07c693', + 'devui-list-item-selected-bg': '#f3fef9', + 'devui-list-item-strip-bg': '#f3fef9', + 'devui-connected-overlay-line': '#07c693', + 'devui-embed-search-bg': '#f3fef9', + 'devui-float-block-shadow': 'rgba(94, 224, 181, 0.3)', + 'devui-primary': '#3DCCA6', + 'devui-primary-hover': '#6DDEBB', + 'devui-primary-active': '#369676', + 'devui-info-line': '#0486b1', + 'devui-info-bg': '#e3f0f5', + 'devui-success-line': '#50d492', + 'devui-success-bg': '#edfff9', + 'devui-primary-line': '#3DCCA6', + 'devui-primary-bg': '#f3fef9', + 'devui-default-line': '#3DCCA6', + 'devui-default-bg': '#f3f8f7', + 'devui-primary-disabled': '#c5f0e5', + 'devui-icon-fill-active-disabled': '#c5f0e5', + 'devui-range-item-hover-bg': '#d8f9ea',}, + extends: 'devui-light-theme', + isDark: false, +}); +export const devuiDarkTheme: Theme = new Theme({ + id: 'devui-dark-theme', + name: 'Dark Mode', + cnName: '深色主题', + data: { + 'devui-global-bg': '#202124', + 'devui-global-bg-normal': '#202124', + 'devui-base-bg': '#2E2F31', + 'devui-base-bg-dark': '#2e2f31', + 'devui-brand': '#5e7ce0', + 'devui-brand-foil': '#313a61', + 'devui-brand-hover': '#425288', + 'devui-brand-active': '#526ecc', + 'devui-brand-active-focus': '#344899', + 'devui-contrast': '#C7000B', + 'devui-text': '#E8E8E8', + 'devui-text-weak': '#A0A0A0', + 'devui-aide-text': '#909090', + 'devui-aide-text-stress': '#A0A0A0', + 'devui-placeholder': '#8A8A8A', + 'devui-light-text': '#ffffff', + 'devui-dark-text': '#252b3a', + 'devui-link': '#526ECC', + 'devui-link-active': '#344899', + 'devui-link-light': '#96adfa', + 'devui-link-light-active': '#beccfa', + 'devui-line': '#505153', + 'devui-dividing-line': '#3D3E40', + 'devui-block': '#606061', + 'devui-area': '#34363A', + 'devui-danger': '#f66f6a', + 'devui-warning': '#fac20a', + 'devui-waiting': '#5e6580', + 'devui-success': '#50d4ab', + 'devui-info': '#5e7ce0', + 'devui-initial': '#64676e', + 'devui-unavailable': '#5b5b5c', + 'devui-shadow': 'rgba(17, 18, 19, 0.4)', + 'devui-light-shadow': 'rgba(17, 18, 19, 0.5)', + // 图标 + 'devui-icon-text': '#E8E8E8', + 'devui-icon-bg': '#2E2F31', + 'devui-icon-fill': '#606061', + 'devui-icon-fill-hover': '#73788a', + 'devui-icon-fill-active': '#5e7ce0', + 'devui-icon-fill-active-hover': '#526ecc', + // 表单 + 'devui-form-control-line': '#505153', + 'devui-form-control-line-hover': '#909090', + 'devui-form-control-line-active': '#5e7ce0', + 'devui-form-control-line-active-hover': '#344899', + 'devui-list-item-active-bg': '#5e7ce0', + 'devui-list-item-active-text': '#ffffff', + 'devui-list-item-active-hover-bg': '#526ecc', + 'devui-list-item-hover-bg': '#383838', + 'devui-list-item-hover-text': '#526ecc', + 'devui-list-item-selected-bg': '#454545', + 'devui-list-item-strip-bg': '#383838', + // 禁用 + 'devui-disabled-bg': '#3D3E44', + 'devui-disabled-line': '#505153', + 'devui-disabled-text': '#7D7D7D', + 'devui-primary-disabled': '#2B3458', + 'devui-icon-fill-active-disabled': '#2B3458', + // 特殊背景色 + 'devui-label-bg': '#46443F', + 'devui-connected-overlay-bg': '#2F2F2F', + 'devui-connected-overlay-line': '#526ecc', + 'devui-fullscreen-overlay-bg': '#2E2F31', + 'devui-feedback-overlay-bg': '#4C4C4C', + 'devui-feedback-overlay-text': '#DFE1E6', + 'devui-embed-search-bg': '#383838', + 'devui-embed-search-bg-hover': '#3D3E40', + 'devui-float-block-shadow': 'rgba(94, 124, 224, 0.3)', + 'devui-highlight-overlay': 'rgba(255, 255, 255, 0.1)', + 'devui-range-item-hover-bg': '#454545', + // 按钮 + 'devui-primary': '#5e7ce0', + 'devui-primary-hover': '#425288', + 'devui-primary-active': '#344899', + 'devui-contrast-hover': '#D64A52', + 'devui-contrast-active': '#B12220', + // 状态 + 'devui-danger-line': '#985C5A', + 'devui-danger-bg': '#4B3A39', + 'devui-warning-line': '#8D6138', + 'devui-warning-bg': '#554434', + 'devui-info-line': '#546BB7', + 'devui-info-bg': '#383D4F', + 'devui-success-line': '#5D887D', + 'devui-success-bg': '#304642', + 'devui-primary-line': '#546BB7', + 'devui-primary-bg': '#383D4F', + 'devui-default-line': '#5e7ce0', + 'devui-default-bg': '#383838', + }, + extends: 'devui-light-theme', + isDark: true, +}); +export const devuiGreenDarkTheme: Theme = new Theme({ + id: 'devui-green-dark-theme', + name: 'Green - Dark Mode', + cnName: '绿色深色主题', + data: { ...devuiDarkTheme.data, 'devui-brand': '#3DCCA6', + 'devui-brand-foil': '#395e54', + 'devui-brand-hover': '#4c9780', + 'devui-brand-active': '#07c693', + 'devui-brand-active-focus': '#297058', + 'devui-link': '#07c693', + 'devui-link-active': '#08a57b', + 'devui-info': '#046788', + 'devui-initial': '#64676e', + 'devui-icon-fill-active': '#3DCCA6', + 'devui-icon-fill-active-hover': '#07c693', + 'devui-form-control-line-active': '#3DCCA6', + 'devui-form-control-line-active-hover': '#297058', + 'devui-list-item-active-bg': '#3DCCA6', + 'devui-list-item-active-hover-bg': '#07c693', + 'devui-list-item-hover-text': '#07c693', + 'devui-connected-overlay-line': '#07c693', + 'devui-embed-search-bg': '#3f4241', + 'devui-float-block-shadow': 'rgba(94, 224, 181, 0.3)', + 'devui-primary': '#3DCCA6', + 'devui-primary-hover': '#6DDEBB', + 'devui-primary-active': '#369676', + 'devui-info-line': '#035e7c', + 'devui-info-bg': '#383c3d', + 'devui-primary-line': '#3DCCA6', + 'devui-primary-bg': '#3f4241', + 'devui-default-line': '#3DCCA6', + 'devui-default-bg': '#383838', + 'devui-primary-disabled': '#28544B', + 'devui-icon-fill-active-disabled': '#28544B',}, + extends: 'devui-dark-theme', + isDark: true, +}); diff --git a/packages/devui-theme/src/theme/theme-management.ts b/packages/devui-theme/src/theme/theme-management.ts new file mode 100644 index 0000000000..2797888239 --- /dev/null +++ b/packages/devui-theme/src/theme/theme-management.ts @@ -0,0 +1,105 @@ +import cssVars from 'css-vars-ponyfill'; +import { Subscription } from 'rxjs'; +import { THEME_KEY } from './key-config'; +import { Theme } from './theme'; +import { devuiDarkTheme, devuiLightTheme } from './theme-data'; +import { ThemeService } from './theme-service'; +import { EventBus } from './utils'; + +/** + * usage + * main.ts + ```ts + import { ThemeServiceInit } from 'devui-theme'; + ThemeServiceInit(); + ``` + * +*/ +export function ThemeServiceInit( + themes?: { [themeName: string]: Theme }, + defaultThemeName?: string, + extraData?: { + [themeName: string]: { + appendClasses?: Array; + cssVariables?: { + [cssVarName: string]: string; + }; + }; + }, + ieSupport = false, // TODO:css-var-ponyflll 仍有一些问题待定位 + allowDynamicTheme = false +) { + if (typeof window === 'undefined') { + return null; + } + + window[THEME_KEY.themeCollection] = themes || { + 'devui-light-theme': devuiLightTheme, + 'devui-dark-theme': devuiDarkTheme, + }; + window[THEME_KEY.currentTheme] = defaultThemeName || 'devui-light-theme'; + const eventBus = window['globalEventBus'] || new EventBus(); // window.globalEventBus 为 框架的事件总线 + const themeService = new ThemeService(eventBus); + window[THEME_KEY.themeService] = themeService; + + themeService.setExtraData(extraData || { + 'devui-dark-theme': { + appendClasses: ['dark-mode'] + } + }); + themeService.initializeTheme(null, allowDynamicTheme); + if (ieSupport) { + ieSupportCssVar(); + } + return themeService; +} + +export function ThemeServiceFollowSystemOn(themeConfig?: { lightThemeName: string; darkThemeName: string }): Subscription { + if (typeof window === 'undefined') { + return null; + } + + const themeService: ThemeService = window[THEME_KEY.themeService]; + themeService.registerMediaQuery(); + return themeService.mediaQuery.prefersColorSchemeChange.subscribe(value => { + if (value === 'dark') { + themeService.applyTheme(window[THEME_KEY.themeCollection][themeConfig && themeConfig.darkThemeName || 'devui-dark-theme']); + } else { + themeService.applyTheme(window[THEME_KEY.themeCollection][themeConfig && themeConfig.lightThemeName || 'devui-light-theme']); + } + }); +} +export function ThemeServiceFollowSystemOff(sub?: Subscription) { + if (typeof window === 'undefined') { + return null; + } + + if (sub) { + sub.unsubscribe(); + } + const themeService = window[THEME_KEY.themeService]; + themeService.unregisterMediaQuery(); +} + +export function ieSupportCssVar() { + if (typeof window === 'undefined') { + return null; + } + + const isNativeSupport = window['CSS'] && CSS.supports && CSS.supports('(--a: 0)') || false; + if (isNativeSupport) { return; } + cssVars({ watch: true, silent: true }); + const observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + cssVars({ watch: false, silent: true }); + cssVars({ watch: true, silent: true }); + }); + }); + + const config = { attributes: true, attributeFilter: [THEME_KEY.uiThemeAttributeName] }; + + observer.observe(document.querySelector(`#${THEME_KEY.styleElementId}`), config); +} + +// TODO: management should handle add / remove theme from theme collection. +// TODO: move global variables(window.xxxx) to single namespace diff --git a/packages/devui-theme/src/theme/theme-service.ts b/packages/devui-theme/src/theme/theme-service.ts new file mode 100644 index 0000000000..28fd31d7a1 --- /dev/null +++ b/packages/devui-theme/src/theme/theme-service.ts @@ -0,0 +1,189 @@ +import { THEME_KEY } from './key-config'; +import { PrefersColorSchemeMediaQuery } from './media-query'; +import { Theme } from './theme'; +import { ContextService, EventBus, IContextService, IEventBus, IStorageService, StorageService } from './utils/index'; + +/** + * 负责CSS变量主题的装卸,主题元数据转换成主题数据 + */ +export class ThemeService { + eventBus: IEventBus; + storage: IStorageService; + context: IContextService; + currentTheme: Theme; + contentElement: HTMLStyleElement; + colorTransitionElement: HTMLStyleElement; + extraData: { + [themeId: string]: { + cssVariables?: { + [varname: string]: string; + }; + appendClasses?: Array; + }; + }; + private _appendedClasses: Array; + set appendClasses(classes: Array) { + if (this._appendedClasses) { + this.removeAppendedClass(this._appendedClasses); + } + if (classes) { + this.addAppendClass(classes); + } + this._appendedClasses = classes; + } + + get appendClasses() { + return this._appendedClasses; + } + + public mediaQuery: PrefersColorSchemeMediaQuery; + + constructor(eventBus?: IEventBus, storage?: IStorageService, context?: IContextService) { + this.eventBus = eventBus === undefined ? new EventBus() : eventBus; + this.storage = storage === undefined ? new StorageService() : storage; + this.context = context === undefined ? new ContextService() : context; + } + + initializeTheme(specificThemeId?: string, allowDynamicTheme?: boolean) { + const themeId = specificThemeId + || this.storage.tryGetLocalStorage(THEME_KEY.userLastPreferTheme) + || this.context.getDataFromNameSpace(THEME_KEY.currentTheme); + let theme; + + if (themeId) { + const themes = this.context.getDataFromNameSpace(THEME_KEY.themeCollection); + if (themes && Object.keys(themes).length > 0) { + theme = themes[themeId]; + } + } + this.currentTheme = theme || { + id: 'empty-theme', + name: '', + data: {} + }; + this.createColorTransition(); + if (!theme && allowDynamicTheme) { + return; + } + this.applyTheme(this.currentTheme); + } + + formatCSSVariables(themeData: Theme['data']) { + return Object.keys(themeData).map( + cssVar => ('--' + cssVar + ':' + themeData[cssVar]) + ).join(';'); + } + + applyTheme(theme: Theme) { + this.addColorTransition(); + this.currentTheme = theme; + if (!this.contentElement) { + const styleElement = document.getElementById(THEME_KEY.styleElementId); + if (styleElement) { + this.contentElement = styleElement; + } else { + this.contentElement = document.createElement('style'); + this.contentElement.id = THEME_KEY.styleElementId; + document.head.appendChild(this.contentElement); + } + + } + this.contentElement.innerText = ':root { ' + this.formatCSSVariables(theme.data) + ' }'; + this.contentElement.setAttribute(THEME_KEY.uiThemeAttributeName, this.currentTheme.id); + document.body.setAttribute(THEME_KEY.uiThemeAttributeName, this.currentTheme.id); + + // 用于挂载额外变量和类名 + this.applyExtraData(); + this.saveCustomTheme(this.currentTheme); + + // 通知外部主题变更 + this.notify(theme, 'themeChanged'); + setTimeout(() => {this.removeColorTransition(); }, 500); + } + + saveCustomTheme(customTheme: Theme) { + this.storage.trySetLocalStorage(THEME_KEY.userLastPreferTheme, customTheme.id); + this.storage.trySetLocalStorage(THEME_KEY.userLastPreferThemeData, JSON.stringify(customTheme.data)); + this.context.setDataFromNameSpace(THEME_KEY.currentTheme, customTheme.id); + } + + private notify(theme: Theme, eventType: string) { + if (!this.eventBus) { return; } + this.eventBus.trigger(eventType, theme); + } + + setEventBus(eb: IEventBus) { + this.eventBus = eb; + } + + private addAppendClass(classNames: Array) { + document.body.classList.add(...classNames); + } + + private removeAppendedClass(classNames: Array) { + document.body.classList.remove(...classNames); + } + + setExtraData(data, apply = false) { + this.extraData = data; + if (apply) { + this.applyExtraData(); + } + } + + private applyExtraData() { + const theme = this.currentTheme; + if (this.extraData && this.extraData[theme.id] && this.extraData[theme.id].cssVariables) { + this.contentElement.innerText + = ':root { ' + this.formatCSSVariables(theme.data) + ' }' + + ':root { ' + this.formatCSSVariables(this.extraData[theme.id].cssVariables) + ' }'; + } + if (this.extraData && this.extraData[theme.id] && this.extraData[theme.id].appendClasses) { + this.appendClasses = this.extraData[theme.id].appendClasses; + } else { + this.appendClasses = undefined; + } + } + + public unloadTheme() { + if (this.contentElement && document.contains(this.contentElement)) { + this.contentElement.parentElement.removeChild(this.contentElement); + } + if (this.appendClasses) { + this.appendClasses = undefined; + } + } + + public registerMediaQuery() { + if (!this.mediaQuery) { + this.mediaQuery = new PrefersColorSchemeMediaQuery(); + } + this.mediaQuery.register(); + } + + public unregisterMediaQuery() { + if (!this.mediaQuery) { + return; + } + this.mediaQuery.unregister(); + this.mediaQuery = undefined; + } + + private createColorTransition() { + this.colorTransitionElement = document.createElement('style'); + this.colorTransitionElement.id = THEME_KEY.transitionStyleElementId; + this.colorTransitionElement.innerText = ` + * { transition: background .3s ease-out, background-color .3s ease-out, + border .3s ease-out, border-color .3s ease-out, + box-shadow .3s ease-out, box-shadow-color .3s ease-out} + `; + } + + private addColorTransition() { + document.head.appendChild(this.colorTransitionElement); + } + private removeColorTransition() { + if (!this.colorTransitionElement.parentElement) {return; } + this.colorTransitionElement.parentElement.removeChild(this.colorTransitionElement); + } +} diff --git a/packages/devui-theme/src/theme/theme.ts b/packages/devui-theme/src/theme/theme.ts new file mode 100644 index 0000000000..753dbe9cb8 --- /dev/null +++ b/packages/devui-theme/src/theme/theme.ts @@ -0,0 +1,43 @@ +export type ThemeId = string; + +export class Theme { + id: ThemeId; + name: string; + cnName?: string; + data: { + [cssVarName: string]: string; + }; + extends?: ThemeId; + isDark?: boolean; + isPreview?: boolean; + isExtendable?: boolean; + extra?: { + appendClass?: Array; + cssVariables?: { + [cssVarName: string]: string; + }; + [prop: string]: any; + } | any; + + constructor(theme: { + id: ThemeId; + name: string; + cnName?: string; + data: { + [cssVarName: string]: string; + }; + extends?: ThemeId; + isDark?: boolean; + isPreview?: boolean; + isExtendable?: boolean; + }) { + this.id = theme.id; + this.name = theme.name; + this.cnName = theme.cnName || this.name; + this.data = theme.data; + this.extends = theme.extends || null; + this.isDark = theme.isDark || undefined; + this.isPreview = theme.isPreview || false; + this.isExtendable = theme.isExtendable || true; + } +} diff --git a/packages/devui-theme/src/theme/utils/context-service.ts b/packages/devui-theme/src/theme/utils/context-service.ts new file mode 100644 index 0000000000..577f966843 --- /dev/null +++ b/packages/devui-theme/src/theme/utils/context-service.ts @@ -0,0 +1,17 @@ +import { IContextService } from './interface'; +export class ContextService implements IContextService { + getDataFromNameSpace(nameSpace: string) { + if (typeof window === 'undefined') { + return null; + } + + return window[nameSpace]; + } + setDataFromNameSpace(nameSpace: string, value: any) { + if (typeof window === 'undefined') { + return; + } + + window[nameSpace] = value; + } +} diff --git a/packages/devui-theme/src/theme/utils/event-bus.ts b/packages/devui-theme/src/theme/utils/event-bus.ts new file mode 100644 index 0000000000..0eb9218e4f --- /dev/null +++ b/packages/devui-theme/src/theme/utils/event-bus.ts @@ -0,0 +1,87 @@ +import { IEventBus } from './interface'; + +export class EventBus implements IEventBus { + private eventBusCore = []; + private areFuncEqual(a, b) { + return a.toString() === b.toString(); + } + private isKeyValueObjInArr(arr, key, val) { + const filteredArr = arr.filter(entry => { + return entry[key] === val; + }); + return filteredArr.length > 0; + } + private removeFuncInFuncArr(arr, fn) { + for (let z = 0; z < arr.length; z++) { + if (this.areFuncEqual(arr[z], fn)) { + arr.splice(z, 1); + } + } + return arr; + } + private getKeyValueObjInArr(arr, key, val) { + const filteredArr = arr.filter(entry => { + return entry[key] === val; + }); + return filteredArr[0]; + } + private addEvent(eventName, eventFunc) { + if (!this.isKeyValueObjInArr(this.eventBusCore, 'eventName', eventName)) { + this.eventBusCore.push({ eventName: eventName, eventFuncArr: [eventFunc] }); + } else { + this.eventBusCore = this.eventBusCore.map(event => { + if (event['eventName'] === eventName) { + event.eventFuncArr.push(eventFunc); + } + return event; + }); + } + } + public add(eventName, callbacks) { + if (!eventName) { + return; + } + if (typeof callbacks === 'function') { + for (let i = 1; i < arguments.length; i++) { + this.addEvent(eventName, arguments[i]); + } + } + if (typeof callbacks === 'object' && callbacks.forEach) { + callbacks.forEach(fn => { + this.addEvent(eventName, fn); + }); + } + } + public remove(eventName, callbacks) { + if (!eventName) { + return; + } + for (let i = 0; i < this.eventBusCore.length; i++) { + if (this.eventBusCore[i].eventName === eventName) { + if (arguments.length === 1) { + return this.eventBusCore.splice(i, 1); + } + const removedEvent = this.eventBusCore.splice(i, 1)[0]; + if (typeof callbacks === 'function') { + for (let k = 1; k < arguments.length; k++) { + removedEvent.eventFuncArr = this.removeFuncInFuncArr(removedEvent.eventFuncArr, arguments[k]); + } + } + if (typeof callbacks === 'object' && callbacks.length) { + for (let x = 0; x < callbacks.length; x++) { + removedEvent.eventFuncArr = this.removeFuncInFuncArr(removedEvent.eventFuncArr, callbacks[x]); + } + } + this.eventBusCore.push(removedEvent); + } + } + } + public trigger(eventName, data) { + const event = this.getKeyValueObjInArr(this.eventBusCore, 'eventName', eventName); + if (event) { + (event.eventFuncArr || []).forEach(fn => { + fn.apply(this, data); + }); + } + } +} diff --git a/packages/devui-theme/src/theme/utils/index.ts b/packages/devui-theme/src/theme/utils/index.ts new file mode 100644 index 0000000000..7c70488766 --- /dev/null +++ b/packages/devui-theme/src/theme/utils/index.ts @@ -0,0 +1,4 @@ +export * from './interface'; +export * from './event-bus'; +export * from './context-service'; +export * from './storage-service'; diff --git a/packages/devui-theme/src/theme/utils/interface.ts b/packages/devui-theme/src/theme/utils/interface.ts new file mode 100644 index 0000000000..0c14534949 --- /dev/null +++ b/packages/devui-theme/src/theme/utils/interface.ts @@ -0,0 +1,15 @@ +export interface IStorageService { + tryGetLocalStorage(key: string): any; + trySetLocalStorage(key: string, value: any): void; +} + +export interface IContextService { + getDataFromNameSpace(nameSpace: string): any; + setDataFromNameSpace(nameSpace: string, value: any): any; +} + +export interface IEventBus { + add(eventName: string, callbacks: Function): void; + remove(eventName: string, callbacks: Function): void; + trigger(eventName: string, data: any): void; +} diff --git a/packages/devui-theme/src/theme/utils/storage-service.ts b/packages/devui-theme/src/theme/utils/storage-service.ts new file mode 100644 index 0000000000..773c704069 --- /dev/null +++ b/packages/devui-theme/src/theme/utils/storage-service.ts @@ -0,0 +1,17 @@ +import { IStorageService } from './interface'; +export class StorageService implements IStorageService { + tryGetLocalStorage(key: string) { + if (typeof window === 'undefined') { + return null; + } + + return window.localStorage.getItem(key); + } + trySetLocalStorage(key: string, value: any) { + if (typeof window === 'undefined') { + return; + } + + window.localStorage.setItem(key, value); + } +} diff --git a/packages/devui-theme/tsconfig.json b/packages/devui-theme/tsconfig.json new file mode 100644 index 0000000000..af31eb8152 --- /dev/null +++ b/packages/devui-theme/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"] + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/devui-theme/tsconfig.node.json b/packages/devui-theme/tsconfig.node.json new file mode 100644 index 0000000000..e993792cb1 --- /dev/null +++ b/packages/devui-theme/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "esnext", + "moduleResolution": "node" + }, + "include": ["vite.config.ts"] +} diff --git a/packages/devui-theme/vite.config.ts b/packages/devui-theme/vite.config.ts new file mode 100644 index 0000000000..d89419e5fb --- /dev/null +++ b/packages/devui-theme/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' + +// https://vitejs.dev/config/ +export default defineConfig({ + publicDir: false, + build: { + lib: { + entry: resolve(__dirname, 'src/index.ts'), + name: 'DevuiTheme', + fileName: (format) => `index.${format}.js` + }, + rollupOptions: { + // 确保外部化处理那些你不想打包进库的依赖 + external: ['vue'], + output: { + // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量 + globals: { + vue: 'Vue' + } + } + } + } +}) diff --git a/packages/devui-vue/docs/.vitepress/devui-theme/components/NavBar.vue b/packages/devui-vue/docs/.vitepress/devui-theme/components/NavBar.vue index a9599ff500..167ed87d8a 100644 --- a/packages/devui-vue/docs/.vitepress/devui-theme/components/NavBar.vue +++ b/packages/devui-vue/docs/.vitepress/devui-theme/components/NavBar.vue @@ -1,6 +1,6 @@