Files
catalog/ts_web/elements/upl-statuspage-statusbar.ts
2025-06-29 22:36:12 +00:00

237 lines
6.3 KiB
TypeScript

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 { fonts, colors } 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 })
public overallStatus: IOverallStatus = {
status: 'operational',
message: 'All Systems Operational',
lastUpdated: Date.now(),
affectedServices: 0,
totalServices: 0
};
@property({ type: Boolean })
public loading: boolean = false;
@property({ type: Boolean })
public expandable: boolean = true;
constructor() {
super();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
padding: 0;
display: block;
background: transparent;
font-family: ${unsafeCSS(fonts.base)};
}
.statusbar-container {
margin: auto;
max-width: 1200px;
padding: 0 24px 24px 24px;
position: relative;
}
.statusbar-inner {
display: flex;
align-items: center;
justify-content: center;
min-height: 64px;
padding: 16px 24px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
font-weight: 500;
font-size: 16px;
border: 1px solid transparent;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
}
.statusbar-inner:hover {
transform: translateY(-1px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06);
}
.statusbar-inner:active {
transform: translateY(0);
}
.statusbar-inner.operational {
background: ${colors.status.operational};
border-color: ${colors.status.operational};
color: white;
}
.statusbar-inner.degraded {
background: ${colors.status.degraded};
border-color: ${colors.status.degraded};
color: white;
}
.statusbar-inner.partial_outage {
background: ${colors.status.partial};
border-color: ${colors.status.partial};
color: white;
}
.statusbar-inner.major_outage {
background: ${colors.status.major};
border-color: ${colors.status.major};
color: white;
}
.statusbar-inner.maintenance {
background: ${colors.status.maintenance};
border-color: ${colors.status.maintenance};
color: white;
}
.status-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.status-main {
display: flex;
align-items: center;
gap: 8px;
}
.status-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
font-size: 14px;
}
.loading-skeleton {
background: ${cssManager.bdTheme(
'linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%)',
'linear-gradient(90deg, #1f1f1f 25%, #262626 50%, #1f1f1f 75%)'
)};
background-size: 200% 100%;
animation: loading 1.5s infinite;
height: 64px;
border-radius: 8px;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.status-details {
font-size: 14px;
opacity: 0.9;
}
.last-updated {
font-size: 13px;
text-align: right;
margin-top: 12px;
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
}
@media (max-width: 640px) {
.statusbar-inner {
font-size: 14px;
padding: 12px 16px;
min-height: 56px;
}
.status-icon {
width: 18px;
height: 18px;
font-size: 12px;
}
}
`,
]
public render(): TemplateResult {
const getStatusIcon = () => {
switch (this.overallStatus.status) {
case 'operational':
return '✓';
case 'degraded':
return '!';
case 'partial_outage':
return '⚠';
case 'major_outage':
return '✕';
case 'maintenance':
return '🔧';
default:
return '';
}
};
const formatLastUpdated = () => {
const date = new Date(this.overallStatus.lastUpdated);
return date.toLocaleString();
};
const handleClick = () => {
if (this.expandable) {
this.dispatchEvent(new CustomEvent('statusClick', {
detail: { status: this.overallStatus },
bubbles: true,
composed: true
}));
}
};
return html`
<div class="statusbar-container">
${this.loading ? html`
<div class="loading-skeleton"></div>
` : html`
<div class="statusbar-inner ${this.overallStatus.status}" @click=${handleClick}>
<div class="status-content">
<div class="status-main">
<span class="status-icon">${getStatusIcon()}</span>
<span>${this.overallStatus.message}</span>
</div>
${this.overallStatus.affectedServices > 0 ? html`
<div class="status-details">
${this.overallStatus.affectedServices} of ${this.overallStatus.totalServices} services affected
</div>
` : ''}
</div>
</div>
<div class="last-updated">
Last updated: ${formatLastUpdated()}
</div>
`}
</div>
`;
}
}