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
|
- 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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;">
|
||||||
|
@ -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>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
|
@ -14,7 +14,37 @@ 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 = () => {
|
||||||
|
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>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html`
|
||||||
<style>
|
<style>
|
||||||
.demo-container {
|
.demo-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -50,58 +80,28 @@ export class DeesAppuiTabs extends DeesElement {
|
|||||||
<div class="demo-container">
|
<div class="demo-container">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-title">Horizontal Tabs with Animated Indicator</div>
|
<div class="section-title">Horizontal Tabs with Animated Indicator</div>
|
||||||
<dees-appui-tabs
|
<dees-appui-tabs .tabs=${horizontalTabs}>
|
||||||
.tabs=${[
|
${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.')}
|
||||||
{ 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>
|
</dees-appui-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-title">Vertical Tabs Layout</div>
|
<div class="section-title">Vertical Tabs Layout</div>
|
||||||
<div class="two-column">
|
<div class="two-column">
|
||||||
<dees-appui-tabs
|
<dees-appui-tabs .tabStyle=${'vertical'} .tabs=${verticalTabs}></dees-appui-tabs>
|
||||||
.tabStyle=${'vertical'}
|
${demoContent('Vertical tabs work great for settings pages and navigation menus. The animated indicator smoothly transitions between selections.')}
|
||||||
.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.
|
|
||||||
</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=${[
|
|
||||||
{ 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>
|
</dees-appui-tabs>
|
||||||
</div>
|
</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) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const indicator = this.shadowRoot.querySelector('.tabIndicator') as HTMLElement;
|
private getIndicatorElement(): HTMLElement | null {
|
||||||
if (!indicator) {
|
return this.shadowRoot.querySelector('.tabIndicator');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable transition for initial positioning
|
private handleInitialTransition(indicator: HTMLElement): void {
|
||||||
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) {
|
|
||||||
// Use getBoundingClientRect for accurate positioning
|
|
||||||
const wrapperRect = indicator.parentElement.getBoundingClientRect();
|
const wrapperRect = indicator.parentElement.getBoundingClientRect();
|
||||||
const contentRect = tabContent.getBoundingClientRect();
|
const contentRect = tabContent.getBoundingClientRect();
|
||||||
|
|
||||||
// Calculate the position relative to wrapper
|
|
||||||
const contentLeft = contentRect.left - wrapperRect.left;
|
const contentLeft = contentRect.left - wrapperRect.left;
|
||||||
|
|
||||||
// Set width to match content plus small padding
|
|
||||||
const indicatorWidth = contentRect.width + 8;
|
const indicatorWidth = contentRect.width + 8;
|
||||||
|
|
||||||
// Center the indicator on content (shift left by half the extra width)
|
|
||||||
const indicatorLeft = contentLeft - 4;
|
const indicatorLeft = contentLeft - 4;
|
||||||
|
|
||||||
indicator.style.width = `${indicatorWidth}px`;
|
indicator.style.width = `${indicatorWidth}px`;
|
||||||
indicator.style.left = `${indicatorLeft}px`;
|
indicator.style.left = `${indicatorLeft}px`;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// For vertical tabs
|
private updateVerticalIndicator(indicator: HTMLElement, tabElement: HTMLElement): void {
|
||||||
const tabsContainer = this.shadowRoot.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
|
const tabsContainer = this.shadowRoot.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
|
||||||
|
if (!tabsContainer) return;
|
||||||
|
|
||||||
indicator.style.top = `${selectedTabElement.offsetTop + tabsContainer.offsetTop}px`;
|
indicator.style.top = `${tabElement.offsetTop + tabsContainer.offsetTop}px`;
|
||||||
indicator.style.height = `${selectedTabElement.clientHeight}px`;
|
indicator.style.height = `${tabElement.clientHeight}px`;
|
||||||
}
|
|
||||||
|
|
||||||
indicator.style.opacity = '1';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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>`
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user