- Exported BulkTriageViewComponent and its related types from findings module. - Created a new accessibility test suite for score components using axe-core. - Introduced design tokens for score components to standardize styling. - Enhanced score breakdown popover for mobile responsiveness with drag handle. - Added date range selector functionality to score history chart component. - Implemented unit tests for date range selector in score history chart. - Created Storybook stories for bulk triage view and score history chart with date range selector.
8.0 KiB
8.0 KiB
Design Tokens
CSS custom properties (design tokens) for the Evidence-Weighted Score component suite.
Score Colors
Bucket Colors
:root {
/* Act Now (90-100) - Critical priority */
--score-bucket-act-now: #DC2626;
--score-bucket-act-now-light: #FEE2E2;
--score-bucket-act-now-dark: #991B1B;
/* Schedule Next (70-89) - High priority */
--score-bucket-schedule-next: #D97706;
--score-bucket-schedule-next-light: #FEF3C7;
--score-bucket-schedule-next-dark: #92400E;
/* Investigate (40-69) - Medium priority */
--score-bucket-investigate: #2563EB;
--score-bucket-investigate-light: #DBEAFE;
--score-bucket-investigate-dark: #1E40AF;
/* Watchlist (0-39) - Low priority */
--score-bucket-watchlist: #6B7280;
--score-bucket-watchlist-light: #F3F4F6;
--score-bucket-watchlist-dark: #374151;
}
Flag Colors
:root {
/* Live Signal - Active runtime signals */
--score-flag-live-signal: #16A34A;
--score-flag-live-signal-light: #DCFCE7;
--score-flag-live-signal-dark: #166534;
/* Proven Path - Verified reachability */
--score-flag-proven-path: #2563EB;
--score-flag-proven-path-light: #DBEAFE;
--score-flag-proven-path-dark: #1E40AF;
/* Vendor N/A - Vendor not affected */
--score-flag-vendor-na: #6B7280;
--score-flag-vendor-na-light: #F3F4F6;
--score-flag-vendor-na-dark: #374151;
/* Speculative - Unconfirmed evidence */
--score-flag-speculative: #D97706;
--score-flag-speculative-light: #FEF3C7;
--score-flag-speculative-dark: #92400E;
}
Component Tokens
ScorePill
:root {
--score-pill-font-family: system-ui, -apple-system, sans-serif;
--score-pill-font-weight: 600;
--score-pill-border-radius: 4px;
/* Size: Small */
--score-pill-sm-height: 20px;
--score-pill-sm-min-width: 24px;
--score-pill-sm-padding: 0 4px;
--score-pill-sm-font-size: 12px;
/* Size: Medium */
--score-pill-md-height: 24px;
--score-pill-md-min-width: 32px;
--score-pill-md-padding: 0 6px;
--score-pill-md-font-size: 14px;
/* Size: Large */
--score-pill-lg-height: 28px;
--score-pill-lg-min-width: 40px;
--score-pill-lg-padding: 0 8px;
--score-pill-lg-font-size: 16px;
/* Interactive states */
--score-pill-hover-scale: 1.05;
--score-pill-focus-ring: 2px solid var(--color-focus);
--score-pill-focus-offset: 2px;
}
ScoreBadge
:root {
--score-badge-font-family: system-ui, -apple-system, sans-serif;
--score-badge-font-weight: 500;
--score-badge-border-radius: 4px;
/* Size: Small */
--score-badge-sm-height: 20px;
--score-badge-sm-padding: 2px 6px;
--score-badge-sm-font-size: 11px;
--score-badge-sm-icon-size: 12px;
/* Size: Medium */
--score-badge-md-height: 24px;
--score-badge-md-padding: 4px 8px;
--score-badge-md-font-size: 12px;
--score-badge-md-icon-size: 14px;
/* Icon-only mode */
--score-badge-icon-only-size: 20px;
--score-badge-icon-only-padding: 4px;
}
ScoreBreakdownPopover
:root {
--score-popover-max-width: 360px;
--score-popover-padding: 16px;
--score-popover-border-radius: 8px;
--score-popover-background: #FFFFFF;
--score-popover-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
/* Dimension bars */
--score-dimension-bar-height: 8px;
--score-dimension-bar-radius: 4px;
--score-dimension-bar-bg: #E5E7EB;
/* Header */
--score-popover-header-font-size: 24px;
--score-popover-header-font-weight: 700;
/* Labels */
--score-popover-label-font-size: 12px;
--score-popover-label-color: #6B7280;
/* Explanations */
--score-popover-explanation-font-size: 13px;
--score-popover-explanation-color: #374151;
}
ScoreHistoryChart
:root {
--score-chart-line-color: #3B82F6;
--score-chart-line-width: 2px;
--score-chart-area-fill: rgba(59, 130, 246, 0.1);
--score-chart-area-gradient-start: rgba(59, 130, 246, 0.2);
--score-chart-area-gradient-end: rgba(59, 130, 246, 0);
/* Data points */
--score-chart-point-size: 6px;
--score-chart-point-hover-size: 8px;
--score-chart-point-border-width: 2px;
--score-chart-point-border-color: #FFFFFF;
/* Grid */
--score-chart-grid-color: #E5E7EB;
--score-chart-grid-width: 1px;
/* Bands */
--score-chart-band-opacity: 0.1;
/* Axis */
--score-chart-axis-color: #9CA3AF;
--score-chart-axis-font-size: 11px;
/* Tooltip */
--score-chart-tooltip-bg: #1F2937;
--score-chart-tooltip-color: #FFFFFF;
--score-chart-tooltip-padding: 8px 12px;
--score-chart-tooltip-radius: 6px;
}
Dark Mode
@media (prefers-color-scheme: dark) {
:root {
/* Backgrounds */
--score-popover-background: #1F2937;
--score-chart-tooltip-bg: #374151;
/* Text */
--score-popover-label-color: #D1D5DB;
--score-popover-explanation-color: #F9FAFB;
/* Borders */
--score-dimension-bar-bg: #374151;
--score-chart-grid-color: #374151;
/* Adjust bucket light colors for dark mode */
--score-bucket-act-now-light: rgba(220, 38, 38, 0.2);
--score-bucket-schedule-next-light: rgba(217, 119, 6, 0.2);
--score-bucket-investigate-light: rgba(37, 99, 235, 0.2);
--score-bucket-watchlist-light: rgba(107, 114, 128, 0.2);
}
}
Semantic Tokens
:root {
/* Focus states */
--color-focus: #3B82F6;
--color-focus-ring: rgba(59, 130, 246, 0.3);
/* Interactive */
--color-interactive: #3B82F6;
--color-interactive-hover: #2563EB;
--color-interactive-active: #1D4ED8;
/* Status */
--color-success: #16A34A;
--color-warning: #D97706;
--color-error: #DC2626;
--color-info: #2563EB;
/* Neutral */
--color-text-primary: #111827;
--color-text-secondary: #6B7280;
--color-text-disabled: #9CA3AF;
--color-border: #E5E7EB;
--color-background: #FFFFFF;
--color-surface: #F9FAFB;
}
Usage in Components
SCSS Import
// styles/_tokens.scss
@use 'sass:map';
$score-buckets: (
'act-now': (
color: var(--score-bucket-act-now),
light: var(--score-bucket-act-now-light),
dark: var(--score-bucket-act-now-dark),
),
'schedule-next': (
color: var(--score-bucket-schedule-next),
light: var(--score-bucket-schedule-next-light),
dark: var(--score-bucket-schedule-next-dark),
),
'investigate': (
color: var(--score-bucket-investigate),
light: var(--score-bucket-investigate-light),
dark: var(--score-bucket-investigate-dark),
),
'watchlist': (
color: var(--score-bucket-watchlist),
light: var(--score-bucket-watchlist-light),
dark: var(--score-bucket-watchlist-dark),
),
);
@mixin bucket-colors($bucket) {
$colors: map.get($score-buckets, $bucket);
background-color: map.get($colors, color);
color: white;
&.light {
background-color: map.get($colors, light);
color: map.get($colors, dark);
}
}
TypeScript Constants
// score-colors.ts
export const SCORE_BUCKET_COLORS = {
ActNow: {
bg: '#DC2626',
light: '#FEE2E2',
dark: '#991B1B',
},
ScheduleNext: {
bg: '#D97706',
light: '#FEF3C7',
dark: '#92400E',
},
Investigate: {
bg: '#2563EB',
light: '#DBEAFE',
dark: '#1E40AF',
},
Watchlist: {
bg: '#6B7280',
light: '#F3F4F6',
dark: '#374151',
},
} as const;
export const SCORE_FLAG_COLORS = {
'live-signal': { bg: '#16A34A', light: '#DCFCE7' },
'proven-path': { bg: '#2563EB', light: '#DBEAFE' },
'vendor-na': { bg: '#6B7280', light: '#F3F4F6' },
'speculative': { bg: '#D97706', light: '#FEF3C7' },
} as const;
export function getBucketColor(score: number): string {
if (score >= 90) return SCORE_BUCKET_COLORS.ActNow.bg;
if (score >= 70) return SCORE_BUCKET_COLORS.ScheduleNext.bg;
if (score >= 40) return SCORE_BUCKET_COLORS.Investigate.bg;
return SCORE_BUCKET_COLORS.Watchlist.bg;
}
Accessibility Notes
- All color combinations meet WCAG 2.1 AA contrast requirements (4.5:1 for text)
- Bucket colors use both color and position/labels for identification
- Flag badges use icons in addition to colors
- Focus states use high-contrast ring colors
Related Files
src/Web/StellaOps.Web/src/styles/_tokens.scss- SCSS token definitionssrc/Web/StellaOps.Web/src/app/core/constants/score-colors.ts- TypeScript constants