Repository navigation
Eagle Public Page Layout
React eagle-public (react line) draws every page from one set of layout primitives: a page container width, a shared blue masthead band, and a breadcrumb trail.
- Wrap the page's content in
page-container, or nest it inside a band that already carries the class, such asPageMasthead's inner wrapper. - Use
PageMastheadfor a page that opens with a titled band. Passtitleand, unless the page is the site root,breadcrumbsending in the current page. - Add no competing width, max-width or side-padding rule. If a bespoke rule is unavoidable, use
--page-max-widthor--page-gutter. Add no text measure cap. - Let a full-bleed page (like the map) opt out of the container on its own route class, not by overriding
.page-containerglobally.
src/assets/styles/base/layout.css states the content width once, as CSS custom properties on :root:
| Token | Value | Notes |
|---|---|---|
--page-max-width |
1440px |
Cap on every content band. |
--page-gutter |
16px below 860px, 24px from 860px up |
Side padding. |
Text has no reading-width cap of its own. A long-text block (.compliance-tab__intro, .content-wrapper) runs the full width of its container, bounded by --page-max-width and the gutter.
The .page-container class applies max-width, centering and the side gutter. Bootstrap's .container, .container-fluid and the .container-{sm,md,lg,xl,xxl} family share the same rule, so a static page or the comments page that still carries Bootstrap markup lines up with the rest of the site.
Put .page-container on the element inside a full-width band, not on the router outlet (app-shell.tsx's <main class="app-content">). A band's background (masthead, footer, hero strip) runs edge to edge, and only its content is capped and gutter-padded. On the outlet, the class would cap every band's background too.
The map route opts out. app-shell.tsx adds a projects-route class to .app-content when the path starts with /projects. src/app/pages/projects/projects.css uses .app-content.projects-route:has(.projects-view) to let the map page run full width and full height.
src/app/layout/page-masthead.tsx renders the blue band every page opens with. It is a <section> with a page-masthead__inner page-container wrapper, so its content lines up with the header, the footer and the rest of the page.
| Prop | Type | Purpose |
|---|---|---|
title |
ReactNode |
The page's h1. A node, so a page can show a loading placeholder while the real title fetches. |
breadcrumbs |
Crumb[] |
Root first, current page last. Omitted on the site root. |
meta |
ReactNode |
One line under the title. |
actions |
ReactNode |
Controls that sit under the title and meta line, not beside them. |
children |
ReactNode |
Extra content the page puts inside the band, under the title row. |
busy |
boolean |
Sets aria-busy while the page's title data is loading. |
className |
string |
Extra class on the band, for a page that styles its own actions or content. |
The band always renders a breadcrumb row, even when a page passes no breadcrumbs. The row holds its height (min-height: 1.313rem) either way, so the title sits at the same vertical offset on every page.
From 860px wide, the band has a min-height of 14rem. That is the tallest band the site has (search, whose title is followed by a keyword field and a record-type row).
The title and meta line clip to one line with an ellipsis, so a long name cannot push the band past it. e2e/tests/page-layout.spec.ts checks that every band lands on this floor. Below 860px the band is sized by its content, because a project name and the home search row wrap by different amounts on a phone.
PageMasthead is used by:
- the home page (
src/app/pages/home/home.tsx) - search (
src/app/pages/search/unified-search.tsx) - project details, through
src/app/pages/project/project-masthead.tsx, which fills the band with the project's name, proponent/location asmeta, and subscribe/copy-link buttons asactions
src/app/layout/breadcrumbs.tsx exports Breadcrumbs({ items }), where each item is a Crumb:
interface Crumb {
label: string;
to?: string; // omitted, or ignored, on the last crumb
}The trail is ordered root first. The last crumb is the current page and renders as plain text (aria-current="page"), never a link. The whole trail sits on one line and ellipses rather than wrapping.
The project details trail is Home > Search > the project's name. The middle crumb links to searchUrl('projects') (src/app/routes/legacy-search.ts), not a bare /search, so it lands on the projects record type.
src/app/components/display-grid/types.ts marks a column defaultHidden: true when it starts switched off. The column picker still lists it so a reader can turn it on.
The grid's URL state (src/app/components/display-grid/use-grid-url-state.ts) reads that default through the cols query param:
- no
colsin the URL: the record type's owndefaultHiddenColumnsapply. -
cols=none: every column shows, including ones that default to hidden. -
cols=a,b: exactly the named columns are hidden.
Projects sort by name ascending (+name, src/app/pages/search/types/projects.ts) unless the URL names another sort.
htmlToText (src/app/utils/safe-html.ts) is the one helper for turning stored HTML into plain text. It sanitizes with DOMPurify, inserts spaces around elements that a reader would see a line break around, and collapses whitespace. Use it for any plain-text rendering of a rich-text field (a meta line, a card summary), not a one-off regex.
e2e/tests/page-layout.spec.ts checks, at viewport widths 390, 860, 1024 and 1600:
- the page container's content and the footer share the same left/right edges under a full-width header, on home, search, a static page and a project page
- every masthead band shares one background, one top padding and one title offset
- the masthead's floor height (
14rem) holds from 860px up - a project band's actions sit under its description, not pushed out to the right
The visual parity suite (e2e/parity) drives /search against the design prototype, state by state, and checks structural measurements and a pixel screenshot per state.
The pixel comparison is parked behind a PIXEL_COMPARISON_PARKED flag in unified-search.parity.spec.ts, because the reference screenshots do not match the built page. Structural checks still run. Unparking needs new references captured from an updated design handoff, with yarn parity:reference.
For project pages drawn from a content file, see Extended Project Pages.
Eagle Documentation
Operations
- Deployment Pipeline
- Helm Charts
- Reverse Proxy
- Rollback Procedures
- Eagle Public Release Lines
- Eagle Search Archive
- Azure Hosting
- Front Door Per-App Split
- Front Door Ban Ladder
- Error Logging
- Secrets Management
Architecture
- Architecture Overview
- API Architecture
- Analytics Architecture
- Angular Patterns
- Eagle Public Page Layout
- Extended Project Pages
- ENGAGE–EAGLE Integration
- BCGW Project Export
- Eagle Search
- Eagle DEMI
- Eagle DEMI Push Sweep
- Eagle DEMI Re-Push and Backfill
- Eagle Notify
- Project Data Models
- Async Jobs API
Configuration
Help
Repositories
- eagle-public
- eagle-admin
- eagle-api
- eagle-search
- eagle-demi
- eagle-edge
- eagle-notify
- eagle-analytics
- eagle-helper-pods
- eao-nginx
Environments