diff --git a/nextjs-app/app/rankings/page.tsx b/nextjs-app/app/rankings/page.tsx index 15b2c02..6078cd3 100644 --- a/nextjs-app/app/rankings/page.tsx +++ b/nextjs-app/app/rankings/page.tsx @@ -29,7 +29,7 @@ export default async function RankingsPage({ searchParams }: RankingsPageProps) const { metric: metricParam, local_authority, year: yearParam, phase: phaseParam } = await searchParams; const phase = phaseParam || 'primary'; - const metric = metricParam || (phase === 'secondary' ? 'attainment_8_score' : 'rwm_high_pct'); + const metric = metricParam || (phase === 'secondary' ? 'attainment_8_score' : 'rwm_expected_pct'); const year = yearParam ? parseInt(yearParam) : undefined; // Fetch rankings data with error handling diff --git a/nextjs-app/components/MetricTooltip.module.css b/nextjs-app/components/MetricTooltip.module.css index 22324f6..ccf0801 100644 --- a/nextjs-app/components/MetricTooltip.module.css +++ b/nextjs-app/components/MetricTooltip.module.css @@ -6,7 +6,17 @@ } .icon { - font-size: 0.85em; + /* A real button: 24px tap target (WCAG 2.5.8) drawn as the small glyph. */ + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 24px; + min-height: 24px; + margin: -6px 0; + padding: 0; + border: none; + background: none; + font-size: 0.9em; color: var(--text-muted, #8a7a72); cursor: help; line-height: 1; @@ -14,8 +24,9 @@ transition: color 0.15s ease; } -.wrapper:hover .icon { - color: var(--accent-coral, #e07256); +.wrapper:hover .icon, +.icon[aria-expanded="true"] { + color: var(--accent-coral-dark, #b04a2e); } .tooltip { @@ -39,12 +50,18 @@ transition: opacity 0.15s ease, visibility 0.15s ease; } -/* Keep tooltip visible when hovering over it */ -.wrapper:hover .tooltip { +/* Reveal on hover (desktop), keyboard focus, or explicit tap/click toggle. */ +.wrapper:hover .tooltip, +.wrapper:focus-within .tooltip, +.tooltipOpen { visibility: visible; opacity: 1; } +.tooltipOpen { + pointer-events: auto; +} + /* Small arrow pointing down */ .tooltip::after { content: ''; @@ -75,19 +92,23 @@ margin-top: 0.1rem; } -/* Flip tooltip below when near top of screen */ @media (max-width: 480px) { .tooltip { width: 180px; } } -/* On phones the icon was rendering at ~9px and the tooltip relied on - :hover, which doesn't fire on touch. Rather than build a tap-to-show - layer with backdrop dismissal, hide the helper entirely — the metric - labels themselves carry the meaning. */ +/* Anchor the bubble to open rightward on phones — icons follow their labels, + which start at the left edge, so centring pushed the bubble off-screen. */ @media (max-width: 640px) { - .wrapper { - display: none; + .tooltip { + left: -12px; + right: auto; + transform: none; + } + + .tooltip::after { + left: 16px; + transform: none; } } diff --git a/nextjs-app/components/MetricTooltip.tsx b/nextjs-app/components/MetricTooltip.tsx index 8021926..6bef6f4 100644 --- a/nextjs-app/components/MetricTooltip.tsx +++ b/nextjs-app/components/MetricTooltip.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useEffect, useRef, useState } from 'react'; import { METRIC_EXPLANATIONS } from '@/lib/metrics'; import styles from './MetricTooltip.module.css'; @@ -16,12 +17,43 @@ export function MetricTooltip({ metricKey, label, plain, detail }: MetricTooltip const tooltipPlain = plain ?? explanation?.plain; const tooltipDetail = detail ?? explanation?.detail; + // Tap/click/keyboard toggle so the definition is reachable on touch devices + // and by keyboard, not just mouse hover (hover still works on desktop). + const [open, setOpen] = useState(false); + const wrapperRef = useRef(null); + + useEffect(() => { + if (!open) return; + const dismiss = (e: Event) => { + if (wrapperRef.current && e.target instanceof Node && !wrapperRef.current.contains(e.target)) { + setOpen(false); + } + }; + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') setOpen(false); + }; + document.addEventListener('click', dismiss); + document.addEventListener('keydown', onKey); + return () => { + document.removeEventListener('click', dismiss); + document.removeEventListener('keydown', onKey); + }; + }, [open]); + if (!tooltipPlain) return null; return ( - - - + + + {tooltipLabel && {tooltipLabel}} {tooltipPlain} {tooltipDetail && {tooltipDetail}} diff --git a/nextjs-app/components/RankingsView.module.css b/nextjs-app/components/RankingsView.module.css index 0a392c5..124c65e 100644 --- a/nextjs-app/components/RankingsView.module.css +++ b/nextjs-app/components/RankingsView.module.css @@ -55,13 +55,13 @@ } .phaseTabActive { - background: var(--accent-coral, #e07256); + background: var(--accent-coral-dark, #b04a2e); color: white; font-weight: 600; } .phaseTabActive:hover { - background: var(--accent-coral, #e07256); + background: var(--accent-coral-darker, #9c3f26); } /* Filters */ @@ -290,7 +290,7 @@ } .schoolLink:hover { - color: var(--accent-coral, #e07256); + color: var(--accent-coral-dark, #b04a2e); } .areaCell, @@ -298,6 +298,14 @@ color: var(--text-secondary, #5c564d); } +/* LA subline under the school name — mobile only (Area column hidden there). */ +.schoolCellArea { + display: none; + font-size: 0.75rem; + font-weight: 400; + color: var(--text-muted, #8a847a); +} + .valueCell { text-align: center; font-size: 1rem; @@ -374,20 +382,26 @@ font-size: 0.875rem; } - /* Hide less-critical columns on mobile so the metric value stays visible */ + /* Hide less-critical columns on mobile so the metric value stays visible. + Area moves to a subline under the school name (.schoolCellArea) — with a + four-column layout the value still overflowed a 390px viewport and the + whole point of the page (the score) needed a sideways swipe to see. */ .typeHeader, .typeCell, .actionHeader, - .actionCell { + .actionCell, + .areaHeader, + .areaCell { display: none; } - .schoolHeader { - min-width: 140px; + .schoolCellArea { + display: block; + margin-top: 0.15rem; } - .areaHeader { - min-width: 80px; + .schoolHeader { + min-width: 0; } .valueHeader, diff --git a/nextjs-app/components/RankingsView.tsx b/nextjs-app/components/RankingsView.tsx index 98795c3..63c7261 100644 --- a/nextjs-app/components/RankingsView.tsx +++ b/nextjs-app/components/RankingsView.tsx @@ -75,7 +75,7 @@ export function RankingsView({ }; const handlePhaseChange = (phase: string) => { - const defaultMetric = phase === 'secondary' ? 'attainment_8_score' : 'rwm_high_pct'; + const defaultMetric = phase === 'secondary' ? 'attainment_8_score' : 'rwm_expected_pct'; updateFilters({ phase, metric: defaultMetric }); }; @@ -273,6 +273,11 @@ export function RankingsView({ {ranking.school_name} + {/* On phones the Area column is hidden; the LA moves here + so the metric value fits on screen without swiping. */} + {ranking.local_authority && ( + {ranking.local_authority} + )} {ranking.local_authority || '-'} {ranking.school_type || '-'}