@psu-flex/core-ui-federated-wc
v1.4.7
Published
Registers PSU chrome as **vanilla custom elements** (shadow DOM) for external apps: Angular, static HTML, Drupal, etc.
Downloads
252
Readme
@psu-flex/core-ui-federated-wc
Registers PSU chrome as vanilla custom elements (shadow DOM) for external apps: Angular, static HTML, Drupal, etc.
Built on @psu-flex/chrome-elements — no React, no r2wc, no @psu-flex/wp-wc-resolver.
Components
| Custom element | Content |
| ------------------------ | ------------------------- |
| <psu-brand-bar> | Brand bar + search flyout |
| <psu-mega-menu-header> | Mega menu header |
| <psu-site-footer> | Site footer |
| <psu-brand-footer> | Brand footer |
Tag rename: Legacy tags were
psf-*. Current tags arepsu-*.
Internal Next.js apps
PSU Flex Next.js apps should not use this package. Use the server/CTF path instead:
@psu-flex/core-ui-federated—BrandBarNewCtf,BrandFooterNewCtf@psu-flex/core-ui-layout-navigation—MegaMenuHeaderCtf,FooterNewCtf
Those render HTML in the document (not shadow DOM) and load @psu-flex/chrome-elements/*.css automatically.
Quick start
1. Register elements + fonts (once per app)
Angular (main.ts):
import { defineAllPsuElements } from '@psu-flex/core-ui-federated-wc';
defineAllPsuElements();Next.js / React (client component):
'use client';
import { useEffect } from 'react';
import { defineAllPsuElements } from '@psu-flex/core-ui-federated-wc';
export function FederatedClient() {
useEffect(() => {
defineAllPsuElements();
}, []);
return null;
}Importing the package alone does not register tags — you must call defineAllPsuElements().
2. Load document fonts
Theme tokens and component layout CSS live inside each element’s shadow root. Only fonts must be loaded on the document:
Angular angular.json:
"styles": ["node_modules/@psu-flex/wc-styles/dist/psf-styles.css"]HTML:
<link rel="stylesheet" href="node_modules/@psu-flex/wc-styles/dist/psf-styles.css" />defineAllPsuElements() also calls injectPsuFonts() (font link injection). Loading psf-styles.css is still recommended for full @font-face coverage.
3. Add tags to your template
<psu-brand-bar></psu-brand-bar>
<psu-mega-menu-header></psu-mega-menu-header>
<!-- page content -->
<psu-site-footer></psu-site-footer>
<psu-brand-footer></psu-brand-footer>4. Hydrate after fetch
Raw Contentful/API payloads are mapped inside each element. Pass the same JSON shape.
Option A — props (Angular, recommended):
brandBarEl.props = {
...data.brandBar,
isExternal: true,
extraSearchOptions: searchoptions, // optional, legacy shape
};
headerEl.props = data.header;
footerEl.props = data.footer;
brandFooterEl.props = data.brandFooter;Option B — data attribute (serialized JSON):
import {
hydrateChromeElementData,
hydrateAllPsuChromeElements,
} from '@psu-flex/core-ui-federated-wc';
hydrateAllPsuChromeElements({
brandBar: { ...data.brandBar, isExternal: true },
header: data.header,
footer: data.footer,
brandFooter: data.brandFooter,
});
// Or per element:
hydrateChromeElementData(document.querySelector('psu-brand-bar'), payload);Brand bar search (external apps)
External embeds should send users to Penn State search unless you override it.
| Prop | External default | Purpose |
| -------------------- | ---------------- | --------------------------------------------------------------- |
| isExternal | true (on CE) | Form action → https://www.psu.edu/search |
| extraSearchOptions | — | Extra radios; optional slug, queryParamName, urlToInclude |
| searchAction | — | Override target URL (usually omit for external) |
Do not set searchAction: '/search' on external sites unless that route exists on your host.
// Correct for external Angular / static hosts
brandBarEl.props = {
...data.brandBar,
isExternal: true,
extraSearchOptions: mySearchOptions,
};Fetching data
Cache federated data on the server when possible, then hydrate on the client after defineAllPsuElements().
APIs (PSU Flex endpoints)
| API | Data |
| ---------------------------------------------------------------------------------------- | ------------------------------------------- |
| fetchAllFederatedData | All chrome (recommended) |
| fetchBrandBar | Brand bar only |
| fetchHeader | Header only |
| fetchFooter | Footer only |
| Brand footer | Via fetchAllFederatedData → brandFooter |
Example — fetch all (Next.js server → client hydrate)
// fetchFederatedData.ts
export async function fetchAllFederatedData() {
const response = await fetch('https://psu-flex-endpoints.vercel.app/api/fetchAllFederatedData', {
next: { revalidate: 3600 },
});
if (!response.ok) throw new Error('Failed to fetch federated data');
return response.json();
}// layout.tsx (server)
import { FederatedClient } from './FederatedClient';
import { fetchAllFederatedData } from './fetchFederatedData';
export default async function RootLayout({ children }) {
const data = await fetchAllFederatedData();
return (
<html lang="en">
<head>
<link rel="stylesheet" href="/node_modules/@psu-flex/wc-styles/dist/psf-styles.css" />
</head>
<body>
<FederatedClient data={data} />
<psu-brand-bar id="psu-brand-bar" />
<psu-mega-menu-header />
<main>{children}</main>
<psu-site-footer />
<psu-brand-footer />
</body>
</html>
);
}// FederatedClient.tsx
'use client';
import { useEffect } from 'react';
import { defineAllPsuElements, hydrateAllPsuChromeElements } from '@psu-flex/core-ui-federated-wc';
export function FederatedClient({
data,
}: {
data: Awaited<ReturnType<typeof fetchAllFederatedData>>;
}) {
useEffect(() => {
defineAllPsuElements();
if (data) {
hydrateAllPsuChromeElements({
brandBar: { ...data.brandBar, isExternal: true },
header: data.header,
footer: data.footer,
brandFooter: data.brandFooter,
});
}
}, [data]);
return null;
}Example — Angular
<psu-brand-bar #brandBar></psu-brand-bar>
<psu-mega-menu-header #header></psu-mega-menu-header>
<psu-site-footer #footer></psu-site-footer>
<psu-brand-footer #brandFooter></psu-brand-footer>// app.component.ts — after fetch
this.brandBar.nativeElement.props = { ...data.brandBar, isExternal: true };
this.header.nativeElement.props = data.header;
this.footer.nativeElement.props = data.footer;
this.brandFooter.nativeElement.props = data.brandFooter;Use CUSTOM_ELEMENTS_SCHEMA in your Angular component.
API reference
defineAllPsuElements()
Registers all four custom elements and injects document fonts. Call once before hydrating.
hydrateChromeElementData(el, payload)
Sets element.data / data attribute with JSON (same contract as legacy r2wc data: 'string').
hydrateAllPsuChromeElements({ brandBar, header, footer, brandFooter })
Hydrates the four default tags in one call.
Individual registrars
import {
definePsuBrandBar,
definePsuMegaMenuHeader,
definePsuSiteFooter,
definePsuBrandFooter,
injectPsuFonts,
} from '@psu-flex/core-ui-federated-wc';Migration from psf-* / r2wc
| Before | After |
| ------------------------------------- | ---------------------------------------------------- |
| psf-brand-bar | psu-brand-bar |
| psf-header | psu-mega-menu-header |
| psf-footer | psu-site-footer |
| psf-brand-footer | psu-brand-footer |
| @psu-flex/wp-wc-resolver in webpack | Not needed |
| React BrandBarWcClient + r2wc | defineAllPsuElements() + props / data |
| searchoptions prop on React client | extraSearchOptions on props + isExternal: true |
Related packages
@psu-flex/chrome-elements— render, map-props, behavior (advanced / tree-shake per component)@psu-flex/wc-styles— document fonts CSS
