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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import BottomButtons from "./components/BottomButtons";
import { BrandingDetails } from "./steps/BrandingDetails";
import { generateTokens, handleExport } from "./utils";
import { Colors, FileData, Step } from "./types";
import ReviewDetails from "./steps/ReviewDetails";

const steps = [
{
Expand Down Expand Up @@ -89,7 +90,7 @@ const ThemeGeneratorConfigPage = () => {
case 1:
return <></>;
case 2:
return <></>;
return <ReviewDetails generatedTokens={tokens} logos={logos} />;
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { DxcContainer, DxcFlex, DxcGrid, DxcImage, DxcTypography } from "@dxc-technology/halstack-react";
import { useMemo } from "react";
import { Logos } from "../../types";

const BrandingAsset = ({ label, logo }: { label: string; logo?: string }) => {
if (!logo) return null;
return (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking into account the comment about creating an array with a label and logo(l.30), we should check here if the logo exists or not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

<DxcContainer width="250px" height="100%">
<DxcFlex direction="column" gap="var(--spacing-gap-xs)" fullHeight>
<DxcTypography fontWeight="var(--typography-label-semibold)">{label}</DxcTypography>
<DxcContainer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we show anything if there is no logo selected?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did change this to show empty grey containers. There are some solutions that we can argue about:

  • having default logos and use those by default
  • leaving empty grey placeholders like now
  • removing the Branding assets section in the review step if there are no logos and showing only those that do have a logo assigned

width="100%"
maxHeight="calc(100% - 28px)"
height="130px"
background={{ color: "var(--color-bg-neutral-light)" }}
borderRadius="var(--border-radius-m)"
>
<DxcFlex alignItems="center" justifyContent="center" fullHeight>
<DxcImage width="100%" height="100%" objectFit="contain" src={logo} alt={label} />
</DxcFlex>
</DxcContainer>
</DxcFlex>
</DxcContainer>
);
};

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 (
<DxcGrid templateColumns={["repeat(4, 1fr)"]} templateRows={["1fr"]} gap="var(--spacing-gap-ml)">
{brandingAssets.length &&
brandingAssets.map(({ label, logo }) => <BrandingAsset key={label} label={label} logo={logo} />)}
</DxcGrid>
);
};

export default ReviewBrandingAssets;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { DxcContainer, DxcFlex } from "@dxc-technology/halstack-react";

const ReviewSectionContainer = ({ title, children }: { title: React.ReactNode; children: React.ReactNode }) => {
return (
<DxcContainer
boxSizing="border-box"
width="100%"
maxWidth="1112px"
padding="var(--spacing-padding-ml)"
borderRadius="var(--border-radius-m)"
background={{ color: "var(--color-bg-neutral-lightest)" }}
boxShadow="var(--shadow-100)"
>
<DxcFlex direction="column" gap="var(--spacing-gap-ml)">
{title}
{children}
</DxcFlex>
</DxcContainer>
);
};

export default ReviewSectionContainer;
Original file line number Diff line number Diff line change
@@ -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 (
<DxcGrid
templateColumns={["100px", "repeat(10, 1fr)"]}
templateRows={["auto", "repeat(9, 1fr)"]}
gap="var(--spacing-gap-xs)"
>
{SHADE_VALUES.map((value, index) =>
index === 0 ? (
<DxcGrid.Item column={"2/3"} key={"shade_" + value}>
<DxcTypography
fontSize="var(--typography-body-s)"
color="var(--color-fg-neutral-strong)"
textAlign="center"
>
{value}
</DxcTypography>
</DxcGrid.Item>
) : (
<DxcTypography
key={"shade_" + value}
fontSize="var(--typography-body-s)"
color="var(--color-fg-neutral-strong)"
textAlign="center"
>
{value}
</DxcTypography>
)
)}
{Object.entries(tokenGroups).map(([group, colors]) => (
<React.Fragment key={group}>
<DxcFlex alignItems="center">
<DxcTypography fontWeight="var(--typography-label-semibold)">
{group.charAt(0).toUpperCase() + group.slice(1)}
</DxcTypography>
</DxcFlex>
{colors.map((color: string, index: number) => (
<div
key={group + "_" + SHADE_VALUES[index]}
style={{
backgroundColor: color,
width: "100%",
aspectRatio: "1 / 1",
borderRadius: "var(--border-radius-m)",
}}
/>
))}
</React.Fragment>
))}
</DxcGrid>
);
};

export default ReviewTokensGrid;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{group} should be capitalized, as it is in the design

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { DxcContainer, DxcTypography } from "@dxc-technology/halstack-react";

const ReviewTokensList = ({ themeJson }: { themeJson: string }) => {
return (
<DxcContainer height="100%" overflow="auto" borderRadius="var(--border-radius-m)">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scrollbar of this container is not the same as in the design, and is not positioned in the same spot.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This topic has been discussed with design already.

<DxcTypography as="pre" fontSize="var(--typography-body-xs)" whiteSpace="pre">
{themeJson}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tokens list should also include logos, does it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Moved the json string creation to ReviewDetails instead to avoid code repetition.

</DxcTypography>
</DxcContainer>
);
};

export default ReviewTokensList;
68 changes: 68 additions & 0 deletions apps/website/screens/theme-generator/steps/ReviewDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { DxcButton, DxcFlex, DxcGrid, DxcTypography, useToast } from "@dxc-technology/halstack-react";
import { Logos, Tokens } from "../types";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import paths are inconsistent: ReviewTokensGrid and ReviewTokensList use absolute paths while ReviewBrandingAssets and copyToClipboard use relative paths. For consistency, consider unifying to one style.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

import ReviewTokensGrid from "../components/review/ReviewTokensGrid";
import ReviewTokensList from "../components/review/ReviewTokensList";
import ReviewBrandingAssets from "../components/review/ReviewBrandingAssets";
import { copyToClipboard } from "../utils";
import { useMemo } from "react";
import ReviewSectionContainer from "../components/review/ReviewSectionContainer";

const ReviewDetails = ({ generatedTokens, logos }: { generatedTokens: Tokens; logos: Logos }) => {
const toast = useToast();

const themeJson = useMemo(() => {
const themeObject = {
tokens: generatedTokens,
logos: {
mainLogo: "",
footerLogo: "",
footerReducedLogo: "",
favicon: "",
},
};
return JSON.stringify(themeObject, null, 2);
}, [generatedTokens]);

const handleCopy = () => {
copyToClipboard(themeJson)
.then(() => {
toast.success({ message: "Copied to clipboard" });
})
.catch(() => {
toast.warning({ message: "Failed to copy to clipboard" });
});
};

return (
<>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pleas remove this fragment

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component is returning two DxcContainers without this fragment we would receive an error.

<ReviewSectionContainer
title={
<DxcFlex justifyContent="space-between" alignItems="center">
<DxcTypography fontSize="var(--typography-title-l)" fontWeight="var(--typography-title-bold)">
Color palette & theme
</DxcTypography>
<DxcButton mode="secondary" icon="content_copy" size={{ height: "medium" }} onClick={handleCopy} />
</DxcFlex>
}
>
<DxcGrid templateColumns={["2fr", "1fr"]} templateRows={["368px"]} gap="var(--spacing-gap-m)">
<DxcFlex justifyContent="center">
<ReviewTokensGrid generatedTokens={generatedTokens} />
</DxcFlex>
<ReviewTokensList themeJson={themeJson} />
</DxcGrid>
</ReviewSectionContainer>
<ReviewSectionContainer
title={
<DxcTypography fontSize="var(--typography-title-l)" fontWeight="var(--typography-title-bold)">
Branding assets
</DxcTypography>
}
>
<ReviewBrandingAssets logos={logos} />
</ReviewSectionContainer>
</>
);
};

export default ReviewDetails;
17 changes: 17 additions & 0 deletions apps/website/screens/theme-generator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type Tokens = Record<string, string>;
export type BaseColors = Record<string, CssColor>;

export type Step = 0 | 1 | 2;

export type Colors = {
primary: CssColor;
secondary: CssColor;
Expand All @@ -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;
Expand Down
29 changes: 28 additions & 1 deletion apps/website/screens/theme-generator/utils.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -106,3 +106,30 @@ export const handleExport = (tokens: Tokens) => {
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;
};