Extract inline styles to CSS classes
Move JavaScript style objects from home page components into reusable CSS classes in App.css. This follows the existing pattern (intel-button, intel-card, intel-input) and consolidates all visual styling in one place. New CSS classes added: - .panel-card (--accent, --warning, --teal) — sidebar panels - .section-heading (--accent, --warning, --teal) — monospace headings - .stat-card modifiers (--clickable, --active, --warning, --danger) - .stat-card__label / .stat-card__value (--accent, --neutral, etc.) - .severity-badge (--critical, --high, --medium, --low) - .glow-dot (--critical, --high, --medium, --low) - .sidebar-ticket — compact ticket cards - .workflow-item — Ivanti workflow entries - .workflow-state-badge — teal state pill - .ticket-status-badge — small status indicator - .archive-item (--active, --resolved) — finding archive entries - .big-counter (--warning, --teal) — large centered stat numbers Benefits: - 578 fewer lines of JavaScript across components - Styles are browser-cached separately from JS bundle - Single source of truth for the design system - Easier to update colors/spacing project-wide
This commit is contained in:
@@ -1,74 +1,55 @@
|
||||
import React from 'react';
|
||||
|
||||
const statCard = {
|
||||
background: 'linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(51, 65, 85, 0.9) 100%)',
|
||||
border: '2px solid #0EA5E9',
|
||||
borderRadius: '0.5rem',
|
||||
padding: '1rem',
|
||||
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.5), 0 0 20px rgba(14, 165, 233, 0.15), inset 0 1px 0 rgba(14, 165, 233, 0.15)',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.15s, box-shadow 0.15s',
|
||||
};
|
||||
function StatCard({ label, value, color = 'accent', variant, onClick, active }) {
|
||||
const cardClasses = [
|
||||
'stat-card',
|
||||
onClick && 'stat-card--clickable',
|
||||
active && 'stat-card--active',
|
||||
variant && `stat-card--${variant}`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const topGlow = (color) => ({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '2px',
|
||||
background: `linear-gradient(90deg, transparent, ${color}, transparent)`,
|
||||
boxShadow: `0 0 8px ${color}80`,
|
||||
});
|
||||
|
||||
function StatCard({ label, value, color = '#0EA5E9', borderColor, onClick, active }) {
|
||||
const cardStyle = {
|
||||
...statCard,
|
||||
...(borderColor ? { border: `2px solid ${borderColor}`, boxShadow: `0 4px 16px rgba(0, 0, 0, 0.5), 0 0 20px ${borderColor}26, inset 0 1px 0 ${borderColor}26` } : {}),
|
||||
...(active ? { transform: 'scale(1.03)', boxShadow: `0 4px 24px rgba(0, 0, 0, 0.6), 0 0 28px ${color}40` } : {}),
|
||||
};
|
||||
const valueClass = `stat-card__value stat-card__value--${color}`;
|
||||
|
||||
return (
|
||||
<div style={cardStyle} onClick={onClick} role={onClick ? 'button' : undefined} tabIndex={onClick ? 0 : undefined} aria-label={`${label}: ${value}`}>
|
||||
<div style={topGlow(color)}></div>
|
||||
<div style={{ fontSize: '0.75rem', textTransform: 'uppercase', letterSpacing: '0.1em', color: '#CBD5E1', marginBottom: '0.25rem' }}>
|
||||
{label}
|
||||
</div>
|
||||
<div style={{ fontSize: '1.5rem', fontWeight: '700', fontFamily: 'monospace', color, textShadow: `0 0 16px ${color}66` }}>
|
||||
{value}
|
||||
</div>
|
||||
<div
|
||||
className={cardClasses}
|
||||
onClick={onClick}
|
||||
role={onClick ? 'button' : undefined}
|
||||
tabIndex={onClick ? 0 : undefined}
|
||||
aria-label={`${label}: ${value}`}
|
||||
>
|
||||
<div className="stat-card__label">{label}</div>
|
||||
<div className={valueClass}>{value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function StatsBar({ totalCVEs, vendorEntries, openTickets, criticalCount, onFilterSeverity, activeSeverity }) {
|
||||
return (
|
||||
// ⚠️ CONVENTION: Use inline styles or App.css classes instead of Tailwind utility classes (grid grid-cols-1 md:grid-cols-4 gap-4)
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<StatCard
|
||||
label="Total CVEs"
|
||||
value={totalCVEs}
|
||||
color="#0EA5E9"
|
||||
color="accent"
|
||||
onClick={() => onFilterSeverity && onFilterSeverity('All Severities')}
|
||||
active={activeSeverity === 'All Severities'}
|
||||
/>
|
||||
<StatCard
|
||||
label="Vendor Entries"
|
||||
value={vendorEntries}
|
||||
color="#E2E8F0"
|
||||
color="neutral"
|
||||
/>
|
||||
<StatCard
|
||||
label="Open Tickets"
|
||||
value={openTickets}
|
||||
color="#F59E0B"
|
||||
borderColor="#F59E0B"
|
||||
color="warning"
|
||||
variant="warning"
|
||||
/>
|
||||
<StatCard
|
||||
label="Critical"
|
||||
value={criticalCount}
|
||||
color="#EF4444"
|
||||
borderColor="#EF4444"
|
||||
color="danger"
|
||||
variant="danger"
|
||||
onClick={() => onFilterSeverity && onFilterSeverity(activeSeverity === 'Critical' ? 'All Severities' : 'Critical')}
|
||||
active={activeSeverity === 'Critical'}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user