Adding a customizable Widget to the Theme Editor - HL Vanilla Community
<main> <article class="userContent"> <p>This guide will help us through the process of adding a section in the theme editor to style a component. In this example we will be implementing customizable <em>Discussion Lists.</em></p><div class="embedExternal embedImage display-large float-none"> <div class="embedExternal-content"> <a class="embedImage-link" href="https://us.v-cdn.net/5022541/uploads/UZHC83NHTXD5/image.png" rel="nofollow noreferrer noopener ugc" target="_blank"> <img class="embedImage-img" src="https://us.v-cdn.net/5022541/uploads/UZHC83NHTXD5/image.png" alt="image.png" height="1042" width="1421" loading="lazy" data-display-size="large" data-float="none"></img></a> </div> </div> <h2 data-id="prerequisites">Prerequisites</h2><p>You will need:</p><ul><li>A react component to customize <em>(in this case, the DiscussionList)</em></li><li>Theme variables used for styling the component <em>(make sure those are documented as well, to help other devs)</em></li><li>Some dummy data to render a preview of the component <em>(make sure the data is appropriate as it will be customer facing)</em></li></ul><h2 data-id="adding-a-section-to-the-panel">Adding a section to the panel</h2><p>The panel to the right of the Theme Editor is contextual. It will change when selecting a widget in the page.</p><p>In some cases, we need to select a different part of the page to customize in order tochange the content of the Left Panel (the preview) and display items we want to customize. Those are grouped by section titles in the dropdown. For example, selecting <em>Quick Links</em> here will change the preview to the Content page, which presents a simple discussion list page which quick links and a banner.</p><p>In order to add a section to the dropdown we will need to change a few files.</p><h4 data-id="add-a-variable-panel-type">Add a variable panel type</h4><pre class="code codeBlock" spellcheck="false" tabindex="0">// ActivePanelContext.tsx. export enum ActiveVariablePanel { GLOBAL = "global", TITLE_BAR = "titleBar", BANNER = "banner", CONTENT_BANNER = "contentBanner", WIDGETS = "widgets", QUICKLINKS = "quickLinks", // A new section is added here: DISCUSSION_LIST = "discussionList", } </pre><h4 data-id="add-a-section-to-the-dropdown">Add a section to the dropdown</h4><pre class="code codeBlock" spellcheck="false" tabindex="0">{/* ActivePanelChooser.tsx */} <DropDownSection title={t("Global & Homepage")} noSeparator> <SectionItem panel={ActiveVariablePanel.GLOBAL} /> <SectionItem panel={ActiveVariablePanel.TITLE_BAR} /> <SectionItem panel={ActiveVariablePanel.BANNER} /> <SectionItem panel={ActiveVariablePanel.WIDGETS} /> </DropDownSection> <DropDownSection title={t("Content")}> <SectionItem panel={ActiveVariablePanel.CONTENT_BANNER} /> <SectionItem panel={ActiveVariablePanel.QUICKLINKS} /> {/* A new section is added here: */} <SectionItem panel={ActiveVariablePanel.DISCUSSION_LIST} /> </DropDownSection> </pre><h4 data-id="add-a-section-name">Add a section name</h4><pre class="code codeBlock" spellcheck="false" tabindex="0">// ActivePanelChooser.tsx function sectionToName(panel: ActiveVariablePanel) { switch (panel) { case ActiveVariablePanel.QUICKLINKS: return t("Quick Links"); case ActiveVariablePanel.TITLE_BAR: return t("Title Bar"); case ActiveVariablePanel.BANNER: return t("Banner"); case ActiveVariablePanel.CONTENT_BANNER: return t("Content Banner"); case ActiveVariablePanel.WIDGETS: return t("Widgets"); // A new section title is added here: case ActiveVariablePanel.DISCUSSION_LIST: return t("Discussion List"); case ActiveVariablePanel.GLOBAL: default: return t("Global Styles"); } } </pre><p>At this point you should now have the section showing up in the panel dropdown.</p><div class="embedExternal embedImage display-large float-none"> <div class="embedExternal-content"> <a class="embedImage-link" href="https://us.v-cdn.net/5022541/uploads/HMSDN3ANC449/image.png" rel="nofollow noreferrer noopener ugc" target="_blank"> <img class="embedImage-img" src="https://us.v-cdn.net/5022541/uploads/HMSDN3ANC449/image.png" alt="image.png" height="321" width="313" loading="lazy" data-display-size="large" data-float="none"></img></a> </div> </div> <h2 data-id="creating-a-new-widget-in-the-preview">Creating a new widget in the preview</h2><p>The preview page to the left needs a new <em>Discussion List </em>that we can preview while we customize it's variables.</p><h4 data-id="defining-the-preview-page-for-a-section">Defining the preview page for a section</h4><p>First, we will need to define the preview page for that panel section</p><pre class="code codeBlock" spellcheck="false" tabindex="0">// ThemeEditorPreviewPage.tsx function ThemePreviewPages() { const { activePanel } = useActivePanelContext(); let content: React.ReactNode = null; switch (activePanel) { case ActiveVariablePanel.BANNER: case ActiveVariablePanel.GLOBAL: case ActiveVariablePanel.TITLE_BAR: content = <ThemeEditorPreviewContentsGlobal />; break; case ActiveVariablePanel.QUICKLINKS: // Add the discussion list here: case ActiveVariablePanel.DISCUSSION_LIST: case ActiveVariablePanel.CONTENT_BANNER: content = <ThemeEditorPreviewContentsContent />; break; default: content = <ThemeEditorPreviewContentsGlobal />; break; } return <BannerContextProvider key={activePanel}>{content}</BannerContextProvider>; } </pre><p>Clicking on the Discussion List section will now change the preview page to <em>ThemeEditorPreviewContentsContent</em>. The name of this component seems confusing but it's structured like this: <em>ThemeEditor -- PreviewContents -- {Section}</em>. In this case the <em>Content </em>page matches the title of the dropdown we just edited.</p><h4 data-id="adding-a-component-to-the-preview-page.">Adding a component to the Preview Page.</h4><p>We can now add our <em>DiscussionListView</em> component to the preview page.</p><pre class="code codeBlock" spellcheck="false" tabindex="0">// ThemeEditorPreviewContentsContent.tsx export function ThemeEditorPreviewContentsContent() { const classes = themeEditorPreviewClasses(); return ( <div className={classes.content}> {/* This component will be tasked with selecting the right panel section when clicked */} <PanelActivator panel={ActiveVariablePanel.DISCUSSION_LIST} color={globalVariables().mainColors.fg} > <DiscussionListView discussions={themeEditorDiscussionData} /> </PanelActivator> </div> ); } </pre><p>We should now see this in the preview:</p><div class="embedExternal embedImage display-large float-none"> <div class="embedExternal-content"> <a class="embedImage-link" href="https://us.v-cdn.net/5022541/uploads/GGACF385EA16/image.png" rel="nofollow noreferrer noopener ugc" target="_blank"> <img class="embedImage-img" src="https://us.v-cdn.net/5022541/uploads/GGACF385EA16/image.png" alt="image.png" height="593" width="935" loading="lazy" data-display-size="large" data-float="none"></img></a> </div> </div> <p><br></p><h2 data-id="adding-variables-to-the-panel">Adding Variables to the panel</h2><div class="embedExternal embedImage display-large float-none"> <div class="embedExternal-content"> <a class="embedImage-link" href="https://us.v-cdn.net/5022541/uploads/SAJTKGGD389F/image.png" rel="nofollow noreferrer noopener ugc" target="_blank"> <img class="embedImage-img" src="https://us.v-cdn.net/5022541/uploads/SAJTKGGD389F/image.png" alt="image.png" height="396" width="370" loading="lazy" data-display-size="large" data-float="none"></img></a> </div> </div> <p><br></p><p>In order to add variables to our Discussion Lists panel, we will first need to add a section to the panel.</p><pre class="code codeBlock" spellcheck="false" tabindex="0">// ThemeBuilderPanel.tsx function PanelItems() { const { activePanel } = useActivePanelContext(); switch (activePanel) { case ActiveVariablePanel.QUICKLINKS: return <ThemeBuilderSectionPanel />; case ActiveVariablePanel.TITLE_BAR: return <ThemeBuilderSectionTitleBar />; case ActiveVariablePanel.BANNER: return <ThemeBuilderSectionBanner />; case ActiveVariablePanel.CONTENT_BANNER: return <ThemeBuilderSectionContentBanner />; case ActiveVariablePanel.WIDGETS: return <ThemeBuilderSectionWidgets />; // Our section goes here: case ActiveVariablePanel.DISCUSSION_LIST: return <ThemeBuilderSectionDiscussionList />; case ActiveVariablePanel.GLOBAL: default: return <ThemeBuilderSectionGlobal />; } } </pre><p>We can now create our section component</p><pre class="code codeBlock" spellcheck="false" tabindex="0">// ThemeBuilderSectionDiscussionList.tsx export function ThemeBuilderSectionDiscussionList() { return ( <> <ActivePanelChooser titlePanel={ActiveVariablePanel.DISCUSSION_LIST} /> {/* ... Our variables will go here. */} </> ); } </pre><h2 data-id="building-the-structure-of-our-panel-section">Building the structure of our panel section</h2><p>We now have an empty theme builder section. We can go ahead and create our editable variables using the following reusable components:</p><h4 data-id="themebuildersection">ThemeBuilderSection</h4><p>Delimited by a title and a line separator, they group together similar variables.</p><pre class="code codeBlock" spellcheck="false" tabindex="0"><ThemeBuilderBlock label={t("Background")}> <ThemeColorPicker variableKey={"discussionList.contentBoxes.depth1.background.color"} /> </ThemeBuilderBlock> </pre><h4 data-id="themebuilderblock">ThemeBuilderBlock</h4><p>A block creates a row of Label and Input for a variable</p><pre class="code codeBlock" spellcheck="false" tabindex="0"><ThemeBuilderSection label={t("List Item")}> <ThemeBuilderBlock label={t("Border")}> <ThemeDropDown variableKey={"discussionList.contentBoxes.depth2.borderType"} options={themeListBorderTypeOptions()} /> </ThemeBuilderBlock> </ThemeBuilderSection> </pre><h4 data-id="theme-inputs">Theme Inputs</h4><p>Various inputs are available to use inside of a <em>ThemeBuilderBlock</em>. These are found in <em>library/src/scripts/forms/themeEditor.</em></p><p>These are automatically bound to a variable of the theme, customized by the <em>variableKey</em> property.</p><pre class="code codeBlock" spellcheck="false" tabindex="0"><ThemeToggle variableKey={"discussionList.item.metas.display.lastUser"} /> </pre><h2 data-id="congratulations!">Congratulations!</h2><div class="js-embed embedResponsive" data-embedjson="{"height":900,"width":1600,"url":"https:\/\/gfycat.com\/ifr\/NaiveGoodKangaroo","embedType":"iframe"}"> <a href="https://gfycat.com/ifr/NaiveGoodKangaroo" rel="nofollow noreferrer ugc"> https://gfycat.com/ifr/NaiveGoodKangaroo </a> </div><p>That's it. You've created a whole new section to customize the theme. This will be very invaluable for clients and services alike, and adds immediate value to our product.</p> </article> </main>