Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c2c76cf
chore(#3844): add runtime V1/V2 token toggle to PR playgrounds
twjeffery Apr 20, 2026
a76dbef
Merge pull request #3845 from GovAlta/tom/playground-token-toggle
twjeffery May 6, 2026
a70367a
feat(#3746): add notification panel document page
twjeffery Apr 30, 2026
097bde8
feat(#3737): use wrapper JSDocs in docs
bdfranck Apr 1, 2026
274da45
chore(#3903): drop unused frontmatter fields from component and examp…
twjeffery May 4, 2026
8c2c2f4
chore(#3906): migrate Get Started to a content collection
twjeffery May 5, 2026
134ddfe
fix(#3602): change FileUploadInput error state to match FileUploadCar…
willcodeforcoffee Apr 17, 2026
8d9a428
fix(#3892): stop popover from closing parent popover, drawer, or modal
bdfranck May 5, 2026
1ee188c
chore: re-name PR files and titles from "3982" to "3892"
bdfranck May 8, 2026
1a2ef5d
fix(#3657): use the goa progress component in the temp notification
chrisolsen Apr 16, 2026
9f39510
fix(#3668): temporary notification refinements
twjeffery Apr 14, 2026
545a46d
fix(#3605): use :focus-visible across interactive components
twjeffery Apr 22, 2026
938505f
chore(#3636): accordion actions slot and filled version
willcodeforcoffee Apr 21, 2026
28fc94b
docs(#3889): Restructure examples for page and product size docs
twjeffery May 5, 2026
a9fc543
docs(#3875): add Updating your product developer docs page
Spark450 May 14, 2026
4e1c5dd
feat(#3814): Add trailingSlot to Work Side Menu Item
bdfranck May 11, 2026
0cf18c1
feat(#3873): add dark mode theming with React context and Angular ser…
vanessatran-ddi May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 14 additions & 0 deletions apps/prs/angular/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,23 @@
[open]="true"
(onNavigate)="handleNavigate($event)"
[primaryContent]="primaryTemplate"
[secondaryContent]="secondaryTemplate"
>
</goab-work-side-menu>

<ng-template #secondaryTemplate>
<goab-work-side-menu-item
[label]="'Switch to ' + (tokenMode === 'v1' ? 'V2' : 'V1') + ' tokens'"
icon="swap-horizontal"
[url]="tokenToggleUrl"
></goab-work-side-menu-item>
<goab-work-side-menu-item
[icon]="theme.mode() === 'dark' ? 'sunny' : 'moon'"
[label]="theme.mode() === 'dark' ? 'Light mode' : 'Dark mode'"
url="#toggle-theme"
></goab-work-side-menu-item>
</ng-template>

<ng-template #primaryTemplate>
<goab-work-side-menu-item
label="All Components"
Expand Down
21 changes: 21 additions & 0 deletions apps/prs/angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
GoabAppFooter,
GoabAppHeader,
GoabMicrositeHeader,
GoabThemeService,
GoabWorkSideMenu,
GoabWorkSideMenuItem,
GoabWorkSideMenuGroup,
Expand All @@ -15,6 +16,14 @@ import {
docsRouteDefinitions,
featureRouteDefinitions,
} from "./generated/pr-route-manifest.generated";
import {
applyTokenVersion,
resolveTokenVersion,
TokenVersion,
} from "./token-version/token-version";

// Sentinel URL handled by handleNavigate to toggle tokens instead of routing.
const TOKEN_TOGGLE_URL = "#tokens";

@Component({
standalone: true,
Expand All @@ -39,9 +48,12 @@ export class AppComponent {
readonly featureRouteDefinitions = featureRouteDefinitions;
readonly docsRouteDefinitions = docsRouteDefinitions;
readonly baseHref = inject(LocationStrategy).getBaseHref();
readonly tokenToggleUrl = TOKEN_TOGGLE_URL;
tokenMode: TokenVersion = resolveTokenVersion();

private fullPageRoutes = ["/features/2885"];
private router = inject(Router);
readonly theme = inject(GoabThemeService);

constructor() {
this.router.events
Expand All @@ -54,6 +66,15 @@ export class AppComponent {
}

handleNavigate(path: string): void {
if (path === TOKEN_TOGGLE_URL) {
this.tokenMode = this.tokenMode === "v1" ? "v2" : "v1";
applyTokenVersion(this.tokenMode);
return;
}
if (path === "#toggle-theme") {
this.theme.toggle();
return;
}
const internal = path.startsWith(this.baseHref)
? "/" + path.slice(this.baseHref.length)
: path;
Expand Down
50 changes: 50 additions & 0 deletions apps/prs/angular/src/app/token-version/token-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export type TokenVersion = "v1" | "v2";

const STORAGE_KEY = "goa-token-version";
const LINK_ID = "goa-tokens-v2";
const URL_PARAM = "tokens";

// Served at this path via the asset copy configured in project.json.
// Relative URL so it resolves against document.baseURI, which respects
// the deploy base on PR preview builds (an absolute /v2-tokens/... would
// 404 against the host root instead of the app's deploy path).
const V2_TOKENS_URL = "v2-tokens/tokens.css";

export function resolveTokenVersion(): TokenVersion {
const params = new URLSearchParams(window.location.search);
const fromUrl = params.get(URL_PARAM);
if (fromUrl === "v1" || fromUrl === "v2") return fromUrl;

const fromSession = sessionStorage.getItem(STORAGE_KEY);
if (fromSession === "v1" || fromSession === "v2") return fromSession;

return "v2";
}

export function applyTokenVersion(mode: TokenVersion): void {
// Link-ordering invariant: V2 stylesheet must be the LAST stylesheet in
// <head> so cascade resolves V2 over V1 unambiguously. Remove any existing
// node first, then append so the fresh node lands last.
document.getElementById(LINK_ID)?.remove();

if (mode === "v2") {
const link = document.createElement("link");
link.id = LINK_ID;
link.rel = "stylesheet";
link.href = V2_TOKENS_URL;
document.head.appendChild(link);
}

sessionStorage.setItem(STORAGE_KEY, mode);

// Only sync URL param if already present; don't add clutter on first toggle.
const url = new URL(window.location.href);
if (url.searchParams.has(URL_PARAM)) {
url.searchParams.set(URL_PARAM, mode);
window.history.replaceState({}, "", url);
}
}

// Eager side effect: resolve and apply at module load so V2 is in <head>
// before Angular bootstraps. Import this module once from main.ts.
applyTokenVersion(resolveTokenVersion());
13 changes: 13 additions & 0 deletions apps/prs/angular/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app/app.module";
// This import has a side effect: token-version.ts calls applyTokenVersion at
// module load, which puts the V2 stylesheet link in <head> before Angular
// bootstraps. Without this, the page would flash V1 on first paint.
import {
applyTokenVersion,
resolveTokenVersion,
} from "./app/token-version/token-version";

platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(() => {
// Re-apply after Angular's bundled styles.css is in <head>, so the V2
// link lands last in the cascade. Without this, the bundled V1 @import
// overrides V2 and the toggle appears to do nothing.
applyTokenVersion(resolveTokenVersion());
})
.catch((err) => console.error(err));
39 changes: 39 additions & 0 deletions apps/prs/angular/src/routes/bugs/3602/bug3602.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<h1>3602 FileUploadInput and FileUploadCard Improvements</h1>

<goab-form-item label="Small file (100KB limit)">
<goab-file-upload-input
maxFileSize="100KB"
(onSelectFile)="onSmallFileSelect($event)"
></goab-file-upload-input>
</goab-form-item>

<div style="padding-top: 0.75rem; padding-bottom: 2rem">
@for (f of smallFiles; track f.id) {
<goab-file-upload-card
[filename]="f.filename"
[size]="f.size"
[type]="f.type"
[progress]="f.progress"
(onDelete)="onSmallFileDelete($event, f.id)"
></goab-file-upload-card>
}
</div>

<goab-form-item label="Large file (50MB limit)">
<goab-file-upload-input
maxFileSize="50MB"
(onSelectFile)="onLargeFileSelect($event)"
></goab-file-upload-input>
</goab-form-item>

<div style="padding-top: 0.75rem; padding-bottom: 2rem">
@for (f of largeFiles; track f.id) {
<goab-file-upload-card
[filename]="f.filename"
[size]="f.size"
[type]="f.type"
[progress]="f.progress"
(onDelete)="onLargeFileDelete($event, f.id)"
></goab-file-upload-card>
}
</div>
76 changes: 76 additions & 0 deletions apps/prs/angular/src/routes/bugs/3602/bug3602.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import {
GoabFileUploadInputOnSelectFileDetail,
GoabFileUploadOnDeleteDetail,
} from "@abgov/ui-components-common";
import {
GoabFileUploadCard,
GoabFileUploadInput,
GoabFormItem,
} from "@abgov/angular-components";

type UploadedFile = {
id: string;
filename: string;
size: number;
type?: string;
progress: number;
};

@Component({
standalone: true,
selector: "abgov-bug3602",
templateUrl: "./bug3602.component.html",
imports: [CommonModule, GoabFileUploadInput, GoabFileUploadCard, GoabFormItem],
})
export class Bug3602Component {
smallFiles: UploadedFile[] = [];
largeFiles: UploadedFile[] = [];

onSmallFileSelect(detail: GoabFileUploadInputOnSelectFileDetail) {
const file = detail.file;
this.smallFiles = [
...this.smallFiles,
{
id: `${file.name}-${Date.now()}`,
filename: file.name,
size: file.size,
type: file.type,
progress: -1,
},
];
}

onLargeFileSelect(detail: GoabFileUploadInputOnSelectFileDetail) {
const file = detail.file;
this.largeFiles = [
...this.largeFiles,
{
id: `${file.name}-${Date.now()}`,
filename: file.name,
size: file.size,
type: file.type,
progress: -1,
},
];
}

private removeFirstMatchingFile(files: UploadedFile[], id: string): UploadedFile[] {
const index = files.findIndex((f) => f.id === id);
if (index === -1) {
return files;
}
return [...files.slice(0, index), ...files.slice(index + 1)];
}

onSmallFileDelete(detail: GoabFileUploadOnDeleteDetail, id: string) {
console.log("Deleting file with id:", id);
this.smallFiles = this.removeFirstMatchingFile(this.smallFiles, id);
}

onLargeFileDelete(detail: GoabFileUploadOnDeleteDetail, id: string) {
console.log("Deleting file with id:", id);
this.largeFiles = this.removeFirstMatchingFile(this.largeFiles, id);
}
}
6 changes: 6 additions & 0 deletions apps/prs/angular/src/routes/bugs/3602/bug3602.route.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"title": "FileUploadInput and FileUploadCard Improvements",
"path": "bugs/3602",
"id": "3602",
"type": "bug"
}
Loading
Loading