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

Move $().setupVerticalNavigation(...) logic to React #6463

Merged
merged 21 commits into from Dec 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion app/assets/javascripts/miq_application.js
Expand Up @@ -1517,7 +1517,6 @@ $(function() {
$(window).on('resize', _.debounce(miqResetSizeTimer, 1000));

check_for_ellipsis();
$().setupVerticalNavigation(true);
});

function miqScrollToSelected(div_name) {
Expand Down
33 changes: 2 additions & 31 deletions app/assets/stylesheets/vertical_navigation.scss
Expand Up @@ -85,19 +85,6 @@
.nav-pf-secondary-nav {
.nav-item-pf-header {
a {
::before {
font-family: "FontAwesome";
content: '\f08d';
color: #0099d3;
margin-right: 7px;
opacity: 0;
}
&.collapsed {
::before {
transform: rotate(45deg);
display: inline-block;
}
}
color: #fff;
opacity: 1;
font-size: 16px;
Expand Down Expand Up @@ -206,7 +193,7 @@

&.collapsed {
.menu-list-group-item, .menu-list-group-item.active {
>a.top-level {
>a.top-level-item {
>.list-group-item-value {
display: none;
}
Expand All @@ -216,25 +203,9 @@
right: 10px;
}
}
>a.top-level:hover {
>a.top-level-item:hover {
width: calc(75px + 1px);
}
}
}
}

.secondary-collapse-toggle-pf,
.tertiary-collapse-toggle-pf {
&::before {
content: '\f08d';
}

&.collapsed {
&::before {
content: '\f08d';
display: inline-block;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
}
}
}
15 changes: 15 additions & 0 deletions app/javascript/components/main-menu/helpers.js
Expand Up @@ -21,3 +21,18 @@ export const handleUnsavedChanges = (type) => {
}
return window.miqCheckForChanges();
};

export const saveVerticalMenuState = (isVerticalMenuCollapsed) => {
window.localStorage.setItem('patternfly-navigation-primary', isVerticalMenuCollapsed ? 'collapsed' : 'expanded');
};

export const adaptContentWidth = (isVerticalMenuCollapsed) => {
const content = window.document.getElementsByClassName('container-pf-nav-pf-vertical-with-sub-menus')[0];
if (content) {
if (isVerticalMenuCollapsed) {
content.classList.add('collapsed-nav');
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bit unfortunate we have to manipulate these classes manually, since this is a react component and the data is in state. Any way to use react here? :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, never mind, that particular element is in app/views/layouts/_content.html.haml and app/views/layouts/_center_div_*.html.haml, and is still outside of the react navbar.

So, this makes sense for now, no issues :)

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah you would have to have the whole layout as a react component. No way around it for now i am afraid

} else {
content.classList.remove('collapsed-nav');
}
}
};
57 changes: 47 additions & 10 deletions app/javascript/components/main-menu/main-menu.jsx
@@ -1,10 +1,14 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Grid } from 'patternfly-react';
import ClassNames from 'classnames';
import { useSelector } from 'react-redux';
import isEqual from 'lodash/isEqual';
import TopLevel from './top-level';
import SecondLevel from './second-level';
import ThirdLevel from './third-level';
import { menuProps, RecursiveMenuProps } from './recursive-props';
import { adaptContentWidth } from './helpers';

const Fallback = props => <ThirdLevel level={2} {...props} />;

Expand All @@ -15,15 +19,48 @@ const getLevelComponent = level => ({

export const MenuItem = ({ level, ...props }) => getLevelComponent(level)(props);

const MainMenu = ({ menu }) => (
<Grid className="top-navbar">
<div className="nav-pf-vertical nav-pf-vertical-with-sub-menus nav-pf-vertical-collapsible-menus">
<ul className="list-group" id="maintab">
{menu.map(props => <MenuItem key={props.id} level={0} {...props} />)}
</ul>
</div>
</Grid>
);
export const HoverContext = React.createContext();

const MainMenu = ({ menu }) => {
const [activeIds, setActiveIds] = useState({});
const isVerticalMenuCollapsed = useSelector(({ menuReducer: { isVerticalMenuCollapsed } }) => isVerticalMenuCollapsed);

useEffect(() => {
adaptContentWidth(isVerticalMenuCollapsed);
}, [isVerticalMenuCollapsed]);

const handleSetActiveIds = (value) => {
if (!isEqual(activeIds, { ...activeIds, ...value })) {
setActiveIds(prevState => ({ ...prevState, ...value }));
}
};

return (
<Grid>
<div
onMouseLeave={() => handleSetActiveIds({ topLevelId: undefined, secondLevelId: undefined })}
id="main-menu"
className={ClassNames(
'nav-pf-vertical nav-pf-vertical-with-sub-menus nav-pf-vertical-collapsible-menus',
{
'hover-secondary-nav-pf': activeIds.topLevelId,
'hover-tertiary-nav-pf': activeIds.secondLevelId,
collapsed: isVerticalMenuCollapsed,
},
)
}
>
<ul className="list-group" id="maintab">
<HoverContext.Provider value={activeIds}>
{menu.map(props => (
<MenuItem key={props.id} level={0} handleSetActiveIds={handleSetActiveIds} {...props} />
))}
</HoverContext.Provider>
</ul>
</div>
</Grid>
);
};

MainMenu.propTypes = {
menu: PropTypes.arrayOf(PropTypes.shape({
Expand Down
75 changes: 47 additions & 28 deletions app/javascript/components/main-menu/second-level.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { MenuItem } from './main-menu';
import ClassNames from 'classnames';
import { MenuItem, HoverContext } from './main-menu';
import { menuProps } from './recursive-props';
import {
getHrefByType, getIdByCategory, handleUnsavedChanges,
Expand All @@ -15,34 +16,52 @@ const SecondLevel = ({
level,
type,
active,
}) => (
<li className={`menu-list-group-item ${items.length > 0 ? 'tertiary-nav-item-pf' : ''} ${active ? 'active' : ''}`} id={getIdByCategory(items.length > 0, id)}>
<a
href={getHrefByType(type, href, id)}
onClick={(event) => {
if (handleUnsavedChanges(type) === false) {
event.preventDefault();
}
return false;
}}
target={getTargetByType(type)}
handleSetActiveIds,
}) => {
const hoveredSecondLevelId = useContext(HoverContext).secondLevelId;
const hasSubitems = items.length > 0;

return (
<li
className={ClassNames(
'menu-list-group-item',
{
'tertiary-nav-item-pf': hasSubitems,
'is-hover': hoveredSecondLevelId === id,
active,
},
)}
id={getIdByCategory(hasSubitems, id)}
onMouseEnter={() => (handleSetActiveIds(hasSubitems ? { secondLevelId: id } : undefined))}
onMouseLeave={() => handleSetActiveIds({ secondLevelId: undefined })}
>
<span className="list-group-item-value">{title}</span>
</a>
<div className="nav-pf-tertiary-nav">
<div className="nav-item-pf-header">
<a className="tertiary-collapse-toggle-pf" data-toggle="collapse-tertiary-nav">
<span>{title}</span>
</a>
<a
href={getHrefByType(type, href, id)}
onClick={(event) => {
if (handleUnsavedChanges(type) === false) {
event.preventDefault();
}
return false;
}}
target={getTargetByType(type)}
>
<span className="list-group-item-value">{title}</span>
</a>
<div className="nav-pf-tertiary-nav">
<div className="nav-item-pf-header">
<a>
<span>{title}</span>
</a>
</div>
{hasSubitems && (
<ul className="list-group">
{items.map(props => <MenuItem key={props.id} level={level + 1} {...props} />)}
</ul>
)}
</div>
{items.length > 0 && (
<ul className="list-group">
{items.map(props => <MenuItem key={props.id} level={level + 1} {...props} />)}
</ul>
)}
</div>
</li>
);
</li>
);
};

SecondLevel.propTypes = {
...menuProps,
Expand Down
11 changes: 10 additions & 1 deletion app/javascript/components/main-menu/third-level.jsx
@@ -1,4 +1,5 @@
import React from 'react';
import ClassNames from 'classnames';
import { menuProps } from './recursive-props';
import { getHrefByType, handleUnsavedChanges } from './helpers';
import getTargetByType from '../../helpers/get-target-by-type';
Expand All @@ -11,7 +12,15 @@ const ThirdLevel = ({
visible,
type,
}) => (!visible ? null : (
<li className={`menu-list-group-item ${active ? 'active' : ''}`} id={`menu_item_${id}`}>
<li
id={`menu_item_${id}`}
className={ClassNames(
'menu-list-group-item',
{
active,
},
)}
>
<a
href={getHrefByType(type, href, id)}
onClick={(event) => {
Expand Down
47 changes: 37 additions & 10 deletions app/javascript/components/main-menu/top-level.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { MenuItem } from './main-menu';
import ClassNames from 'classnames';
import { MenuItem, HoverContext } from './main-menu';
import { menuProps, RecursiveMenuProps } from './recursive-props';
import {
getHrefByType, getSectionId, handleUnsavedChanges, getItemId,
Expand All @@ -16,35 +17,48 @@ const TopLevel = ({
active,
items,
type,
handleSetActiveIds,
}) => {
const hoveredTopLevelId = useContext(HoverContext).topLevelId;
const isSection = items.length > 0;

if (isSection) {
return (
<li className={`${active ? 'active' : ''} menu-list-group-item secondary-nav-item-pf`} id={getSectionId(id)}>
<li
className={ClassNames(
'menu-list-group-item',
'secondary-nav-item-pf',
{
active,
'is-hover': hoveredTopLevelId === id,
},
)}
id={getSectionId(id)}
onMouseEnter={() => handleSetActiveIds({ topLevelId: id })}
onBlur={() => undefined}
>
<a
className="top-level-item"
href={getHrefByType(type, href, id)}
onClick={(event) => {
if (handleUnsavedChanges(type) === false) {
event.preventDefault();
}
return false;
}}
href={getHrefByType(type, href, id)}
target={getTargetByType(type)}
className="top-level"
>
<span className={icon} />
<span className="list-group-item-value">{title}</span>
</a>
<React.Fragment>
<div className="nav-pf-secondary-nav" id={`menu-${id}`}>
<div className="nav-item-pf-header">
<a className="secondary-collapse-toggle-pf top-level" data-toggle="collapse-secondary-nav">
<a className="top-level-item">
<span>{title}</span>
</a>
</div>
<ul className="list-group">
{items.map(props => <MenuItem key={props.id} level={level + 1} {...props} />)}
{items.map(props => <MenuItem key={props.id} level={level + 1} handleSetActiveIds={handleSetActiveIds} {...props} />)}
</ul>
</div>
</React.Fragment>
Expand All @@ -53,15 +67,28 @@ const TopLevel = ({
}

return (
<li className={`${active ? 'active' : ''} menu-list-group-item`} id={getItemId(id)}>
<li
id={getItemId(id)}
className={ClassNames(
'menu-list-group-item',
{
active,
},
)
}
onMouseEnter={() => handleSetActiveIds({ topLevelId: undefined })}
onBlur={() => undefined}
>
<a
className="top-level-item"
href={getHrefByType(type, href, id)}
onClick={(event) => {
if (handleUnsavedChanges(type) === false) {
event.preventDefault();
}
return false;
}}
href={getHrefByType(type, href, id)}
target={getTargetByType(type)}
>
<span className={icon} />
<span className="list-group-item-value">{title}</span>
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/components/top-navbar/index.js
Expand Up @@ -2,6 +2,7 @@ import RightSection from './right-section';
import Configuration from './configuration';
import CustomLogo from './custom-logo';
import Help from './help';
import NavbarHeader from './navbar-header';
import Notifications from './notifications';
import UserOptions from './user-options';

Expand All @@ -10,6 +11,7 @@ export default {
Configuration,
CustomLogo,
Help,
NavbarHeader,
Notifications,
UserOptions,
};