diff --git a/.nogit/theme-test.html b/.nogit/theme-test.html new file mode 100644 index 0000000..9ace80e --- /dev/null +++ b/.nogit/theme-test.html @@ -0,0 +1,67 @@ + + + + Theme Test + + + + + + +
+
Severity Variants
+
+ + + + +
+
+ +
+
Status Variants
+
+ + + + + +
+
+ + + + diff --git a/.playwright-mcp/dark-mode-emulated.png b/.playwright-mcp/dark-mode-emulated.png new file mode 100644 index 0000000..900c13b Binary files /dev/null and b/.playwright-mcp/dark-mode-emulated.png differ diff --git a/.playwright-mcp/dark-mode-option-cards.png b/.playwright-mcp/dark-mode-option-cards.png new file mode 100644 index 0000000..995a9ec Binary files /dev/null and b/.playwright-mcp/dark-mode-option-cards.png differ diff --git a/.playwright-mcp/light-mode-option-cards.png b/.playwright-mcp/light-mode-option-cards.png new file mode 100644 index 0000000..02a670d Binary files /dev/null and b/.playwright-mcp/light-mode-option-cards.png differ diff --git a/changelog.md b/changelog.md index 42684e0..6f94fd8 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-12-26 - 1.2.0 - feat(elements) +add upladmin-option-card component and migrate option/status UIs to use it; refactor monitor form multitoggle subscriptions and event handling; improve theme color handling and dark-mode styles; add demos, Playwright snapshots, and migration plan + +- Add new upladmin-option-card web component (implementation, index export, and demo). +- Replace inline option/status card markup with in incident-form and incident-update. +- Refactor upladmin-monitor-form: subscribe/unsubscribe multitoggle change subjects, handle lifecycle (firstUpdated/disconnected), and adjust event handlers (@newValue/@change usage). +- Swap hardcoded color tokens for cssManager.bdTheme for better light/dark theming; add dark-mode media tweak for filter select. +- Add Playwright snapshot images (.playwright-mcp) and a detailed readme.plan.md describing interface migration tasks. + ## 2025-12-24 - 1.1.0 - feat(monitor) add extended monitor statuses, check configuration, status overrides/paused indicators, and incident update templates diff --git a/readme.plan.md b/readme.plan.md new file mode 100644 index 0000000..852127d --- /dev/null +++ b/readme.plan.md @@ -0,0 +1,396 @@ +# Plan: Migrate Shared Interfaces to @uptime.link/interfaces + +## Overview + +Move shared type definitions from `catalog_admin/ts_web/interfaces/` to the canonical `../interfaces` package (`@uptime.link/interfaces`) to ensure consistency across all uptime.link packages. + +## Decisions (Resolved) + +1. **Check Config**: Use base type + discriminated union variants (elegant, type-safe) +2. **Incident Status**: Create unified type in shared package, migrate both packages +3. **Form Interfaces**: Keep local in catalog_admin (UI-specific) +4. **Versioning**: Manual releases - will notify when ready to publish + +--- + +## Task 1: Add Base Types to Shared Package + +**File: `../interfaces/ts/data/types.ts`** (new) + +```typescript +// Status types for monitors/services +export type TStatusType = + | 'operational' + | 'degraded' + | 'partial_outage' + | 'major_outage' + | 'maintenance' + | 'initializing' + | 'error' + | 'paused'; + +// Check types (discriminant values) +export type TCheckType = 'assumption' | 'function' | 'pwa' | 'pagerank'; + +// Status mode for monitors +export type TStatusMode = 'auto' | 'manual'; + +// Incident severity +export type TIncidentSeverity = 'critical' | 'major' | 'minor' | 'maintenance'; + +// Incident status (unified) +export type TIncidentStatus = + | 'investigating' + | 'identified' + | 'monitoring' + | 'resolved' + | 'postmortem'; +``` + +--- + +## Task 2: Refactor Check Interfaces with Base + Variants + +**File: `../interfaces/ts/data/checks/index.ts`** (refactor) + +```typescript +import { TStatusType, TCheckType } from '../types.js'; + +// ============================================ +// Base Interface +// ============================================ + +export interface ICheckBase { + id: string; + name: string; + description?: string; + enabled: boolean; + intervalMs?: number; + lastRun?: number; + lastResult?: 'success' | 'failure' | 'pending'; +} + +// ============================================ +// Discriminated Variants +// ============================================ + +export interface IAssumptionCheck extends ICheckBase { + checkType: 'assumption'; + assumedStatus: TStatusType; +} + +export interface IFunctionCheck extends ICheckBase { + checkType: 'function'; + functionUrl: string; + expectedStatusCode?: number; + timeoutMs?: number; + headers?: Record; +} + +export interface IPwaCheck extends ICheckBase { + checkType: 'pwa'; + targetUrl: string; + lighthouseThreshold?: number; + categories?: ('performance' | 'accessibility' | 'best-practices' | 'seo')[]; +} + +export interface IPageRankCheck extends ICheckBase { + checkType: 'pagerank'; + targetUrl: string; + minimumRank?: number; + searchEngine?: 'google' | 'bing'; +} + +// ============================================ +// Union Type (for UI and generic handling) +// ============================================ + +export type TCheck = + | IAssumptionCheck + | IFunctionCheck + | IPwaCheck + | IPageRankCheck; + +// ============================================ +// Check Collection +// ============================================ + +export interface ICheckCollection { + id: string; + name: string; + checks: TCheck[]; +} +``` + +--- + +## Task 3: Create Unified Incident Interface + +**File: `../interfaces/ts/data/incident.ts`** (refactor) + +```typescript +import { TIncidentSeverity, TIncidentStatus } from './types.js'; + +export interface IIncidentUpdate { + id: string; + incidentId: string; + status: TIncidentStatus; + message: string; + createdAt: number; + createdBy?: string; +} + +export interface IIncident { + id: string; + title: string; + description: string; + severity: TIncidentSeverity; + status: TIncidentStatus; + + // Affected services + affectedServiceIds: string[]; + + // Timeline + createdAt: number; + updatedAt: number; + resolvedAt?: number; + + // Updates history + updates: IIncidentUpdate[]; + + // Metadata + createdBy?: string; + isScheduled?: boolean; + scheduledStartTime?: number; + scheduledEndTime?: number; +} +``` + +--- + +## Task 4: Add Service Status Interface + +**File: `../interfaces/ts/data/servicestatus.ts`** (new) + +```typescript +import { TStatusType, TStatusMode, TCheckType } from './types.js'; + +export interface IServiceStatus { + id: string; + name: string; + displayName: string; + description?: string; + + // Current state + currentStatus: TStatusType; + lastChecked: number; + responseTime: number; + + // Uptime metrics + uptime30d: number; + uptime90d: number; + + // Organization + category?: string; + dependencies?: string[]; + + // Status management + statusMode: TStatusMode; + manualStatus?: TStatusType; + paused: boolean; + + // Check configuration (references check collection) + checkType?: TCheckType; + checkCollectionId?: string; + intervalMs?: number; +} + +export interface IStatusHistoryPoint { + timestamp: number; + status: TStatusType; + responseTime?: number; +} + +export interface IOverallStatus { + status: TStatusType; + message?: string; + lastUpdated: number; +} +``` + +--- + +## Task 5: Add Status Page Config + +**File: `../interfaces/ts/data/statuspageconfig.ts`** (new) + +```typescript +import { IOverallStatus } from './servicestatus.js'; + +export interface IStatusPageConfig { + id: string; + name: string; + slug: string; + + // Branding + logoUrl?: string; + faviconUrl?: string; + primaryColor?: string; + + // Content + headerTitle: string; + headerDescription?: string; + + // Features + showHistoricalUptime: boolean; + showResponseTime: boolean; + showSubscribeButton: boolean; + + // Service grouping + serviceGroups: IServiceGroup[]; + + // Overall status override + overallStatus?: IOverallStatus; +} + +export interface IServiceGroup { + id: string; + name: string; + description?: string; + serviceIds: string[]; + expanded: boolean; +} +``` + +--- + +## Task 6: Update Shared Package Exports + +**File: `../interfaces/ts/data/index.ts`** (update) + +```typescript +export * from './types.js'; +export * from './checks/index.js'; +export * from './incident.js'; +export * from './servicestatus.js'; +export * from './statuspageconfig.js'; +// ... existing exports +``` + +--- + +## Task 7: Update catalog_admin Interfaces + +**File: `catalog_admin/ts_web/interfaces/index.ts`** (refactor) + +```typescript +// Re-export shared types from @uptime.link/interfaces +export { + // Types + TStatusType, + TCheckType, + TStatusMode, + TIncidentSeverity, + TIncidentStatus, + + // Check interfaces + ICheckBase, + IAssumptionCheck, + IFunctionCheck, + IPwaCheck, + IPageRankCheck, + TCheck, + ICheckCollection, + + // Incident interfaces + IIncident, + IIncidentUpdate, + + // Service/Status interfaces + IServiceStatus, + IStatusHistoryPoint, + IOverallStatus, + IStatusPageConfig, + IServiceGroup, +} from '@uptime.link/interfaces'; + +// ============================================ +// Form Interfaces (UI-specific, kept local) +// ============================================ + +import type { TStatusType, TCheckType, TStatusMode, TIncidentSeverity } from '@uptime.link/interfaces'; + +export interface IMonitorFormData { + name: string; + displayName: string; + description?: string; + category?: string; + checkType: TCheckType; + intervalMs: number; + statusMode: TStatusMode; + paused: boolean; + // Check-specific fields (form flattens the discriminated union) + assumedStatus?: TStatusType; + functionUrl?: string; + expectedStatusCode?: number; + targetUrl?: string; + lighthouseThreshold?: number; + minimumRank?: number; +} + +export interface IIncidentFormData { + title: string; + description: string; + severity: TIncidentSeverity; + affectedServiceIds: string[]; + isScheduled: boolean; + scheduledStartTime?: number; + scheduledEndTime?: number; +} + +export interface IIncidentUpdateFormData { + status: string; + message: string; +} +``` + +--- + +## Task 8: Update Component Imports + +Scan and update all components that import from local interfaces to ensure they work with the new types. Key files: + +- `ts_web/elements/upladmin-monitor-form/upladmin-monitor-form.ts` +- `ts_web/elements/upladmin-monitor-list/upladmin-monitor-list.ts` +- `ts_web/elements/upladmin-incident-form/upladmin-incident-form.ts` +- `ts_web/elements/upladmin-incident-list/upladmin-incident-list.ts` +- `ts_web/elements/upladmin-incident-update/upladmin-incident-update.ts` + +--- + +## Task 9: Build and Test + +1. Build `../interfaces`: `cd ../interfaces && pnpm build` +2. **Notify for release** of `@uptime.link/interfaces` +3. Update dependency: `pnpm update @uptime.link/interfaces` +4. Build catalog_admin: `pnpm build` +5. Verify no type errors +6. Test UI components manually + +--- + +## Files Summary + +### New files in `../interfaces`: +- `ts/data/types.ts` +- `ts/data/servicestatus.ts` +- `ts/data/statuspageconfig.ts` + +### Modified files in `../interfaces`: +- `ts/data/checks/index.ts` (refactor to base + variants) +- `ts/data/incident.ts` (unified interface) +- `ts/data/index.ts` (add exports) + +### Modified files in `catalog_admin`: +- `ts_web/interfaces/index.ts` (re-export from shared + local form types) +- Component files (if import paths change) diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 12e3bb8..e9b3e06 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@uptime.link/statuspage-admin', - version: '1.1.0', + version: '1.2.0', description: 'Admin components for managing UptimeLink status pages, monitors, and incidents.' } diff --git a/ts_web/elements/index.ts b/ts_web/elements/index.ts index 7f71459..5321278 100644 --- a/ts_web/elements/index.ts +++ b/ts_web/elements/index.ts @@ -1,3 +1,6 @@ +// Shared components +export * from './upladmin-option-card/index.js'; + // Monitor components export * from './upladmin-monitor-form/index.js'; export * from './upladmin-monitor-list/index.js'; diff --git a/ts_web/elements/upladmin-incident-form/upladmin-incident-form.ts b/ts_web/elements/upladmin-incident-form/upladmin-incident-form.ts index 4110743..53ef18c 100644 --- a/ts_web/elements/upladmin-incident-form/upladmin-incident-form.ts +++ b/ts_web/elements/upladmin-incident-form/upladmin-incident-form.ts @@ -168,51 +168,6 @@ export class UpladminIncidentForm extends DeesElement { gap: ${unsafeCSS(sharedStyles.spacing.sm)}; } - .option-card { - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - padding: 18px 14px; - background: ${sharedStyles.colors.background.primary}; - border: 2px solid ${sharedStyles.colors.border.default}; - border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)}; - cursor: pointer; - transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)}; - text-align: center; - } - - .option-card:hover { - border-color: ${sharedStyles.colors.border.strong}; - background: ${sharedStyles.colors.background.muted}; - } - - .option-card.selected { - border-color: ${sharedStyles.colors.accent.primary}; - background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(96, 165, 250, 0.1)')}; - } - - .option-card input { - display: none; - } - - .option-label { - font-size: 13px; - font-weight: 600; - color: ${sharedStyles.colors.text.primary}; - } - - .option-desc { - font-size: 11px; - color: ${sharedStyles.colors.text.muted}; - line-height: 1.3; - } - - .severity-critical dees-icon { --icon-color: ${sharedStyles.colors.status.majorOutage}; } - .severity-major dees-icon { --icon-color: ${sharedStyles.colors.status.partialOutage}; } - .severity-minor dees-icon { --icon-color: ${sharedStyles.colors.status.degraded}; } - .severity-maintenance dees-icon { --icon-color: ${sharedStyles.colors.status.maintenance}; } - .field-label { display: block; font-size: 13px; @@ -363,20 +318,14 @@ export class UpladminIncidentForm extends DeesElement {
${severityOptions.map(opt => html` - + this.handleSeverityChange(opt.value)} + > `)}
@@ -385,19 +334,13 @@ export class UpladminIncidentForm extends DeesElement {
${statusOptions.map(opt => html` - + this.handleStatusChange(opt.value)} + > `)}
diff --git a/ts_web/elements/upladmin-incident-list/upladmin-incident-list.ts b/ts_web/elements/upladmin-incident-list/upladmin-incident-list.ts index 31b5642..4d61314 100644 --- a/ts_web/elements/upladmin-incident-list/upladmin-incident-list.ts +++ b/ts_web/elements/upladmin-incident-list/upladmin-incident-list.ts @@ -260,8 +260,8 @@ export class UpladminIncidentList extends DeesElement { .incident-status.postmortem { background: ${cssManager.bdTheme('rgba(168, 85, 247, 0.1)', 'rgba(168, 85, 247, 0.2)')}; - color: #a855f7; - --icon-color: #a855f7; + color: ${cssManager.bdTheme('#9333ea', '#a855f7')}; + --icon-color: ${cssManager.bdTheme('#9333ea', '#a855f7')}; } .incident-meta { diff --git a/ts_web/elements/upladmin-incident-update/upladmin-incident-update.ts b/ts_web/elements/upladmin-incident-update/upladmin-incident-update.ts index c8b93ff..ba6856f 100644 --- a/ts_web/elements/upladmin-incident-update/upladmin-incident-update.ts +++ b/ts_web/elements/upladmin-incident-update/upladmin-incident-update.ts @@ -157,52 +157,6 @@ export class UpladminIncidentUpdate extends DeesElement { gap: ${unsafeCSS(sharedStyles.spacing.sm)}; } - .status-option { - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - padding: 18px 14px; - background: ${sharedStyles.colors.background.primary}; - border: 2px solid ${sharedStyles.colors.border.default}; - border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)}; - cursor: pointer; - transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)}; - text-align: center; - } - - .status-option:hover { - border-color: ${sharedStyles.colors.border.strong}; - background: ${sharedStyles.colors.background.muted}; - } - - .status-option.selected { - border-color: ${sharedStyles.colors.accent.primary}; - background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(96, 165, 250, 0.1)')}; - } - - .status-option input { - display: none; - } - - .status-option.investigating dees-icon { --icon-color: ${sharedStyles.colors.status.partialOutage}; } - .status-option.identified dees-icon { --icon-color: ${sharedStyles.colors.status.degraded}; } - .status-option.monitoring dees-icon { --icon-color: ${sharedStyles.colors.status.maintenance}; } - .status-option.resolved dees-icon { --icon-color: ${sharedStyles.colors.status.operational}; } - .status-option.postmortem dees-icon { --icon-color: #a855f7; } - - .status-label { - font-size: 13px; - font-weight: 600; - color: ${sharedStyles.colors.text.primary}; - } - - .status-desc { - font-size: 11px; - color: ${sharedStyles.colors.text.muted}; - line-height: 1.3; - } - .field-label { display: block; font-size: 13px; @@ -358,20 +312,14 @@ export class UpladminIncidentUpdate extends DeesElement {
${statusOptions.map(opt => html` - + this.handleStatusChange(opt.value)} + > `)}
diff --git a/ts_web/elements/upladmin-monitor-form/upladmin-monitor-form.ts b/ts_web/elements/upladmin-monitor-form/upladmin-monitor-form.ts index 7ed088b..fb87b6f 100644 --- a/ts_web/elements/upladmin-monitor-form/upladmin-monitor-form.ts +++ b/ts_web/elements/upladmin-monitor-form/upladmin-monitor-form.ts @@ -10,6 +10,7 @@ import { unsafeCSS, state, } from '@design.estate/dees-element'; +import type { DeesInputMultitoggle } from '@design.estate/dees-catalog'; import * as sharedStyles from '../../styles/shared.styles.js'; import type { IMonitorFormData, IServiceStatus, ICheckConfig, TStatusType, TCheckType, TStatusMode } from '../../interfaces/index.js'; import { demoFunc } from './upladmin-monitor-form.demo.js'; @@ -60,6 +61,14 @@ export class UpladminMonitorForm extends DeesElement { pagerank: 'PageRank', }; + private getCheckTypeLabel(): string { + return this.checkTypeLabels[this.formData.checkType] || 'Assumption'; + } + + private getStatusModeLabel(): string { + return this.formData.statusMode === 'auto' ? 'Auto' : 'Manual'; + } + private intervalOptions = [ { value: 60000, label: '1 min' }, { value: 300000, label: '5 min' }, @@ -183,6 +192,8 @@ export class UpladminMonitorForm extends DeesElement { ` ]; + private subscriptions: Array<{ unsubscribe: () => void }> = []; + async connectedCallback() { await super.connectedCallback(); if (this.monitor) { @@ -190,10 +201,47 @@ export class UpladminMonitorForm extends DeesElement { } } + async disconnectedCallback() { + await super.disconnectedCallback(); + this.subscriptions.forEach(sub => sub.unsubscribe()); + this.subscriptions = []; + } + + async firstUpdated() { + await this.updateComplete; + this.setupMultitoggleSubscriptions(); + } + updated(changedProperties: Map) { if (changedProperties.has('monitor') && this.monitor) { this.formData = { ...this.monitor }; } + // Re-setup subscriptions after each render in case elements changed + this.setupMultitoggleSubscriptions(); + } + + private subscribedElements = new WeakSet(); + + private setupMultitoggleSubscriptions() { + // Subscribe to check type toggle (only if not already subscribed) + const checkTypeToggle = this.shadowRoot?.querySelector('#checkTypeToggle') as DeesInputMultitoggle; + if (checkTypeToggle && !this.subscribedElements.has(checkTypeToggle)) { + this.subscribedElements.add(checkTypeToggle); + const sub = checkTypeToggle.changeSubject.subscribe(() => { + this.handleCheckTypeChange(checkTypeToggle.selectedOption); + }); + this.subscriptions.push(sub); + } + + // Subscribe to status mode toggle (only if not already subscribed) + const statusModeToggle = this.shadowRoot?.querySelector('#statusModeToggle') as DeesInputMultitoggle; + if (statusModeToggle && !this.subscribedElements.has(statusModeToggle)) { + this.subscribedElements.add(statusModeToggle); + const sub = statusModeToggle.changeSubject.subscribe(() => { + this.handleStatusModeChange(statusModeToggle.selectedOption); + }); + this.subscriptions.push(sub); + } } public render(): TemplateResult { @@ -266,10 +314,10 @@ export class UpladminMonitorForm extends DeesElement {
this.handleCheckTypeChange(e.detail.selectedOption)} + .options=${['Assumption', 'Function', 'PWA', 'PageRank']} + .selectedOption=${this.getCheckTypeLabel()} >
@@ -293,18 +341,18 @@ export class UpladminMonitorForm extends DeesElement { .label=${'Pause Monitor'} .description=${'When paused, status will show as "paused" and checks won\'t run'} .value=${this.formData.paused} - @changeSubject=${(e: CustomEvent) => this.updateField('paused', e.detail.value)} + @newValue=${(e: CustomEvent) => this.updateField('paused', e.detail)} > ${isEdit ? html`
this.handleStatusModeChange(e.detail.selectedOption)} + .selectedOption=${this.getStatusModeLabel()} > ${this.formData.statusMode === 'manual' ? html` @@ -319,7 +367,7 @@ export class UpladminMonitorForm extends DeesElement { ]} .selectedOption=${this.formData.manualStatus || 'operational'} .direction=${'horizontal'} - @changeSubject=${(e: CustomEvent) => this.updateField('manualStatus', e.detail.selectedOption)} + @change=${(e: CustomEvent) => this.updateField('manualStatus', e.detail.value)} > ` : ''}
@@ -435,12 +483,12 @@ export class UpladminMonitorForm extends DeesElement { this.updateCheckConfig('checkGoogle', e.detail.value)} + @newValue=${(e: CustomEvent) => this.updateCheckConfig('checkGoogle', e.detail)} > this.updateCheckConfig('checkBing', e.detail.value)} + @newValue=${(e: CustomEvent) => this.updateCheckConfig('checkBing', e.detail)} >
diff --git a/ts_web/elements/upladmin-monitor-list/upladmin-monitor-list.ts b/ts_web/elements/upladmin-monitor-list/upladmin-monitor-list.ts index 9ebf029..1892924 100644 --- a/ts_web/elements/upladmin-monitor-list/upladmin-monitor-list.ts +++ b/ts_web/elements/upladmin-monitor-list/upladmin-monitor-list.ts @@ -128,6 +128,12 @@ export class UpladminMonitorList extends DeesElement { box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(96, 165, 250, 0.15)')}; } + @media (prefers-color-scheme: dark) { + .filter-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23a1a1aa' d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E"); + } + } + .table-container { padding: 0; } @@ -189,20 +195,20 @@ export class UpladminMonitorList extends DeesElement { .status-badge.initializing { background: ${cssManager.bdTheme('rgba(107, 114, 128, 0.1)', 'rgba(107, 114, 128, 0.15)')}; - color: #6b7280; - --icon-color: #6b7280; + color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; + --icon-color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; } .status-badge.error { background: ${cssManager.bdTheme('rgba(220, 38, 38, 0.1)', 'rgba(220, 38, 38, 0.15)')}; - color: #dc2626; - --icon-color: #dc2626; + color: ${cssManager.bdTheme('#dc2626', '#f87171')}; + --icon-color: ${cssManager.bdTheme('#dc2626', '#f87171')}; } .status-badge.paused { background: ${cssManager.bdTheme('rgba(139, 92, 246, 0.1)', 'rgba(139, 92, 246, 0.15)')}; - color: #8b5cf6; - --icon-color: #8b5cf6; + color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')}; + --icon-color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')}; } /* Status indicators for override and pause */ @@ -224,12 +230,12 @@ export class UpladminMonitorList extends DeesElement { .status-indicator.override { background: ${cssManager.bdTheme('rgba(234, 179, 8, 0.15)', 'rgba(234, 179, 8, 0.2)')}; - --icon-color: #eab308; + --icon-color: ${cssManager.bdTheme('#d97706', '#fbbf24')}; } .status-indicator.paused { background: ${cssManager.bdTheme('rgba(139, 92, 246, 0.15)', 'rgba(139, 92, 246, 0.2)')}; - --icon-color: #8b5cf6; + --icon-color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')}; } .monitor-info { diff --git a/ts_web/elements/upladmin-option-card/index.ts b/ts_web/elements/upladmin-option-card/index.ts new file mode 100644 index 0000000..9fe5a37 --- /dev/null +++ b/ts_web/elements/upladmin-option-card/index.ts @@ -0,0 +1 @@ +export * from './upladmin-option-card.js'; diff --git a/ts_web/elements/upladmin-option-card/upladmin-option-card.demo.ts b/ts_web/elements/upladmin-option-card/upladmin-option-card.demo.ts new file mode 100644 index 0000000..63056d4 --- /dev/null +++ b/ts_web/elements/upladmin-option-card/upladmin-option-card.demo.ts @@ -0,0 +1,118 @@ +import { html, css, cssManager } from '@design.estate/dees-element'; +import type { UpladminOptionCard } from './upladmin-option-card.js'; + +export const demoFunc = () => html` + +
+
+
Severity Variants
+
+ + + + +
+
+ +
+
Status Variants
+
+ + + + + +
+
+ +
+
States
+
+ + + +
+
+
+`; diff --git a/ts_web/elements/upladmin-option-card/upladmin-option-card.ts b/ts_web/elements/upladmin-option-card/upladmin-option-card.ts new file mode 100644 index 0000000..11920bb --- /dev/null +++ b/ts_web/elements/upladmin-option-card/upladmin-option-card.ts @@ -0,0 +1,178 @@ +import { + DeesElement, + property, + html, + customElement, + type TemplateResult, + css, + cssManager, + unsafeCSS, +} from '@design.estate/dees-element'; +import * as sharedStyles from '../../styles/shared.styles.js'; +import { demoFunc } from './upladmin-option-card.demo.js'; + +declare global { + interface HTMLElementTagNameMap { + 'upladmin-option-card': UpladminOptionCard; + } +} + +export type TOptionVariant = + // Severity variants + | 'critical' | 'major' | 'minor' | 'maintenance' + // Status variants + | 'investigating' | 'identified' | 'monitoring' | 'resolved' | 'postmortem' + // Generic variants + | 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'; + +@customElement('upladmin-option-card') +export class UpladminOptionCard extends DeesElement { + public static demo = demoFunc; + + @property({ type: String }) + accessor icon: string = ''; + + @property({ type: String }) + accessor label: string = ''; + + @property({ type: String }) + accessor description: string = ''; + + @property({ type: String, reflect: true }) + accessor variant: TOptionVariant = 'default'; + + @property({ type: Boolean, reflect: true }) + accessor selected: boolean = false; + + @property({ type: Boolean, reflect: true }) + accessor disabled: boolean = false; + + public static styles = [ + cssManager.defaultStyles, + css` + :host { + display: block; + font-family: ${unsafeCSS(sharedStyles.fonts.base)}; + } + + .option-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + padding: 18px 14px; + background: ${cssManager.bdTheme('#ffffff', '#09090b')}; + border: 2px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')}; + border-radius: 6px; + cursor: pointer; + transition: all 0.1s ease; + text-align: center; + user-select: none; + } + + .option-card:hover:not(.disabled) { + border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')}; + background: ${cssManager.bdTheme('#f4f4f5', '#18181b')}; + } + + :host([selected]) .option-card { + border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')}; + background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(96, 165, 250, 0.1)')}; + } + + :host([disabled]) .option-card { + opacity: 0.5; + cursor: not-allowed; + } + + .option-label { + font-size: 13px; + font-weight: 600; + color: ${cssManager.bdTheme('#09090b', '#fafafa')}; + } + + .option-desc { + font-size: 11px; + color: ${cssManager.bdTheme('#a1a1aa', '#71717a')}; + line-height: 1.3; + } + + /* Variant icon colors - all using bdTheme for proper light/dark support */ + + /* Severity variants */ + :host([variant="critical"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#dc2626', '#f87171')}; + } + :host([variant="major"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#ea580c', '#fb923c')}; + } + :host([variant="minor"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#ca8a04', '#fbbf24')}; + } + :host([variant="maintenance"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#2563eb', '#60a5fa')}; + } + + /* Status variants */ + :host([variant="investigating"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#ea580c', '#fb923c')}; + } + :host([variant="identified"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#ca8a04', '#fbbf24')}; + } + :host([variant="monitoring"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#2563eb', '#60a5fa')}; + } + :host([variant="resolved"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#16a34a', '#22c55e')}; + } + :host([variant="postmortem"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#9333ea', '#a855f7')}; + } + + /* Generic variants */ + :host([variant="default"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; + } + :host([variant="primary"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')}; + } + :host([variant="success"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#16a34a', '#22c55e')}; + } + :host([variant="warning"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#ca8a04', '#fbbf24')}; + } + :host([variant="danger"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#dc2626', '#f87171')}; + } + :host([variant="info"]) dees-icon { + --icon-color: ${cssManager.bdTheme('#2563eb', '#60a5fa')}; + } + + dees-icon { + color: var(--icon-color); + } + `, + ]; + + public render(): TemplateResult { + return html` +
+ ${this.icon ? html`` : ''} + ${this.label ? html`${this.label}` : ''} + ${this.description ? html`${this.description}` : ''} +
+ `; + } + + private handleClick() { + if (this.disabled) return; + + this.dispatchEvent(new CustomEvent('select', { + detail: { selected: !this.selected }, + bubbles: true, + composed: true, + })); + } +}