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-meta-section/FooterMetaSectionWrapper.test.svelte b/libs/web-components/src/components/footer-meta-section/FooterMetaSectionWrapper.test.svelte
index 1acef177ad..e5dd3e8d29 100644
--- a/libs/web-components/src/components/footer-meta-section/FooterMetaSectionWrapper.test.svelte
+++ b/libs/web-components/src/components/footer-meta-section/FooterMetaSectionWrapper.test.svelte
@@ -1,4 +1,4 @@
-
+
-
- Link 1
+
+ Link 1
Link 2
+ Privacy
+ Custom content
diff --git a/libs/web-components/src/components/footer-nav-section/FooterNavSection.spec.ts b/libs/web-components/src/components/footer-nav-section/FooterNavSection.spec.ts
new file mode 100644
index 0000000000..0cc4828eb2
--- /dev/null
+++ b/libs/web-components/src/components/footer-nav-section/FooterNavSection.spec.ts
@@ -0,0 +1,14 @@
+import { render } from "@testing-library/svelte";
+import "@testing-library/jest-dom";
+import FooterNavSectionWrapper from "./FooterNavSectionWrapper.test.svelte";
+
+describe("FooterNavSection", () => {
+ it("renders its heading and slotted content without replacing it", () => {
+ const { getByTestId, getByText } = render(FooterNavSectionWrapper);
+
+ expect(getByText("Resources")).toBeInTheDocument();
+ expect(getByTestId("standard-link")).toHaveAttribute("href", "/services");
+ expect(getByTestId("goa-link").tagName).toBe("GOA-LINK");
+ expect(getByTestId("custom-content")).toHaveTextContent("Custom content");
+ });
+});
diff --git a/libs/web-components/src/components/footer-nav-section/FooterNavSection.svelte b/libs/web-components/src/components/footer-nav-section/FooterNavSection.svelte
index 10f72cc443..4e999f408f 100644
--- a/libs/web-components/src/components/footer-nav-section/FooterNavSection.svelte
+++ b/libs/web-components/src/components/footer-nav-section/FooterNavSection.svelte
@@ -1,52 +1,22 @@
-
+
{#if heading}
{heading}
{/if}
-
-
-
-
- 1 ? "block" : "flex"};
@@ -55,16 +25,13 @@
--wide-column-count: ${maxcolumncount};
`}
>
- {#each children as child}
- - {child.innerHTML}
- {/each}
-
+
+
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
+