2026-08-02 21:33:28 +01:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useState, type ReactNode } from 'react';
|
|
|
|
|
import styles from './schoolSections.module.css';
|
|
|
|
|
|
2026-08-16 13:42:06 +01:00
|
|
|
export interface AdmissionsView {
|
|
|
|
|
id: string;
|
|
|
|
|
/** Button text. "This year" and the "N-year trend" label are asserted by the
|
|
|
|
|
* e2e journey, so they are not free to drift. */
|
|
|
|
|
label: string;
|
|
|
|
|
content: ReactNode;
|
|
|
|
|
/** The year view sizes its tile grid to match the chart beside it, so each
|
|
|
|
|
* view keeps its own class rather than sharing one wrapper style. */
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-02 21:33:28 +01:00
|
|
|
/**
|
|
|
|
|
* The only interactive part of the primary admissions section, and the only
|
|
|
|
|
* client component in components/school/.
|
|
|
|
|
*
|
2026-08-16 13:42:06 +01:00
|
|
|
* Every view is always present in the DOM and visibility is toggled with the
|
|
|
|
|
* `hidden` attribute, so the server-rendered markup passed in as content never
|
|
|
|
|
* ships as client JavaScript — this component carries the state and nothing
|
|
|
|
|
* else.
|
2026-08-02 21:33:28 +01:00
|
|
|
*
|
|
|
|
|
* It spans the header and the viewport because the segmented control sits
|
|
|
|
|
* inside .admissionsHeader beside the <h2> while the viewport is a sibling
|
|
|
|
|
* below it; wrapping only one would change the DOM the CSS depends on.
|
|
|
|
|
*/
|
|
|
|
|
export function AdmissionsViewToggle({
|
|
|
|
|
title,
|
|
|
|
|
subtitle,
|
2026-08-16 13:42:06 +01:00
|
|
|
views,
|
2026-08-02 21:33:28 +01:00
|
|
|
}: {
|
|
|
|
|
title: ReactNode;
|
|
|
|
|
subtitle: ReactNode;
|
2026-08-16 13:42:06 +01:00
|
|
|
views: AdmissionsView[];
|
2026-08-02 21:33:28 +01:00
|
|
|
}) {
|
2026-08-16 13:42:06 +01:00
|
|
|
const [active, setActive] = useState(views[0]?.id);
|
2026-08-02 21:33:28 +01:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div className={styles.admissionsHeader}>
|
|
|
|
|
<h2 className={styles.sectionTitle}>{title}</h2>
|
|
|
|
|
<div className={styles.admissionsSeg} role="group" aria-label="Admissions view">
|
2026-08-16 13:42:06 +01:00
|
|
|
{views.map((v) => (
|
|
|
|
|
<button
|
|
|
|
|
key={v.id}
|
|
|
|
|
type="button"
|
|
|
|
|
aria-pressed={active === v.id}
|
|
|
|
|
onClick={() => setActive(v.id)}
|
|
|
|
|
>
|
|
|
|
|
{v.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
2026-08-02 21:33:28 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{subtitle}
|
|
|
|
|
<div className={styles.admissionsViewport}>
|
2026-08-16 13:42:06 +01:00
|
|
|
{views.map((v) => (
|
|
|
|
|
<div key={v.id} className={v.className} hidden={active !== v.id}>
|
|
|
|
|
{v.content}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
2026-08-02 21:33:28 +01:00
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|