diff --git a/app/assets/stylesheets/patternfly_overrides.scss b/app/assets/stylesheets/patternfly_overrides.scss
index d72f21ab8f3..773bcb79f45 100644
--- a/app/assets/stylesheets/patternfly_overrides.scss
+++ b/app/assets/stylesheets/patternfly_overrides.scss
@@ -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);
+ }
+ }
+ }
+}
+
+
diff --git a/app/helpers/application_helper/navbar.rb b/app/helpers/application_helper/navbar.rb
index 0270954dd3f..f2bad38901d 100644
--- a/app/helpers/application_helper/navbar.rb
+++ b/app/helpers/application_helper/navbar.rb
@@ -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
diff --git a/app/javascript/components/main-menu/helpers.js b/app/javascript/components/main-menu/helpers.js
new file mode 100644
index 00000000000..e1215e3aba7
--- /dev/null
+++ b/app/javascript/components/main-menu/helpers.js
@@ -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();
+};
diff --git a/app/javascript/components/main-menu/main-menu.jsx b/app/javascript/components/main-menu/main-menu.jsx
new file mode 100644
index 00000000000..170f6c0acfd
--- /dev/null
+++ b/app/javascript/components/main-menu/main-menu.jsx
@@ -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 => ;
+
+const getLevelComponent = level => ({
+ 0: props => ,
+ 1: props => ,
+})[level] || Fallback;
+
+export const MenuItem = ({ level, ...props }) => getLevelComponent(level)(props);
+
+const MainMenu = ({ menu }) => (
+
+
+
+ {menu.map(props => )}
+
+
+
+);
+
+MainMenu.propTypes = {
+ menu: PropTypes.arrayOf(PropTypes.shape({
+ ...menuProps,
+ items: PropTypes.arrayOf(PropTypes.shape({
+ ...menuProps,
+ items: PropTypes.arrayOf(PropTypes.shape(RecursiveMenuProps())),
+ })),
+ })).isRequired,
+};
+
+export default MainMenu;
diff --git a/app/javascript/components/main-menu/recursive-props.js b/app/javascript/components/main-menu/recursive-props.js
new file mode 100644
index 00000000000..ae34a993cf6
--- /dev/null
+++ b/app/javascript/components/main-menu/recursive-props.js
@@ -0,0 +1,16 @@
+import PropTypes from 'prop-types';
+
+export const menuProps = {
+ id: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ iconClass: PropTypes.string,
+ href: PropTypes.string.isRequired,
+ preventHref: PropTypes.bool,
+ visible: PropTypes.bool,
+ active: PropTypes.bool,
+};
+
+export const RecursiveMenuProps = () => ({
+ ...menuProps,
+ items: PropTypes.arrayOf(PropTypes.shape(menuProps)),
+});
diff --git a/app/javascript/components/main-menu/second-level.jsx b/app/javascript/components/main-menu/second-level.jsx
new file mode 100644
index 00000000000..bb36e2a9dbf
--- /dev/null
+++ b/app/javascript/components/main-menu/second-level.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { MenuItem } from './main-menu';
+import { menuProps } from './recursive-props';
+import {
+ getHrefByType, getIdByCategory, getTargetByType, handleUnsavedChanges,
+} from './helpers';
+
+const SecondLevel = ({
+ id,
+ title,
+ href,
+ items,
+ level,
+ type,
+ active,
+}) => (
+
0 ? 'tertiary-nav-item-pf' : ''} ${active ? 'active' : ''}`} id={getIdByCategory(items.length > 0, id)}>
+ {
+ if (handleUnsavedChanges(type) === false) {
+ event.preventDefault();
+ }
+ return false;
+ }}
+ target={getTargetByType(type)}
+ >
+ {title}
+
+
+
+ {items.length > 0 && (
+
+ {items.map(props => )}
+
+ )}
+
+
+);
+
+SecondLevel.propTypes = {
+ ...menuProps,
+ items: PropTypes.arrayOf(PropTypes.shape({
+ ...menuProps,
+ })),
+};
+
+SecondLevel.defaultProps = {
+ items: [],
+};
+
+export default SecondLevel;
diff --git a/app/javascript/components/main-menu/third-level.jsx b/app/javascript/components/main-menu/third-level.jsx
new file mode 100644
index 00000000000..3d09edbafac
--- /dev/null
+++ b/app/javascript/components/main-menu/third-level.jsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { menuProps } from './recursive-props';
+import { getHrefByType, getTargetByType, handleUnsavedChanges } from './helpers';
+
+const ThirdLevel = ({
+ id,
+ title,
+ href,
+ active,
+ visible,
+ type,
+}) => (!visible ? null : (
+
+));
+
+ThirdLevel.propTypes = {
+ ...menuProps,
+};
+
+export default ThirdLevel;
diff --git a/app/javascript/components/main-menu/top-level.jsx b/app/javascript/components/main-menu/top-level.jsx
new file mode 100644
index 00000000000..80c9bb9952e
--- /dev/null
+++ b/app/javascript/components/main-menu/top-level.jsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { MenuItem } from './main-menu';
+import { menuProps, RecursiveMenuProps } from './recursive-props';
+import {
+ getHrefByType, getSectionId, getTargetByType, handleUnsavedChanges, getItemId,
+} from './helpers';
+
+const TopLevel = ({
+ level,
+ id,
+ title,
+ iconClass,
+ href,
+ active,
+ items,
+ type,
+}) => {
+ const isSection = items.length > 0;
+
+ if (isSection) {
+ return (
+
+ {
+ if (handleUnsavedChanges(type) === false) {
+ event.preventDefault();
+ }
+ return false;
+ }}
+ href={getHrefByType(type, href, id)}
+ target={getTargetByType(type)}
+ className="top-level"
+ >
+
+ {title}
+
+
+
+
+
+ );
+ }
+
+ return (
+
+ {
+ if (handleUnsavedChanges(type) === false) {
+ event.preventDefault();
+ }
+ return false;
+ }}
+ href={getHrefByType(type, href, id)}
+ >
+
+ {title}
+
+
+ );
+};
+
+TopLevel.propTypes = {
+ ...menuProps,
+ items: PropTypes.arrayOf(PropTypes.shape({
+ ...menuProps,
+ items: PropTypes.arrayOf(PropTypes.shape(RecursiveMenuProps())),
+ })),
+};
+
+TopLevel.defaultProps = {
+ items: [],
+};
+
+export default TopLevel;
diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js
index 6c04450df92..7e552ea9185 100644
--- a/app/javascript/packs/component-definitions-common.js
+++ b/app/javascript/packs/component-definitions-common.js
@@ -20,6 +20,7 @@ import GenericGroupWrapper from '../react/generic_group_wrapper';
import GraphQLExplorer from '../graphql-explorer';
import { HierarchicalTreeView } from '../components/tree-view';
import ImportDatastoreViaGit from '../components/automate-import-export-form/import-datastore-via-git';
+import MainMenu from '../components/main-menu/main-menu';
import MiqAboutModal from '../components/miq-about-modal';
import MiqToolbar from '../components/miq-toolbar';
import OptimizationList from '../optimization/optimization_list';
@@ -60,6 +61,7 @@ ManageIQ.component.addReact('GenericGroupWrapper', GenericGroupWrapper);
ManageIQ.component.addReact('GraphQLExplorer', GraphQLExplorer);
ManageIQ.component.addReact('HierarchicalTreeView', HierarchicalTreeView);
ManageIQ.component.addReact('ImportDatastoreViaGit', ImportDatastoreViaGit);
+ManageIQ.component.addReact('MainMenu', MainMenu);
ManageIQ.component.addReact('MiqAboutModal', MiqAboutModal);
ManageIQ.component.addReact('MiqToolbar', MiqToolbar);
ManageIQ.component.addReact('OptimizationList', OptimizationList);
diff --git a/app/javascript/packs/globals.js b/app/javascript/packs/globals.js
index 340deb2dbc1..80e5a6a9612 100644
--- a/app/javascript/packs/globals.js
+++ b/app/javascript/packs/globals.js
@@ -18,9 +18,23 @@ require('jquery-ui/ui/widgets/sortable');
require('jquery-ujs');
require('jquery.observe_field');
require('patternfly-bootstrap-treeview');
-require('patternfly/dist/js/patternfly.min');
require('jquery.hotkeys');
+// all of patternfly js except for vertical navigation (patternfly-functions-vertical-nav.js)
+// list from https://github.com/patternfly/patternfly/blob/master/Gruntfile.js#L62-L85
+require('patternfly/dist/js/patternfly-settings.js');
+require('patternfly/dist/js/patternfly-functions-base.js');
+require('patternfly/dist/js/patternfly-functions-list.js');
+require('patternfly/dist/js/patternfly-functions-sidebar.js');
+require('patternfly/dist/js/patternfly-functions-popovers.js');
+require('patternfly/dist/js/patternfly-functions-data-tables.js');
+require('patternfly/dist/js/patternfly-functions-navigation.js');
+require('patternfly/dist/js/patternfly-functions-count-chars.js');
+require('patternfly/dist/js/patternfly-functions-colors.js');
+require('patternfly/dist/js/patternfly-functions-charts.js');
+require('patternfly/dist/js/patternfly-functions-fixed-heights.js');
+require('patternfly/dist/js/patternfly-functions-tree-grid.js');
+
window.angular = require('angular');
require('angular-ui-bootstrap');
require('angular-gettext');
@@ -48,9 +62,9 @@ require('moment-duration-format')(window.moment);
require('@pf3/timeline');
window.CodeMirror = require('codemirror');
-require('codemirror/mode/css/css.js'); // not referenced directly, needed by htmlmixed
+require('codemirror/mode/css/css.js'); // not referenced directly, needed by htmlmixed
require('codemirror/mode/htmlmixed/htmlmixed.js');
-require('codemirror/mode/javascript/javascript.js'); // not referenced directly, needed by htmlmixed
+require('codemirror/mode/javascript/javascript.js'); // not referenced directly, needed by htmlmixed
require('codemirror/mode/ruby/ruby.js');
require('codemirror/mode/shell/shell.js');
require('codemirror/mode/xml/xml.js');
diff --git a/app/javascript/spec/main-menu/__snapshots__/main-menu.spec.js.snap b/app/javascript/spec/main-menu/__snapshots__/main-menu.spec.js.snap
new file mode 100644
index 00000000000..fe62382fc89
--- /dev/null
+++ b/app/javascript/spec/main-menu/__snapshots__/main-menu.spec.js.snap
@@ -0,0 +1,768 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Main menu test should render correctly 1`] = `
+
+
+
+
+
+`;
diff --git a/app/javascript/spec/main-menu/main-menu.spec.js b/app/javascript/spec/main-menu/main-menu.spec.js
new file mode 100644
index 00000000000..9787d066e84
--- /dev/null
+++ b/app/javascript/spec/main-menu/main-menu.spec.js
@@ -0,0 +1,112 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import toJson from 'enzyme-to-json';
+import MainMenu from '../../components/main-menu/main-menu';
+import TopLevel from '../../components/main-menu/top-level';
+import SecondLevel from '../../components/main-menu/second-level';
+import ThirdLevel from '../../components/main-menu/third-level';
+
+describe('Main menu test', () => {
+ let mockNavItems;
+
+ beforeEach(() => {
+ mockNavItems = [{
+ id: 'vi',
+ title: 'Cloud Intel',
+ iconClass: 'fa fa-dashboard',
+ href: '/dashboard/maintab/?tab=vi',
+ preventHref: true,
+ visible: true,
+ active: true,
+ type: 'default',
+ items: [{
+ id: 'dashboard',
+ title: 'Dashboard',
+ iconClass: null,
+ href: '/dashboard/show',
+ preventHref: false,
+ visible: true,
+ active: true,
+ type: 'modal',
+ items: [],
+ }, {
+ id: 'chargeback',
+ title: 'Chargeback',
+ iconClass: null,
+ href: '/chargeback/explorer',
+ preventHref: false,
+ visible: true,
+ active: false,
+ type: 'big_iframe',
+ items: [],
+ }],
+ }, {
+ id: 'compute',
+ title: 'Compute',
+ iconClass: 'pficon pficon-cpu',
+ href: '/dashboard/maintab/?tab=compute',
+ preventHref: true,
+ visible: true,
+ active: false,
+ type: 'new_window',
+ items: [{
+ id: 'clo',
+ title: 'Clouds',
+ iconClass: 'fa fa-plus',
+ href: '/dashboard/maintab/?tab=clo',
+ preventHref: true,
+ visible: true,
+ active: false,
+ type: 'new_window',
+ items: [{
+ id: 'ems_cloud',
+ title: 'Providers',
+ iconClass: null,
+ href: '/ems_cloud/show_list',
+ preventHref: false,
+ visible: true,
+ active: true,
+ type: 'new_window',
+ items: [],
+ }, {
+ id: 'availability_zone',
+ title: 'Availability Zones',
+ iconClass: null,
+ href: '/availability_zone/show_list',
+ preventHref: false,
+ visible: true,
+ active: false,
+ type: 'default',
+ items: [],
+ }, {
+ id: 'host_aggregate',
+ title: 'Host Aggregates',
+ iconClass: null,
+ href: '/host_aggregate/show_list',
+ preventHref: false,
+ visible: false,
+ active: false,
+ type: 'new_window',
+ items: [],
+ }],
+ }],
+ }];
+ });
+
+ it('should render correctly', () => {
+ const wrapper = mount();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ });
+
+ it('should render proper level components', () => {
+ const wrapper = mount();
+ expect(wrapper.find(TopLevel)).toHaveLength(2);
+ expect(wrapper.find(SecondLevel)).toHaveLength(3);
+ expect(wrapper.find(ThirdLevel)).toHaveLength(3);
+ });
+
+ it('should render active third level components properly', () => {
+ const wrapper = mount();
+ expect(wrapper.find(ThirdLevel).find('li.menu-list-group-item.active')).toHaveLength(1);
+ });
+});
diff --git a/app/javascript/spec/toolbar/__snapshots__/dashboard_toolbar.spec.jsx.snap b/app/javascript/spec/toolbar/__snapshots__/dashboard_toolbar.spec.jsx.snap
index 0bb8ce27407..a26232b30d5 100644
--- a/app/javascript/spec/toolbar/__snapshots__/dashboard_toolbar.spec.jsx.snap
+++ b/app/javascript/spec/toolbar/__snapshots__/dashboard_toolbar.spec.jsx.snap
@@ -23,140 +23,146 @@ exports[` renders ok 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ add
+
+
+
+
+
+
+
+
+
+
+
+
item_nav_class(menu_section), :id => "menu_item_#{menu_section.id}"}
- %a{menu_section.link_params}
- %span{:class => menu_section.icon}
- %span.list-group-item-value
- = _(menu_section.name)
-
- - else
- %li.list-group-item.secondary-nav-item-pf{"data-target" => "#menu-#{menu_section.id}", :class => section_nav_class(menu_section)}
- %a{menu_section.link_params}
- %span{:class => menu_section.icon}
- %span.list-group-item-value
- = h(_(menu_section.name))
-
- - if menu_section.subsection?
- .nav-pf-secondary-nav{:id => "#menu-#{menu_section.id}"}
- .nav-item-pf-header
- %a.secondary-collapse-toggle-pf{"data-toggle" => "collapse-secondary-nav"}
- %span
- = h(_(menu_section.name))
-
- %ul.list-group
- - menu_section.items.each do |menu_item|
- - if menu_item.visible? && menu_item.leaf?
- %li.list-group-item{:class => item_nav_class(menu_item), :id => "menu_item_#{menu_item.id}"}
- %a{menu_item.link_params}
- %span.list-group-item-value
- = _(menu_item.name)
-
- - elsif menu_item.visible?
- %li.list-group-item.tertiary-nav-item-pf{"data-target" => "#menu-#{menu_item.id}", :class => section_nav_class(menu_item)}
- %a{menu_item.link_params}
- %span.list-group-item-value
- = _(menu_item.name)
- .nav-pf-tertiary-nav
- .nav-item-pf-header
- %a.tertiary-collapse-toggle-pf{"data-toggle" => "collapse-tertiary-nav"}
- %span
- = h(_(menu_item.name))
-
- %ul.list-group
- - menu_item.items.each do |menu_item_deep|
- - if menu_item_deep.visible?
- %li.list-group-item{:class => item_nav_class(menu_item_deep), :id => "menu_item_#{menu_item_deep.id}"}
- %a{menu_item_deep.link_params}
- %span.list-group-item-value
- = _(menu_item_deep.name)
-
- - else
- .nav-pf-secondary-nav{:id => "#menu-#{menu_section.id}"}
- .nav-item-pf-header
- %a.secondary-collapse-toggle-pf{"data-toggle" => "collapse-secondary-nav"}
- %span
- = h(_(menu_section.name))
- %ul.list-group
- - menu_section.items.each do |menu_item|
- - if menu_item.visible?
- %li.list-group-item{:class => item_nav_class(menu_item), :id => "menu_item_#{menu_item.id}"}
- %a{menu_item.link_params}
- %span.list-group-item-value
- = _(menu_item.name)
+= react('MainMenu', { :menu => menu_to_json })