feat(detail): move religious character into header details + mobile reveal #83

Merged
tudor merged 1 commits from feat/detail-header-details-reveal into main 2026-07-24 12:15:14 +00:00
5 changed files with 124 additions and 20 deletions
Showing only changes of commit 96d5fcf5b0 - Show all commits
+18
View File
@@ -148,6 +148,24 @@ test('school detail page shows GIAS identity/contact details and drops the unwir
}
});
test('header details collapse behind a "Show all details" toggle on mobile', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
const [urn] = await twoPrimaryUrns(page);
await page.goto(`/school/${urn}`);
await expect(page.locator('h1').first()).toBeVisible({ timeout: 15_000 });
// Collapsed by default on mobile/tablet …
const details = page.locator('#school-header-details');
await expect(details).toBeHidden();
const toggle = page.getByRole('button', { name: /show all details/i });
await expect(toggle).toBeVisible();
// … and the link reveals them (label flips to "Hide details").
await toggle.click();
await expect(details).toBeVisible();
await expect(page.getByRole('button', { name: /hide details/i })).toBeVisible();
});
test('a report-card school shows its report card, dated to the report-card inspection', async ({ page }) => {
// Detail views detected report cards via `framework`, which the API never
// sets to "ReportCard" — so report-card schools rendered as legacy ratings
@@ -197,6 +197,22 @@
text-decoration: underline;
}
/* "Show all details" reveal — only rendered on mobile/tablet, where the
header details block is collapsed below the fold. Hidden on desktop. */
.detailsToggle {
display: none;
align-items: center;
gap: 0.25rem;
margin-top: 0.5rem;
padding: 0;
background: none;
border: none;
font-size: 0.8125rem;
font-weight: 600;
color: var(--accent-teal, #2d7d7d);
cursor: pointer;
}
/* Gender split card — sits in the Pupils & Inclusion heroStatGrid */
.genderSplitValue {
display: flex;
@@ -1205,14 +1221,24 @@
gap: 0.375rem;
}
/* Secondary header info (headteacher, website, pupil count, trust)
isn't needed above the fold on phones — pupil count lives in the
Pupils & Inclusion section, website is one scroll away. Reclaim
the ~3 vertical lines so the actual metrics surface sooner. */
/* Secondary header info (headteacher, website, pupil count, trust,
contact, area) isn't needed above the fold on phones/tablets, so it's
collapsed by default and revealed on demand via the "Show all details"
link — reclaiming the vertical space so the metrics surface sooner. */
.detailsToggle {
display: inline-flex;
}
.headerDetails {
display: none;
}
.headerDetailsOpen {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.metricsGrid {
grid-template-columns: repeat(2, 1fr);
}
+25 -8
View File
@@ -99,6 +99,9 @@ export function SchoolDetailView({
const heroMapRef = useRef<SchoolHeroMapHandle>(null);
// "All ▾" jump menu listing every section.
const [sectionsOpen, setSectionsOpen] = useState(false);
// Header details (headteacher, contact, trust, area) collapse behind a
// "Show all details" link on mobile/tablet, where they're below the fold.
const [detailsOpen, setDetailsOpen] = useState(false);
// Back returns to wherever the user came from; deep-links (no in-app history)
// fall back to search so the button never dead-ends or leaves the site.
@@ -354,13 +357,6 @@ export function SchoolDetailView({
{schoolInfo.age_range && (
<span className={styles.metaItem}>{formatAgeRange(schoolInfo.age_range)}</span>
)}
{schoolInfo.religious_denomination && (
<span className={styles.metaItem}>
{['Does not apply', 'None'].includes(schoolInfo.religious_denomination)
? 'None'
: schoolInfo.religious_denomination}
</span>
)}
{schoolInfo.nursery_provision && (
<span className={styles.metaItem}>Nursery</span>
)}
@@ -391,7 +387,20 @@ export function SchoolDetailView({
)}
</p>
)}
<div className={styles.headerDetails}>
<button
type="button"
className={styles.detailsToggle}
aria-expanded={detailsOpen}
aria-controls="school-header-details"
onClick={() => setDetailsOpen((o) => !o)}
>
{detailsOpen ? 'Hide details' : 'Show all details'}
<span aria-hidden="true">{detailsOpen ? '▴' : '▾'}</span>
</button>
<div
id="school-header-details"
className={`${styles.headerDetails}${detailsOpen ? ` ${styles.headerDetailsOpen}` : ''}`}
>
{schoolInfo.headteacher_name && (
<span className={styles.headerDetail}>
<strong>Headteacher:</strong> {schoolInfo.headteacher_name}
@@ -433,6 +442,14 @@ export function SchoolDetailView({
</a>
</span>
)}
{schoolInfo.religious_denomination && (
<span className={styles.headerDetail}>
<strong>Religious character:</strong>{' '}
{['Does not apply', 'None'].includes(schoolInfo.religious_denomination)
? 'None'
: schoolInfo.religious_denomination}
</span>
)}
{schoolInfo.county && (
<span className={styles.headerDetail}>
<strong>County:</strong> {schoolInfo.county}
@@ -207,6 +207,22 @@
text-decoration: underline;
}
/* "Show all details" reveal — only rendered on mobile/tablet, where the
header details block is collapsed below the fold. Hidden on desktop. */
.detailsToggle {
display: none;
align-items: center;
gap: 0.25rem;
margin-top: 0.5rem;
padding: 0;
background: none;
border: none;
font-size: 0.8125rem;
font-weight: 600;
color: var(--accent-teal, #2d7d7d);
cursor: pointer;
}
.actions {
display: flex;
gap: 0.5rem;
@@ -1066,7 +1082,18 @@
padding: 0.1rem 0.375rem;
}
/* Collapsed below the fold on phones/tablets; revealed via "Show all
details" so the metrics surface sooner. */
.detailsToggle {
display: inline-flex;
}
.headerDetails {
display: none;
}
.headerDetailsOpen {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
@@ -83,6 +83,8 @@ export function SecondarySchoolDetailView({
const isInComparison = isSelected(schoolInfo.urn);
const [activeSection, setActiveSection] = useState<string>('');
// Header details collapse behind a "Show all details" link on mobile/tablet.
const [detailsOpen, setDetailsOpen] = useState(false);
const latestResults = yearlyData.length > 0 ? yearlyData[yearlyData.length - 1] : null;
@@ -246,13 +248,6 @@ export function SecondarySchoolDetailView({
{schoolInfo.age_range && (
<span className={styles.badge}>{formatAgeRange(schoolInfo.age_range)}</span>
)}
{schoolInfo.religious_denomination && (
<span className={styles.badge}>
{['Does not apply', 'None'].includes(schoolInfo.religious_denomination)
? 'None'
: schoolInfo.religious_denomination}
</span>
)}
{schoolInfo.nursery_provision && (
<span className={styles.badge}>Nursery</span>
)}
@@ -288,7 +283,20 @@ export function SecondarySchoolDetailView({
)}
</p>
)}
<div className={styles.headerDetails}>
<button
type="button"
className={styles.detailsToggle}
aria-expanded={detailsOpen}
aria-controls="school-header-details"
onClick={() => setDetailsOpen((o) => !o)}
>
{detailsOpen ? 'Hide details' : 'Show all details'}
<span aria-hidden="true">{detailsOpen ? '▴' : '▾'}</span>
</button>
<div
id="school-header-details"
className={`${styles.headerDetails}${detailsOpen ? ` ${styles.headerDetailsOpen}` : ''}`}
>
{schoolInfo.headteacher_name && (
<span className={styles.headerDetail}>
<strong>Headteacher:</strong> {schoolInfo.headteacher_name}
@@ -326,6 +334,14 @@ export function SecondarySchoolDetailView({
</a>
</span>
)}
{schoolInfo.religious_denomination && (
<span className={styles.headerDetail}>
<strong>Religious character:</strong>{' '}
{['Does not apply', 'None'].includes(schoolInfo.religious_denomination)
? 'None'
: schoolInfo.religious_denomination}
</span>
)}
{schoolInfo.county && (
<span className={styles.headerDetail}>
<strong>County:</strong> {schoolInfo.county}