feat(elements): standardize dashboard and detail views on dees tile and stats grid components

This commit is contained in:
2026-04-07 22:27:23 +00:00
parent b91d3a9341
commit 9aad9c0c2a
16 changed files with 1148 additions and 972 deletions

View File

@@ -24,6 +24,13 @@ export interface IConfigSectionAction {
detail?: any;
}
export interface IConfigSectionLink {
label: string;
href: string;
icon?: string;
external?: boolean;
}
declare global {
interface HTMLElementTagNameMap {
'sz-config-section': SzConfigSection;
@@ -46,6 +53,12 @@ export class SzConfigSection extends DeesElement {
{ key: 'Auto Renew', value: true, type: 'boolean' },
{ key: 'Renew Threshold', value: '30 days' },
] as IConfigField[]}
.links=${[
{ label: 'Docs', href: 'https://code.foss.global/serve.zone/smartproxy', icon: 'lucide:bookOpen', external: true },
] as IConfigSectionLink[]}
.actions=${[
{ label: 'Configure', icon: 'lucide:settings', event: 'configure' },
] as IConfigSectionAction[]}
></sz-config-section>
<sz-config-section
title="Email Server"
@@ -57,6 +70,13 @@ export class SzConfigSection extends DeesElement {
{ key: 'Hostname', value: null },
{ key: 'Domains', value: ['example.com', 'mail.example.com'], type: 'pills' },
] as IConfigField[]}
.links=${[
{ label: 'Docs', href: 'https://code.foss.global/serve.zone/smartmta', icon: 'lucide:bookOpen', external: true },
{ label: 'Source', href: 'https://code.foss.global/serve.zone/smartmta', icon: 'lucide:github', external: true },
] as IConfigSectionLink[]}
.actions=${[
{ label: 'Enable', icon: 'lucide:power', event: 'enable' },
] as IConfigSectionAction[]}
></sz-config-section>
<sz-config-section
title="DNS Server"
@@ -68,6 +88,9 @@ export class SzConfigSection extends DeesElement {
{ key: 'Port', value: 53 },
{ key: 'NS Domains', value: ['ns1.example.com', 'ns2.example.com'], type: 'pills' },
] as IConfigField[]}
.links=${[
{ label: 'Getting Started', href: 'https://docs.example.com/dns', icon: 'lucide:bookOpen', external: true },
] as IConfigSectionLink[]}
></sz-config-section>
`;
@@ -91,6 +114,9 @@ export class SzConfigSection extends DeesElement {
@property({ type: Array })
public accessor actions: IConfigSectionAction[] = [];
@property({ type: Array })
public accessor links: IConfigSectionLink[] = [];
@property({ type: Boolean })
public accessor collapsible: boolean = false;
@@ -108,20 +134,25 @@ export class SzConfigSection extends DeesElement {
margin-bottom: 16px;
}
.section {
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
border-radius: 8px;
overflow: hidden;
dees-tile {
display: block;
}
:host([collapsed]) dees-tile::part(content) {
display: none;
}
:host([collapsed]) dees-tile::part(footer) {
display: none;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
border-bottom: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
padding: 10px 16px;
gap: 12px;
width: 100%;
box-sizing: border-box;
cursor: default;
user-select: none;
}
@@ -131,30 +162,31 @@ export class SzConfigSection extends DeesElement {
}
:host([collapsible]) .section-header:hover {
background: ${cssManager.bdTheme('#ebebed', '#1c1c1f')};
background: var(--dees-color-hover);
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
gap: 10px;
min-width: 0;
flex: 1;
}
.header-icon {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: ${cssManager.bdTheme('#e4e4e7', '#27272a')};
border-radius: 8px;
width: 28px;
height: 28px;
background: var(--dees-color-border-default);
border-radius: 6px;
flex-shrink: 0;
}
.header-icon dees-icon {
font-size: 18px;
color: ${cssManager.bdTheme('#52525b', '#a1a1aa')};
font-size: 14px;
color: var(--dees-color-text-muted);
}
.header-text {
@@ -162,15 +194,17 @@ export class SzConfigSection extends DeesElement {
}
.header-title {
font-size: 15px;
font-weight: 600;
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
font-size: 13px;
font-weight: 500;
letter-spacing: -0.01em;
color: var(--dees-color-text-secondary);
line-height: 1.3;
}
.header-subtitle {
font-size: 12px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
font-size: 11px;
color: var(--dees-color-text-muted);
letter-spacing: -0.01em;
line-height: 1.3;
margin-top: 1px;
}
@@ -236,30 +270,60 @@ export class SzConfigSection extends DeesElement {
background: ${cssManager.bdTheme('#f59e0b', '#fbbf24')};
}
/* Action buttons */
.header-action {
display: inline-flex;
/* Footer action buttons — canonical dees-modal / dees-tile pattern */
.section-footer {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: 5px;
padding: 4px 12px;
border-radius: 6px;
gap: 0;
height: 36px;
width: 100%;
box-sizing: border-box;
}
.tile-button {
padding: 0 16px;
height: 100%;
text-align: center;
font-size: 12px;
font-weight: 500;
color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
cursor: pointer;
user-select: none;
transition: all 0.15s ease;
background: transparent;
border: none;
cursor: pointer;
transition: background 150ms ease;
border-left: 1px solid var(--dees-color-border-subtle);
color: var(--dees-color-text-muted);
white-space: nowrap;
display: flex;
align-items: center;
gap: 6px;
font-family: inherit;
text-decoration: none;
}
.header-action:hover {
background: ${cssManager.bdTheme('rgba(37,99,235,0.08)', 'rgba(96,165,250,0.1)')};
.tile-button:first-child {
border-left: none;
}
.header-action dees-icon {
font-size: 14px;
.tile-button:hover {
background: var(--dees-color-hover);
color: var(--dees-color-text-primary);
}
.tile-button.primary {
color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
font-weight: 600;
}
.tile-button.primary:hover {
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.08)', 'hsl(213.1 93.9% 67.8% / 0.08)')};
color: ${cssManager.bdTheme('hsl(217.2 91.2% 50%)', 'hsl(213.1 93.9% 75%)')};
}
.tile-button dees-icon {
font-size: 12px;
}
/* Chevron */
@@ -274,8 +338,8 @@ export class SzConfigSection extends DeesElement {
}
.chevron dees-icon {
font-size: 16px;
color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
font-size: 14px;
color: var(--dees-color-text-muted);
}
/* Content */
@@ -283,10 +347,6 @@ export class SzConfigSection extends DeesElement {
padding: 0;
}
.section-content.collapsed {
display: none;
}
/* Field rows */
.field-row {
display: flex;
@@ -439,6 +499,13 @@ export class SzConfigSection extends DeesElement {
}
}
updated(changedProperties: Map<string, any>) {
super.updated(changedProperties);
if (changedProperties.has('isCollapsed')) {
this.toggleAttribute('collapsed', this.isCollapsed);
}
}
public render(): TemplateResult {
const statusLabels: Record<string, string> = {
'enabled': 'Enabled',
@@ -448,8 +515,9 @@ export class SzConfigSection extends DeesElement {
};
return html`
<div class="section">
<dees-tile>
<div
slot="header"
class="section-header"
@click=${() => {
if (this.collapsible) {
@@ -475,8 +543,36 @@ export class SzConfigSection extends DeesElement {
${statusLabels[this.status] || this.status}
</span>
` : ''}
${this.collapsible ? html`
<span class="chevron ${this.isCollapsed ? 'collapsed' : ''}">
<dees-icon .icon=${'lucide:chevronDown'}></dees-icon>
</span>
` : ''}
</div>
</div>
<div class="section-content">
${this.fields.map(field => this.renderField(field))}
<div class="slot-content">
<slot></slot>
</div>
</div>
${this.links.length > 0 || this.actions.length > 0 ? html`
<div slot="footer" class="section-footer">
${this.links.map(link => html`
<a
class="tile-button"
href=${link.href}
target=${link.external ? '_blank' : '_self'}
rel=${link.external ? 'noopener noreferrer' : ''}
@click=${(e: Event) => e.stopPropagation()}
>
${link.icon ? html`<dees-icon .icon=${link.icon}></dees-icon>` : ''}
${link.label}
${link.external ? html`<dees-icon .icon=${'lucide:externalLink'}></dees-icon>` : ''}
</a>
`)}
${this.actions.map(action => html`
<button class="header-action" @click=${(e: Event) => {
<button class="tile-button primary" @click=${(e: Event) => {
e.stopPropagation();
this.dispatchEvent(new CustomEvent(action.event || 'action', {
detail: action.detail || { label: action.label },
@@ -488,20 +584,9 @@ export class SzConfigSection extends DeesElement {
${action.label}
</button>
`)}
${this.collapsible ? html`
<span class="chevron ${this.isCollapsed ? 'collapsed' : ''}">
<dees-icon .icon=${'lucide:chevronDown'}></dees-icon>
</span>
` : ''}
</div>
</div>
<div class="section-content ${this.isCollapsed ? 'collapsed' : ''}">
${this.fields.map(field => this.renderField(field))}
<div class="slot-content">
<slot></slot>
</div>
</div>
</div>
` : ''}
</dees-tile>
`;
}