diff --git a/apps/website/hooks/useCopyToClipboard.tsx b/apps/website/hooks/useCopyToClipboard.tsx new file mode 100644 index 0000000000..27d6fe3256 --- /dev/null +++ b/apps/website/hooks/useCopyToClipboard.tsx @@ -0,0 +1,20 @@ +import { useToast } from "@dxc-technology/halstack-react"; + +const useCopyToClipboard = () => { + const toast = useToast(); + + const handleCopy = (text: string) => { + navigator.clipboard + .writeText(text) + .then(() => { + toast.success({ message: "Code copied to the clipboard." }); + }) + .catch(() => { + toast.warning({ message: "Failed to copy the text to the clipboard." }); + }); + }; + + return handleCopy; +}; + +export default useCopyToClipboard; diff --git a/apps/website/pages/_app.tsx b/apps/website/pages/_app.tsx index 5f63224d90..942240c825 100644 --- a/apps/website/pages/_app.tsx +++ b/apps/website/pages/_app.tsx @@ -2,7 +2,7 @@ import { ReactElement, ReactNode, useMemo, useState } from "react"; import type { NextPage } from "next"; import type { AppProps } from "next/app"; import Head from "next/head"; -import { DxcApplicationLayout, DxcLink, DxcToastsQueue } from "@dxc-technology/halstack-react"; +import { DxcApplicationLayout, DxcInset, DxcLink, DxcToastsQueue } from "@dxc-technology/halstack-react"; import MainContent from "@/common/MainContent"; import { useRouter } from "next/router"; import { LinkDetails, LinksSectionDetails, LinksSections } from "@/common/pagesList"; @@ -116,11 +116,13 @@ export default function App({ Component, pageProps, emotionCache = clientSideEmo appTitle="Theme Generator" sideContent={ !currentPath.includes("/theme-generator/user-guide") && ( - - - User guide - - + + + + User guide + + + ) } /> @@ -137,7 +139,7 @@ export default function App({ Component, pageProps, emotionCache = clientSideEmo /> ) } - footer={isThemeGenerator && } + footer={isThemeGenerator ? : undefined} > diff --git a/apps/website/screens/common/example/Example.tsx b/apps/website/screens/common/example/Example.tsx index 23970f0030..afdc75dd42 100644 --- a/apps/website/screens/common/example/Example.tsx +++ b/apps/website/screens/common/example/Example.tsx @@ -2,7 +2,8 @@ import { useState } from "react"; import styled from "@emotion/styled"; import { LiveProvider, LiveEditor, LiveError, LivePreview } from "react-live"; import theme from "./liveEditorTheme"; -import { DxcButton, DxcFlex, useToast } from "@dxc-technology/halstack-react"; +import { DxcButton, DxcFlex } from "@dxc-technology/halstack-react"; +import useCopyToClipboard from "hooks/useCopyToClipboard"; const StyledPreview = styled.div` background-color: var(--color-bg-neutral-lightest); @@ -49,24 +50,13 @@ type ExamplePropTypes = { }; // const Example = ({ actionsVisible = true, defaultIsVisible = false, example }: ExamplePropTypes) => { - const toast = useToast(); const [isCodeVisible, changeIsCodeVisible] = useState(defaultIsVisible); const [liveCode, setLiveCode] = useState(example.code); const handleCodeOnClick = () => { changeIsCodeVisible(!isCodeVisible); }; - - const handleCopy = () => { - navigator.clipboard - .writeText(liveCode) - .then(() => { - toast.success({ message: "Code copied to the clipboard." }); - }) - .catch(() => { - toast.warning({ message: "Failed to copy the text to the clipboard." }); - }); - }; + const handleCopy = useCopyToClipboard(); return ( @@ -79,7 +69,16 @@ const Example = ({ actionsVisible = true, defaultIsVisible = false, example }: E {actionsVisible && ( - {isCodeVisible && } + {isCodeVisible && ( + { + handleCopy(liveCode); + }} + /> + )} { case 1: return <>; case 2: - return <>; + return ; } }; diff --git a/apps/website/screens/theme-generator/components/BottomButtons.tsx b/apps/website/screens/theme-generator/components/BottomButtons.tsx index be00b1aca2..7a2974d19b 100644 --- a/apps/website/screens/theme-generator/components/BottomButtons.tsx +++ b/apps/website/screens/theme-generator/components/BottomButtons.tsx @@ -15,7 +15,7 @@ const BottomButtons = ({ }) => ( diff --git a/apps/website/screens/theme-generator/components/branding/ColorCard.tsx b/apps/website/screens/theme-generator/components/branding/ColorCard.tsx index 5ed50535ae..c056ddcbbc 100644 --- a/apps/website/screens/theme-generator/components/branding/ColorCard.tsx +++ b/apps/website/screens/theme-generator/components/branding/ColorCard.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; -import { DxcContainer, DxcFlex, DxcPopover, DxcTextInput, useToast } from "@dxc-technology/halstack-react"; +import { DxcContainer, DxcFlex, DxcPopover, DxcTextInput } from "@dxc-technology/halstack-react"; import styled from "@emotion/styled"; import { SketchPicker } from "react-color"; -import { copyToClipboard } from "../../utils"; +import useCopyToClipboard from "hooks/useCopyToClipboard"; const ColorBox = styled.button<{ color: string }>` aspect-ratio: 1; @@ -36,7 +36,6 @@ export const ColorCard = ({ const [isOpen, setIsOpen] = useState(false); const [inputValue, setInputValue] = useState(color); const [error, setError] = useState(); - const toast = useToast(); const handleBlur = ({ value, error }: { value: string; error?: string }) => { setError(error); @@ -45,15 +44,7 @@ export const ColorCard = ({ } }; - const handleCopy = (value: string) => { - copyToClipboard(value) - .then(() => { - toast.success({ message: "Copied to clipboard" }); - }) - .catch(() => { - toast.warning({ message: "Failed to copy to clipboard" }); - }); - }; + const handleCopy = useCopyToClipboard(); return ( { + return ( + + + {label} + + + {logo ? ( + + ) : ( + No asset selected. + )} + + + + + ); +}; + +const ReviewBrandingAssets = ({ logos }: { logos: Logos }) => { + const brandingAssets = useMemo(() => { + return [ + { label: "Main logo", logo: logos.mainLogo?.[0]?.preview }, + { label: "Footer logo", logo: logos.footerLogo?.[0]?.preview }, + { label: "Reduced footer logo", logo: logos.footerReducedLogo?.[0]?.preview }, + { label: "Favicon", logo: logos.favicon?.[0]?.preview }, + ]; + }, [logos]); + + return ( + + {brandingAssets.some((asset) => asset.logo) ? ( + brandingAssets.map((asset) => ) + ) : ( + No branding assets selected. + )} + + ); +}; + +export default ReviewBrandingAssets; diff --git a/apps/website/screens/theme-generator/components/review/ReviewSectionContainer.tsx b/apps/website/screens/theme-generator/components/review/ReviewSectionContainer.tsx new file mode 100644 index 0000000000..a15502c24b --- /dev/null +++ b/apps/website/screens/theme-generator/components/review/ReviewSectionContainer.tsx @@ -0,0 +1,22 @@ +import { DxcContainer, DxcFlex } from "@dxc-technology/halstack-react"; + +const ReviewSectionContainer = ({ title, children }: { title: React.ReactNode; children: React.ReactNode }) => { + return ( + + + {title} + {children} + + + ); +}; + +export default ReviewSectionContainer; diff --git a/apps/website/screens/theme-generator/components/review/ReviewTokensGrid.tsx b/apps/website/screens/theme-generator/components/review/ReviewTokensGrid.tsx new file mode 100644 index 0000000000..639f622448 --- /dev/null +++ b/apps/website/screens/theme-generator/components/review/ReviewTokensGrid.tsx @@ -0,0 +1,60 @@ +import { DxcFlex, DxcGrid, DxcTypography } from "@dxc-technology/halstack-react"; +import { Tokens } from "../../types"; +import React, { useMemo } from "react"; +import { divideColorTokens, SHADE_VALUES } from "../../utils"; + +const ReviewTokensGrid = ({ generatedTokens }: { generatedTokens: Tokens }) => { + const tokenGroups = useMemo(() => divideColorTokens(generatedTokens), [generatedTokens]); + return ( + + {SHADE_VALUES.map((value, index) => + index === 0 ? ( + + + {value} + + + ) : ( + + {value} + + ) + )} + {Object.entries(tokenGroups).map(([group, colors]) => ( + + + + {group.charAt(0).toUpperCase() + group.slice(1)} + + + {colors.map((color: string, index: number) => ( +
+ ))} + + ))} + + ); +}; + +export default ReviewTokensGrid; diff --git a/apps/website/screens/theme-generator/components/review/ReviewTokensList.tsx b/apps/website/screens/theme-generator/components/review/ReviewTokensList.tsx new file mode 100644 index 0000000000..59a414aea1 --- /dev/null +++ b/apps/website/screens/theme-generator/components/review/ReviewTokensList.tsx @@ -0,0 +1,13 @@ +import { DxcContainer, DxcTypography } from "@dxc-technology/halstack-react"; + +const ReviewTokensList = ({ themeJson }: { themeJson: string }) => { + return ( + + + {themeJson} + + + ); +}; + +export default ReviewTokensList; diff --git a/apps/website/screens/theme-generator/steps/ReviewDetails.tsx b/apps/website/screens/theme-generator/steps/ReviewDetails.tsx new file mode 100644 index 0000000000..0089da9614 --- /dev/null +++ b/apps/website/screens/theme-generator/steps/ReviewDetails.tsx @@ -0,0 +1,63 @@ +import { DxcButton, DxcFlex, DxcGrid, DxcTypography } from "@dxc-technology/halstack-react"; +import { Logos, Tokens } from "../types"; +import ReviewTokensGrid from "../components/review/ReviewTokensGrid"; +import ReviewTokensList from "../components/review/ReviewTokensList"; +import ReviewBrandingAssets from "../components/review/ReviewBrandingAssets"; +import useCopyToClipboard from "hooks/useCopyToClipboard"; +import { useMemo } from "react"; +import ReviewSectionContainer from "../components/review/ReviewSectionContainer"; + +const ReviewDetails = ({ generatedTokens, logos }: { generatedTokens: Tokens; logos: Logos }) => { + const themeJson = useMemo(() => { + const themeObject = { + tokens: generatedTokens, + logos: { + mainLogo: "", + footerLogo: "", + footerReducedLogo: "", + favicon: "", + }, + }; + return JSON.stringify(themeObject, null, 2); + }, [generatedTokens]); + + const handleCopy = useCopyToClipboard(); + + return ( + <> + + + Color palette & theme + + handleCopy(themeJson)} + /> + + } + > + + + + + + + + + Branding assets + + } + > + + + + ); +}; + +export default ReviewDetails; diff --git a/apps/website/screens/theme-generator/types.ts b/apps/website/screens/theme-generator/types.ts index 0a444a9f18..0b82e23756 100644 --- a/apps/website/screens/theme-generator/types.ts +++ b/apps/website/screens/theme-generator/types.ts @@ -5,6 +5,7 @@ export type Tokens = Record; export type BaseColors = Record; export type Step = 0 | 1 | 2; + export type Colors = { primary: CssColor; secondary: CssColor; @@ -15,22 +16,38 @@ export type Colors = { error: CssColor; warning: CssColor; }; + +export type TokenGroups = { + primary: string[]; + secondary: string[]; + tertiary: string[]; + semantic01: string[]; + semantic02: string[]; + semantic03: string[]; + semantic04: string[]; + neutral: string[]; + alpha: string[]; +}; + export type FileData = { error?: string; file: File; preview?: string; }; + export type Logos = { mainLogo: FileData[]; footerLogo: FileData[]; footerReducedLogo: FileData[]; favicon: FileData[]; }; + export type Field = { id: string; label: string; helperText: string; }; + export type BrandingDetailsSection = { id: string; title: string; diff --git a/apps/website/screens/theme-generator/utils.ts b/apps/website/screens/theme-generator/utils.ts index 25cf9062a2..943e55293e 100644 --- a/apps/website/screens/theme-generator/utils.ts +++ b/apps/website/screens/theme-generator/utils.ts @@ -1,5 +1,5 @@ import { Color, BackgroundColor, Theme, type CssColor } from "@adobe/leonardo-contrast-colors"; -import { BaseColors, Tokens } from "./types"; +import { BaseColors, TokenGroups, Tokens } from "./types"; /** * Contrast ratios for generating color shades @@ -103,6 +103,29 @@ export const handleExport = (tokens: Tokens) => { downloadAnchorNode.remove(); }; -export const copyToClipboard = (value: string) => { - return navigator.clipboard.writeText(value); +export const divideColorTokens = (tokens: Tokens) => { + const groups: TokenGroups = { + primary: [], + secondary: [], + tertiary: [], + semantic01: [], + semantic02: [], + semantic03: [], + semantic04: [], + neutral: [], + alpha: [], + }; + + for (const [key, value] of Object.entries(tokens)) { + // divides tokens names into 3 sections + const sections = key.match(/^--color-([a-z0-9]+)-(.+)$/); + if (sections) { + // the second section indicates the group (primary, secondary, etc.) + const group = sections[1]; + if (group && group in groups) { + groups[group as keyof TokenGroups].push(value); + } + } + } + return groups; };