-
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
base: theme-generator
Are you sure you want to change the base?
Changes from all commits
5c971af
94c3b91
ced937c
8840ea8
c282499
af050eb
06da26b
28a0d4b
089252a
1b68b31
313fa6b
c108b33
0a909f4
ce257af
2693c85
7754fb4
75c0881
fdc638f
ca76b1e
b77d7b2
be82f62
7734634
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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"; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this select be
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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!
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; |
Uh oh!
There was an error while loading. Please reload this page.