import { DeesElement, property, html, customElement, type TemplateResult, cssManager, css, unsafeCSS } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; import type { IOverallStatus } from '../interfaces/index.js'; import * as sharedStyles from '../styles/shared.styles.js'; import { demoFunc } from './upl-statuspage-statusbar.demo.js'; declare global { interface HTMLElementTagNameMap { 'upl-statuspage-statusbar': UplStatuspageStatusbar; } } @customElement('upl-statuspage-statusbar') export class UplStatuspageStatusbar extends DeesElement { public static demo = demoFunc; @property({ type: Object }) accessor overallStatus: IOverallStatus = { status: 'operational', message: 'All Systems Operational', lastUpdated: Date.now(), affectedServices: 0, totalServices: 0 }; @property({ type: Boolean }) accessor loading: boolean = false; @property({ type: Boolean }) accessor expandable: boolean = true; constructor() { super(); } public static styles = [ cssManager.defaultStyles, css` :host { padding: 0; display: block; background: transparent; font-family: ${unsafeCSS(sharedStyles.fonts.base)}; } .statusbar-container { margin: auto; max-width: 1200px; padding: ${unsafeCSS(sharedStyles.spacing.lg)}; position: relative; } .statusbar-inner { display: flex; align-items: center; justify-content: space-between; min-height: 72px; padding: ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.xl)}; border-radius: ${unsafeCSS(sharedStyles.borderRadius.xl)}; cursor: default; transition: all ${unsafeCSS(sharedStyles.durations.slow)} ${unsafeCSS(sharedStyles.easings.default)}; position: relative; overflow: hidden; font-weight: 500; font-size: 15px; letter-spacing: -0.01em; border: 1px solid ${sharedStyles.colors.border.default}; box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)}; } /* Gradient background overlay */ .statusbar-inner::before { content: ''; position: absolute; inset: 0; opacity: 1; transition: opacity ${unsafeCSS(sharedStyles.durations.slow)} ${unsafeCSS(sharedStyles.easings.default)}; z-index: 0; } /* Left accent border */ .statusbar-inner::after { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; transition: background ${unsafeCSS(sharedStyles.durations.normal)} ${unsafeCSS(sharedStyles.easings.default)}; z-index: 1; } /* Operational - green gradient */ .statusbar-inner.operational { background: ${sharedStyles.colors.background.card}; } .statusbar-inner.operational::before { background: ${sharedStyles.statusGradients.operational}; } .statusbar-inner.operational::after { background: ${sharedStyles.colors.status.operational}; } /* Degraded - yellow/amber gradient */ .statusbar-inner.degraded { background: ${sharedStyles.colors.background.card}; } .statusbar-inner.degraded::before { background: ${sharedStyles.statusGradients.degraded}; } .statusbar-inner.degraded::after { background: ${sharedStyles.colors.status.degraded}; } /* Partial outage - orange/red gradient */ .statusbar-inner.partial_outage { background: ${sharedStyles.colors.background.card}; } .statusbar-inner.partial_outage::before { background: ${sharedStyles.statusGradients.partial}; } .statusbar-inner.partial_outage::after { background: ${sharedStyles.colors.status.partial}; } /* Major outage - red gradient */ .statusbar-inner.major_outage { background: ${sharedStyles.colors.background.card}; } .statusbar-inner.major_outage::before { background: ${sharedStyles.statusGradients.major}; } .statusbar-inner.major_outage::after { background: ${sharedStyles.colors.status.major}; } /* Maintenance - blue gradient */ .statusbar-inner.maintenance { background: ${sharedStyles.colors.background.card}; } .statusbar-inner.maintenance::before { background: ${sharedStyles.statusGradients.maintenance}; } .statusbar-inner.maintenance::after { background: ${sharedStyles.colors.status.maintenance}; } .statusbar-inner:hover { border-color: ${sharedStyles.colors.border.muted}; box-shadow: ${unsafeCSS(sharedStyles.shadows.md)}; transform: translateY(-1px); } .statusbar-inner:hover::before { opacity: 1.2; } .status-indicator { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; position: relative; } .status-indicator::after { content: ''; position: absolute; inset: -3px; border-radius: 50%; opacity: 0.2; } .statusbar-inner.operational .status-indicator { background: ${sharedStyles.colors.status.operational}; box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(22, 163, 74, 0.15)', 'rgba(34, 197, 94, 0.2)')}; } .statusbar-inner.operational .status-indicator::after { background: ${sharedStyles.colors.status.operational}; animation: pulse-ring 2s ease-out infinite; } .statusbar-inner.degraded .status-indicator { background: ${sharedStyles.colors.status.degraded}; box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(217, 119, 6, 0.15)', 'rgba(251, 191, 36, 0.2)')}; } .statusbar-inner.partial_outage .status-indicator { background: ${sharedStyles.colors.status.partial}; box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(220, 38, 38, 0.15)', 'rgba(248, 113, 113, 0.2)')}; } .statusbar-inner.major_outage .status-indicator { background: ${sharedStyles.colors.status.major}; box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(185, 28, 28, 0.15)', 'rgba(239, 68, 68, 0.2)')}; animation: pulse-indicator 1s ease-in-out infinite; } .statusbar-inner.maintenance .status-indicator { background: ${sharedStyles.colors.status.maintenance}; box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(37, 99, 235, 0.15)', 'rgba(96, 165, 250, 0.2)')}; } @keyframes pulse-ring { 0% { transform: scale(1); opacity: 0.2; } 50% { transform: scale(1.5); opacity: 0; } 100% { transform: scale(1); opacity: 0; } } @keyframes pulse-indicator { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } .status-content { display: flex; align-items: center; gap: ${unsafeCSS(sharedStyles.spacing.md)}; flex: 1; padding-left: ${unsafeCSS(sharedStyles.spacing.sm)}; } .status-main { display: flex; align-items: center; gap: ${unsafeCSS(sharedStyles.spacing.sm)}; color: ${sharedStyles.colors.text.primary}; } .status-message { font-weight: 600; } .status-details { font-size: 13px; font-weight: 400; color: ${sharedStyles.colors.text.secondary}; } .loading-skeleton { background: ${sharedStyles.colors.background.card}; border: 1px solid ${sharedStyles.colors.border.default}; border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)}; height: 64px; position: relative; overflow: hidden; } .loading-skeleton::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: ${cssManager.bdTheme( 'linear-gradient(90deg, transparent 0%, rgba(0,0,0,0.04) 50%, transparent 100%)', 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.04) 50%, transparent 100%)' )}; animation: loading 1.5s ${unsafeCSS(sharedStyles.easings.default)} infinite; } @keyframes loading { 0% { transform: translateX(-100%); } 100% { transform: translateX(200%); } } .last-updated { font-size: 12px; color: ${sharedStyles.colors.text.muted}; white-space: nowrap; padding: 4px 10px; background: ${sharedStyles.colors.background.muted}; border-radius: ${unsafeCSS(sharedStyles.borderRadius.full)}; transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)}; } .statusbar-inner:hover .last-updated { background: ${cssManager.bdTheme('#e4e4e7', '#3f3f46')}; } @media (max-width: 768px) { .statusbar-container { padding: ${unsafeCSS(sharedStyles.spacing.md)}; } .statusbar-inner { flex-direction: column; align-items: flex-start; gap: ${unsafeCSS(sharedStyles.spacing.sm)}; min-height: auto; padding: ${unsafeCSS(sharedStyles.spacing.md)}; } .status-main { flex-direction: column; align-items: flex-start; gap: 4px; } .last-updated { align-self: flex-start; } } @media (max-width: 640px) { .statusbar-inner { font-size: 14px; } .status-indicator { width: 8px; height: 8px; } .last-updated { font-size: 11px; } } `, ] public render(): TemplateResult { const formatLastUpdated = () => { const date = new Date(this.overallStatus.lastUpdated); const now = new Date(); const diff = now.getTime() - date.getTime(); const minutes = Math.floor(diff / 60000); if (minutes < 1) return 'Just now'; if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; return date.toLocaleDateString(); }; return html`
`; } }