Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5c971af
Merge branch 'master' of github.com:dxc-technology/halstack-react int…
Mil4n0r Feb 20, 2026
94c3b91
First version of preview for theme generator (unfinished)
Mil4n0r Feb 25, 2026
ced937c
Removed clg
Mil4n0r Feb 25, 2026
8840ea8
Added most of the missing components to the preview
Mil4n0r Feb 26, 2026
c282499
Improved layout and added missing examples
Mil4n0r Feb 27, 2026
af050eb
Merge branch 'theme-generator' of github.com:dxc-technology/halstack-…
Mil4n0r Mar 2, 2026
06da26b
Improved layout and adjusted to skeleton structure
Mil4n0r Mar 2, 2026
28a0d4b
Reverted change on MainContainer
Mil4n0r Mar 2, 2026
089252a
Added complete examples
Mil4n0r Mar 2, 2026
1b68b31
Fixed import for GridColumn type
Mil4n0r Mar 2, 2026
313fa6b
Merge branch 'theme-generator' of github.com:dxc-technology/halstack-…
Mil4n0r Mar 5, 2026
c108b33
Added fix for layout exceeding expected preview area
Mil4n0r Mar 5, 2026
0a909f4
Inverted wrong logic for clearing examples
Mil4n0r Mar 6, 2026
ce257af
Fixed minor errors detected in review
Mil4n0r Mar 6, 2026
2693c85
Improved component preview missing some modes
Mil4n0r Mar 6, 2026
7754fb4
Removed unused previews
Mil4n0r Mar 6, 2026
75c0881
Updated package-lock
Mil4n0r Mar 6, 2026
fdc638f
Added partially applied theme to preview and inline styles prop
Mil4n0r Mar 6, 2026
ca76b1e
Removed commented code
Mil4n0r Mar 6, 2026
b77d7b2
Made Select searchable and divided avatar in 2 rows for better readib…
Mil4n0r Mar 6, 2026
be82f62
Changed select size to 'small'
Mil4n0r Mar 6, 2026
7734634
Added ApplicationLayout and logos to examples
Mil4n0r Mar 6, 2026
7584596
Prevented having to use inline styles in HalstackContext
Mil4n0r Mar 9, 2026
eb04b31
Added disclaimer text
Mil4n0r Mar 9, 2026
5222d60
Added types to prevent cross-package import
Mil4n0r Mar 9, 2026
82379fc
Normalized padding values
Mil4n0r Mar 9, 2026
10e10ae
Added preview title for each element
Mil4n0r Mar 9, 2026
c25e43a
Changed disclaimer text so it is only visible in layout examples
Mil4n0r Mar 9, 2026
be6a874
Fixed tooltip behavior inside flex column being misaligned
Mil4n0r Mar 9, 2026
42f1ae2
Updated examples
Mil4n0r Mar 9, 2026
5030da0
Fit content for contextual menu
Mil4n0r Mar 9, 2026
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 @@ -2,6 +2,9 @@ import { useRef, useState } from "react";
import { DxcContainer, DxcFlex, DxcWizard } from "@dxc-technology/halstack-react";
import StepHeading from "./components/StepHeading";
import BottomButtons from "./components/BottomButtons";
import ThemeGeneratorPreviewPage from "./ThemeGeneratorPreviewPage";
// import { FileData } from "../../../../packages/lib/src/file-input/types";

import { BrandingDetails } from "./steps/BrandingDetails";
import { generateTokens, handleExport } from "./utils";
import { Colors, FileData, Step } from "./types";
Expand Down Expand Up @@ -87,7 +90,7 @@ const ThemeGeneratorConfigPage = () => {
case 0:
return <BrandingDetails colors={colors} onColorsChange={setColors} logos={logos} onLogosChange={setLogos} />;
case 1:
return <></>;
return <ThemeGeneratorPreviewPage tokens={tokens} logos={logos} />;
case 2:
return <></>;
}
Expand Down Expand Up @@ -118,7 +121,7 @@ const ThemeGeneratorConfigPage = () => {
boxSizing="border-box"
margin={{ left: "auto", right: "auto" }}
>
<DxcFlex direction="column" alignItems="center" gap="var(--spacing-gap-xl)">
<DxcFlex direction="column" alignItems="center" gap="var(--spacing-gap-xl)" fullHeight>
<StepHeading title={steps[currentStep].title} subtitle={steps[currentStep].subtitle} />
{renderStepContent()}
</DxcFlex>
Expand Down
267 changes: 267 additions & 0 deletions apps/website/screens/theme-generator/ThemeGeneratorPreviewPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import {
DxcButton,
DxcContainer,
DxcFlex,
DxcSelect,
DxcToggleGroup,
DxcTypography,
HalstackProvider,
} from "@dxc-technology/halstack-react";
import { ReactNode, SVGProps, useMemo, useState } from "react";
import componentsList from "../common/componentsList.json";
import { componentsRegistry, examplesRegistry } from "screens/utilities/theme-generator/componentsRegistry";
import styled from "@emotion/styled";
import { ComponentItem, Logos } from "./types";

// TODO: Both of these types should be exported by @dxc-technology/halstack-react when they are available
type SVG = ReactNode & SVGProps<SVGSVGElement>;

type ListOptionType = {
/**
* Element used as the icon that will be placed before the option label.
* It can be an inline SVG or Material Symbol name. If the url option
* is the chosen one, take into account that the component's
* color styling tokens will not be applied to the image.
*/
icon?: string | SVG;
/**
* Label of the option to be shown in the select's listbox.
*/
label: string;
/**
* Value of the option. It should be unique and
* not an empty string, which is reserved to the empty option added
* by optional prop.
*/
value: string;
};

const informationIcon = (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path
d="M11 7H13V9H11V7ZM11 11H13V17H11V11ZM12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20Z"
fill="#494949"
/>
</svg>
);

const exampleOptions = [
{
label: "Application example",
value: "/examples/application",
icon: "settings",
},
{
label: "Dashboard example",
value: "/examples/dashboard",
icon: "dashboard",
},
{
label: "Form example",
value: "/examples/form",
icon: "description",
},
];

const componentsExceptions = [
"/components/application-layout",
"/components/bleed",
"/components/bulleted-list",
"/components/card",
"/components/container",
"/components/dialog",
"/components/flex",
"/components/footer",
"/components/grid",
"/components/header",
"/components/heading",
"/components/image",
"/components/inset",
"/components/paragraph",
"/components/popover",
"/components/sidenav",
"/components/typography",
];

const mapToSelectGroups = (data: ComponentItem[]) => {
const collectOptions = (items: ComponentItem[]): ListOptionType[] => {
return items.flatMap((item) => {
const current: ListOptionType[] =
item.path && !componentsExceptions.includes(item.path)
? [
{
label: item.label,
value: item.path,
icon: item.icon,
},
]
: [];

const nested = item.links ? collectOptions(item.links) : [];

return [...current, ...nested];
});
};

return data.map((category) => ({
label: category.label,
options: collectOptions(category.links ?? []),
}));
};

const ThemeGeneratorPreviewPage = ({ tokens, logos }: { tokens: Record<string, string>; logos: Logos }) => {
const [mode, setMode] = useState<"components" | "examples">("components");

const [selectedComponents, setSelectedComponents] = useState<string[]>([]);
const [selectedExample, setSelectedExample] = useState<string>("");

const componentOptions = useMemo(() => {
return mapToSelectGroups(componentsList as ComponentItem[]);
}, []);

const getLabelFromValue = (value: string) =>
componentOptions.flatMap((group) => group.options).find((opt) => opt.value === value)?.label;

const displayedPreview = useMemo(() => {
if (mode === "components") {
return selectedComponents.map((component) => {
const ComponentPreview = componentsRegistry[component as keyof typeof componentsRegistry];
return ComponentPreview ? (
<DxcFlex direction="column" gap="var(--spacing-gap-s)" key={component}>
<DxcTypography
color="var(--color-fg-neutral-strongest)"
fontFamily="var(--typography-font-family)"
fontSize="var(--typography-title-s)"
fontWeight="var(--typography-title-bold)"
>
{getLabelFromValue(component)}
</DxcTypography>
<ComponentPreview key={component} />
</DxcFlex>
) : null;
});
}

if (mode === "examples") {
const ExamplePreview = examplesRegistry[selectedExample as keyof typeof examplesRegistry];
return ExamplePreview ? <ExamplePreview logos={logos} key={selectedExample} /> : null;
}

return null;
}, [mode, selectedComponents, selectedExample]);

return (
<DxcContainer width="100%" height="100%">
<DxcFlex direction="column" gap="var(--spacing-gap-s)" fullHeight>
<DxcFlex direction="row" justifyContent="space-between" alignItems="center">
<DxcToggleGroup
options={[
{ label: "Components", icon: "category", value: 1 },
{ label: "Layout examples", icon: "dashboard", value: 2 },
]}
value={mode === "components" ? 1 : 2}
onChange={(value: number) => setMode(value === 1 ? "components" : "examples")}
/>
{mode === "components" && (
<DxcSelect
placeholder="Select components"
options={componentOptions}
multiple
value={selectedComponents}
onChange={({ value }) => {
setSelectedComponents(value);
}}
enableSelectAll
searchable
size="small"
/>
)}

{mode === "examples" && (
<DxcSelect
placeholder="Select examples"
options={exampleOptions}
value={selectedExample}
onChange={({ value }) => {
setSelectedExample(value);
}}
searchable
/>
)}
</DxcFlex>
{mode === "examples" && (
<DxcFlex gap="var(--spacing-gap-xs)">
{informationIcon}
<DxcTypography
color="var(--color-fg-neutral-strongest)"
fontSize="var(--typography-label-m)"
fontWeight="var(--typography-label-regular)"
>
Some components are presentational examples. The layouts shown are for demonstration purposes only and do
not represent actual components.
</DxcTypography>
</DxcFlex>
)}
{/* TODO: Turn this into a separate componente called PreviewArea or similar? */}
<DxcContainer
borderRadius="var(--border-radius-l)"
border={{
width: "var(--border-width-s)",
color: "var(--border-color-neutral-medium)",
style: "var(--border-style-default)",
}}
background={{ color: "var(--color-bg-neutral-lightest)" }}
padding="var(--spacing-padding-s)"
height="100%"
>
{(mode === "components" && selectedComponents.length > 0) || (mode === "examples" && !!selectedExample) ? (
<DxcFlex direction="column" gap="var(--spacing-gap-ml)" fullHeight>
<DxcFlex justifyContent="flex-end">
<DxcButton
icon="filled_delete"
size={{ height: "medium" }}
title="Delete selection"
onClick={() => {
if (mode === "components") {
setSelectedComponents([]);
} else {
setSelectedExample("");
}
}}
mode="secondary"
semantic="error"
disabled={mode === "components" ? selectedComponents.length === 0 : !selectedExample}
/>
</DxcFlex>
<PreviewAreaContainer>
<HalstackProvider opinionatedTheme={tokens}>
<DxcFlex direction="column" gap="var(--spacing-gap-l)">
{displayedPreview}
</DxcFlex>
</HalstackProvider>
</PreviewAreaContainer>
</DxcFlex>
) : (
<DxcFlex alignItems="center" justifyContent="center" fullHeight>
<DxcTypography
color="var(--color-fg-neutral-dark)"
fontFamily="var(--typography-font-family)"
fontSize="var(--typography-body-s)"
fontWeight="var(--typography-body-regular)"
>
Select {mode === "components" ? "a component" : "an example"} to preview
</DxcTypography>
</DxcFlex>
)}
</DxcContainer>
</DxcFlex>
</DxcContainer>
);
};

const PreviewAreaContainer = styled.div`
flex: 1 1 0;
overflow: auto;
`;

export default ThemeGeneratorPreviewPage;
8 changes: 8 additions & 0 deletions apps/website/screens/theme-generator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ export type BrandingDetailsSection = {
icon: ReactNode;
fields: Field[];
};

export type ComponentItem = {
label: string;
icon?: string;
path?: string;
status?: string;
links?: ComponentItem[];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
AccordionPreview,
AlertPreview,
AvatarPreview,
BadgePreview,
BreadcrumbsPreview,
ButtonPreview,
CheckboxPreview,
ChipPreview,
ContextualMenuPreview,
DataGridPreview,
DateInputPreview,
DividerPreview,
DropdownPreview,
FileInputPreview,
LinkPreview,
NavTabsPreview,
NumberInputPreview,
PaginatorPreview,
PasswordInputPreview,
ProgressBarPreview,
QuickNavPreview,
RadioGroupPreview,
ResultsetTablePreview,
SelectPreview,
SliderPreview,
SpinnerPreview,
StatusLightPreview,
SwitchPreview,
TablePreview,
TabsPreview,
TextareaPreview,
TextInputPreview,
ToastPreview,
ToggleGroupPreview,
TooltipPreview,
WizardPreview,
} from "./previews/components";
import { ApplicationPreview, DashboardPreview, FormPreview } from "./previews/examples";

export const componentsRegistry = {
"/components/accordion": AccordionPreview,
"/components/avatar": AvatarPreview,
"/components/divider": DividerPreview,
"/components/wizard": WizardPreview,
"/components/data-grid": DataGridPreview,
"/components/paginator": PaginatorPreview,
"/components/resultset-table": ResultsetTablePreview,
"/components/table": TablePreview,
"/components/alert": AlertPreview,
"/components/progress-bar": ProgressBarPreview,
"/components/spinner": SpinnerPreview,
"/components/toast": ToastPreview,
"/components/tooltip": TooltipPreview,
"/components/button": ButtonPreview,
"/components/checkbox": CheckboxPreview,
"/components/date-input": DateInputPreview,
"/components/file-input": FileInputPreview,
"/components/number-input": NumberInputPreview,
"/components/password-input": PasswordInputPreview,
"/components/radio-group": RadioGroupPreview,
"/components/select": SelectPreview,
"/components/slider": SliderPreview,
"/components/switch": SwitchPreview,
"/components/text-input": TextInputPreview,
"/components/textarea": TextareaPreview,
"/components/toggle-group": ToggleGroupPreview,
"/components/breadcrumbs": BreadcrumbsPreview,
"/components/contextual-menu": ContextualMenuPreview,
"/components/dropdown": DropdownPreview,
"/components/link": LinkPreview,
"/components/nav-tabs": NavTabsPreview,
"/components/quick-nav": QuickNavPreview,
"/components/tabs": TabsPreview,
"/components/badge": BadgePreview,
"/components/chip": ChipPreview,
"/components/status-light": StatusLightPreview,
};

export const examplesRegistry = {
"/examples/application": ApplicationPreview,
"/examples/dashboard": DashboardPreview,
"/examples/form": FormPreview,
};
Loading