Skip to content

Commit

Permalink
DOP-5093: make tabs selectors interactive on offline builds (#1325)
Browse files Browse the repository at this point in the history
  • Loading branch information
seungpark authored Dec 12, 2024
1 parent 9b05e57 commit 1357b26
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 32 deletions.
30 changes: 5 additions & 25 deletions gatsby-ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { renderToString } from 'react-dom/server';
import { theme } from './src/theme/docsTheme';
import EuclidCircularASemiBold from './src/styles/fonts/EuclidCircularA-Semibold-WebXL.woff';
import redirectBasedOnLang from './src/utils/head-scripts/redirect-based-on-lang';
import { getHtmlLangFormat } from './src/utils/locale';
import bindTabUI from './src/utils/head-scripts/offline-ui/tabs';
import { OFFLINE_HEAD_SCRIPTS } from './src/utils/head-scripts/offline-ui';
import { isOfflineDocsBuild } from './src/utils/is-offline-docs-build';
import updateSidenavHeight from './src/utils/head-scripts/offline-ui/sidenav';
import { getHtmlLangFormat } from './src/utils/locale';

export const onRenderBody = ({ setHeadComponents, setHtmlAttributes }) => {
if (isOfflineDocsBuild) {
return setHeadComponents([...OFFLINE_HEAD_SCRIPTS]);
}
const headComponents = [
// GTM Pathway
<script
Expand Down Expand Up @@ -88,28 +90,6 @@ export const onRenderBody = ({ setHeadComponents, setHtmlAttributes }) => {
/>
);
}
if (isOfflineDocsBuild) {
headComponents.push(
<script
key="offline-docs-ui"
type="text/javascript"
dangerouslySetInnerHTML={{
// Call function immediately on load
__html: `!${bindTabUI}()`,
}}
/>
);
headComponents.push(
<script
key="offline-sidenav-ui"
type="text/javascript"
dangerouslySetInnerHTML={{
// Call function immediately on load
__html: `!${updateSidenavHeight}()`,
}}
/>
);
}
setHtmlAttributes({
// Help work with translated content locally; Smartling should handle rewriting the lang
lang: process.env.GATSBY_LOCALE ? getHtmlLangFormat(process.env.GATSBY_LOCALE) : 'en',
Expand Down
75 changes: 74 additions & 1 deletion src/components/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Option, Select as LGSelect } from '@leafygreen-ui/select';
import PropTypes from 'prop-types';
import { palette } from '@leafygreen-ui/palette';
import { color, focusRing } from '@leafygreen-ui/tokens';
import Icon from '@leafygreen-ui/icon';
import { theme } from '../theme/docsTheme';
import { isOfflineDocsBuild } from '../utils/is-offline-docs-build';

const Label = styled('p')`
font-size: ${theme.fontSize.small};
Expand Down Expand Up @@ -63,6 +65,8 @@ const disabledLabelStyle = css`
const selectStyle = css`
> button {
background-color: ${color.light.background.primary.default};
text-align: left;
${isOfflineDocsBuild && `color: ${palette.black}`};
// Override button default color
> *:last-child {
Expand Down Expand Up @@ -154,12 +158,80 @@ const optionStyling = css`
}
`;

const OFFLINE_SELECT_ID = 'offline-select';

const PortalContainer = forwardRef(({ ...props }, ref) => (
<div className={cx(portalStyle, props.className)} ref={ref}>
<div id={isOfflineDocsBuild ? OFFLINE_SELECT_ID : null} className={cx(portalStyle, props.className)} ref={ref}>
{props.children}
</div>
));

const offlineMenuStyling = css`
position: absolute;
background: white;
font-size: 13px;
width: 100%;
`;

const offlineListStyle = css`
list-style: none;
padding: 8px 0px;
border-radius: 12px;
box-shadow: rgba(0, 30, 43, 0.25) 0px 4px 7px 0px;
margin: 6px 0 0 0;
max-height: 250px;
overflow: scroll;
`;

const offlineListItemStyle = css`
display: flex;
width: 100%;
outline: none;
overflow-wrap: anywhere;
position: relative;
padding: 8px 12px;
cursor: pointer;
color: ${palette.gray.dark3};
align-items: center;
line-height: ${theme.fontSize.default};
&:hover {
background-color: ${palette.gray.light2};
}
svg {
opacity: 0;
margin-right: 6px;
}
&[selected='true'] {
font-weight: bold;
svg {
opacity: 1;
}
}
`;

const OfflineMenu = ({ choices }) => {
return (
<div className={cx(offlineMenuStyling, 'offline-select-menu')}>
<ul className={cx(offlineListStyle)}>
{choices.map((choice, idx) => (
<li
className={cx('offline-select-choice', offlineListItemStyle)}
data-value={choice.value}
data-text={choice.text}
key={idx}
>
<Icon fill={palette.blue.base} glyph={'Checkmark'} />
<span>{choice.text}</span>
</li>
))}
</ul>
</div>
);
};

const Select = ({
className,
choices,
Expand Down Expand Up @@ -212,6 +284,7 @@ const Select = ({
</Option>
))}
</LGSelect>
{isOfflineDocsBuild && <OfflineMenu choices={choices} />}
</PortalContainer>
);
};
Expand Down
17 changes: 13 additions & 4 deletions src/components/Tabs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getNestedValue } from '../../utils/get-nested-value';
import { isBrowser } from '../../utils/is-browser';
import { getLocalValue } from '../../utils/browser-storage';
import { getPlaintext } from '../../utils/get-plaintext';
import { getOfflineId, TABS_ID } from '../../utils/head-scripts/offline-ui/tabs';
import { TABS_CLASSNAME, getOfflineId } from '../../utils/head-scripts/offline-ui/tabs';
import { isOfflineDocsBuild } from '../../utils/is-offline-docs-build';
import { TabContext } from './tab-context';

Expand Down Expand Up @@ -176,15 +176,21 @@ const Tabs = ({ nodeData: { children, options = {} }, page, ...rest }) => {
[activeTab, setActiveTab, tabIds, tabsetName]
);

// TODO: if this is a drivers tabs set, include drivers in classname

return (
<>
<div ref={scrollAnchorRef} aria-hidden="true"></div>
<CodeProvider>
<LeafyTabs
className={cx(getTabsStyling({ isHidden, isProductLanding }), isOfflineDocsBuild ? TABS_ID : '')}
className={cx(
getTabsStyling({ isHidden, isProductLanding }),
isOfflineDocsBuild ? TABS_CLASSNAME : '',
tabsetName
)}
aria-label={`Tabs to describe usage of ${tabsetName}`}
selected={activeTab}
id={isOfflineDocsBuild ? `${TABS_ID}-${getOfflineId(tabsetName)}` : undefined}
id={isOfflineDocsBuild ? `${TABS_CLASSNAME}-${getOfflineId(tabsetName)}` : undefined}
setSelected={handleClick}
forceRenderAllTabPanels={true}
>
Expand All @@ -204,7 +210,10 @@ const Tabs = ({ nodeData: { children, options = {} }, page, ...rest }) => {
<HeadingContextProvider
heading={lastHeading ? `${lastHeading} - ${getPlaintext(tab.argument)}` : getPlaintext(tab.argument)}
>
<div className={cx(tabContentStyling, isProductLanding ? productLandingTabContentStyling : '')}>
<div
data-value={isOfflineDocsBuild ? tabId : null}
className={cx(tabContentStyling, isProductLanding ? productLandingTabContentStyling : '')}
>
{tab.children.map((child, i) => (
<ComponentFactory {...rest} key={`${tabId}-${i}`} nodeData={child} />
))}
Expand Down
17 changes: 17 additions & 0 deletions src/utils/head-scripts/offline-ui/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import bindTabUI from './tabs';
import updateSidenavHeight from './sidenav';
import bindTabsSelectorsUI from './tabs-selectors';
const OFFLINE_UI_CLASSNAME = 'snooty-offline-ui';

const getScript = ({ key, fn }) => (
<script
className={OFFLINE_UI_CLASSNAME}
key={key}
type="text/javascript"
dangerouslySetInnerHTML={{ __html: `!${fn}()` }}
/>
);

export const OFFLINE_HEAD_SCRIPTS = [bindTabUI, updateSidenavHeight, bindTabsSelectorsUI].map((fn, idx) =>
getScript({ key: `offline-docs-ui-${idx}`, fn })
);
72 changes: 72 additions & 0 deletions src/utils/head-scripts/offline-ui/tabs-selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Tabs selectors are singular per page
* For offline builds, refer to one ID from TabSelectors component
*
*/

function bindTabsSelectorsUI() {
function onChoiceClick({ e, choices, tabsComponents, menu, button }) {
// get the data-value attribute
const tabName = e.currentTarget.getAttribute('data-value');

// set choice selected for styling css
for (const choice of choices) {
choice.setAttribute('selected', false);
}
e.currentTarget.setAttribute('selected', true);

// handle the tabsComponents
for (const tabsComponent of tabsComponents) {
// find the tab within each tabsComponent
const tabContent = tabsComponent.querySelector(`[data-value=${tabName}`);
if (!tabContent) {
continue;
}
// if it exists, hide all other tabs, and show this tab
for (const tabPanel of tabsComponent.querySelectorAll('[role=tabpanel]')) {
tabPanel.style.display = tabPanel.contains(tabContent) ? 'block' : 'none';
}
}
const buttonChildren = button.querySelectorAll('div');
const selectTextelm = buttonChildren[buttonChildren.length - 1];
selectTextelm.innerText = e.currentTarget.getAttribute('data-text');

// finally hide the menu
menu.setAttribute('hidden', true);
}

const onContentLoaded = () => {
const selectPortal = document.querySelector('#offline-select');

// bind menu opening to select component
const button = selectPortal?.querySelector('button');
const menu = selectPortal?.querySelector('.offline-select-menu');
button?.addEventListener('click', () => {
menu.toggleAttribute('hidden');
});

// bind choices selection to showing tabbed content
const choices = menu?.querySelectorAll('.offline-select-choice') ?? [];
const tabsComponents = document.querySelectorAll('.offline-tabs.drivers');
for (const choice of choices) {
choice?.addEventListener('click', (e) => {
onChoiceClick({ e, choices, tabsComponents, menu, button });
});
}

// select the first choice on load
// TODO: possibly add to local storage and store info here
choices[0].click();

// bind document click to close menu
document.addEventListener('click', (e) => {
if (!selectPortal.contains(e.target)) {
menu.setAttribute('hidden', true);
}
});
};

document.addEventListener('DOMContentLoaded', onContentLoaded, false);
}

export default bindTabsSelectorsUI;
2 changes: 1 addition & 1 deletion src/utils/head-scripts/offline-ui/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ export function getOfflineId(tabsetName) {
return tabsetName.replace(/[^a-zA-Z0-9]/g, '-');
}

export const TABS_ID = `offline-tabs`;
export const TABS_CLASSNAME = `offline-tabs`;
3 changes: 2 additions & 1 deletion src/utils/setup/save-asset-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ const { siteMetadata } = require('../site-metadata');

const GATSBY_IMAGE_EXTENSIONS = ['webp', 'png', 'avif'];

const needsImageOptimization = ['dotcomprd', 'dotcomstg'].includes(siteMetadata.snootyEnv);
const needsImageOptimization =
['dotcomprd', 'dotcomstg'].includes(siteMetadata.snootyEnv) && process.env['OFFLINE_DOCS'] !== 'true';

const saveFile = async (file, data) => {
// save files both to "public" and "src/images" directories
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/__snapshots__/SearchResults.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,7 @@ exports[`Search Results Page considers a given search filter query param and dis
.emotion-63>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-63>button>*:last-child>svg {
Expand Down Expand Up @@ -1649,6 +1650,7 @@ exports[`Search Results Page considers a given search filter query param and dis
.emotion-75>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-75>button>*:last-child>svg {
Expand Down Expand Up @@ -2683,6 +2685,7 @@ exports[`Search Results Page does not return results for a given search term wit
.emotion-35>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-35>button>*:last-child>svg {
Expand Down Expand Up @@ -2922,6 +2925,7 @@ exports[`Search Results Page does not return results for a given search term wit
.emotion-47>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-47>button>*:last-child>svg {
Expand Down Expand Up @@ -4465,6 +4469,7 @@ exports[`Search Results Page renders loading images before returning no results
.emotion-128>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-128>button>*:last-child>svg {
Expand Down Expand Up @@ -4704,6 +4709,7 @@ exports[`Search Results Page renders loading images before returning no results
.emotion-140>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-140>button>*:last-child>svg {
Expand Down Expand Up @@ -5962,6 +5968,7 @@ exports[`Search Results Page renders loading images before returning nonempty re
.emotion-128>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-128>button>*:last-child>svg {
Expand Down Expand Up @@ -6201,6 +6208,7 @@ exports[`Search Results Page renders loading images before returning nonempty re
.emotion-140>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-140>button>*:last-child>svg {
Expand Down Expand Up @@ -7726,6 +7734,7 @@ exports[`Search Results Page renders results from a given search term query para
.emotion-58>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-58>button>*:last-child>svg {
Expand Down Expand Up @@ -7965,6 +7974,7 @@ exports[`Search Results Page renders results from a given search term query para
.emotion-70>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-70>button>*:last-child>svg {
Expand Down
1 change: 1 addition & 0 deletions tests/unit/__snapshots__/Select.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ exports[`Select renders select correctly 1`] = `
.emotion-1>button {
background-color: #FFFFFF;
text-align: left;
}
.emotion-1>button>*:last-child>svg {
Expand Down

0 comments on commit 1357b26

Please sign in to comment.