Compare commits

..

2 Commits

Author SHA1 Message Date
888430d55a v3.1.0
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 15:40:12 +00:00
85424d07cd feat(dees-appui): Add collapsible/compact mode to AppUI sidebars (mainmenu & secondarymenu) with toggles, tooltips and improved z-index stacking 2025-12-08 15:40:12 +00:00
6 changed files with 373 additions and 10 deletions

View File

@@ -1,5 +1,15 @@
# Changelog
## 2025-12-08 - 3.1.0 - feat(dees-appui)
Add collapsible/compact mode to AppUI sidebars (mainmenu & secondarymenu) with toggles, tooltips and improved z-index stacking
- Add collapsed property to dees-appui-mainmenu and dees-appui-secondarymenu (reflect: true) to enable compact horizontal mode.
- Add floating collapse toggle buttons and public toggleCollapse() methods on mainmenu and secondarymenu; these dispatch 'collapse-change' events (bubbles & composed).
- Expose and track collapse state in dees-appui-base via mainmenuCollapsed and secondarymenuCollapsed properties; bind states to child components and re-emit collapse-change events as mainmenu-collapse-change and secondarymenu-collapse-change.
- Implement collapsed styles and animations: reduced sidebar widths, hide/compact labels and headers, center icons, hide badges, and add smooth width/opacity transitions.
- Add tooltips that appear for tabs/items when sidebars are collapsed to preserve discoverability.
- Adjust layout grid in DeesAppuiBase (use auto columns) and add explicit z-index layering to ensure proper stacking order of mainmenu, secondarymenu, maincontent and activitylog.
## 2025-12-08 - 3.0.1 - fix(dees-appui)
Normalize header heights and box-sizing for App UI components

View File

@@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-catalog",
"version": "3.0.1",
"version": "3.1.0",
"private": false,
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
"main": "dist_ts_web/index.js",

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@design.estate/dees-catalog',
version: '3.0.1',
version: '3.1.0',
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
}

View File

@@ -89,6 +89,13 @@ export class DeesAppuiBase extends DeesElement {
@property({ type: Array })
accessor secondarymenuOptions: (interfaces.ISelectionOption | { divider: true })[] = [];
// Collapse states
@property({ type: Boolean })
accessor mainmenuCollapsed: boolean = false;
@property({ type: Boolean })
accessor secondarymenuCollapsed: boolean = false;
// Properties for maincontent
@property({ type: Array })
accessor maincontentTabs: interfaces.ITab[] = [];
@@ -124,9 +131,30 @@ export class DeesAppuiBase extends DeesElement {
height: calc(100% - 40px);
width: 100%;
display: grid;
grid-template-columns: 200px 240px 1fr 240px;
grid-template-columns: auto auto 1fr 240px;
grid-template-rows: 1fr;
}
/* Z-index layering for proper stacking (position: relative required for z-index to work) */
.maingrid > dees-appui-mainmenu {
position: relative;
z-index: 3;
}
.maingrid > dees-appui-secondarymenu {
position: relative;
z-index: 2;
}
.maingrid > dees-appui-maincontent {
position: relative;
z-index: 1;
}
.maingrid > dees-appui-activitylog {
position: relative;
z-index: 1;
}
`,
];
@@ -156,14 +184,18 @@ export class DeesAppuiBase extends DeesElement {
.bottomTabs=${this.mainmenuBottomTabs}
.tabs=${this.mainmenuTabs}
.selectedTab=${this.mainmenuSelectedTab}
.collapsed=${this.mainmenuCollapsed}
@tab-select=${(e: CustomEvent) => this.handleMainmenuTabSelect(e)}
@collapse-change=${(e: CustomEvent) => this.handleMainmenuCollapseChange(e)}
></dees-appui-mainmenu>
<dees-appui-secondarymenu
.heading=${this.secondarymenuHeading}
.groups=${this.secondarymenuGroups}
.selectionOptions=${this.secondarymenuOptions}
.selectedItem=${this.secondarymenuSelectedItem}
.collapsed=${this.secondarymenuCollapsed}
@item-select=${(e: CustomEvent) => this.handleSecondarymenuItemSelect(e)}
@collapse-change=${(e: CustomEvent) => this.handleSecondarymenuCollapseChange(e)}
></dees-appui-secondarymenu>
<dees-appui-maincontent
.tabs=${this.maincontentTabs}
@@ -244,4 +276,23 @@ export class DeesAppuiBase extends DeesElement {
composed: true
}));
}
// Event handlers for collapse state changes
private handleMainmenuCollapseChange(e: CustomEvent) {
this.mainmenuCollapsed = e.detail.collapsed;
this.dispatchEvent(new CustomEvent('mainmenu-collapse-change', {
detail: e.detail,
bubbles: true,
composed: true
}));
}
private handleSecondarymenuCollapseChange(e: CustomEvent) {
this.secondarymenuCollapsed = e.detail.collapsed;
this.dispatchEvent(new CustomEvent('secondarymenu-collapse-change', {
detail: e.detail,
bubbles: true,
composed: true
}));
}
}

View File

@@ -87,27 +87,74 @@ export class DeesAppuiMainmenu extends DeesElement {
@property()
accessor selectedTab: interfaces.ITab;
@property({ type: Boolean, reflect: true })
accessor collapsed: boolean = false;
public static styles = [
cssManager.defaultStyles,
css`
:host {
--menu-width-expanded: 200px;
--menu-width-collapsed: 56px;
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
display: block;
height: 100%;
}
.mainContainer {
--menuWidth: 200px;
color: ${cssManager.bdTheme('#666', '#ccc')};
z-index: ${zIndexLayers.fixed.appBar};
display: flex;
flex-direction: column;
position: relative;
width: var(--menuWidth);
width: var(--menu-width-expanded);
height: 100%;
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
user-select: none;
border-right: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
transition: width 0.25s ease;
}
:host([collapsed]) .mainContainer {
width: var(--menu-width-collapsed);
}
/* Floating collapse toggle button */
.collapse-toggle {
position: absolute;
right: -12px;
top: 24px;
transform: translateY(-50%);
width: 24px;
height: 24px;
border-radius: 50%;
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#3f3f46')};
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
color: ${cssManager.bdTheme('#737373', '#a1a1aa')};
opacity: 0;
transition: opacity 0.2s ease, background 0.15s ease;
padding: 0;
}
.collapse-toggle:hover {
background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
}
:host(:hover) .collapse-toggle {
opacity: 1;
}
.collapse-toggle dees-icon {
font-size: 14px;
}
/* Logo Section */
@@ -122,18 +169,32 @@ export class DeesAppuiMainmenu extends DeesElement {
box-sizing: border-box;
}
.logoSection dees-icon {
.logoSection .logoIcon {
font-size: 22px;
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
flex-shrink: 0;
}
.logoSection .logoText {
flex: 1;
font-size: 15px;
font-weight: 600;
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease, width 0.25s ease;
}
:host([collapsed]) .logoSection {
justify-content: center;
padding: 0 8px;
}
:host([collapsed]) .logoSection .logoText {
opacity: 0;
width: 0;
overflow: hidden;
}
/* Middle Section (scrollable) */
@@ -178,6 +239,17 @@ export class DeesAppuiMainmenu extends DeesElement {
color: ${cssManager.bdTheme('#737373', '#737373')};
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
overflow: hidden;
transition: opacity 0.2s ease, max-height 0.25s ease;
max-height: 30px;
}
:host([collapsed]) .groupHeader {
opacity: 0;
max-height: 0;
padding: 0;
margin: 0;
}
.groupTabs {
@@ -186,6 +258,10 @@ export class DeesAppuiMainmenu extends DeesElement {
gap: 2px;
}
:host([collapsed]) .menuGroup {
padding: 0 4px;
}
/* Tab Item */
.tab {
position: relative;
@@ -242,6 +318,60 @@ export class DeesAppuiMainmenu extends DeesElement {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease, width 0.25s ease;
}
/* Collapsed tab styles */
:host([collapsed]) .tab {
justify-content: center;
padding: 10px;
gap: 0;
}
:host([collapsed]) .tab .tabLabel {
opacity: 0;
width: 0;
position: absolute;
}
:host([collapsed]) .tab.selectedTab::before {
left: -4px;
}
/* Tooltip for collapsed state */
.tab-tooltip {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: 12px;
padding: 6px 12px;
background: var(--tooltip-bg);
color: var(--tooltip-fg);
border-radius: 6px;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tab-tooltip::before {
content: '';
position: absolute;
left: -4px;
top: 50%;
transform: translateY(-50%);
border: 4px solid transparent;
border-right-color: var(--tooltip-bg);
}
:host([collapsed]) .tab:hover .tab-tooltip {
opacity: 1;
transition-delay: 1s;
}
/* Bottom Section */
@@ -253,6 +383,10 @@ export class DeesAppuiMainmenu extends DeesElement {
flex-direction: column;
gap: 2px;
}
:host([collapsed]) .bottomSection {
padding: 8px 4px;
}
`,
];
@@ -261,6 +395,9 @@ export class DeesAppuiMainmenu extends DeesElement {
const allTabs = this.getAllTabs();
return html`
<button class="collapse-toggle" @click="${() => this.toggleCollapse()}">
<dees-icon .icon="${this.collapsed ? 'lucide:chevronRight' : 'lucide:chevronLeft'}"></dees-icon>
</button>
<div class="mainContainer" @contextmenu=${(eventArg: MouseEvent) => {
DeesContextmenu.openContextMenuWithOptions(eventArg, [{
name: 'app settings',
@@ -270,7 +407,7 @@ export class DeesAppuiMainmenu extends DeesElement {
}}>
${this.logoIcon || this.logoText ? html`
<div class="logoSection">
${this.logoIcon ? html`<dees-icon .icon="${this.logoIcon}"></dees-icon>` : ''}
${this.logoIcon ? html`<dees-icon class="logoIcon" .icon="${this.logoIcon}"></dees-icon>` : ''}
${this.logoText ? html`<span class="logoText">${this.logoText}</span>` : ''}
</div>
` : ''}
@@ -321,6 +458,7 @@ export class DeesAppuiMainmenu extends DeesElement {
>
<dees-icon .icon="${tabArg.iconName || ''}"></dees-icon>
<span class="tabLabel">${tabArg.key}</span>
<span class="tab-tooltip">${tabArg.key}</span>
</div>
`;
}
@@ -351,4 +489,13 @@ export class DeesAppuiMainmenu extends DeesElement {
this.updateTab(allTabs[0]);
}
}
public toggleCollapse(): void {
this.collapsed = !this.collapsed;
this.dispatchEvent(new CustomEvent('collapse-change', {
detail: { collapsed: this.collapsed },
bubbles: true,
composed: true
}));
}
}

View File

@@ -89,11 +89,16 @@ export class DeesAppuiSecondarymenu extends DeesElement {
@state()
accessor collapsedGroups: Set<string> = new Set();
/** Horizontal collapse state */
@property({ type: Boolean, reflect: true })
accessor collapsed: boolean = false;
public static styles = [
cssManager.defaultStyles,
css`
:host {
--sidebar-width: 240px;
--sidebar-width-expanded: 240px;
--sidebar-width-collapsed: 56px;
--sidebar-bg: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
--sidebar-fg: ${cssManager.bdTheme('#525252', '#a3a3a3')};
--sidebar-fg-muted: ${cssManager.bdTheme('#737373', '#737373')};
@@ -102,6 +107,8 @@ export class DeesAppuiSecondarymenu extends DeesElement {
--sidebar-hover: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.06)')};
--sidebar-active: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
--sidebar-accent: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
/* Badge colors */
--badge-default-bg: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
@@ -115,11 +122,16 @@ export class DeesAppuiSecondarymenu extends DeesElement {
display: block;
height: 100%;
width: var(--sidebar-width);
width: var(--sidebar-width-expanded);
background: var(--sidebar-bg);
border-right: 1px solid var(--sidebar-border);
font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
user-select: none;
transition: width 0.25s ease;
}
:host([collapsed]) {
width: var(--sidebar-width-collapsed);
}
.maincontainer {
@@ -127,6 +139,43 @@ export class DeesAppuiSecondarymenu extends DeesElement {
flex-direction: column;
height: 100%;
overflow: hidden;
position: relative;
}
/* Floating collapse toggle button */
.collapse-toggle {
position: absolute;
right: -12px;
top: 24px;
transform: translateY(-50%);
width: 24px;
height: 24px;
border-radius: 50%;
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#3f3f46')};
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
color: ${cssManager.bdTheme('#737373', '#a1a1aa')};
opacity: 0;
transition: opacity 0.2s ease, background 0.15s ease;
padding: 0;
}
.collapse-toggle:hover {
background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
}
:host(:hover) .collapse-toggle {
opacity: 1;
}
.collapse-toggle dees-icon {
font-size: 14px;
}
/* Header Section */
@@ -142,12 +191,25 @@ export class DeesAppuiSecondarymenu extends DeesElement {
}
.header .heading {
flex: 1;
font-size: 14px;
font-weight: 600;
color: var(--sidebar-fg-active);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease, width 0.25s ease;
}
:host([collapsed]) .header {
justify-content: center;
padding: 0 8px;
}
:host([collapsed]) .header .heading {
opacity: 0;
width: 0;
overflow: hidden;
}
/* Scrollable Menu Section */
@@ -181,6 +243,10 @@ export class DeesAppuiSecondarymenu extends DeesElement {
margin-bottom: 4px;
}
:host([collapsed]) .menuGroup {
padding: 0 4px;
}
.groupHeader {
display: flex;
align-items: center;
@@ -188,7 +254,8 @@ export class DeesAppuiSecondarymenu extends DeesElement {
padding: 8px 8px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s ease;
transition: background 0.15s ease, opacity 0.2s ease, max-height 0.25s ease;
max-height: 40px;
}
.groupHeader:hover {
@@ -204,6 +271,8 @@ export class DeesAppuiSecondarymenu extends DeesElement {
color: var(--sidebar-fg-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
overflow: hidden;
}
.groupHeader .groupTitle dees-icon {
@@ -221,6 +290,15 @@ export class DeesAppuiSecondarymenu extends DeesElement {
transform: rotate(-90deg);
}
/* Hide group headers when horizontally collapsed */
:host([collapsed]) .groupHeader {
opacity: 0;
max-height: 0;
padding: 0;
margin: 0;
pointer-events: none;
}
/* Group Items Container */
.groupItems {
overflow: hidden;
@@ -234,6 +312,12 @@ export class DeesAppuiSecondarymenu extends DeesElement {
opacity: 0;
}
/* Always show items when horizontally collapsed (regardless of group collapse state) */
:host([collapsed]) .groupItems {
max-height: none;
opacity: 1;
}
/* Menu Item */
.menuItem {
position: relative;
@@ -292,6 +376,60 @@ export class DeesAppuiSecondarymenu extends DeesElement {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease, width 0.25s ease;
}
/* Collapsed menu item styles */
:host([collapsed]) .menuItem {
justify-content: center;
padding: 8px;
gap: 0;
}
:host([collapsed]) .menuItem .itemLabel {
opacity: 0;
width: 0;
position: absolute;
}
:host([collapsed]) .menuItem.selected::before {
left: -4px;
}
/* Tooltip for collapsed state */
.item-tooltip {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: 12px;
padding: 6px 12px;
background: var(--tooltip-bg);
color: var(--tooltip-fg);
border-radius: 6px;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.item-tooltip::before {
content: '';
position: absolute;
left: -4px;
top: 50%;
transform: translateY(-50%);
border: 4px solid transparent;
border-right-color: var(--tooltip-bg);
}
:host([collapsed]) .menuItem:hover .item-tooltip {
opacity: 1;
transition-delay: 1s;
}
/* Badge Styles */
@@ -328,6 +466,10 @@ export class DeesAppuiSecondarymenu extends DeesElement {
color: var(--badge-error-fg);
}
:host([collapsed]) .badge {
display: none;
}
/* Divider */
.divider {
height: 1px;
@@ -344,6 +486,9 @@ export class DeesAppuiSecondarymenu extends DeesElement {
public render(): TemplateResult {
return html`
<button class="collapse-toggle" @click="${() => this.toggleCollapse()}">
<dees-icon .icon="${this.collapsed ? 'lucide:chevronRight' : 'lucide:chevronLeft'}"></dees-icon>
</button>
<div class="maincontainer">
<div class="header">
<span class="heading">${this.heading}</span>
@@ -392,6 +537,7 @@ export class DeesAppuiSecondarymenu extends DeesElement {
${item.badge !== undefined ? html`
<span class="badge ${item.badgeVariant || 'default'}">${item.badge}</span>
` : ''}
<span class="item-tooltip">${item.key}</span>
</div>
`;
}
@@ -424,6 +570,15 @@ export class DeesAppuiSecondarymenu extends DeesElement {
this.collapsedGroups = newCollapsed;
}
public toggleCollapse(): void {
this.collapsed = !this.collapsed;
this.dispatchEvent(new CustomEvent('collapse-change', {
detail: { collapsed: this.collapsed },
bubbles: true,
composed: true
}));
}
private selectItem(item: interfaces.ISecondaryMenuItem, group?: interfaces.ISecondaryMenuGroup): void {
this.selectedItem = item;
item.action();