Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions apps/prs/angular/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@
"output": "/v2-tokens"
}
],
"styles": [
"apps/prs/angular/src/styles.css",
"node_modules/@abgov/design-tokens-v2/dist/tokens.css"
],
"styles": ["apps/prs/angular/src/styles.css"],
"scripts": []
},
"configurations": {
Expand Down
9 changes: 9 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,18 @@
[open]="true"
(onNavigate)="handleNavigate($event)"
[primaryContent]="primaryTemplate"
[secondaryContent]="tokenToggleTemplate"
>
</goab-work-side-menu>

<ng-template #tokenToggleTemplate>
<goab-work-side-menu-item
[label]="'Switch to ' + (tokenMode === 'v1' ? 'V2' : 'V1') + ' tokens'"
icon="swap-horizontal"
[url]="tokenToggleUrl"
></goab-work-side-menu-item>
</ng-template>

<ng-template #primaryTemplate>
<goab-work-side-menu-item
label="All Components"
Expand Down
15 changes: 15 additions & 0 deletions apps/prs/angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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,6 +47,8 @@ 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);
Expand All @@ -54,6 +64,11 @@ export class AppComponent {
}

handleNavigate(path: string): void {
if (path === TOKEN_TOGGLE_URL) {
this.tokenMode = this.tokenMode === "v1" ? "v2" : "v1";
applyTokenVersion(this.tokenMode);
return;
}
const internal = path.startsWith(this.baseHref)
? "/" + path.slice(this.baseHref.length)
: path;
Expand Down
47 changes: 47 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,47 @@
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.
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));
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit, OnDestroy } from "@angular/core";
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import {
GoabIconButton,
GoabWorkSideMenu,
Expand Down Expand Up @@ -46,25 +46,8 @@ type Notification = {
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class Feat2885Component implements OnInit, OnDestroy {
export class Feat2885Component {
menuOpen = true;
private v2TokensLink: HTMLLinkElement | null = null;

ngOnInit() {
// Dynamically load v2 design tokens only while this page is mounted,
// so they don't leak into other routes in the SPA.
this.v2TokensLink = document.createElement("link");
this.v2TokensLink.rel = "stylesheet";
this.v2TokensLink.href = "/v2-tokens/tokens.css";
document.head.appendChild(this.v2TokensLink);
}

ngOnDestroy() {
if (this.v2TokensLink) {
document.head.removeChild(this.v2TokensLink);
this.v2TokensLink = null;
}
}

notifications: Notification[] = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy } from "@angular/core";
import { Component } from "@angular/core";
import {
GoabBlock,
GoabDivider,
Expand All @@ -22,24 +22,9 @@ import { GoabMenuButtonOnActionDetail } from "@abgov/ui-components-common";
GoabText,
],
})
export class Feat3229Component implements OnInit, OnDestroy {
private v2TokensLink: HTMLLinkElement | null = null;
export class Feat3229Component {
lastAction = "";

ngOnInit() {
this.v2TokensLink = document.createElement("link");
this.v2TokensLink.rel = "stylesheet";
this.v2TokensLink.href = "/v2-tokens/tokens.css";
document.head.appendChild(this.v2TokensLink);
}

ngOnDestroy() {
if (this.v2TokensLink) {
document.head.removeChild(this.v2TokensLink);
this.v2TokensLink = null;
}
}

handleAction(detail: GoabMenuButtonOnActionDetail, label?: string) {
const source = label ? ` (${label})` : "";
this.lastAction = `Action "${detail.action}"${source}`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy } from "@angular/core";
import { Component } from "@angular/core";
import {
GoabBlock,
GoabDivider,
Expand All @@ -20,9 +20,7 @@ import { GoabTableOnSortDetail, GoabTableOnMultiSortDetail, GoabTableSortEntry }
GoabTableSortHeader,
],
})
export class Feat3344Component implements OnInit, OnDestroy {
private v2TokensLink: HTMLLinkElement | null = null;

export class Feat3344Component {
currentSorts: GoabTableSortEntry[] = [];
multiSorts: GoabTableSortEntry[] = [];
test3Sorts: GoabTableSortEntry[] = [];
Expand All @@ -39,20 +37,6 @@ export class Feat3344Component implements OnInit, OnDestroy {
multiSorted = [...this.data];
test3Sorted = [...this.data];

ngOnInit() {
this.v2TokensLink = document.createElement("link");
this.v2TokensLink.rel = "stylesheet";
this.v2TokensLink.href = "/v2-tokens/tokens.css";
document.head.appendChild(this.v2TokensLink);
}

ngOnDestroy() {
if (this.v2TokensLink) {
document.head.removeChild(this.v2TokensLink);
this.v2TokensLink = null;
}
}

onSingleSortChange(detail: GoabTableOnSortDetail) {
this.currentSorts = [{ column: detail.sortBy, direction: detail.sortDir === 1 ? "asc" : "desc" }];
this.singleSorted = this.sortData(this.data, this.currentSorts);
Expand Down
1 change: 0 additions & 1 deletion apps/prs/angular/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* You can add global styles to this file, and also import other style files */
@import "../../../../dist/libs/web-components/index.css";
@import "@abgov/design-tokens-v2/dist/tokens.css";

:root {
--goa-space-fill: 32ch;
Expand Down
37 changes: 33 additions & 4 deletions apps/prs/react/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CSSProperties } from "react";
import { useState, type CSSProperties } from "react";
import { Outlet, useNavigate } from "react-router-dom";
import {
GoabAppFooter,
Expand All @@ -14,7 +14,20 @@ import {
docsRouteDefinitions,
featureRouteDefinitions,
} from "./route-manifest";
import "@abgov/design-tokens-v2/dist/tokens.css"; // Production tokens. Comment out to test with legacy V1 token values.

import "@abgov/style";
// Runtime V1/V2 token switching. Importing this module applies the currently
// selected token set (default V2) to <head> before the app renders. The
// playground's work-side-menu exposes a secondary item that flips between
// V1 and V2 at runtime without editing source or restarting the dev server.
import {
applyTokenVersion,
resolveTokenVersion,
type TokenVersion,
} from "./tokenVersion";

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

const appContentStyle: CSSProperties = {
display: "flex",
Expand All @@ -25,8 +38,17 @@ const appContentStyle: CSSProperties = {
export function App() {
const navigate = useNavigate();
const baseUrl = import.meta.env.BASE_URL;
const [tokenMode, setTokenMode] = useState<TokenVersion>(() =>
resolveTokenVersion(),
);

const handleNavigate = (path: string) => {
const handleSideMenuNavigate = (path: string) => {
if (path === TOKEN_TOGGLE_URL) {
const next: TokenVersion = tokenMode === "v1" ? "v2" : "v1";
setTokenMode(next);
applyTokenVersion(next);
return;
}
const internal = path.startsWith(baseUrl) ? "/" + path.slice(baseUrl.length) : path;
navigate(internal);
};
Expand All @@ -42,7 +64,14 @@ export function App() {
heading="Testing Playground"
url={baseUrl}
open={true}
onNavigate={handleNavigate}
onNavigate={handleSideMenuNavigate}
secondaryContent={
<GoabWorkSideMenuItem
label={`Switch to ${tokenMode === "v1" ? "V2" : "V1"} tokens`}
icon="swap-horizontal"
url={TOKEN_TOGGLE_URL}
/>
}
primaryContent={
<>
<GoabWorkSideMenuGroup icon="alert-circle" heading="Bugs">
Expand Down
47 changes: 47 additions & 0 deletions apps/prs/react/src/app/tokenVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// ?url tells Vite to resolve the asset path without injecting the CSS.
import v2TokensUrl from "@abgov/design-tokens-v2/dist/tokens.css?url";

export type TokenVersion = "v1" | "v2";

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

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 = v2TokensUrl;
document.head.appendChild(link);
}

sessionStorage.setItem(STORAGE_KEY, mode);

// Only sync URL param if it's already in the URL; 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 React's first paint. Importing this module once from app.tsx runs this.
applyTokenVersion(resolveTokenVersion());
14 changes: 1 addition & 13 deletions apps/prs/react/src/routes/bugs/bug3548.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState } from "react";
import {
GoabBlock,
GoabText,
Expand All @@ -10,21 +10,9 @@ import {
GoabWorkSideMenuItem,
} from "@abgov/react-components";

import v2TokensUrl from "@abgov/design-tokens-v2/dist/tokens.css?url";

export function Bug3548Route() {
const [open, setOpen] = useState(true);

useEffect(() => {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = v2TokensUrl;
document.head.appendChild(link);
return () => {
document.head.removeChild(link);
};
}, []);

function onToggle() {
setOpen((prev) => !prev);
}
Expand Down
Loading
Loading