Skip to content
Snippets Groups Projects
Commit 9e9bd26c authored by Embruch, Gerd's avatar Embruch, Gerd
Browse files

added navigation panels

parent 7478daa3
Branches
No related tags found
No related merge requests found
<!doctype html>
<html lang="en">
<html lang="en" class="overflow-x-hidden h-full font-Uhh">
<head>
<meta charset="UTF-8" />
......
@tailwind base;@tailwind components;@tailwind utilities;@layer base{img,svg,video,canvas,audio,iframe,embed,object{display:inline;vertical-align:middle}}/*# sourceMappingURL=tailwind.presets.min.css.map */
\ No newline at end of file
@tailwind base;@tailwind components;@tailwind utilities;@layer base{img,svg,video,canvas,audio,iframe,embed,object{display:inline;vertical-align:middle}*{@apply border-border}body{@apply bg-background text-foreground}}/*# sourceMappingURL=tailwind.presets.min.css.map */
\ No newline at end of file
{"version":3,"sources":["../sass/tailwind.presets.scss"],"names":[],"mappings":"AAAA,cAAA,CACA,oBAAA,CACA,mBAAA,CAEA,YACE,+CAQE,cAAA,CACA,qBAAA,CAAA","file":"tailwind.presets.min.css"}
\ No newline at end of file
{"version":3,"sources":["../sass/tailwind.presets.scss"],"names":[],"mappings":"AAAA,cAAA,CACA,oBAAA,CACA,mBAAA,CAEA,YACE,+CAQE,cAAA,CACA,qBAAA,CAEF,EACE,oBAAA,CAEF,KACE,oCAAA,CAAA","file":"tailwind.presets.min.css"}
\ No newline at end of file
......@@ -14,4 +14,10 @@
display: inline;
vertical-align: middle;
}
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Outlet } from 'react-router-dom';
import Header from './partials/Header';
import Subheader from './partials/subheader/Subheader';
import Navbar from './partials/navbar/Navbar';
function MainLayout() {
// #################################
// HOOKS
// #################################
// ### SET BODY CLASSES
useEffect(() => {
// ### on run exec this code
document.body.className = '';
document.body.classList.add('overflow-x-hidden', 'h-full');
document.getElementById('root').classList.remove('grid-rows-[auto_1fr]', 'h-full');
document.getElementById('root').classList.add('grid-rows-[auto_1fr]', 'min-h-full', 'w-screen', 'max-h-full', 'sm:grid-rows-[auto_auto_auto_1fr]');
}, []);
// ### CHECK MOBILE MENU STATE
const [showMobileNav, setShowMobileNav] = useState(false);
// #################################
// FUNCTIONS
// #################################
const toggleMobileNav = (toggle) => {
setShowMobileNav(!showMobileNav);
};
// #################################
// OUTPUT
// #################################
return (
<>
<Header />
<Header showMobileNav={showMobileNav} toggleMobileNav={toggleMobileNav} />
<Subheader />
<main role="main" className="relative row-start-2 col-span-full flex sm:justify-center overflow-y-auto sm:row-start-4">
<div className="box-border px-4 mt-2 container">
<Outlet />
</div>
</main>
<div>Navbar</div>
<Navbar showMobileNav={showMobileNav} toggleMobileNav={toggleMobileNav} />
</>
);
}
......
......@@ -7,7 +7,7 @@ import Hamburger from 'hamburger-react';
function Header() {
function Header({ showMobileNav, toggleMobileNav }) {
// #################################
// HOOKS
// #################################
......@@ -37,7 +37,7 @@ function Header() {
<RiFilter2Line className='text-3xl' />
</div>
<div className="h-full w-8">
<Hamburger label='show navigation' duration={0.3} />
<Hamburger label='show navigation' toggled={showMobileNav} onToggle={toggleMobileNav} duration={0.3} />
</div>
</span>
</div>
......
import React from 'react';
import { Link } from 'react-router-dom';
function DesktopLink({ to, children }) {
// #################################
// HOOKS
// #################################
// #################################
// FUNCTIONS
// #################################
// #################################
// OUTPUT
// #################################
return (
<>
<Link to={to} className="peer block p-2 hover:bg-UhhWhite hover:text-UhhBlue">{children}</Link>
</>
);
}
export default React.memo(DesktopLink);
\ No newline at end of file
import React from 'react';
import DesktopLink from './DesktopLink';
function DesktopNav({ filteredSitemap }) {
// #################################
// HOOKS
// #################################
// #################################
// FUNCTIONS
// #################################
// recursively render given menu
const renderMenu = (menu, parent = null) => {
if (!menu) return;
return menu.map((item, idx) => (
<li key={`link-${idx}`} className="relative">
{item.children?.length ? (
<>
<DesktopLink to={parent ? `${parent.path}/${item.path}` : item.path}>{item.title}</DesktopLink>
<ul className="absolute z-50 h-0 overflow-y-hidden bg-UhhBlue border-UhhBlue hover:h-auto peer-hover:h-auto">
{renderMenu(item.children, item)}
</ul>
</>
) : (
<DesktopLink to={parent ? `${parent.path}/${item.path}` : item.path}>{item.title}</DesktopLink>
)}
</li>
));
};
// #################################
// OUTPUT
// #################################
return (
<ul className="hidden sm:flex px-4 ont-UhhBC text-2xl container flex justify-between text-UhhWhite font-UhhSLC">
{filteredSitemap[0]?.children && renderMenu(filteredSitemap[0].children)}
</ul>
);
}
export default React.memo(DesktopNav);
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
import { Link } from 'react-router-dom';
function MobileLink({ to, children, mobileNavState, dispatchMobileNavState, childNode, level, toggleMobileNav }) {
// #################################
// HOOKS
// #################################
// ### HAS VALID CHILDS
const [hasValidChilds, setHasValidChilds] = useState(false);
useEffect(() => {
// only nodes with children
if (childNode?.children) {
let validChilds = childNode.children.map(child => {
// hidden or index childs aren't valid
if (child.hidden || child.index) return false;
// others are
return true;
}).filter(Boolean);
// if > 0 valid childs found set to true
if (validChilds.length) setHasValidChilds(true);
}
}, []);
// #################################
// FUNCTIONS
// #################################
// #################################
// OUTPUT
// #################################
return (
<>
<span className={`flex border-b border-b-UhhWhite text-2xl ${level === 'pov' ? 'font-UhhBC' : null}`}>
<span className={`w-16 py-2 px-6 text-center
${level === 'pov' && mobileNavState.povParentNode[0] ?
'cursor-pointer md:hover:bg-UhhWhite md:hover:text-UhhBlue opacity-100'
: null}`}>
{level === 'pov' && mobileNavState.povParentNode[0] ?
<RiArrowLeftSLine
onClick={() => { dispatchMobileNavState({ type: 'setPath', payload: mobileNavState.povParentNode[0]?.path }); }} />
: null}
</span>
<Link
to={to}
onClick={() => toggleMobileNav()}
className={`md:hover:bg-UhhWhite md:hover:text-UhhBlue py-2 pr-2 w-full
${level === 'pov' ?
'pl-20'
: 'pl-28'}`}>
{children}
</Link>
<span
className={`w-16 py-2 px-6 text-center
${level !== 'pov' && hasValidChilds ?
'cursor-pointer md:hover:bg-UhhWhite md:hover:text-UhhBlue opacity-100'
: null}`}>
{level !== 'pov' && hasValidChilds ?
<RiArrowRightSLine
onClick={() => { dispatchMobileNavState({ type: 'setPath', payload: childNode?.path }); }} />
: null}
</span>
</span>
</>
);
}
export default React.memo(MobileLink);
\ No newline at end of file
import React, { useEffect, useReducer } from 'react';
import MobileLink from './MobileLink';
function reducer(mobileNavState, action) {
switch (action.type) {
case 'setNode':
return { ...mobileNavState, povNode: action.payload };
case 'setParentNode':
return { ...mobileNavState, povParentNode: action.payload };
case 'setPath':
return { ...mobileNavState, povPath: action.payload };
default:
return action.payload;
}
}
function MobileNav({ filteredSitemap, showMobileNav, toggleMobileNav }) {
// #################################
// HOOKS
// #################################
const [mobileNavState, dispatchMobileNavState] = useReducer(reducer, { povNode: [], povPath: location.pathname, povParentNode: [] });
// fetch new nodes on each change of PoV
useEffect(() => {
// loop & search for new PoV's path
function sitemapSearch(array, needle, parent) {
for (let i = 0; i < array.length; i++) {
if (array[i].path === needle) {
// set node
dispatchMobileNavState({ type: 'setNode', payload: [array[i]] });
// set parent
dispatchMobileNavState({ type: 'setParentNode', payload: [parent] });
return 'found';
}
else if (array[i].children?.length) {
const result = sitemapSearch(array[i].children, needle, array[i]);
// return the result only if it's actually found otherwise keep looping
if (result) return result;
}
}
}
// search nodes for new PoV Path
sitemapSearch(filteredSitemap, mobileNavState.povPath);
// triggered on PoV path change
}, [mobileNavState.povPath, filteredSitemap]);
// #################################
// FUNCTIONS
// #################################
// #################################
// OUTPUT
// #################################
// TODO: smoothen mobile nav output
return (
<div className={`sm:hidden h-full relative flex z-[900] max-h-full transform transition-all duration-150 ease-out ${showMobileNav ? 'right-0 opacity-100' : '-right-full opacity-0'}`}>
<div className="grow bg-UhhLightBlue" onClick={() => { toggleMobileNav(); }}></div>
<div className="absolute bg-UhhBlue right-0 z-[900] inset-y-0 min-w-[300px] text-UhhWhite">
<ul className="max-h-full">
{filteredSitemap && mobileNavState.povNode.map((link, idx) =>
<li key={`link-${idx}`}>
<MobileLink
level='pov'
toggleMobileNav={toggleMobileNav}
mobileNavState={mobileNavState}
dispatchMobileNavState={dispatchMobileNavState}
to={link.path}>
{link.title}
</MobileLink>
{
link.children?.length ? (
<ul key={`${idx}-childs`}>
{link.children.map((child, cidx) => {
if (child.hidden || child.index) return false;
return <MobileLink
key={`${idx}-child-${cidx}`}
toggleMobileNav={toggleMobileNav}
mobileNavState={mobileNavState}
dispatchMobileNavState={dispatchMobileNavState}
childNode={child}
to={link.path !== '/' ? `${link.path}/${child.path}` : `${child.path}`}>
{child.title}
</MobileLink>;
}).filter(Boolean)}
</ul>
) : (null)
}
</li>
)}
</ul>
</div>
</div>
);
}
export default React.memo(MobileNav);
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { sitemap } from "/src/routes/Sitemap";
import DesktopNav from './DesktopNav';
import MobileNav from './MobileNav';
function Navbar(props) {
// #################################
// HOOKS
// #################################
// ### FILTERED SITEMAP
const [filteredSitemap, setFilteredSitemap] = useState([]);
useEffect(() => {
// fetch all links for navbars
const [overall] = sitemap.filter((item) => item.title === 'MenuBar');
// fetch all children from /
const [home] = overall.children;
// recursively filter sitemap
function flatFilter(nestedProp, searchKey, searchValue, arr) {
return arr.filter(o => {
// slightly customized for searchKey = object
const keep = o[searchKey] && o[searchKey].hasOwnProperty(searchValue);
if (keep && o[nestedProp]) {
o[nestedProp] = flatFilter(nestedProp, searchKey, searchValue, o[nestedProp]);
}
return keep;
});
}
setFilteredSitemap(flatFilter('children', 'handle', 'crumb', [home]));
}, []);
// #################################
// FUNCTIONS
// #################################
// #################################
// OUTPUT
// #################################
return (
<nav className="row-start-2 col-span-full sm:flex sm:justify-center sm:bg-UhhBlue">
{/* mobile */}
<MobileNav filteredSitemap={filteredSitemap} showMobileNav={props.showMobileNav} toggleMobileNav={props.toggleMobileNav} />
{/* desktop - render starts with children of home */}
<DesktopNav filteredSitemap={filteredSitemap} />
</nav >
);
}
export default React.memo(Navbar);
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment