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

Vertical navbar #5854

Merged
merged 30 commits into from
Nov 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
24e56b0
WIP
martinpovolny May 21, 2019
ee24b79
Rewritten structure generation, moved to navbar.rb
fhlavac Jul 23, 2019
ff1f05d
Rewritten and divided navbar component
fhlavac Jul 23, 2019
270a834
Created main-menu tests
fhlavac Jul 23, 2019
c2545c6
Fixed issues from review
fhlavac Jul 31, 2019
fcd0fc7
Fixed trailing whitespaces
fhlavac Jul 31, 2019
baf1b67
Added support for missing link_params from review
fhlavac Aug 6, 2019
dabe752
Added empty line after guard clause to navbar.rb
fhlavac Aug 27, 2019
915fb6e
Fixed highlighting of active item at second level
fhlavac Sep 25, 2019
f2c32ed
Added common checkForChanges handling
fhlavac Oct 3, 2019
cda7508
Moved shared item-type logic to helpers.js
fhlavac Oct 9, 2019
3d45614
Fixed modal item-type click behavior
fhlavac Oct 9, 2019
0921472
Fixed wrong IDs
fhlavac Oct 9, 2019
b64ade1
Updated test snapshot
fhlavac Oct 9, 2019
1da7629
Cleaned code and updated snapshot
fhlavac Oct 9, 2019
805ab47
Addded missing # to data-targets
fhlavac Oct 10, 2019
717e6eb
Updated snapshot
fhlavac Oct 10, 2019
fc71dab
Removed PF js imports for vertical navbar
fhlavac Oct 16, 2019
7a3ff1e
Replaced list-group-item class with custom class
fhlavac Oct 24, 2019
595afdb
Replaced onMouseDown handling with onClick
fhlavac Oct 24, 2019
e088660
Updated third-level section test and snapshot
fhlavac Oct 24, 2019
74903ab
Added "collapse" styling at the top-level
fhlavac Oct 30, 2019
53897c9
Updated test snapshots
fhlavac Oct 30, 2019
9dc4894
Added id to top and second level items
fhlavac Nov 6, 2019
72d108a
Remove expanding at top-level when no items
fhlavac Nov 6, 2019
bd79296
Update snapshots after adding top+second level ids
fhlavac Nov 6, 2019
4a288aa
Update dashboard_toolbar snapshot
fhlavac Nov 6, 2019
1a07efd
Make IDs for sections and items different
fhlavac Nov 6, 2019
a727a4b
Remove data-targets at top and second level
fhlavac Nov 6, 2019
4eb3f7d
Update main-menu snapshots
fhlavac Nov 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions app/assets/stylesheets/patternfly_overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1078,3 +1078,230 @@ table.table-compressed tr td .piechart {
table.c3-tooltip td {
background-color: #393f44;
}

.nav-pf-vertical {
.menu-list-group-item {
border-bottom: 1px solid rgb(3, 3, 3);
border-top: 1px solid rgb(3, 3, 3);
box-sizing: border-box;
padding: 0;
margin-bottom: -1px;
position: relative;
&:first-child {
border-top: none;
}
a {
background-color: transparent;
color: #d1d1d1;
cursor: pointer;
display: block;
font-size: 14px;
font-weight: 400;
height: 63px;
line-height: 26px;
padding: 17px 20px 17px 25px;
position: relative;
white-space: nowrap;
width: 225px;
text-decoration: none;
.pficon, .fa {
color: #72767b;
float: left;
font-size: 20px;
line-height: 26px;
margin-right: 10px;
text-align: center;
width: 24px;
}
}
.list-group-item-value {
flex: 1;
max-width: none;
padding-right: 15px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
}
}
.menu-list-group-item.active {
a {
background-color: #393f44;
color: #fff;
font-weight: 600;
.pficon, .fa {
color: #39a5dc;
}
}
a:before {
background: #39a5dc;
content: " ";
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 3px;
}
.nav-pf-secondary-nav {
.nav-item-pf-header {
a {
::before {
opacity: 1;
}
}
}
}
}
.menu-list-group-item:hover {
a {
background-color: #393f44;
color: #fff;
font-weight: 600;
width: calc(225px + 1px);
z-index: 1031;
.pficon, .fa {
color: #39a5dc;
}
}
}
.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;
font-family: "Open Sans", Helvetica, Arial, sans-serif;
font-weight: 400;
cursor: pointer;
padding: 0;
height: auto;
background: transparent;
z-index: 0;
}
}
.menu-list-group-item {
border: none;
padding: 0 0 5px 0;
width: 225px;
margin-bottom: -1px;
&.tertiary-nav-item-pf {
a {
::after {
color: #72767b;
content: "\f105";
display: block;
font-family: "FontAwesome";
font-size: 20px;
line-height: 20px;
padding: 0;
position: absolute;
right: 20px;
top: 4px;
}
}
}
a {
background-color: #393f44;
color: #d1d1d1;
font-size: 12px;
font-weight: inherit;
height: inherit;
margin-left: 20px;
width: calc(225px - 20px);
display: flex;
opacity: 1;
padding: 4px 0 2px 0;
.list-group-item-value {
padding-left: 5px;
flex: 1;
max-width: none;
padding-right: 15px;
}
}
}
.menu-list-group-item.active, .menu-list-group-item:hover {
a {
background-color: #4d5258;
color: #fff;
font-weight: 600;
}
}
a:before {
display: none;
}
.menu-list-group-item:hover {
a {
color: #fff;
font-weight: 600;
}
}
}

.nav-pf-tertiary-nav{
.nav-item-pf-header {
a {
font-size: 16px;
font-weight: 400 !important;
margin: 0;
padding: 0;
background-color: #4d5258;
color: #fff;
::after {
display: none !important;
}
}
}
.menu-list-group-item {
a {
background-color: #4d5258;
span {
font-weight: 400;
color: #fff;
}
::after {
display: none !important;
}
}
}
.menu-list-group-item.active, .menu-list-group-item:hover {
a {
background-color: #393f44;
span {
font-weight: 600;
}
}
}
}

&.collapsed {
.menu-list-group-item, .menu-list-group-item.active {
>a.top-level {
>.list-group-item-value {
display: none;
}
padding: 17px 0 17px 25px;
width: 75px;
&::after {
right: 10px;
}
}
>a.top-level:hover {
width: calc(75px + 1px);
}
}
}
}


79 changes: 49 additions & 30 deletions app/helpers/application_helper/navbar.rb
Original file line number Diff line number Diff line change
@@ -1,45 +1,64 @@
module ApplicationHelper
module Navbar
def menu_to_json(placement = :default)
structure = []
Menu::Manager.menu(placement) do |menu_section|
next unless menu_section.visible?

structure << item_to_hash(menu_section)
end
structure
end

def item_to_hash(item)
{
:id => item.id,
:title => item.name,
:iconClass => item.icon,
:href => item.link_params[:href],
:type => item.type,
:preventHref => !item.href,
:visible => item.visible?,
:active => item_active?(item),
:items => item.items.to_a.map(&method(:item_to_hash))
}
end

# FIXME: The 'active' below is an active section not an item. That is wrong.
# What works is the "legacy" part that compares @layout to item.id.
# This assumes that these matches -- @layout and item.id. Moving forward we
# need to remove that assumption. However to do that we need figure some way
# to identify the active menu item here.
def item_nav_class(item)
active = controller.menu_section_id(controller.params) || @layout.to_sym
def item_active?(item)
if item.leaf?
# FIXME: remove @layout condition when every controller sets menu_section properly
active = controller.menu_section_id(controller.params) || @layout.to_sym
item.id.to_sym == active || item.id.to_sym == @layout.to_sym
else
return section_nav_class_iframe(item) if params[:action] == 'iframe'

# FIXME: remove @layout condition when every controller sets menu_section properly
item.id.to_sym == active || item.id.to_sym == @layout.to_sym ? 'active' : nil
end
active = controller.menu_section_id(controller.params) || @layout.to_sym

# special handling for custom menu sections and items
def section_nav_class_iframe(section)
if params[:sid].present?
section.id.to_s == params[:sid] ? 'active' : nil
elsif params[:id].present?
section.contains_item_id?(params[:id]) ? 'active' : nil
end
end

def section_nav_class(section)
return section_nav_class_iframe(section) if params[:action] == 'iframe'

active = controller.menu_section_id(controller.params) || @layout.to_sym
if item.parent.nil?
# first-level, fallback to old logic for now
# FIXME: exception behavior to remove
active = 'my_tasks' if %w[my_tasks all_tasks].include?(@layout)
active = 'cloud_volume' if @layout == 'cloud_volume_snapshot' || @layout == 'cloud_volume_backup'
active = 'cloud_object_store_container' if @layout == 'cloud_object_store_object'
active = active.to_sym
end

if section.parent.nil?
# first-level, fallback to old logic for now
# FIXME: exception behavior to remove
active = 'my_tasks' if %w[my_tasks all_tasks].include?(@layout)
active = 'cloud_volume' if @layout == 'cloud_volume_snapshot' || @layout == 'cloud_volume_backup'
active = 'cloud_object_store_container' if @layout == 'cloud_object_store_object'
active = active.to_sym
# FIXME: remove to_s, to_sym once all items use symbol ids
item.id.to_sym == active ||
item.contains_item_id?(active.to_s) ||
item.contains_item_id?(active.to_sym)
end
end

return 'active' if section.id.to_sym == active

# FIXME: remove to_s, to_sym once all items use symbol ids
return 'active' if section.contains_item_id?(active.to_s)
return 'active' if section.contains_item_id?(active.to_sym)
# special handling for custom menu sections and items
def section_nav_class_iframe(section)
params[:sid].present? && section.id.to_s == params[:sid] ||
params[:id].present? && section.contains_item_id?(params[:id])
end
end
end
25 changes: 25 additions & 0 deletions app/javascript/components/main-menu/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const getHrefByType = (type, href, id) => {
switch (type) {
case 'big_iframe':
return `/dashboard/iframe?id=${id}`;
case 'modal':
return undefined;
default:
return href;
}
};

export const getTargetByType = type => (type === 'new_window' ? '_blank' : '_self');

export const getItemId = id => `menu_item_${id}`;

export const getSectionId = id => `menu_section_${id}`;

export const getIdByCategory = (isSection, id) => (isSection ? getSectionId(id) : getItemId(id));

export const handleUnsavedChanges = (type) => {
if (type === 'modal') {
return sendDataWithRx({ type: 'showAboutModal' });
}
return window.miqCheckForChanges();
};
38 changes: 38 additions & 0 deletions app/javascript/components/main-menu/main-menu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Grid } from 'patternfly-react';
import TopLevel from './top-level';
import SecondLevel from './second-level';
import ThirdLevel from './third-level';
import { menuProps, RecursiveMenuProps } from './recursive-props';

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

const getLevelComponent = level => ({
0: props => <TopLevel level={level} {...props} />,
1: props => <SecondLevel level={level} {...props} />,
})[level] || Fallback;

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>
);

MainMenu.propTypes = {
menu: PropTypes.arrayOf(PropTypes.shape({
...menuProps,
items: PropTypes.arrayOf(PropTypes.shape({
...menuProps,
items: PropTypes.arrayOf(PropTypes.shape(RecursiveMenuProps())),
})),
})).isRequired,
};

export default MainMenu;
Loading