import { DeesElement, type TemplateResult, customElement, html, css, cssManager, state, } from '@design.estate/dees-element'; import { themeDefaultStyles } from '../../00theme.js'; import '../../dees-icon/dees-icon.js'; import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js'; import type { IBottomBarWidget, IBottomBarAction, IBottomBarAPI, } from '../../interfaces/appconfig.js'; import { demoFunc } from './dees-appui-bottombar.demo.js'; declare global { interface HTMLElementTagNameMap { 'dees-appui-bottombar': DeesAppuiBottombar; } } @customElement('dees-appui-bottombar') export class DeesAppuiBottombar extends DeesElement implements IBottomBarAPI { public static demo = demoFunc; // INSTANCE PROPERTIES @state() accessor widgets: IBottomBarWidget[] = []; @state() accessor actions: IBottomBarAction[] = []; public static styles = [ themeDefaultStyles, cssManager.defaultStyles, css` :host { display: block; height: 24px; flex-shrink: 0; user-select: none; } .bottom-bar { height: 24px; display: flex; align-items: center; padding: 0 8px; gap: 4px; background: ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(0 0% 6%)')}; border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 15%)')}; font-size: 11px; color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')}; } .widget { display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 3px; cursor: pointer; transition: background 0.15s ease, color 0.15s ease; white-space: nowrap; } .widget:hover { background: ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 12%)')}; color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')}; } .widget dees-icon { flex-shrink: 0; } .widget-separator { width: 1px; height: 14px; background: ${cssManager.bdTheme('hsl(0 0% 80%)', 'hsl(0 0% 20%)')}; margin: 0 4px; } /* Status colors matching dees-workspace-bottombar */ .widget.active { color: ${cssManager.bdTheme('hsl(210 100% 45%)', 'hsl(210 100% 60%)')}; } .widget.success { color: ${cssManager.bdTheme('hsl(142 70% 35%)', 'hsl(142 70% 50%)')}; } .widget.warning { color: ${cssManager.bdTheme('hsl(38 92% 45%)', 'hsl(38 92% 55%)')}; } .widget.error { color: ${cssManager.bdTheme('hsl(0 70% 50%)', 'hsl(0 70% 60%)')}; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .spinning { animation: spin 1s linear infinite; } .spacer { flex: 1; } .action-button { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 3px; cursor: pointer; transition: background 0.15s ease; color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')}; } .action-button:hover { background: ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 12%)')}; color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')}; } .action-button.disabled { opacity: 0.5; cursor: not-allowed; } .action-button.disabled:hover { background: transparent; color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')}; } `, ]; public render(): TemplateResult { const leftWidgets = this.widgets .filter(w => w.position !== 'right') .sort((a, b) => (a.order || 0) - (b.order || 0)); const rightWidgets = this.widgets .filter(w => w.position === 'right') .sort((a, b) => (a.order || 0) - (b.order || 0)); const leftActions = this.actions.filter(a => a.position === 'left'); const rightActions = this.actions.filter(a => a.position !== 'left'); return html`
${leftActions.map(action => this.renderAction(action))} ${leftWidgets.map((widget, index) => html` ${index > 0 || leftActions.length > 0 ? html`
` : ''} ${this.renderWidget(widget)} `)}
${rightWidgets.map((widget, index) => html` ${this.renderWidget(widget)} ${index < rightWidgets.length - 1 || rightActions.length > 0 ? html`
` : ''} `)} ${rightActions.map(action => this.renderAction(action))}
`; } private renderWidget(widget: IBottomBarWidget): TemplateResult { const statusClass = widget.status && widget.status !== 'idle' ? widget.status : ''; const iconName = widget.iconName ? (widget.iconName.startsWith('lucide:') ? widget.iconName : `lucide:${widget.iconName}`) : ''; return html`
widget.onClick?.()} @contextmenu=${(e: MouseEvent) => this.handleWidgetContextMenu(e, widget)} > ${iconName ? html` ` : ''} ${widget.label ? html`${widget.label}` : ''}
`; } private renderAction(action: IBottomBarAction): TemplateResult { const iconName = action.iconName.startsWith('lucide:') ? action.iconName : `lucide:${action.iconName}`; return html`
!action.disabled && action.onClick?.()} >
`; } private async handleWidgetContextMenu(e: MouseEvent, widget: IBottomBarWidget): Promise { if (!widget.contextMenuItems || widget.contextMenuItems.length === 0) return; e.preventDefault(); const menuItems: Parameters[1] = []; for (const item of widget.contextMenuItems) { if (item.divider) { menuItems.push({ divider: true }); } else { menuItems.push({ name: item.name, iconName: item.iconName, action: async () => { await item.action(); }, disabled: item.disabled, }); } } await DeesContextmenu.openContextMenuWithOptions(e, menuItems); } // ========================================== // API METHODS (implements IBottomBarAPI) // ========================================== /** * Add a widget to the bottom bar */ public addWidget(widget: IBottomBarWidget): void { // Remove existing widget with same ID if present this.widgets = this.widgets.filter(w => w.id !== widget.id); this.widgets = [...this.widgets, widget]; } /** * Update an existing widget by ID */ public updateWidget(id: string, update: Partial): void { this.widgets = this.widgets.map(w => w.id === id ? { ...w, ...update } : w ); } /** * Remove a widget by ID */ public removeWidget(id: string): void { this.widgets = this.widgets.filter(w => w.id !== id); } /** * Get a widget by ID */ public getWidget(id: string): IBottomBarWidget | undefined { return this.widgets.find(w => w.id === id); } /** * Clear all widgets */ public clearWidgets(): void { this.widgets = []; } /** * Add an action button */ public addAction(action: IBottomBarAction): void { this.actions = this.actions.filter(a => a.id !== action.id); this.actions = [...this.actions, action]; } /** * Remove an action by ID */ public removeAction(id: string): void { this.actions = this.actions.filter(a => a.id !== id); } /** * Clear all actions */ public clearActions(): void { this.actions = []; } }