feat(appui-tabs): add closeable tabs and auto-hide behavior for content tabs, plus API and events to control them
This commit is contained in:
@@ -1,5 +1,212 @@
|
||||
import { html, cssManager } from '@design.estate/dees-element';
|
||||
import { html, cssManager, css, DeesElement, customElement, state } from '@design.estate/dees-element';
|
||||
import * as interfaces from '../../interfaces/index.js';
|
||||
import type { DeesAppuiTabs } from './dees-appui-tabs.js';
|
||||
|
||||
// Interactive demo component for closeable tabs
|
||||
@customElement('demo-closeable-tabs')
|
||||
class DemoCloseableTabs extends DeesElement {
|
||||
@state()
|
||||
accessor tabs: interfaces.IMenuItem[] = [
|
||||
{ key: 'Main', iconName: 'lucide:home', action: () => console.log('Main clicked') },
|
||||
];
|
||||
|
||||
@state()
|
||||
accessor tabCounter: number = 0;
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
button {
|
||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
|
||||
border: 1px solid ${cssManager.bdTheme('rgba(59, 130, 246, 0.3)', 'rgba(59, 130, 246, 0.3)')};
|
||||
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
button:hover {
|
||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.2)', 'rgba(59, 130, 246, 0.2)')};
|
||||
}
|
||||
.info {
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
background: ${cssManager.bdTheme('rgba(0,0,0,0.02)', 'rgba(255,255,255,0.02)')};
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
||||
}
|
||||
`
|
||||
];
|
||||
|
||||
private addTab() {
|
||||
this.tabCounter++;
|
||||
const tabKey = `Document ${this.tabCounter}`;
|
||||
this.tabs = [
|
||||
...this.tabs,
|
||||
{
|
||||
key: tabKey,
|
||||
iconName: 'lucide:file',
|
||||
action: () => console.log(`${tabKey} clicked`),
|
||||
closeable: true,
|
||||
onClose: () => this.removeTab(tabKey)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private removeTab(tabKey: string) {
|
||||
this.tabs = this.tabs.filter(t => t.key !== tabKey);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<dees-appui-tabs
|
||||
.tabs=${this.tabs}
|
||||
@tab-close=${(e: CustomEvent) => this.removeTab(e.detail.tab.key)}
|
||||
></dees-appui-tabs>
|
||||
<div class="controls">
|
||||
<button @click=${() => this.addTab()}>+ Add New Tab</button>
|
||||
</div>
|
||||
<div class="info">
|
||||
Click the X button on tabs to close them. The "Main" tab is not closeable.
|
||||
<br>Current tabs: ${this.tabs.length}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Interactive demo for auto-hide feature
|
||||
@customElement('demo-autohide-tabs')
|
||||
class DemoAutoHideTabs extends DeesElement {
|
||||
@state()
|
||||
accessor tabs: interfaces.IMenuItem[] = [
|
||||
{ key: 'Tab 1', iconName: 'lucide:file', action: () => console.log('Tab 1') },
|
||||
{ key: 'Tab 2', iconName: 'lucide:file', action: () => console.log('Tab 2') },
|
||||
];
|
||||
|
||||
@state()
|
||||
accessor autoHide: boolean = true;
|
||||
|
||||
@state()
|
||||
accessor threshold: number = 1;
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.tabs-container {
|
||||
min-height: 60px;
|
||||
border: 1px dashed ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.tabs-container dees-appui-tabs {
|
||||
width: 100%;
|
||||
}
|
||||
.placeholder {
|
||||
color: ${cssManager.bdTheme('#a1a1aa', '#71717a')};
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
button {
|
||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
|
||||
border: 1px solid ${cssManager.bdTheme('rgba(59, 130, 246, 0.3)', 'rgba(59, 130, 246, 0.3)')};
|
||||
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
button:hover {
|
||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.2)', 'rgba(59, 130, 246, 0.2)')};
|
||||
}
|
||||
button.danger {
|
||||
background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.1)', 'rgba(239, 68, 68, 0.1)')};
|
||||
border-color: ${cssManager.bdTheme('rgba(239, 68, 68, 0.3)', 'rgba(239, 68, 68, 0.3)')};
|
||||
color: ${cssManager.bdTheme('#ef4444', '#f87171')};
|
||||
}
|
||||
button.danger:hover {
|
||||
background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.2)', 'rgba(239, 68, 68, 0.2)')};
|
||||
}
|
||||
.info {
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
background: ${cssManager.bdTheme('rgba(0,0,0,0.02)', 'rgba(255,255,255,0.02)')};
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
||||
}
|
||||
`
|
||||
];
|
||||
|
||||
private tabCounter = 2;
|
||||
|
||||
private addTab() {
|
||||
this.tabCounter++;
|
||||
this.tabs = [...this.tabs, {
|
||||
key: `Tab ${this.tabCounter}`,
|
||||
iconName: 'lucide:file',
|
||||
action: () => console.log(`Tab ${this.tabCounter}`)
|
||||
}];
|
||||
}
|
||||
|
||||
private removeLastTab() {
|
||||
if (this.tabs.length > 0) {
|
||||
this.tabs = this.tabs.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
private clearTabs() {
|
||||
this.tabs = [];
|
||||
}
|
||||
|
||||
render() {
|
||||
const shouldHide = this.autoHide && this.tabs.length <= this.threshold;
|
||||
|
||||
return html`
|
||||
<div class="tabs-container">
|
||||
${shouldHide
|
||||
? html`<span class="placeholder">Tabs hidden (${this.tabs.length} tabs ≤ threshold ${this.threshold})</span>`
|
||||
: html`<dees-appui-tabs
|
||||
.tabs=${this.tabs}
|
||||
.autoHide=${this.autoHide}
|
||||
.autoHideThreshold=${this.threshold}
|
||||
></dees-appui-tabs>`
|
||||
}
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button @click=${() => this.addTab()}>+ Add Tab</button>
|
||||
<button class="danger" @click=${() => this.removeLastTab()}>- Remove Tab</button>
|
||||
<button class="danger" @click=${() => this.clearTabs()}>Clear All</button>
|
||||
<button @click=${() => { this.threshold = 0; }}>Threshold: 0</button>
|
||||
<button @click=${() => { this.threshold = 1; }}>Threshold: 1</button>
|
||||
<button @click=${() => { this.threshold = 2; }}>Threshold: 2</button>
|
||||
</div>
|
||||
<div class="info">
|
||||
Auto-hide: ${this.autoHide ? 'ON' : 'OFF'} | Threshold: ${this.threshold} | Tabs: ${this.tabs.length}
|
||||
<br>Tabs will hide when count ≤ threshold.
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export const demoFunc = () => {
|
||||
const horizontalTabs: interfaces.IMenuItem[] = [
|
||||
@@ -71,6 +278,16 @@ export const demoFunc = () => {
|
||||
${demoContent('Select a tab to see the smooth sliding animation of the indicator. The indicator automatically adjusts its width to match the tab content with minimal padding.')}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Closeable Tabs (Browser-style)</div>
|
||||
<demo-closeable-tabs></demo-closeable-tabs>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Auto-hide Tabs</div>
|
||||
<demo-autohide-tabs></demo-autohide-tabs>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Vertical Tabs Layout</div>
|
||||
<div class="two-column">
|
||||
|
||||
Reference in New Issue
Block a user