- 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.
335 lines
8.0 KiB
Markdown
335 lines
8.0 KiB
Markdown
# Design Tokens
|
|
|
|
CSS custom properties (design tokens) for the Evidence-Weighted Score component suite.
|
|
|
|
## Score Colors
|
|
|
|
### Bucket Colors
|
|
|
|
```css
|
|
: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
|
|
|
|
```css
|
|
: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
|
|
|
|
```css
|
|
: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
|
|
|
|
```css
|
|
: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
|
|
|
|
```css
|
|
: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
|
|
|
|
```css
|
|
: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
|
|
|
|
```css
|
|
@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
|
|
|
|
```css
|
|
: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
|
|
|
|
```scss
|
|
// 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
|
|
|
|
```typescript
|
|
// 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 definitions
|
|
- `src/Web/StellaOps.Web/src/app/core/constants/score-colors.ts` - TypeScript constants
|