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.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": ,