6 Commits

5 changed files with 120 additions and 66 deletions

View File

@@ -1,5 +1,23 @@
# Changelog
## 2026-04-07 - 2.12.3 - fix(route-card)
align route card with dees-tile layout and update header and footer styling
- replace the custom card wrapper with dees-tile and move content into header, body, and footer slots
- restyle header, route name, and action buttons to match shared tile design tokens and interaction states
- wrap empty and populated route states consistently inside the tile component
## 2026-04-07 - 2.12.2 - fix(ts_web)
adjust route card section background color in dark theme
- Updates the dark theme background for route card sections from #0a0a0a to #101010 for improved visual consistency.
## 2026-04-07 - 2.12.1 - fix(ts_web)
handle slotted config section content visibility and field row borders correctly
- track whether the default slot has assigned content and hide the slot container when empty
- wrap rendered fields in a dedicated list so the last field row border is removed correctly when slot content is present
## 2026-04-07 - 2.12.0 - feat(elements)
standardize dashboard and detail views on dees tile and stats grid components

View File

@@ -1,6 +1,6 @@
{
"name": "@serve.zone/catalog",
"version": "2.12.0",
"version": "2.12.3",
"private": false,
"description": "UI component catalog for serve.zone",
"main": "dist_ts_web/index.js",

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/catalog',
version: '2.12.0',
version: '2.12.3',
description: 'UI component catalog for serve.zone'
}

View File

@@ -126,6 +126,9 @@ export class SzConfigSection extends DeesElement {
@state()
accessor isCollapsed: boolean = false;
@state()
accessor hasSlottedContent: boolean = false;
public static styles = [
cssManager.defaultStyles,
css`
@@ -347,6 +350,11 @@ export class SzConfigSection extends DeesElement {
padding: 0;
}
.fields-list {
display: flex;
flex-direction: column;
}
/* Field rows */
.field-row {
display: flex;
@@ -357,7 +365,7 @@ export class SzConfigSection extends DeesElement {
gap: 16px;
}
.field-row:last-child {
.fields-list .field-row:last-child {
border-bottom: none;
}
@@ -472,9 +480,8 @@ export class SzConfigSection extends DeesElement {
border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1e')};
}
.slot-content:empty {
.slot-content.empty {
display: none;
border-top: none;
}
/* Badge type */
@@ -506,6 +513,11 @@ export class SzConfigSection extends DeesElement {
}
}
private onSlotChange(e: Event) {
const slot = e.target as HTMLSlotElement;
this.hasSlottedContent = slot.assignedNodes({ flatten: true }).length > 0;
}
public render(): TemplateResult {
const statusLabels: Record<string, string> = {
'enabled': 'Enabled',
@@ -551,9 +563,13 @@ export class SzConfigSection extends DeesElement {
</div>
</div>
<div class="section-content">
${this.fields.map(field => this.renderField(field))}
<div class="slot-content">
<slot></slot>
${this.fields.length > 0 ? html`
<div class="fields-list">
${this.fields.map(field => this.renderField(field))}
</div>
` : ''}
<div class="slot-content ${!this.hasSlottedContent ? 'empty' : ''}">
<slot @slotchange=${this.onSlotChange}></slot>
</div>
</div>
${this.links.length > 0 || this.actions.length > 0 ? html`

View File

@@ -163,26 +163,25 @@ export class SzRouteCard extends DeesElement {
display: block;
}
.card {
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
border-radius: 8px;
padding: 20px;
dees-tile::part(outer) {
transition: border-color 200ms ease, box-shadow 200ms ease;
}
.card:hover {
border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
dees-tile:hover::part(outer) {
border-color: var(--dees-color-border-strong);
box-shadow: 0 2px 8px ${cssManager.bdTheme('rgba(0,0,0,0.06)', 'rgba(0,0,0,0.2)')};
}
/* Header */
.header {
display: flex;
align-items: flex-start;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 4px;
height: 40px;
padding: 0 16px;
width: 100%;
box-sizing: border-box;
}
.header-left {
@@ -190,6 +189,7 @@ export class SzRouteCard extends DeesElement {
align-items: center;
gap: 8px;
min-width: 0;
flex: 1;
}
.status-dot {
@@ -209,9 +209,10 @@ export class SzRouteCard extends DeesElement {
}
.route-name {
font-size: 15px;
font-weight: 600;
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
font-size: 14px;
font-weight: 500;
letter-spacing: -0.01em;
color: var(--dees-color-text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -253,6 +254,10 @@ export class SzRouteCard extends DeesElement {
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
}
.card-body {
padding: 16px 20px;
}
.description {
font-size: 13px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
@@ -296,7 +301,7 @@ export class SzRouteCard extends DeesElement {
padding: 10px 14px;
margin-bottom: 12px;
border-radius: 0 6px 6px 0;
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
background: ${cssManager.bdTheme('#fafafa', '#101010')};
}
.section:last-of-type {
@@ -503,43 +508,52 @@ export class SzRouteCard extends DeesElement {
color: ${cssManager.bdTheme('#0e7490', '#22d3ee')};
}
.card-actions {
.card-footer {
display: flex;
gap: 8px;
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1a')};
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: 0;
height: 36px;
width: 100%;
box-sizing: border-box;
}
.action-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 12px;
border-radius: 6px;
.tile-button {
padding: 0 16px;
height: 100%;
text-align: center;
font-size: 12px;
font-weight: 500;
cursor: pointer;
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
color: ${cssManager.bdTheme('#52525b', '#a1a1aa')};
transition: all 150ms ease;
user-select: none;
transition: all 0.15s ease;
background: transparent;
border: none;
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;
}
.action-btn:hover {
border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
.tile-button:first-child {
border-left: none;
}
.action-btn.edit:hover {
border-color: ${cssManager.bdTheme('#93c5fd', 'rgba(59, 130, 246, 0.5)')};
color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
.tile-button:hover {
background: var(--dees-color-hover);
color: var(--dees-color-text-primary);
}
.action-btn.delete:hover {
border-color: ${cssManager.bdTheme('#fca5a5', 'rgba(239, 68, 68, 0.5)')};
.tile-button.edit:hover {
color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
}
.tile-button.delete:hover {
background: ${cssManager.bdTheme('#fee2e2', 'rgba(239, 68, 68, 0.1)')};
color: ${cssManager.bdTheme('#dc2626', '#f87171')};
}
`,
@@ -547,7 +561,11 @@ export class SzRouteCard extends DeesElement {
public render(): TemplateResult {
if (!this.route) {
return html`<div class="card"><div class="no-route">No route data</div></div>`;
return html`
<dees-tile>
<div class="card-body"><div class="no-route">No route data</div></div>
</dees-tile>
`;
}
const r = this.route;
@@ -557,9 +575,9 @@ export class SzRouteCard extends DeesElement {
const security = r.security;
return html`
<div class="card">
<dees-tile>
<!-- Header -->
<div class="header">
<div slot="header" class="header">
<div class="header-left">
<span class="status-dot ${isEnabled ? 'enabled' : 'disabled'}"></span>
<span class="route-name">${r.name || r.id || 'Unnamed Route'}</span>
@@ -570,16 +588,17 @@ export class SzRouteCard extends DeesElement {
</div>
</div>
${r.description ? html`<div class="description">${r.description}</div>` : ''}
<div class="card-body">
${r.description ? html`<div class="description">${r.description}</div>` : ''}
<div class="meta-row">
${r.tags && r.tags.length > 0
? html`<div class="tags">${r.tags.map((t) => html`<span class="tag">${t}</span>`)}</div>`
: html`<div></div>`}
${r.priority != null ? html`<span class="priority">Priority: ${r.priority}</span>` : ''}
</div>
<div class="meta-row">
${r.tags && r.tags.length > 0
? html`<div class="tags">${r.tags.map((t) => html`<span class="tag">${t}</span>`)}</div>`
: html`<div></div>`}
${r.priority != null ? html`<span class="priority">Priority: ${r.priority}</span>` : ''}
</div>
<!-- Match Section -->
<!-- Match Section -->
<div class="section match">
<div class="section-label">Match</div>
<div class="field-row">
@@ -735,19 +754,20 @@ export class SzRouteCard extends DeesElement {
`
: ''}
<!-- VPN Section -->
${this.renderVpn()}
<!-- VPN Section -->
${this.renderVpn()}
<!-- Linked References Section -->
${this.renderLinked()}
<!-- Linked References Section -->
${this.renderLinked()}
<!-- Feature Icons Row -->
${this.renderFeatures()}
<!-- Feature Icons Row -->
${this.renderFeatures()}
</div>
<!-- Action Buttons -->
${this.showActions ? html`
<div class="card-actions">
<button class="action-btn edit" @click=${(e: Event) => {
<div slot="footer" class="card-footer">
<button class="tile-button edit" @click=${(e: Event) => {
e.stopPropagation();
this.dispatchEvent(new CustomEvent('route-edit', {
detail: this.route,
@@ -755,7 +775,7 @@ export class SzRouteCard extends DeesElement {
composed: true,
}));
}}>Edit</button>
<button class="action-btn delete" @click=${(e: Event) => {
<button class="tile-button delete" @click=${(e: Event) => {
e.stopPropagation();
this.dispatchEvent(new CustomEvent('route-delete', {
detail: this.route,
@@ -765,7 +785,7 @@ export class SzRouteCard extends DeesElement {
}}>Delete</button>
</div>
` : ''}
</div>
</dees-tile>
`;
}