mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Merge pull request #7723 from cjcenizal/7467/pin-sidebar-functionality
[7467] Add "Collapse/Expand" button to sidebar.
This commit is contained in:
commit
962f036f42
24 changed files with 381 additions and 250 deletions
3
src/core_plugins/kibana/public/assets/play-circle.svg
Normal file
3
src/core_plugins/kibana/public/assets/play-circle.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
|
||||
<path d="M4 0c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-1 2l3 2-3 2v-4z" />
|
||||
</svg>
|
After Width: | Height: | Size: 176 B |
|
@ -100,6 +100,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) {
|
|||
safeLayout();
|
||||
$window.on('resize', safeLayout);
|
||||
$scope.$on('ready:vis', safeLayout);
|
||||
$scope.$on('globalNav:update', safeLayout);
|
||||
}
|
||||
|
||||
// return the panel object for an element.
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
<div class="content" chrome-context >
|
||||
<!-- TODO: These config dropdowns shouldn't be hard coded -->
|
||||
<nav class="app-links-wrapper" ng-show="chrome.getVisible()">
|
||||
|
||||
<li
|
||||
ng-if="!chrome.getBrand('logo') && !chrome.getBrand('smallLogo')"
|
||||
aria-label="{{ chrome.getAppTitle() }} Logo"
|
||||
class="logo kibana"
|
||||
></li>
|
||||
|
||||
<li
|
||||
ng-if="chrome.getBrand('logo')"
|
||||
ng-style="{ 'background': chrome.getBrand('logo') }"
|
||||
aria-label="{{ chrome.getAppTitle() }} Logo"
|
||||
class="logo hidden-sm"
|
||||
></li>
|
||||
|
||||
<li
|
||||
ng-if="chrome.getBrand('smallLogo')"
|
||||
ng-style="{ 'background': chrome.getBrand('smallLogo') }"
|
||||
aria-label="{{ chrome.getAppTitle() }} Logo"
|
||||
class="logo-small visible-sm hidden-xs"
|
||||
></li>
|
||||
|
||||
<app-switcher>
|
||||
</app-switcher>
|
||||
|
||||
<div class="bottom-apps">
|
||||
<div class="chrome-actions" kbn-chrome-append-nav-controls></div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
<div class="app-wrapper" ng-class="{ 'hidden-chrome': !chrome.getVisible() }">
|
||||
<div class="app-wrapper-panel">
|
||||
<kbn-notifications list="notifList"></kbn-notifications>
|
||||
<kbn-loading-indicator></kbn-loading-indicator>
|
||||
<div
|
||||
class="application"
|
||||
ng-class="'tab-' + chrome.getFirstPathSegment() + ' ' + chrome.getApplicationClasses()"
|
||||
ng-view
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -10,8 +10,10 @@ import 'ui/timefilter';
|
|||
import 'ui/notify';
|
||||
import 'ui/private';
|
||||
import 'ui/promises';
|
||||
import 'ui/storage';
|
||||
import 'ui/directives/kbn_src';
|
||||
import 'ui/watch_multi';
|
||||
import './services';
|
||||
|
||||
let chrome = {};
|
||||
let internals = _.defaults(
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
<app-switcher-link
|
||||
ng-repeat="link in switcher.shownNavLinks"
|
||||
app-switcher-link-is-active="link.active"
|
||||
app-switcher-link-is-disabled="!switcher.isNavLinkEnabled(link)"
|
||||
app-switcher-link-tooltip="link.tooltip"
|
||||
app-switcher-link-on-click="switcher.ensureNavigation($event, link)"
|
||||
app-switcher-link-href="link.active ? link.url : (link.lastSubUrl || link.url)"
|
||||
app-switcher-link-icon="link.icon"
|
||||
app-switcher-link-title="link.title"
|
||||
></app-switcher-link>
|
|
@ -1,72 +0,0 @@
|
|||
@import (reference) "~ui/styles/mixins";
|
||||
@import (reference) "~ui/styles/variables";
|
||||
|
||||
body { overflow-x: hidden; }
|
||||
|
||||
.app-links-wrapper {
|
||||
width: @as-open-width;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
background-color: @app-links-wrapper-background;
|
||||
overflow: hidden;
|
||||
transition: width @transition-time;
|
||||
transition-delay: @transition-delay;
|
||||
|
||||
&:hover {
|
||||
.app-title {
|
||||
display: inline-block;
|
||||
}
|
||||
+ .app-wrapper {
|
||||
transform: translateX(@as-open-width - @as-closed-width);
|
||||
}
|
||||
}
|
||||
|
||||
.logo-small,
|
||||
.logo {
|
||||
height: 70px;
|
||||
width: @as-open-width;
|
||||
list-style-type: none;
|
||||
&.kibana {
|
||||
background-image: url("~ui/images/kibana.svg");
|
||||
background-position: 6px 10px;
|
||||
background-size: 140px 50px;
|
||||
background-repeat: no-repeat;
|
||||
background-color: #e8488b;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-apps {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.app-wrapper {
|
||||
.real-flex-parent();
|
||||
position: absolute;
|
||||
transition: transform @transition-time;
|
||||
transition-delay: @transition-delay;
|
||||
left: @as-closed-width;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
|
||||
&.hidden-chrome {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.app-wrapper-panel {
|
||||
.flex-parent(@shrink: 0);
|
||||
box-shadow: -4px 0px 3px rgba(0,0,0,0.2);
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
|
||||
import appSwitcherLinkTemplate from './app_switcher_link.html';
|
||||
import './app_switcher_link.less';
|
||||
import uiModules from 'ui/modules';
|
||||
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
module.directive('appSwitcherLink', chrome => {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
isActive: '=appSwitcherLinkIsActive',
|
||||
isDisabled: '=appSwitcherLinkIsDisabled',
|
||||
tooltip: '=appSwitcherLinkTooltip',
|
||||
onClick: '&appSwitcherLinkOnClick',
|
||||
href: '=appSwitcherLinkHref',
|
||||
kbnRoute: '=appSwitcherLinkKbnRoute',
|
||||
icon: '=appSwitcherLinkIcon',
|
||||
title: '=appSwitcherLinkTitle'
|
||||
},
|
||||
template: appSwitcherLinkTemplate,
|
||||
link: scope => {
|
||||
scope.getHref = () => {
|
||||
if (scope.href) {
|
||||
return scope.href;
|
||||
}
|
||||
|
||||
if (scope.kbnRoute) {
|
||||
return chrome.addBasePath(scope.kbnRoute);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
|
@ -26,7 +26,7 @@ describe('appSwitcher directive', function () {
|
|||
|
||||
env = {
|
||||
$scope: $rootScope,
|
||||
$el: $compile($('<app-switcher>'))($rootScope),
|
||||
$el: $compile($('<app-switcher chrome="chrome">'))($rootScope),
|
||||
currentHref: href,
|
||||
location: domLocation
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
<global-nav-link
|
||||
ng-repeat="link in switcher.shownNavLinks"
|
||||
is-active="link.active"
|
||||
is-disabled="!switcher.isNavLinkEnabled(link)"
|
||||
tooltip-content="switcher.getTooltip(link)"
|
||||
on-click="switcher.ensureNavigation($event, link)"
|
||||
href="link.active ? link.url : (link.lastSubUrl || link.url)"
|
||||
icon="link.icon"
|
||||
title="link.title"
|
||||
></global-nav-link>
|
|
@ -1,7 +1,6 @@
|
|||
import DomLocationProvider from 'ui/dom_location';
|
||||
import { parse } from 'url';
|
||||
import { bindKey } from 'lodash';
|
||||
import './app_switcher.less';
|
||||
import uiModules from 'ui/modules';
|
||||
import appSwitcherTemplate from './app_switcher.html';
|
||||
|
||||
|
@ -55,11 +54,12 @@ uiModules
|
|||
.directive('appSwitcher', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
chrome: '=',
|
||||
},
|
||||
template: appSwitcherTemplate,
|
||||
controllerAs: 'switcher',
|
||||
controller($scope, appSwitcherEnsureNavigation) {
|
||||
// since we render this in an isolate scope we can't "require: ^chrome", but
|
||||
// rather than remove all helpfull checks we can just check here.
|
||||
controller($scope, appSwitcherEnsureNavigation, globalNavState) {
|
||||
if (!$scope.chrome || !$scope.chrome.getNavLinks) {
|
||||
throw new TypeError('appSwitcher directive requires the "chrome" config-object');
|
||||
}
|
||||
|
@ -72,6 +72,15 @@ uiModules
|
|||
// links don't cause full-navigation events in certain scenarios
|
||||
// so we force them when needed
|
||||
this.ensureNavigation = appSwitcherEnsureNavigation;
|
||||
|
||||
this.getTooltip = link => {
|
||||
// If the sidebar is open then we don't need to show the title because
|
||||
// it will already be visible.
|
||||
if (globalNavState.isOpen()) {
|
||||
return link.tooltip;
|
||||
}
|
||||
return link.tooltip ? link.title + ' - ' + link.tooltip : link.title;
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
46
src/ui/public/chrome/directives/global_nav/global_nav.html
Normal file
46
src/ui/public/chrome/directives/global_nav/global_nav.html
Normal file
|
@ -0,0 +1,46 @@
|
|||
<nav
|
||||
class="global-nav"
|
||||
ng-class="{'is-global-nav-open': isGlobalNavOpen}"
|
||||
ng-show="isVisible"
|
||||
>
|
||||
<!-- Logo -->
|
||||
<li
|
||||
ng-if="!logoBrand && !smallLogoBrand"
|
||||
aria-label="{{ appTitle }} Logo"
|
||||
class="logo kibana"
|
||||
></li>
|
||||
|
||||
<li
|
||||
ng-if="logoBrand"
|
||||
ng-style="{ 'background': logoBrand }"
|
||||
aria-label="{{ appTitle }} Logo"
|
||||
class="logo hidden-sm"
|
||||
></li>
|
||||
|
||||
<li
|
||||
ng-if="smallLogoBrand"
|
||||
ng-style="{ 'background': smallLogoBrand }"
|
||||
aria-label="{{ appTitle }} Logo"
|
||||
class="logo-small visible-sm hidden-xs"
|
||||
></li>
|
||||
|
||||
<!-- Main apps -->
|
||||
<app-switcher
|
||||
chrome="chrome"
|
||||
></app-switcher>
|
||||
|
||||
<!-- Bottom button -->
|
||||
<div class="gloal-nav__bottom-links">
|
||||
<div class="chrome-actions" kbn-chrome-append-nav-controls></div>
|
||||
|
||||
<!-- Open/close sidebar -->
|
||||
<global-nav-link
|
||||
class="{{ globalNavToggleButton.classes }}"
|
||||
tooltip-content="globalNavToggleButton.tooltipContent"
|
||||
on-click="toggleGlobalNav($event)"
|
||||
icon="'/plugins/kibana/assets/play-circle.svg'"
|
||||
title="globalNavToggleButton.title"
|
||||
></global-nav-link>
|
||||
</div>
|
||||
|
||||
</nav>
|
51
src/ui/public/chrome/directives/global_nav/global_nav.js
Normal file
51
src/ui/public/chrome/directives/global_nav/global_nav.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
|
||||
import './app_switcher';
|
||||
import './global_nav_link';
|
||||
|
||||
import globalNavTemplate from './global_nav.html';
|
||||
import './global_nav.less';
|
||||
import uiModules from 'ui/modules';
|
||||
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
module.directive('globalNav', globalNavState => {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
chrome: '=',
|
||||
isVisible: '=',
|
||||
logoBrand: '=',
|
||||
smallLogoBrand: '=',
|
||||
appTitle: '=',
|
||||
},
|
||||
template: globalNavTemplate,
|
||||
link: scope => {
|
||||
// App switcher functionality.
|
||||
function updateGlobalNav() {
|
||||
const isOpen = globalNavState.isOpen();
|
||||
scope.isGlobalNavOpen = isOpen;
|
||||
scope.globalNavToggleButton = {
|
||||
classes: isOpen ? 'global-nav-link--close' : undefined,
|
||||
title: isOpen ? 'Collapse' : 'Expand',
|
||||
tooltipContent: isOpen ? 'Collapse side bar' : 'Expand side bar',
|
||||
};
|
||||
|
||||
// Notify visualizations, e.g. the dashboard, that they should re-render.
|
||||
scope.$root.$broadcast('globalNav:update');
|
||||
}
|
||||
|
||||
updateGlobalNav();
|
||||
|
||||
scope.$root.$on('globalNavState:change', () => {
|
||||
updateGlobalNav();
|
||||
});
|
||||
|
||||
scope.toggleGlobalNav = event => {
|
||||
event.preventDefault();
|
||||
globalNavState.setOpen(!globalNavState.isOpen());
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
});
|
44
src/ui/public/chrome/directives/global_nav/global_nav.less
Normal file
44
src/ui/public/chrome/directives/global_nav/global_nav.less
Normal file
|
@ -0,0 +1,44 @@
|
|||
|
||||
@import (reference) "~ui/styles/variables";
|
||||
|
||||
.global-nav {
|
||||
width: @as-closed-width;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
background-color: @app-links-wrapper-background;
|
||||
overflow: hidden;
|
||||
|
||||
&.is-global-nav-open {
|
||||
width: @as-open-width;
|
||||
|
||||
.app-title {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
+ .app-wrapper {
|
||||
left: @as-open-width;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-small,
|
||||
.logo {
|
||||
height: 70px;
|
||||
width: @as-open-width;
|
||||
list-style-type: none;
|
||||
&.kibana {
|
||||
background-image: url("~ui/images/kibana.svg");
|
||||
background-position: 6px 10px;
|
||||
background-size: 140px 50px;
|
||||
background-repeat: no-repeat;
|
||||
background-color: #e8488b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gloal-nav__bottom-links {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
|
@ -2,9 +2,9 @@ import sinon from 'auto-release-sinon';
|
|||
import ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
|
||||
import '../app_switcher_link';
|
||||
import '../global_nav_link';
|
||||
|
||||
describe('appSwitcherLink directive', () => {
|
||||
describe('globalNavLink directive', () => {
|
||||
let scope;
|
||||
let $compile;
|
||||
|
||||
|
@ -19,15 +19,15 @@ describe('appSwitcherLink directive', () => {
|
|||
|
||||
function create(attrs) {
|
||||
const template = `
|
||||
<app-switcher-link
|
||||
app-switcher-link-is-active="appSwitcherLinkIsActive"
|
||||
app-switcher-link-is-disabled="appSwitcherLinkIsDisabled"
|
||||
app-switcher-link-tooltip="appSwitcherLinkTooltip"
|
||||
app-switcher-link-on-click="appSwitcherLinkOnClick()"
|
||||
app-switcher-link-href="appSwitcherLinkHref"
|
||||
app-switcher-link-kbn-route="appSwitcherLinkKbnRoute"
|
||||
app-switcher-link-icon="appSwitcherLinkIcon"
|
||||
app-switcher-link-title="appSwitcherLinkTitle"
|
||||
<global-nav-link
|
||||
is-active="isActive"
|
||||
is-disabled="isDisabled"
|
||||
tooltip-content="tooltipContent"
|
||||
on-click="onClick()"
|
||||
href="href"
|
||||
kbn-route="kbnRoute"
|
||||
icon="icon"
|
||||
title="title"
|
||||
/>
|
||||
`;
|
||||
|
||||
|
@ -42,96 +42,96 @@ describe('appSwitcherLink directive', () => {
|
|||
|
||||
describe('interface', () => {
|
||||
|
||||
describe('appSwitcherLinkIsActive attribute', () => {
|
||||
describe('isActive attribute', () => {
|
||||
it(`doesn't apply the active class when false`, () => {
|
||||
const element = create({
|
||||
appSwitcherLinkIsActive: false,
|
||||
isActive: false,
|
||||
});
|
||||
expect(element.hasClass('active')).to.be(false);
|
||||
});
|
||||
|
||||
it('applies the active class when true', () => {
|
||||
const element = create({
|
||||
appSwitcherLinkIsActive: true,
|
||||
isActive: true,
|
||||
});
|
||||
expect(element.hasClass('active')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appSwitcherLinkIsDisabled attribute', () => {
|
||||
it(`doesn't apply the is-app-switcher-link-disabled class when false`, () => {
|
||||
describe('isDisabled attribute', () => {
|
||||
it(`doesn't apply the is-global-nav-link-disabled class when false`, () => {
|
||||
const element = create({
|
||||
appSwitcherLinkIsDisabled: false,
|
||||
isDisabled: false,
|
||||
});
|
||||
expect(element.hasClass('is-app-switcher-link-disabled')).to.be(false);
|
||||
expect(element.hasClass('is-global-nav-link-disabled')).to.be(false);
|
||||
});
|
||||
|
||||
it('applies the is-app-switcher-link-disabled class when true', () => {
|
||||
it('applies the is-global-nav-link-disabled class when true', () => {
|
||||
const element = create({
|
||||
appSwitcherLinkIsDisabled: true,
|
||||
isDisabled: true,
|
||||
});
|
||||
expect(element.hasClass('is-app-switcher-link-disabled')).to.be(true);
|
||||
expect(element.hasClass('is-global-nav-link-disabled')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appSwitcherLinkTooltip attribute', () => {
|
||||
describe('tooltipContent attribute', () => {
|
||||
it('is applied to the tooltip directive', () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkTooltip: 'hello i am a tooltip',
|
||||
tooltipContent: 'hello i am a tooltip',
|
||||
};
|
||||
const element = create(attrs);
|
||||
expect(element.attr('tooltip')).to.be(attrs.appSwitcherLinkTooltip);
|
||||
expect(element.attr('tooltip')).to.be(attrs.tooltipContent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appSwitcherLinkOnClick attribute', () => {
|
||||
describe('onClick attribute', () => {
|
||||
it('is called when the link is clicked', () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkOnClick: sinon.spy(),
|
||||
onClick: sinon.spy(),
|
||||
};
|
||||
const element = create(attrs);
|
||||
element.find('[data-test-subj=appLink]').click();
|
||||
sinon.assert.called(attrs.appSwitcherLinkOnClick);
|
||||
sinon.assert.called(attrs.onClick);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appSwitcherLinkHref attribute', () => {
|
||||
describe('href attribute', () => {
|
||||
it('is applied to the link', () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkHref: 'link to a website',
|
||||
href: 'link to a website',
|
||||
};
|
||||
const element = create(attrs);
|
||||
const link = element.find('[data-test-subj=appLink]');
|
||||
expect(link.attr('href')).to.be(attrs.appSwitcherLinkHref);
|
||||
expect(link.attr('href')).to.be(attrs.href);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appSwitcherLinkKbnRoute attribute', () => {
|
||||
describe('kbnRoute attribute', () => {
|
||||
it(`is applied to the link when href isn't defined`, () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkKbnRoute: '#test',
|
||||
kbnRoute: '#test',
|
||||
};
|
||||
const element = create(attrs);
|
||||
const link = element.find('[data-test-subj=appLink]');
|
||||
expect(link.attr('href')).to.be(attrs.appSwitcherLinkKbnRoute);
|
||||
expect(link.attr('href')).to.be(attrs.kbnRoute);
|
||||
});
|
||||
|
||||
it(`isn't applied to the link when href is defined`, () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkHref: 'link to a website',
|
||||
appSwitcherLinkKbnRoute: '#test',
|
||||
href: 'link to a website',
|
||||
kbnRoute: '#test',
|
||||
};
|
||||
const element = create(attrs);
|
||||
const link = element.find('[data-test-subj=appLink]');
|
||||
expect(link.attr('href')).not.to.be(attrs.appSwitcherLinkKbnRoute);
|
||||
expect(link.attr('href')).not.to.be(attrs.kbnRoute);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appSwitcherLinkIcon attribute', () => {
|
||||
describe('icon attribute', () => {
|
||||
describe('when present', () => {
|
||||
it('displays the img element', () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkIcon: 'icon url',
|
||||
icon: 'icon url',
|
||||
};
|
||||
const element = create(attrs);
|
||||
const img = element.find('img');
|
||||
|
@ -140,7 +140,7 @@ describe('appSwitcherLink directive', () => {
|
|||
|
||||
it('hides the placeholder', () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkIcon: 'icon url',
|
||||
icon: 'icon url',
|
||||
};
|
||||
const element = create(attrs);
|
||||
const placeholder = element.find('[data-test-subj=appLinkIconPlaceholder]');
|
||||
|
@ -149,18 +149,18 @@ describe('appSwitcherLink directive', () => {
|
|||
|
||||
it(`is set as the img src`, () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkIcon: 'icon url',
|
||||
icon: 'icon url',
|
||||
};
|
||||
const element = create(attrs);
|
||||
const img = element.find('img');
|
||||
expect(img.attr('src')).to.contain(encodeURI(attrs.appSwitcherLinkIcon));
|
||||
expect(img.attr('src')).to.contain(encodeURI(attrs.icon));
|
||||
});
|
||||
});
|
||||
|
||||
describe('when not present', () => {
|
||||
it('hides the img element', () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkIcon: undefined,
|
||||
icon: undefined,
|
||||
};
|
||||
const element = create(attrs);
|
||||
const img = element.find('img');
|
||||
|
@ -169,7 +169,7 @@ describe('appSwitcherLink directive', () => {
|
|||
|
||||
it('displays the placeholder', () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkIcon: undefined,
|
||||
icon: undefined,
|
||||
};
|
||||
const element = create(attrs);
|
||||
const placeholder = element.find('[data-test-subj=appLinkIconPlaceholder]');
|
||||
|
@ -178,33 +178,33 @@ describe('appSwitcherLink directive', () => {
|
|||
|
||||
it(`uses the title's first letter as the placeholder`, () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkIcon: undefined,
|
||||
appSwitcherLinkTitle: 'Xyz',
|
||||
icon: undefined,
|
||||
title: 'Xyz',
|
||||
};
|
||||
const element = create(attrs);
|
||||
const placeholder = element.find('[data-test-subj=appLinkIconPlaceholder]');
|
||||
expect(placeholder.text()).to.contain(attrs.appSwitcherLinkTitle[0]);
|
||||
expect(placeholder.text()).to.contain(attrs.title[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('appSwitcherLinkTitle attribute', () => {
|
||||
describe('title attribute', () => {
|
||||
it('is displayed', () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkTitle: 'demo title',
|
||||
title: 'demo title',
|
||||
};
|
||||
const element = create(attrs);
|
||||
const title = element.find('.app-switcher-link__title');
|
||||
expect(title.text().trim()).to.be(attrs.appSwitcherLinkTitle);
|
||||
const title = element.find('.global-nav-link__title');
|
||||
expect(title.text().trim()).to.be(attrs.title);
|
||||
});
|
||||
|
||||
it('is set as a title attribute on the anchor tag', () => {
|
||||
const attrs = {
|
||||
appSwitcherLinkTitle: 'demo title',
|
||||
title: 'demo title',
|
||||
};
|
||||
const element = create(attrs);
|
||||
const link = element.find('[data-test-subj=appLink]');
|
||||
expect(link.attr('title')).to.be(attrs.appSwitcherLinkTitle);
|
||||
expect(link.attr('title')).to.be(attrs.title);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,35 +1,35 @@
|
|||
<div
|
||||
class="app-switcher-link"
|
||||
ng-class="{ active: isActive, 'is-app-switcher-link-disabled': isDisabled }"
|
||||
tooltip="{{ tooltip }}"
|
||||
class="global-nav-link {{classes}}"
|
||||
ng-class="{ active: isActive, 'is-global-nav-link-disabled': isDisabled }"
|
||||
tooltip="{{ tooltipContent }}"
|
||||
tooltip-placement="right"
|
||||
tooltip-popup-delay="400"
|
||||
tooltip-popup-delay="0"
|
||||
tooltip-append-to-body="1"
|
||||
>
|
||||
<a
|
||||
class="app-switcher-link__anchor"
|
||||
class="global-nav-link__anchor"
|
||||
href="{{ getHref() }}"
|
||||
ng-click="onClick({ $event: $event })"
|
||||
data-test-subj="appLink"
|
||||
title="{{ title }}"
|
||||
>
|
||||
<div class="app-switcher-link__icon">
|
||||
<div class="global-nav-link__icon">
|
||||
<img
|
||||
ng-if="icon"
|
||||
class="app-switcher-link__icon-image"
|
||||
class="global-nav-link__icon-image"
|
||||
kbn-src="{{ '/' + icon }}"
|
||||
>
|
||||
|
||||
<span
|
||||
ng-if="!icon"
|
||||
class="app-switcher-link__icon-placeholder"
|
||||
class="global-nav-link__icon-placeholder"
|
||||
data-test-subj="appLinkIconPlaceholder"
|
||||
>
|
||||
{{ title[0] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="app-switcher-link__title">
|
||||
<div class="global-nav-link__title">
|
||||
{{ title }}
|
||||
</div>
|
||||
</a>
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
import globalNavLinkTemplate from './global_nav_link.html';
|
||||
import './global_nav_link.less';
|
||||
import uiModules from 'ui/modules';
|
||||
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
module.directive('globalNavLink', chrome => {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
isActive: '=',
|
||||
isDisabled: '=',
|
||||
tooltipContent: '=',
|
||||
onClick: '&',
|
||||
href: '=',
|
||||
kbnRoute: '=',
|
||||
icon: '=',
|
||||
title: '=',
|
||||
},
|
||||
template: globalNavLinkTemplate,
|
||||
link: scope => {
|
||||
scope.getHref = () => {
|
||||
if (scope.href) {
|
||||
return scope.href;
|
||||
}
|
||||
|
||||
if (scope.kbnRoute) {
|
||||
return chrome.addBasePath(scope.kbnRoute);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,14 +1,15 @@
|
|||
@import (reference) "~ui/styles/variables";
|
||||
|
||||
.app-switcher-link {
|
||||
width: @as-open-width;
|
||||
.global-nav-link {
|
||||
position: relative;
|
||||
width: @as-closed-width;
|
||||
height: @app-icon-height;
|
||||
line-height: @app-line-height;
|
||||
|
||||
&.is-app-switcher-link-disabled {
|
||||
&.is-global-nav-link-disabled {
|
||||
opacity: 0.5;
|
||||
|
||||
.app-switcher-link__anchor {
|
||||
.global-nav-link__anchor {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
@ -17,27 +18,30 @@
|
|||
&.active {
|
||||
background-color: @app-links-active-background;
|
||||
|
||||
.app-switcher-link__anchor {
|
||||
.global-nav-link__anchor {
|
||||
color: @white;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-switcher-link__anchor {
|
||||
.is-global-nav-open {
|
||||
.global-nav-link {
|
||||
width: @as-open-width;
|
||||
}
|
||||
}
|
||||
|
||||
.global-nav-link__anchor {
|
||||
display: block;
|
||||
height: 100%;
|
||||
color: #ebf7fa;
|
||||
|
||||
/**
|
||||
* 1. TODO: Override anchor styles. Fix this by removing a tag styles.
|
||||
*/
|
||||
&:focus {
|
||||
color: #ebf7fa; /* 1 */
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
|
||||
.app-switcher-link__icon {
|
||||
.global-nav-link__icon {
|
||||
display: inline-block;
|
||||
width: @as-closed-width;
|
||||
height: @app-icon-height;
|
||||
|
@ -49,7 +53,7 @@
|
|||
/**
|
||||
* This imgae is used to display the icon.
|
||||
*/
|
||||
.app-switcher-link__icon-image {
|
||||
.global-nav-link__icon-image {
|
||||
height: 18px;
|
||||
margin-top: 8px;
|
||||
filter: invert(100%);
|
||||
|
@ -58,20 +62,33 @@
|
|||
/**
|
||||
* This placeholder text gets shown if there is no specified icon.
|
||||
*/
|
||||
.app-switcher-link__icon-placeholder {
|
||||
.global-nav-link__icon-placeholder {
|
||||
line-height: @app-icon-height;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.app-switcher-link__title {
|
||||
.global-nav-link__title {
|
||||
display: none;
|
||||
width: calc(@as-open-width - @as-closed-width);
|
||||
display: inline-block;
|
||||
float: right;
|
||||
font-size: 0.9em;
|
||||
text-align: left;
|
||||
padding-left: 3px;
|
||||
line-height: @app-icon-height;
|
||||
white-space: nowrap;
|
||||
|
||||
.is-global-nav-open & {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.global-nav-link--close {
|
||||
/**
|
||||
* 1. Translation accounts for the icon image being slightly off-center.
|
||||
*/
|
||||
.global-nav-link__icon {
|
||||
transform: translateX(1px) scaleX(-1); /* 1 */
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import './app_switcher';
|
||||
import './app_switcher_link';
|
||||
|
||||
import './global_nav';
|
||||
|
||||
import kbnChromeProv from './kbn_chrome';
|
||||
import kbnChromeNavControlsProv from './append_nav_controls';
|
||||
import './kbn_loading_indicator';
|
||||
|
|
21
src/ui/public/chrome/directives/kbn_chrome.html
Normal file
21
src/ui/public/chrome/directives/kbn_chrome.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<div class="content" chrome-context >
|
||||
<global-nav
|
||||
chrome="chrome"
|
||||
is-visible="chrome.getVisible()"
|
||||
logo-brand="chrome.getBrand('logo')"
|
||||
small-logo-brand="chrome.getBrand('smallLogo')"
|
||||
app-title="chrome.getAppTitle()"
|
||||
></global-nav>
|
||||
|
||||
<div class="app-wrapper" ng-class="{ 'hidden-chrome': !chrome.getVisible() }">
|
||||
<div class="app-wrapper-panel">
|
||||
<kbn-notifications list="notifList"></kbn-notifications>
|
||||
<kbn-loading-indicator></kbn-loading-indicator>
|
||||
<div
|
||||
class="application"
|
||||
ng-class="'tab-' + chrome.getFirstPathSegment() + ' ' + chrome.getApplicationClasses()"
|
||||
ng-view
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,15 +1,16 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
import './kbn_chrome.less';
|
||||
import UiModules from 'ui/modules';
|
||||
|
||||
export default function (chrome, internals) {
|
||||
|
||||
UiModules
|
||||
.get('kibana')
|
||||
.directive('kbnChrome', function ($rootScope) {
|
||||
.directive('kbnChrome', $rootScope => {
|
||||
return {
|
||||
template($el) {
|
||||
const $content = $(require('ui/chrome/chrome.html'));
|
||||
const $content = $(require('./kbn_chrome.html'));
|
||||
const $app = $content.find('.application');
|
||||
|
||||
if (internals.rootController) {
|
||||
|
@ -43,6 +44,7 @@ export default function (chrome, internals) {
|
|||
// and some local values
|
||||
chrome.httpActive = $http.pendingRequests;
|
||||
$scope.notifList = require('ui/notify')._notifs;
|
||||
|
||||
return chrome;
|
||||
}
|
||||
};
|
||||
|
|
34
src/ui/public/chrome/directives/kbn_chrome.less
Normal file
34
src/ui/public/chrome/directives/kbn_chrome.less
Normal file
|
@ -0,0 +1,34 @@
|
|||
|
||||
@import (reference) "~ui/styles/mixins";
|
||||
@import (reference) "~ui/styles/variables";
|
||||
|
||||
body { overflow-x: hidden; }
|
||||
|
||||
.app-wrapper {
|
||||
.real-flex-parent();
|
||||
position: absolute;
|
||||
left: @as-closed-width;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
|
||||
/**
|
||||
* 1. Dirty, but we need to override the .is-global-nav-open state
|
||||
* when we're looking at the log-in screen.
|
||||
*/
|
||||
&.hidden-chrome {
|
||||
left: 0 !important; /* 1 */
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.app-wrapper-panel {
|
||||
.flex-parent(@shrink: 0);
|
||||
box-shadow: -4px 0px 3px rgba(0,0,0,0.2);
|
||||
}
|
18
src/ui/public/chrome/services/global_nav_state.js
Normal file
18
src/ui/public/chrome/services/global_nav_state.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
import modules from 'ui/modules';
|
||||
import angular from 'angular';
|
||||
|
||||
modules.get('kibana')
|
||||
.service('globalNavState', (localStorage, $rootScope) => {
|
||||
return {
|
||||
isOpen: () => {
|
||||
return localStorage.get('kibana.isGlobalNavOpen');
|
||||
},
|
||||
|
||||
setOpen: isOpen => {
|
||||
localStorage.set('kibana.isGlobalNavOpen', isOpen);
|
||||
$rootScope.$broadcast('globalNavState:change');
|
||||
return isOpen;
|
||||
}
|
||||
};
|
||||
});
|
1
src/ui/public/chrome/services/index.js
Normal file
1
src/ui/public/chrome/services/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
import './global_nav_state';
|
|
@ -342,7 +342,5 @@
|
|||
@as-closed-width: 53px;
|
||||
@app-icon-height: 38px;
|
||||
@app-line-height: 24px;
|
||||
@transition-time: .35s;
|
||||
@transition-delay: .25s;
|
||||
@app-links-wrapper-background: #3caed2;
|
||||
@app-links-active-background: #2f99c1;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue