Compare commits

..

8 Commits

Author SHA1 Message Date
ac1ef4e497 v3.1.2
Some checks failed
Default (tags) / security (push) Failing after 12s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 16:17:52 +00:00
9c61c0542b fix(DeesAppuiMainmenu, DeesAppuiSecondarymenu): Add position: relative to main and secondary app UI menus to fix positioning of overlays and tooltips 2025-12-08 16:17:52 +00:00
5c099c8057 v3.1.1
Some checks failed
Default (tags) / security (push) Failing after 15s
Default (tags) / test (push) Failing after 18s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 16:16:25 +00:00
82b4afa95a fix(dees-appui): Extract demos for main and secondary app menus, adjust collapsed styles and toggle placement, bump devDependency 2025-12-08 16:16:25 +00:00
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
24d3afe85d v3.0.1
Some checks failed
Default (tags) / security (push) Failing after 15s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 14:50:53 +00:00
9735af05c8 fix(dees-appui): Normalize header heights and box-sizing for App UI components 2025-12-08 14:50:53 +00:00
11 changed files with 532 additions and 119 deletions

View File

@@ -1,5 +1,39 @@
# Changelog # Changelog
## 2025-12-08 - 3.1.2 - fix(DeesAppuiMainmenu, DeesAppuiSecondarymenu)
Add position: relative to main and secondary app UI menus to fix positioning of overlays and tooltips
- ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.ts: add `position: relative` to host styles
- ts_web/elements/00group-appui/dees-appui-secondarymenu/dees-appui-secondarymenu.ts: add `position: relative` to host styles
- Fixes incorrect positioning for absolutely positioned children (tooltips, overlays, badges) inside the main and secondary menus
## 2025-12-08 - 3.1.1 - fix(dees-appui)
Extract demos for main and secondary app menus, adjust collapsed styles and toggle placement, bump devDependency
- Extracted inline demo markup into separate demo files: ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.demo.ts and ts_web/elements/00group-appui/dees-appui-secondarymenu/dees-appui-secondarymenu.demo.ts and wired them up via imported demoFunc to reduce component size.
- Moved collapse toggle button markup in both dees-appui-mainmenu and dees-appui-secondarymenu templates to after the main container to improve layout/stacking and focus behavior.
- Adjusted collapsed logo/heading styles: removed extra padding/gap and hide logo text using display:none for a cleaner collapsed state.
- Bumped devDependency @git.zone/tswatch from ^2.3.1 to ^2.3.2 in package.json.
## 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
- Set topbar/header heights to 48px (was 40px) and adjusted dependent offsets (activity container top, topShadow position) in dees-appui-activitylog.
- Make logo and secondary menu headers fixed 48px tall and replace vertical padding with horizontal padding for consistent vertical alignment (dees-appui-mainmenu, dees-appui-secondarymenu).
- Ensure tabs wrapper uses explicit 48px height and tabsContainer fills height (height:100%) to keep tab items vertically centered (dees-appui-tabs).
- Add box-sizing: border-box to affected header/logo containers to prevent overflow and ensure correct sizing.
- Minor CSS alignment and overflow fixes to improve consistent layout and scrolling behavior across the app UI components.
## 2025-12-08 - 3.0.0 - BREAKING CHANGE(dees-appui-secondarymenu) ## 2025-12-08 - 3.0.0 - BREAKING CHANGE(dees-appui-secondarymenu)
Add SecondaryMenu component and replace Mainselector with new SecondaryMenu in AppUI Add SecondaryMenu component and replace Mainselector with new SecondaryMenu in AppUI

View File

@@ -1,6 +1,6 @@
{ {
"name": "@design.estate/dees-catalog", "name": "@design.estate/dees-catalog",
"version": "3.0.0", "version": "3.1.2",
"private": false, "private": false,
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.", "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", "main": "dist_ts_web/index.js",
@@ -48,7 +48,7 @@
"@git.zone/tsbuild": "^3.1.2", "@git.zone/tsbuild": "^3.1.2",
"@git.zone/tsbundle": "^2.6.3", "@git.zone/tsbundle": "^2.6.3",
"@git.zone/tstest": "^3.1.3", "@git.zone/tstest": "^3.1.3",
"@git.zone/tswatch": "^2.3.1", "@git.zone/tswatch": "^2.3.2",
"@push.rocks/projectinfo": "^5.0.2", "@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/tapbundle": "^6.0.3", "@push.rocks/tapbundle": "^6.0.3",
"@types/node": "^24.10.1" "@types/node": "^24.10.1"

20
pnpm-lock.yaml generated
View File

@@ -100,8 +100,8 @@ importers:
specifier: ^3.1.3 specifier: ^3.1.3
version: 3.1.3(@push.rocks/smartserve@1.3.0)(socks@2.8.7)(typescript@5.9.3) version: 3.1.3(@push.rocks/smartserve@1.3.0)(socks@2.8.7)(typescript@5.9.3)
'@git.zone/tswatch': '@git.zone/tswatch':
specifier: ^2.3.1 specifier: ^2.3.2
version: 2.3.1(@tiptap/pm@2.27.1) version: 2.3.2(@tiptap/pm@2.27.1)
'@push.rocks/projectinfo': '@push.rocks/projectinfo':
specifier: ^5.0.2 specifier: ^5.0.2
version: 5.0.2 version: 5.0.2
@@ -552,8 +552,8 @@ packages:
resolution: {integrity: sha512-t+/cKV21JHK8X7NGAmihs5M/eMm+V+jn4R5rzfwGG97WJFAcP5qE1Os9VYtyZw3tx/NZXA2yA4abo/ELluTuRA==} resolution: {integrity: sha512-t+/cKV21JHK8X7NGAmihs5M/eMm+V+jn4R5rzfwGG97WJFAcP5qE1Os9VYtyZw3tx/NZXA2yA4abo/ELluTuRA==}
hasBin: true hasBin: true
'@git.zone/tswatch@2.3.1': '@git.zone/tswatch@2.3.2':
resolution: {integrity: sha512-KjJRXs+XkXXXDUqmEb1tZuDM1IEVI/RzAo3wS2bFv/UhOY7iYAxPL+eKl55FxBFftB4ecAfxeBhbhijTbklAMA==} resolution: {integrity: sha512-M4B4D7BlfGb3ssgMHFMHc+hhEmxY6WzNd87ihuLReXMD+twjMA99u2hyAvAnboa3lERlixvHz/nN+rdbGH5VlA==}
hasBin: true hasBin: true
'@hapi/bourne@3.0.0': '@hapi/bourne@3.0.0':
@@ -1020,8 +1020,8 @@ packages:
'@push.rocks/smartversion@3.0.5': '@push.rocks/smartversion@3.0.5':
resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==} resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==}
'@push.rocks/smartwatch@5.0.0': '@push.rocks/smartwatch@5.1.0':
resolution: {integrity: sha512-uuWUlTo0l5LWOWoOuTMG7zzxpUNKBcyqoB+zyQ24NHTtSYNcaUJtaQzTO2gxMXr5sqiZDkohlThS0KvsBc3g7w==} resolution: {integrity: sha512-vf4yT8F8V+vstnrr0sh64rGF/8Z8dZlLR4T95KcVtm7F0H3dKxLcvZ3HT0xayw0gQow8+CCtDdts9Wgq5ipJTw==}
engines: {node: '>=20.0.0'} engines: {node: '>=20.0.0'}
'@push.rocks/smartxml@2.0.0': '@push.rocks/smartxml@2.0.0':
@@ -4735,7 +4735,7 @@ snapshots:
'@push.rocks/smartsitemap': 2.0.4 '@push.rocks/smartsitemap': 2.0.4
'@push.rocks/smartstream': 3.2.5 '@push.rocks/smartstream': 3.2.5
'@push.rocks/smarttime': 4.1.1 '@push.rocks/smarttime': 4.1.1
'@push.rocks/smartwatch': 5.0.0 '@push.rocks/smartwatch': 5.1.0
'@push.rocks/taskbuffer': 3.5.0 '@push.rocks/taskbuffer': 3.5.0
'@push.rocks/webrequest': 4.0.1 '@push.rocks/webrequest': 4.0.1
'@push.rocks/webstore': 2.0.20 '@push.rocks/webstore': 2.0.20
@@ -5613,7 +5613,7 @@ snapshots:
- utf-8-validate - utf-8-validate
- vue - vue
'@git.zone/tswatch@2.3.1(@tiptap/pm@2.27.1)': '@git.zone/tswatch@2.3.2(@tiptap/pm@2.27.1)':
dependencies: dependencies:
'@api.global/typedserver': 7.11.0(@tiptap/pm@2.27.1) '@api.global/typedserver': 7.11.0(@tiptap/pm@2.27.1)
'@git.zone/tsbundle': 2.6.3 '@git.zone/tsbundle': 2.6.3
@@ -5626,7 +5626,7 @@ snapshots:
'@push.rocks/smartlog': 3.1.10 '@push.rocks/smartlog': 3.1.10
'@push.rocks/smartlog-destination-local': 9.0.2 '@push.rocks/smartlog-destination-local': 9.0.2
'@push.rocks/smartshell': 3.3.0 '@push.rocks/smartshell': 3.3.0
'@push.rocks/smartwatch': 5.0.0 '@push.rocks/smartwatch': 5.1.0
'@push.rocks/taskbuffer': 3.5.0 '@push.rocks/taskbuffer': 3.5.0
transitivePeerDependencies: transitivePeerDependencies:
- '@nuxt/kit' - '@nuxt/kit'
@@ -6672,7 +6672,7 @@ snapshots:
'@types/semver': 7.7.1 '@types/semver': 7.7.1
semver: 7.7.3 semver: 7.7.3
'@push.rocks/smartwatch@5.0.0': '@push.rocks/smartwatch@5.1.0':
dependencies: dependencies:
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartenv': 6.0.0 '@push.rocks/smartenv': 6.0.0

View File

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

View File

@@ -63,13 +63,14 @@ export class DeesAppuiActivitylog extends DeesElement {
.topbar { .topbar {
position: absolute; position: absolute;
top: 0px; top: 0px;
height: 40px; height: 48px;
width: 100%; width: 100%;
padding: 0px 16px; padding: 0px 16px;
background: ${cssManager.bdTheme('#ffffff', '#09090b')}; background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
display: flex; display: flex;
align-items: center; align-items: center;
box-sizing: border-box;
} }
.topbar .heading { .topbar .heading {
@@ -81,7 +82,7 @@ export class DeesAppuiActivitylog extends DeesElement {
.activityContainer { .activityContainer {
position: absolute; position: absolute;
top: 40px; top: 48px;
bottom: 48px; bottom: 48px;
width: 100%; width: 100%;
padding: 12px 0px; padding: 12px 0px;
@@ -315,7 +316,7 @@ export class DeesAppuiActivitylog extends DeesElement {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 24px; height: 24px;
top: 40px; top: 48px;
background: ${cssManager.bdTheme( background: ${cssManager.bdTheme(
'linear-gradient(0deg, transparent 0%, #fafafa 100%)', 'linear-gradient(0deg, transparent 0%, #fafafa 100%)',
'linear-gradient(0deg, transparent 0%, #0a0a0a 100%)' 'linear-gradient(0deg, transparent 0%, #0a0a0a 100%)'

View File

@@ -89,6 +89,13 @@ export class DeesAppuiBase extends DeesElement {
@property({ type: Array }) @property({ type: Array })
accessor secondarymenuOptions: (interfaces.ISelectionOption | { divider: true })[] = []; 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 // Properties for maincontent
@property({ type: Array }) @property({ type: Array })
accessor maincontentTabs: interfaces.ITab[] = []; accessor maincontentTabs: interfaces.ITab[] = [];
@@ -124,9 +131,30 @@ export class DeesAppuiBase extends DeesElement {
height: calc(100% - 40px); height: calc(100% - 40px);
width: 100%; width: 100%;
display: grid; display: grid;
grid-template-columns: 200px 240px 1fr 240px; grid-template-columns: auto auto 1fr 240px;
grid-template-rows: 1fr; 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} .bottomTabs=${this.mainmenuBottomTabs}
.tabs=${this.mainmenuTabs} .tabs=${this.mainmenuTabs}
.selectedTab=${this.mainmenuSelectedTab} .selectedTab=${this.mainmenuSelectedTab}
.collapsed=${this.mainmenuCollapsed}
@tab-select=${(e: CustomEvent) => this.handleMainmenuTabSelect(e)} @tab-select=${(e: CustomEvent) => this.handleMainmenuTabSelect(e)}
@collapse-change=${(e: CustomEvent) => this.handleMainmenuCollapseChange(e)}
></dees-appui-mainmenu> ></dees-appui-mainmenu>
<dees-appui-secondarymenu <dees-appui-secondarymenu
.heading=${this.secondarymenuHeading} .heading=${this.secondarymenuHeading}
.groups=${this.secondarymenuGroups} .groups=${this.secondarymenuGroups}
.selectionOptions=${this.secondarymenuOptions} .selectionOptions=${this.secondarymenuOptions}
.selectedItem=${this.secondarymenuSelectedItem} .selectedItem=${this.secondarymenuSelectedItem}
.collapsed=${this.secondarymenuCollapsed}
@item-select=${(e: CustomEvent) => this.handleSecondarymenuItemSelect(e)} @item-select=${(e: CustomEvent) => this.handleSecondarymenuItemSelect(e)}
@collapse-change=${(e: CustomEvent) => this.handleSecondarymenuCollapseChange(e)}
></dees-appui-secondarymenu> ></dees-appui-secondarymenu>
<dees-appui-maincontent <dees-appui-maincontent
.tabs=${this.maincontentTabs} .tabs=${this.maincontentTabs}
@@ -244,4 +276,23 @@ export class DeesAppuiBase extends DeesElement {
composed: true 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

@@ -0,0 +1,50 @@
import { html } from '@design.estate/dees-element';
export const demoFunc = () => html`
<style>
.demo-mainmenu-container {
display: flex;
height: 100%;
background: #1a1a1a;
border-radius: 8px;
}
.demo-mainmenu-container .spacer {
flex: 1;
background: #0f0f0f;
}
</style>
<div class="demo-mainmenu-container">
<dees-appui-mainmenu
.logoIcon=${'lucide:box'}
.logoText=${'Acme App'}
.menuGroups=${[
{
tabs: [
{ key: 'Dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard') },
{ key: 'Inbox', iconName: 'lucide:inbox', action: () => console.log('Inbox') },
]
},
{
name: 'Workspace',
tabs: [
{ key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects') },
{ key: 'Tasks', iconName: 'lucide:checkSquare', action: () => console.log('Tasks') },
{ key: 'Documents', iconName: 'lucide:fileText', action: () => console.log('Documents') },
]
},
{
name: 'Analytics',
tabs: [
{ key: 'Reports', iconName: 'lucide:barChart3', action: () => console.log('Reports') },
{ key: 'Insights', iconName: 'lucide:lightbulb', action: () => console.log('Insights') },
]
}
]}
.bottomTabs=${[
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help') },
]}
></dees-appui-mainmenu>
<div class="spacer"></div>
</div>
`;

View File

@@ -12,6 +12,7 @@ import {
cssManager, cssManager,
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js'; import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
import { demoFunc } from './dees-appui-mainmenu.demo.js';
/** /**
* the most left menu * the most left menu
@@ -19,49 +20,7 @@ import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
*/ */
@customElement('dees-appui-mainmenu') @customElement('dees-appui-mainmenu')
export class DeesAppuiMainmenu extends DeesElement { export class DeesAppuiMainmenu extends DeesElement {
public static demo = () => html` public static demo = demoFunc;
<style>
.demo-mainmenu-container {
height: 500px;
background: #1a1a1a;
border-radius: 8px;
overflow: hidden;
}
</style>
<div class="demo-mainmenu-container">
<dees-appui-mainmenu
.logoIcon=${'lucide:box'}
.logoText=${'Acme App'}
.menuGroups=${[
{
tabs: [
{ key: 'Dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard') },
{ key: 'Inbox', iconName: 'lucide:inbox', action: () => console.log('Inbox') },
]
},
{
name: 'Workspace',
tabs: [
{ key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects') },
{ key: 'Tasks', iconName: 'lucide:checkSquare', action: () => console.log('Tasks') },
{ key: 'Documents', iconName: 'lucide:fileText', action: () => console.log('Documents') },
]
},
{
name: 'Analytics',
tabs: [
{ key: 'Reports', iconName: 'lucide:barChart3', action: () => console.log('Reports') },
{ key: 'Insights', iconName: 'lucide:lightbulb', action: () => console.log('Insights') },
]
}
]}
.bottomTabs=${[
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help') },
]}
></dees-appui-mainmenu>
</div>
`;
// INSTANCE // INSTANCE
@@ -87,27 +46,75 @@ export class DeesAppuiMainmenu extends DeesElement {
@property() @property()
accessor selectedTab: interfaces.ITab; accessor selectedTab: interfaces.ITab;
@property({ type: Boolean, reflect: true })
accessor collapsed: boolean = false;
public static styles = [ public static styles = [
cssManager.defaultStyles, cssManager.defaultStyles,
css` css`
:host { :host {
--menu-width-expanded: 200px;
--menu-width-collapsed: 56px;
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
position: relative;
display: block; display: block;
height: 100%; height: 100%;
} }
.mainContainer { .mainContainer {
--menuWidth: 200px;
color: ${cssManager.bdTheme('#666', '#ccc')}; color: ${cssManager.bdTheme('#666', '#ccc')};
z-index: ${zIndexLayers.fixed.appBar}; z-index: ${zIndexLayers.fixed.appBar};
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
width: var(--menuWidth); width: var(--menu-width-expanded);
height: 100%; height: 100%;
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')}; background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
user-select: none; user-select: none;
border-right: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')}; border-right: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; 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 */ /* Logo Section */
@@ -115,23 +122,38 @@ export class DeesAppuiMainmenu extends DeesElement {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 16px 14px; height: 48px;
padding: 0 14px;
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')}; border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
flex-shrink: 0; flex-shrink: 0;
box-sizing: border-box;
}
.logoSection .logoIcon {
font-size: 22px;
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
flex-shrink: 0;
} }
.logoSection dees-icon {
font-size: 22px;
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
}
.logoSection .logoText { .logoSection .logoText {
flex: 1;
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
transition: opacity 0.2s ease, width 0.25s ease;
}
:host([collapsed]) .logoSection {
justify-content: center;
padding: 0;
gap: 0;
}
:host([collapsed]) .logoSection .logoText {
display: none;
} }
/* Middle Section (scrollable) */ /* Middle Section (scrollable) */
@@ -176,6 +198,17 @@ export class DeesAppuiMainmenu extends DeesElement {
color: ${cssManager.bdTheme('#737373', '#737373')}; color: ${cssManager.bdTheme('#737373', '#737373')};
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; 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 { .groupTabs {
@@ -184,6 +217,10 @@ export class DeesAppuiMainmenu extends DeesElement {
gap: 2px; gap: 2px;
} }
:host([collapsed]) .menuGroup {
padding: 0 4px;
}
/* Tab Item */ /* Tab Item */
.tab { .tab {
position: relative; position: relative;
@@ -240,6 +277,60 @@ export class DeesAppuiMainmenu extends DeesElement {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; 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 */ /* Bottom Section */
@@ -251,6 +342,10 @@ export class DeesAppuiMainmenu extends DeesElement {
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
} }
:host([collapsed]) .bottomSection {
padding: 8px 4px;
}
`, `,
]; ];
@@ -268,7 +363,7 @@ export class DeesAppuiMainmenu extends DeesElement {
}}> }}>
${this.logoIcon || this.logoText ? html` ${this.logoIcon || this.logoText ? html`
<div class="logoSection"> <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>` : ''} ${this.logoText ? html`<span class="logoText">${this.logoText}</span>` : ''}
</div> </div>
` : ''} ` : ''}
@@ -283,6 +378,9 @@ export class DeesAppuiMainmenu extends DeesElement {
</div> </div>
` : ''} ` : ''}
</div> </div>
<button class="collapse-toggle" @click="${() => this.toggleCollapse()}">
<dees-icon .icon="${this.collapsed ? 'lucide:chevronRight' : 'lucide:chevronLeft'}"></dees-icon>
</button>
`; `;
} }
@@ -319,6 +417,7 @@ export class DeesAppuiMainmenu extends DeesElement {
> >
<dees-icon .icon="${tabArg.iconName || ''}"></dees-icon> <dees-icon .icon="${tabArg.iconName || ''}"></dees-icon>
<span class="tabLabel">${tabArg.key}</span> <span class="tabLabel">${tabArg.key}</span>
<span class="tab-tooltip">${tabArg.key}</span>
</div> </div>
`; `;
} }
@@ -349,4 +448,13 @@ export class DeesAppuiMainmenu extends DeesElement {
this.updateTab(allTabs[0]); 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

@@ -0,0 +1,52 @@
import { html } from '@design.estate/dees-element';
import type * as interfaces from '../../interfaces/index.js';
export const demoFunc = () => html`
<style>
.demo-secondarymenu-container {
display: flex;
height: 100%;
background: #1a1a1a;
border-radius: 8px;
}
.demo-secondarymenu-container .spacer {
flex: 1;
background: #0f0f0f;
}
</style>
<div class="demo-secondarymenu-container">
<dees-appui-secondarymenu
.heading=${'Projects'}
.groups=${[
{
name: 'Active',
iconName: 'lucide:folder',
items: [
{ key: 'Frontend App', iconName: 'code', action: () => console.log('Frontend'), badge: 3, badgeVariant: 'warning' },
{ key: 'API Server', iconName: 'server', action: () => console.log('API'), badge: 'new', badgeVariant: 'success' },
{ key: 'Database', iconName: 'database', action: () => console.log('Database') },
]
},
{
name: 'Archived',
iconName: 'lucide:archive',
collapsed: true,
items: [
{ key: 'Legacy System', iconName: 'box', action: () => console.log('Legacy') },
{ key: 'Old API', iconName: 'server', action: () => console.log('Old API') },
]
},
{
name: 'Settings',
iconName: 'lucide:settings',
items: [
{ key: 'Configuration', iconName: 'sliders', action: () => console.log('Config') },
{ key: 'Integrations', iconName: 'plug', action: () => console.log('Integrations'), badge: 5, badgeVariant: 'error' },
]
}
] as interfaces.ISecondaryMenuGroup[]}
@item-select=${(e: CustomEvent) => console.log('Selected:', e.detail)}
></dees-appui-secondarymenu>
<div class="spacer"></div>
</div>
`;

View File

@@ -14,6 +14,7 @@ import {
css, css,
cssManager, cssManager,
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
import { demoFunc } from './dees-appui-secondarymenu.demo.js';
/** /**
* Secondary navigation menu for sub-navigation within MainMenu views * Secondary navigation menu for sub-navigation within MainMenu views
@@ -21,51 +22,7 @@ import {
*/ */
@customElement('dees-appui-secondarymenu') @customElement('dees-appui-secondarymenu')
export class DeesAppuiSecondarymenu extends DeesElement { export class DeesAppuiSecondarymenu extends DeesElement {
public static demo = () => html` public static demo = demoFunc;
<style>
.demo-container {
height: 500px;
display: flex;
background: #1a1a1a;
border-radius: 8px;
overflow: hidden;
}
</style>
<div class="demo-container">
<dees-appui-secondarymenu
.heading=${'Projects'}
.groups=${[
{
name: 'Active',
iconName: 'lucide:folder',
items: [
{ key: 'Frontend App', iconName: 'code', action: () => console.log('Frontend'), badge: 3, badgeVariant: 'warning' },
{ key: 'API Server', iconName: 'server', action: () => console.log('API'), badge: 'new', badgeVariant: 'success' },
{ key: 'Database', iconName: 'database', action: () => console.log('Database') },
]
},
{
name: 'Archived',
iconName: 'lucide:archive',
collapsed: true,
items: [
{ key: 'Legacy System', iconName: 'box', action: () => console.log('Legacy') },
{ key: 'Old API', iconName: 'server', action: () => console.log('Old API') },
]
},
{
name: 'Settings',
iconName: 'lucide:settings',
items: [
{ key: 'Configuration', iconName: 'sliders', action: () => console.log('Config') },
{ key: 'Integrations', iconName: 'plug', action: () => console.log('Integrations'), badge: 5, badgeVariant: 'error' },
]
}
] as interfaces.ISecondaryMenuGroup[]}
@item-select=${(e: CustomEvent) => console.log('Selected:', e.detail)}
></dees-appui-secondarymenu>
</div>
`;
// INSTANCE // INSTANCE
@@ -89,11 +46,16 @@ export class DeesAppuiSecondarymenu extends DeesElement {
@state() @state()
accessor collapsedGroups: Set<string> = new Set(); accessor collapsedGroups: Set<string> = new Set();
/** Horizontal collapse state */
@property({ type: Boolean, reflect: true })
accessor collapsed: boolean = false;
public static styles = [ public static styles = [
cssManager.defaultStyles, cssManager.defaultStyles,
css` css`
:host { :host {
--sidebar-width: 240px; --sidebar-width-expanded: 240px;
--sidebar-width-collapsed: 56px;
--sidebar-bg: ${cssManager.bdTheme('#fafafa', '#0a0a0a')}; --sidebar-bg: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
--sidebar-fg: ${cssManager.bdTheme('#525252', '#a3a3a3')}; --sidebar-fg: ${cssManager.bdTheme('#525252', '#a3a3a3')};
--sidebar-fg-muted: ${cssManager.bdTheme('#737373', '#737373')}; --sidebar-fg-muted: ${cssManager.bdTheme('#737373', '#737373')};
@@ -102,6 +64,8 @@ export class DeesAppuiSecondarymenu extends DeesElement {
--sidebar-hover: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.06)')}; --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-active: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
--sidebar-accent: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; --sidebar-accent: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
/* Badge colors */ /* Badge colors */
--badge-default-bg: ${cssManager.bdTheme('#f4f4f5', '#27272a')}; --badge-default-bg: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
@@ -113,13 +77,19 @@ export class DeesAppuiSecondarymenu extends DeesElement {
--badge-error-bg: ${cssManager.bdTheme('#fee2e2', '#450a0a')}; --badge-error-bg: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
--badge-error-fg: ${cssManager.bdTheme('#991b1b', '#f87171')}; --badge-error-fg: ${cssManager.bdTheme('#991b1b', '#f87171')};
position: relative;
display: block; display: block;
height: 100%; height: 100%;
width: var(--sidebar-width); width: var(--sidebar-width-expanded);
background: var(--sidebar-bg); background: var(--sidebar-bg);
border-right: 1px solid var(--sidebar-border); border-right: 1px solid var(--sidebar-border);
font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
user-select: none; user-select: none;
transition: width 0.25s ease;
}
:host([collapsed]) {
width: var(--sidebar-width-collapsed);
} }
.maincontainer { .maincontainer {
@@ -127,6 +97,43 @@ export class DeesAppuiSecondarymenu extends DeesElement {
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
overflow: hidden; 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 */ /* Header Section */
@@ -134,18 +141,33 @@ export class DeesAppuiSecondarymenu extends DeesElement {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 16px 16px; height: 48px;
padding: 0 16px;
border-bottom: 1px solid var(--sidebar-border); border-bottom: 1px solid var(--sidebar-border);
flex-shrink: 0; flex-shrink: 0;
box-sizing: border-box;
} }
.header .heading { .header .heading {
flex: 1;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
color: var(--sidebar-fg-active); color: var(--sidebar-fg-active);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; 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 */ /* Scrollable Menu Section */
@@ -179,6 +201,10 @@ export class DeesAppuiSecondarymenu extends DeesElement {
margin-bottom: 4px; margin-bottom: 4px;
} }
:host([collapsed]) .menuGroup {
padding: 0 4px;
}
.groupHeader { .groupHeader {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -186,7 +212,8 @@ export class DeesAppuiSecondarymenu extends DeesElement {
padding: 8px 8px; padding: 8px 8px;
cursor: pointer; cursor: pointer;
border-radius: 6px; 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 { .groupHeader:hover {
@@ -202,6 +229,8 @@ export class DeesAppuiSecondarymenu extends DeesElement {
color: var(--sidebar-fg-muted); color: var(--sidebar-fg-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
white-space: nowrap;
overflow: hidden;
} }
.groupHeader .groupTitle dees-icon { .groupHeader .groupTitle dees-icon {
@@ -219,6 +248,15 @@ export class DeesAppuiSecondarymenu extends DeesElement {
transform: rotate(-90deg); 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 */ /* Group Items Container */
.groupItems { .groupItems {
overflow: hidden; overflow: hidden;
@@ -232,6 +270,12 @@ export class DeesAppuiSecondarymenu extends DeesElement {
opacity: 0; opacity: 0;
} }
/* Always show items when horizontally collapsed (regardless of group collapse state) */
:host([collapsed]) .groupItems {
max-height: none;
opacity: 1;
}
/* Menu Item */ /* Menu Item */
.menuItem { .menuItem {
position: relative; position: relative;
@@ -290,6 +334,60 @@ export class DeesAppuiSecondarymenu extends DeesElement {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; 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 */ /* Badge Styles */
@@ -326,6 +424,10 @@ export class DeesAppuiSecondarymenu extends DeesElement {
color: var(--badge-error-fg); color: var(--badge-error-fg);
} }
:host([collapsed]) .badge {
display: none;
}
/* Divider */ /* Divider */
.divider { .divider {
height: 1px; height: 1px;
@@ -352,6 +454,9 @@ export class DeesAppuiSecondarymenu extends DeesElement {
: this.renderLegacyOptions()} : this.renderLegacyOptions()}
</div> </div>
</div> </div>
<button class="collapse-toggle" @click="${() => this.toggleCollapse()}">
<dees-icon .icon="${this.collapsed ? 'lucide:chevronRight' : 'lucide:chevronLeft'}"></dees-icon>
</button>
`; `;
} }
@@ -390,6 +495,7 @@ export class DeesAppuiSecondarymenu extends DeesElement {
${item.badge !== undefined ? html` ${item.badge !== undefined ? html`
<span class="badge ${item.badgeVariant || 'default'}">${item.badge}</span> <span class="badge ${item.badgeVariant || 'default'}">${item.badge}</span>
` : ''} ` : ''}
<span class="item-tooltip">${item.key}</span>
</div> </div>
`; `;
} }
@@ -422,6 +528,15 @@ export class DeesAppuiSecondarymenu extends DeesElement {
this.collapsedGroups = newCollapsed; 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 { private selectItem(item: interfaces.ISecondaryMenuItem, group?: interfaces.ISecondaryMenuGroup): void {
this.selectedItem = item; this.selectedItem = item;
item.action(); item.action();

View File

@@ -132,7 +132,9 @@ export class DeesAppuiTabs extends DeesElement {
} }
.tabs-wrapper.horizontal-wrapper { .tabs-wrapper.horizontal-wrapper {
height: 48px;
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
box-sizing: border-box;
} }
.tabsContainer { .tabsContainer {
@@ -146,7 +148,7 @@ export class DeesAppuiTabs extends DeesElement {
font-size: 14px; font-size: 14px;
overflow-x: auto; overflow-x: auto;
scrollbar-width: none; scrollbar-width: none;
height: 48px; height: 100%;
padding: 0 16px; padding: 0 16px;
gap: 4px; gap: 4px;
} }