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

@@ -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;