Skip to content
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

DOP-3424: "On this page" subnav incorrectly displays headings for all language tabs #1262

Merged
merged 21 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions src/components/Contents/contents-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ const defaultContextValue = {
activeHeadingId: null,
headingNodes: [],
showContentsComponent: true,
activeSelectorId: null,
setActiveSelectorId: () => {},
activeSelectorIds: {},
mayaraman19 marked this conversation as resolved.
Show resolved Hide resolved
setActiveSelectorIds: () => {},
};

const ContentsContext = React.createContext(defaultContextValue);

const ContentsProvider = ({ children, headingNodes = [] }) => {
const activeHeadingId = useActiveHeading(headingNodes);
const [activeSelectorId, setActiveSelectorId] = useState(null);
const [activeSelectorIds, setActiveSelectorIds] = useState({});

const { project } = useSnootyMetadata();
// The guides site is the only site that takes advantage of headings, but never uses the Contents component
const showContentsComponent = project !== 'guides';

return (
<ContentsContext.Provider
value={{ activeHeadingId, headingNodes, showContentsComponent, activeSelectorId, setActiveSelectorId }}
value={{ activeHeadingId, headingNodes, showContentsComponent, activeSelectorIds, setActiveSelectorIds }}
>
{children}
</ContentsContext.Provider>
Expand Down
42 changes: 37 additions & 5 deletions src/components/Contents/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useContext } from 'react';
import { isEmpty } from 'lodash';
import PropTypes from 'prop-types';
import { formatText } from '../../utils/format-text';
import { ContentsContext } from './contents-context';
Expand All @@ -9,13 +10,44 @@ const formatTextOptions = {
literalEnableInline: true,
};

/* recursively go through selector ids defined by parser
everything in headingSelectorIds must be present in activeSelectorIds
activeSelectorIds structure:
{
methodSelector?: str,
tab?: [str],
}
headingSelectorIds structure (comes from parser):
{
method-option | tab: str,
children?: {
tab: str,
children?: {
tab: str,
...
}
}
}
*/
const isHeadingVisible = (headingSelectorIds, activeSelectorIds) => {
if (!headingSelectorIds || isEmpty(headingSelectorIds)) return true;
const headingsMethodParent = headingSelectorIds['method-option'];
const headingsTabParent = headingSelectorIds['tab'];
if (
(headingsMethodParent && headingsMethodParent !== activeSelectorIds.methodSelector) ||
(headingsTabParent && !activeSelectorIds.tab?.includes(headingsTabParent))
) {
return false;
}
return isHeadingVisible(headingSelectorIds.children ?? {}, activeSelectorIds);
};

const Contents = ({ className }) => {
const { activeHeadingId, headingNodes, showContentsComponent, activeSelectorId } = useContext(ContentsContext);
const { activeHeadingId, headingNodes, showContentsComponent, activeSelectorIds } = useContext(ContentsContext);

// Don't filter if selector_id is null/undefined
const filteredNodes = headingNodes.filter(
(headingNode) => !headingNode.selector_id || headingNode.selector_id === activeSelectorId
);
const filteredNodes = headingNodes.filter((headingNode) => {
return isHeadingVisible(headingNode.selector_ids, activeSelectorIds);
});

if (filteredNodes.length === 0 || !showContentsComponent) {
return null;
Expand Down
14 changes: 8 additions & 6 deletions src/components/MethodSelector/MethodSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@ const hrStyle = css`
const MethodSelector = ({ nodeData: { children } }) => {
const optionCount = children.length;
const [selectedMethod, setSelectedMethod] = useState(children[0]?.options?.id);
const { setActiveSelectorId } = useContext(ContentsContext);
const { activeSelectorIds, setActiveSelectorIds } = useContext(ContentsContext);
const [selectedIdx, setSelectedIdx] = useState(0);

// set on initial load
// Load method ID saved from last session, if applicable.
useEffect(() => {
const savedMethodId = getLocalValue(STORAGE_KEY);
Expand All @@ -119,12 +120,12 @@ const MethodSelector = ({ nodeData: { children } }) => {
if (savedMethodId && validOptions.includes(savedMethodId)) {
setSelectedMethod(savedMethodId);
setSelectedIdx(validOptions.indexOf(savedMethodId));
if (activeSelectorIds?.methodSelector !== savedMethodId)
setActiveSelectorIds({ ...activeSelectorIds, methodSelector: savedMethodId });
} else if (activeSelectorIds?.methodSelector !== children[0]?.options?.id) {
setActiveSelectorIds({ ...activeSelectorIds, methodSelector: children[0]?.options?.id });
}
}, [children]);

useEffect(() => {
setActiveSelectorId(selectedMethod);
}, [selectedMethod, setActiveSelectorId]);
}, [activeSelectorIds, setActiveSelectorIds, children]);
rayangler marked this conversation as resolved.
Show resolved Hide resolved

return (
<>
Expand All @@ -135,6 +136,7 @@ const MethodSelector = ({ nodeData: { children } }) => {
onChange={({ target: { defaultValue } }) => {
const [id, idx] = defaultValue.split('-');
setSelectedMethod(id);
setActiveSelectorIds({ ...activeSelectorIds, methodSelector: id });
setSelectedIdx(idx);
setLocalValue(STORAGE_KEY, id);
reportAnalytics('MethodOptionSelected', {
Expand Down
22 changes: 20 additions & 2 deletions src/components/Tabs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { Tabs as LeafyTabs, Tab as LeafyTab } from '@leafygreen-ui/tabs';
import { palette } from '@leafygreen-ui/palette';
import { CodeProvider } from '../Code/code-context';
import ComponentFactory from '../ComponentFactory';
import { HeadingContextProvider, useHeadingContext } from '../../context/heading-context';
import { theme } from '../../theme/docsTheme';
import { reportAnalytics } from '../../utils/report-analytics';
import { getNestedValue } from '../../utils/get-nested-value';
import { isBrowser } from '../../utils/is-browser';
import { HeadingContextProvider, useHeadingContext } from '../../context/heading-context';
import { getLocalValue } from '../../utils/browser-storage';
import { getPlaintext } from '../../utils/get-plaintext';
import { TabContext } from './tab-context';

Expand Down Expand Up @@ -119,6 +120,20 @@ const Tabs = ({ nodeData: { children, options = {} }, page, ...rest }) => {
const isProductLanding = page?.options?.template === 'product-landing';
const { lastHeading } = useHeadingContext();

const initLoad = useRef(false);

// get non-TabSelector tabs in localstorage
useEffect(() => {
if (initLoad.current) return;
initLoad.current = true;

const localTabs = getLocalValue('activeTabs');
let activeTabIdx = tabIds.indexOf(localTabs?.[tabsetName]);
activeTabIdx = activeTabIdx > -1 ? activeTabIdx : 0;
setActiveTabIndex(activeTabIdx);
setActiveTab({ [tabsetName]: tabIds[activeTabIdx] });
}, [setActiveTab, tabIds, tabsetName]);

useEffect(() => {
const index = tabIds.indexOf(activeTabs[tabsetName]);
if (index !== -1) {
Expand All @@ -128,6 +143,9 @@ const Tabs = ({ nodeData: { children, options = {} }, page, ...rest }) => {

const handleClick = useCallback(
(index) => {
if (activeTab === index) {
return;
}
const tabId = tabIds[index];
const priorAnchorOffset = getPosition(scrollAnchorRef.current).y;

Expand All @@ -142,7 +160,7 @@ const Tabs = ({ nodeData: { children, options = {} }, page, ...rest }) => {
window.scrollTo(0, getPosition(scrollAnchorRef.current).y + window.scrollY - priorAnchorOffset);
}, 40);
},
[setActiveTab, tabIds, tabsetName] // eslint-disable-line react-hooks/exhaustive-deps
[activeTab, setActiveTab, tabIds, tabsetName]
);

return (
Expand Down
15 changes: 13 additions & 2 deletions src/components/Tabs/tab-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
* child components to read and update
*/

import React, { useEffect, useReducer, useRef } from 'react';
import React, { useContext, useEffect, useReducer, useRef } from 'react';
import { isEmpty } from 'lodash';
import { getLocalValue, setLocalValue } from '../../utils/browser-storage';
import { DRIVER_ICON_MAP } from '../icons/DriverIconMap';
import { ContentsContext } from '../Contents/contents-context';
import { makeChoices } from './make-choices';

const defaultContextValue = {
Expand Down Expand Up @@ -54,14 +56,23 @@ const getLocalTabs = (localTabs, selectors) =>
const TabProvider = ({ children, selectors = {} }) => {
// init value to {} to match server and client side
const [activeTabs, setActiveTab] = useReducer(reducer, {});
const { setActiveSelectorIds } = useContext(ContentsContext);

const initLoaded = useRef(false);

useEffect(() => {
// dont update local value on initial load
if (!initLoaded.current) return;
setLocalValue('activeTabs', activeTabs);
}, [activeTabs]);

if (isEmpty(activeTabs)) {
return;
}

// on Tab update, update the active selector ids
// so headings can be shown/hidden
setActiveSelectorIds((activeSelectorIds) => ({ ...activeSelectorIds, tab: Object.values(activeTabs) }));
}, [activeTabs, setActiveSelectorIds]);

// initial effect to read from local storage
// used in an effect to keep SSG HTML consistent
Expand Down
Loading