Files
git.stella-ops.org/docs/ui/components/design-tokens.md
StellaOps Bot 17613acf57 feat: add bulk triage view component and related stories
- 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.
2025-12-26 01:01:35 +02:00

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
  • src/Web/StellaOps.Web/src/styles/_tokens.scss - SCSS token definitions
  • src/Web/StellaOps.Web/src/app/core/constants/score-colors.ts - TypeScript constants