Skip to content

Commit

Permalink
PLANET-6530 Add new Secondary Navigation Block
Browse files Browse the repository at this point in the history
- Added all necessary files for the new block
- Added tests for the new block
  • Loading branch information
Osong-Michael committed Feb 10, 2025
1 parent d24febf commit b61338d
Show file tree
Hide file tree
Showing 15 changed files with 384 additions and 1 deletion.
39 changes: 39 additions & 0 deletions assets/src/blocks/SecondaryNavigation/SecondaryNavigationBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {SecondaryNavigationEditor} from './SecondaryNavigationEditor';
import {example} from './example';

const {__} = wp.i18n;

const BLOCK_NAME = 'planet4-blocks/secondary-navigation';

export const registerSecondaryNavigationBlock = () => {
const {registerBlockType} = wp.blocks;

registerBlockType(BLOCK_NAME, {
title: 'Secondary Navigation Menu',
description: __('Inserts a secondary navigation menu to the page that leads to different sections of the same page.', 'planet4-blocks-backend'),
icon: 'menu-alt3',
category: 'planet4-blocks-beta',
attributes: {
levels: {
type: 'array',
default: [{heading: 2, link: true}],
},
exampleMenuItems: { // Used for the block's preview, which can't extract items from anything.
type: 'array',
},
},
isExample: {
type: 'boolean',
default: false,
},
supports: {
multiple: false, // Use the block just once per post.
html: false,
},
edit: SecondaryNavigationEditor,
save() {
return null;
},
example,
});
};
66 changes: 66 additions & 0 deletions assets/src/blocks/SecondaryNavigation/SecondaryNavigationEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {getHeadingsFromBlocks} from './generateHeadingsForBlock';

const {useSelect} = wp.data;
const {InspectorControls} = wp.blockEditor;
const {PanelBody} = wp.components;
const {__} = wp.i18n;

const renderEdit = () => {
return (
<InspectorControls>
<PanelBody title={__('Learn more about this block', 'planet4-blocks-backend')} initialOpen={false}>
<p className="components-base-control__help">
<a target="_blank" href="https://planet4.greenpeace.org/content/blocks/table-of-contents/" rel="noreferrer">
P4 Handbook - P4 Secondary Navigation Menu
</a>
{' '} &#128203;
</p>
</PanelBody>
</InspectorControls>
);
};

const renderView = attributes => {
const {
levels,
isExample,
exampleMenuItems,
} = attributes;

const blocks = useSelect(select => select('core/block-editor').getBlocks(), []);

const flatHeadings = getHeadingsFromBlocks(blocks, levels);

const menuItems = isExample ? exampleMenuItems : flatHeadings;

return (
<section className="block secondary-navigation-block">
{menuItems.length > 0 ?
<div className="secondary-navigation-menu">
<ul className="secondary-navigation-item">
{menuItems.map(({anchor, content}) => (
<li key={anchor}>
<a
className="secondary-navigation-link"
href={`#${anchor}`}
>
{content}
</a>
</li>
))}
</ul>
</div> :
<div className="EmptyMessage">
{__('There are not any pre-established headings that this block can display in the form of a secondary navigation menu. Please add headings to your page.', 'planet4-blocks-backend')}
</div>
}
</section>
);
};

export const SecondaryNavigationEditor = ({attributes, isSelected}) => (
<>
{isSelected && renderEdit()}
{renderView(attributes)}
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {getHeadingsFromDom} from '../TableOfContents/getHeadingsFromDom';

export const SecondaryNavigationFrontend = ({levels}) => {
const headings = getHeadingsFromDom(levels);
const setActive = event => {
const allLinks = document.querySelectorAll('.secondary-navigation-link');
allLinks.forEach(link => link.classList.remove('active'));
event.target.classList.add('active');
};

return (
<section className="block secondary-navigation-block">
<div className="secondary-navigation-menu container">
<ul className="secondary-navigation-item">
{headings.map(({anchor, content}) => (
<li
key={anchor}
>
<a
className="secondary-navigation-link"
href={`#${anchor}`}
onClick={setActive}
>
{content}
</a>
</li>
))}
</ul>
</div>
</section>
);
};
27 changes: 27 additions & 0 deletions assets/src/blocks/SecondaryNavigation/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const example = {
viewportWidth: 992,
attributes: {
isExample: true,
exampleMenuItems: [
{
content: 'Title 1',
anchor: 'title-1',
level: 2,
shouldLink: true,
},
{
content: 'Title 2',
anchor: 'title-2',
level: 2,
shouldLink: true,
},
{
content: 'Title 3',
anchor: 'title-3',
level: 2,
shouldLink: true,
},
],
},
};

30 changes: 30 additions & 0 deletions assets/src/blocks/SecondaryNavigation/generateHeadingsForBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {generateAnchor} from '../TableOfContents/generateAnchor';
import {unescape} from '../../functions/unescape';

const stripTags = str => str.replace(/(<([^>]+)>)/ig, ''); //NOSONAR

export const getHeadingsFromBlocks = (blocks, selectedLevels) => {
const headings = [];
blocks.forEach(block => {
if (block.name === 'core/heading') {
const blockLevel = block.attributes.level;

const levelConfig = selectedLevels.find(selected => selected.heading === blockLevel);

if (!levelConfig) {
return;
}

const anchor = block.attributes.anchor || generateAnchor(block.attributes.content, headings.map(h => h.anchor));

headings.push({
level: blockLevel,
content: unescape(stripTags(block.attributes.content)),
anchor,
shouldLink: levelConfig.link,
});
}
});

return headings;
};
4 changes: 3 additions & 1 deletion assets/src/blocks/TableOfContents/getHeadingsFromDom.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export const getHeadingsFromDom = selectedLevels => {
}

// Get all heading tags that we need to query
const headingsSelector = selectedLevels.map(level => `:not(.table-of-contents-block) > h${level.heading}`);
const headingsSelector = selectedLevels.map(
level => `:not(.table-of-contents-block):not(.secondary-navigation-block) > h${level.heading}`
);

const usedAnchors = [];

Expand Down
2 changes: 2 additions & 0 deletions assets/src/editorIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {addButtonLinkPasteWarning} from './block-editor/addButtonLinkPasteWarnin
import {addBlockFilters} from './block-editor/BlockFilters';
import {replaceTaxonomyTermSelectors} from './block-editor/replaceTaxonomyTermSelectors';
import {setupImageBlockExtension} from './block-editor/setupImageBlockExtension';
import {registerSecondaryNavigationBlock} from './blocks/SecondaryNavigation/SecondaryNavigationBlock';

wp.domReady(() => {
// Blocks
Expand All @@ -29,6 +30,7 @@ wp.domReady(() => {
registerTimelineBlock();
registerPostsListBlock();
registerTopicLinkBlock();
registerSecondaryNavigationBlock();

// Block Templates
registerBlockTemplates();
Expand Down
2 changes: 2 additions & 0 deletions assets/src/frontendIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import {ColumnsFrontend} from './blocks/Columns/ColumnsFrontend';
import {TopicLinkFrontend} from './blocks/TopicLink/TopicLinkFrontend';
import {setupLightboxForImages} from './blocks/components/Lightbox/setupLightboxForImages';
import {setupParallax} from './blocks/components/Parallax/setupParallax';
import {SecondaryNavigationFrontend} from './blocks/SecondaryNavigation/SecondaryNavigationFrontend';

// Render React components
const COMPONENTS = {
'planet4-blocks/submenu': TableOfContentsFrontend,
'planet4-blocks/happypoint': HappyPointFrontend,
'planet4-blocks/columns': ColumnsFrontend,
'planet4-blocks/topic-link': TopicLinkFrontend,
'planet4-blocks/secondary-navigation': SecondaryNavigationFrontend,
};

document.addEventListener('DOMContentLoaded', () => {
Expand Down
2 changes: 2 additions & 0 deletions assets/src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {setupClickabelActionsListCards} from './actions_list_clickable_cards';
import {removeNoPostText} from './query-no-posts';
import {removeRelatedPostsSection} from './remove_related_section_no_posts';
import {setupCountrySelector} from './country_selector';
import {makeSecondaryNavigationStickyonScroll} from './sticky_on_scroll_sn';

function requireAll(r) {
r.keys().forEach(r);
Expand All @@ -32,4 +33,5 @@ document.addEventListener('DOMContentLoaded', () => {
removeRelatedPostsSection();
setupClickabelActionsListCards();
setupCountrySelector();
makeSecondaryNavigationStickyonScroll();
});
20 changes: 20 additions & 0 deletions assets/src/js/sticky_on_scroll_sn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Adds class to Secondary Navigation block to make it sticky on scroll
export const makeSecondaryNavigationStickyonScroll = () => {
setTimeout(() => {
const stickyElement = document.querySelector('.secondary-navigation-block');
const container = document.querySelector('.page-content');
const offset = -500;

window.addEventListener('scroll', () => {
const containerRect = container.getBoundingClientRect();
const stickyRect = stickyElement.getBoundingClientRect();

if (containerRect.top <= offset && containerRect.bottom > stickyRect.height + offset) {
stickyElement.classList.add('stuck');
} else {
stickyElement.classList.remove('stuck');
}
});

}, 1000);
};
1 change: 1 addition & 0 deletions assets/src/scss/blocks.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Beta blocks
@import "blocks/ActionsList/ActionsListStyle";
@import "blocks/PostsList/PostsListStyle";
@import "blocks/SecondaryNavigation/SecondaryNavigationStyle";

// P4 Blocks
@import "blocks/Accordion/AccordionStyle";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
.secondary-navigation-block {
margin-left: calc(-50vw - -50%);
width: 100vw;
height: 70px;
padding: 10px;
background: var(--color-background-navigation_bar);
border-bottom: 0 transparent;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
position: relative;

@include small-and-less {
> .container {
max-width: 100%;
}
}
}

.page-content > p,
.page-content > h2 {
z-index: -1;
}

.stuck {
position: fixed;
top: 80px;
left: 0;

@include small-and-less {
top: 90px;
}
}

.secondary-navigation-item {
list-style: none;
display: flex;
justify-content: center;
overflow-x: auto;
flex-wrap: nowrap;
scroll-padding-inline-start: 24px;
gap: 48px;

@include small-and-less {
justify-content: flex-start;
}

li {
padding: 5px;
min-width: 150px;
flex-shrink: 0;
text-align: center;
cursor: pointer;
scroll-snap-align: start;

&:has(a.active) {
border-bottom: 4px solid var(--gp-green-400);

a {
color: var(--grey-900) !important;

&:hover {
text-decoration: none;
}
}
}

&:hover {
text-decoration: none;
color: var(--grey-900) !important;
border-bottom: 4px solid var(--gp-green-400);
}
}
}

.secondary-navigation-link {
color: var(--grey-600) !important;
font-weight: var(--font-weight-bold);
font-family: var(--font-family-primary);
font-size: var(--font-size-xxs--font-family-primary);
line-height: var(--line-height-xs--font-family-primary);

&:hover {
text-decoration: none;
}
}
2 changes: 2 additions & 0 deletions src/BlockSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class BlockSettings
self::P4_BLOCKS_PREFIX . '/take-action-boxout',
self::P4_BLOCKS_PREFIX . '/timeline',
self::P4_BLOCKS_PREFIX . '/guestbook',
self::P4_BLOCKS_PREFIX . '/secondary-navigation',
self::HUBSPOT_FORMS_BLOCK,
self::GRAVITY_FORMS_BLOCK,
];
Expand Down Expand Up @@ -93,6 +94,7 @@ class BlockSettings
self::P4_BLOCKS_PREFIX . '/take-action-boxout',
self::P4_BLOCKS_PREFIX . '/timeline',
self::P4_BLOCKS_PREFIX . '/guestbook',
self::P4_BLOCKS_PREFIX . '/secondary-navigation',
self::HUBSPOT_FORMS_BLOCK,
self::GRAVITY_FORMS_BLOCK,
];
Expand Down
Loading

0 comments on commit b61338d

Please sign in to comment.