feat(dees-simple-appdash): add nested sidebar subviews and preserve submit labels from slotted text
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-04-08 - 3.68.0 - feat(dees-simple-appdash)
|
||||||
|
add nested sidebar subviews and preserve submit labels from slotted text
|
||||||
|
|
||||||
|
- support grouped navigation items with expandable subviews and parent-to-first-subview fallback in the app dashboard
|
||||||
|
- allow dees-form-submit to derive its button text from light DOM content when no explicit text property is set
|
||||||
|
|
||||||
## 2026-04-07 - 3.67.1 - fix(repo)
|
## 2026-04-07 - 3.67.1 - fix(repo)
|
||||||
no changes to commit
|
no changes to commit
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-catalog',
|
name: '@design.estate/dees-catalog',
|
||||||
version: '3.67.1',
|
version: '3.68.0',
|
||||||
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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,12 +75,23 @@ export class DeesFormSubmit extends DeesElement {
|
|||||||
.text=${this.text}
|
.text=${this.text}
|
||||||
?disabled=${this.disabled}
|
?disabled=${this.disabled}
|
||||||
@clicked=${this.submit}
|
@clicked=${this.submit}
|
||||||
>
|
></dees-button>
|
||||||
<slot></slot>
|
|
||||||
</dees-button>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async firstUpdated() {
|
||||||
|
// Capture light DOM text content as the button label. dees-button wipes
|
||||||
|
// its own light DOM during extractLightDom(), so we cannot simply forward
|
||||||
|
// a <slot> into it — we have to hoist the text onto the .text property
|
||||||
|
// ourselves before handing it to dees-button.
|
||||||
|
if (!this.text) {
|
||||||
|
const slotText = this.textContent?.trim();
|
||||||
|
if (slotText) {
|
||||||
|
this.text = slotText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async submit() {
|
public async submit() {
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -353,12 +353,35 @@ export const demoFunc = () => html`
|
|||||||
name: 'Analytics',
|
name: 'Analytics',
|
||||||
iconName: 'lucide:lineChart',
|
iconName: 'lucide:lineChart',
|
||||||
element: DemoViewAnalytics,
|
element: DemoViewAnalytics,
|
||||||
|
subViews: [
|
||||||
|
{
|
||||||
|
name: 'Overview',
|
||||||
|
iconName: 'lucide:activity',
|
||||||
|
element: DemoViewAnalytics,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Reports',
|
||||||
|
iconName: 'lucide:fileText',
|
||||||
|
element: DemoViewDashboard,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
iconName: 'lucide:settings',
|
iconName: 'lucide:settings',
|
||||||
element: DemoViewSettings,
|
subViews: [
|
||||||
}
|
{
|
||||||
|
name: 'Profile',
|
||||||
|
iconName: 'lucide:user',
|
||||||
|
element: DemoViewSettings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Billing',
|
||||||
|
iconName: 'lucide:creditCard',
|
||||||
|
element: DemoViewSettings,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
] as IView[]}
|
] as IView[]}
|
||||||
@logout=${() => {
|
@logout=${() => {
|
||||||
console.log('Logout event triggered');
|
console.log('Logout event triggered');
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ declare global {
|
|||||||
export interface IView {
|
export interface IView {
|
||||||
name: string;
|
name: string;
|
||||||
iconName?: string;
|
iconName?: string;
|
||||||
element: DeesElement['constructor']['prototype'];
|
element?: DeesElement['constructor']['prototype'];
|
||||||
|
subViews?: IView[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TGlobalMessageType = 'info' | 'success' | 'warning' | 'error';
|
export type TGlobalMessageType = 'info' | 'success' | 'warning' | 'error';
|
||||||
@@ -250,6 +251,55 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.viewTab .chevron {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.5;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
transition: transform 0.2s ease, opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTab.hasSubs:hover .chevron {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTab.hasSubs.groupActive .chevron {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subViews {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
margin: 2px 0 4px 12px;
|
||||||
|
padding-left: 12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subViews::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
width: 1px;
|
||||||
|
background: var(--dees-color-border-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTab.sub {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTab.sub dees-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTab.sub.selected::before {
|
||||||
|
left: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
.appActions {
|
.appActions {
|
||||||
padding: 12px 8px;
|
padding: 12px 8px;
|
||||||
border-top: 1px solid var(--dees-color-border-default);
|
border-top: 1px solid var(--dees-color-border-default);
|
||||||
@@ -563,10 +613,12 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
<div class="viewTabs-container">
|
<div class="viewTabs-container">
|
||||||
<div class="section-label">Navigation</div>
|
<div class="section-label">Navigation</div>
|
||||||
<div class="viewTabs">
|
<div class="viewTabs">
|
||||||
${this.viewTabs.map(
|
${this.viewTabs.map((view) => {
|
||||||
(view) => html`
|
const hasSubs = !!view.subViews?.length;
|
||||||
|
const groupActive = hasSubs && this.isGroupActive(view);
|
||||||
|
return html`
|
||||||
<div
|
<div
|
||||||
class="viewTab ${this.selectedView === view ? 'selected' : ''}"
|
class="viewTab ${this.selectedView === view ? 'selected' : ''} ${hasSubs ? 'hasSubs' : ''} ${groupActive ? 'groupActive' : ''}"
|
||||||
@click=${() => this.loadView(view)}
|
@click=${() => this.loadView(view)}
|
||||||
>
|
>
|
||||||
${view.iconName ? html`
|
${view.iconName ? html`
|
||||||
@@ -575,9 +627,34 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
<dees-icon .icon="${'lucide:file'}"></dees-icon>
|
<dees-icon .icon="${'lucide:file'}"></dees-icon>
|
||||||
`}
|
`}
|
||||||
<span>${view.name}</span>
|
<span>${view.name}</span>
|
||||||
|
${hasSubs ? html`
|
||||||
|
<dees-icon class="chevron" .icon="${'lucide:chevronDown'}"></dees-icon>
|
||||||
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
`
|
${hasSubs && groupActive ? html`
|
||||||
)}
|
<div class="subViews">
|
||||||
|
${view.subViews!.map(
|
||||||
|
(sub) => html`
|
||||||
|
<div
|
||||||
|
class="viewTab sub ${this.selectedView === sub ? 'selected' : ''}"
|
||||||
|
@click=${(e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.loadView(sub);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${sub.iconName ? html`
|
||||||
|
<dees-icon .icon="${sub.iconName.includes(':') ? sub.iconName : `lucide:${sub.iconName}`}"></dees-icon>
|
||||||
|
` : html`
|
||||||
|
<dees-icon .icon="${'lucide:dot'}"></dees-icon>
|
||||||
|
`}
|
||||||
|
<span>${sub.name}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
`;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="appActions">
|
<div class="appActions">
|
||||||
@@ -771,8 +848,23 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private isGroupActive(view: IView): boolean {
|
||||||
|
if (this.selectedView === view) return true;
|
||||||
|
return view.subViews?.some((sv) => sv === this.selectedView) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
private currentView!: DeesElement;
|
private currentView!: DeesElement;
|
||||||
public async loadView(viewArg: IView) {
|
public async loadView(viewArg: IView) {
|
||||||
|
// Group-only parent: resolve to first sub view with an element
|
||||||
|
if (!viewArg.element && viewArg.subViews?.length) {
|
||||||
|
const firstNavigable = viewArg.subViews.find((sv) => sv.element);
|
||||||
|
if (firstNavigable) {
|
||||||
|
return this.loadView(firstNavigable);
|
||||||
|
}
|
||||||
|
return; // nothing navigable — ignore click
|
||||||
|
}
|
||||||
|
if (!viewArg.element) return; // safety: no element and no subs → no-op
|
||||||
|
|
||||||
const appcontent = this.shadowRoot!.querySelector('.appcontent')!;
|
const appcontent = this.shadowRoot!.querySelector('.appcontent')!;
|
||||||
const view = new viewArg.element();
|
const view = new viewArg.element();
|
||||||
if (this.currentView) {
|
if (this.currentView) {
|
||||||
|
|||||||
Reference in New Issue
Block a user