This commit is contained in:
Juergen Kunz
2025-06-27 22:55:20 +00:00
parent 7b6c135cd3
commit 811737adcd
7 changed files with 163 additions and 154 deletions

View File

@ -10,7 +10,11 @@ Update multiple components with shadcn-aligned styling and improved animations
- Updated dees-appui-tabs with animated sliding indicator for both horizontal and vertical layouts - Updated dees-appui-tabs with animated sliding indicator for both horizontal and vertical layouts
- Fixed indicator positioning to be perfectly centered on tab content - Fixed indicator positioning to be perfectly centered on tab content
- Indicator width is content width + 8px for minimal visual padding - Indicator width is content width + 8px for minimal visual padding
- Adjusted tab spacing to start more to the left with tighter padding - Fixed tab content centering by using consistent padding (12px → 16px on all sides)
- Fixed icon rendering by correcting property name from .iconName to .icon
- Added visual separators between tabs for better distinction
- Added subtle hover backgrounds for improved interactivity
- Refactored tabs component code for better maintainability and elegance
- Improved overall spacing and visual consistency across components - Improved overall spacing and visual consistency across components
## 2025-06-27 - 1.10.1 - fix(modal) ## 2025-06-27 - 1.10.1 - fix(modal)

View File

@ -529,9 +529,9 @@ Base container component for application layout structure with integrated appbar
// Main menu configuration (left sidebar) // Main menu configuration (left sidebar)
.mainmenuTabs=${[ .mainmenuTabs=${[
{ key: 'dashboard', iconName: 'home', action: () => {} }, { key: 'dashboard', iconName: 'lucide:home', action: () => {} },
{ key: 'projects', iconName: 'folder', action: () => {} }, { key: 'projects', iconName: 'lucide:folder', action: () => {} },
{ key: 'settings', iconName: 'cog', action: () => {} } { key: 'settings', iconName: 'lucide:settings', action: () => {} }
]} ]}
.mainmenuSelectedTab=${selectedTab} .mainmenuSelectedTab=${selectedTab}
@ -545,7 +545,7 @@ Base container component for application layout structure with integrated appbar
// Main content tabs // Main content tabs
.maincontentTabs=${[ .maincontentTabs=${[
{ key: 'tab1', iconName: 'file', action: () => {} } { key: 'tab1', iconName: 'lucide:file', action: () => {} }
]} ]}
// Event handlers // Event handlers

View File

@ -65,10 +65,10 @@ export const demoFunc = () => {
// Main menu tabs (left sidebar) // Main menu tabs (left sidebar)
const mainMenuTabs: ITab[] = [ const mainMenuTabs: ITab[] = [
{ key: 'dashboard', iconName: 'home', action: () => console.log('Dashboard selected') }, { key: 'dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard selected') },
{ key: 'projects', iconName: 'folder', action: () => console.log('Projects selected') }, { key: 'projects', iconName: 'lucide:folder', action: () => console.log('Projects selected') },
{ key: 'analytics', iconName: 'lineChart', action: () => console.log('Analytics selected') }, { key: 'analytics', iconName: 'lucide:lineChart', action: () => console.log('Analytics selected') },
{ key: 'settings', iconName: 'settings', action: () => console.log('Settings selected') }, { key: 'settings', iconName: 'lucide:settings', action: () => console.log('Settings selected') },
]; ];
// Selector options (second sidebar) // Selector options (second sidebar)
@ -83,9 +83,9 @@ export const demoFunc = () => {
// Main content tabs // Main content tabs
const mainContentTabs: ITab[] = [ const mainContentTabs: ITab[] = [
{ key: 'Details', iconName: 'file', action: () => console.log('Details tab') }, { key: 'Details', iconName: 'lucide:file', action: () => console.log('Details tab') },
{ key: 'Logs', iconName: 'list', action: () => console.log('Logs tab') }, { key: 'Logs', iconName: 'lucide:list', action: () => console.log('Logs tab') },
{ key: 'Metrics', iconName: 'lineChart', action: () => console.log('Metrics tab') }, { key: 'Metrics', iconName: 'lucide:lineChart', action: () => console.log('Metrics tab') },
]; ];
// Profile menu items // Profile menu items

View File

@ -19,9 +19,9 @@ export class DeesAppuiMaincontent extends DeesElement {
public static demo = () => html` public static demo = () => html`
<dees-appui-maincontent <dees-appui-maincontent
.tabs=${[ .tabs=${[
{ key: 'Overview', iconName: 'home', action: () => console.log('Overview') }, { key: 'Overview', iconName: 'lucide:home', action: () => console.log('Overview') },
{ key: 'Details', iconName: 'file', action: () => console.log('Details') }, { key: 'Details', iconName: 'lucide:file', action: () => console.log('Details') },
{ key: 'Settings', iconName: 'cog', action: () => console.log('Settings') }, { key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
]} ]}
> >
<div slot="content" style="padding: 40px; color: #ccc;"> <div slot="content" style="padding: 40px; color: #ccc;">

View File

@ -22,10 +22,10 @@ export class DeesAppuiMainmenu extends DeesElement {
public static demo = () => html` public static demo = () => html`
<dees-appui-mainmenu <dees-appui-mainmenu
.tabs=${[ .tabs=${[
{ key: 'Dashboard', iconName: 'home', action: () => console.log('Dashboard') }, { key: 'Dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard') },
{ key: 'Projects', iconName: 'folder', action: () => console.log('Projects') }, { key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects') },
{ key: 'Analytics', iconName: 'lineChart', action: () => console.log('Analytics') }, { key: 'Analytics', iconName: 'lucide:lineChart', action: () => console.log('Analytics') },
{ key: 'Settings', iconName: 'settings', action: () => console.log('Settings') }, { key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
]} ]}
></dees-appui-mainmenu> ></dees-appui-mainmenu>
`; `;
@ -35,7 +35,7 @@ export class DeesAppuiMainmenu extends DeesElement {
// INSTANCE // INSTANCE
@property({ type: Array }) @property({ type: Array })
public tabs: interfaces.ITab[] = [ public tabs: interfaces.ITab[] = [
{ key: '⚠️ Please set tabs', iconName: 'alertTriangle', action: () => console.warn('No tabs configured for mainmenu') }, { key: '⚠️ Please set tabs', iconName: 'lucide:alertTriangle', action: () => console.warn('No tabs configured for mainmenu') },
]; ];
@property() @property()
@ -112,7 +112,7 @@ export class DeesAppuiMainmenu extends DeesElement {
this.updateTab(tabArg); this.updateTab(tabArg);
}}" }}"
> >
<dees-icon .icon="${tabArg.iconName ? `lucide:${tabArg.iconName}` : ''}"></dees-icon> <dees-icon .icon="${tabArg.iconName || ''}"></dees-icon>
</div> </div>
`; `;
})} })}

View File

@ -14,94 +14,94 @@ import * as domtools from '@design.estate/dees-domtools';
@customElement('dees-appui-tabs') @customElement('dees-appui-tabs')
export class DeesAppuiTabs extends DeesElement { export class DeesAppuiTabs extends DeesElement {
public static demo = () => html` public static demo = () => {
<style> const horizontalTabs: interfaces.ITab[] = [
.demo-container { { key: 'Home', iconName: 'lucide:home', action: () => console.log('Home clicked') },
display: flex; { key: 'Analytics Dashboard', iconName: 'lucide:lineChart', action: () => console.log('Analytics clicked') },
flex-direction: column; { key: 'Reports', iconName: 'lucide:fileText', action: () => console.log('Reports clicked') },
gap: 32px; { key: 'User Settings', iconName: 'lucide:settings', action: () => console.log('Settings clicked') },
padding: 48px; { key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help clicked') },
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')}; ];
min-height: 100vh;
}
.section { const verticalTabs: interfaces.ITab[] = [
background: ${cssManager.bdTheme('#ffffff', '#18181b')}; { key: 'Profile', iconName: 'lucide:user', action: () => console.log('Profile clicked') },
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; { key: 'Security', iconName: 'lucide:shield', action: () => console.log('Security clicked') },
border-radius: 8px; { key: 'Notifications', iconName: 'lucide:bell', action: () => console.log('Notifications clicked') },
padding: 24px; { key: 'Integrations', iconName: 'lucide:link', action: () => console.log('Integrations clicked') },
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); { key: 'Advanced', iconName: 'lucide:code', action: () => console.log('Advanced clicked') },
} ];
.section-title { const noIndicatorTabs: interfaces.ITab[] = [
font-size: 18px; { key: 'All', action: () => console.log('All clicked') },
font-weight: 600; { key: 'Active', action: () => console.log('Active clicked') },
margin-bottom: 16px; { key: 'Completed', action: () => console.log('Completed clicked') },
color: ${cssManager.bdTheme('#09090b', '#fafafa')}; { key: 'Archived', action: () => console.log('Archived clicked') },
} ];
.two-column { const demoContent = (text: string) => html`
display: grid; <div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
grid-template-columns: 200px 1fr; ${text}
gap: 24px;
align-items: start;
}
</style>
<div class="demo-container">
<div class="section">
<div class="section-title">Horizontal Tabs with Animated Indicator</div>
<dees-appui-tabs
.tabs=${[
{ key: 'Home', iconName: 'lucide:home', action: () => console.log('Home clicked') },
{ key: 'Analytics Dashboard', iconName: 'lucide:lineChart', action: () => console.log('Analytics clicked') },
{ key: 'Reports', iconName: 'lucide:fileText', action: () => console.log('Reports clicked') },
{ key: 'User Settings', iconName: 'lucide:settings', action: () => console.log('Settings clicked') },
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help clicked') },
]}
>
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
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>
</dees-appui-tabs>
</div> </div>
`;
<div class="section"> return html`
<div class="section-title">Vertical Tabs Layout</div> <style>
<div class="two-column"> .demo-container {
<dees-appui-tabs display: flex;
.tabStyle=${'vertical'} flex-direction: column;
.tabs=${[ gap: 32px;
{ key: 'Profile', iconName: 'lucide:user', action: () => console.log('Profile clicked') }, padding: 48px;
{ key: 'Security', iconName: 'lucide:shield', action: () => console.log('Security clicked') }, background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
{ key: 'Notifications', iconName: 'lucide:bell', action: () => console.log('Notifications clicked') }, min-height: 100vh;
{ key: 'Integrations', iconName: 'lucide:link', action: () => console.log('Integrations clicked') }, }
{ key: 'Advanced', iconName: 'lucide:code', action: () => console.log('Advanced clicked') },
]} .section {
></dees-appui-tabs> background: ${cssManager.bdTheme('#ffffff', '#18181b')};
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};"> border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
Vertical tabs work great for settings pages and navigation menus. The animated indicator smoothly transitions between selections. border-radius: 8px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.two-column {
display: grid;
grid-template-columns: 200px 1fr;
gap: 24px;
align-items: start;
}
</style>
<div class="demo-container">
<div class="section">
<div class="section-title">Horizontal Tabs with Animated Indicator</div>
<dees-appui-tabs .tabs=${horizontalTabs}>
${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.')}
</dees-appui-tabs>
</div>
<div class="section">
<div class="section-title">Vertical Tabs Layout</div>
<div class="two-column">
<dees-appui-tabs .tabStyle=${'vertical'} .tabs=${verticalTabs}></dees-appui-tabs>
${demoContent('Vertical tabs work great for settings pages and navigation menus. The animated indicator smoothly transitions between selections.')}
</div> </div>
</div> </div>
</div>
<div class="section"> <div class="section">
<div class="section-title">Without Indicator</div> <div class="section-title">Without Indicator</div>
<dees-appui-tabs <dees-appui-tabs .showTabIndicator=${false} .tabs=${noIndicatorTabs}>
.showTabIndicator=${false} ${demoContent('Tabs can also be used without the animated indicator by setting showTabIndicator to false.')}
.tabs=${[ </dees-appui-tabs>
{ key: 'All', action: () => console.log('All clicked') }, </div>
{ key: 'Active', action: () => console.log('Active clicked') },
{ key: 'Completed', action: () => console.log('Completed clicked') },
{ key: 'Archived', action: () => console.log('Archived clicked') },
]}
>
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
Tabs can also be used without the animated indicator by setting showTabIndicator to false.
</div>
</dees-appui-tabs>
</div> </div>
</div> `;
`; };
// INSTANCE // INSTANCE
@property({ @property({
@ -378,32 +378,44 @@ export class DeesAppuiTabs extends DeesElement {
private indicatorInitialized = false; private indicatorInitialized = false;
private updateTabIndicator() { private updateTabIndicator() {
if (!this.selectedTab || !this.showTabIndicator) { if (!this.shouldShowIndicator()) return;
return;
const selectedTabElement = this.getSelectedTabElement();
if (!selectedTabElement) return;
const indicator = this.getIndicatorElement();
if (!indicator) return;
this.handleInitialTransition(indicator);
if (this.tabStyle === 'horizontal') {
this.updateHorizontalIndicator(indicator, selectedTabElement);
} else {
this.updateVerticalIndicator(indicator, selectedTabElement);
} }
indicator.style.opacity = '1';
}
private shouldShowIndicator(): boolean {
return this.selectedTab && this.showTabIndicator && this.tabs.includes(this.selectedTab);
}
private getSelectedTabElement(): HTMLElement | null {
const selectedIndex = this.tabs.indexOf(this.selectedTab); const selectedIndex = this.tabs.indexOf(this.selectedTab);
if (selectedIndex === -1) { const isHorizontal = this.tabStyle === 'horizontal';
return; const selector = isHorizontal
}
// Select the correct tab element using nth-child
const selector = this.tabStyle === 'horizontal'
? `.tabs-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})` ? `.tabs-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`
: `.vertical-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`; : `.vertical-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`;
const selectedTabElement = this.shadowRoot.querySelector(selector) as HTMLElement; return this.shadowRoot.querySelector(selector);
}
if (!selectedTabElement) { private getIndicatorElement(): HTMLElement | null {
return; return this.shadowRoot.querySelector('.tabIndicator');
} }
const indicator = this.shadowRoot.querySelector('.tabIndicator') as HTMLElement; private handleInitialTransition(indicator: HTMLElement): void {
if (!indicator) {
return;
}
// Disable transition for initial positioning
if (!this.indicatorInitialized) { if (!this.indicatorInitialized) {
indicator.classList.add('no-transition'); indicator.classList.add('no-transition');
this.indicatorInitialized = true; this.indicatorInitialized = true;
@ -412,35 +424,28 @@ export class DeesAppuiTabs extends DeesElement {
indicator.classList.remove('no-transition'); indicator.classList.remove('no-transition');
}, 50); }, 50);
} }
}
if (this.tabStyle === 'horizontal') { private updateHorizontalIndicator(indicator: HTMLElement, tabElement: HTMLElement): void {
const tabContent = selectedTabElement.querySelector('.tab-content') as HTMLElement; const tabContent = tabElement.querySelector('.tab-content') as HTMLElement;
if (!tabContent) return;
if (tabContent) { const wrapperRect = indicator.parentElement.getBoundingClientRect();
// Use getBoundingClientRect for accurate positioning const contentRect = tabContent.getBoundingClientRect();
const wrapperRect = indicator.parentElement.getBoundingClientRect();
const contentRect = tabContent.getBoundingClientRect();
// Calculate the position relative to wrapper const contentLeft = contentRect.left - wrapperRect.left;
const contentLeft = contentRect.left - wrapperRect.left; const indicatorWidth = contentRect.width + 8;
const indicatorLeft = contentLeft - 4;
// Set width to match content plus small padding indicator.style.width = `${indicatorWidth}px`;
const indicatorWidth = contentRect.width + 8; indicator.style.left = `${indicatorLeft}px`;
}
// Center the indicator on content (shift left by half the extra width) private updateVerticalIndicator(indicator: HTMLElement, tabElement: HTMLElement): void {
const indicatorLeft = contentLeft - 4; const tabsContainer = this.shadowRoot.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
if (!tabsContainer) return;
indicator.style.width = `${indicatorWidth}px`; indicator.style.top = `${tabElement.offsetTop + tabsContainer.offsetTop}px`;
indicator.style.left = `${indicatorLeft}px`; indicator.style.height = `${tabElement.clientHeight}px`;
}
} else {
// For vertical tabs
const tabsContainer = this.shadowRoot.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
indicator.style.top = `${selectedTabElement.offsetTop + tabsContainer.offsetTop}px`;
indicator.style.height = `${selectedTabElement.clientHeight}px`;
}
indicator.style.opacity = '1';
} }
} }

View File

@ -35,17 +35,17 @@ export class DeesAppuiView extends DeesElement {
id: 'demo-view', id: 'demo-view',
name: 'Demo View', name: 'Demo View',
description: 'A demonstration view', description: 'A demonstration view',
iconName: 'home', iconName: 'lucide:home',
tabs: [ tabs: [
{ {
key: 'overview', key: 'overview',
iconName: 'chart-line', iconName: 'lucide:lineChart',
action: () => console.log('Overview tab'), action: () => console.log('Overview tab'),
content: html`<div style="padding: 20px;">Overview Content</div>` content: html`<div style="padding: 20px;">Overview Content</div>`
}, },
{ {
key: 'details', key: 'details',
iconName: 'file-alt', iconName: 'lucide:fileText',
action: () => console.log('Details tab'), action: () => console.log('Details tab'),
content: html`<div style="padding: 20px;">Details Content</div>` content: html`<div style="padding: 20px;">Details Content</div>`
} }