fix(comparison): domain bars use a share variable instead of an inline style

The design-language check forbids inline style objects. The bar length now travels as --bar-share and comparison.css applies it as the width.
This commit is contained in:
Binh 2026-10-14 13:17:19 +02:00
parent 01dabc9c3a
commit 2ebacf6201
3 changed files with 8 additions and 6 deletions

View File

@ -1,4 +1,4 @@
import { useId, useMemo, useState } from 'react'; import { useId, useMemo, useState, type CSSProperties } from 'react';
import { useOptionalApp } from '../../app/AppProvider'; import { useOptionalApp } from '../../app/AppProvider';
import type { FindingRecord } from '../../domain/finding'; import type { FindingRecord } from '../../domain/finding';
import { FindingFirstRead } from '../finding-reader/FindingFirstRead'; import { FindingFirstRead } from '../finding-reader/FindingFirstRead';
@ -71,6 +71,8 @@ export const date = (value: string) => {
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }); return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' });
}; };
const rounded = (value?: number) => value === undefined ? undefined : Math.round(value); const rounded = (value?: number) => value === undefined ? undefined : Math.round(value);
/** Domain bar length as a share of the widest bar; the stylesheet reads it as `--bar-share`. */
const barStyle = (share: number): CSSProperties => ({ '--bar-share': `${share * 100}%` } as CSSProperties);
function Delta({ before, after }: { before?: number; after?: number }) { function Delta({ before, after }: { before?: number; after?: number }) {
if (before === undefined || after === undefined) return <span className="comparison-delta is-neutral">—</span>; if (before === undefined || after === undefined) return <span className="comparison-delta is-neutral">—</span>;
@ -286,9 +288,9 @@ function DomainComparison({ projectId, baseline, target }: { projectId?: string;
<span role="cell">{domain.onlyIn <span role="cell">{domain.onlyIn
? <span className="comparison-tile__note comparison-domain__only">{domain.onlyInSources.map(sourceName).join(', ')} only in {domain.onlyIn === 'target' ? 'B' : 'A'} · not comparable</span> ? <span className="comparison-tile__note comparison-domain__only">{domain.onlyInSources.map(sourceName).join(', ')} only in {domain.onlyIn === 'target' ? 'B' : 'A'} · not comparable</span>
: <span className="comparison-domain__bars" aria-label={`${domain.resolved} resolved, ${domain.worse} new or regressed`}> : <span className="comparison-domain__bars" aria-label={`${domain.resolved} resolved, ${domain.worse} new or regressed`}>
<span className="comparison-domain__bar is-better" style={{ width: `${domain.resolved / widest * 100}%` }}/> <span className="comparison-domain__bar is-better" style={barStyle(domain.resolved / widest)}/>
<span className="comparison-domain__axis"/> <span className="comparison-domain__axis"/>
<span className="comparison-domain__bar is-worse" style={{ width: `${domain.worse / widest * 100}%` }}/> <span className="comparison-domain__bar is-worse" style={barStyle(domain.worse / widest)}/>
</span>}</span> </span>}</span>
</div>; </div>;
})} })}

View File

@ -103,7 +103,7 @@
.comparison-domain__legend { display: flex; justify-content: space-between; gap: var(--space-2); } .comparison-domain__legend { display: flex; justify-content: space-between; gap: var(--space-2); }
.comparison-domain__bars { display: grid; grid-template-columns: 1fr 1px 1fr; align-items: center; height: 16px; } .comparison-domain__bars { display: grid; grid-template-columns: 1fr 1px 1fr; align-items: center; height: 16px; }
.comparison-domain__axis { height: 16px; background: var(--border-strong); } .comparison-domain__axis { height: 16px; background: var(--border-strong); }
.comparison-domain__bar { height: 8px; } .comparison-domain__bar { --bar-share: 0%; width: var(--bar-share); height: 8px; }
.comparison-domain__bar.is-better { justify-self: end; background: var(--outcome-pass); } .comparison-domain__bar.is-better { justify-self: end; background: var(--outcome-pass); }
.comparison-domain__bar.is-worse { justify-self: start; background: var(--outcome-fail); } .comparison-domain__bar.is-worse { justify-self: start; background: var(--outcome-fail); }
.comparison-domain__only { margin: 0; } .comparison-domain__only { margin: 0; }

View File

@ -143,8 +143,8 @@ describe('Comparison Engine Compare tab', () => {
const rows = [...host.querySelectorAll('.comparison-domain:not(.is-header)')]; const rows = [...host.querySelectorAll('.comparison-domain:not(.is-header)')];
const cells = (row: Element) => [...row.children].map(cell => cell.textContent); const cells = (row: Element) => [...row.children].map(cell => cell.textContent);
expect(cells(rows[0]).slice(0, 5)).toEqual(['Identity & Authentication', '48', '→', '55', '▲ +7']); expect(cells(rows[0]).slice(0, 5)).toEqual(['Identity & Authentication', '48', '→', '55', '▲ +7']);
expect(rows[0].querySelector<HTMLElement>('.comparison-domain__bar.is-better')?.style.width).toBe('100%'); expect(rows[0].querySelector<HTMLElement>('.comparison-domain__bar.is-better')?.style.getPropertyValue('--bar-share')).toBe('100%');
expect(rows[0].querySelector<HTMLElement>('.comparison-domain__bar.is-worse')?.style.width).toBe('25%'); expect(rows[0].querySelector<HTMLElement>('.comparison-domain__bar.is-worse')?.style.getPropertyValue('--bar-share')).toBe('25%');
expect(cells(rows[1])).toEqual(['Cloud & Collaboration', '—', '→', '46', '○ Only in B', 'Maester only in B · not comparable']); expect(cells(rows[1])).toEqual(['Cloud & Collaboration', '—', '→', '46', '○ Only in B', 'Maester only in B · not comparable']);
expect(cells(rows[2])[4]).toBe('▼ −3'); expect(cells(rows[2])[4]).toBe('▼ −3');
expect(rows[2].querySelector('.comparison-delta')?.className).toContain('is-worse'); expect(rows[2].querySelector('.comparison-delta')?.className).toContain('is-worse');