Skip to content

Commit

Permalink
DOP-4566: set init values for tabs and tab selectors during build time (
Browse files Browse the repository at this point in the history
  • Loading branch information
seungpark authored May 1, 2024
1 parent 21a7e99 commit 7bcb7cb
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 73 deletions.
53 changes: 31 additions & 22 deletions src/components/DocumentBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import DocsLandingSD from './StructuredData/DocsLandingSD';
import BreadcrumbSchema from './StructuredData/BreadcrumbSchema';
import { InstruqtProvider } from './Instruqt/instruqt-context';
import { SuspenseHelper } from './SuspenseHelper';
import { TabProvider } from './Tabs/tab-context';

// lazy load the unified footer to improve page load speed
const LazyFooter = lazy(() => import('./Footer'));
Expand Down Expand Up @@ -103,28 +104,30 @@ const DocumentBody = (props) => {

return (
<>
<InstruqtProvider hasLabDrawer={page?.options?.instruqt}>
<Widgets
location={location}
pageOptions={page?.options}
pageTitle={pageTitle}
slug={slug}
isInPresentationMode={isInPresentationMode}
template={template}
>
<ImageContextProvider images={props.data?.pageImage?.images ?? []}>
<FootnoteContext.Provider value={{ footnotes }}>
<div id="template-container">
<Template {...props} useChatbot={useChatbot}>
{pageNodes.map((child, index) => (
<ComponentFactory key={index} metadata={metadata} nodeData={child} page={page} slug={slug} />
))}
</Template>
</div>
</FootnoteContext.Provider>
</ImageContextProvider>
</Widgets>
</InstruqtProvider>
<TabProvider selectors={page?.options?.selectors}>
<InstruqtProvider hasLabDrawer={page?.options?.instruqt}>
<Widgets
location={location}
pageOptions={page?.options}
pageTitle={pageTitle}
slug={slug}
isInPresentationMode={isInPresentationMode}
template={template}
>
<ImageContextProvider images={props.data?.pageImage?.images ?? []}>
<FootnoteContext.Provider value={{ footnotes }}>
<div id="template-container">
<Template {...props} useChatbot={useChatbot}>
{pageNodes.map((child, index) => (
<ComponentFactory key={index} metadata={metadata} nodeData={child} page={page} slug={slug} />
))}
</Template>
</div>
</FootnoteContext.Provider>
</ImageContextProvider>
</Widgets>
</InstruqtProvider>
</TabProvider>
{!isInPresentationMode && (
<div data-testid="consistent-footer" id="footer-container">
<SuspenseHelper fallback={null}>
Expand All @@ -148,6 +151,12 @@ DocumentBody.propTypes = {
pageContext: PropTypes.shape({
slug: PropTypes.string.isRequired,
}),
data: PropTypes.shape({
page: PropTypes.shape({
children: PropTypes.array,
options: PropTypes.object,
}).isRequired,
}).isRequired,
};

export default DocumentBody;
Expand Down
39 changes: 21 additions & 18 deletions src/components/DocumentBodyPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SEO from './SEO';
import FootnoteContext from './Footnote/footnote-context';
import ComponentFactory from './ComponentFactory';
import { InstruqtProvider } from './Instruqt/instruqt-context';
import { TabProvider } from './Tabs/tab-context';

// Identify the footnotes on a page and all footnote_reference nodes that refer to them.
// Returns a map wherein each key is the footnote name, and each value is an object containing:
Expand Down Expand Up @@ -102,24 +103,26 @@ const DocumentBody = (props) => {
metadata={metadata}
>
<SEO pageTitle={pageTitle} siteTitle={siteTitle} />
<InstruqtProvider hasLabDrawer={page?.options?.instruqt}>
<Widgets
location={location}
pageOptions={page?.options}
pageTitle={pageTitle}
slug={slug}
template={template}
isInPresentationMode={isInPresentationMode}
>
<FootnoteContext.Provider value={{ footnotes }}>
<Template {...props} useChatbot={useChatbot}>
{pageNodes.map((child, index) => (
<ComponentFactory key={index} metadata={metadata} nodeData={child} page={page} slug={slug} />
))}
</Template>
</FootnoteContext.Provider>
</Widgets>
</InstruqtProvider>
<TabProvider selectors={page?.options?.selectors}>
<InstruqtProvider hasLabDrawer={page?.options?.instruqt}>
<Widgets
location={location}
pageOptions={page?.options}
pageTitle={pageTitle}
slug={slug}
template={template}
isInPresentationMode={isInPresentationMode}
>
<FootnoteContext.Provider value={{ footnotes }}>
<Template {...props} useChatbot={useChatbot}>
{pageNodes.map((child, index) => (
<ComponentFactory key={index} metadata={metadata} nodeData={child} page={page} slug={slug} />
))}
</Template>
</FootnoteContext.Provider>
</Widgets>
</InstruqtProvider>
</TabProvider>
<footer style={{ width: '100%', height: '568px', backgroundColor: '#061621' }}></footer>
</Layout>
);
Expand Down
24 changes: 10 additions & 14 deletions src/components/RootProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,25 @@ import { VersionContextProvider } from '../context/version-context';
import { TocContextProvider } from '../context/toc-context';
import { HeaderContextProvider } from './Header/header-context';
import { SidenavContextProvider } from './Sidenav';
import { TabProvider } from './Tabs/tab-context';
import { ContentsProvider } from './Contents/contents-context';

const RootProvider = ({ children, headingNodes, selectors, slug, repoBranches, remoteMetadata, project }) => {
const RootProvider = ({ children, headingNodes, slug, repoBranches, remoteMetadata }) => {
return (
<TabProvider selectors={selectors}>
<ContentsProvider headingNodes={headingNodes}>
<HeaderContextProvider>
<VersionContextProvider repoBranches={repoBranches} slug={slug}>
<TocContextProvider remoteMetadata={remoteMetadata}>
<SidenavContextProvider>{children}</SidenavContextProvider>
</TocContextProvider>
</VersionContextProvider>
</HeaderContextProvider>
</ContentsProvider>
</TabProvider>
<ContentsProvider headingNodes={headingNodes}>
<HeaderContextProvider>
<VersionContextProvider repoBranches={repoBranches} slug={slug}>
<TocContextProvider remoteMetadata={remoteMetadata}>
<SidenavContextProvider>{children}</SidenavContextProvider>
</TocContextProvider>
</VersionContextProvider>
</HeaderContextProvider>
</ContentsProvider>
);
};

RootProvider.propTypes = {
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
headingNodes: PropTypes.arrayOf(PropTypes.object),
selectors: PropTypes.object,
};

export default RootProvider;
2 changes: 1 addition & 1 deletion src/components/Tabs/TabSelectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const getLabel = (name) => {
}
};

const makeChoices = ({ name, iconMapping, options }) =>
export const makeChoices = ({ name, iconMapping, options }) =>
Object.entries(options).map(([tabId, title]) => ({
text: getPlaintext(title),
value: tabId,
Expand Down
21 changes: 6 additions & 15 deletions src/components/Tabs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,27 +89,18 @@ const Tabs = ({ nodeData: { children, options = {} }, page, ...rest }) => {
const { activeTabs, selectors, setActiveTab } = useContext(TabContext);
const tabIds = children.map((child) => getTabId(child));
const tabsetName = options.tabset || generateAnonymousTabsetName(tabIds);
const [activeTab, setActiveTabIndex] = useState(0);
const [activeTab, setActiveTabIndex] = useState(() => {
// activeTabIdx at build time should be -1 if tabsetName !== drivers
// since no local storage to read, and no default tabs
const activeTabIdx = tabIds.indexOf(activeTabs?.[tabsetName]);
return activeTabIdx > -1 ? activeTabIdx : 0;
});

const scrollAnchorRef = useRef();
const previousTabsetChoice = activeTabs[tabsetName];
// Hide tabset if it includes the :hidden: option, or if it is controlled by a dropdown selector
const isHidden = options.hidden || Object.keys(selectors).includes(tabsetName);
const isProductLanding = page?.options?.template === 'product-landing';

useEffect(() => {
if (!previousTabsetChoice || !tabIds.includes(previousTabsetChoice)) {
const index = children.findIndex((item) => item.options.tabid === 'nodejs');
if (index > -1) {
// Set first tab to nodejs if no tab was previously selected
setActiveTab({ name: tabsetName, value: getTabId(children[index]) });
} else {
// Set first tab as active if no tab was previously selected and cant find nodejs
setActiveTab({ name: tabsetName, value: getTabId(children[0]) });
}
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps

useEffect(() => {
const index = tabIds.indexOf(activeTabs[tabsetName]);
if (index !== -1) {
Expand Down
60 changes: 59 additions & 1 deletion src/components/Tabs/tab-context.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/**
* Context that contains language selector tab data
* Stores which tab is currently active by reading local storage
* or by setting default values, and allows
* child components to read and update
*/

import React, { useEffect, useReducer } from 'react';
import IconC from '../icons/C';
import IconCompass from '../icons/Compass';
Expand All @@ -19,6 +26,8 @@ import IconObjectiveC from '../icons/ObjectiveC';
import IconJavascript from '../icons/Javascript';
import IconTypescript from '../icons/Typescript';
import IconDart from '../icons/Dart';
import { isBrowser } from '../../utils/is-browser';
import { makeChoices } from './TabSelectors';

const DRIVER_ICON_MAP = {
c: IconC,
Expand Down Expand Up @@ -64,8 +73,57 @@ const reducer = (prevState, { name, value }) => {
};
};

const initActiveTabs = (choicesPerSelector, localActiveTabs) => {
// all tabbed content is read from browser local storage
// if there is no browser, wait for client side local storage
// hidden content should be handled from tab components
if (!isBrowser) {
return {};
}
// get default tabs based on availability
const defaultRes = Object.keys(choicesPerSelector || {}).reduce((res, selectorKey) => {
const nodeOptionIdx = choicesPerSelector[selectorKey].findIndex((tab) => tab.value === 'nodejs');
// NOTE: default tabs should be specified here
if (selectorKey === 'drivers' && nodeOptionIdx > -1) {
res[selectorKey] = 'nodejs';
} else {
res[selectorKey] = choicesPerSelector[selectorKey][0].value;
}
return res;
}, {});

// get local active tabs
const activeTabsRes = Object.keys(localActiveTabs || {}).reduce((res, activeTabKey) => {
if (typeof localActiveTabs[activeTabKey] === 'string') {
res[activeTabKey] = localActiveTabs[activeTabKey];
}
return res;
}, {});

// tabs initialize with default tabs overwritten by local storage tabs
const initialTabs = { ...defaultRes, ...activeTabsRes };
setLocalValue('activeTabs', initialTabs);
return initialTabs;
};

const TabProvider = ({ children, selectors = {} }) => {
const [activeTabs, setActiveTab] = useReducer(reducer, getLocalValue('activeTabs') || defaultContextValue.activeTabs);
// convert selectors to tab options first here, then set init values
// selectors are determined at build time

const choicesPerSelector = Object.keys(selectors).reduce((res, selector) => {
res[selector] = makeChoices({
name: selector,
options: selectors[selector],
...(selector === 'drivers' && { iconMapping: DRIVER_ICON_MAP }),
});
return res;
}, {});

const [activeTabs, setActiveTab] = useReducer(
reducer,
getLocalValue('activeTabs'),
initActiveTabs.bind(null, choicesPerSelector)
);

useEffect(() => {
setLocalValue('activeTabs', activeTabs);
Expand Down
7 changes: 6 additions & 1 deletion src/layouts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ const DefaultLayout = ({ children, data: { page }, pageContext: { slug, repoBran
slug={slug}
repoBranches={repoBranches}
headingNodes={page?.ast?.options?.headings}
selectors={page?.ast?.options?.selectors}
remoteMetadata={remoteMetadata}
project={project}
>
Expand Down Expand Up @@ -140,6 +139,12 @@ DefaultLayout.propTypes = {
slug: PropTypes.string,
template: PropTypes.string,
}).isRequired,
data: PropTypes.shape({
page: PropTypes.shape({
children: PropTypes.array,
options: PropTypes.object,
}).isRequired,
}).isRequired,
};

export default DefaultLayout;
1 change: 0 additions & 1 deletion src/layouts/preview-layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ const DefaultLayout = ({
repoBranches={repoBranches}
associatedReposInfo={associatedReposInfo}
headingNodes={page?.options?.headings}
selectors={page?.options?.selectors}
isAssociatedProduct={isAssociatedProduct}
>
<GlobalGrid>
Expand Down

0 comments on commit 7bcb7cb

Please sign in to comment.