import { html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { USWDSBaseComponent } from '../../utils/base-component.js';
import { initializeFooter } from './usa-footer-behavior.js';
// Import official USWDS compiled CSS
import '../../styles/styles.css';
export interface FooterLink {
label: string;
href: string;
}
export interface FooterSection {
title: string;
links: FooterLink[];
}
/**
* USA Footer Web Component
*
* Minimal wrapper around USWDS footer functionality.
* Uses USWDS-mirrored behavior pattern for 100% behavioral parity.
*
* IMPORTANT: The footer and identifier are separate components in USWDS.
* Place AFTER on the page, not inside it.
*
* @example
* ```html
*
*
* ```
*
* @element usa-footer
* @fires footer-link-click - Dispatched when a footer link is clicked
*
* @see README.mdx - Complete API documentation, usage examples, and implementation notes
* @see CHANGELOG.mdx - Component version history and breaking changes
* @see TESTING.mdx - Testing documentation and coverage reports
*
* @uswds-js-reference https://github.com/uswds/uswds/tree/develop/packages/usa-footer/src/index.js
* @uswds-css-reference https://github.com/uswds/uswds/tree/develop/packages/usa-footer/src/styles/_usa-footer.scss
* @uswds-docs https://designsystem.digital.gov/components/footer/
* @uswds-guidance https://designsystem.digital.gov/components/footer/#guidance
* @uswds-accessibility https://designsystem.digital.gov/components/footer/#accessibility
*/
@customElement('usa-footer')
export class USAFooter extends USWDSBaseComponent {
static override styles = css`
:host {
display: block;
}
:host([hidden]) {
display: none;
}
`;
@property({ type: String })
variant: 'slim' | 'medium' | 'big' = 'medium';
@property({ type: String })
agencyName = '';
@property({ type: Array })
sections: FooterSection[] = [];
private slottedContent: string = '';
// Store cleanup function from behavior
private cleanup?: () => void;
override connectedCallback() {
super.connectedCallback();
this.setAttribute('data-web-component-managed', 'true');
// Capture any initial content before render
if (this.childNodes.length > 0 && this.sections.length === 0) {
this.slottedContent = this.innerHTML;
this.innerHTML = '';
}
}
override disconnectedCallback() {
super.disconnectedCallback();
this.cleanup?.();
}
override async firstUpdated(changedProperties: Map) {
// ARCHITECTURE: Script Tag Pattern
// USWDS is loaded globally via script tag in .storybook/preview-head.html
// Components just render HTML - USWDS enhances automatically via window.USWDS
// ARCHITECTURE: USWDS-Mirrored Behavior Pattern
// Uses dedicated behavior file (usa-footer-behavior.ts) that replicates USWDS source exactly
super.firstUpdated(changedProperties);
// Wait for DOM to be fully rendered
await this.updateComplete;
await new Promise((resolve) => requestAnimationFrame(() => resolve(undefined)));
// Initialize using mirrored USWDS behavior
this.cleanup = initializeFooter(this);
}
override updated(changedProperties: Map) {
super.updated(changedProperties);
// Apply captured content using DOM manipulation
this.applySlottedContent();
}
private applySlottedContent() {
if (this.slottedContent) {
const slotElement = this.querySelector('slot');
if (slotElement && this.sections.length === 0) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = this.slottedContent;
slotElement.replaceWith(...Array.from(tempDiv.childNodes));
}
}
}
private handleLinkClick(link: FooterLink, e: Event) {
const event = new CustomEvent('footer-link-click', {
detail: {
label: link.label,
href: link.href,
},
bubbles: true,
composed: true,
cancelable: true,
});
this.dispatchEvent(event);
// Only navigate if event wasn't cancelled and href is provided
if (!event.defaultPrevented && link.href) {
// Allow the default link behavior to handle navigation
return;
} else {
e.preventDefault();
}
}
private renderFooterNav() {
if (this.sections.length === 0) return '';
if (this.variant === 'big') {
return this.renderBigFooterNav();
} else {
return this.renderMediumFooterNav();
}
}
private renderMediumFooterNav() {
return html`
`;
}
private renderBigFooterNav() {
return html`
`;
}
private renderFooterSecondary() {
// Only render secondary section for medium and big variants
// This provides agency info and contact details per USWDS structure
if (this.variant === 'slim') {
return '';
}
return html`
`;
}
private renderMediumFooterSections() {
return this.sections.map((section) => this.renderMediumFooterSection(section));
}
private renderMediumFooterSection(section: FooterSection) {
// For medium footer, render section title as primary link (USWDS pattern)
const primaryLink = section.links.length > 0 ? section.links[0] : { label: section.title, href: '#' };
return html`
`;
}
private renderBigFooterSections() {
return this.sections.map((section) => this.renderBigFooterSection(section));
}
private renderBigFooterSection(section: FooterSection) {
// For big footer, render collapsible sections with headings and sublists (USWDS pattern)
return html`
`;
}
private renderSectionLinks(links: FooterLink[]) {
return links.map((link) => this.renderSectionLink(link));
}
private renderSectionLink(link: FooterLink) {
return html`
`;
}
// Use light DOM for USWDS compatibility
protected override createRenderRoot(): HTMLElement {
return this as any;
}
override render() {
const footerClasses = ['usa-footer', `usa-footer--${this.variant}`].filter(Boolean).join(' ');
return html`
`;
}
}