feat(pagespeed): render audit sub-group headers in diagnostics

Group audits within each category by sub-group (e.g., "Names and
Labels", "Contrast") with small uppercase headers, matching the
pagespeed.web.dev layout.
This commit is contained in:
Usman Baig
2026-03-22 22:03:13 +01:00
parent a0173636d4
commit 98429f82f5
2 changed files with 49 additions and 3 deletions

View File

@@ -526,9 +526,7 @@ export default function PageSpeedPage() {
</div> </div>
{groupAudits.length > 0 && ( {groupAudits.length > 0 && (
<div className="divide-y divide-neutral-100 dark:divide-neutral-800"> <AuditsBySubGroup audits={groupAudits} />
{groupAudits.map(audit => <AuditRow key={audit.id} audit={audit} />)}
</div>
)} )}
{groupPassed.length > 0 && ( {groupPassed.length > 0 && (
@@ -550,6 +548,52 @@ export default function PageSpeedPage() {
) )
} }
// * Group audits by sub-group within a category (e.g., "Names and Labels", "Contrast")
function AuditsBySubGroup({ audits }: { audits: AuditSummary[] }) {
// * Collect unique sub-groups in order of appearance
const subGroupOrder: string[] = []
const bySubGroup: Record<string, AuditSummary[]> = {}
for (const audit of audits) {
const key = audit.sub_group || '__none__'
if (!bySubGroup[key]) {
bySubGroup[key] = []
subGroupOrder.push(key)
}
bySubGroup[key].push(audit)
}
// * If no sub-groups exist, render flat list
if (subGroupOrder.length === 1 && subGroupOrder[0] === '__none__') {
return (
<div className="divide-y divide-neutral-100 dark:divide-neutral-800">
{audits.map(audit => <AuditRow key={audit.id} audit={audit} />)}
</div>
)
}
return (
<div className="space-y-5">
{subGroupOrder.map(key => {
const items = bySubGroup[key]
const title = items[0]?.sub_group_title
return (
<div key={key}>
{title && (
<h4 className="text-[11px] font-semibold text-neutral-400 dark:text-neutral-500 uppercase tracking-wider mb-2">
{title}
</h4>
)}
<div className="divide-y divide-neutral-100 dark:divide-neutral-800">
{items.map(audit => <AuditRow key={audit.id} audit={audit} />)}
</div>
</div>
)
})}
</div>
)
}
// * Severity indicator based on audit score (pagespeed.web.dev style) // * Severity indicator based on audit score (pagespeed.web.dev style)
function AuditSeverityIcon({ score }: { score: number | null }) { function AuditSeverityIcon({ score }: { score: number | null }) {
if (score === null) { if (score === null) {

View File

@@ -21,6 +21,8 @@ export interface AuditSummary {
savings_ms?: number savings_ms?: number
category: 'opportunity' | 'diagnostic' | 'passed' category: 'opportunity' | 'diagnostic' | 'passed'
group?: string // "performance", "accessibility", "best-practices", "seo" group?: string // "performance", "accessibility", "best-practices", "seo"
sub_group?: string // "a11y-names-labels", "a11y-contrast", etc.
sub_group_title?: string // "Names and Labels", "Contrast", etc.
details?: AuditDetailItem[] details?: AuditDetailItem[]
} }