import { DeesElement, customElement, html, css, cssManager, property, state, type TemplateResult, } from '@design.estate/dees-element'; export interface IConfigField { key: string; value: string | number | boolean | string[] | null; type?: 'text' | 'boolean' | 'badge' | 'pills' | 'code' | 'link'; description?: string; linkTo?: string; } export interface IConfigSectionAction { label: string; icon?: string; event?: string; detail?: any; } export interface IConfigSectionLink { label: string; href: string; icon?: string; external?: boolean; } declare global { interface HTMLElementTagNameMap { 'sz-config-section': SzConfigSection; } } @customElement('sz-config-section') export class SzConfigSection extends DeesElement { public static demo = () => html` `; public static demoGroups = ['Configuration']; @property({ type: String }) public accessor title: string = ''; @property({ type: String }) public accessor subtitle: string = ''; @property({ type: String }) public accessor icon: string = ''; @property({ type: String }) public accessor status: 'enabled' | 'disabled' | 'not-configured' | 'warning' = 'enabled'; @property({ type: Array }) public accessor fields: IConfigField[] = []; @property({ type: Array }) public accessor actions: IConfigSectionAction[] = []; @property({ type: Array }) public accessor links: IConfigSectionLink[] = []; @property({ type: Boolean }) public accessor collapsible: boolean = false; @property({ type: Boolean }) public accessor collapsed: boolean = false; @state() accessor isCollapsed: boolean = false; public static styles = [ cssManager.defaultStyles, css` :host { display: block; margin-bottom: 16px; } 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; padding: 10px 16px; gap: 12px; width: 100%; box-sizing: border-box; cursor: default; user-select: none; } :host([collapsible]) .section-header { cursor: pointer; } :host([collapsible]) .section-header:hover { background: var(--dees-color-hover); } .header-left { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1; } .header-icon { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; background: var(--dees-color-border-default); border-radius: 6px; flex-shrink: 0; } .header-icon dees-icon { font-size: 14px; color: var(--dees-color-text-muted); } .header-text { min-width: 0; } .header-title { font-size: 13px; font-weight: 500; letter-spacing: -0.01em; color: var(--dees-color-text-secondary); line-height: 1.3; } .header-subtitle { font-size: 11px; color: var(--dees-color-text-muted); letter-spacing: -0.01em; line-height: 1.3; margin-top: 1px; } .header-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; } /* Status badge */ .status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 9999px; font-size: 12px; font-weight: 500; white-space: nowrap; } .status-badge.enabled { background: ${cssManager.bdTheme('#dcfce7', 'rgba(34,197,94,0.2)')}; color: ${cssManager.bdTheme('#16a34a', '#22c55e')}; } .status-badge.disabled { background: ${cssManager.bdTheme('#fee2e2', 'rgba(239,68,68,0.2)')}; color: ${cssManager.bdTheme('#dc2626', '#ef4444')}; } .status-badge.not-configured { background: ${cssManager.bdTheme('#f4f4f5', 'rgba(113,113,122,0.2)')}; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; } .status-badge.warning { background: ${cssManager.bdTheme('#fef3c7', 'rgba(245,158,11,0.15)')}; color: ${cssManager.bdTheme('#92400e', '#fbbf24')}; } .status-dot { width: 7px; height: 7px; border-radius: 50%; } .status-badge.enabled .status-dot { background: ${cssManager.bdTheme('#16a34a', '#22c55e')}; } .status-badge.disabled .status-dot { background: ${cssManager.bdTheme('#dc2626', '#ef4444')}; } .status-badge.not-configured .status-dot { background: ${cssManager.bdTheme('#a1a1aa', '#52525b')}; } .status-badge.warning .status-dot { background: ${cssManager.bdTheme('#f59e0b', '#fbbf24')}; } /* Footer action buttons — canonical dees-modal / dees-tile pattern */ .section-footer { display: flex; flex-direction: row; justify-content: flex-end; align-items: center; 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; cursor: pointer; 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; text-decoration: none; } .tile-button:first-child { border-left: none; } .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 */ .chevron { display: flex; align-items: center; transition: transform 200ms ease; } .chevron.collapsed { transform: rotate(-90deg); } .chevron dees-icon { font-size: 14px; color: var(--dees-color-text-muted); } /* Content */ .section-content { padding: 0; } /* Field rows */ .field-row { display: flex; align-items: flex-start; justify-content: space-between; padding: 10px 20px; border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1e')}; gap: 16px; } .field-row:last-child { border-bottom: none; } .field-key { font-size: 13px; font-weight: 500; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; flex-shrink: 0; min-width: 140px; padding-top: 1px; } .field-value { font-size: 13px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; text-align: right; word-break: break-all; } .field-value.null-value { color: ${cssManager.bdTheme('#a1a1aa', '#52525b')}; font-style: italic; font-family: inherit; } /* Boolean display */ .bool-value { display: inline-flex; align-items: center; gap: 5px; font-family: inherit; font-size: 13px; font-weight: 500; } .bool-value.true { color: ${cssManager.bdTheme('#16a34a', '#22c55e')}; } .bool-value.false { color: ${cssManager.bdTheme('#dc2626', '#ef4444')}; } .bool-dot { width: 6px; height: 6px; border-radius: 50%; } .bool-value.true .bool-dot { background: ${cssManager.bdTheme('#16a34a', '#22c55e')}; } .bool-value.false .bool-dot { background: ${cssManager.bdTheme('#dc2626', '#ef4444')}; } /* Pills */ .pills { display: flex; flex-wrap: wrap; gap: 5px; justify-content: flex-end; } .pill { display: inline-flex; align-items: center; padding: 2px 9px; border-radius: 9999px; font-size: 12px; font-weight: 500; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; background: ${cssManager.bdTheme('#eff6ff', 'rgba(59,130,246,0.1)')}; color: ${cssManager.bdTheme('#2563eb', '#60a5fa')}; } /* Code value */ .code-value { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 12px; background: ${cssManager.bdTheme('#f4f4f5', '#18181b')}; padding: 2px 8px; border-radius: 4px; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; } /* Link value */ .link-value { color: ${cssManager.bdTheme('#2563eb', '#60a5fa')}; cursor: pointer; text-decoration: none; font-family: inherit; font-size: 13px; } .link-value:hover { text-decoration: underline; } /* Description hint */ .field-description { font-size: 11px; color: ${cssManager.bdTheme('#a1a1aa', '#52525b')}; margin-top: 3px; text-align: right; } /* Slot for custom content */ .slot-content { border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1e')}; } .slot-content:empty { display: none; border-top: none; } /* Badge type */ .badge-value { display: inline-flex; align-items: center; padding: 2px 9px; border-radius: 9999px; font-size: 12px; font-weight: 500; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')}; color: ${cssManager.bdTheme('#52525b', '#a1a1aa')}; } `, ]; async connectedCallback() { await super.connectedCallback(); this.isCollapsed = this.collapsed; if (this.collapsible) { this.setAttribute('collapsible', ''); } } updated(changedProperties: Map) { super.updated(changedProperties); if (changedProperties.has('isCollapsed')) { this.toggleAttribute('collapsed', this.isCollapsed); } } public render(): TemplateResult { const statusLabels: Record = { 'enabled': 'Enabled', 'disabled': 'Disabled', 'not-configured': 'Not Configured', 'warning': 'Warning', }; return html` { if (this.collapsible) { this.isCollapsed = !this.isCollapsed; } }} > ${this.icon ? html` ` : ''} ${this.title} ${this.subtitle ? html`${this.subtitle}` : ''} ${this.status ? html` ${statusLabels[this.status] || this.status} ` : ''} ${this.collapsible ? html` ` : ''} ${this.fields.map(field => this.renderField(field))} ${this.links.length > 0 || this.actions.length > 0 ? html` ` : ''} `; } private renderField(field: IConfigField): TemplateResult { return html` ${field.key} ${this.renderFieldValue(field)} ${field.description ? html`${field.description}` : ''} `; } private renderFieldValue(field: IConfigField): TemplateResult { const value = field.value; const type = field.type || this.inferType(value); // Null / undefined if (value === null || value === undefined) { return html`Not configured`; } switch (type) { case 'boolean': return html` ${value ? 'Enabled' : 'Disabled'} `; case 'pills': if (Array.isArray(value) && value.length === 0) { return html`None`; } return html` ${(value as string[]).map(v => html`${v}`)} `; case 'code': return html`${String(value)}`; case 'badge': return html`${String(value)}`; case 'link': return html` { if (field.linkTo) { this.dispatchEvent(new CustomEvent('navigate', { detail: { target: field.linkTo }, bubbles: true, composed: true, })); } }} >${String(value)} `; default: return html`${String(value)}`; } } private inferType(value: unknown): string { if (typeof value === 'boolean') return 'boolean'; if (Array.isArray(value)) return 'pills'; return 'text'; } }