Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
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
223 changes: 223 additions & 0 deletions apps/website/screens/theme-generator/ThemeGeneratorPreviewPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import {
DxcButton,
DxcContainer,
DxcFlex,
DxcSelect,
DxcToggleGroup,
DxcTypography,
HalstackProvider,
} from "@dxc-technology/halstack-react";
import { useMemo, useState } from "react";
import componentsList from "../common/componentsList.json";
import { componentsRegistry, examplesRegistry } from "screens/utilities/theme-generator/componentsRegistry";
import { ListOptionType } from "../../../../packages/lib/src/select/types";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this be imported from @dxc-technology/halstack-react ? instead of this cross-package import?.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't currently export types. (We should ideally use something like DefinitelyTyped). For now I can simply repeat the typing in this file, is it okay?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I think I prefer this option, although we must be aware that if it changes in one place, we have to change it here too.

import styled from "@emotion/styled";
import { Logos } from "./types";

// JSON Structure type, this should go in the json file or somewhere else

type ComponentItem = {
label: string;
icon?: string;
path?: string;
status?: string;
links?: ComponentItem[];
};

/////////////////////////////////////

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 displayedPreview = useMemo(() => {
if (mode === "components") {
return selectedComponents.map((component) => {
const ComponentPreview = componentsRegistry[component as keyof typeof componentsRegistry];
return ComponentPreview ? <ComponentPreview key={component} /> : 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}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this select be medium size? Seems to be larger than in design.
I would add searchable functionality to the select to make it easier the selection.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is already medium size (it's the default one).

You are right on the searchable, I would note to improve the search functionality for group selectors to be included as indexes to filter (currently, if we search "Feedback", no results will be shown). This was already done for Sidenav so we could probably reuse some of the logic, not high priority by any means though, just note it as a low prio issue if possible.

Fixed!

Copy link
Collaborator

Choose a reason for hiding this comment

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

I mean if it should be small size. It seems larger in implementation than 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.

You are right sorry! I was only checking height which does not change between sizes.

Fixed!

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>
{/* 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) ? (
<HalstackProvider opinionatedTheme={tokens} style={{ height: "100%" }}>
<DxcFlex direction="column" gap="var(--spacing-gap-l)" 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>
<CustomPreviewArea mode={mode}>{displayedPreview}</CustomPreviewArea>
</DxcFlex>
</HalstackProvider>
) : (
<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>
);
};

// TODO: this is just a quick solution to make the preview area scrollable when the content is too big, I don't know what other approach that doesn't
// involve adding custom styles could be used. (fullHeight does not do anything here either)
const CustomPreviewArea = styled.div<{ mode: string }>`
display: flex;
flex-direction: column;
gap: var(--spacing-gap-l);
flex: 1 1 0;
overflow: auto;
align-items: ${(props) => (props.mode === "components" ? "flex-start" : "center")};
`;

export default ThemeGeneratorPreviewPage;
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,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { DxcAccordion, DxcInset, DxcParagraph } from "@dxc-technology/halstack-react";

const AccordionPreview = () => {
return (
<DxcAccordion>
<DxcAccordion.AccordionItem label="Label" icon="filled_info" assistiveText="Assistive text" subLabel="Sublabel">
<DxcInset space="var(--spacing-padding-l)">
<DxcParagraph>Content</DxcParagraph>
</DxcInset>
</DxcAccordion.AccordionItem>
</DxcAccordion>
);
};

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

const AlertPreview = () => {
return (
<DxcFlex direction="row" gap="var(--spacing-gap-l)">
<DxcAlert
message={{ text: "Success message" }}
primaryAction={{ label: "Primary action", onClick: () => {} }}
secondaryAction={{ label: "Secondary action", onClick: () => {} }}
semantic="success"
title="Success"
/>
<DxcAlert
message={{ text: "Information message" }}
primaryAction={{ label: "Primary action", onClick: () => {} }}
secondaryAction={{ label: "Secondary action", onClick: () => {} }}
semantic="info"
title="Information"
/>
<DxcAlert
message={{ text: "Warning message" }}
primaryAction={{ label: "Primary action", onClick: () => {} }}
secondaryAction={{ label: "Secondary action", onClick: () => {} }}
semantic="warning"
title="Warning"
/>
<DxcAlert
message={{ text: "Error message" }}
primaryAction={{ label: "Primary action", onClick: () => {} }}
secondaryAction={{ label: "Secondary action", onClick: () => {} }}
semantic="error"
title="Error"
/>
</DxcFlex>
);
};

export default AlertPreview;
Loading