refactor: Update UI components for consistency and improve layout

This commit is contained in:
elipeter 2026-05-06 04:38:04 -04:00
parent da619171cf
commit 77be7f10d9
74 changed files with 3186 additions and 618 deletions

View file

@ -23,10 +23,10 @@ export function HorizontalBarChart({
);
}
const barH = 22;
const gap = 4;
const labelW = 110;
const valueW = 45;
const barH = 32;
const gap = 12;
const labelW = 120;
const valueW = 48;
const barAreaW = width - labelW - valueW - 16;
const totalH = items.length * (barH + gap);
const maxVal = maxValue ?? Math.max(...items.map((i) => i.value), 1);
@ -49,7 +49,7 @@ export function HorizontalBarChart({
x={labelW - 8}
y={y + barH / 2 + 4}
textAnchor="end"
fontSize={11}
fontSize={13}
fontFamily="var(--font)"
fill="var(--text-secondary)"
>
@ -68,7 +68,7 @@ export function HorizontalBarChart({
x={labelW + barAreaW + 8}
y={y + barH / 2 + 4}
textAnchor="start"
fontSize={11}
fontSize={13}
fontFamily="var(--font-mono)"
fontWeight={600}
fill="var(--text)"

View file

@ -16,7 +16,7 @@ export function LineChart({
points,
color = 'var(--accent)',
width = 400,
height = 160,
height = 240,
}: LineChartProps) {
if (!points || points.length < 2) {
return (

View file

@ -111,7 +111,7 @@ export function HeaderBar({ onStartScan, onOpenPalette }: HeaderBarProps) {
className="btn btn-primary btn-sm"
onClick={onStartScan}
>
Start Scan
Start scan
</button>
)}
</div>

View file

@ -14,6 +14,7 @@ import {
import type { FC } from 'react';
import type { IconProps } from '../icons/Icons';
import { useHealth } from '../../api/queries/health';
import { useOverview } from '../../api/queries/overview';
import { useSSE } from '../../contexts/SSEContext';
interface NavItem {
@ -89,17 +90,19 @@ function navLinkClass({ isActive }: { isActive: boolean }) {
export function Sidebar() {
const { data: health } = useHealth();
const { data: overview } = useOverview();
const { isScanRunning } = useSSE();
const primary = NAV_SECTIONS.filter((n) => n.group === 'primary');
const secondary = NAV_SECTIONS.filter((n) => n.group === 'secondary');
const footer = NAV_SECTIONS.filter((n) => n.group === 'footer');
const findingsCount =
overview && overview.state !== 'empty' ? overview.total_findings : null;
return (
<aside className="sidebar">
<div className="sidebar-header">
<span className="logo">nyx</span>
{health?.version && <span className="version">v{health.version}</span>}
<img src="/logo.png" alt="Nyx" className="sidebar-logo-img" />
</div>
<ul className="nav-list">
@ -113,12 +116,15 @@ export function Sidebar() {
<span className="nav-icon">
<item.Icon />
</span>
<span>{item.label}</span>
<span className="nav-label">{item.label}</span>
{item.id === 'findings' && findingsCount != null && (
<span className="nav-badge">{findingsCount}</span>
)}
</NavLink>
</li>
))}
<li className="nav-separator" />
<li className="nav-section-header">Tools</li>
{secondary.map((item) => (
<li key={item.id}>
@ -126,7 +132,7 @@ export function Sidebar() {
<span className="nav-icon">
<item.Icon />
</span>
<span>{item.label}</span>
<span className="nav-label">{item.label}</span>
</NavLink>
</li>
))}
@ -140,7 +146,7 @@ export function Sidebar() {
<span className="nav-icon">
<item.Icon />
</span>
<span>{item.label}</span>
<span className="nav-label">{item.label}</span>
</NavLink>
</li>
))}

View file

@ -1,3 +1,4 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import type {
HealthScore,
@ -25,8 +26,17 @@ export function HealthScoreCard({
posture?: PostureSummary;
}) {
const gradeClass = `grade-${health.grade.toLowerCase()}`;
const gradeAccent =
health.grade === 'A' || health.grade === 'B'
? 'var(--green)'
: health.grade === 'C'
? 'var(--amber)'
: 'var(--red)';
return (
<div className="card health-card">
<div
className="card health-card"
style={{ '--health-accent': gradeAccent } as React.CSSProperties}
>
<div className="health-eyebrow">Health Score</div>
<div className="health-headline">
<div className={`health-grade-block ${gradeClass}`}>
@ -44,12 +54,26 @@ export function HealthScoreCard({
)}
</div>
<div className="health-components">
{health.components.map((c) => (
<div className="health-component" key={c.label} title={c.detail}>
<div className="health-component-score">{c.score}</div>
<div className="health-component-label">{c.label}</div>
</div>
))}
{health.components.map((c) => {
const barColor =
c.score >= 70
? 'var(--green)'
: c.score >= 40
? 'var(--amber)'
: 'var(--red)';
return (
<div className="health-component" key={c.label} title={c.detail}>
<div className="health-component-label">{c.label}</div>
<div className="health-component-bar-track">
<div
className="health-component-fill"
style={{ width: `${c.score}%`, background: barColor }}
/>
</div>
<div className="health-component-score">{c.score}</div>
</div>
);
})}
</div>
</div>
</div>
@ -113,7 +137,13 @@ export function BacklogCard({ backlog }: { backlog: BacklogStats }) {
function BucketBar({ buckets }: { buckets: OverviewCount[] }) {
const total = buckets.reduce((s, b) => s + b.count, 0);
if (total === 0) return null;
const colors = ['#3498db', '#2ecc71', '#f1c40f', '#e67e22', '#e74c3c'];
const colors = [
'var(--accent)',
'var(--green)',
'var(--amber)',
'var(--red)',
'var(--muted)',
];
return (
<div
className="bucket-bar"
@ -149,10 +179,10 @@ export function ConfidenceDistributionChart({
);
}
const segments = [
{ label: 'High', value: dist.high, color: '#27ae60' },
{ label: 'Medium', value: dist.medium, color: '#f39c12' },
{ label: 'Low', value: dist.low, color: '#95a5a6' },
{ label: 'None', value: dist.none, color: '#bdc3c7' },
{ label: 'High', value: dist.high, color: 'var(--green)' },
{ label: 'Medium', value: dist.medium, color: 'var(--amber)' },
{ label: 'Low', value: dist.low, color: 'var(--muted)' },
{ label: 'None', value: dist.none, color: 'var(--subtle)' },
];
return (
<div className="confidence-dist">
@ -484,7 +514,7 @@ export function SuppressionHygieneCard({
<div className="kv-row kv-row-emphasis">
<dt
className="kv-label"
title="Share of suppressions that are not pinned to a specific finding fingerprint. Lower is better — it means triage is decisive rather than blanket-silencing whole rules or files."
title="Share of suppressions that are not pinned to a specific finding fingerprint. Lower is better because triage is decisive rather than blanket-silencing whole rules or files."
>
Blanket rate
<span className="kv-hint">Lower is better</span>

View file

@ -12,7 +12,7 @@ export interface PaletteCommand {
id: string;
/** Visible label. */
label: string;
/** Optional secondary line — section, hint, shortcut. */
/** Optional secondary line such as section, hint, or shortcut. */
hint?: string;
/** Group label for visual separation. */
group?: string;

View file

@ -3,7 +3,7 @@ interface LoadingStateProps {
/**
* Suppresses the spinner for the first ~150ms so trivially-fast queries
* don't flash a spinner on screen. The text shows instantly so there's
* always *something* but the visible spin only kicks in if work is
* always something, but the visible spin only kicks in if work is
* actually slow.
*/
delaySpinnerMs?: number;