添加折叠面板
This commit is contained in:
121
src/components/collapse/index.css
Normal file
121
src/components/collapse/index.css
Normal file
@@ -0,0 +1,121 @@
|
||||
/* Root Container */
|
||||
.bim-collapse {
|
||||
background-color: var(--bim-bg-color, #ffffff);
|
||||
border: 1px solid var(--bim-border-color, #d9d9d9);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: var(--bim-text-color, rgba(0, 0, 0, 0.88));
|
||||
}
|
||||
|
||||
.bim-collapse.is-ghost {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.bim-collapse.is-ghost .bim-collapse-item {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.bim-collapse.is-ghost .bim-collapse-header {
|
||||
background-color: transparent;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.bim-collapse.is-ghost .bim-collapse-content {
|
||||
background-color: transparent;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* Item */
|
||||
.bim-collapse-item {
|
||||
border-bottom: 1px solid var(--bim-border-color, #d9d9d9);
|
||||
}
|
||||
|
||||
.bim-collapse-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.bim-collapse-item.is-disabled .bim-collapse-header {
|
||||
color: var(--bim-disabled-color, rgba(0, 0, 0, 0.25));
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.bim-collapse-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background-color: var(--bim-header-bg-color, rgba(0, 0, 0, 0.02));
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bim-collapse-header:hover {
|
||||
background-color: var(--bim-header-hover-bg-color, rgba(0, 0, 0, 0.05));
|
||||
}
|
||||
|
||||
/* Arrow Icon */
|
||||
.bim-collapse-arrow {
|
||||
margin-right: 12px;
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
transition: transform 0.24s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bim-collapse-arrow svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.bim-collapse-item.is-active .bim-collapse-arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* Icon (User provided) */
|
||||
.bim-collapse-icon {
|
||||
margin-right: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bim-collapse-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* Title */
|
||||
.bim-collapse-title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Extra */
|
||||
.bim-collapse-extra {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.bim-collapse-content {
|
||||
overflow: hidden;
|
||||
background-color: var(--bim-content-bg-color, #ffffff);
|
||||
border-top: 1px solid var(--bim-border-color, #d9d9d9);
|
||||
transition: height 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.bim-collapse-content.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bim-collapse-content-box {
|
||||
padding: 16px;
|
||||
}
|
||||
244
src/components/collapse/index.ts
Normal file
244
src/components/collapse/index.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
import './index.css';
|
||||
import { CollapseOptions, CollapseItemConfig } from './types';
|
||||
import { IBimComponent } from '../../types/component';
|
||||
import { t, localeManager } from '../../services/locale';
|
||||
import { themeManager } from '../../services/theme';
|
||||
import type { ThemeConfig } from '../../themes/types';
|
||||
|
||||
/**
|
||||
* 单个折叠面板项
|
||||
*/
|
||||
class BimCollapseItem {
|
||||
public element: HTMLElement;
|
||||
public headerEl!: HTMLElement;
|
||||
public contentEl!: HTMLElement;
|
||||
public contentBoxEl!: HTMLElement;
|
||||
public arrowEl!: HTMLElement;
|
||||
public titleEl!: HTMLElement;
|
||||
|
||||
private config: CollapseItemConfig;
|
||||
private parent: BimCollapse;
|
||||
|
||||
constructor(config: CollapseItemConfig, parent: BimCollapse) {
|
||||
this.config = config;
|
||||
this.parent = parent;
|
||||
this.element = this.createDom();
|
||||
}
|
||||
|
||||
private createDom(): HTMLElement {
|
||||
const itemEl = document.createElement('div');
|
||||
itemEl.className = `bim-collapse-item ${this.config.className || ''}`;
|
||||
if (this.config.disabled) itemEl.classList.add('is-disabled');
|
||||
itemEl.dataset.id = this.config.id;
|
||||
|
||||
// <20><>部区域
|
||||
this.headerEl = document.createElement('div');
|
||||
this.headerEl.className = 'bim-collapse-header';
|
||||
|
||||
// 箭头图标
|
||||
this.arrowEl = document.createElement('span');
|
||||
this.arrowEl.className = 'bim-collapse-arrow';
|
||||
this.arrowEl.innerHTML = `<svg viewBox="0 0 1024 1024"><path d="M288 192l448 320-448 320z"></path></svg>`;
|
||||
this.headerEl.appendChild(this.arrowEl);
|
||||
|
||||
// 自定义图标 (可选)
|
||||
if (this.config.icon) {
|
||||
const iconEl = document.createElement('span');
|
||||
iconEl.className = 'bim-collapse-icon';
|
||||
iconEl.innerHTML = this.config.icon;
|
||||
this.headerEl.appendChild(iconEl);
|
||||
}
|
||||
|
||||
// 标题文本
|
||||
this.titleEl = document.createElement('span');
|
||||
this.titleEl.className = 'bim-collapse-title';
|
||||
this.titleEl.textContent = t(this.config.title); // 初始翻译
|
||||
this.headerEl.appendChild(this.titleEl);
|
||||
|
||||
// 额外内容 (可选,如右侧标签)
|
||||
if (this.config.extra) {
|
||||
const extraEl = document.createElement('div');
|
||||
extraEl.className = 'bim-collapse-extra';
|
||||
if (typeof this.config.extra === 'string') {
|
||||
extraEl.innerHTML = this.config.extra;
|
||||
} else {
|
||||
extraEl.appendChild(this.config.extra);
|
||||
}
|
||||
this.headerEl.appendChild(extraEl);
|
||||
}
|
||||
|
||||
// 点击事件
|
||||
this.headerEl.addEventListener('click', () => {
|
||||
if (this.config.disabled) return;
|
||||
this.parent.toggleItem(this.config.id);
|
||||
});
|
||||
|
||||
itemEl.appendChild(this.headerEl);
|
||||
|
||||
// 内容区域
|
||||
this.contentEl = document.createElement('div');
|
||||
this.contentEl.className = 'bim-collapse-content is-hidden';
|
||||
|
||||
this.contentBoxEl = document.createElement('div');
|
||||
this.contentBoxEl.className = 'bim-collapse-content-box';
|
||||
|
||||
if (typeof this.config.content === 'string') {
|
||||
this.contentBoxEl.innerHTML = this.config.content;
|
||||
} else {
|
||||
this.contentBoxEl.appendChild(this.config.content);
|
||||
}
|
||||
|
||||
this.contentEl.appendChild(this.contentBoxEl);
|
||||
itemEl.appendChild(this.contentEl);
|
||||
|
||||
return itemEl;
|
||||
}
|
||||
|
||||
public updateLocale() {
|
||||
if (this.titleEl) {
|
||||
this.titleEl.textContent = t(this.config.title);
|
||||
}
|
||||
}
|
||||
|
||||
public setActive(isActive: boolean) {
|
||||
if (isActive) {
|
||||
this.element.classList.add('is-active');
|
||||
this.contentEl.classList.remove('is-hidden');
|
||||
// 简单的动画处理:设置 height
|
||||
// 实际生产中可能需要更复杂的 JS 动画库或 transitionend 事件处理
|
||||
// 这里依赖 CSS transition
|
||||
} else {
|
||||
this.element.classList.remove('is-active');
|
||||
this.contentEl.classList.add('is-hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 折叠面板组件
|
||||
*/
|
||||
export class BimCollapse implements IBimComponent {
|
||||
private element: HTMLElement;
|
||||
private options: CollapseOptions;
|
||||
private items: Map<string, BimCollapseItem> = new Map();
|
||||
private activeIds: Set<string> = new Set();
|
||||
private unsubscribeLocale: (() => void) | null = null;
|
||||
private unsubscribeTheme: (() => void) | null = null;
|
||||
|
||||
constructor(options: CollapseOptions) {
|
||||
this.options = {
|
||||
bordered: true,
|
||||
accordion: false,
|
||||
...options
|
||||
};
|
||||
|
||||
this.element = document.createElement('div');
|
||||
this.element.className = `bim-collapse ${this.options.className || ''}`;
|
||||
if (!this.options.bordered) this.element.style.border = 'none';
|
||||
if (this.options.ghost) this.element.classList.add('is-ghost');
|
||||
|
||||
const container = typeof this.options.container === 'string'
|
||||
? document.getElementById(this.options.container)
|
||||
: this.options.container;
|
||||
|
||||
if (container) {
|
||||
container.appendChild(this.element);
|
||||
}
|
||||
|
||||
// 初始化激活的 ID
|
||||
if (this.options.activeIds) {
|
||||
this.options.activeIds.forEach(id => this.activeIds.add(id));
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
public init() {
|
||||
// 创建子项
|
||||
this.options.items.forEach(itemConfig => {
|
||||
const item = new BimCollapseItem(itemConfig, this);
|
||||
this.items.set(itemConfig.id, item);
|
||||
this.element.appendChild(item.element);
|
||||
|
||||
// 设置初始状态
|
||||
if (this.activeIds.has(itemConfig.id)) {
|
||||
item.setActive(true);
|
||||
}
|
||||
});
|
||||
|
||||
// 订阅语言变更
|
||||
this.unsubscribeLocale = localeManager.subscribe(() => {
|
||||
this.setLocales();
|
||||
});
|
||||
|
||||
// 订阅主题变更
|
||||
this.unsubscribeTheme = themeManager.subscribe((theme) => {
|
||||
this.setTheme(theme);
|
||||
});
|
||||
|
||||
// 初始应用主题
|
||||
this.setTheme(themeManager.getTheme());
|
||||
}
|
||||
|
||||
public toggleItem(id: string) {
|
||||
const isActive = this.activeIds.has(id);
|
||||
|
||||
if (this.options.accordion) {
|
||||
// 手风琴模式:关闭其他所有,只展开目标
|
||||
this.activeIds.clear();
|
||||
if (!isActive) {
|
||||
this.activeIds.add(id);
|
||||
}
|
||||
} else {
|
||||
// 普通模式:切换目标状态
|
||||
if (isActive) {
|
||||
this.activeIds.delete(id);
|
||||
} else {
|
||||
this.activeIds.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
this.refreshState();
|
||||
|
||||
if (this.options.onChange) {
|
||||
this.options.onChange(Array.from(this.activeIds));
|
||||
}
|
||||
}
|
||||
|
||||
private refreshState() {
|
||||
this.items.forEach((item, id) => {
|
||||
item.setActive(this.activeIds.has(id));
|
||||
});
|
||||
}
|
||||
|
||||
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-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);
|
||||
}
|
||||
|
||||
public setLocales(): void {
|
||||
this.items.forEach(item => item.updateLocale());
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
if (this.unsubscribeLocale) {
|
||||
this.unsubscribeLocale();
|
||||
this.unsubscribeLocale = null;
|
||||
}
|
||||
if (this.unsubscribeTheme) {
|
||||
this.unsubscribeTheme();
|
||||
this.unsubscribeTheme = null;
|
||||
}
|
||||
this.element.remove();
|
||||
this.items.clear();
|
||||
}
|
||||
}
|
||||
49
src/components/collapse/types.ts
Normal file
49
src/components/collapse/types.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
export interface CollapseItemConfig {
|
||||
/** 唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 标题文本的翻译键 (例如 'panel.attributes') */
|
||||
title: string;
|
||||
|
||||
/** 内容: HTML字符串 或 HTMLElement */
|
||||
content: string | HTMLElement;
|
||||
|
||||
/** 标题栏左侧图标 (SVG 字符串, 可选) */
|
||||
icon?: string;
|
||||
|
||||
/** 标题栏右侧额外内容 (可选) */
|
||||
extra?: string | HTMLElement;
|
||||
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
|
||||
/** 自定义类名 */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface CollapseOptions {
|
||||
/** 挂载容器 */
|
||||
container: HTMLElement | string;
|
||||
|
||||
/** 面板项列表 */
|
||||
items: CollapseItemConfig[];
|
||||
|
||||
/** 是否开启手风琴模式 (默认 false) */
|
||||
accordion?: boolean;
|
||||
|
||||
/** 初始展开的面板 ID 列表 */
|
||||
activeIds?: string[];
|
||||
|
||||
/** 是否显示边框 (默认 true) */
|
||||
bordered?: boolean;
|
||||
|
||||
/** 是否幽灵模式 (默认 false) */
|
||||
ghost?: boolean;
|
||||
|
||||
/** 自定义类名 */
|
||||
className?: string;
|
||||
|
||||
/** 切换面板时的回调 */
|
||||
onChange?: (activeIds: string[]) => void;
|
||||
}
|
||||
Reference in New Issue
Block a user