diff --git a/src/ui/public/chrome/chrome.html b/src/ui/public/chrome/chrome.html
index 419615d665dda..42b78e7ee7f39 100644
--- a/src/ui/public/chrome/chrome.html
+++ b/src/ui/public/chrome/chrome.html
@@ -26,7 +26,7 @@
diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.html b/src/ui/public/chrome/directives/app_switcher/app_switcher.html
index 3a70738dcb73b..7d8291bc26ae5 100644
--- a/src/ui/public/chrome/directives/app_switcher/app_switcher.html
+++ b/src/ui/public/chrome/directives/app_switcher/app_switcher.html
@@ -1,26 +1,11 @@
-
+
diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.js b/src/ui/public/chrome/directives/app_switcher/app_switcher.js
index 9119e0bf63828..3181f88d62cdc 100644
--- a/src/ui/public/chrome/directives/app_switcher/app_switcher.js
+++ b/src/ui/public/chrome/directives/app_switcher/app_switcher.js
@@ -1,7 +1,7 @@
import DomLocationProvider from 'ui/dom_location';
import { parse } from 'url';
import { bindKey } from 'lodash';
-import '../app_switcher/app_switcher.less';
+import './app_switcher.less';
import uiModules from 'ui/modules';
import appSwitcherTemplate from './app_switcher.html';
diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.less b/src/ui/public/chrome/directives/app_switcher/app_switcher.less
index e1cc894fd3a27..0d22b23c0aa0a 100644
--- a/src/ui/public/chrome/directives/app_switcher/app_switcher.less
+++ b/src/ui/public/chrome/directives/app_switcher/app_switcher.less
@@ -70,81 +70,3 @@ body { overflow-x: hidden; }
.flex-parent(@shrink: 0);
box-shadow: -4px 0px 3px rgba(0,0,0,0.2);
}
-
-.app-links {
- text-align: justify;
-
- .app-link {
- width: @as-open-width;
- height: @app-icon-height;
- line-height: @app-line-height;
-
- > a {
- display: block;
- height: 100%;
- color: #ebf7fa;
- }
-
- &.is-app-switcher-app-link-disabled {
- opacity: 0.5;
-
- .app-link__anchor {
- cursor: default;
- }
- }
-
- .app-icon {
- float: left;
- font-weight: bold;
- text-align: center;
- font-size: 1.7em;
- display: inline-block;
- height: @app-icon-height;
- width: @as-closed-width;
-
- > img {
- height: 18px;
- margin-top: 8px;
- filter: invert(100%);
- }
- > i {
- color: #fff;
- line-height: @app-icon-height
- }
- }
-
- .app-icon-missing {
- float: left;
- text-align: center;
- font-size: 1.7em;
- display: inline-block;
- height: @app-icon-height;
- line-height: @app-icon-height;
- width: @as-closed-width;
- background-position: center;
- background-size: contain;
- background-repeat: no-repeat;
- }
-
- .app-title {
- 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;
- }
-
- &:hover,
- &.active {
- background-color: @app-links-active-background;
- > a {
- color: @white;
- text-decoration: none;
- }
- }
- }
-
-}
diff --git a/src/ui/public/chrome/directives/app_switcher_link/__tests__/app-switcher-link.js b/src/ui/public/chrome/directives/app_switcher_link/__tests__/app-switcher-link.js
new file mode 100644
index 0000000000000..25768f2072893
--- /dev/null
+++ b/src/ui/public/chrome/directives/app_switcher_link/__tests__/app-switcher-link.js
@@ -0,0 +1,211 @@
+import sinon from 'auto-release-sinon';
+import ngMock from 'ng_mock';
+import expect from 'expect.js';
+
+import '../app_switcher_link';
+
+describe('appSwitcherLink directive', () => {
+ let scope;
+ let $compile;
+
+ beforeEach(ngMock.module('kibana'));
+
+ beforeEach(() => {
+ ngMock.inject(($rootScope, _$compile_) => {
+ scope = $rootScope.$new();
+ $compile = _$compile_;
+ });
+ });
+
+ function create(attrs) {
+ const template = `
+
+ `;
+
+ const element = $compile(template)(scope);
+
+ scope.$apply(() => {
+ Object.assign(scope, attrs);
+ });
+
+ return element;
+ }
+
+ describe('interface', () => {
+
+ describe('appSwitcherLinkIsActive attribute', () => {
+ it(`doesn't apply the active class when false`, () => {
+ const element = create({
+ appSwitcherLinkIsActive: false,
+ });
+ expect(element.hasClass('active')).to.be(false);
+ });
+
+ it('applies the active class when true', () => {
+ const element = create({
+ appSwitcherLinkIsActive: true,
+ });
+ expect(element.hasClass('active')).to.be(true);
+ });
+ });
+
+ describe('appSwitcherLinkIsDisabled attribute', () => {
+ it(`doesn't apply the is-app-switcher-link-disabled class when false`, () => {
+ const element = create({
+ appSwitcherLinkIsDisabled: false,
+ });
+ expect(element.hasClass('is-app-switcher-link-disabled')).to.be(false);
+ });
+
+ it('applies the is-app-switcher-link-disabled class when true', () => {
+ const element = create({
+ appSwitcherLinkIsDisabled: true,
+ });
+ expect(element.hasClass('is-app-switcher-link-disabled')).to.be(true);
+ });
+ });
+
+ describe('appSwitcherLinkTooltip attribute', () => {
+ it('is applied to the tooltip directive', () => {
+ const attrs = {
+ appSwitcherLinkTooltip: 'hello i am a tooltip',
+ };
+ const element = create(attrs);
+ expect(element.attr('tooltip')).to.be(attrs.appSwitcherLinkTooltip);
+ });
+ });
+
+ describe('appSwitcherLinkOnClick attribute', () => {
+ it('is called when the link is clicked', () => {
+ const attrs = {
+ appSwitcherLinkOnClick: sinon.spy(),
+ };
+ const element = create(attrs);
+ element.find('[data-test-subj=appLink]').click();
+ sinon.assert.called(attrs.appSwitcherLinkOnClick);
+ });
+ });
+
+ describe('appSwitcherLinkHref attribute', () => {
+ it('is applied to the link', () => {
+ const attrs = {
+ appSwitcherLinkHref: 'link to a website',
+ };
+ const element = create(attrs);
+ const link = element.find('[data-test-subj=appLink]');
+ expect(link.attr('href')).to.be(attrs.appSwitcherLinkHref);
+ });
+ });
+
+ describe('appSwitcherLinkKbnRoute attribute', () => {
+ it(`is applied to the link when href isn't defined`, () => {
+ const attrs = {
+ appSwitcherLinkKbnRoute: '#test',
+ };
+ const element = create(attrs);
+ const link = element.find('[data-test-subj=appLink]');
+ expect(link.attr('href')).to.be(attrs.appSwitcherLinkKbnRoute);
+ });
+
+ it(`isn't applied to the link when href is defined`, () => {
+ const attrs = {
+ appSwitcherLinkHref: 'link to a website',
+ appSwitcherLinkKbnRoute: '#test',
+ };
+ const element = create(attrs);
+ const link = element.find('[data-test-subj=appLink]');
+ expect(link.attr('href')).not.to.be(attrs.appSwitcherLinkKbnRoute);
+ });
+ });
+
+ describe('appSwitcherLinkIcon attribute', () => {
+ describe('when present', () => {
+ it('displays the img element', () => {
+ const attrs = {
+ appSwitcherLinkIcon: 'icon url',
+ };
+ const element = create(attrs);
+ const img = element.find('img');
+ expect(img.length).to.be(1);
+ });
+
+ it('hides the placeholder', () => {
+ const attrs = {
+ appSwitcherLinkIcon: 'icon url',
+ };
+ const element = create(attrs);
+ const placeholder = element.find('[data-test-subj=appLinkIconPlaceholder]');
+ expect(placeholder.length).to.be(0);
+ });
+
+ it(`is set as the img src`, () => {
+ const attrs = {
+ appSwitcherLinkIcon: 'icon url',
+ };
+ const element = create(attrs);
+ const img = element.find('img');
+ expect(img.attr('src')).to.contain(encodeURI(attrs.appSwitcherLinkIcon));
+ });
+ });
+
+ describe('when not present', () => {
+ it('hides the img element', () => {
+ const attrs = {
+ appSwitcherLinkIcon: undefined,
+ };
+ const element = create(attrs);
+ const img = element.find('img');
+ expect(img.length).to.be(0);
+ });
+
+ it('displays the placeholder', () => {
+ const attrs = {
+ appSwitcherLinkIcon: undefined,
+ };
+ const element = create(attrs);
+ const placeholder = element.find('[data-test-subj=appLinkIconPlaceholder]');
+ expect(placeholder.length).to.be(1);
+ });
+
+ it(`uses the title's first letter as the placeholder`, () => {
+ const attrs = {
+ appSwitcherLinkIcon: undefined,
+ appSwitcherLinkTitle: 'Xyz',
+ };
+ const element = create(attrs);
+ const placeholder = element.find('[data-test-subj=appLinkIconPlaceholder]');
+ expect(placeholder.text()).to.contain(attrs.appSwitcherLinkTitle[0]);
+ });
+ });
+ });
+
+ describe('appSwitcherLinkTitle attribute', () => {
+ it('is displayed', () => {
+ const attrs = {
+ appSwitcherLinkTitle: 'demo title',
+ };
+ const element = create(attrs);
+ const title = element.find('.app-switcher-link__title');
+ expect(title.text().trim()).to.be(attrs.appSwitcherLinkTitle);
+ });
+
+ it('is set as a title attribute on the anchor tag', () => {
+ const attrs = {
+ appSwitcherLinkTitle: 'demo title',
+ };
+ const element = create(attrs);
+ const link = element.find('[data-test-subj=appLink]');
+ expect(link.attr('title')).to.be(attrs.appSwitcherLinkTitle);
+ });
+ });
+ });
+});
diff --git a/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.html b/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.html
new file mode 100644
index 0000000000000..6a7843d0430cf
--- /dev/null
+++ b/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.html
@@ -0,0 +1,36 @@
+
diff --git a/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.js b/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.js
new file mode 100644
index 0000000000000..11adda2828a78
--- /dev/null
+++ b/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.js
@@ -0,0 +1,35 @@
+
+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);
+ }
+ };
+ }
+ };
+});
diff --git a/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.less b/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.less
new file mode 100644
index 0000000000000..c2b84d8376781
--- /dev/null
+++ b/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.less
@@ -0,0 +1,77 @@
+@import (reference) "~ui/styles/variables";
+
+.app-switcher-link {
+ width: @as-open-width;
+ height: @app-icon-height;
+ line-height: @app-line-height;
+
+ &.is-app-switcher-link-disabled {
+ opacity: 0.5;
+
+ .app-switcher-link__anchor {
+ cursor: default;
+ }
+ }
+
+ &:hover,
+ &.active {
+ background-color: @app-links-active-background;
+
+ .app-switcher-link__anchor {
+ color: @white;
+ text-decoration: none;
+ }
+ }
+}
+
+ .app-switcher-link__anchor {
+ display: block;
+ height: 100%;
+ color: #ebf7fa;
+
+ /**
+ * 1. TODO: Override anchor styles. Fix this by removing a tag styles.
+ */
+ &:focus {
+ color: #ebf7fa; /* 1 */
+ }
+ }
+
+ .app-switcher-link__icon {
+ display: inline-block;
+ width: @as-closed-width;
+ height: @app-icon-height;
+ float: left;
+ text-align: center;
+ font-size: 1.7em;
+ }
+
+ /**
+ * This imgae is used to display the icon.
+ */
+ .app-switcher-link__icon-image {
+ height: 18px;
+ margin-top: 8px;
+ filter: invert(100%);
+ }
+
+ /**
+ * This placeholder text gets shown if there is no specified icon.
+ */
+ .app-switcher-link__icon-placeholder {
+ line-height: @app-icon-height;
+ background-position: center;
+ background-size: contain;
+ background-repeat: no-repeat;
+ }
+
+ .app-switcher-link__title {
+ 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;
+ }
diff --git a/src/ui/public/chrome/directives/index.js b/src/ui/public/chrome/directives/index.js
index c39bb921d3a5b..7be137eb52539 100644
--- a/src/ui/public/chrome/directives/index.js
+++ b/src/ui/public/chrome/directives/index.js
@@ -1,4 +1,5 @@
import './app_switcher';
+import './app_switcher_link';
import kbnChromeProv from './kbn_chrome';
import kbnChromeNavControlsProv from './append_nav_controls';
import './kbn_loading_indicator';
diff --git a/src/ui/public/styles/variables/for-theme.less b/src/ui/public/styles/variables/for-theme.less
index 21511cc7dccc5..8f49e090bf361 100644
--- a/src/ui/public/styles/variables/for-theme.less
+++ b/src/ui/public/styles/variables/for-theme.less
@@ -106,7 +106,6 @@
// AppSwitcher =================================================================
@app-switcher-application-bg: @gray-lighter;
-@app-switcher-app-link-color: @white;
@app-switcher-app-title-color: @text-color;
@app-switcher-app-description-color: @gray;