From 50005272f836003ed536cb417264afcd6a4f4117 Mon Sep 17 00:00:00 2001 From: Dustin Nielsen Date: Wed, 12 Aug 2026 10:27:47 -0600 Subject: [PATCH] feat(#2812): Updated footer to accept slotted content --- .../routes/docs/footer/footer.component.html | 14 ++ .../routes/docs/footer/footer.component.ts | 3 +- .../react/src/routes/docs/footer/footer.tsx | 17 ++ docs/src/data/configurations/footer.ts | 43 +++++ .../src/assets/css/components.css | 168 ++++++++++++++---- .../FooterMetaSection.spec.ts | 35 ++-- .../FooterMetaSection.svelte | 82 ++------- .../FooterMetaSectionWrapper.test.svelte | 15 +- .../FooterNavSection.spec.ts | 14 ++ .../FooterNavSection.svelte | 83 +++------ .../FooterNavSectionWrapper.test.svelte | 15 ++ 11 files changed, 302 insertions(+), 187 deletions(-) create mode 100644 libs/web-components/src/components/footer-nav-section/FooterNavSection.spec.ts create mode 100644 libs/web-components/src/components/footer-nav-section/FooterNavSectionWrapper.test.svelte diff --git a/apps/prs/angular/src/routes/docs/footer/footer.component.html b/apps/prs/angular/src/routes/docs/footer/footer.component.html index 9ecc12f99c..3863e3b9aa 100644 --- a/apps/prs/angular/src/routes/docs/footer/footer.component.html +++ b/apps/prs/angular/src/routes/docs/footer/footer.component.html @@ -12,6 +12,20 @@

With meta section

+

With links

+ + + Apply online + + All services + + + + Privacy + Accessibility + + +

With navigation

diff --git a/apps/prs/angular/src/routes/docs/footer/footer.component.ts b/apps/prs/angular/src/routes/docs/footer/footer.component.ts index 4e698e1c3b..5d179c051e 100644 --- a/apps/prs/angular/src/routes/docs/footer/footer.component.ts +++ b/apps/prs/angular/src/routes/docs/footer/footer.component.ts @@ -3,12 +3,13 @@ import { GoabAppFooter, GoabAppFooterMetaSection, GoabAppFooterNavSection, + GoabLink, } from "@abgov/angular-components"; @Component({ standalone: true, selector: "abgov-docs-footer", templateUrl: "./footer.component.html", - imports: [GoabAppFooter, GoabAppFooterMetaSection, GoabAppFooterNavSection], + imports: [GoabAppFooter, GoabAppFooterMetaSection, GoabAppFooterNavSection, GoabLink], }) export class DocsFooterComponent {} diff --git a/apps/prs/react/src/routes/docs/footer/footer.tsx b/apps/prs/react/src/routes/docs/footer/footer.tsx index 9947e1aa29..ca0ae2274a 100644 --- a/apps/prs/react/src/routes/docs/footer/footer.tsx +++ b/apps/prs/react/src/routes/docs/footer/footer.tsx @@ -2,6 +2,7 @@ import { GoabAppFooter, GoabAppFooterMetaSection, GoabAppFooterNavSection, + GoabLink, } from "@abgov/react-components"; export function DocsFooterRoute() { @@ -51,6 +52,22 @@ export function DocsFooterRoute() { Accessibility + +

With links

+ + + Apply online + + All services + + + + Privacy + + Accessibility + + + ); } diff --git a/docs/src/data/configurations/footer.ts b/docs/src/data/configurations/footer.ts index 746c430b8c..41d0656067 100644 --- a/docs/src/data/configurations/footer.ts +++ b/docs/src/data/configurations/footer.ts @@ -142,6 +142,49 @@ export const footerConfigurations: ComponentConfigurations = { Terms of use Accessibility +`, + }, + }, + { + id: "with-links", + name: "With links", + description: "Footer sections with standard links and link components", + code: { + react: ` + + Apply online + + All services + + + + Privacy + Accessibility + +`, + angular: ` + + Apply online + + All services + + + + Privacy + Accessibility + +`, + webComponents: ` + + Apply online + + All services + + + + Privacy + Accessibility + `, }, }, diff --git a/libs/web-components/src/assets/css/components.css b/libs/web-components/src/assets/css/components.css index 88a4038bc6..923a28b8d7 100644 --- a/libs/web-components/src/assets/css/components.css +++ b/libs/web-components/src/assets/css/components.css @@ -33,6 +33,31 @@ goa-app-header-menu a:first-of-type { box-shadow: none; } +/* GoAAppFooter */ +/** needed to override a, a:visited at reset.css */ +goa-app-footer-nav-section > a, +goa-app-footer-nav-section > a:visited, +goa-app-footer-meta-section > a, +goa-app-footer-meta-section > a:visited { + color: var(--goa-footer-color-links); + cursor: pointer; +} + +goa-app-footer-nav-section > a:hover, +goa-app-footer-meta-section > a:hover { + color: var(--goa-footer-color-links-hover); +} + +goa-app-footer-nav-section > a:focus-visible, +goa-app-footer-meta-section > a:focus-visible { + outline: var(--goa-footer-link-focus); + border-radius: var(--goa-footer-link-focus-border-radius); +} + +goa-app-footer-meta-section > a { + white-space: nowrap; +} + /* Table */ goa-table table { @@ -51,9 +76,18 @@ goa-table.sticky thead { goa-table td { font: var(--goa-typography-body-m); - padding: var(--goa-table-padding-data, var(--goa-space-xs) var(--goa-space-m) var(--goa-space-xs)); - background-color: var(--goa-table-color-bg-data, var(--goa-color-greyscale-white)); - border-bottom: var(--goa-table-data-border, 1px solid var(--goa-color-greyscale-200)); + padding: var( + --goa-table-padding-data, + var(--goa-space-xs) var(--goa-space-m) var(--goa-space-xs) + ); + background-color: var( + --goa-table-color-bg-data, + var(--goa-color-greyscale-white) + ); + border-bottom: var( + --goa-table-data-border, + 1px solid var(--goa-color-greyscale-200) + ); vertical-align: top; box-sizing: border-box; min-height: var(--goa-table-height-data, var(--goa-space-2xl)); @@ -76,15 +110,24 @@ goa-table[variant="relaxed"] td { } goa-table thead th { - background-color: var(--goa-table-color-bg-heading, var(--goa-color-greyscale-white)); + background-color: var( + --goa-table-color-bg-heading, + var(--goa-color-greyscale-white) + ); color: var(--goa-table-color-heading, var(--goa-color-greyscale-600)); font: var(--goa-table-typography-heading, var(--goa-typography-heading-s)); - padding: var(--goa-table-padding-heading, var(--goa-space-s) var(--goa-space-m) var(--goa-space-xs)); + padding: var( + --goa-table-padding-heading, + var(--goa-space-s) var(--goa-space-m) var(--goa-space-xs) + ); text-align: left; - border-bottom: var(--goa-table-heading-border, 2px solid var(--goa-color-greyscale-600)); + border-bottom: var( + --goa-table-heading-border, + 2px solid var(--goa-color-greyscale-600) + ); vertical-align: bottom; min-height: var(--goa-table-height-heading, 56px); - } +} .goa-table-number-column { font: var(--goa-typography-number-m); @@ -96,12 +139,18 @@ goa-table thead th { } goa-table thead th:has(goa-table-sort-header) { - padding: 0; + padding: 0; } goa-table thead th:has(goa-table-sort-header):hover { - background-color: var(--goa-table-color-bg-heading-hover, var(--goa-color-greyscale-100)); - color: var(--goa-table-color-heading-hover, var(--goa-color-interactive-hover)); + background-color: var( + --goa-table-color-bg-heading-hover, + var(--goa-color-greyscale-100) + ); + color: var( + --goa-table-color-heading-hover, + var(--goa-color-interactive-hover) + ); } goa-table tfoot td { @@ -120,16 +169,25 @@ goa-table[version="2"] thead th:last-child:not(:has(goa-table-sort-header)) { } /* Headers with TableSortHeader - override token padding for button inside (all variants) */ -goa-table[version="2"] thead th:first-child:has(goa-table-sort-header):not(:last-child) { - --goa-table-padding-heading: 18px var(--goa-space-m) var(--goa-space-m) var(--goa-space-l); +goa-table[version="2"] + thead + th:first-child:has(goa-table-sort-header):not(:last-child) { + --goa-table-padding-heading: 18px var(--goa-space-m) var(--goa-space-m) + var(--goa-space-l); } -goa-table[version="2"] thead th:last-child:has(goa-table-sort-header):not(:first-child) { - --goa-table-padding-heading: 18px var(--goa-space-l) var(--goa-space-m) var(--goa-space-m); +goa-table[version="2"] + thead + th:last-child:has(goa-table-sort-header):not(:first-child) { + --goa-table-padding-heading: 18px var(--goa-space-l) var(--goa-space-m) + var(--goa-space-m); } -goa-table[version="2"] thead th:first-child:last-child:has(goa-table-sort-header) { - --goa-table-padding-heading: 18px var(--goa-space-l) var(--goa-space-m) var(--goa-space-l); +goa-table[version="2"] + thead + th:first-child:last-child:has(goa-table-sort-header) { + --goa-table-padding-heading: 18px var(--goa-space-l) var(--goa-space-m) + var(--goa-space-l); } /* Data and footer cells */ @@ -143,7 +201,6 @@ goa-table[version="2"] tfoot td:last-child { padding-right: var(--goa-space-l, 24px); } - /* DataGrid keyboard focus ring on table cells. goa-data-grid signals focus via [data-focused] / [data-focus-corner] attributes so the visual styling lives with the table. The :focus rule @@ -156,7 +213,8 @@ goa-table th:focus { goa-table td[data-focused], goa-table th[data-focused] { - outline: var(--goa-border-width-l, 0.1875rem) solid var(--goa-color-interactive-focus); + outline: var(--goa-border-width-l, 0.1875rem) solid + var(--goa-color-interactive-focus); /* Inset so the outline stays visible on edge cells inside the table's overflow: hidden container. */ outline-offset: -0.1875rem; @@ -186,16 +244,22 @@ goa-data-grid [role="gridcell"]:not(td):not(th):focus { } goa-data-grid [role="gridcell"]:not(td):not(th)[data-focused] { - outline: var(--goa-border-width-l, 0.1875rem) solid var(--goa-color-interactive-focus); + outline: var(--goa-border-width-l, 0.1875rem) solid + var(--goa-color-interactive-focus); outline-offset: 0.125rem; } - /* Normal Variant Cell Types */ goa-table td.goa-table-cell--text { - padding-top: var(--goa-table-padding-cell-text, var(--goa-space-s)) !important; - padding-bottom: var(--goa-table-padding-cell-text, var(--goa-space-s)) !important; + padding-top: var( + --goa-table-padding-cell-text, + var(--goa-space-s) + ) !important; + padding-bottom: var( + --goa-table-padding-cell-text, + var(--goa-space-s) + ) !important; } goa-table td.goa-table-cell--checkbox, @@ -212,8 +276,14 @@ goa-table td.goa-table-cell--form-field { } goa-table td.goa-table-cell--badge { - padding-top: var(--goa-table-padding-cell-badge, var(--goa-space-s)) !important; - padding-bottom: var(--goa-table-padding-cell-badge, var(--goa-space-s)) !important; + padding-top: var( + --goa-table-padding-cell-badge, + var(--goa-space-s) + ) !important; + padding-bottom: var( + --goa-table-padding-cell-badge, + var(--goa-space-s) + ) !important; } /* Alignment helper classes */ @@ -230,8 +300,14 @@ goa-table th.goa-table-cell--numeric goa-table-sort-header { } goa-table td.goa-table-cell--numeric { - padding-top: var(--goa-table-padding-cell-text, var(--goa-space-s)) !important; - padding-bottom: var(--goa-table-padding-cell-text, var(--goa-space-s)) !important; + padding-top: var( + --goa-table-padding-cell-text, + var(--goa-space-s) + ) !important; + padding-bottom: var( + --goa-table-padding-cell-text, + var(--goa-space-s) + ) !important; } goa-table td.goa-table-cell--actions { @@ -242,8 +318,14 @@ goa-table td.goa-table-cell--actions { } goa-table td.goa-table-cell--text-supporting { - padding-top: var(--goa-table-padding-cell-text, var(--goa-space-s)) !important; - padding-bottom: var(--goa-table-padding-cell-text, var(--goa-space-s)) !important; + padding-top: var( + --goa-table-padding-cell-text, + var(--goa-space-s) + ) !important; + padding-bottom: var( + --goa-table-padding-cell-text, + var(--goa-space-s) + ) !important; } /* Relaxed variant overrides for alignment/layout classes */ @@ -255,12 +337,21 @@ goa-table[variant="relaxed"] td.goa-table-cell--numeric { goa-table[variant="relaxed"] td.goa-table-cell--actions { padding-top: var(--goa-table-padding-cell-actions-relaxed, 14px) !important; - padding-bottom: var(--goa-table-padding-cell-actions-relaxed, 14px) !important; + padding-bottom: var( + --goa-table-padding-cell-actions-relaxed, + 14px + ) !important; } goa-table[variant="relaxed"] td.goa-table-cell--text-supporting { - padding-top: var(--goa-table-padding-cell-text-supporting-relaxed, 8px) !important; - padding-bottom: var(--goa-table-padding-cell-text-supporting-relaxed, 8px) !important; + padding-top: var( + --goa-table-padding-cell-text-supporting-relaxed, + 8px + ) !important; + padding-bottom: var( + --goa-table-padding-cell-text-supporting-relaxed, + 8px + ) !important; } /* Relaxed Variant Cell Types */ @@ -272,12 +363,21 @@ goa-table[variant="relaxed"] td.goa-table-cell--text { goa-table[variant="relaxed"] td.goa-table-cell--checkbox { padding-top: var(--goa-table-padding-cell-checkbox-relaxed, 10px) !important; - padding-bottom: var(--goa-table-padding-cell-checkbox-relaxed, 10px) !important; + padding-bottom: var( + --goa-table-padding-cell-checkbox-relaxed, + 10px + ) !important; } goa-table[variant="relaxed"] td.goa-table-cell--form-field { - padding-top: var(--goa-table-padding-cell-form-field-relaxed, 11px) !important; - padding-bottom: var(--goa-table-padding-cell-form-field-relaxed, 11px) !important; + padding-top: var( + --goa-table-padding-cell-form-field-relaxed, + 11px + ) !important; + padding-bottom: var( + --goa-table-padding-cell-form-field-relaxed, + 11px + ) !important; } goa-table[variant="relaxed"] td.goa-table-cell--badge { diff --git a/libs/web-components/src/components/footer-meta-section/FooterMetaSection.spec.ts b/libs/web-components/src/components/footer-meta-section/FooterMetaSection.spec.ts index 598a4d3062..a5f711ee39 100644 --- a/libs/web-components/src/components/footer-meta-section/FooterMetaSection.spec.ts +++ b/libs/web-components/src/components/footer-meta-section/FooterMetaSection.spec.ts @@ -1,18 +1,25 @@ -import { render } from '@testing-library/svelte'; -import FooterMetaSectionWrapper from './FooterMetaSectionWrapper.test.svelte'; -import { fireEvent } from '@testing-library/svelte'; -import '@testing-library/jest-dom'; -describe('FooterMetaSection', () => { - it('should display and handle link clicks correctly', async () => { - const { getByTestId, getByText } = render(FooterMetaSectionWrapper); +import { fireEvent, render } from "@testing-library/svelte"; +import "@testing-library/jest-dom"; +import FooterMetaSectionWrapper from "./FooterMetaSectionWrapper.test.svelte"; + +describe("FooterMetaSection", () => { + it("renders slotted content without replacing it", () => { + const { getByTestId } = render(FooterMetaSectionWrapper); + + expect(getByTestId("standard-link")).toHaveAttribute( + "href", + "https://example.com", + ); + expect(getByTestId("goa-link").tagName).toBe("GOA-LINK"); + expect(getByTestId("custom-content")).toHaveTextContent("Custom content"); + }); + + it("preserves events on slotted content", async () => { + const { getByTestId } = render(FooterMetaSectionWrapper); + const link2 = getByTestId("specialClick"); - const link1 = getByText('Link 1'); - const link2 = getByTestId('specialClick'); - expect(link1).toBeTruthy(); - expect(link2).toBeTruthy(); - await fireEvent.click(link2); await fireEvent.click(link2); - const clickedText = getByTestId('link-is-clicked'); - expect(clickedText).toHaveTextContent('Link 2 clicked'); + + expect(getByTestId("link-is-clicked")).toHaveTextContent("Link 2 clicked"); }); }); diff --git a/libs/web-components/src/components/footer-meta-section/FooterMetaSection.svelte b/libs/web-components/src/components/footer-meta-section/FooterMetaSection.svelte index c4a1e7b6d0..eb0ce4915f 100644 --- a/libs/web-components/src/components/footer-meta-section/FooterMetaSection.svelte +++ b/libs/web-components/src/components/footer-meta-section/FooterMetaSection.svelte @@ -1,96 +1,40 @@ -
-
diff --git a/libs/web-components/src/components/footer-nav-section/FooterNavSectionWrapper.test.svelte b/libs/web-components/src/components/footer-nav-section/FooterNavSectionWrapper.test.svelte new file mode 100644 index 0000000000..b99b8d0177 --- /dev/null +++ b/libs/web-components/src/components/footer-nav-section/FooterNavSectionWrapper.test.svelte @@ -0,0 +1,15 @@ + + + + + + Services + Contact + Custom content +