Files
dees-catalog/ts_web/elements/00group-appui/dees-appui-view/dees-appui-view.ts

180 lines
4.2 KiB
TypeScript
Raw Normal View History

import * as interfaces from '../../interfaces/index.js';
import {
DeesElement,
type TemplateResult,
property,
customElement,
html,
css,
cssManager,
state,
} from '@design.estate/dees-element';
import '../dees-appui-tabs/dees-appui-tabs.js';
import type { DeesAppuiTabs } from '../dees-appui-tabs/dees-appui-tabs.js';
import { demoFunc } from './dees-appui-view.demo.js';
import { themeDefaultStyles } from '../../00theme.js';
export interface IViewTab extends interfaces.IMenuItem {
content?: TemplateResult | (() => TemplateResult);
useSlotName?: boolean;
slotName?: string;
paddingPx?: number;
}
export interface IAppView {
id: string;
name: string;
description?: string;
iconName?: string;
tabs: IViewTab[];
menuItems?: interfaces.IMenuItem[];
}
@customElement('dees-appui-view')
export class DeesAppuiView extends DeesElement {
public static demo = demoFunc;
// INSTANCE
@property({ type: Object })
accessor viewConfig: IAppView;
@state()
accessor selectedTab: IViewTab | null = null;
@state()
accessor tabs: DeesAppuiTabs;
@property({ type: Number })
accessor paddingPx: number = 16;
public static styles = [
themeDefaultStyles,
cssManager.defaultStyles,
css`
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
:host {
display: block;
position: relative;
width: 100%;
height: 100%;
background: #161616;
}
.view-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.view-header {
background: #000000;
border-bottom: 1px solid #333;
flex-shrink: 0;
}
.view-content {
flex: 1;
position: relative;
overflow: hidden;
}
.tab-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: auto;
opacity: 0;
transition: opacity 0.2s;
}
.tab-content.active {
opacity: 1;
}
dees-appui-tabs {
height: 60px;
}
`,
];
public render(): TemplateResult {
if (!this.viewConfig) {
return html`<div>No view configuration provided</div>`;
}
return html`
<div class="view-container">
<div class="view-header">
<dees-appui-tabs
.tabs=${this.viewConfig.tabs}
.selectedTab=${this.selectedTab}
@tab-select=${(e: CustomEvent) => this.handleTabSelect(e)}
></dees-appui-tabs>
</div>
<div class="view-content">
${this.viewConfig.tabs.map((tab) => {
const isActive = tab === this.selectedTab;
const content = typeof tab.content === 'function' ? tab.content() : tab.content;
const padding = tab.paddingPx ?? this.paddingPx;
return html`
<div class="tab-content ${isActive ? 'active' : ''}" style="padding: ${padding}px;">
${content
? content
: tab.useSlotName
? html`<slot name="${tab.slotName || tab.key}"></slot>`
: ''}
</div>
`;
})}
</div>
</div>
`;
}
async firstUpdated() {
this.tabs = this.shadowRoot.querySelector('dees-appui-tabs');
if (this.viewConfig?.tabs?.length > 0) {
this.selectedTab = this.viewConfig.tabs[0];
}
}
private handleTabSelect(e: CustomEvent) {
this.selectedTab = e.detail.tab;
// Re-emit the event with view context
this.dispatchEvent(new CustomEvent('view-tab-select', {
detail: {
view: this.viewConfig,
tab: e.detail.tab
},
bubbles: true,
composed: true
}));
}
// Public methods for external control
public selectTab(tabKey: string) {
const tab = this.viewConfig.tabs.find(t => t.key === tabKey);
if (tab) {
this.selectedTab = tab;
if (this.tabs) {
this.tabs.selectedTab = tab;
}
}
}
public getMenuItems(): interfaces.IMenuItem[] {
return this.viewConfig?.menuItems || [];
}
public getTabs(): IViewTab[] {
return this.viewConfig?.tabs || [];
}
}