Skip to content

Commit bacb43d

Browse files
sharmrjmokimo
authored andcommitted
MWPW-161273 Standalone Gnav needs a release cycle [Bundle] (adobecom#3132)
* bundled standalone gnav and footer * Fixed dark mode; load css from navigation.js * Refactored global footer to no longer use milo modal to render region-nav * global footer unit test * fix lint * Added keyboard navigation to the new region nav modal * export stuff from modal.js * replaced the new modal implementation with a more explicit usage of the current milo modal so that it can be bundled * code compatibility ignore pattern * Fix keyboard navigation unit tests * fixed footer unit test after changing the implementation of the region nav (again) * added sourcemaps; fixed dark mode issue * navigation unit tests * Cover uncovered lines in global-footer.js * prevent FOUC in region nav * built latest gnav changes * modified package.json to have a files field * use evergreen css for non-bundled and built css for bundled * Fixed region picker when there's no hash * Added a workflow to release standalone feds and removed dist from the PR * changed workflow_call to workflow_dispatch * Adjusted the cd command in the workflow * added a working directory * missed a space in the gh release upload command * added GITHUB_TOKEN to the upload asset step * fixed an error with file upload in the workflow * Removed a console.log from the build script; explicitly load fragment block in footer * Removed an unused import * Renamed a funciton in the build file and added a comment * Fixed region nav breaking on certain milo consumers * Fixed region nav breaking on some milo consumers for real this time * Removed an unused import * Added logic to not call the region nav code twice * unit test * modified a standalone footer unit test slightly * Removed a comment
1 parent 1aa4831 commit bacb43d

27 files changed

+750
-58
lines changed

.eslintrc-code-compatibility.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
],
1515
ignorePatterns: [
1616
'/libs/deps/*',
17+
'/libs/navigation/dist/*',
1718
'/tools/loc/*',
1819
],
1920
};

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ module.exports = {
6262
'/libs/features/mas/*',
6363
'/tools/loc/*',
6464
'/libs/features/spectrum-web-components/*',
65+
'/libs/navigation/dist/*',
6566
],
6667
plugins: [
6768
'chai-friendly',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Create a Release for Standalone Feds GlobalNav and Footer
2+
on:
3+
workflow_dispatch:
4+
inputs:
5+
version:
6+
description: 'Version'
7+
required: true
8+
type: string
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
release-feds:
15+
name: Release Standalone Feds
16+
runs-on: ubuntu-latest
17+
strategy:
18+
matrix:
19+
node-version: [20.x]
20+
defaults:
21+
run:
22+
working-directory: ./libs/navigation
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v4
26+
with:
27+
fetch-depth: 2
28+
29+
- name: Set up Node.js ${{ matrix.node-version }}
30+
uses: actions/setup-node@v4
31+
with:
32+
node-version: ${{ matrix.node-version }}
33+
34+
- name: Install dependencies
35+
run: npm install
36+
37+
- name: Build Files
38+
run: node ./build.mjs
39+
40+
- name: Generate tarball
41+
run: npm pack
42+
43+
- name: Create Release
44+
env:
45+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46+
run: |
47+
gh release create "feds-standalone-v${{ inputs.version }}" \
48+
--repo="$GITHUB_REPOSITORY" \
49+
--title="@adobecom/standalone-feds v${{ inputs.version }} Release" \
50+
--generate-notes
51+
52+
- name: Upload Files to Release
53+
env:
54+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
run: gh release upload "feds-standalone-v${{ inputs.version }}" "adobecom-standalone-feds-${{ inputs.version }}.tgz"

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ logs/*
1111
test-html-results/
1212
test-results/
1313
test-a11y-results/
14+
libs/navigation/dist/

libs/blocks/global-footer/global-footer.css

+14
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,20 @@
252252
height: 12px;
253253
}
254254

255+
@media (min-width: 600px) {
256+
dialog.feds-dialog {
257+
max-width: 80vw;
258+
width: fit-content;
259+
}
260+
}
261+
262+
@media (min-width: 1200px) {
263+
dialog.feds-dialog {
264+
width: 1200px;
265+
max-width: calc((100% - 6px) - 2em);
266+
}
267+
}
268+
255269
@media (min-width: 900px) {
256270
/* If there is too much content, float it on multiple rows */
257271
.feds-footer-wrapper .feds-menu-content {

libs/blocks/global-footer/global-footer.js

+34-6
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import {
44
decorateLinks,
55
getMetadata,
66
getConfig,
7-
loadBlock,
87
localizeLink,
8+
loadStyle,
99
} from '../../utils/utils.js';
1010

1111
import {
@@ -217,6 +217,8 @@ class Footer {
217217
</svg>
218218
${regionPickerTextElem}
219219
</a>`;
220+
regionPickerElem.dataset.modalPath = `${url.pathname}#_inline`;
221+
regionPickerElem.dataset.modalHash = url.hash;
220222
const regionPickerWrapperClass = 'feds-regionPicker-wrapper';
221223
this.elements.regionPicker = toFragment`<div class="${regionPickerWrapperClass}">
222224
${regionPickerElem}
@@ -230,24 +232,48 @@ class Footer {
230232
// Hash -> region selector opens a modal
231233
decorateAutoBlock(regionPickerElem); // add modal-specific attributes
232234
// TODO remove logs after finding the root cause for the region picker 404s -> MWPW-143627
235+
regionPickerElem.href = url.hash;
233236
if (regionPickerElem.classList[0] !== 'modal') {
234237
lanaLog({
235238
message: `Modal block class missing from region picker pre loading the block; locale: ${locale}; regionPickerElem: ${regionPickerElem.outerHTML}`,
236239
tags: 'errorType=warn,module=global-footer',
237240
});
238241
}
239-
await loadBlock(regionPickerElem); // load modal logic and styles
242+
loadStyle(`${base}/blocks/modal/modal.css`);
243+
const { default: initModal } = await import('../modal/modal.js');
244+
const modal = await initModal(regionPickerElem);
245+
246+
const loadRegionNav = async () => {
247+
const block = document.querySelector('.region-nav');
248+
if (block && getConfig().standaloneGnav) {
249+
// on standalone the region-nav will fail to load automatically through
250+
// the modal calling fragment.js. In that case we will have data-failed=true
251+
// and we should manually load region nav
252+
// If that's not the case then we're not a standalone gnav
253+
// and we mustn't load region-nav twice.
254+
if (block.getAttribute('data-failed') !== 'true') return;
255+
block.classList.add('hide');
256+
loadStyle(`${base}/blocks/region-nav/region-nav.css`);
257+
const { default: initRegionNav } = await import('../region-nav/region-nav.js');
258+
initRegionNav(block);
259+
// decoratePlaceholders(block, getConfig());
260+
block.classList.remove('hide');
261+
}
262+
};
263+
264+
if (modal) await loadRegionNav(); // just in case the modal is already open
265+
240266
if (regionPickerElem.classList[0] !== 'modal') {
241267
lanaLog({
242268
message: `Modal block class missing from region picker post loading the block; locale: ${locale}; regionPickerElem: ${regionPickerElem.outerHTML}`,
243269
tags: 'errorType=warn,module=global-footer',
244270
});
245271
}
246-
// 'decorateAutoBlock' logic replaces class name entirely, need to add it back
247-
regionPickerElem.classList.add(regionPickerClass);
248272
regionPickerElem.addEventListener('click', () => {
249273
if (!isRegionPickerExpanded()) {
250274
regionPickerElem.setAttribute('aria-expanded', 'true');
275+
// wait for the modal to load before we load the region nav
276+
window.addEventListener('milo:modal:loaded', loadRegionNav, { once: true });
251277
}
252278
});
253279
// Set aria-expanded to false when region modal is closed
@@ -262,7 +288,8 @@ class Footer {
262288
regionSelector.href = localizeLink(regionSelector.href);
263289
decorateAutoBlock(regionSelector); // add fragment-specific class(es)
264290
this.elements.regionPicker.append(regionSelector); // add fragment after regionPickerElem
265-
await loadBlock(regionSelector); // load fragment and replace original link
291+
const { default: initFragment } = await import('../fragment/fragment.js');
292+
await initFragment(regionSelector); // load fragment and replace original link
266293
// Update aria-expanded on click
267294
regionPickerElem.addEventListener('click', (e) => {
268295
e.preventDefault();
@@ -271,14 +298,15 @@ class Footer {
271298
});
272299
// Close region picker dropdown on outside click
273300
document.addEventListener('click', (e) => {
301+
e.preventDefault();
274302
if (isRegionPickerExpanded()
275303
&& !e.target.closest(`.${regionPickerWrapperClass}`)) {
276304
regionPickerElem.setAttribute('aria-expanded', false);
277305
}
278306
});
279307
}
280308

281-
return this.regionPicker;
309+
return this.elements.regionPicker;
282310
};
283311

284312
decorateSocial = () => {

libs/blocks/global-navigation/base.css

+2-2
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@
118118
align-items: center;
119119
}
120120

121-
header.global-navigation {
122-
visibility: visible;
121+
header.global-navigation.ready {
122+
visibility: visible !important;
123123
}
124124

125125
/* Desktop styles */

libs/blocks/global-navigation/global-navigation.js

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint import/no-relative-packages: 0 */
12
/* eslint-disable no-async-promise-executor */
23
import {
34
getConfig,
@@ -20,7 +21,6 @@ import {
2021
isTangentToViewport,
2122
lanaLog,
2223
loadBaseStyles,
23-
loadBlock,
2424
loadDecorateMenu,
2525
rootPath,
2626
loadStyles,
@@ -222,7 +222,7 @@ const decorateProfileTrigger = async ({ avatar }) => {
222222
let keyboardNav;
223223
const setupKeyboardNav = async () => {
224224
keyboardNav = keyboardNav || new Promise(async (resolve) => {
225-
const KeyboardNavigation = await loadBlock('./keyboard/index.js');
225+
const { default: KeyboardNavigation } = await import('./utilities/keyboard/index.js');
226226
const instance = new KeyboardNavigation();
227227
resolve(instance);
228228
});
@@ -428,17 +428,17 @@ class Gnav {
428428
this.block.removeEventListener('keydown', this.loadDelayed);
429429
if (this.searchPresent()) {
430430
const [
431-
Search,
431+
{ default: Search },
432432
] = await Promise.all([
433-
loadBlock('../features/search/gnav-search.js'),
433+
import('./features/search/gnav-search.js'),
434434
loadStyles(rootPath('features/search/gnav-search.css')),
435435
]);
436436
this.Search = Search;
437437
}
438438

439439
if (!this.useUniversalNav) {
440-
const [ProfileDropdown] = await Promise.all([
441-
loadBlock('../features/profile/dropdown.js'),
440+
const [{ default: ProfileDropdown }] = await Promise.all([
441+
import('./features/profile/dropdown.js'),
442442
loadStyles(rootPath('features/profile/dropdown.css')),
443443
]);
444444
this.ProfileDropdown = ProfileDropdown;
@@ -543,7 +543,7 @@ class Gnav {
543543
const unavVersion = new URLSearchParams(window.location.search).get('unavVersion') || '1.3';
544544
await Promise.all([
545545
loadScript(`https://${environment}.adobeccstatic.com/unav/${unavVersion}/UniversalNav.js`),
546-
loadStyles(`https://${environment}.adobeccstatic.com/unav/${unavVersion}/UniversalNav.css`),
546+
loadStyles(`https://${environment}.adobeccstatic.com/unav/${unavVersion}/UniversalNav.css`, true),
547547
]);
548548

549549
const getChildren = () => {
@@ -913,7 +913,7 @@ class Gnav {
913913

914914
const menuLogic = await loadDecorateMenu();
915915

916-
menuLogic.decorateMenu({
916+
await menuLogic.decorateMenu({
917917
item,
918918
template,
919919
type: itemType,
@@ -1024,7 +1024,7 @@ class Gnav {
10241024
const breadcrumbsElem = this.block.querySelector('.breadcrumbs');
10251025
// Breadcrumbs are not initially part of the nav, need to decorate the links
10261026
if (breadcrumbsElem) decorateLinks(breadcrumbsElem);
1027-
const createBreadcrumbs = await loadBlock('../features/breadcrumbs/breadcrumbs.js');
1027+
const { default: createBreadcrumbs } = await import('./features/breadcrumbs/breadcrumbs.js');
10281028
this.elements.breadcrumbsWrapper = await createBreadcrumbs(breadcrumbsElem);
10291029
return this.elements.breadcrumbsWrapper;
10301030
};
@@ -1094,5 +1094,6 @@ export default async function init(block) {
10941094
const mepMartech = mep?.martech || '';
10951095
block.setAttribute('daa-lh', `gnav|${getExperienceName()}${mepMartech}`);
10961096
if (isDarkMode()) block.classList.add('feds--dark');
1097+
block.classList.add('ready');
10971098
return gnav;
10981099
}

libs/blocks/global-navigation/utilities/getUserEntitlements.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint import/no-relative-packages: 0 */
12
/* eslint-disable camelcase */
23
import { getConfig } from '../../../utils/utils.js';
34

libs/blocks/global-navigation/utilities/getUserEventHistory.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint import/no-relative-packages: 0 */
12
/* eslint-disable no-promise-executor-return, no-async-promise-executor */
23
import { getConfig } from '../../../utils/utils.js';
34

libs/blocks/global-navigation/utilities/utilities.js

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint import/no-relative-packages: 0 */
12
import {
23
getConfig, getMetadata, loadStyle, loadLana, decorateLinks, localizeLink,
34
} from '../../../utils/utils.js';
@@ -134,7 +135,9 @@ export function rootPath(path) {
134135
return url;
135136
}
136137

137-
export function loadStyles(url) {
138+
export function loadStyles(url, override = false) {
139+
const { standaloneGnav } = getConfig();
140+
if (standaloneGnav && !override) return;
138141
loadStyle(url, (e) => {
139142
if (e === 'error') {
140143
lanaLog({
@@ -155,6 +158,8 @@ export function isDarkMode() {
155158
// since they can be independent of each other.
156159
// CSS imports were not used due to duplication of file include
157160
export async function loadBaseStyles() {
161+
const { standaloneGnav } = getConfig();
162+
if (standaloneGnav) return;
158163
if (isDarkMode()) {
159164
new Promise((resolve) => { loadStyle(rootPath('base.css'), resolve); })
160165
.then(() => loadStyles(rootPath('dark-nav.css')));
@@ -164,10 +169,6 @@ export async function loadBaseStyles() {
164169
}
165170
}
166171

167-
export function loadBlock(path) {
168-
return import(path).then((module) => module.default);
169-
}
170-
171172
let cachedDecorateMenu;
172173
export async function loadDecorateMenu() {
173174
if (cachedDecorateMenu) return cachedDecorateMenu;
@@ -177,15 +178,12 @@ export async function loadDecorateMenu() {
177178
resolve = _resolve;
178179
});
179180

180-
const [{ decorateMenu, decorateLinkGroup }] = await Promise.all([
181-
loadBlock('./menu/menu.js'),
181+
const [menu] = await Promise.all([
182+
import('./menu/menu.js'),
182183
loadStyles(rootPath('utilities/menu/menu.css')),
183184
]);
184185

185-
resolve({
186-
decorateMenu,
187-
decorateLinkGroup,
188-
});
186+
resolve(menu.default);
189187
return cachedDecorateMenu;
190188
}
191189

libs/blocks/region-nav/region-nav.css

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
column-count: 1;
5757
}
5858

59+
.region-nav.hide {
60+
display: none;
61+
}
62+
5963
@media (min-width: 600px) {
6064
.region-nav > div:nth-of-type(2) {
6165
column-count: 3;

libs/navigation/base.css

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import '../blocks/global-navigation/base.css';

libs/navigation/bootstrapper.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
export default async function bootstrapBlock(miloLibs, blockConfig) {
1+
/* eslint import/no-relative-packages: 0 */
2+
export default async function bootstrapBlock(initBlock, blockConfig) {
23
const { name, targetEl, layout, noBorder, jarvis } = blockConfig;
3-
const { getConfig, createTag, loadLink, loadScript } = await import(`${miloLibs}/utils/utils.js`);
4-
const { default: initBlock } = await import(`${miloLibs}/blocks/${name}/${name}.js`);
5-
6-
const styles = [`${miloLibs}/blocks/${name}/${name}.css`, `${miloLibs}/navigation/navigation.css`];
7-
styles.forEach((url) => loadLink(url, { rel: 'stylesheet' }));
4+
const { getConfig, createTag, loadScript } = await import('../utils/utils.js');
85

96
const setNavLayout = () => {
107
const element = document.querySelector(targetEl);
@@ -41,7 +38,7 @@ export default async function bootstrapBlock(miloLibs, blockConfig) {
4138

4239
await initBlock(document.querySelector(targetEl));
4340
if (blockConfig.targetEl === 'footer') {
44-
const { loadPrivacy } = await import(`${miloLibs}/scripts/delayed.js`);
41+
const { loadPrivacy } = await import('../scripts/delayed.js');
4542
setTimeout(() => {
4643
loadPrivacy(getConfig, loadScript);
4744
}, blockConfig.delay);

0 commit comments

Comments
 (0)