Merge pull request 'fix(detail): restore the shell's mobile header/nav CSS' (#85) from perf/server-client-split into main
Stage (build -> staging -> E2E gate) / Build Backend (FastAPI) (push) Successful in 46s
Stage (build -> staging -> E2E gate) / Build Frontend (Next.js) (push) Successful in 55s
Stage (build -> staging -> E2E gate) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 2m13s
Stage (build -> staging -> E2E gate) / Deploy to Staging (push) Successful in 2s
Stage (build -> staging -> E2E gate) / E2E Journeys against Staging (push) Successful in 1m12s
Stage (build -> staging -> E2E gate) / Build Backend (FastAPI) (push) Successful in 46s
Stage (build -> staging -> E2E gate) / Build Frontend (Next.js) (push) Successful in 55s
Stage (build -> staging -> E2E gate) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 2m13s
Stage (build -> staging -> E2E gate) / Deploy to Staging (push) Successful in 2s
Stage (build -> staging -> E2E gate) / E2E Journeys against Staging (push) Successful in 1m12s
Reviewed-on: #85
This commit was merged in pull request #85.
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guards against "stranded" CSS: when markup moves between components, its
|
||||||
|
* rules must move with it. CSS Modules hash class names per file, so a rule
|
||||||
|
* left behind in another stylesheet silently stops applying — exactly how the
|
||||||
|
* header's mobile `.headerDetails { display: none }` (and the section-nav
|
||||||
|
* mobile swap) stopped working when the detail view was split into a client
|
||||||
|
* shell plus server sections.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const SCHOOL_DIR = path.join(__dirname, '..', '..', 'components', 'school');
|
||||||
|
|
||||||
|
function classesDefinedIn(css: string): Set<string> {
|
||||||
|
return new Set(Array.from(css.matchAll(/\.(-?[_a-zA-Z][\w-]*)/g), (m) => m[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function classesUsedIn(tsx: string): Set<string> {
|
||||||
|
return new Set(Array.from(tsx.matchAll(/\bstyles\.([_a-zA-Z][\w]*)/g), (m) => m[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const components = fs
|
||||||
|
.readdirSync(SCHOOL_DIR)
|
||||||
|
.filter((f) => f.endsWith('.tsx'))
|
||||||
|
.map((file) => {
|
||||||
|
const source = fs.readFileSync(path.join(SCHOOL_DIR, file), 'utf8');
|
||||||
|
const importMatch = source.match(/import\s+styles\s+from\s+['"](.+?\.module\.css)['"]/);
|
||||||
|
return { file, source, stylesheet: importMatch?.[1] };
|
||||||
|
})
|
||||||
|
.filter((c) => c.stylesheet);
|
||||||
|
|
||||||
|
describe('school detail stylesheets', () => {
|
||||||
|
it.each(components.map((c) => [c.file, c.source, c.stylesheet as string]))(
|
||||||
|
'%s only uses classes its own stylesheet defines',
|
||||||
|
(_file, source, stylesheet) => {
|
||||||
|
const css = fs.readFileSync(path.join(SCHOOL_DIR, stylesheet), 'utf8');
|
||||||
|
const defined = classesDefinedIn(css);
|
||||||
|
const missing = Array.from(classesUsedIn(source)).filter((c) => !defined.has(c));
|
||||||
|
expect(missing).toEqual([]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -662,8 +662,106 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|
||||||
.sectionNavAll {
|
.sectionNavAll {
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.headerContent {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnAdd,
|
||||||
|
.btnRemove {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schoolName {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pills wrap horizontally instead of stacking — short tokens like
|
||||||
|
"Manchester" / "Voluntary aided" fit 2 per row instead of 3 full
|
||||||
|
rows of empty horizontal space. */
|
||||||
|
.meta {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.sectionNavLinks {
|
||||||
|
-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
|
||||||
|
mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When scrolled to the end, drop the fade so the last item isn't dimmed. */
|
||||||
|
.sectionNavLinks.atEnd {
|
||||||
|
-webkit-mask-image: none;
|
||||||
|
mask-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionsPanel {
|
||||||
|
position: fixed;
|
||||||
|
top: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: auto;
|
||||||
|
max-height: 74vh;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
padding: 0.5rem 0.6rem calc(0.8rem + env(safe-area-inset-bottom, 0));
|
||||||
|
box-shadow: 0 -10px 40px rgba(26, 22, 18, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionsItem {
|
||||||
|
padding: 0.7rem 0.6rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Swap which controls show at the mobile breakpoint. Declared last so these
|
||||||
|
display rules win over the base (equal-specificity) declarations above. */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.sectionNavLinks,
|
||||||
|
.sectionNavCompare,
|
||||||
|
.sectionNavAll,
|
||||||
|
.sectionNavBackLabel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionNavMenu {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionNavCompareIcon {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user