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-5093: make tabs selectors interactive on offline builds #1325

Merged
merged 6 commits into from
Dec 12, 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
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 =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor change to prevent image optimization from offline builds. will put in a separate PR if preferred!

['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
Loading