-
Interactive example
-
- {(state === "good" && (
-
- )) ||
- (state === "bad" && (
+ showStackblitzBtn,
+ projectTitle,
+ projectDescription,
+ type,
+}) => {
+ const pageMetadata = React.useContext(PageMetadataContext);
+
+ const getTypeOfProject = (snippet: Snippet) => {
+ if (type === "pattern") {
+ return " pattern";
+ }
+ if (snippet.language === "React") {
+ return " component";
+ }
+ return "";
+ };
+
+ return (
+
+
Interactive example
+
+ {(state === "good" && (
- ))}
- {children}
+ )) ||
+ (state === "bad" && (
+
+ ))}
+ {children}
+
+ {snippets && (
+
+
+
+ {snippets.map((snippet, index) => (
+ {snippet.language}
+ ))}
+
+
+ {snippets.map((snippet, index) => (
+
+
+
+ ))}
+
+ )}
- {snippets && (
-
-
-
- {snippets.map((snippet, index) => (
- {snippet.language}
- ))}
-
-
- {snippets.map((snippet, index) => (
-
-
-
- ))}
-
- )}
-
-);
+ );
+};
export default ComponentPreview;
diff --git a/src/content/static/components/CookiesData/index.tsx b/src/content/static/components/CookiesData/index.tsx
index e0bc43306..345597f28 100644
--- a/src/content/static/components/CookiesData/index.tsx
+++ b/src/content/static/components/CookiesData/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
import { debounce } from "../../../../utils/helpers";
import CookiesCards from "./CookiesCards";
@@ -31,14 +31,18 @@ const CookiesData = ({ headers, data, caption }: CookiesDataProps) => {
const [viewportWidth, setViewportWidth] = useState(defaultViewportWidth);
- React.useEffect(() => {
- window.addEventListener(
- "resize",
- debounce(() => setViewportWidth(window.innerWidth))
- );
+ useEffect(() => {
+ const handleResize = debounce(() => setViewportWidth(window.innerWidth));
+
+ window.addEventListener("resize", handleResize);
+
+ // Cleanup function
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
}, []);
- if (viewportWidth > 991) {
+ if (viewportWidth > 992) {
return
;
}
diff --git a/src/content/structured/patterns/components/StackblitzButton/index.tsx b/src/content/structured/patterns/components/StackblitzButton/index.tsx
new file mode 100644
index 000000000..30b9cc92e
--- /dev/null
+++ b/src/content/structured/patterns/components/StackblitzButton/index.tsx
@@ -0,0 +1,127 @@
+import React, { useEffect, useState } from "react";
+import sdk from "@stackblitz/sdk";
+import {
+ createIndexTsx,
+ createReactIndexHTML,
+ createWebComponentsIndexHTML,
+ packageJson,
+ tsConfig,
+ viteConfig,
+} from "./stackblitz-helpers";
+import { StackblitzLogo } from "../../../../../assets/svg";
+import { debounce } from "../../../../../utils/helpers";
+
+export type StackblitzProps = {
+ codeSnippet?: string;
+ isWebComponents?: boolean;
+ projectTitle: string;
+ projectDescription?: string;
+};
+
+const StackblitzButton: React.FC
= ({
+ codeSnippet,
+ isWebComponents,
+ projectTitle,
+ projectDescription,
+}) => {
+ let defaultViewportWidth = 0;
+
+ if (typeof window !== "undefined") {
+ defaultViewportWidth = window.innerWidth;
+ }
+
+ const [viewportWidth, setViewportWidth] =
+ useState(defaultViewportWidth);
+
+ const isLargeViewport: boolean = viewportWidth > 992;
+
+ useEffect(() => {
+ const handleResize = debounce(() => setViewportWidth(window.innerWidth));
+
+ window.addEventListener("resize", handleResize);
+
+ // Cleanup function
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, []);
+
+ const createStackblitzProject = () => {
+ const files: { [key: string]: string } = {};
+ const isWebComponentsInternal: boolean =
+ isWebComponents !== undefined ? isWebComponents : true;
+ const isJSX: boolean = true;
+ // Add functionality to manage whether Stackblitz project should use TypeScript or JavaScript
+ const ext = isJSX ? "jsx" : "tsx";
+
+ if (codeSnippet !== undefined && codeSnippet !== "") {
+ if (isWebComponents) {
+ files[`index.html`] = createWebComponentsIndexHTML(codeSnippet);
+ } else {
+ files[`src/index.${ext}`] = codeSnippet;
+ }
+ }
+
+ // Define the index.tsx content for a React app
+ const indexTsx = createIndexTsx(projectTitle, true);
+
+ // Change file structure for React code examples
+ if (
+ (isWebComponents !== undefined && !isWebComponents) ||
+ !isWebComponentsInternal
+ ) {
+ files[`src/app.${ext}`] = files[`src/index.${ext}`];
+ files[`src/index.${ext}`] = indexTsx;
+ files[`index.html`] = createReactIndexHTML(ext);
+ files["vite.config.js"] = viteConfig;
+ if (ext === "tsx") {
+ files["tsconfig.json"] = tsConfig;
+ }
+ }
+
+ files["package.json"] = JSON.stringify(
+ packageJson(projectTitle, isWebComponentsInternal, ext),
+ null,
+ 2
+ );
+
+ const description =
+ projectDescription === undefined || projectDescription === ""
+ ? ""
+ : projectDescription;
+
+ sdk.openProject(
+ {
+ title: `ICDS ${projectTitle}`,
+ description,
+ files,
+ template: "node",
+ },
+ {
+ openFile: isWebComponentsInternal ? [`index.html`] : [`src/app.${ext}`],
+ }
+ );
+ };
+
+ return (
+ createStackblitzProject()}
+ >
+ {isLargeViewport && (
+ <>
+
+
+
+ Stackblitz
+ >
+ )}
+ {!isLargeViewport && }
+
+ );
+};
+
+export default StackblitzButton;
diff --git a/src/content/structured/patterns/components/StackblitzButton/stackblitz-helpers.ts b/src/content/structured/patterns/components/StackblitzButton/stackblitz-helpers.ts
new file mode 100644
index 000000000..7b42bd0ad
--- /dev/null
+++ b/src/content/structured/patterns/components/StackblitzButton/stackblitz-helpers.ts
@@ -0,0 +1,272 @@
+import kebabCase from "lodash.kebabcase";
+import startCase from "lodash.startcase";
+
+const designSystemPackageJson = require("../../../../../../package.json");
+
+export const createIndexTsx = (
+ componentName: string,
+ isCodeSnippet?: boolean
+) => {
+ const component = isCodeSnippet
+ ? startCase(componentName.replace(/\(.*?\)/g, "")).replace(/\s/g, "")
+ : componentName;
+ return `import { StrictMode } from 'react';
+ import { createRoot } from 'react-dom/client';
+ import { BrowserRouter } from 'react-router-dom';
+
+ import ${component} from './app';
+
+ const root = createRoot(document.getElementById('root'));
+
+ root.render(
+
+
+ <${component} />
+
+
+ );`;
+};
+
+export const createReactIndexHTML = (ext: string) => `
+
+
+
+ Vite + React + TS
+
+
+
+
+
+ `;
+
+export const packageJson = (
+ projectTitle: string,
+ isWebComponents: boolean,
+ fileExtension: "jsx" | "tsx"
+) => {
+ const dependenciesArray = isWebComponents
+ ? []
+ : [
+ ["@mdi/js", "^7.4.47"],
+ [
+ "@ukic/fonts",
+ `${designSystemPackageJson.dependencies["@ukic/fonts"]}`,
+ ],
+ [
+ "@ukic/react",
+ `${designSystemPackageJson.dependencies["@ukic/react"]}`,
+ ],
+ ["react", "^18.2.0"],
+ ["react-dom", "^18.2.0"],
+ ["react-jss", "^10.10.0"],
+ ["react-router-dom", "^6.22.0"],
+ ];
+
+ if (fileExtension === "tsx") {
+ dependenciesArray.splice(3, 0, [
+ "@ukic/web-components",
+ `${designSystemPackageJson.dependencies["@ukic/web-components"]}`,
+ ]);
+ }
+
+ const dependencies = Object.fromEntries(dependenciesArray);
+
+ const reactDevDependencies = {
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "@vitejs/plugin-react": "^4.2.1",
+ };
+ const devDependencies = { vite: "^5.0.12" };
+
+ return {
+ name: `icds-${kebabCase(projectTitle)}`,
+ version: "0.0.0",
+ private: true,
+ scripts: {
+ dev: "vite",
+ build: "vite build",
+ preview: "vite preview",
+ prettier: "prettier --write .",
+ },
+ dependencies,
+ devDependencies: isWebComponents
+ ? { ...devDependencies }
+ : { ...devDependencies, ...reactDevDependencies },
+ stackblitz: {
+ startCommand:
+ "npm i --save-dev prettier && npm run prettier && npm run dev",
+ },
+ };
+};
+
+export const tsConfig = `{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "lib": ["DOM", "ES2022"],
+ "moduleResolution": "node",
+ "target": "ES2022"
+ }
+ }`;
+
+export const viteConfig = `import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})
+`;
+
+export const createWebComponentsIndexHTML = (
+ codeSnippet: string
+) => `
+
+ Home
+
+
+
+
+
+
+ ${codeSnippet}
+
+
+
+ `;
+
+export const createReactAppTsx = (
+ codeSnippet: string,
+ componentName: string,
+ fileExtension: "jsx" | "tsx",
+ isPattern: boolean = false
+): string => {
+ // Helper function to find uses of ICDS components and MDI in code snippet
+ const getImports = (
+ tag: string | undefined,
+ isMDI: boolean = false,
+ isPatternInternal: boolean = false
+ ) => {
+ let regex: RegExp;
+ if (isMDI) {
+ regex = new RegExp(`{${tag}[^}]*}`, "g");
+ } else if (isPatternInternal) {
+ regex = /(?!(<|<\/|\.))\bIc[A-Za-z]+/g;
+ } else {
+ regex = new RegExp(`<${tag}[^>]*>`, "g");
+ }
+
+ return isPatternInternal
+ ? codeSnippet.match(regex) || []
+ : (codeSnippet.match(regex) || []).map((match) =>
+ match
+ .slice(isPatternInternal ? 0 : 1, match.indexOf(isMDI ? "}" : " "))
+ .trim()
+ );
+ };
+
+ // Helper function to sort and remove duplicates
+ const sortAndRemoveDuplicates = (matches: string[]) =>
+ Array.from(new Set(matches)).sort();
+
+ // Get matches for "Ic", "SlottedSVG", and "mdi".
+ // Sort and remove duplicates
+ const sortedICDSComponents = sortAndRemoveDuplicates([
+ ...getImports("Ic"),
+ ...getImports("SlottedSVG"),
+ ]);
+ const sortedMDIcons = sortAndRemoveDuplicates(getImports("mdi", true));
+ const component = startCase(componentName.replace(/\(.*?\)/g, "")).replace(
+ /\s/g,
+ ""
+ );
+ let getIcTypes: string[] = [];
+ if (isPattern) {
+ const allIcMatches = getImports(undefined, false, true);
+ const allIcComponents = getImports("Ic");
+ getIcTypes = allIcMatches
+ .filter((item: string) => !allIcComponents.includes(item))
+ .concat(
+ allIcComponents.filter((item: string) => !allIcMatches.includes(item))
+ )
+ .sort();
+ }
+
+ // Check if codeSnippet contains "return(" or "return ("
+ const containsReturn = /return\s*\(/g.test(codeSnippet);
+
+ // Check if codeSnippet contains "createUseStyles" from 'react-jss'
+ const containsCreateUseStyles = /createUseStyles\(\{/.test(codeSnippet);
+
+ // Check if codeSnippet contains any React hooks
+ const getReactHooks = (code: string) => {
+ const hooks = ["useState", "useRef", "useEffect"];
+ const foundHooks = hooks.filter((hook) => code.includes(hook));
+ return foundHooks;
+ };
+
+ const reactHookImports = getReactHooks(codeSnippet);
+
+ // Create React import statement
+ let reactImportStatement = "import React";
+ const reactImports = [];
+ if (fileExtension === "tsx") {
+ reactImports.push("FC");
+ }
+ if (reactHookImports.length > 0) {
+ reactImports.push(...reactHookImports);
+ }
+ if (reactImports.length > 0) {
+ reactImportStatement += `, { ${reactImports.join(", ")} }`;
+ }
+ reactImportStatement += " from 'react';";
+
+ // Conditionally render "return(" in the returned output string
+ const returnStatement = containsReturn ? "" : "return(";
+
+ let importedICDSComponents;
+ if (sortedICDSComponents.length > 3) {
+ importedICDSComponents = `
+ ${sortedICDSComponents.join(",\n ")}\n`;
+ } else {
+ importedICDSComponents = `${sortedICDSComponents.join(", ")} `;
+ }
+
+ return `${reactImportStatement}
+import { ${importedICDSComponents}} from '@ukic/react';
+${
+ fileExtension === "tsx" && isPattern && getIcTypes.length > 0
+ ? `import type { ${getIcTypes.join(", ")} } from '@ukic/web-components';`
+ : ""
+}
+${
+ sortedMDIcons.length > 0
+ ? `import { ${sortedMDIcons.join(", ")} } from '@mdi/js';`
+ : ""
+}
+${
+ containsCreateUseStyles
+ ? `import { createUseStyles } from 'react-jss';
+`
+ : ""
+}
+import "@ukic/fonts/dist/fonts.css";
+import "@ukic/react/dist/core/core.css";
+import "@ukic/react/dist/core/normalize.css";
+
+const ${component}${fileExtension === "tsx" ? `: FC` : ""} = () => {
+ ${returnStatement}
+ ${codeSnippet}
+ ${containsReturn ? "" : ")"}};
+export default ${component};`;
+};
diff --git a/src/context/PageMetadata.tsx b/src/context/PageMetadata.tsx
new file mode 100644
index 000000000..155bb7e68
--- /dev/null
+++ b/src/context/PageMetadata.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+
+interface PageMetadataContextProps {
+ pageTitle: string;
+}
+
+const PageMetadataContext = React.createContext({
+ pageTitle: "",
+});
+
+export default PageMetadataContext;
diff --git a/src/templates/CoreTemplate/index.tsx b/src/templates/CoreTemplate/index.tsx
index 45406a6b7..51bc93bf0 100644
--- a/src/templates/CoreTemplate/index.tsx
+++ b/src/templates/CoreTemplate/index.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useMemo } from "react";
import { MDXRenderer } from "gatsby-plugin-mdx";
import clsx from "clsx";
@@ -20,6 +20,7 @@ import WrappedH4 from "../../components/WrappedH4";
import WrappedLi from "../../components/WrappedLi";
import WrappedLink from "../../components/WrappedLink";
import WrappedP from "../../components/WrappedP";
+import PageMetadataContext from "../../context/PageMetadata";
const { MDXProvider } = require("@mdx-js/react");
@@ -52,6 +53,11 @@ const CoreMDXLayout: React.FC = ({
DoubleDoDontCaution,
};
+ const pageTitleValue = useMemo(
+ () => ({ pageTitle: mdx.frontmatter.title }),
+ []
+ );
+
return (
@@ -65,7 +71,9 @@ const CoreMDXLayout: React.FC
= ({
-
{children}
+
+ {children}
+
{mdx.fields.navSection === "components" && (