feat(theme): 重构主题系统,新增 glass-pill 按钮样式

- ThemeConfig 接口扩展至 60+ 语义化属性
- 新增深浅主题预设 (glassPill overrides)
- button-group 支持 glass-pill 样式变体
- 默认主题改为浅色
- 移除 toolbar 容器硬编码定位
- 统一组件 CSS 变量命名规范
- 暂时隐藏下拉箭头
This commit is contained in:
yuding
2026-01-21 15:50:07 +08:00
parent 8d027419e4
commit 19f7e3ffbc
34 changed files with 6840 additions and 4706 deletions

View File

@@ -3,22 +3,15 @@
width: 100%;
height: 100%;
font-family: sans-serif;
color: #bf1d1d;
box-sizing: border-box;
overflow: hidden;
/* 防止内容溢出 */
background: linear-gradient(to bottom, rgb(214, 224, 235), rgb(246, 250, 255));
}
/* ... (中间代码不变) ... */
/* 操作按钮组容器 */
.bim-engine-opt-btn-container {
position: absolute;
/* 改为绝对定位 */
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
}

View File

@@ -124,7 +124,6 @@ export class BimEngine extends EventEmitter {
private updateTheme(theme: ThemeConfig) {
if (this.wrapper) {
this.wrapper.style.backgroundColor = theme.background;
this.wrapper.style.color = theme.textPrimary;
}
}

View File

@@ -306,4 +306,202 @@
.bim-btn-group-root.is-bottom-toolbar .opt-btn {
padding: 8px;
}
/* ========== type-glass-pill: 胶囊容器 + 毛玻璃 + 圆形按钮 ========== */
/* 使用主题系统 CSS 变量,支持深色/浅色主题切换 */
.bim-btn-group-root.type-glass-pill .bim-btn-group-section {
background: var(--bim-glass-pill-section-bg);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid var(--bim-glass-pill-section-border);
border-radius: 9999px;
box-shadow: var(--bim-glass-pill-section-shadow);
padding: 8px;
gap: 10px;
}
.bim-btn-group-root.type-glass-pill .opt-btn {
width: 48px;
height: 48px;
min-width: 48px;
border-radius: 9999px;
background: var(--bim-glass-pill-btn-bg);
border: 1px solid var(--bim-glass-pill-btn-border);
box-shadow: var(--bim-glass-pill-btn-shadow);
color: var(--bim-text-primary);
padding: 0;
transition: all 200ms ease-out;
}
.bim-btn-group-root.type-glass-pill .opt-btn:hover {
background: var(--bim-glass-pill-btn-bg-hover);
transform: scale(1.05);
box-shadow: var(--bim-glass-pill-btn-shadow-hover);
}
.bim-btn-group-root.type-glass-pill .opt-btn:active {
transform: scale(0.98);
}
.bim-btn-group-root.type-glass-pill .opt-btn.active {
background: var(--bim-primary);
border: 1px solid var(--bim-primary-active);
color: var(--bim-text-inverse);
box-shadow: var(--bim-shadow-glow);
}
.bim-btn-group-root.type-glass-pill .opt-btn.active:hover {
background: var(--bim-primary-hover);
transform: scale(1.05);
}
.bim-btn-group-root.type-glass-pill .opt-btn.active:active {
transform: scale(0.98);
}
.bim-btn-group-root.type-glass-pill .opt-btn.active .opt-btn-icon {
color: var(--bim-icon-inverse);
}
.bim-btn-group-root.type-glass-pill .opt-btn.disabled {
opacity: 0.3;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.bim-btn-group-root.type-glass-pill .opt-btn-icon {
width: 18px;
height: 18px;
color: var(--bim-glass-pill-icon-color);
transition: color 200ms ease-out;
}
.bim-btn-group-root.type-glass-pill .opt-btn-icon svg {
width: 18px;
height: 18px;
}
.bim-btn-group-root.type-glass-pill .opt-btn:hover .opt-btn-icon {
color: var(--bim-glass-pill-icon-color-hover);
}
.bim-btn-group-root.type-glass-pill .opt-btn-label {
display: none !important;
}
.bim-btn-group-root.type-glass-pill .opt-btn-text-wrapper {
position: absolute;
top: 2px;
right: 2px;
width: 0;
height: 0;
margin: 0;
padding: 0;
}
.bim-btn-group-root.type-glass-pill .opt-btn-arrow {
font-size: 8px;
position: absolute;
top: 0;
right: 0;
color: var(--bim-text-tertiary);
}
/* glass-pill 二级菜单 - 浅色主题 */
.opt-btn-dropdown.type-glass-pill {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(226, 232, 240, 0.6);
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
padding: 6px;
}
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
border-radius: 8px;
color: rgb(51, 65, 85);
padding: 8px 12px;
font-size: 13px;
font-weight: 400;
transition: all 100ms ease-out;
}
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:hover {
background: rgb(241, 245, 249);
}
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:active {
background: rgb(226, 232, 240);
}
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item.active {
background: rgb(59, 130, 246);
color: white;
}
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item.active .opt-btn-icon {
color: white;
}
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item.active .opt-btn-dropdown-label {
color: white;
}
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon {
width: 18px;
height: 18px;
color: rgb(51, 65, 85);
flex-shrink: 0;
}
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon svg {
width: 18px;
height: 18px;
}
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-dropdown-label {
white-space: nowrap;
color: rgb(51, 65, 85);
}
/* glass-pill 二级菜单 - 深色主题 */
.opt-btn-dropdown.type-glass-pill.theme-dark {
background: rgba(21, 34, 50, 0.98);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(51, 65, 85, 0.5);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item {
color: #ffffff;
}
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:hover {
background: #1f2d3e;
}
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:active {
background: #2a3f54;
}
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item.active {
background: rgb(59, 130, 246);
color: white;
}
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-icon {
color: #ffffff;
}
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-dropdown-label {
color: #ffffff;
}

View File

@@ -93,6 +93,10 @@ export class BimButtonGroup implements IBimComponent {
this.container.classList.add(this.options.className);
}
if (this.options.type && this.options.type !== 'default') {
this.container.classList.add(`type-${this.options.type}`);
}
this.updatePosition();
// 添加事件拦截,防止点击穿透到 3D 引擎
@@ -217,17 +221,16 @@ export class BimButtonGroup implements IBimComponent {
*/
public setTheme(theme: ThemeConfig): void {
const themeColors: ButtonGroupColors = {
backgroundColor: theme.panelBackground,
btnBackgroundColor: theme.componentBackground,
btnHoverColor: theme.componentHover,
btnActiveColor: theme.componentActive,
iconColor: theme.icon,
backgroundColor: theme.bgElevated,
btnBackgroundColor: theme.componentBg,
btnHoverColor: theme.componentBgHover,
btnActiveColor: theme.componentBgActive,
iconColor: theme.iconDefault,
iconActiveColor: theme.iconActive,
textColor: theme.textSecondary,
textActiveColor: theme.textPrimary
};
// 只应用没有被自定义的颜色
Object.entries(themeColors).forEach(([key, value]) => {
const colorKey = key as keyof ButtonGroupColors;
if (!this.customColors.has(colorKey)) {
@@ -235,8 +238,90 @@ export class BimButtonGroup implements IBimComponent {
}
});
this.container.classList.remove('theme-dark', 'theme-light');
this.container.classList.add(`theme-${theme.name}`);
this.applyStyles();
this.setPrimaryColor(theme.primary);
this.applyThemeCssVars(theme);
}
/**
* 应用主题系统的 CSS 变量到容器
* 供 glass-pill 等样式变体使用
*/
private applyThemeCssVars(theme: ThemeConfig): void {
const style = this.container.style;
style.setProperty('--bim-primary', theme.primary);
style.setProperty('--bim-primary-hover', theme.primaryHover);
style.setProperty('--bim-primary-active', theme.primaryActive);
style.setProperty('--bim-bg-glass', theme.bgGlass);
style.setProperty('--bim-bg-glass-blur', theme.bgGlassBlur);
style.setProperty('--bim-bg-elevated', theme.bgElevated);
style.setProperty('--bim-bg-overlay', theme.bgOverlay);
style.setProperty('--bim-bg-inset', theme.bgInset);
style.setProperty('--bim-text-primary', theme.textPrimary);
style.setProperty('--bim-text-secondary', theme.textSecondary);
style.setProperty('--bim-text-tertiary', theme.textTertiary);
style.setProperty('--bim-text-inverse', theme.textInverse);
style.setProperty('--bim-icon-default', theme.iconDefault);
style.setProperty('--bim-icon-hover', theme.iconHover);
style.setProperty('--bim-icon-active', theme.iconActive);
style.setProperty('--bim-icon-inverse', theme.iconInverse);
style.setProperty('--bim-border-default', theme.borderDefault);
style.setProperty('--bim-border-subtle', theme.borderSubtle);
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
style.setProperty('--bim-component-bg-active', theme.componentBgActive);
style.setProperty('--bim-shadow-sm', theme.shadowSm);
style.setProperty('--bim-shadow-md', theme.shadowMd);
style.setProperty('--bim-shadow-lg', theme.shadowLg);
style.setProperty('--bim-shadow-glow', theme.shadowGlow);
const gp = theme.overrides?.glassPill;
if (gp) {
if (gp.sectionBg) style.setProperty('--bim-glass-pill-section-bg', gp.sectionBg);
if (gp.sectionBorder) style.setProperty('--bim-glass-pill-section-border', gp.sectionBorder);
if (gp.sectionShadow) style.setProperty('--bim-glass-pill-section-shadow', gp.sectionShadow);
if (gp.btnBg) style.setProperty('--bim-glass-pill-btn-bg', gp.btnBg);
if (gp.btnBorder) style.setProperty('--bim-glass-pill-btn-border', gp.btnBorder);
if (gp.btnShadow) style.setProperty('--bim-glass-pill-btn-shadow', gp.btnShadow);
if (gp.btnBgHover) style.setProperty('--bim-glass-pill-btn-bg-hover', gp.btnBgHover);
if (gp.btnShadowHover) style.setProperty('--bim-glass-pill-btn-shadow-hover', gp.btnShadowHover);
if (gp.iconColor) style.setProperty('--bim-glass-pill-icon-color', gp.iconColor);
if (gp.iconColorHover) style.setProperty('--bim-glass-pill-icon-color-hover', gp.iconColorHover);
}
this.syncDropdownCssVars(theme);
}
/**
* 同步 CSS 变量到所有 dropdown 元素
* dropdown 被添加到 body无法继承容器的 CSS 变量
*/
private syncDropdownCssVars(theme: ThemeConfig): void {
const dropdowns = document.querySelectorAll('.opt-btn-dropdown');
dropdowns.forEach(dropdown => {
const style = (dropdown as HTMLElement).style;
style.setProperty('--bim-primary', theme.primary);
style.setProperty('--bim-bg-overlay', theme.bgOverlay);
style.setProperty('--bim-bg-glass-blur', theme.bgGlassBlur);
style.setProperty('--bim-border-subtle', theme.borderSubtle);
style.setProperty('--bim-text-primary', theme.textPrimary);
style.setProperty('--bim-text-inverse', theme.textInverse);
style.setProperty('--bim-icon-default', theme.iconDefault);
style.setProperty('--bim-icon-inverse', theme.iconInverse);
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
style.setProperty('--bim-component-bg-active', theme.componentBgActive);
style.setProperty('--bim-shadow-lg', theme.shadowLg);
});
}
/**
@@ -395,12 +480,13 @@ export class BimButtonGroup implements IBimComponent {
textWrapper.appendChild(label);
}
if (button.children && button.children.length > 0) {
const arrow = document.createElement('span');
arrow.className = 'opt-btn-arrow';
arrow.textContent = '▼';
textWrapper.appendChild(arrow);
}
// TODO: 暂时隐藏下拉箭头
// if (button.children && button.children.length > 0) {
// const arrow = document.createElement('span');
// arrow.className = 'opt-btn-arrow';
// arrow.textContent = '▼';
// textWrapper.appendChild(arrow);
// }
// 只有当有内容时才添加 wrapper
if (textWrapper.hasChildNodes()) {
@@ -543,6 +629,13 @@ export class BimButtonGroup implements IBimComponent {
dropdown.style.flexDirection = 'row'; // 纵向按钮组,菜单横向排列
}
if (this.options.type && this.options.type !== 'default') {
dropdown.classList.add(`type-${this.options.type}`);
}
const currentTheme = themeManager.getTheme();
dropdown.classList.add(`theme-${currentTheme.name}`);
// 先添加到 DOM 以便计算尺寸
document.body.appendChild(dropdown);

View File

@@ -1,5 +1,8 @@
export type ButtonType = 'button' | 'menu';
/** 按钮组样式类型 */
export type ButtonGroupType = 'default' | 'glass-pill';
/** 按钮配置 */
export interface ButtonConfig {
id: string;
@@ -76,6 +79,9 @@ export type ExpandDirection = 'up' | 'down' | 'left' | 'right';
export interface ButtonGroupOptions extends ButtonGroupColors {
container: HTMLElement | string;
/** 样式类型 (默认 'default') */
type?: ButtonGroupType;
/** 屏幕位置 (如 top-left) */
position?: GroupPosition;

View File

@@ -213,16 +213,13 @@ export class BimCollapse implements IBimComponent {
public setTheme(theme: ThemeConfig): void {
const style = this.element.style;
style.setProperty('--bim-bg-color', theme.panelBackground);
style.setProperty('--bim-border-color', theme.border);
style.setProperty('--bim-bg-color', theme.bgElevated);
style.setProperty('--bim-border-color', theme.borderDefault);
style.setProperty('--bim-text-color', theme.textPrimary);
// 头部默认背景色使用 componentBackground
style.setProperty('--bim-header-bg-color', theme.componentHover);
style.setProperty('--bim-header-hover-bg-color', theme.componentHover);
style.setProperty('--bim-content-bg-color', theme.panelBackground);
style.setProperty('--bim-disabled-color', theme.textSecondary);
style.setProperty('--bim-header-bg-color', theme.componentBgHover);
style.setProperty('--bim-header-hover-bg-color', theme.componentBgActive);
style.setProperty('--bim-content-bg-color', theme.bgElevated);
style.setProperty('--bim-disabled-color', theme.textDisabled);
}
public setLocales(): void {

View File

@@ -143,7 +143,7 @@ export class BimDescription implements IBimComponent {
style.setProperty('--bim-text-color', theme.textPrimary);
style.setProperty('--bim-label-color', theme.textSecondary);
style.setProperty('--bim-value-color', theme.textPrimary);
style.setProperty('--bim-border-color', theme.border);
style.setProperty('--bim-border-color', theme.borderDefault);
}
public setLocales(): void {

View File

@@ -1,37 +1,29 @@
:root {
--bim-dialog-bg: rgba(17, 17, 17, 0.95);
--bim-dialog-header-bg: #2a2a2a;
--bim-dialog-title-color: #fff;
--bim-dialog-text-color: #ccc;
--bim-dialog-border-color: #444;
}
.bim-dialog {
position: absolute;
background-color: var(--bim-dialog-bg);
border: 1px solid var(--bim-dialog-border-color);
background-color: var(--bim-dialog-bg, var(--bim-bg-elevated));
border: 1px solid var(--bim-dialog-border-color, var(--bim-border-default));
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
box-shadow: var(--bim-shadow-lg);
display: flex;
flex-direction: column;
z-index: 10001; /* 提高 z-index确保在第三方 SDK (10000) 之上 */
color: var(--bim-dialog-title-color);
z-index: 10001;
color: var(--bim-dialog-title-color, var(--bim-text-primary));
overflow: hidden;
min-width: 200px;
min-height: 100px;
pointer-events: auto; /* 确保弹窗可以接收事件 */
pointer-events: auto;
}
.bim-dialog-header {
height: 32px;
background-color: var(--bim-dialog-header-bg);
background-color: var(--bim-dialog-header-bg, var(--bim-bg-inset));
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
cursor: default;
user-select: none;
border-bottom: 1px solid var(--bim-dialog-border-color);
border-bottom: 1px solid var(--bim-dialog-border-color, var(--bim-border-default));
flex-shrink: 0;
}
@@ -51,20 +43,20 @@
.bim-dialog-close {
cursor: pointer;
font-size: 18px;
color: #999;
color: var(--bim-text-tertiary);
line-height: 1;
margin-left: 8px;
}
.bim-dialog-close:hover {
color: #fff;
color: var(--bim-text-primary);
}
.bim-dialog-content {
flex: 1;
overflow: auto;
font-size: 14px;
color: var(--bim-dialog-text-color);
color: var(--bim-dialog-text-color, var(--bim-text-secondary));
}
/* 缩放句柄 */
@@ -78,7 +70,6 @@
z-index: 10;
}
/* 右下角装饰,类似斜线 */
.bim-dialog-resize-handle::after {
content: '';
position: absolute;
@@ -86,10 +77,10 @@
right: 3px;
width: 6px;
height: 6px;
border-right: 2px solid #666;
border-bottom: 2px solid #666;
border-right: 2px solid var(--bim-text-tertiary);
border-bottom: 2px solid var(--bim-text-tertiary);
}
.bim-dialog-resize-handle:hover::after {
border-color: #fff;
border-color: var(--bim-text-primary);
}

View File

@@ -57,11 +57,19 @@ export class BimDialog implements IBimComponent {
*/
public setTheme(theme: ThemeConfig) {
const style = this.element.style;
if (!this.options.backgroundColor) style.setProperty('--bim-dialog-bg', theme.panelBackground);
if (!this.options.headerBackgroundColor) style.setProperty('--bim-dialog-header-bg', theme.componentHover);
if (!this.options.backgroundColor) style.setProperty('--bim-dialog-bg', theme.bgElevated);
if (!this.options.headerBackgroundColor) style.setProperty('--bim-dialog-header-bg', theme.bgInset);
if (!this.options.titleColor) style.setProperty('--bim-dialog-title-color', theme.textPrimary);
if (!this.options.textColor) style.setProperty('--bim-dialog-text-color', theme.textPrimary);
if (!this.options.borderColor) style.setProperty('--bim-dialog-border-color', theme.border);
if (!this.options.textColor) style.setProperty('--bim-dialog-text-color', theme.textSecondary);
if (!this.options.borderColor) style.setProperty('--bim-dialog-border-color', theme.borderDefault);
style.setProperty('--bim-bg-elevated', theme.bgElevated);
style.setProperty('--bim-bg-inset', theme.bgInset);
style.setProperty('--bim-text-primary', theme.textPrimary);
style.setProperty('--bim-text-secondary', theme.textSecondary);
style.setProperty('--bim-text-tertiary', theme.textTertiary);
style.setProperty('--bim-border-default', theme.borderDefault);
style.setProperty('--bim-shadow-lg', theme.shadowLg);
}
/**

View File

@@ -58,7 +58,7 @@ export class Engine implements IBimComponent {
// 保存配置选项(设置默认值)
this.options = {
backgroundColor: options.backgroundColor ?? 'linear-gradient(to bottom, rgb(214, 224, 235), rgb(246, 250, 255))', // 固定背景渐变色
backgroundColor: 'linear-gradient(to bottom, rgb(214, 224, 235), rgb(246, 250, 255))', // 固定背景渐变色
version: options.version ?? 'v1', // 默认使用 v1 版本
showStats: options.showStats ?? false, // 默认不显示统计
showViewCube: options.showViewCube ?? true, // 默认显示视图立方体

View File

@@ -150,9 +150,9 @@ export class MeasurePanel implements IBimComponent {
const style = this.element.style;
// 这些变量不会强制覆盖外部Dialog已有变量只做兜底
style.setProperty('--bim-measure-border', theme.border ?? 'rgba(255, 255, 255, 0.12)');
style.setProperty('--bim-measure-divider', theme.border ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-measure-icon-color', theme.icon ?? '#ddd');
style.setProperty('--bim-measure-border', theme.borderDefault ?? 'rgba(255, 255, 255, 0.12)');
style.setProperty('--bim-measure-divider', theme.borderDefault ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-measure-icon-color', theme.iconDefault ?? '#ddd');
style.setProperty('--bim-measure-label-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.70)');
style.setProperty('--bim-measure-value-color', theme.textPrimary ?? 'rgba(255, 255, 255, 0.90)');
@@ -161,9 +161,9 @@ export class MeasurePanel implements IBimComponent {
// 设置面板“保存设置”按钮用主题色
style.setProperty('--bim-measure-primary', theme.primary ?? '#0078d4');
style.setProperty('--bim-measure-primary-hover', theme.primaryHover ?? '#0063b1');
style.setProperty('--bim-measure-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
style.setProperty('--bim-measure-btn-hover-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-measure-btn-active-bg', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
style.setProperty('--bim-measure-btn-bg', theme.componentBg ?? 'rgba(255, 255, 255, 0.06)');
style.setProperty('--bim-measure-btn-hover-bg', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-measure-btn-active-bg', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.14)');
}
/**

View File

@@ -1,16 +1,16 @@
.bim-menu {
display: flex;
flex-direction: column;
background: var(--bim-ui_bg_color, #2b2d30);
background: var(--bim-bg-elevated);
border-radius: 4px;
padding: 4px 0;
margin: 0; /* 移除默认外边距 */
list-style: none; /* 移除列表小圆点 */
margin: 0;
list-style: none;
min-width: 160px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
box-shadow: var(--bim-shadow-lg);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
user-select: none;
color: var(--bim-ui_text_primary, #ffffff);
color: var(--bim-text-primary);
}
.bim-menu-group {
@@ -20,7 +20,7 @@
.bim-menu-divider {
height: 1px;
background-color: var(--bim-ui_border_color, #3e4145);
background-color: var(--bim-divider);
margin: 4px 0;
}
@@ -32,11 +32,11 @@
transition: background-color 0.2s;
font-size: 13px;
position: relative;
color: var(--bim-ui_text_primary, #ffffff);
color: var(--bim-text-primary);
}
.bim-menu-item:hover {
background-color: var(--bim-ui_bg_hover, #3e4145);
background-color: var(--bim-component-bg-hover);
}
.bim-menu-item.disabled {

View File

@@ -49,10 +49,11 @@ export class BimMenu implements IBimComponent {
*/
public setTheme(theme: ThemeConfig) {
const style = this.element.style;
style.setProperty('--bim-ui_bg_color', theme.panelBackground);
style.setProperty('--bim-ui_text_primary', theme.textPrimary);
style.setProperty('--bim-ui_border_color', theme.border);
style.setProperty('--bim-ui_bg_hover', theme.componentHover);
style.setProperty('--bim-bg-elevated', theme.bgElevated);
style.setProperty('--bim-text-primary', theme.textPrimary);
style.setProperty('--bim-divider', theme.divider);
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
style.setProperty('--bim-shadow-lg', theme.shadowLg);
}
/**

View File

@@ -69,11 +69,11 @@ export class SectionAxisPanel implements IBimComponent {
*/
public setTheme(theme: ThemeConfig): void {
const style = this.element.style;
style.setProperty('--bim-section-axis-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
style.setProperty('--bim-section-axis-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-section-axis-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
style.setProperty('--bim-section-axis-btn-bg', theme.componentBg ?? 'rgba(255, 255, 255, 0.06)');
style.setProperty('--bim-section-axis-btn-hover', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-section-axis-btn-active', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.14)');
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
style.setProperty('--bim-icon-color', theme.iconDefault ?? '#ccc');
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
}

View File

@@ -339,11 +339,11 @@ export class SectionBoxPanel implements IBimComponent {
public setTheme(theme: ThemeConfig): void {
if (!this.element) return;
const style = this.element.style;
style.setProperty('--bim-section-box-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
style.setProperty('--bim-section-box-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-section-box-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
style.setProperty('--bim-section-box-btn-bg', theme.componentBg ?? 'rgba(255, 255, 255, 0.06)');
style.setProperty('--bim-section-box-btn-hover', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-section-box-btn-active', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.14)');
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
style.setProperty('--bim-icon-color', theme.iconDefault ?? '#ccc');
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
}

View File

@@ -55,11 +55,11 @@ export class SectionPlanePanel implements IBimComponent {
*/
public setTheme(theme: ThemeConfig): void {
const style = this.element.style;
style.setProperty('--bim-section-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
style.setProperty('--bim-section-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-section-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
style.setProperty('--bim-section-btn-bg', theme.componentBg ?? 'rgba(255, 255, 255, 0.06)');
style.setProperty('--bim-section-btn-hover', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.10)');
style.setProperty('--bim-section-btn-active', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.14)');
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
style.setProperty('--bim-icon-color', theme.iconDefault ?? '#ccc');
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
}

View File

@@ -189,15 +189,15 @@ export class BimTab implements IBimComponent {
*/
public setTheme(theme: ThemeConfig): void {
const style = this.element.style;
style.setProperty('--bim-tab-bg', theme.panelBackground);
style.setProperty('--bim-tab-nav-bg', theme.panelBackground);
style.setProperty('--bim-tab-bg', theme.bgElevated);
style.setProperty('--bim-tab-nav-bg', theme.bgElevated);
style.setProperty('--bim-tab-text', theme.textPrimary);
style.setProperty('--bim-tab-text-secondary', theme.textSecondary);
style.setProperty('--bim-tab-text-active', theme.primary);
style.setProperty('--bim-tab-border', theme.border);
style.setProperty('--bim-tab-hover-bg', theme.componentHover);
style.setProperty('--bim-tab-active-bg', theme.componentActive);
style.setProperty('--bim-tab-icon', theme.icon);
style.setProperty('--bim-tab-border', theme.borderDefault);
style.setProperty('--bim-tab-hover-bg', theme.componentBgHover);
style.setProperty('--bim-tab-active-bg', theme.componentBgActive);
style.setProperty('--bim-tab-icon', theme.iconDefault);
}
/**

View File

@@ -1,15 +1,14 @@
/* 树容器 */
.bim-tree {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden; /* 内部滚动 */
overflow: hidden;
font-size: 14px;
color: var(--bim-ui_text_primary, #333);
user-select: none; /* 防止双击选中文字 */
position: relative; /* 为下拉框定位 */
background: transparent; /* 保持透明背景 */
color: var(--bim-text-primary);
user-select: none;
position: relative;
background: transparent;
}
/* 搜索区域 */
@@ -34,8 +33,8 @@
left: 8px;
width: 16px;
height: 16px;
color: var(--bim-ui_text_secondary, #999);
pointer-events: none; /* 让点击穿透到 input */
color: var(--bim-text-secondary);
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
@@ -49,33 +48,30 @@
.bim-tree-search-input {
width: 100%;
height: 30px;
/* 左侧留出图标位置: 8px left + 16px icon + 4px gap = 28px */
padding: 4px 8px 4px 30px;
border: 1px solid var(--bim-ui_border_color, #d9d9d9);
border: 1px solid var(--bim-border-default);
border-radius: 4px;
outline: none;
font-size: 13px;
color: inherit;
/* 输入框背景半透明或白色,看设计,通常输入框本身还是需要背景的,否则文字看不清 */
background-color: var(--bim-ui_bg_color, #fff);
background-color: var(--bim-bg-elevated);
transition: all 0.2s;
box-sizing: border-box;
}
.bim-tree-search-input:focus {
border-color: var(--bim-primary_color, #1890ff);
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
border-color: var(--bim-primary);
box-shadow: var(--bim-focus-ring);
}
/* 搜索结果下拉框 */
.bim-tree-search-results {
position: absolute;
top: 100%;
left: 8px;
right: 8px;
background-color: var(--bim-ui_bg_color, #fff);
border: 1px solid var(--bim-ui_border_color, #eee);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background-color: var(--bim-bg-elevated);
border: 1px solid var(--bim-border-default);
box-shadow: var(--bim-shadow-md);
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
@@ -98,7 +94,7 @@
}
.bim-tree-search-item:hover {
background-color: var(--bim-ui_bg_hover, #f5f5f5);
background-color: var(--bim-component-bg-hover);
}
.bim-tree-search-item-title {
@@ -108,7 +104,7 @@
.bim-tree-search-item-path {
font-size: 12px;
color: var(--bim-ui_text_secondary, #999);
color: var(--bim-text-secondary);
margin-top: 2px;
display: block;
}
@@ -139,10 +135,9 @@
}
.bim-tree-node-content:hover {
background-color: var(--bim-ui_bg_hover, rgba(0, 0, 0, 0.05)); /* 由主题提供,深色模式一致 */
background-color: var(--bim-component-bg-hover);
}
/* 展开/折叠箭头 */
.bim-tree-switcher {
width: 24px;
height: 32px;
@@ -150,7 +145,7 @@
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--bim-ui_text_secondary, #999);
color: var(--bim-text-secondary);
transition: transform 0.2s;
flex-shrink: 0;
}
@@ -170,14 +165,13 @@
visibility: hidden; /*叶子节点占位但不显示*/
}
/* 复选框 */
.bim-tree-checkbox {
width: 16px;
height: 16px;
border: 1px solid var(--bim-ui_border_color, #d9d9d9);
border: 1px solid var(--bim-border-default);
border-radius: 2px;
margin-right: 8px;
background-color: var(--bim-ui_bg_color, #fff);
background-color: var(--bim-bg-elevated);
position: relative;
cursor: pointer;
flex-shrink: 0;
@@ -185,13 +179,12 @@
}
.bim-tree-checkbox:hover {
border-color: var(--bim-primary_color, #1890ff);
border-color: var(--bim-primary);
}
/* 选中状态 */
.bim-tree-checkbox.is-checked {
background-color: var(--bim-primary_color, #1890ff);
border-color: var(--bim-primary_color, #1890ff);
background-color: var(--bim-primary);
border-color: var(--bim-primary);
}
.bim-tree-checkbox.is-checked::after {
@@ -201,16 +194,15 @@
left: 4px;
width: 5px;
height: 9px;
border: 2px solid #fff;
border: 2px solid var(--bim-text-inverse);
border-top: 0;
border-left: 0;
transform: rotate(45deg);
}
/* 半选状态 */
.bim-tree-checkbox.is-indeterminate {
background-color: var(--bim-ui_bg_color, #fff);
border-color: var(--bim-primary_color, #1890ff);
background-color: var(--bim-bg-elevated);
border-color: var(--bim-primary);
}
.bim-tree-checkbox.is-indeterminate::after {
@@ -220,22 +212,21 @@
left: 3px;
width: 8px;
height: 2px;
background-color: var(--bim-primary_color, #1890ff);
background-color: var(--bim-primary);
}
/* 禁用状态 */
.bim-tree-node.is-disabled .bim-tree-checkbox {
background-color: #f5f5f5;
border-color: #d9d9d9;
background-color: var(--bim-component-bg-disabled);
border-color: var(--bim-border-disabled);
cursor: not-allowed;
}
.bim-tree-node.is-disabled .bim-tree-checkbox.is-checked {
background-color: #d9d9d9;
background-color: var(--bim-text-disabled);
}
.bim-tree-node.is-disabled .bim-tree-node-content {
color: var(--bim-ui_text_disabled, #ccc);
color: var(--bim-text-disabled);
cursor: not-allowed;
}
@@ -275,10 +266,9 @@
display: block;
}
/* 选中高亮状态 */
.bim-tree-node-content.is-selected {
background-color: var(--bim-ui_bg_selected, rgba(24, 144, 255, 0.2));
color: var(--bim-primary_color, #1890ff);
background-color: var(--bim-component-bg-selected);
color: var(--bim-primary);
}
/* 节点操作栏 */

View File

@@ -235,13 +235,20 @@ export class BimTree implements IBimComponent {
*/
public setTheme(theme: ThemeConfig): void {
const style = this.element.style;
style.setProperty('--bim-ui_bg_color', theme.panelBackground);
style.setProperty('--bim-ui_text_primary', theme.textPrimary);
style.setProperty('--bim-ui_text_secondary', theme.textSecondary || '#999');
style.setProperty('--bim-ui_border_color', theme.border);
style.setProperty('--bim-ui_bg_hover', theme.componentHover);
style.setProperty('--bim-primary_color', theme.primary);
// style.setProperty('--bim-ui_text_disabled', theme.textDisabled); // 如果 ThemeConfig 有这个字段
style.setProperty('--bim-primary', theme.primary);
style.setProperty('--bim-bg-elevated', theme.bgElevated);
style.setProperty('--bim-text-primary', theme.textPrimary);
style.setProperty('--bim-text-secondary', theme.textSecondary);
style.setProperty('--bim-text-tertiary', theme.textTertiary);
style.setProperty('--bim-text-disabled', theme.textDisabled);
style.setProperty('--bim-text-inverse', theme.textInverse);
style.setProperty('--bim-border-default', theme.borderDefault);
style.setProperty('--bim-border-disabled', theme.borderDisabled);
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
style.setProperty('--bim-component-bg-selected', theme.componentBgSelected);
style.setProperty('--bim-component-bg-disabled', theme.componentBgDisabled);
style.setProperty('--bim-shadow-md', theme.shadowMd);
style.setProperty('--bim-focus-ring', theme.focusRing);
}
/**

View File

@@ -434,20 +434,20 @@ export class WalkControlPanel implements IBimComponent {
public setTheme(theme: ThemeConfig): void {
if (!this.element) return;
const style = this.element.style;
style.setProperty('--bim-walk-control-bg', theme.panelBackground ?? 'rgba(0, 0, 0, 0.8)');
style.setProperty('--bim-walk-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.15)');
style.setProperty('--bim-walk-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.3)');
style.setProperty('--bim-walk-control-bg', theme.bgElevated ?? 'rgba(0, 0, 0, 0.8)');
style.setProperty('--bim-walk-btn-hover', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.15)');
style.setProperty('--bim-walk-btn-active', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.3)');
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
style.setProperty('--bim-primary-hover', theme.primaryHover ?? '#40a9ff');
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
style.setProperty('--bim-icon-color', theme.iconDefault ?? '#ccc');
style.setProperty('--bim-text-color', theme.textPrimary ?? '#fff');
style.setProperty('--bim-divider-color', theme.border ?? 'rgba(255, 255, 255, 0.2)');
style.setProperty('--bim-speed-group-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.1)');
style.setProperty('--bim-speed-btn-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.1)');
style.setProperty('--bim-speed-btn-hover', theme.componentActive ?? 'rgba(255, 255, 255, 0.2)');
style.setProperty('--bim-select-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.1)');
style.setProperty('--bim-select-border', theme.border ?? 'rgba(255, 255, 255, 0.2)');
style.setProperty('--bim-select-option-bg', theme.panelBackground ?? '#333');
style.setProperty('--bim-divider-color', theme.borderDefault ?? 'rgba(255, 255, 255, 0.2)');
style.setProperty('--bim-speed-group-bg', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.1)');
style.setProperty('--bim-speed-btn-bg', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.1)');
style.setProperty('--bim-speed-btn-hover', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.2)');
style.setProperty('--bim-select-bg', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.1)');
style.setProperty('--bim-select-border', theme.borderDefault ?? 'rgba(255, 255, 255, 0.2)');
style.setProperty('--bim-select-option-bg', theme.bgElevated ?? '#333');
}
public destroy(): void {

View File

@@ -28,11 +28,12 @@ export class ToolbarManager extends BimComponent {
this.toolbar = new Toolbar({
container: this.toolbarContainer,
type: 'glass-pill',
showLabel: true,
direction: 'row',
position: 'bottom-center', // 底部居中
align: 'vertical', // 图标在上
expand: 'up' // 向上展开
position: 'bottom-right',
align: 'vertical',
expand: 'up'
});
// 注入 engine 到 Toolbar

View File

@@ -7,7 +7,7 @@ type ThemeChangeListener = (theme: ThemeConfig) => void;
* 主题管理器 (单例)
*/
export class ThemeManager {
private currentTheme: ThemeConfig = darkTheme;
private currentTheme: ThemeConfig = lightTheme;
private listeners: ThemeChangeListener[] = [];
constructor() {

View File

@@ -1,51 +1,237 @@
import { ThemeConfig } from './types';
/**
* 深色主题 (默认)
* Tailwind CSS Color Palette Reference (for maintainers)
*
* Slate: #f8fafc #f1f5f9 #e2e8f0 #cbd5e1 #94a3b8 #64748b #475569 #334155 #1e293b #0f172a
* Blue: #eff6ff #dbeafe #bfdbfe #93c5fd #60a5fa #3b82f6 #2563eb #1d4ed8 #1e40af #1e3a8a
* Green: #f0fdf4 #dcfce7 #bbf7d0 #86efac #4ade80 #22c55e #16a34a #15803d #166534 #14532d
* Amber: #fffbeb #fef3c7 #fde68a #fcd34d #fbbf24 #f59e0b #d97706 #b45309 #92400e #78350f
* Red: #fef2f2 #fee2e2 #fecaca #fca5a5 #f87171 #ef4444 #dc2626 #b91c1c #991b1b #7f1d1d
* Cyan: #ecfeff #cffafe #a5f3fc #67e8f9 #22d3ee #06b6d4 #0891b2 #0e7490 #155e75 #164e63
*/
export const darkTheme: ThemeConfig = {
name: 'dark',
primary: '#0078d4',
primaryHover: '#0063b1',
// 修改:背景色统一为浅灰,不再跟随深色模式变黑
background: '#f5f5f5',
panelBackground: 'rgba(30, 30, 30, 0.9)',
textPrimary: '#ffffff',
textSecondary: '#cccccc',
border: '#444444',
icon: '#cccccc',
iconActive: '#ffffff',
componentBackground: 'transparent',
componentHover: '#4e4d4dff',
componentActive: 'rgba(255, 255, 255, 0.1)'
};
/**
* 浅色主题
*/
export const lightTheme: ThemeConfig = {
name: 'light',
primary: '#0078d4',
primaryHover: '#106ebe',
// 统一为浅灰
background: '#f5f5f5',
panelBackground: '#ffffff',
primary: '#2563eb',
primaryHover: '#1d4ed8',
primaryActive: '#1e40af',
primarySubtle: 'rgba(37, 99, 235, 0.1)',
textPrimary: '#333333',
textSecondary: '#666666',
success: '#16a34a',
successHover: '#15803d',
successSubtle: 'rgba(22, 163, 74, 0.1)',
border: '#e0e0e0',
warning: '#d97706',
warningHover: '#b45309',
warningSubtle: 'rgba(217, 119, 6, 0.1)',
icon: '#555555',
iconActive: '#0078d4',
danger: '#dc2626',
dangerHover: '#b91c1c',
dangerSubtle: 'rgba(220, 38, 38, 0.1)',
componentBackground: 'transparent',
componentHover: '#f0f0f0',
componentActive: '#e0e0e0'
};
info: '#0891b2',
infoHover: '#0e7490',
infoSubtle: 'rgba(8, 145, 178, 0.1)',
bgBase: '#f8fafc',
bgElevated: '#ffffff',
bgOverlay: 'rgba(255, 255, 255, 0.98)',
bgInset: '#f1f5f9',
bgGlass: 'rgba(255, 255, 255, 0.8)',
bgGlassBlur: '24px',
textPrimary: '#0f172a',
textSecondary: '#475569',
textTertiary: '#94a3b8',
textDisabled: '#cbd5e1',
textInverse: '#ffffff',
textLink: '#2563eb',
textLinkHover: '#1d4ed8',
iconDefault: '#64748b',
iconHover: '#334155',
iconActive: '#2563eb',
iconDisabled: '#cbd5e1',
iconInverse: '#ffffff',
borderDefault: '#e2e8f0',
borderSubtle: '#f1f5f9',
borderStrong: '#cbd5e1',
borderDisabled: '#f1f5f9',
divider: '#e2e8f0',
componentBg: 'transparent',
componentBgHover: 'rgba(15, 23, 42, 0.04)',
componentBgActive: 'rgba(15, 23, 42, 0.08)',
componentBgSelected: 'rgba(37, 99, 235, 0.08)',
componentBgDisabled: '#f8fafc',
focusRing: 'rgba(37, 99, 235, 0.5)',
focusRingOffset: '#ffffff',
selectionBg: 'rgba(37, 99, 235, 0.15)',
selectionText: '#0f172a',
shadowSm: '0 2px 8px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08)',
shadowMd: '0 4px 12px rgba(0, 0, 0, 0.12), 0 6px 20px rgba(0, 0, 0, 0.1)',
shadowLg: '0 10px 25px rgba(0, 0, 0, 0.15), 0 6px 10px rgba(0, 0, 0, 0.1)',
shadowXl: '0 20px 40px rgba(0, 0, 0, 0.2), 0 10px 15px rgba(0, 0, 0, 0.1)',
shadowGlow: '0 2px 8px rgba(59, 130, 246, 0.3), 0 4px 16px rgba(59, 130, 246, 0.2)',
scrollbarTrack: '#f1f5f9',
scrollbarThumb: '#cbd5e1',
scrollbarThumbHover: '#94a3b8',
overrides: {
dialog: {
headerBg: '#f8fafc',
},
toolbar: {
bg: 'rgba(255, 255, 255, 0.9)',
buttonBg: 'rgba(255, 255, 255, 0.95)',
buttonBgHover: '#ffffff',
buttonBgActive: '#2563eb',
},
glassPill: {
sectionBg: 'rgba(255, 255, 255, 0.8)',
sectionBorder: 'rgba(226, 232, 240, 0.5)',
sectionShadow: '0 4px 24px rgba(0, 0, 0, 0.06)',
btnBg: 'rgba(255, 255, 255, 0.9)',
btnBorder: 'rgba(203, 213, 225, 0.6)',
btnShadow: '0 2px 8px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08)',
btnBgHover: '#ffffff',
btnShadowHover: '0 4px 12px rgba(0, 0, 0, 0.12), 0 6px 20px rgba(0, 0, 0, 0.1)',
iconColor: '#334155',
iconColorHover: '#1e293b',
},
input: {
bg: '#ffffff',
bgFocus: '#ffffff',
placeholder: '#94a3b8',
},
},
};
export const darkTheme: ThemeConfig = {
name: 'dark',
primary: '#3b82f6',
primaryHover: '#60a5fa',
primaryActive: '#2563eb',
primarySubtle: 'rgba(59, 130, 246, 0.15)',
success: '#22c55e',
successHover: '#4ade80',
successSubtle: 'rgba(34, 197, 94, 0.15)',
warning: '#f59e0b',
warningHover: '#fbbf24',
warningSubtle: 'rgba(245, 158, 11, 0.15)',
danger: '#ef4444',
dangerHover: '#f87171',
dangerSubtle: 'rgba(239, 68, 68, 0.15)',
info: '#06b6d4',
infoHover: '#22d3ee',
infoSubtle: 'rgba(6, 182, 212, 0.15)',
bgBase: '#152232',
bgElevated: '#1f2d3e',
bgOverlay: 'rgba(21, 34, 50, 0.98)',
bgInset: '#152232',
bgGlass: 'rgba(21, 34, 50, 0.85)',
bgGlassBlur: '24px',
textPrimary: '#ffffff',
textSecondary: '#94a3b8',
textTertiary: '#64748b',
textDisabled: '#475569',
textInverse: '#152232',
textLink: '#60a5fa',
textLinkHover: '#93c5fd',
iconDefault: '#ffffff',
iconHover: '#ffffff',
iconActive: '#3b82f6',
iconDisabled: '#475569',
iconInverse: '#152232',
borderDefault: 'rgba(51, 65, 85, 0.5)',
borderSubtle: 'rgba(51, 65, 85, 0.5)',
borderStrong: '#475569',
borderDisabled: '#1e293b',
divider: '#334155',
componentBg: 'transparent',
componentBgHover: 'rgba(248, 250, 252, 0.06)',
componentBgActive: 'rgba(248, 250, 252, 0.1)',
componentBgSelected: 'rgba(59, 130, 246, 0.2)',
componentBgDisabled: '#1e293b',
focusRing: 'rgba(59, 130, 246, 0.5)',
focusRingOffset: '#1e293b',
selectionBg: 'rgba(59, 130, 246, 0.3)',
selectionText: '#f8fafc',
shadowSm: '0 2px 8px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3)',
shadowMd: '0 4px 12px rgba(0, 0, 0, 0.5), 0 6px 20px rgba(0, 0, 0, 0.4)',
shadowLg: '0 10px 25px rgba(0, 0, 0, 0.6), 0 6px 10px rgba(0, 0, 0, 0.4)',
shadowXl: '0 20px 40px rgba(0, 0, 0, 0.7), 0 10px 15px rgba(0, 0, 0, 0.5)',
shadowGlow: '0 2px 8px rgba(59, 130, 246, 0.4), 0 4px 16px rgba(59, 130, 246, 0.25)',
scrollbarTrack: '#1e293b',
scrollbarThumb: '#475569',
scrollbarThumbHover: '#64748b',
overrides: {
dialog: {
headerBg: '#0f172a',
},
toolbar: {
bg: 'rgba(30, 41, 59, 0.9)',
buttonBg: 'rgba(51, 65, 85, 0.6)',
buttonBgHover: 'rgba(71, 85, 105, 0.7)',
buttonBgActive: '#3b82f6',
},
glassPill: {
sectionBg: 'rgba(30, 41, 59, 0.8)',
sectionBorder: 'rgba(51, 65, 85, 0.5)',
sectionShadow: '0 4px 24px rgba(0, 0, 0, 0.15)',
btnBg: 'rgba(51, 65, 85, 0.5)',
btnBorder: 'rgba(71, 85, 105, 0.4)',
btnShadow: '0 2px 8px rgba(0, 0, 0, 0.25), 0 4px 12px rgba(0, 0, 0, 0.2)',
btnBgHover: 'rgba(71, 85, 105, 0.6)',
btnShadowHover: '0 4px 12px rgba(0, 0, 0, 0.3), 0 6px 20px rgba(0, 0, 0, 0.25)',
iconColor: '#e2e8f0',
iconColorHover: '#f8fafc',
},
input: {
bg: '#0f172a',
bgFocus: '#1e293b',
placeholder: '#64748b',
},
},
};
export function createThemeFromPartial(base: ThemeConfig, partial: Partial<ThemeConfig>): ThemeConfig {
return {
...base,
...partial,
overrides: {
...base.overrides,
...partial.overrides,
},
};
}
export function getThemeByName(name: string): ThemeConfig {
switch (name) {
case 'light':
return lightTheme;
case 'dark':
default:
return darkTheme;
}
}

View File

@@ -1,43 +1,407 @@
/**
* 全局主题配置接口
* 定义系统通用的语义化颜色
* BIM Engine SDK - Unified Theme System
*
* Design Principles:
* 1. Semantic naming - colors describe PURPOSE, not appearance
* 2. Hierarchical structure - organized by category for scalability
* 3. Complete coverage - all UI states and component needs
* 4. Accessibility - ensures proper contrast ratios
* 5. Glassmorphism support - includes transparency and blur values
*
* Color Palette Reference: Tailwind CSS (slate, blue, red, green, amber)
*/
// ============================================================================
// Color Primitives (for reference, not exported directly)
// ============================================================================
/**
* Semantic Color Token
* Represents a single color value with optional transparency
*/
type ColorToken = string;
/**
* Shadow Token
* CSS box-shadow value
*/
type ShadowToken = string;
/**
* Blur Token
* CSS backdrop-filter blur value (e.g., '24px')
*/
type BlurToken = string;
// ============================================================================
// Theme Configuration Interface
// ============================================================================
/**
* Complete Theme Configuration Interface
*
* All colors use semantic naming to describe their purpose.
* Components should map these to their internal CSS variables.
*/
export interface ThemeConfig {
/** 主题名称 */
// =========================================================================
// Meta
// =========================================================================
/** Theme identifier: 'dark' | 'light' | custom name */
name: string;
/** 品牌色/主色 */
primary: string;
/** 主色悬停/激活态 */
primaryHover: string;
/** 基础背景色 (应用整体背景) */
background: string;
/** 面板背景色 (工具栏、弹窗背景) */
panelBackground: string;
// =========================================================================
// Brand / Primary Colors
// =========================================================================
/** 主要文字颜色 */
textPrimary: string;
/** 次要文字颜色 */
textSecondary: string;
/** Primary brand color - used for key actions, links, active states */
primary: ColorToken;
/** Primary color on hover */
primaryHover: ColorToken;
/** Primary color when pressed/active */
primaryActive: ColorToken;
/** Subtle primary for backgrounds (e.g., selected row) */
primarySubtle: ColorToken;
// =========================================================================
// Status / Semantic Colors
// =========================================================================
/** 边框/分割线颜色 */
border: string;
/** Success color - confirmations, completed states */
success: ColorToken;
/** Success color on hover */
successHover: ColorToken;
/** Subtle success for backgrounds */
successSubtle: ColorToken;
/** Warning color - caution, pending states */
warning: ColorToken;
/** Warning color on hover */
warningHover: ColorToken;
/** Subtle warning for backgrounds */
warningSubtle: ColorToken;
/** Danger/Error color - destructive actions, errors */
danger: ColorToken;
/** Danger color on hover */
dangerHover: ColorToken;
/** Subtle danger for backgrounds */
dangerSubtle: ColorToken;
/** Info color - informational states */
info: ColorToken;
/** Info color on hover */
infoHover: ColorToken;
/** Subtle info for backgrounds */
infoSubtle: ColorToken;
// =========================================================================
// Background Colors (Layered System)
// =========================================================================
/** 图标默认颜色 */
icon: string;
/** 图标激活颜色 */
iconActive: string;
/** Base/canvas background - the deepest layer (app background) */
bgBase: ColorToken;
/** Elevated surface - panels, cards, dialogs floating above base */
bgElevated: ColorToken;
/** Overlay background - modals, dropdowns, popovers */
bgOverlay: ColorToken;
/** Inset/recessed background - inputs, wells, sunken areas */
bgInset: ColorToken;
/** 交互组件背景 (如按钮默认背景) */
componentBackground: string;
/** 交互组件悬停背景 */
componentHover: string;
/** 交互组件激活背景 */
componentActive: string;
/**
* Glassmorphism surface - semi-transparent with blur
* Used for floating toolbars, modern panels
*/
bgGlass: ColorToken;
/** Glassmorphism blur amount */
bgGlassBlur: BlurToken;
// =========================================================================
// Text Colors
// =========================================================================
/** Primary text - headings, body text, high emphasis */
textPrimary: ColorToken;
/** Secondary text - descriptions, labels, medium emphasis */
textSecondary: ColorToken;
/** Tertiary text - placeholders, hints, low emphasis */
textTertiary: ColorToken;
/** Disabled text - inactive elements */
textDisabled: ColorToken;
/** Inverted text - text on primary/dark backgrounds */
textInverse: ColorToken;
/** Link text color */
textLink: ColorToken;
/** Link text on hover */
textLinkHover: ColorToken;
// =========================================================================
// Icon Colors
// =========================================================================
/** Default icon color */
iconDefault: ColorToken;
/** Icon on hover */
iconHover: ColorToken;
/** Active/selected icon */
iconActive: ColorToken;
/** Disabled icon */
iconDisabled: ColorToken;
/** Inverted icon (on colored backgrounds) */
iconInverse: ColorToken;
// =========================================================================
// Border / Divider Colors
// =========================================================================
/** Default border - inputs, cards, containers */
borderDefault: ColorToken;
/** Subtle border - dividers, separators */
borderSubtle: ColorToken;
/** Strong border - focus rings, emphasis */
borderStrong: ColorToken;
/** Disabled border */
borderDisabled: ColorToken;
/** Divider color - horizontal/vertical separators */
divider: ColorToken;
// =========================================================================
// Interactive Component States
// =========================================================================
/**
* Component Background States
* Used for buttons, list items, interactive elements
*/
/** Default component background (often transparent) */
componentBg: ColorToken;
/** Component background on hover */
componentBgHover: ColorToken;
/** Component background when pressed */
componentBgActive: ColorToken;
/** Component background when selected */
componentBgSelected: ColorToken;
/** Disabled component background */
componentBgDisabled: ColorToken;
// =========================================================================
// Focus / Selection States
// =========================================================================
/** Focus ring color */
focusRing: ColorToken;
/** Focus ring offset color (for contrast) */
focusRingOffset: ColorToken;
/** Selection background (text selection, highlighted items) */
selectionBg: ColorToken;
/** Selection text color */
selectionText: ColorToken;
// =========================================================================
// Shadows
// =========================================================================
/** Small shadow - subtle elevation (buttons, small cards) */
shadowSm: ShadowToken;
/** Medium shadow - moderate elevation (dropdowns, popovers) */
shadowMd: ShadowToken;
/** Large shadow - high elevation (modals, dialogs) */
shadowLg: ShadowToken;
/** Extra large shadow - maximum elevation */
shadowXl: ShadowToken;
/** Glow shadow for active/highlighted elements */
shadowGlow: ShadowToken;
// =========================================================================
// Scrollbar Colors
// =========================================================================
/** Scrollbar track background */
scrollbarTrack: ColorToken;
/** Scrollbar thumb */
scrollbarThumb: ColorToken;
/** Scrollbar thumb on hover */
scrollbarThumbHover: ColorToken;
// =========================================================================
// Specific Component Overrides (Optional)
// These allow fine-grained control when semantic tokens aren't enough
// =========================================================================
/**
* Component-specific overrides
* Only define these when absolutely necessary
*/
overrides?: {
/** Dialog specific colors */
dialog?: {
headerBg?: ColorToken;
footerBg?: ColorToken;
};
/** Toolbar specific colors */
toolbar?: {
bg?: ColorToken;
buttonBg?: ColorToken;
buttonBgHover?: ColorToken;
buttonBgActive?: ColorToken;
};
/** Input specific colors */
input?: {
bg?: ColorToken;
bgFocus?: ColorToken;
placeholder?: ColorToken;
};
/** Glass-pill button group specific colors */
glassPill?: {
sectionBg?: ColorToken;
sectionBorder?: ColorToken;
sectionShadow?: ShadowToken;
btnBg?: ColorToken;
btnBorder?: ColorToken;
btnShadow?: ShadowToken;
btnBgHover?: ColorToken;
btnShadowHover?: ShadowToken;
iconColor?: ColorToken;
iconColorHover?: ColorToken;
};
};
}
/**
* 主题类型定义
* Partial theme config for customization
* Allows users to override only specific tokens
*/
export type PartialThemeConfig = Partial<ThemeConfig> & { name: string };
/**
* Theme type definition
*/
export type ThemeType = 'dark' | 'light' | 'custom';
// ============================================================================
// CSS Variable Mapping
// ============================================================================
/**
* CSS Variable Name Mapping
*
* All CSS variables follow the pattern: --bim-{category}-{property}
*
* Categories:
* - primary, success, warning, danger, info (semantic colors)
* - bg (backgrounds)
* - text (typography)
* - icon (iconography)
* - border (borders)
* - component (interactive states)
* - shadow (elevation)
* - focus (focus states)
* - scrollbar (scrollbar styling)
*/
export const CSS_VAR_MAP: Record<keyof Omit<ThemeConfig, 'name' | 'overrides'>, string> = {
// Primary
primary: '--bim-primary',
primaryHover: '--bim-primary-hover',
primaryActive: '--bim-primary-active',
primarySubtle: '--bim-primary-subtle',
// Success
success: '--bim-success',
successHover: '--bim-success-hover',
successSubtle: '--bim-success-subtle',
// Warning
warning: '--bim-warning',
warningHover: '--bim-warning-hover',
warningSubtle: '--bim-warning-subtle',
// Danger
danger: '--bim-danger',
dangerHover: '--bim-danger-hover',
dangerSubtle: '--bim-danger-subtle',
// Info
info: '--bim-info',
infoHover: '--bim-info-hover',
infoSubtle: '--bim-info-subtle',
// Backgrounds
bgBase: '--bim-bg-base',
bgElevated: '--bim-bg-elevated',
bgOverlay: '--bim-bg-overlay',
bgInset: '--bim-bg-inset',
bgGlass: '--bim-bg-glass',
bgGlassBlur: '--bim-bg-glass-blur',
// Text
textPrimary: '--bim-text-primary',
textSecondary: '--bim-text-secondary',
textTertiary: '--bim-text-tertiary',
textDisabled: '--bim-text-disabled',
textInverse: '--bim-text-inverse',
textLink: '--bim-text-link',
textLinkHover: '--bim-text-link-hover',
// Icons
iconDefault: '--bim-icon-default',
iconHover: '--bim-icon-hover',
iconActive: '--bim-icon-active',
iconDisabled: '--bim-icon-disabled',
iconInverse: '--bim-icon-inverse',
// Borders
borderDefault: '--bim-border-default',
borderSubtle: '--bim-border-subtle',
borderStrong: '--bim-border-strong',
borderDisabled: '--bim-border-disabled',
divider: '--bim-divider',
// Components
componentBg: '--bim-component-bg',
componentBgHover: '--bim-component-bg-hover',
componentBgActive: '--bim-component-bg-active',
componentBgSelected: '--bim-component-bg-selected',
componentBgDisabled: '--bim-component-bg-disabled',
// Focus
focusRing: '--bim-focus-ring',
focusRingOffset: '--bim-focus-ring-offset',
selectionBg: '--bim-selection-bg',
selectionText: '--bim-selection-text',
// Shadows
shadowSm: '--bim-shadow-sm',
shadowMd: '--bim-shadow-md',
shadowLg: '--bim-shadow-lg',
shadowXl: '--bim-shadow-xl',
shadowGlow: '--bim-shadow-glow',
// Scrollbar
scrollbarTrack: '--bim-scrollbar-track',
scrollbarThumb: '--bim-scrollbar-thumb',
scrollbarThumbHover: '--bim-scrollbar-thumb-hover',
};
// ============================================================================
// Legacy Compatibility Mapping
// ============================================================================
/**
* Maps old ThemeConfig properties to new ones
* Used for backward compatibility during migration
*/
export interface LegacyThemeMapping {
/** Old property name -> New property name */
background: 'bgBase';
panelBackground: 'bgElevated';
border: 'borderDefault';
icon: 'iconDefault';
componentBackground: 'componentBg';
componentHover: 'componentBgHover';
componentActive: 'componentBgActive';
}