-
Notifications
You must be signed in to change notification settings - Fork 17
[doc] Theme generator Preview (Step 2) #2417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
raquelarrojo
merged 31 commits into
theme-generator
from
Mil4n0r/theme-generator-preview
Mar 9, 2026
Merged
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 94c3b91
First version of preview for theme generator (unfinished)
Mil4n0r ced937c
Removed clg
Mil4n0r 8840ea8
Added most of the missing components to the preview
Mil4n0r c282499
Improved layout and added missing examples
Mil4n0r af050eb
Merge branch 'theme-generator' of github.com:dxc-technology/halstack-…
Mil4n0r 06da26b
Improved layout and adjusted to skeleton structure
Mil4n0r 28a0d4b
Reverted change on MainContainer
Mil4n0r 089252a
Added complete examples
Mil4n0r 1b68b31
Fixed import for GridColumn type
Mil4n0r 313fa6b
Merge branch 'theme-generator' of github.com:dxc-technology/halstack-…
Mil4n0r c108b33
Added fix for layout exceeding expected preview area
Mil4n0r 0a909f4
Inverted wrong logic for clearing examples
Mil4n0r ce257af
Fixed minor errors detected in review
Mil4n0r 2693c85
Improved component preview missing some modes
Mil4n0r 7754fb4
Removed unused previews
Mil4n0r 75c0881
Updated package-lock
Mil4n0r fdc638f
Added partially applied theme to preview and inline styles prop
Mil4n0r ca76b1e
Removed commented code
Mil4n0r b77d7b2
Made Select searchable and divided avatar in 2 rows for better readib…
Mil4n0r be82f62
Changed select size to 'small'
Mil4n0r 7734634
Added ApplicationLayout and logos to examples
Mil4n0r 7584596
Prevented having to use inline styles in HalstackContext
Mil4n0r eb04b31
Added disclaimer text
Mil4n0r 5222d60
Added types to prevent cross-package import
Mil4n0r 82379fc
Normalized padding values
Mil4n0r 10e10ae
Added preview title for each element
Mil4n0r c25e43a
Changed disclaimer text so it is only visible in layout examples
Mil4n0r be6a874
Fixed tooltip behavior inside flex column being misaligned
Mil4n0r 42f1ae2
Updated examples
Mil4n0r 5030da0
Fit content for contextual menu
Mil4n0r File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
267 changes: 267 additions & 0 deletions
267
apps/website/screens/theme-generator/ThemeGeneratorPreviewPage.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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} | ||
raquelarrojo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
apps/website/screens/utilities/theme-generator/componentsRegistry.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.