Creating Custom Navigation
Navigation in the Front-end Framework is handled by a navigation block, a special block that is used to define the base structure of the application. We provide a powerful default navigation block, but you can also create your own custom navigation component to implement your own navigation.
For example, you can create a custom navigation component that uses a different navigation library, or that implements a different page structure than the default side + top navigation.
In this guide, we will first explain how to use the default navigation block, and then we will show how to create a custom navigation block.
Using the Default Navigation Block
The default navigation block is used like the following:
import { createNavigation } from '@basaldev/blocks-frontend-framework';
...
{
navigationComponent: createNavigation({
headerElements: (
isLoggedIn: boolean
): NavigationHeaderElement => {
if (isLoggedIn) {
return ...;
}
return ...;
},
headerLogoElement: (onNavigate) => (
<Link href="/" onNavigate={onNavigate}>
<img ... />
</Link>
),
menuNavigationBlocks: [
...
],
sideNavigationBlocks: [
...
],
}),
}
The createNavigation
function takes an object with the following properties:
headerElements
: A function that returns the header elements based on whether the user is logged in. This allows you to return buttons or links to show at the top-right of the page next to the menu.headerLogoElement
: A function that returns the logo element. This is a react element rendered in the top-left of the page.menuNavigationBlocks
: An array of navigation blocks that are rendered in the top-right dropdown menu. See the Blocks guide for more information.sideNavigationBlocks
: An array of navigation blocks that are rendered in the side navigation bar. See the Blocks guide for more information.
Creating a Custom Navigation Block
Navigation can be implemented by creating a component that takes the following props:
blockProps
: The props for the current block being displayed. You can use this to change the navigation based on the current block'snavigationOptions
, or to use info like the session state or callbacks to trigger navigation.breadcrumbs
: An array of breadcrumbs to display for the current page, based on the page titles and paths of all pages in the current hierarchy (based on the current page'sparentBlockPath
)children
: The block page to render inside of the navigation component.
Here is how the default navigation component is implemented:
export type NavigationHeaderElement =
| {
badges?: Array<null | BadgeProps>;
icons: IconProps[];
links?: Array<{ to: string }>;
type: 'icons';
}
| {
buttons: ButtonProps[];
links?: Array<{ to: string }>;
type: 'buttons';
}
| {
type: 'text';
value: string;
};
export interface NavigationOptions {
footerNavigationItems?: Array<{
text: string;
to: string;
}>;
headerElements?: (isLoggedIn: boolean) => NavigationHeaderElement;
headerLogoElement?: (
onNavigate: BlockComponentProps['onNavigate']
) => React.ReactNode;
menuNavigationBlocks?: Array<React.ComponentType<BlockComponentProps>>;
sideNavigationBlocks?: Array<React.ComponentType<BlockComponentProps>>;
}
export const createNavigation = (options: NavigationOptions) => {
const {
headerElements,
headerLogoElement,
menuNavigationBlocks,
sideNavigationBlocks,
} = options;
const Navigation: React.FC<NavigationComponentProps> = ({
children,
breadcrumbs,
blockProps,
}) => {
const headerType =
blockProps.matchedBlockPage?.navigationOptions?.topBarType ?? 'standard';
const hideBorder =
blockProps.matchedBlockPage?.navigationOptions?.hideTopBarBorder ?? false;
const hideFooter =
blockProps.matchedBlockPage?.navigationOptions?.hideFooter ?? false;
const hideSideNavigation =
blockProps.matchedBlockPage?.navigationOptions?.hideSideNavigation ??
false;
const keepSearchParamsOnBack =
blockProps.matchedBlockPage?.navigationOptions?.keepSearchParamsOnBack ??
false;
const searchParams = blockProps.searchParams;
const isLoggedIn = blockProps.sessionState.isLoggedIn;
const isMobile = blockProps.screenMode === 'mobile';
const [isMenuOpen, setIsMenuOpen] = useState(false);
const onMenuOpen = useCallback(
() => setIsMenuOpen(!isMenuOpen),
[isMenuOpen, setIsMenuOpen]
);
const [leftElement, centerElement, rightElement] = useNavigationBarElements(
{
breadcrumbs,
headerElements,
headerLogoElement,
headerType,
isLoggedIn,
isMobile,
keepSearchParamsOnBack,
onMenuOpen,
onNavigate: blockProps.onNavigate,
searchParams,
urlForRoute: blockProps.urlForRoute,
}
);
const [isSidebarShrink, setShouldSidebarShrink] = useSideNavigationShrink(
blockProps.screenMode
);
const sideNavigationItems = sideNavigationBlocks?.map(
(SideBlock, index) => <SideBlock key={index} {...blockProps} />
);
const menuItems = menuNavigationBlocks?.map((MenuBlock, index) => (
<MenuBlock key={index} {...blockProps} />
));
return (
<div
className={classNames(classes.body, bodyClasses[blockProps.screenMode])}
>
<div
className={classNames(
classes.bodyColumn,
navigationColumnClasses[blockProps.screenMode]
)}
>
<NavigationBar
className={classNames(
classes.navBar,
isMobile ? classes.navBarMobile : undefined
)}
backgroundColor={
headerType === 'singleItem' ? 'secondary' : 'primary'
}
leftElement={leftElement}
centerElement={centerElement}
rightElement={rightElement}
onNavigate={blockProps.onNavigate}
hideBorder={hideBorder}
screenMode={isMobile ? 'mobile' : 'desktop'}
/>
<div className={classes.sidebarRow}>
{!hideSideNavigation &&
!isMobile &&
!!sideNavigationItems &&
sideNavigationItems.length > 0 && (
<SideNavigation
className={classes.sidebarRowSidebar}
backgroundColor="tertiary"
items={sideNavigationItems}
onShrinkToggle={() => {
setShouldSidebarShrink(
isSidebarShrink === 'shrink' ? 'expand' : 'shrink'
);
}}
isShrinkable={true}
isShrunk={isSidebarShrink === 'shrink'}
/>
)}
<main className={classes.sidebarRowMain}>{children}</main>
</div>
{blockProps.screenMode !== 'mobile' && menuItems && (
<Menu
anchorToClassName="headerLogoRightIcon"
isMenuOpen={isMenuOpen}
items={menuItems}
size="wide"
onMenuOpenUpdate={onMenuOpen}
onNavigate={blockProps.onNavigate}
/>
)}
{blockProps.screenMode === 'mobile' && !hideFooter && (
<Footer
footerPath={breadcrumbs}
onNavigate={blockProps.onNavigate}
/>
)}
{blockProps.screenMode === 'mobile' && isMenuOpen && (
<>
<div
className={classes.mobileOverlay}
onClick={() => setIsMenuOpen(false)}
/>
<Icon
className={classes.mobileOverlayClose}
iconSize="medium"
icon="close"
color="surface-primary"
onClick={() => setIsMenuOpen(false)}
/>
</>
)}
{blockProps.screenMode === 'mobile' && !hideSideNavigation && (
<SideNavigation
className={classNames(
classes.mobileSidebar,
isMenuOpen ? classes.mobileSidebarOpen : undefined
)}
backgroundColor="primary"
items={sideNavigationItems ?? []}
isShrinkable={false}
/>
)}
</div>
</div>
);
};
return Navigation;
};
useNavigationBarElements
is a hook that generates the jsx given the options
for the navigation. In your application, it is unlikely that you will need
to create something so dynamic, so you may just implement this as hard-coded
JSX directly.