update
This commit is contained in:
@ -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
|
||||
- Fixed indicator positioning to be perfectly centered on tab content
|
||||
- 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
|
||||
|
||||
## 2025-06-27 - 1.10.1 - fix(modal)
|
||||
|
@ -529,9 +529,9 @@ Base container component for application layout structure with integrated appbar
|
||||
|
||||
// Main menu configuration (left sidebar)
|
||||
.mainmenuTabs=${[
|
||||
{ key: 'dashboard', iconName: 'home', action: () => {} },
|
||||
{ key: 'projects', iconName: 'folder', action: () => {} },
|
||||
{ key: 'settings', iconName: 'cog', action: () => {} }
|
||||
{ key: 'dashboard', iconName: 'lucide:home', action: () => {} },
|
||||
{ key: 'projects', iconName: 'lucide:folder', action: () => {} },
|
||||
{ key: 'settings', iconName: 'lucide:settings', action: () => {} }
|
||||
]}
|
||||
.mainmenuSelectedTab=${selectedTab}
|
||||
|
||||
@ -545,7 +545,7 @@ Base container component for application layout structure with integrated appbar
|
||||
|
||||
// Main content tabs
|
||||
.maincontentTabs=${[
|
||||
{ key: 'tab1', iconName: 'file', action: () => {} }
|
||||
{ key: 'tab1', iconName: 'lucide:file', action: () => {} }
|
||||
]}
|
||||
|
||||
// Event handlers
|
||||
|
@ -65,10 +65,10 @@ export const demoFunc = () => {
|
||||
|
||||
// Main menu tabs (left sidebar)
|
||||
const mainMenuTabs: ITab[] = [
|
||||
{ key: 'dashboard', iconName: 'home', action: () => console.log('Dashboard selected') },
|
||||
{ key: 'projects', iconName: 'folder', action: () => console.log('Projects selected') },
|
||||
{ key: 'analytics', iconName: 'lineChart', action: () => console.log('Analytics selected') },
|
||||
{ key: 'settings', iconName: 'settings', action: () => console.log('Settings selected') },
|
||||
{ key: 'dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard selected') },
|
||||
{ key: 'projects', iconName: 'lucide:folder', action: () => console.log('Projects selected') },
|
||||
{ key: 'analytics', iconName: 'lucide:lineChart', action: () => console.log('Analytics selected') },
|
||||
{ key: 'settings', iconName: 'lucide:settings', action: () => console.log('Settings selected') },
|
||||
];
|
||||
|
||||
// Selector options (second sidebar)
|
||||
@ -83,9 +83,9 @@ export const demoFunc = () => {
|
||||
|
||||
// Main content tabs
|
||||
const mainContentTabs: ITab[] = [
|
||||
{ key: 'Details', iconName: 'file', action: () => console.log('Details tab') },
|
||||
{ key: 'Logs', iconName: 'list', action: () => console.log('Logs tab') },
|
||||
{ key: 'Metrics', iconName: 'lineChart', action: () => console.log('Metrics tab') },
|
||||
{ key: 'Details', iconName: 'lucide:file', action: () => console.log('Details tab') },
|
||||
{ key: 'Logs', iconName: 'lucide:list', action: () => console.log('Logs tab') },
|
||||
{ key: 'Metrics', iconName: 'lucide:lineChart', action: () => console.log('Metrics tab') },
|
||||
];
|
||||
|
||||
// Profile menu items
|
||||
|
@ -19,9 +19,9 @@ export class DeesAppuiMaincontent extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<dees-appui-maincontent
|
||||
.tabs=${[
|
||||
{ key: 'Overview', iconName: 'home', action: () => console.log('Overview') },
|
||||
{ key: 'Details', iconName: 'file', action: () => console.log('Details') },
|
||||
{ key: 'Settings', iconName: 'cog', action: () => console.log('Settings') },
|
||||
{ key: 'Overview', iconName: 'lucide:home', action: () => console.log('Overview') },
|
||||
{ key: 'Details', iconName: 'lucide:file', action: () => console.log('Details') },
|
||||
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
|
||||
]}
|
||||
>
|
||||
<div slot="content" style="padding: 40px; color: #ccc;">
|
||||
|
@ -22,10 +22,10 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<dees-appui-mainmenu
|
||||
.tabs=${[
|
||||
{ key: 'Dashboard', iconName: 'home', action: () => console.log('Dashboard') },
|
||||
{ key: 'Projects', iconName: 'folder', action: () => console.log('Projects') },
|
||||
{ key: 'Analytics', iconName: 'lineChart', action: () => console.log('Analytics') },
|
||||
{ key: 'Settings', iconName: 'settings', action: () => console.log('Settings') },
|
||||
{ key: 'Dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard') },
|
||||
{ key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects') },
|
||||
{ key: 'Analytics', iconName: 'lucide:lineChart', action: () => console.log('Analytics') },
|
||||
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
|
||||
]}
|
||||
></dees-appui-mainmenu>
|
||||
`;
|
||||
@ -35,7 +35,7 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
// INSTANCE
|
||||
@property({ type: Array })
|
||||
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()
|
||||
@ -112,7 +112,7 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
this.updateTab(tabArg);
|
||||
}}"
|
||||
>
|
||||
<dees-icon .icon="${tabArg.iconName ? `lucide:${tabArg.iconName}` : ''}"></dees-icon>
|
||||
<dees-icon .icon="${tabArg.iconName || ''}"></dees-icon>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
|
@ -14,94 +14,94 @@ import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
@customElement('dees-appui-tabs')
|
||||
export class DeesAppuiTabs extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<style>
|
||||
.demo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
padding: 48px;
|
||||
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: ${cssManager.bdTheme('#ffffff', '#18181b')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
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=${[
|
||||
{ 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>
|
||||
public static demo = () => {
|
||||
const horizontalTabs: interfaces.ITab[] = [
|
||||
{ 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') },
|
||||
];
|
||||
|
||||
const verticalTabs: interfaces.ITab[] = [
|
||||
{ key: 'Profile', iconName: 'lucide:user', action: () => console.log('Profile clicked') },
|
||||
{ key: 'Security', iconName: 'lucide:shield', action: () => console.log('Security clicked') },
|
||||
{ key: 'Notifications', iconName: 'lucide:bell', action: () => console.log('Notifications clicked') },
|
||||
{ key: 'Integrations', iconName: 'lucide:link', action: () => console.log('Integrations clicked') },
|
||||
{ key: 'Advanced', iconName: 'lucide:code', action: () => console.log('Advanced clicked') },
|
||||
];
|
||||
|
||||
const noIndicatorTabs: interfaces.ITab[] = [
|
||||
{ key: 'All', action: () => console.log('All clicked') },
|
||||
{ key: 'Active', action: () => console.log('Active clicked') },
|
||||
{ key: 'Completed', action: () => console.log('Completed clicked') },
|
||||
{ key: 'Archived', action: () => console.log('Archived clicked') },
|
||||
];
|
||||
|
||||
const demoContent = (text: string) => html`
|
||||
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
|
||||
${text}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Vertical Tabs Layout</div>
|
||||
<div class="two-column">
|
||||
<dees-appui-tabs
|
||||
.tabStyle=${'vertical'}
|
||||
.tabs=${[
|
||||
{ key: 'Profile', iconName: 'lucide:user', action: () => console.log('Profile clicked') },
|
||||
{ key: 'Security', iconName: 'lucide:shield', action: () => console.log('Security clicked') },
|
||||
{ key: 'Notifications', iconName: 'lucide:bell', action: () => console.log('Notifications clicked') },
|
||||
{ key: 'Integrations', iconName: 'lucide:link', action: () => console.log('Integrations clicked') },
|
||||
{ key: 'Advanced', iconName: 'lucide:code', action: () => console.log('Advanced clicked') },
|
||||
]}
|
||||
></dees-appui-tabs>
|
||||
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
|
||||
Vertical tabs work great for settings pages and navigation menus. The animated indicator smoothly transitions between selections.
|
||||
`;
|
||||
|
||||
return html`
|
||||
<style>
|
||||
.demo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
padding: 48px;
|
||||
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: ${cssManager.bdTheme('#ffffff', '#18181b')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
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 class="section">
|
||||
<div class="section-title">Without Indicator</div>
|
||||
<dees-appui-tabs .showTabIndicator=${false} .tabs=${noIndicatorTabs}>
|
||||
${demoContent('Tabs can also be used without the animated indicator by setting showTabIndicator to false.')}
|
||||
</dees-appui-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Without Indicator</div>
|
||||
<dees-appui-tabs
|
||||
.showTabIndicator=${false}
|
||||
.tabs=${[
|
||||
{ key: 'All', action: () => console.log('All clicked') },
|
||||
{ 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>
|
||||
`;
|
||||
`;
|
||||
};
|
||||
|
||||
// INSTANCE
|
||||
@property({
|
||||
@ -378,32 +378,44 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
private indicatorInitialized = false;
|
||||
|
||||
private updateTabIndicator() {
|
||||
if (!this.selectedTab || !this.showTabIndicator) {
|
||||
return;
|
||||
if (!this.shouldShowIndicator()) 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);
|
||||
if (selectedIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the correct tab element using nth-child
|
||||
const selector = this.tabStyle === 'horizontal'
|
||||
const isHorizontal = this.tabStyle === 'horizontal';
|
||||
const selector = isHorizontal
|
||||
? `.tabs-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`
|
||||
: `.vertical-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`;
|
||||
|
||||
const selectedTabElement = this.shadowRoot.querySelector(selector) as HTMLElement;
|
||||
|
||||
if (!selectedTabElement) {
|
||||
return;
|
||||
}
|
||||
return this.shadowRoot.querySelector(selector);
|
||||
}
|
||||
|
||||
const indicator = this.shadowRoot.querySelector('.tabIndicator') as HTMLElement;
|
||||
if (!indicator) {
|
||||
return;
|
||||
}
|
||||
private getIndicatorElement(): HTMLElement | null {
|
||||
return this.shadowRoot.querySelector('.tabIndicator');
|
||||
}
|
||||
|
||||
// Disable transition for initial positioning
|
||||
private handleInitialTransition(indicator: HTMLElement): void {
|
||||
if (!this.indicatorInitialized) {
|
||||
indicator.classList.add('no-transition');
|
||||
this.indicatorInitialized = true;
|
||||
@ -412,35 +424,28 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
indicator.classList.remove('no-transition');
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.tabStyle === 'horizontal') {
|
||||
const tabContent = selectedTabElement.querySelector('.tab-content') as HTMLElement;
|
||||
|
||||
if (tabContent) {
|
||||
// Use getBoundingClientRect for accurate positioning
|
||||
const wrapperRect = indicator.parentElement.getBoundingClientRect();
|
||||
const contentRect = tabContent.getBoundingClientRect();
|
||||
|
||||
// Calculate the position relative to wrapper
|
||||
const contentLeft = contentRect.left - wrapperRect.left;
|
||||
|
||||
// Set width to match content plus small padding
|
||||
const indicatorWidth = contentRect.width + 8;
|
||||
|
||||
// Center the indicator on content (shift left by half the extra width)
|
||||
const indicatorLeft = contentLeft - 4;
|
||||
|
||||
indicator.style.width = `${indicatorWidth}px`;
|
||||
indicator.style.left = `${indicatorLeft}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`;
|
||||
}
|
||||
private updateHorizontalIndicator(indicator: HTMLElement, tabElement: HTMLElement): void {
|
||||
const tabContent = tabElement.querySelector('.tab-content') as HTMLElement;
|
||||
if (!tabContent) return;
|
||||
|
||||
indicator.style.opacity = '1';
|
||||
const wrapperRect = indicator.parentElement.getBoundingClientRect();
|
||||
const contentRect = tabContent.getBoundingClientRect();
|
||||
|
||||
const contentLeft = contentRect.left - wrapperRect.left;
|
||||
const indicatorWidth = contentRect.width + 8;
|
||||
const indicatorLeft = contentLeft - 4;
|
||||
|
||||
indicator.style.width = `${indicatorWidth}px`;
|
||||
indicator.style.left = `${indicatorLeft}px`;
|
||||
}
|
||||
|
||||
private updateVerticalIndicator(indicator: HTMLElement, tabElement: HTMLElement): void {
|
||||
const tabsContainer = this.shadowRoot.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
|
||||
if (!tabsContainer) return;
|
||||
|
||||
indicator.style.top = `${tabElement.offsetTop + tabsContainer.offsetTop}px`;
|
||||
indicator.style.height = `${tabElement.clientHeight}px`;
|
||||
}
|
||||
}
|
@ -35,17 +35,17 @@ export class DeesAppuiView extends DeesElement {
|
||||
id: 'demo-view',
|
||||
name: 'Demo View',
|
||||
description: 'A demonstration view',
|
||||
iconName: 'home',
|
||||
iconName: 'lucide:home',
|
||||
tabs: [
|
||||
{
|
||||
key: 'overview',
|
||||
iconName: 'chart-line',
|
||||
iconName: 'lucide:lineChart',
|
||||
action: () => console.log('Overview tab'),
|
||||
content: html`<div style="padding: 20px;">Overview Content</div>`
|
||||
},
|
||||
{
|
||||
key: 'details',
|
||||
iconName: 'file-alt',
|
||||
iconName: 'lucide:fileText',
|
||||
action: () => console.log('Details tab'),
|
||||
content: html`<div style="padding: 20px;">Details Content</div>`
|
||||
}
|
||||
|
Reference in New Issue
Block a user