From 72834f83068f3104a1932af0f87dfdcb8620dcf1 Mon Sep 17 00:00:00 2001
From: Jerzy Czopek
Date: Mon, 11 Jan 2016 22:34:52 +0100
Subject: [PATCH] feat(callout): add new directive
Add new directive `uif-callout`.
Closes #15. Closes #119.
---
src/components/callout/.gitignore | 1 +
src/components/callout/calloutArrowEnum.ts | 20 +
.../callout/calloutDirective.spec.ts | 499 ++++++++++++++++++
src/components/callout/calloutDirective.ts | 308 +++++++++++
src/components/callout/calloutTypeEnum.ts | 19 +
src/components/callout/demo/index.html | 237 +++++++++
src/components/callout/demo/index.js | 26 +
src/core/components.ts | 2 +
8 files changed, 1112 insertions(+)
create mode 100644 src/components/callout/.gitignore
create mode 100644 src/components/callout/calloutArrowEnum.ts
create mode 100644 src/components/callout/calloutDirective.spec.ts
create mode 100644 src/components/callout/calloutDirective.ts
create mode 100644 src/components/callout/calloutTypeEnum.ts
create mode 100644 src/components/callout/demo/index.html
create mode 100644 src/components/callout/demo/index.js
diff --git a/src/components/callout/.gitignore b/src/components/callout/.gitignore
new file mode 100644
index 0000000..efc798a
--- /dev/null
+++ b/src/components/callout/.gitignore
@@ -0,0 +1 @@
+!demo/index.js
\ No newline at end of file
diff --git a/src/components/callout/calloutArrowEnum.ts b/src/components/callout/calloutArrowEnum.ts
new file mode 100644
index 0000000..82520ce
--- /dev/null
+++ b/src/components/callout/calloutArrowEnum.ts
@@ -0,0 +1,20 @@
+'use strict';
+
+/**
+ * Enum for callout arrow direction. It is intended to use as string as `uif-arrow` attribute value on `uif-callout` directive.
+ *
+ * @readonly
+ * @enum {string}
+ * @usage
+ *
+ * This is used to generate the string that you pass into the directive. Specifically, the string is passed
+ * to the `uif-arrow` attribute. To evaluate the enum value as a string:
+ *
+ * let arrow: string = CalloutArrow[CalloutArrow.left];
+ */
+export enum CalloutArrow {
+ left,
+ right,
+ top,
+ bottom
+}
diff --git a/src/components/callout/calloutDirective.spec.ts b/src/components/callout/calloutDirective.spec.ts
new file mode 100644
index 0000000..b7ebae8
--- /dev/null
+++ b/src/components/callout/calloutDirective.spec.ts
@@ -0,0 +1,499 @@
+'use strict';
+
+import * as ng from 'angular';
+
+describe('calloutDirectives:', () => {
+ beforeEach(() => {
+ angular.mock.module('officeuifabric.core');
+ angular.mock.module('officeuifabric.components.callout');
+ });
+
+ describe('HTML rendering', () => {
+ let element: JQuery;
+ let scope: ng.IScope;
+
+ beforeEach(inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+ }));
+
+ it('main element should be DIV', () => {
+ let calloutElement: JQuery = element.children().first();
+
+ expect(calloutElement[0].tagName === 'DIV').toBeTruthy();
+ });
+
+ it('main element should have callout CSS class', () => {
+ let calloutElement: JQuery = element.find('div').first();
+
+ expect(calloutElement[0]).toHaveClass('ms-Callout');
+ });
+
+ it('should have div with proper CSS as first child', () => {
+ let mainElement: JQuery = element.find('div.ms-Callout > div').first();
+
+ expect(mainElement[0]).toHaveClass('ms-Callout-main');
+ });
+
+ it('should have inner div rendered with proper CSS', () => {
+ let innerElement: JQuery = element.find('div.ms-Callout-inner');
+
+ expect(innerElement[0]).toBeDefined();
+ expect(innerElement[0]).toHaveClass('ms-Callout-inner');
+
+ let parentElement: JQuery = innerElement.parent();
+ expect(parentElement[0]).toBeDefined();
+ expect(parentElement[0]).toHaveClass('ms-Callout-main');
+ });
+
+ it('plain callout should have left arrow', () => {
+ let calloutElement: JQuery = element.find('div').first();
+
+ expect(calloutElement[0]).toHaveClass('ms-Callout--arrowLeft');
+ });
+
+ it('main element should have proper arrow CSS class', inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('' +
+ '' +
+ '' +
+ '');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+
+ let topArrowElement: JQuery = element.find('div.ms-Callout').eq(0);
+ let bottomArrowElement: JQuery = element.find('div.ms-Callout').eq(1);
+ let leftArrowElement: JQuery = element.find('div.ms-Callout').eq(2);
+ let rightArrowElement: JQuery = element.find('div.ms-Callout').eq(3);
+
+ expect(topArrowElement[0]).toHaveClass('ms-Callout--arrowTop');
+ expect(bottomArrowElement[0]).toHaveClass('ms-Callout--arrowBottom');
+ expect(leftArrowElement[0]).toHaveClass('ms-Callout--arrowLeft');
+ expect(rightArrowElement[0]).toHaveClass('ms-Callout--arrowRight');
+ }));
+
+ it(
+ 'empty arrow attribute logs error',
+ inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService, $log: ng.ILogService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+
+ expect($log.error.logs[0]).toContain('Error [ngOfficeUiFabric] officeuifabric.components.callout - "' +
+ '" is not a valid value for uifArrow. It should be left, right, top, bottom.');
+ // expect(calloutElement[0]).toHaveClass('ms-Callout--arrowLeft');
+ }));
+
+ it(
+ 'main element has CSS class when uif-separator is present',
+ inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+
+ let calloutElement: JQuery = element.find('div.ms-Callout').eq(0);
+ expect(calloutElement[0]).toHaveClass('ms-Callout--actionText');
+ }));
+
+ it(
+ 'main element has CSS class when uif-action-text is present',
+ inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+
+ let calloutElement: JQuery = element.find('div.ms-Callout').eq(0);
+ expect(calloutElement[0]).toHaveClass('ms-Callout--actionText');
+ }));
+
+ it('main element has no separator when uif-action-text and uif-separator not present', () => {
+ let calloutElement: JQuery = element.find('div.ms-Callout').eq(0);
+ expect(calloutElement[0]).not.toHaveClass('ms-Callout--actionText');
+ });
+
+ it(
+ 'main element has proper CSS class uif-type=oobe',
+ inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+
+ let calloutElement: JQuery = element.find('div.ms-Callout').eq(0);
+ expect(calloutElement[0]).toHaveClass('ms-Callout--OOBE');
+ }));
+
+ it(
+ 'main element has proper CSS class uif-type=peek',
+ inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+
+ let calloutElement: JQuery = element.find('div.ms-Callout').eq(0);
+ expect(calloutElement[0]).toHaveClass('ms-Callout--Peek');
+ }));
+
+ it(
+ 'should log error when uif-type empty',
+ inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService, $log: ng.ILogService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+
+ expect($log.error.logs[0]).toContain('Error [ngOfficeUiFabric] officeuifabric.components.callout - ' +
+ '"" is not a valid value for uifType. It should be oobe or peek');
+ }));
+
+ it('main element has no type CSS and no error if uif-type not applied', inject(($log: ng.ILogService) => {
+ let calloutElement: JQuery = element.find('div.ms-Callout').eq(0);
+ expect(calloutElement[0]).not.toHaveClass('ms-Callout--Peek');
+ expect(calloutElement[0]).not.toHaveClass('ms-Callout--OOBE');
+ expect($log.error.length === 0).toBeTruthy();
+ }));
+
+ it(
+ 'main element has proper CSS and HTML when uif-close attribute present',
+ inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+
+ let calloutElement: JQuery = element.find('div.ms-Callout').eq(0);
+ expect(calloutElement[0]).toHaveClass('ms-Callout--close');
+
+ let closeButton: JQuery = calloutElement.find('button.ms-Callout-close').eq(0);
+ expect(closeButton).toBeDefined();
+ expect(closeButton).not.toBeNull();
+
+ let buttonIcon: JQuery = closeButton.find('i').eq(0);
+ expect(buttonIcon[0]).toHaveClass('ms-Icon');
+ expect(buttonIcon[0]).toHaveClass('ms-Icon--x');
+ }));
+
+ });
+
+ describe('callout visibility', () => {
+ let element: JQuery;
+ let scope: any;
+
+ beforeEach(inject(($rootScope: ng.IRootScopeService) => {
+ element = ng.element('');
+ scope = $rootScope.$new();
+
+ scope.vm = {
+ isOpen: false
+ };
+ }));
+
+ it('intially callout is closed', inject(($compile: ng.ICompileService) => {
+ $compile(element)(scope);
+ scope.$digest();
+
+ let calloutElement: JQuery = element.eq(0);
+ expect(calloutElement[0]).toHaveClass('ng-hide');
+ }));
+
+ it('intially callout is open', inject(($compile: ng.ICompileService) => {
+ $compile(element)(scope);
+ scope.vm.isOpen = true;
+
+ scope.$digest();
+
+ let calloutElement: JQuery = element.eq(0);
+ expect(calloutElement[0]).not.toHaveClass('ng-hide');
+ }));
+
+ it('callout opens/closes on uif-is-open change', inject(($compile: ng.ICompileService) => {
+ scope.vm.isOpen = true;
+
+ $compile(element)(scope);
+ scope.$digest();
+
+ // close it
+ scope.vm.isOpen = false;
+ scope.$digest();
+
+ let calloutElement: JQuery = element.eq(0);
+ expect(calloutElement[0]).toHaveClass('ng-hide');
+
+ // reopen
+ scope.vm.isOpen = true;
+ scope.$digest();
+
+ calloutElement = element.find('div.ms-Callout').eq(0);
+ expect(calloutElement[0]).not.toHaveClass('ng-hide');
+ }));
+
+ it('clicking close button closes callout', inject(($compile: ng.ICompileService) => {
+ element = ng.element('');
+
+ scope.vm.isOpen = true;
+
+ $compile(element)(scope);
+ scope.$digest();
+
+ expect(element[0]).not.toHaveClass('ng-hide');
+
+ let closeButton: JQuery = element.find('button.ms-Callout-close').eq(0);
+
+ closeButton.click();
+ scope.$digest();
+
+ expect(element[0]).toHaveClass('ng-hide');
+ expect(scope.vm.isOpen).toBeFalsy();
+ }));
+
+ it('should not close by itself when mouse is over callout', inject(($compile: ng.ICompileService) => {
+ element = ng.element('');
+
+ scope.vm.isOpen = true;
+
+ $compile(element)(scope);
+ scope.$digest();
+
+ let callout: JQuery = element.eq(0);
+ callout.mouseenter();
+ scope.$digest();
+
+ scope.vm.isOpen = false;
+ scope.$digest();
+
+ expect(element[0]).not.toHaveClass('ng-hide');
+
+ }));
+
+ it('should close by itself when mouse outside callout', inject(($compile: ng.ICompileService) => {
+ element = ng.element('');
+
+ scope.vm.isOpen = true;
+
+ $compile(element)(scope);
+ scope.$digest();
+
+ let callout: JQuery = element.eq(0);
+ callout.mouseenter();
+ scope.$digest();
+
+ expect(element[0]).not.toHaveClass('ng-hide');
+
+ scope.vm.isOpen = false;
+ callout.mouseleave();
+ scope.$digest();
+ expect(element[0]).toHaveClass('ng-hide');
+ }));
+
+ it('should close itself when mouse over callout and close button clicked', inject(($compile: ng.ICompileService) => {
+ element = ng.element('');
+
+ scope.vm.isOpen = true;
+
+ $compile(element)(scope);
+ scope.$digest();
+
+ let callout: JQuery = element.eq(0);
+ callout.mouseenter();
+ scope.$digest();
+
+ expect(element[0]).not.toHaveClass('ng-hide');
+
+ let closeButton: JQuery = element.find('button.ms-Callout-close').eq(0);
+ closeButton.click();
+ scope.$digest();
+
+ expect(element[0]).toHaveClass('ng-hide');
+ }));
+
+ it('should not close when mouse moves outside callout but there is close button', inject(($compile: ng.ICompileService) => {
+ element = ng.element('');
+
+ scope.vm.isOpen = true;
+
+ $compile(element)(scope);
+ scope.$digest();
+
+ let callout: JQuery = element.eq(0);
+ callout.mouseenter();
+ scope.$digest();
+
+ expect(element[0]).not.toHaveClass('ng-hide');
+
+ callout.mouseleave();
+ scope.$digest();
+
+ expect(element[0]).not.toHaveClass('ng-hide');
+ }));
+ });
+
+ describe('callout header directive tests', () => {
+ let element: JQuery;
+ let scope: ng.IScope;
+
+ beforeEach(inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+ }));
+
+ it('should be rendered as DIV', () => {
+ let headerDiv: JQuery = element.children('div').first();
+
+ expect(headerDiv.length === 1).toBeTruthy();
+ expect(headerDiv[0].tagName === 'DIV').toBeTruthy();
+ });
+
+ it('should have proper CSS', () => {
+ let headerDiv: JQuery = element.find('div').first();
+
+ expect(headerDiv[0]).toHaveClass('ms-Callout-header');
+ });
+
+ it('main DIV should have child P', () => {
+ let headerDiv: JQuery = element.find('div').first();
+ let headerParagrah: JQuery = headerDiv.children().first();
+
+ expect(headerParagrah[0].tagName === 'P').toBeTruthy();
+
+ });
+
+ it('should have proper CSS on title P', () => {
+ let headerParagrah: JQuery = element.find('p').first();
+
+ expect(headerParagrah[0]).toHaveClass('ms-Callout-title');
+ });
+
+ it('should trasnsclude inside P', inject(($compile: ng.ICompileService) => {
+ element = ng.element('Callout header test!');
+ $compile(element)(scope);
+
+ let headerParagrah: JQuery = element.find('p.ms-Callout-title').eq(0);
+
+ expect(headerParagrah[0].innerHTML.indexOf('Callout header test!')).toBeGreaterThan(-1);
+ }));
+
+ });
+
+ describe('callout content directive tests', () => {
+ let element: JQuery;
+ let scope: ng.IScope;
+
+ beforeEach(inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+ }));
+
+ it('should be rendered as DIV', () => {
+ let headerDiv: JQuery = element.children('div').first();
+
+ expect(headerDiv.length === 1).toBeTruthy();
+ expect(headerDiv[0].tagName === 'DIV').toBeTruthy();
+ });
+
+ it('should have proper CSS', () => {
+ let headerDiv: JQuery = element.find('div').first();
+
+ expect(headerDiv[0]).toHaveClass('ms-Callout-content');
+ });
+
+ it('main DIV should have child P', () => {
+ let headerDiv: JQuery = element.find('div').first();
+ let headerParagrah: JQuery = headerDiv.children().first();
+
+ expect(headerParagrah[0].tagName === 'P').toBeTruthy();
+
+ });
+
+ it('should have proper CSS on content P', () => {
+ let headerParagrah: JQuery = element.find('p').first();
+
+ expect(headerParagrah[0]).toHaveClass('ms-Callout-subText');
+ });
+
+ it('should trasnsclude inside P', inject(($compile: ng.ICompileService) => {
+ element = ng.element('Callout content test!');
+ $compile(element)(scope);
+
+ let headerParagrah: JQuery = element.find('p.ms-Callout-subText').eq(0);
+
+ expect(headerParagrah[0].innerHTML.indexOf('Callout content test!')).toBeGreaterThan(-1);
+ }));
+
+ });
+
+ describe('callout actions directive tests', () => {
+ let element: JQuery;
+ let scope: ng.IScope;
+
+ beforeEach(inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('');
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+ }));
+
+ it('should be rendered as DIV', () => {
+ let headerDiv: JQuery = element.children('div').first();
+
+ expect(headerDiv.length === 1).toBeTruthy();
+ expect(headerDiv[0].tagName === 'DIV').toBeTruthy();
+ });
+
+ it('should have proper CSS', () => {
+ let headerDiv: JQuery = element.find('div').first();
+
+ expect(headerDiv[0]).toHaveClass('ms-Callout-actions');
+ });
+
+ it('should trasnsclude inside main element', inject(($compile: ng.ICompileService) => {
+ element = ng.element('Callout actions test!');
+ $compile(element)(scope);
+
+ expect(element[0].innerHTML.indexOf('Callout actions test!')).toBeGreaterThan(-1);
+ }));
+
+ });
+
+ describe('callout directives rendering together', () => {
+ let element: JQuery;
+ let scope: ng.IScope;
+
+ beforeEach(inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ element = ng.element('' +
+ 'All of your favorite people' +
+ 'Message body is optional. If help documentation is available' +
+ 'Learn more' +
+ '');
+
+ scope = $rootScope;
+ $compile(element)(scope);
+ scope.$digest();
+ }));
+
+ it('should render ms-Callout-inner & header should be appended there', () => {
+ let mainWrapper: JQuery = element.find('div.ms-Callout-main');
+ let innerWrapper: JQuery = element.find('div.ms-Callout-inner');
+
+ expect(mainWrapper.length === 1).toBeTruthy();
+ expect(innerWrapper.length === 1).toBeTruthy();
+
+ let header: JQuery = mainWrapper.children().eq(0);
+
+ expect(header[0].tagName === 'UIF-CALLOUT-HEADER').toBeTruthy();
+
+ });
+
+ });
+
+});
+
diff --git a/src/components/callout/calloutDirective.ts b/src/components/callout/calloutDirective.ts
new file mode 100644
index 0000000..2e58f98
--- /dev/null
+++ b/src/components/callout/calloutDirective.ts
@@ -0,0 +1,308 @@
+'use strict';
+
+import * as ng from 'angular';
+import {CalloutType} from './calloutTypeEnum';
+import {CalloutArrow} from './calloutArrowEnum';
+
+
+/**
+ * @ngdoc class
+ * @name CalloutController
+ * @module officeuifabric.components.callout
+ *
+ * @restrict E
+ *
+ * @description
+ * CalloutController is the controller for the directive
+ *
+ */
+export class CalloutController {
+ public static $inject: string[] = ['$log'];
+ constructor(public $log: ng.ILogService) { }
+}
+
+/**
+ * @ngdoc directive
+ * @name uifCalloutHeader
+ * @module officeuifabric.components.callout
+ *
+ * @restrict E
+ *
+ * @description
+ * `` is an directive for rendering callout header
+ *
+ * @usage
+ *
+ *
+ * All of your favorite people
+ *
+ */
+export class CalloutHeaderDirective implements ng.IDirective {
+ public restrict: string = 'E';
+ public transclude: boolean = true;
+ public replace: boolean = false;
+ public scope: boolean = false;
+ public template: string = '
';
+
+ public static factory(): ng.IDirectiveFactory {
+ const directive: ng.IDirectiveFactory = () => new CalloutHeaderDirective();
+ return directive;
+ }
+
+ public link(scope: any, instanceElement: ng.IAugmentedJQuery, attrs: any, ctrls: any[]): void {
+
+ let mainWrapper: JQuery = instanceElement.parent().parent();
+
+ if (!ng.isUndefined(mainWrapper) && mainWrapper.hasClass('ms-Callout-main')) {
+ let detachedHeader: JQuery = instanceElement.detach();
+ mainWrapper.prepend(detachedHeader);
+ }
+ }
+}
+
+/**
+ * @ngdoc directive
+ * @name uifCalloutContent
+ * @module officeuifabric.components.callout
+ *
+ * @restrict E
+ *
+ * @description
+ * `` is an directive for rendering callout content
+ *
+ * @usage
+ *
+ *
+ * Consider adding a link to learn more at the bottom.
+ *
+ */
+export class CalloutContentDirective implements ng.IDirective {
+ public restrict: string = 'E';
+ public transclude: boolean = true;
+ public replace: boolean = false;
+ public scope: boolean = false;
+ public template: string = '
';
+
+ public static factory(): ng.IDirectiveFactory {
+ const directive: ng.IDirectiveFactory = () => new CalloutContentDirective();
+ return directive;
+ }
+
+}
+
+
+/**
+ * @ngdoc directive
+ * @name uifCalloutActions
+ * @module officeuifabric.components.callout
+ *
+ * @restrict E
+ *
+ * @description
+ * `` is an directive for rendering callout actions
+ *
+ * @usage
+ *
+ *
+ *
+ * Learn more
+ *
+ *
+ */
+export class CalloutActionsDirective implements ng.IDirective {
+ public restrict: string = 'E';
+ public transclude: boolean = true;
+ public replace: boolean = false;
+ public scope: boolean = false;
+ public template: string = '';
+
+ public static factory(): ng.IDirectiveFactory {
+ const directive: ng.IDirectiveFactory = () => new CalloutActionsDirective();
+ return directive;
+ }
+
+}
+
+/**
+ * @ngdoc interface
+ * @name ICalloutAttributes
+ * @module officeuifabric.components.callout
+ *
+ * @description
+ * Those are the attributes that can be used on callout directive.
+ *
+ *
+ * @property {string} uifSeparator - Renders a separating line between content and actions
+ * @property {string} uifActiontext - Render a separating line between content and actions. Same as `uifSeparator`
+ * @property {CalloutArrow} uifArrow - Direction of the arrow
+ * @property {boolean} uifClose - Renders close button
+ */
+interface ICalloutAttributes extends ng.IAttributes {
+ uifSeparator: string;
+ uifActionText: string;
+ uifArrow: string;
+ uifClose: boolean;
+}
+
+/**
+ * @ngdoc interface
+ * @name ICalloutScope
+ * @module officeuifabric.components.callout
+ *
+ * @description
+ * This is the scope used by the directive.
+ *
+ * @property {CalloutType} uifType - Type of callout to render
+ * @property {boolean} ngShow - Callout visible or not.
+ */
+interface ICalloutScope extends ng.IScope {
+ arrowDirection: string;
+ hasSeparator: boolean;
+ uifType: string;
+ ngShow: boolean;
+ closeButton: boolean;
+ isMouseOver: boolean;
+ closeButtonClicked: boolean;
+}
+
+/**
+ * @ngdoc directive
+ * @name uifCallout
+ * @module officeuifabric.components.callout
+ *
+ * @restrict E
+ *
+ * @description
+ * `` is an directive for rendering callout component.
+ * @see /~https://github.com/OfficeDev/Office-UI-Fabric/tree/master/src/components/Callout
+ *
+ * @usage
+ *
+ *
+ * Consider adding a link to learn more at the bottom.
+ *
+ */
+export class CalloutDirective implements ng.IDirective {
+ public restrict: string = 'E';
+ public transclude: boolean = true;
+ public replace: boolean = false;
+ public template: string =
+ '
' +
+ '
';
+
+ public require: string[] = ['uifCallout'];
+
+ public scope: any = {
+ ngShow: '=',
+ uifType: '@'
+ };
+
+ public controller: typeof CalloutController = CalloutController;
+
+ public static factory(): ng.IDirectiveFactory {
+ const directive: ng.IDirectiveFactory = () => new CalloutDirective();
+ return directive;
+ }
+
+ public link(scope: ICalloutScope, instanceElement: ng.IAugmentedJQuery, attrs: ICalloutAttributes, ctrls: any[]): void {
+
+ let calloutController: CalloutController = ctrls[0];
+
+ attrs.$observe('uifType', (calloutType: string) => {
+ if (ng.isUndefined(CalloutType[calloutType])) {
+ calloutController.$log.error('Error [ngOfficeUiFabric] officeuifabric.components.callout - "' +
+ calloutType + '" is not a valid value for uifType. It should be oobe or peek');
+ }
+ });
+
+ if (!attrs.uifArrow) {
+ scope.arrowDirection = 'Left';
+ }
+
+ attrs.$observe('uifArrow', (attrArrowDirection: string) => {
+
+ if (ng.isUndefined(CalloutArrow[attrArrowDirection])) {
+ calloutController.$log.error('Error [ngOfficeUiFabric] officeuifabric.components.callout - "' +
+ attrArrowDirection + '" is not a valid value for uifArrow. It should be left, right, top, bottom.');
+ return;
+ }
+
+ let capitalizedDirection: string = (attrArrowDirection.charAt(0)).toUpperCase();
+ capitalizedDirection += (attrArrowDirection.slice(1)).toLowerCase();
+
+ scope.arrowDirection = capitalizedDirection;
+
+ });
+
+ scope.hasSeparator = (!ng.isUndefined(attrs.uifActionText) || !ng.isUndefined(attrs.uifSeparator));
+
+ if (!ng.isUndefined(attrs.uifClose)) {
+ scope.closeButton = true;
+
+ let closeButtonElement: ng.IAugmentedJQuery = ng.element(
+ '');
+
+ let calloutDiv: JQuery = instanceElement.find('div').eq(0);
+ calloutDiv.append(closeButtonElement);
+
+ // register close button click
+ closeButtonElement.bind('click', (eventObject: JQueryEventObject) => {
+ scope.ngShow = false;
+ scope.closeButtonClicked = true;
+ scope.$apply();
+ });
+ }
+
+ instanceElement.bind('mouseenter', (eventObject: JQueryEventObject) => {
+ scope.isMouseOver = true;
+ scope.$apply();
+ });
+
+ instanceElement.bind('mouseleave', (eventObject: JQueryEventObject) => {
+ scope.isMouseOver = false;
+ scope.$apply();
+ });
+
+
+ scope.$watch('ngShow', (newValue: boolean, oldValue: boolean) => {
+ // close button closes everytime
+ let isClosingByButtonClick: boolean = !newValue && scope.closeButtonClicked;
+ if (isClosingByButtonClick) {
+ scope.ngShow = scope.closeButtonClicked = false;
+ return;
+ }
+
+ if (!newValue) {
+ scope.ngShow = scope.isMouseOver;
+ }
+ });
+
+ scope.$watch('isMouseOver', (newVal: boolean, oldVal: boolean) => {
+ // mouse was over element and now it's out
+ if (!newVal && oldVal) {
+ // when there's button, only button can close.
+ if (!scope.closeButton) {
+ scope.ngShow = false;
+ }
+ }
+ });
+ }
+}
+
+/**
+ * @ngdoc module
+ * @name officeuifabric.components.callout
+ *
+ * @description
+ * Callout
+ *
+ */
+export var module: ng.IModule = ng.module('officeuifabric.components.callout', ['officeuifabric.components'])
+ .directive('uifCallout', CalloutDirective.factory())
+ .directive('uifCalloutHeader', CalloutHeaderDirective.factory())
+ .directive('uifCalloutContent', CalloutContentDirective.factory())
+ .directive('uifCalloutActions', CalloutActionsDirective.factory());
diff --git a/src/components/callout/calloutTypeEnum.ts b/src/components/callout/calloutTypeEnum.ts
new file mode 100644
index 0000000..db981dd
--- /dev/null
+++ b/src/components/callout/calloutTypeEnum.ts
@@ -0,0 +1,19 @@
+'use strict';
+
+/**
+ *
+ * Enum for callout type. It is intended to use as string as `uif-type` attribute value on `uif-callout` directive.
+ *
+ * @readonly
+ * @enum {string}
+ * @usage
+ *
+ * This is used to generate the string that you pass into the directive. Specifically, the string is passed
+ * to the `uif-type` attribute. To evaluate the enum value as a string:
+ *
+ * let type: string = CalloutType[CalloutType.peek];
+ */
+export enum CalloutType {
+ oobe,
+ peek
+}
diff --git a/src/components/callout/demo/index.html b/src/components/callout/demo/index.html
new file mode 100644
index 0000000..82a36a4
--- /dev/null
+++ b/src/components/callout/demo/index.html
@@ -0,0 +1,237 @@
+
+
+
+
+ ngOfficeUiFabric | uif-Callout demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Callout Demo | <uif-callout>
+ In order for this demo to work you must first build the library in debug mode.
+
+
+
To render callout, use the following markup:
+
+
+ <uif-callout uif-arrow="left">
+ <uif-callout-header>All of your favorite people</uif-callout-header>
+ <uif-callout-content>Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom.</uif-callout-content>
+ <uif-callout-actions>
+ <a href="#" class="ms-Callout-link ms-Link ms-Link--hero">Learn more</a>
+ </uif-callout-actions>
+ </uif-callout>
+
+
+
+
+
+ All of your favorite people
+ Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom.
+
+ Learn more
+
+
+
+
+
+
+ This markup will render OOBE callout:
+
+
+ <uif-callout uif-arrow="top" uif-type="oobe">
+ <uif-callout-header>All of your favorite people</uif-callout-header>
+ <uif-callout-content>People automatically puts together all of the people you care most about in one place.</uif-callout-content>
+ <uif-callout-actions>
+ <button class="ms-Callout-button ms-Button ms-Button--primary">
+ <span class="ms-Button-label">More</span>
+ <span class="ms-Button-description">Description of the action this button takes</span>
+ </button>
+ <button class="ms-Callout-button ms-Button">
+ <span class="ms-Button-label">Got it</span>
+ <span class="ms-Button-description">Description of the action this button takes</span>
+ </button>
+ </uif-callout-actions>
+ </uif-callout>
+
+
+
+
+
+ All of your favorite people
+ People automatically puts together all of the people you care most about in one place.
+
+
+
+
+
+
+
+
+ This markup will render Peek callout:
+
+
+ <uif-callout uif-arrow="right" uif-type="peek">
+ <uif-callout-header>Uploaded 2 items to <span class="ms-Link">Alton's OneDrive</span></uif-callout-header>
+ <uif-callout-actions>
+ <button class="ms-Callout-button ms-Button">
+ <span class="ms-Button-label">More</span>
+ <span class="ms-Button-description">Description of the action this button takes</span>
+ </button>
+ </uif-callout-actions>
+ </uif-callout>
+
+ Separator can be rendered between content and actions containers. uif-actiontext and uif-separator server the same purpose.
+
+
+ <uif-callout uif-action-text>
+ <uif-callout-header>All of your favorite people</uif-callout-header>
+ <uif-callout-content>People automatically puts together all of the people you care most about in one place.</uif-callout-header>
+ <uif-callout-actions>
+ <button class="ms-Callout-action ms-Button ms-Button--command">
+ <span class="ms-Callout-actionText ms-Button-icon"><i class="ms-Icon ms-Icon--check"></i></span>
+ <span class="ms-Button-label">Save</span>
+ </button>
+ <button class="ms-Callout-action ms-Button ms-Button--command">
+ <span class="ms-Button-icon"><i class="ms-Icon ms-Icon--x"></i></span>
+ <span class="ms-Callout-actionText ms-Button-label">Cancel</span>
+ </button>
+ </uif-callout-actions>
+ </uif-callout>
+
+
+
+
+
+ All of your favorite people
+ People automatically puts together all of the people you care most about in one place.
+
+
+
+
+
+
+
+
+
+
+
Callout supports ngShow directive. Hover over the link to test.
+
+
+ <uif-callout ng-show="vm.firstVisible" uif-arrow="left">
+ <uif-callout-header>All of your favorite people</uif-callout-header>
+ <uif-callout-content>Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom.</uif-callout-content>
+ <uif-callout-actions>
+ <a href="#" class="ms-Callout-link ms-Link ms-Link--hero">Learn more</a>
+ </uif-callout-actions>
+ </uif-callout>
+
+
+ All of your favorite people
+ Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom.
+
+ Learn more
+
+
+
+
+
+
+
Callout can have close button, with help of uif-close attribute.
+
+
+ <uif-callout ng-show="vm.secondVisible" uif-arrow="left" uif-close>
+ <uif-callout-header>All of your favorite people</uif-callout-header>
+ <uif-callout-content>Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom.</uif-callout-content>
+ <uif-callout-actions>
+ <a href="#" class="ms-Callout-link ms-Link ms-Link--hero">Learn more</a>
+ </uif-callout-actions>
+ </uif-callout>
+
+
+
+
+
+
+
+ All of your favorite people
+ Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom.
+
+ Learn more
+
+
+
+
+
+
+
When ngShow is used without uif-close, callout is closed when mouse leaves from callout:
+
+
+ <uif-callout ng-show="vm.thirdVisible" uif-arrow="left">
+ <uif-callout-header>All of your favorite people</uif-callout-header>
+ <uif-callout-content>Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom.</uif-callout-content>
+ <uif-callout-actions>
+ <a href="#" class="ms-Callout-link ms-Link ms-Link--hero">Learn more</a>
+ </uif-callout-actions>
+ </uif-callout>
+
+
+
+
+
+
+
+ All of your favorite people
+ Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom.
+
+ Learn more
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/callout/demo/index.js b/src/components/callout/demo/index.js
new file mode 100644
index 0000000..0e9046f
--- /dev/null
+++ b/src/components/callout/demo/index.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var demoApp = angular.module('demoApp', [
+ 'officeuifabric.core',
+ 'officeuifabric.components.callout'
+]);
+
+demoApp.controller('calloutDemoController',['$scope', '$timeout', calloutDemoController]);
+
+function calloutDemoController($scope, $timeout){
+ $scope.vm = {
+ firstVisible : false,
+ secondVisible: false,
+ thirdVisible: false
+ };
+
+ $scope.toggleClick = function(){
+ $scope.vm.secondVisible = !$scope.vm.secondVisible;
+ }
+
+ $scope.thirdToggleClick = function(){
+ $timeout(function(){
+ $scope.vm.thirdVisible = !($scope.vm.thirdVisible);
+ }, 1000);
+ };
+}
\ No newline at end of file
diff --git a/src/core/components.ts b/src/core/components.ts
index d69f595..701d8ee 100644
--- a/src/core/components.ts
+++ b/src/core/components.ts
@@ -1,6 +1,7 @@
'use strict';
import * as ng from 'angular';
+import * as calloutModule from '../components/callout/calloutDirective';
import * as choicefieldModule from '../components/choicefield/choicefieldDirective';
import * as contextualMenuModule from '../components/contextualmenu/contextualMenu';
import * as dropdownModule from '../components/dropdown/dropdownDirective';
@@ -24,6 +25,7 @@ import * as toggleModule from '../components/toggle/toggleDirective';
*
*/
export var module: ng.IModule = ng.module('officeuifabric.components', [
+ calloutModule.module.name,
choicefieldModule.module.name,
contextualMenuModule.module.name,
dropdownModule.module.name,