diff --git a/package-lock.json b/package-lock.json
index d0cc76324..574dd331a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,16 +8,17 @@
"name": "code-sandbox",
"version": "0.0.0",
"dependencies": {
- "@abgov/react-components": "6.5.0",
- "@abgov/ui-components-common": "1.5.0",
- "@abgov/web-components": "1.35.1",
+ "@abgov/react-components": "6.6.0-alpha.4",
+ "@abgov/ui-components-common": "1.6.0-alpha.4",
+ "@abgov/web-components": "1.36.0-alpha.6",
"@faker-js/faker": "^8.3.1",
"highlight.js": "^11.8.0",
"js-cookie": "^3.0.5",
"octokit": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-router-dom": "^6.13.0"
+ "react-router-dom": "^6.13.0",
+ "use-debounce": "^10.0.4"
},
"devDependencies": {
"@types/js-cookie": "^3.0.6",
@@ -67,9 +68,9 @@
}
},
"node_modules/@abgov/react-components": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/@abgov/react-components/-/react-components-6.5.0.tgz",
- "integrity": "sha512-Bnhz35v/kDrT1sAhM0/+3kfbk3za4OR9QWAxNV88nkx/Lj3th22T9D+IZc0ACLLIwRZJdvb5umTTZZn+Tl5Wew==",
+ "version": "6.6.0-alpha.4",
+ "resolved": "https://registry.npmjs.org/@abgov/react-components/-/react-components-6.6.0-alpha.4.tgz",
+ "integrity": "sha512-fKCLEiA492r3Qw94yARh4xhYEds/ze8h/PvoxUJ+wcHD3f5Ud8qyb4/yfmt5odO8E2dZnaAm1MzwNCYtja0MCA==",
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
@@ -77,14 +78,14 @@
}
},
"node_modules/@abgov/ui-components-common": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@abgov/ui-components-common/-/ui-components-common-1.5.0.tgz",
- "integrity": "sha512-QhJX1IIAlR1x5bBj3mmM6MJAcq8TYTSK53vOG5MeNKTu3U0ifgGJHNf8ftA7mL70eGLcCVlYsNpA9l+krEfaTg=="
+ "version": "1.6.0-alpha.4",
+ "resolved": "https://registry.npmjs.org/@abgov/ui-components-common/-/ui-components-common-1.6.0-alpha.4.tgz",
+ "integrity": "sha512-mJanU5U8oy2+o6oVP/bHSg9gMbuv33Ak3EPJpEmC4gvqTWjvoZUsRaxBYGDx8SGr+Bt3paQhcV593uctg5EeWQ=="
},
"node_modules/@abgov/web-components": {
- "version": "1.35.1",
- "resolved": "https://registry.npmjs.org/@abgov/web-components/-/web-components-1.35.1.tgz",
- "integrity": "sha512-A8ee+QjiyUJ1UC9USFH8qI0Dnx0LP8guiKpu/mWYtkmM76cu4iwpEQieD9pbGVcM/0xPjqbs59JEih+AEwMt2w==",
+ "version": "1.36.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/@abgov/web-components/-/web-components-1.36.0-alpha.6.tgz",
+ "integrity": "sha512-c7BaBMHkSlcEBOo2yqEK3ipBea9ERQ9qgdPmOKCoOybQjbc2YZAOZHogxqODFg1IikyaOMzfsWRrTTL/Sr3pPA==",
"peerDependencies": {
"@sveltejs/vite-plugin-svelte": "3.x",
"glob": "10.x",
@@ -598,17 +599,13 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"peer": true,
"dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -620,25 +617,16 @@
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "peer": true,
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"peer": true
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"peer": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -2530,9 +2518,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"peer": true,
"dependencies": {
"balanced-match": "^1.0.0"
@@ -3698,6 +3686,17 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-debounce": {
+ "version": "10.0.5",
+ "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.5.tgz",
+ "integrity": "sha512-Q76E3lnIV+4YT9AHcrHEHYmAd9LKwUAbPXDm7FlqVGDHiSOhX3RDjT8dm0AxbJup6WgOb1YEcKyCr11kBJR5KQ==",
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/vite": {
"version": "5.4.19",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
diff --git a/package.json b/package.json
index a1b770c32..9622fe12e 100644
--- a/package.json
+++ b/package.json
@@ -12,9 +12,9 @@
"prettier": "npx prettier . --write"
},
"dependencies": {
- "@abgov/react-components": "6.5.0",
- "@abgov/ui-components-common": "1.5.0",
- "@abgov/web-components": "1.35.1",
+ "@abgov/react-components": "6.6.0-alpha.4",
+ "@abgov/ui-components-common": "1.6.0-alpha.4",
+ "@abgov/web-components": "1.36.0-alpha.6",
"@faker-js/faker": "^8.3.1",
"highlight.js": "^11.8.0",
"js-cookie": "^3.0.5",
diff --git a/public/images/component-thumbnails/temporary-notification.png b/public/images/component-thumbnails/temporary-notification.png
new file mode 100644
index 000000000..bfdc1a78f
Binary files /dev/null and b/public/images/component-thumbnails/temporary-notification.png differ
diff --git a/public/images/example-thumbnails/indeterminate-progress-search.png b/public/images/example-thumbnails/indeterminate-progress-search.png
new file mode 100644
index 000000000..0d5a8283a
Binary files /dev/null and b/public/images/example-thumbnails/indeterminate-progress-search.png differ
diff --git a/public/images/example-thumbnails/multi-step-process-notifications.png b/public/images/example-thumbnails/multi-step-process-notifications.png
new file mode 100644
index 000000000..2093dd1b2
Binary files /dev/null and b/public/images/example-thumbnails/multi-step-process-notifications.png differ
diff --git a/public/images/example-thumbnails/progress-notification-with-cancel.png b/public/images/example-thumbnails/progress-notification-with-cancel.png
new file mode 100644
index 000000000..365173566
Binary files /dev/null and b/public/images/example-thumbnails/progress-notification-with-cancel.png differ
diff --git a/public/images/example-thumbnails/show-a-notification-with-an-action.png b/public/images/example-thumbnails/show-a-notification-with-an-action.png
new file mode 100644
index 000000000..fd2824956
Binary files /dev/null and b/public/images/example-thumbnails/show-a-notification-with-an-action.png differ
diff --git a/public/images/example-thumbnails/show-a-notification.png b/public/images/example-thumbnails/show-a-notification.png
new file mode 100644
index 000000000..e1d26d8a1
Binary files /dev/null and b/public/images/example-thumbnails/show-a-notification.png differ
diff --git a/public/images/example-thumbnails/show-a-user-progress-when-the-time-is-unknown.png b/public/images/example-thumbnails/show-a-user-progress-when-the-time-is-unknown.png
new file mode 100644
index 000000000..193500499
Binary files /dev/null and b/public/images/example-thumbnails/show-a-user-progress-when-the-time-is-unknown.png differ
diff --git a/public/images/example-thumbnails/show-a-user-progress.png b/public/images/example-thumbnails/show-a-user-progress.png
new file mode 100644
index 000000000..58aa23b17
Binary files /dev/null and b/public/images/example-thumbnails/show-a-user-progress.png differ
diff --git a/src/components/component-properties/ComponentProperties.tsx b/src/components/component-properties/ComponentProperties.tsx
index ac1a12607..3d78c54bb 100644
--- a/src/components/component-properties/ComponentProperties.tsx
+++ b/src/components/component-properties/ComponentProperties.tsx
@@ -70,7 +70,7 @@ interface ComponentPropertyProps {
props: ComponentProperty;
}
-function ComponentProperty({ props }: ComponentPropertyProps) {
+export function ComponentProperty({ props }: ComponentPropertyProps) {
return (
diff --git a/src/components/function-properties/FunctionProperties.tsx b/src/components/function-properties/FunctionProperties.tsx
new file mode 100644
index 000000000..587299765
--- /dev/null
+++ b/src/components/function-properties/FunctionProperties.tsx
@@ -0,0 +1,74 @@
+import { GoabText, GoabContainer } from "@abgov/react-components";
+import { ReactNode, useContext, useEffect, useState } from "react";
+
+import { ComponentProperty as ComponentPropertyType, ComponentProperty } from "@components/component-properties/ComponentProperties.tsx";
+import { LanguageVersionContext } from "@contexts/LanguageVersionContext.tsx";
+
+interface Props {
+ properties: ComponentPropertyType[];
+ oldProperties?: ComponentPropertyType[];
+ heading?: string;
+ subHeading?: ReactNode;
+ codeSnippets?: {
+ angular?: ReactNode;
+ react?: ReactNode;
+ };
+}
+
+export const FunctionProperties = (props: Props) => {
+ const { language, version } = useContext(LanguageVersionContext);
+ const [filteredProperties, setFilteredProperties] = useState
([]);
+
+ const filterBy = (properties: ComponentPropertyType[]) => {
+ const result = properties.filter((child: ComponentPropertyType) => {
+ return !child.lang || child.lang === language;
+ });
+ return result;
+ };
+
+ useEffect(() => {
+ if (version === "old") {
+ setFilteredProperties([...filterBy(props.oldProperties || props.properties)]);
+ return;
+ }
+ setFilteredProperties([...filterBy(props.properties)]);
+ }, [language, version, props.properties, props.oldProperties]);
+
+ function dasherize(str: string): string {
+ return str.replace(" ", "-").toLowerCase();
+ }
+
+ return (
+ <>
+
+ {props.heading || "Function Properties"}
+
+
+ {props.heading || "Function Properties"}
+
+ {props.subHeading && (
+
+ {props.subHeading}
+
+ )}
+
+ {props.codeSnippets && (
+
+ {props.codeSnippets.angular}
+ {props.codeSnippets.react}
+
+ )}
+
+
+
+ {filteredProperties.map((property, index) => (
+
+ ))}
+
+
+ >
+ );
+};
diff --git a/src/examples/show-a-notification-with-an-action.tsx b/src/examples/show-a-notification-with-an-action.tsx
new file mode 100644
index 000000000..63c2008cb
--- /dev/null
+++ b/src/examples/show-a-notification-with-an-action.tsx
@@ -0,0 +1,93 @@
+import { GoabButton } from "@abgov/react-components";
+import { CodeSnippet } from "@components/code-snippet/CodeSnippet.tsx";
+import { Sandbox } from "@components/sandbox";
+import { TemporaryNotification } from "@abgov/ui-components-common";
+
+export const ShowANotificationWithAnAction = () => {
+ const comment = () => {
+ const uuid = TemporaryNotification.show(
+ "Edna Mode commented on your assigned case.",
+ {
+ actionText: "View",
+ action: () => {
+ TemporaryNotification.dismiss(uuid);
+ },
+ }
+ );
+ };
+
+ return (
+
+ Comment
+
+ {
+ TemporaryNotification.dismiss(uuid);
+ },
+ }
+ );
+ }
+ }
+ `}
+ />
+
+ {
+ const uuid = TemporaryNotification.show(
+ "Edna Mode commented on your assigned case.",
+ {
+ actionText: "View",
+ action: () => {
+ TemporaryNotification.dismiss(uuid);
+ },
+ }
+ );
+ };
+ `}
+ />
+
+
+
+ Comment
+ >
+ `}
+ />
+
+
+
+ Comment
+ `}
+ />
+
+ );
+};
+
+export default ShowANotificationWithAnAction;
diff --git a/src/examples/show-a-notification.tsx b/src/examples/show-a-notification.tsx
new file mode 100644
index 000000000..2bcdc9d60
--- /dev/null
+++ b/src/examples/show-a-notification.tsx
@@ -0,0 +1,79 @@
+import { GoabButton } from "@abgov/react-components";
+import { CodeSnippet } from "@components/code-snippet/CodeSnippet.tsx";
+import { Sandbox } from "@components/sandbox";
+import { TemporaryNotification } from "@abgov/ui-components-common";
+
+export const ShowANotification = () => {
+ const save = () => {
+ TemporaryNotification.show("Your application has been saved.", {
+ type: "success"
+ });
+ };
+
+ return (
+
+ Save
+
+
+
+ {
+ await api.save();
+
+ TemporaryNotification.show("Your application has been saved.", {
+ type: "success"
+ });
+ };
+ `}
+ />
+
+
+
+ Save
+ >
+ `}
+ />
+
+
+
+ Save
+ `}
+ />
+
+ );
+};
+
+export default ShowANotification;
diff --git a/src/examples/show-a-user-progress-when-the-time-is-unknown.tsx b/src/examples/show-a-user-progress-when-the-time-is-unknown.tsx
new file mode 100644
index 000000000..280eb7dda
--- /dev/null
+++ b/src/examples/show-a-user-progress-when-the-time-is-unknown.tsx
@@ -0,0 +1,175 @@
+import { GoabButton } from "@abgov/react-components";
+import { CodeSnippet } from "@components/code-snippet/CodeSnippet.tsx";
+import { Sandbox } from "@components/sandbox";
+import { TemporaryNotification } from "@abgov/ui-components-common";
+
+export const ShowAUserProgressWhenTheTimeIsUnknown = () => {
+
+ const sendApi = (isCancelledRef: { current: boolean; timeoutId?: number }) => {
+ return new Promise((_resolve, reject) => {
+ // Simulate search failure after random time (3-6 seconds)
+ const searchTime = Math.random() * 3000 + 3000;
+ const timeoutId = setTimeout(() => {
+ if (isCancelledRef.current) {
+ reject("cancelled");
+ } else {
+ reject("error");
+ }
+ }, searchTime);
+
+ // Store timeout ID for potential cancellation
+ isCancelledRef.timeoutId = timeoutId as unknown as number;
+ });
+ };
+
+ const search = () => {
+ const isCancelledRef: { current: boolean; timeoutId?: number } = { current: false };
+
+ const uuid = TemporaryNotification.show("Searching case management system...", {
+ type: "indeterminate",
+ actionText: "Cancel",
+ action: () => {
+ isCancelledRef.current = true;
+ if (isCancelledRef.timeoutId) {
+ clearTimeout(isCancelledRef.timeoutId);
+ }
+ TemporaryNotification.dismiss(uuid);
+ }
+ });
+
+ sendApi(isCancelledRef).then(() => {
+ // This won't be called since sendApi always rejects
+ }).catch((error) => {
+ if (error !== "cancelled") {
+ TemporaryNotification.show("Could not connect to case history", {
+ type: "failure",
+ duration: "medium",
+ cancelUUID: uuid
+ });
+ }
+ });
+ };
+
+ return (
+
+
+ Search case history
+
+
+ {
+ // perform your API call here
+ }
+
+ async search() {
+ const uuid = TemporaryNotification.show("Searching case management system...", {
+ type: "indeterminate",
+ actionText: "Cancel",
+ action: () => {
+ TemporaryNotification.dismiss(uuid);
+ },
+ });
+
+ const err = await this.searchCMS();
+ if (err) {
+ TemporaryNotification.show("Could not connect to case history", {
+ type: "failure",
+ duration: "medium",
+ cancelUUID: uuid
+ });
+ } else {
+ TemporaryNotification.show("Search complete - 47 records found", {
+ type: "success",
+ duration: "medium",
+ actionText: "View",
+ action: () => {
+ console.log("View search results clicked!");
+ },
+ cancelUUID: uuid,
+ });
+ }
+ }
+ }
+ `}
+ />
+
+ => {
+ // perform your API call here
+ };
+
+ const search = async () => {
+ const uuid = TemporaryNotification.show("Searching case management system...", {
+ type: "indeterminate",
+ actionText: "Cancel",
+ action: () => {
+ TemporaryNotification.dismiss(uuid);
+ },
+ });
+
+ const err = await searchCMS();
+ if (err) {
+ TemporaryNotification.show("Could not connect to case history", {
+ type: "failure",
+ duration: "medium",
+ cancelUUID: uuid
+ });
+ } else {
+ TemporaryNotification.show("Search complete - 47 records found", {
+ type: "success",
+ duration: "medium",
+ actionText: "View",
+ action: () => {
+ console.log("View search results clicked!");
+ },
+ cancelUUID: uuid,
+ });
+ }
+ };
+ `}
+ />
+
+
+
+
+ Search case history
+
+ >
+ `}
+ />
+
+
+
+
+ Search case history
+
+ `}
+ />
+
+ );
+};
+
+export default ShowAUserProgressWhenTheTimeIsUnknown;
diff --git a/src/examples/show-a-user-progress.tsx b/src/examples/show-a-user-progress.tsx
new file mode 100644
index 000000000..70d6c39b1
--- /dev/null
+++ b/src/examples/show-a-user-progress.tsx
@@ -0,0 +1,208 @@
+import { GoabButton } from "@abgov/react-components";
+import { CodeSnippet } from "@components/code-snippet/CodeSnippet.tsx";
+import { Sandbox } from "@components/sandbox";
+import { TemporaryNotification } from "@abgov/ui-components-common";
+
+export const ShowAUserProgress = () => {
+
+ const sendApi = (progressCallback: (progress: number) => void, isCancelledRef: { current: boolean }) => {
+ return new Promise((resolve, reject) => {
+ let progress = 0;
+ const interval = setInterval(() => {
+ if (isCancelledRef.current) {
+ clearInterval(interval);
+ reject("cancelled");
+ return;
+ }
+
+ progress += 5;
+ progressCallback(progress);
+
+ if (progress >= 100) {
+ clearInterval(interval);
+ resolve("success");
+ }
+ }, 200);
+ });
+ };
+
+ const downloadReport = () => {
+ const isCancelledRef = { current: false };
+
+ const uuid = TemporaryNotification.show("Downloading report D-23459", {
+ type: "progress",
+ actionText: "Cancel",
+ action: () => {
+ isCancelledRef.current = true;
+ TemporaryNotification.dismiss(uuid);
+ console.log("Download cancelled");
+ }
+ });
+
+ TemporaryNotification.setProgress(uuid, 0);
+
+ const updateProgress = (progress: number) => {
+ TemporaryNotification.setProgress(uuid, progress);
+
+ if (progress >= 100) {
+ setTimeout(() => {
+ TemporaryNotification.show("Report downloaded", {
+ type: "success",
+ duration: "medium",
+ actionText: "View",
+ action: () => {
+ console.log("View report clicked!");
+ },
+ cancelUUID: uuid
+ });
+ }, 300);
+ }
+ };
+
+ sendApi(updateProgress, isCancelledRef)
+ .catch(error => {
+ if (error !== "cancelled") {
+ TemporaryNotification.dismiss(uuid);
+ }
+ });
+ };
+
+ return (
+
+
+ Download report
+
+
+ {
+ // Perform your API call here with progress tracking
+ // Update progress as download progresses (0-100): setProgress(notificationUuid, 20) means 20% complete
+ TemporaryNotification.setProgress(notificationUuid, 25);
+ // ... continue API work ...
+ TemporaryNotification.setProgress(notificationUuid, 50);
+ // ... continue API work ...
+ TemporaryNotification.setProgress(notificationUuid, 75);
+ // ... complete API work ...
+ TemporaryNotification.setProgress(notificationUuid, 100);
+ }
+
+ async download() {
+ const uuid = TemporaryNotification.show("Downloading report D-23459", {
+ type: "progress",
+ actionText: "Cancel",
+ action: () => {
+ TemporaryNotification.dismiss(uuid);
+ },
+ });
+
+ const err = await this.downloadReport(uuid);
+
+ if (err) {
+ TemporaryNotification.show("Download failed", {
+ type: "error",
+ duration: "medium",
+ cancelUUID: uuid
+ });
+ } else {
+ TemporaryNotification.show("Report downloaded", {
+ type: "success",
+ duration: "medium",
+ actionText: "View",
+ action: () => {
+ console.log("View report clicked!");
+ },
+ cancelUUID: uuid,
+ });
+ }
+ }
+ }
+ `}
+ />
+
+ => {
+ // Perform your API call here with progress tracking
+ // Update progress as download progresses (0-100): setProgress(notificationUuid, 20) means 20% complete
+ TemporaryNotification.setProgress(notificationUuid, 25);
+ // ... continue API work ...
+ TemporaryNotification.setProgress(notificationUuid, 50);
+ // ... continue API work ...
+ TemporaryNotification.setProgress(notificationUuid, 75);
+ // ... complete API work ...
+ TemporaryNotification.setProgress(notificationUuid, 100);
+ };
+
+ const downloadReport = async () => {
+ const uuid = TemporaryNotification.show("Downloading report D-23459", {
+ type: "progress",
+ actionText: "Cancel",
+ action: () => {
+ TemporaryNotification.dismiss(uuid);
+ },
+ });
+
+ const err = await downloadReportAPI(uuid);
+
+ if (err) {
+ TemporaryNotification.show("Download failed", {
+ type: "error",
+ duration: "medium",
+ cancelUUID: uuid
+ });
+ } else {
+ TemporaryNotification.show("Report downloaded", {
+ type: "success",
+ duration: "medium",
+ actionText: "View",
+ action: () => {
+ console.log("View report clicked!");
+ },
+ cancelUUID: uuid,
+ });
+ }
+ };
+ `}
+ />
+
+
+
+
+ Download report
+
+ >
+ `}
+ />
+
+
+
+ Download report
+ `}
+ />
+
+ );
+};
+
+export default ShowAUserProgress;
diff --git a/src/examples/temporary-notification/TemporaryNotificationExamples.tsx b/src/examples/temporary-notification/TemporaryNotificationExamples.tsx
new file mode 100644
index 000000000..eab001668
--- /dev/null
+++ b/src/examples/temporary-notification/TemporaryNotificationExamples.tsx
@@ -0,0 +1,37 @@
+import { SandboxHeader } from "@components/sandbox/sandbox-header/sandboxHeader.tsx";
+import { ShowANotification } from "@examples/show-a-notification.tsx";
+import { ShowAUserProgress } from "@examples/show-a-user-progress.tsx";
+import { ShowAUserProgressWhenTheTimeIsUnknown } from "@examples/show-a-user-progress-when-the-time-is-unknown.tsx";
+import { ShowANotificationWithAnAction } from "@examples/show-a-notification-with-an-action.tsx";
+
+export const TemporaryNotificationExamples = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default TemporaryNotificationExamples;
diff --git a/src/routes/components/Components.tsx b/src/routes/components/Components.tsx
index 197fe1405..dc7abe2b5 100644
--- a/src/routes/components/Components.tsx
+++ b/src/routes/components/Components.tsx
@@ -31,7 +31,7 @@ export function Components() {
};
return (
- {componentName}
+ {componentName}
);
};
@@ -62,6 +62,7 @@ export function Components() {
Notification banner
Progress indicator
Skeleton loader
+ {newComponentLabel("Temporary notification")}
Tooltip
diff --git a/src/routes/components/Modal.tsx b/src/routes/components/Modal.tsx
index ccdd5b582..d2a504652 100644
--- a/src/routes/components/Modal.tsx
+++ b/src/routes/components/Modal.tsx
@@ -6,7 +6,7 @@ import {
GoabModal,
GoabModalProps,
GoabTab,
- GoabTabs,
+ GoabTabs
} from "@abgov/react-components";
import { ComponentBinding, Sandbox } from "@components/sandbox";
import { useContext, useEffect, useState } from "react";
@@ -291,7 +291,6 @@ export default function ModalPage() {
figmaLink={FIGMA_LINK}
githubLink="Modal"
/>
-
diff --git a/src/routes/components/TemporaryNotification.tsx b/src/routes/components/TemporaryNotification.tsx
index 2e4b9ee68..8f4237181 100644
--- a/src/routes/components/TemporaryNotification.tsx
+++ b/src/routes/components/TemporaryNotification.tsx
@@ -1,82 +1,498 @@
+import { useState, useContext } from "react";
+import {
+ GoabBadge,
+ GoabButton,
+ GoabTab,
+ GoabTabs,
+ GoabTemporaryNotificationCtrl,
+} from "@abgov/react-components";
import { Category, ComponentHeader } from "@components/component-header/ComponentHeader.tsx";
-import { ComponentBinding, Sandbox } from "@components/sandbox";
-import { useState } from "react";
import {
ComponentProperties,
ComponentProperty,
} from "@components/component-properties/ComponentProperties.tsx";
-
-import { GoabBadge, GoabTab, GoabTabs, GoabCalloutProps } from "@abgov/react-components";
+import { ComponentContent } from "@components/component-content/ComponentContent";
+import { TestIdProperty } from "@components/component-properties/common-properties.ts";
+import { DesignEmpty } from "@components/empty-states/design-empty/DesignEmpty.tsx";
+import { AccessibilityEmpty } from "@components/empty-states/accessibility-empty/AccessibilityEmpty.tsx";
+import { CodeSnippet } from "@components/code-snippet/CodeSnippet.tsx";
+import { Sandbox, ComponentBinding } from "@components/sandbox";
+import { LanguageVersionContext } from "@contexts/LanguageVersionContext.tsx";
+import { OldComponentBanner } from "@components/old-component-banner/OldComponentBanner.tsx";
+import { TemporaryNotification } from "@abgov/ui-components-common";
+import { TemporaryNotificationExamples } from "@examples/temporary-notification/TemporaryNotificationExamples.tsx";
+import { FunctionProperties } from "@components/function-properties/FunctionProperties.tsx";
// == Page props ==
const componentName = "Temporary notification";
-const description = "A temporary notification showing a process started or completed.";
+const description = "Temporary notifications provide brief feedback about an action or event. They appear temporarily and can include an action for users to take.";
+const category = Category.FEEDBACK_AND_ALERTS;
const relatedComponents = [
{ link: "/components/callout", name: "Callout" },
{ link: "/components/notification-banner", name: "Notification banner" }
];
-type ComponentPropsType = GoabCalloutProps;
-type CastingType = {
- // add any required props here
- [key: string]: unknown;
-};
-
-export default function TEMPLATE_Page() {
- const [_componentProps, setComponentProps] = useState({});
+const FIGMA_LINK = "https://www.figma.com/design/3pb2IK8s2QUqWieH79KdN7/%E2%9D%96-Component-library-%7C-DDD?node-id=64940-255303";
+const ACCESSIBILITY_FIGMA_LINK = "https://www.figma.com/design/3pb2IK8s2QUqWieH79KdN7/%E2%9D%96-Component-library-%7C-DDD?node-id=64940-255303";
+export default function TemporaryNotificationPage() {
+ const { version, language } = useContext(LanguageVersionContext);
+
const [componentBindings, setComponentBindings] = useState([
+ {
+ label: "Horizontal position",
+ type: "dropdown",
+ name: "horizontalPosition",
+ options: ["left", "center", "right"],
+ value: "center",
+ },
+ {
+ label: "Message",
+ type: "string",
+ name: "message",
+ value: "This is a notification message",
+ width: "40ch",
+ },
{
label: "Type",
- type: "list",
+ type: "dropdown",
name: "type",
- options: ["basic"],
+ options: ["basic", "success", "failure", "indeterminate", "progress"],
value: "basic",
},
- // ...
+ {
+ label: "Duration",
+ type: "dropdown",
+ name: "duration",
+ options: ["short", "medium", "long"],
+ value: "short",
+ },
+ {
+ label: "Action text",
+ type: "string",
+ name: "actionText",
+ value: "",
+ helpText: "Optional action button text",
+ },
]);
- const componentProperties: ComponentProperty[] = [
+ const handleShowNotification = () => {
+ const props = componentBindings.reduce((acc, binding) => {
+ if (binding.value !== "" && binding.value !== undefined) {
+ acc[binding.name] = binding.value;
+ }
+ return acc;
+ }, {} as Record);
+
+ const options: any = {
+ type: props.type || "basic",
+ duration: props.duration !== undefined ? props.duration : 4000,
+ };
+
+ let notificationId: string;
+
+ if (props.actionText) {
+ options.actionText = props.actionText;
+ options.action = () => {
+ if (notificationId) {
+ TemporaryNotification.dismiss(notificationId);
+ }
+ TemporaryNotification.show("Action performed!", { type: "success", duration: 2000 });
+ };
+ }
+
+ notificationId = TemporaryNotification.show(props.message || "Default message", options);
+ };
+
+ function onSandboxChange(bindings: ComponentBinding[]) {
+ setComponentBindings(bindings);
+ }
+
+ const controllerProperties: ComponentProperty[] = [
{
- name: "type",
- type: "basic",
- description: "",
+ name: "verticalPosition",
+ type: "SnackbarVerticalPosition (top | bottom)",
+ defaultValue: "bottom",
+ description: "Vertical position of notifications on the screen.",
+ },
+ {
+ name: "horizontalPosition",
+ type: "SnackbarHorizontalPosition (left | center | right)",
+ defaultValue: "center",
+ description: "Horizontal position of notifications on the screen.",
},
- // ...
+ TestIdProperty,
];
- function onSandboxChange(bindings: ComponentBinding[], props: Record) {
- setComponentBindings(bindings);
- setComponentProps(props as CastingType);
- }
+ const showMethodProperties: ComponentProperty[] = [
+ {
+ name: "message",
+ type: "string",
+ description: "The message to display in the notification.",
+ },
+ {
+ name: "options",
+ type: "Partial",
+ description: "Optional configuration object for the notification.",
+ },
+ {
+ name: "options.type",
+ type: "GoabTemporaryNotificationType (basic | success | failure | indeterminate | progress)",
+ defaultValue: "basic",
+ description: "The type of notification which determines styling and icon.",
+ },
+ {
+ name: "options.duration",
+ type: "long | medium | short | number",
+ defaultValue: "short",
+ description: "Duration before auto-dismissal. Use 'short' (~3s), 'medium' (~4s), 'long' (~6s), or custom milliseconds.",
+ },
+ {
+ name: "options.actionText",
+ type: "string",
+ description: "Text for the action button. When provided, displays an action button.",
+ },
+ {
+ name: "options.action",
+ type: "() => void",
+ description: "Function to execute when the action button is clicked.",
+ },
+ {
+ name: "options.cancelUUID",
+ type: "string",
+ description: "UUID of an existing notification to cancel when showing this one.",
+ },
+ ];
+
+ const dismissMethodProperties: ComponentProperty[] = [
+ {
+ name: "uuid",
+ type: "string",
+ description: "The UUID of the notification to dismiss. This is the value returned by TemporaryNotification.show().",
+ },
+ ];
+
+ const progressMethodProperties: ComponentProperty[] = [
+ {
+ name: "uuid",
+ type: "string",
+ description: "The UUID of the progress notification to update. This is the value returned by TemporaryNotification.show().",
+ },
+ {
+ name: "progress",
+ type: "number",
+ description: "The progress percentage (0-100) to display in the progress notification.",
+ },
+ ];
return (
<>
-
-
-
- <>>
- {/* */}
-
-
-
-
-
- Design guidelines
-
- >
- }
- >
-
+ {version === "old" && }
+
+ {version === "new" && (
+
+
+
+
+ Component
+
+
+
+ b.name === 'message')?.value}", {
+ type: "${componentBindings.find(b => b.name === 'type')?.value}",
+ duration: "${componentBindings.find(b => b.name === 'duration')?.value}"${componentBindings.find(b => b.name === 'actionText')?.value ? `,
+ actionText: "${componentBindings.find(b => b.name === 'actionText')?.value}",
+ action: () => {
+ TemporaryNotification.show("Action performed!", { type: "success", duration: 2000 });
+ }` : ''}
+ });
+ }
+ }`}
+ />
+
+
+
+
+
+ Show Notification
+ `}
+ />
+
+ {
+ TemporaryNotification.show("${componentBindings.find(b => b.name === 'message')?.value}", {
+ type: "${componentBindings.find(b => b.name === 'type')?.value}",
+ duration: "${componentBindings.find(b => b.name === 'duration')?.value}"${componentBindings.find(b => b.name === 'actionText')?.value ? `,
+ actionText: "${componentBindings.find(b => b.name === 'actionText')?.value}",
+ action: () => {
+ TemporaryNotification.show("Action performed!", { type: "success", duration: 2000 });
+ }` : ''}
+ });
+ };`}
+ />
+
+
+
+
+ Show Notification
+
+ >`}
+ />
+
+ Show Notification
+ b.name === 'verticalPosition')?.value as any || "bottom"}
+ horizontalPosition={componentBindings.find(b => b.name === 'horizontalPosition')?.value as any || "center"}
+ />
+
+
+
+ show(message, options) is a helper function to display temporary notifications in your component. View the table below to learn about the available options.>}
+ properties={showMethodProperties}
+ codeSnippets={{
+ angular: <>
+
+
+ Show Notification
+ `}
+ />
+ >,
+ react: {
+ TemporaryNotification.show("Your message here", {
+ type: "success",
+ duration: "medium",
+ });
+ };
+
+ return <>
+
+ Show Notification
+ >
+ }`}
+ />
+ }}
+ />
+ dismiss(uuid) is a helper function to hide a notification in your component. View the table below to learn about the available options.>}
+ properties={dismissMethodProperties}
+ codeSnippets={{
+ angular: <>
+
+
+ Save
+ `}
+ />
+ >,
+ react:
+
+ Save
+ >
+ }`}
+ />
+ }}
+ />
+ setProgress(uuid, progress) is a helper function to update the progress of a progress notification. View the table below to learn about the available options.>}
+ properties={progressMethodProperties}
+ codeSnippets={{
+ angular: <>
+
+
+ Save
+ `}
+ />
+ >,
+ react:
+
+ Save
+ >
+ }`}
+ />
+ }}
+ />
+
+
+
+ Examples
+
+ >
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
>
);
}
diff --git a/src/routes/root.css b/src/routes/root.css
index c4d61fac5..227f14c26 100644
--- a/src/routes/root.css
+++ b/src/routes/root.css
@@ -26,8 +26,8 @@
Side Menu
==================*/
.side-menu {
- min-width: 180px;
- width: 180px;
+ min-width: 220px;
+ width: 220px;
border-right: 1px solid var(--goa-color-greyscale-200);
padding-bottom: var(--goa-space-l);
}
diff --git a/src/routes/root.tsx b/src/routes/root.tsx
index d774eb85a..576a1ef84 100644
--- a/src/routes/root.tsx
+++ b/src/routes/root.tsx
@@ -4,7 +4,8 @@ import {
GoabAppFooterNavSection,
GoabAppHeader,
GoabMicrositeHeader,
- GoabOneColumnLayout
+ GoabOneColumnLayout,
+ GoabTemporaryNotificationCtrl
} from "@abgov/react-components";
import { useEffect, useState } from "react";
import { Link, Outlet, useLocation } from "react-router-dom";
@@ -42,6 +43,12 @@ export default function Root() {
const showNotification =
location.pathname.startsWith("/components") || location.pathname.startsWith("/examples");
const [visible, setVisibility] = useState(false);
+
+ // to show temporary notification on examples route, except temporary-notification playground which needs playground bindings
+ const shouldRenderTemporaryNotificationCtrl = !(
+ location.pathname.includes("/temporary-notification") &&
+ (location.hash === "#tab-0" || location.hash === "" || !location.hash.includes("#tab-"))
+ );
useEffect(() => {
@@ -100,6 +107,13 @@ export default function Root() {
+
+ {shouldRenderTemporaryNotificationCtrl && (
+
+ )}
);
}
diff --git a/src/versioned-router.tsx b/src/versioned-router.tsx
index 33274e2d1..9cac192f0 100644
--- a/src/versioned-router.tsx
+++ b/src/versioned-router.tsx
@@ -56,6 +56,7 @@ import FilterChipPage from "@routes/components/FilterChip.tsx";
import TextPage from "@routes/components/Text.tsx";
import { DrawerPage } from "@routes/components/Drawer.tsx";
import LinkPage from "@routes/components/Link.tsx";
+import TemporaryNotificationPage from "@routes/components/TemporaryNotification.tsx";
const ComponentRoute: React.FC<{
versionedPaths: Record
;
@@ -120,6 +121,7 @@ export const ComponentsRouter = () => {
"spacer": ,
"table": ,
"tabs": ,
+ "temporary-notification": ,
"text": ,
"text-area": ,
"tooltip": ,