import { useState } from 'react';
import {
CheckIcon,
ChevronDownIcon,
InfoIcon,
MinusIcon,
XIcon,
} from 'lucide-react';
import { OptionTip } from '../../components/seo/option-row';
import { classNames } from '../../lib/utils';
import {
GROUP_LABELS,
GROUP_TIPS,
type AnalysisGroup,
type AnalysisResult,
} from '../analysis/analyzer';
import { isToDo, isToFix, resultTone } from './result-tone';
/** What a failing test offers to do about itself, next to its advice. */
export interface TestAction {
label: string;
run: () => void;
}
const GROUPS: AnalysisGroup[] = [
'basic',
'additional',
'title',
'content',
'product',
'aeo',
];
const Group = ({
group,
results,
defaultOpen,
actionFor,
}: {
group: AnalysisGroup;
results: AnalysisResult[];
defaultOpen: boolean;
actionFor?: (result: AnalysisResult) => TestAction | undefined;
}): JSX.Element => {
const [open, setOpen] = useState(defaultOpen);
// A failed test that weighs nothing is advice ("to improve"), not "to fix";
// one Rank Math runs and the Recomaze rules do not ask for is neither.
const toFix = results.filter(isToFix).length;
const toImprove = results.filter(
result => isToDo(result) && !isToFix(result)
).length;
const allGood = toFix === 0 && toImprove === 0;
const tip = GROUP_TIPS[group];
return (
{/* The count sits after the name and drops under it when the sidebar
is narrow; a name squeezed beside it ran into it. */}
setOpen(current => !current)}
aria-expanded={open}
className="flex min-w-0 flex-1 cursor-pointer flex-wrap items-center gap-x-2 gap-y-1 text-left"
>
{GROUP_LABELS[group]}
{allGood
? 'All good'
: [
toFix > 0 ? `${toFix} to fix` : '',
toImprove > 0 ? `${toImprove} to improve` : '',
]
.filter(Boolean)
.join(' · ')}
{tip && {tip} }
setOpen(current => !current)}
aria-expanded={open}
aria-label={open ? 'Collapse the group' : 'Expand the group'}
className="flex h-5 shrink-0 cursor-pointer items-center"
>
{open && (
{results.map(result => (
{result.rankMathOnly ? (
) : result.partial ? (
) : result.passed ? (
) : (
)}
{result.message}
{result.partial && (
{' '}
({result.score} of {result.max} points)
)}
{result.fix && (
{result.fix}
)}
{isToDo(result) &&
(() => {
const action = actionFor?.(result);
return action ? (
{action.label} →
) : null;
})()}
))}
)}
);
};
/**
* The tests grouped the way Rank Math groups them, failing groups open,
* with the reason each test fails and what to do about it.
*/
export const AnalysisList = ({
results,
actionFor,
}: {
results: AnalysisResult[];
/** The way to fix a failing test here, when there is one. */
actionFor?: (result: AnalysisResult) => TestAction | undefined;
}): JSX.Element => (
{GROUPS.filter(group => results.some(result => result.group === group)).map(
group => {
const own = results.filter(result => result.group === group);
return (
isToDo(result) && result.max > 0)}
actionFor={actionFor}
/>
);
}
)}
);