Bug 1203281 - Fix crop on popup menu in contact list for Loop, r=dmose
authorDavid Critchley <david@priologic.com>
Thu, 17 Sep 2015 16:13:47 -0700
changeset 295748 a0bc22813c0ea3107286173d75d9ca950aaaaaaa
parent 295747 a0d61f4df22cc9ec3dc45efcfd092f6cf89d2b30
child 295749 6b14b48d18834069e70605623c1e806ac6cabf18
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdmose
bugs1203281
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1203281 - Fix crop on popup menu in contact list for Loop, r=dmose
browser/components/loop/content/css/contacts.css
browser/components/loop/content/js/contacts.js
browser/components/loop/content/js/contacts.jsx
browser/components/loop/test/desktop-local/contacts_test.js
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
--- a/browser/components/loop/content/css/contacts.css
+++ b/browser/components/loop/content/css/contacts.css
@@ -131,17 +131,16 @@ html[dir="rtl"] .contact-filter {
 
 .contact,
 .contact-separator {
   padding: .5rem 15px;
   font-size: 13px;
 }
 
 .contact {
-  position: relative;
   display: flex;
   flex-direction: row;
   align-items: center;
   color: #666;
 }
 
 .contact-separator {
   background-color: #eee;
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -152,64 +152,65 @@ loop.contacts = (function(_, mozL10n) {
     }
   });
 
   const ContactDropdown = React.createClass({displayName: "ContactDropdown",
     propTypes: {
       // If the contact is blocked or not.
       blocked: React.PropTypes.bool,
       canEdit: React.PropTypes.bool,
+      // Position of mouse when opening menu
+      eventPosY: React.PropTypes.number.isRequired,
+      // callback function that provides height and top coordinate for contacts container
+      getContainerCoordinates: React.PropTypes.func.isRequired,
       handleAction: React.PropTypes.func.isRequired
     },
 
-    getInitialState: function () {
+    getInitialState: function() {
       return {
         openDirUp: false
       };
     },
 
-    componentDidMount: function () {
-      // This method is called once when the dropdown menu is added to the DOM
-      // inside the contact item.  If the menu extends outside of the visible
-      // area of the scrollable list, it is re-rendered in different direction.
-
-      let menuNode = this.getDOMNode();
-      let menuNodeRect = menuNode.getBoundingClientRect();
-
-      let listNode = document.getElementsByClassName("contact-list")[0];
-      // XXX Workaround the contact-list element not being available in tests.
-      // Assumptions about the embedded DOM are a bad idea, and this needs
-      // reworking. For example, tests use a virtual DOM. Really we should
-      // rework this view with the DropdownMenuMixin, which would save some of this pain.
-      if (!listNode) {
-        return;
-      }
-      let listNodeRect = listNode.getBoundingClientRect();
-
-      if (menuNodeRect.top + menuNodeRect.height >=
-          listNodeRect.top + listNodeRect.height) {
-        this.setState({
-          openDirUp: true
-        });
-      }
-    },
-
     onItemClick: function(event) {
       this.props.handleAction(event.currentTarget.dataset.action);
     },
 
+    componentDidMount: function() {
+      var menuNode = this.getDOMNode();
+      var menuNodeRect = menuNode.getBoundingClientRect();
+      var listNodeCoords = this.props.getContainerCoordinates();
+
+      // Click offset to not display the menu right next to the area clicked.
+      var offset = 10;
+
+      if (this.props.eventPosY + menuNodeRect.height >=
+        listNodeCoords.top + listNodeCoords.height) {
+
+        // Position above click area.
+        menuNode.style.top = this.props.eventPosY - menuNodeRect.height
+          - offset + "px";
+      } else {
+        // Position below click area.
+        menuNode.style.top = this.props.eventPosY + offset + "px";
+      }
+    },
+
     render: function() {
       var cx = React.addons.classSet;
+      var dropdownClasses = cx({
+        "dropdown-menu": true,
+        "dropdown-menu-up": this.state.openDirUp
+      });
       let blockAction = this.props.blocked ? "unblock" : "block";
       let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
                                           : "block_contact_menu_button";
 
       return (
-        React.createElement("ul", {className: cx({ "dropdown-menu": true,
-                            "dropdown-menu-up": this.state.openDirUp })}, 
+        React.createElement("ul", {className: dropdownClasses}, 
           React.createElement("li", {className: cx({ "dropdown-menu-item": true,
                               "disabled": this.props.blocked,
                               "video-call-item": true }), 
               "data-action": "video-call", 
               onClick: this.onItemClick}, 
             React.createElement("i", {className: "icon icon-video-call"}), 
             mozL10n.get("video_call_menu_button")
           ), 
@@ -239,109 +240,123 @@ loop.contacts = (function(_, mozL10n) {
             mozL10n.get("confirm_delete_contact_remove_button")
           )
         )
       );
     }
   });
 
   const ContactDetail = React.createClass({displayName: "ContactDetail",
-    getInitialState: function() {
-      return {
-        showMenu: false
-      };
-    },
-
     propTypes: {
       contact: React.PropTypes.object.isRequired,
+      getContainerCoordinates: React.PropTypes.func.isRequired,
       handleContactAction: React.PropTypes.func
     },
 
-    _onBodyClick: function() {
-      // Hide the menu after other click handlers have been invoked.
-      setTimeout(this.hideDropdownMenu, 10);
+    mixins: [
+      sharedMixins.DropdownMenuMixin()
+    ],
+
+    getInitialState: function() {
+      return {
+        eventPosY: 0
+      };
     },
 
-    showDropdownMenu: function() {
-      document.body.addEventListener("click", this._onBodyClick);
-      this.setState({showMenu: true});
+    handleShowDropdownClick: function(e) {
+      e.preventDefault();
+      e.stopPropagation();
+
+      this.setState({
+        eventPosY: e.pageY
+      });
+
+      this.toggleDropdownMenu();
     },
 
-    hideDropdownMenu: function() {
-      document.body.removeEventListener("click", this._onBodyClick);
+    hideDropdownMenuHandler: function() {
       // Since this call may be deferred, we need to guard it, for example in
       // case the contact was removed in the meantime.
       if (this.isMounted()) {
-        this.setState({showMenu: false});
+        this.hideDropdownMenu();
       }
     },
 
-    componentWillUnmount: function() {
-      document.body.removeEventListener("click", this._onBodyClick);
-    },
-
     shouldComponentUpdate: function(nextProps, nextState) {
       let currContact = this.props.contact;
       let nextContact = nextProps.contact;
       let currContactEmail = getPreferred(currContact, "email").value;
       let nextContactEmail = getPreferred(nextContact, "email").value;
       return (
         currContact.name[0] !== nextContact.name[0] ||
         currContact.blocked !== nextContact.blocked ||
         currContactEmail !== nextContactEmail ||
         nextState.showMenu !== this.state.showMenu
       );
     },
 
     handleAction: function(actionName) {
       if (this.props.handleContactAction) {
         this.props.handleContactAction(this.props.contact, actionName);
+        this.hideDropdownMenuHandler();
       }
     },
 
     canEdit: function() {
       // We cannot modify imported contacts.  For the moment, the check for
       // determining whether the contact is imported is based on its category.
       return this.props.contact.category[0] !== "google";
     },
 
+    /**
+     * Callback called when moving cursor away from the conversation entry.
+     * Will close the dropdown menu.
+     */
+    _handleMouseOut: function() {
+      if (this.state.showMenu) {
+        this.toggleDropdownMenu();
+      }
+    },
+
     render: function() {
       let names = getContactNames(this.props.contact);
       let email = getPreferred(this.props.contact, "email");
       let avatarSrc = navigator.mozLoop.getUserAvatar(email.value);
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
       let avatarCSSClass = cx({
         avatar: true,
         defaultAvatar: !avatarSrc
       });
-
       return (
-        React.createElement("li", {className: contactCSSClass, onMouseLeave: this.hideDropdownMenu}, 
+        React.createElement("li", {className: contactCSSClass, 
+            onMouseLeave: this._handleMouseOut}, 
           React.createElement("div", {className: avatarCSSClass}, 
             avatarSrc ? React.createElement("img", {src: avatarSrc}) : null
           ), 
           React.createElement("div", {className: "details"}, 
             React.createElement("div", {className: "username"}, React.createElement("strong", null, names.firstName), " ", names.lastName, 
               React.createElement("i", {className: cx({"icon icon-blocked": this.props.contact.blocked})})
             ), 
             React.createElement("div", {className: "email"}, email.value)
           ), 
           React.createElement("div", {className: "icons"}, 
             React.createElement("i", {className: "icon icon-contact-video-call", 
                onClick: this.handleAction.bind(null, "video-call")}), 
             React.createElement("i", {className: "icon icon-vertical-ellipsis icon-contact-menu-button", 
-               onClick: this.showDropdownMenu})
+               onClick: this.handleShowDropdownClick})
           ), 
           this.state.showMenu
             ? React.createElement(ContactDropdown, {blocked: this.props.contact.blocked, 
                                canEdit: this.canEdit(), 
+                               eventPosY: this.state.eventPosY, 
+                               getContainerCoordinates: this.props.getContainerCoordinates, 
                                handleAction: this.handleAction})
             : null
           
         )
       );
     }
   });
 
@@ -631,16 +646,27 @@ loop.contacts = (function(_, mozL10n) {
       if (comp !== 0) {
         return comp;
       }
       // If names are equal, compare against unique ids to make sure we have
       // consistent ordering.
       return contact1._guid - contact2._guid;
     },
 
+    getCoordinates: function() {
+      // Returns coordinates for use by child elements to place menus etc that are absolutely positioned
+      var domNode = this.getDOMNode();
+      var domNodeRect = domNode.getBoundingClientRect();
+
+      return {
+        "top": domNodeRect.top,
+        "height": domNodeRect.height
+      };
+    },
+
     _renderFilterClearButton: function() {
       if (this.state.filter) {
         return (
           React.createElement("button", {className: "clear-search", 
                   onClick: this._handleFilterClear})
         );
       }
 
@@ -663,16 +689,17 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     _renderContactsList: function() {
       let cx = React.addons.classSet;
       let shownContacts = this._filterContactsList();
       let viewForItem = item => {
         return (
           React.createElement(ContactDetail, {contact: item, 
+                         getContainerCoordinates: this.getCoordinates, 
                          handleContactAction: this.handleContactAction, 
                          key: item._guid})
         );
       };
 
       // If no contacts to show and filter is set, then none match the search.
       if (!shownContacts.available && !shownContacts.blocked &&
           this.state.filter) {
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -152,64 +152,65 @@ loop.contacts = (function(_, mozL10n) {
     }
   });
 
   const ContactDropdown = React.createClass({
     propTypes: {
       // If the contact is blocked or not.
       blocked: React.PropTypes.bool,
       canEdit: React.PropTypes.bool,
+      // Position of mouse when opening menu
+      eventPosY: React.PropTypes.number.isRequired,
+      // callback function that provides height and top coordinate for contacts container
+      getContainerCoordinates: React.PropTypes.func.isRequired,
       handleAction: React.PropTypes.func.isRequired
     },
 
-    getInitialState: function () {
+    getInitialState: function() {
       return {
         openDirUp: false
       };
     },
 
-    componentDidMount: function () {
-      // This method is called once when the dropdown menu is added to the DOM
-      // inside the contact item.  If the menu extends outside of the visible
-      // area of the scrollable list, it is re-rendered in different direction.
-
-      let menuNode = this.getDOMNode();
-      let menuNodeRect = menuNode.getBoundingClientRect();
-
-      let listNode = document.getElementsByClassName("contact-list")[0];
-      // XXX Workaround the contact-list element not being available in tests.
-      // Assumptions about the embedded DOM are a bad idea, and this needs
-      // reworking. For example, tests use a virtual DOM. Really we should
-      // rework this view with the DropdownMenuMixin, which would save some of this pain.
-      if (!listNode) {
-        return;
-      }
-      let listNodeRect = listNode.getBoundingClientRect();
-
-      if (menuNodeRect.top + menuNodeRect.height >=
-          listNodeRect.top + listNodeRect.height) {
-        this.setState({
-          openDirUp: true
-        });
-      }
-    },
-
     onItemClick: function(event) {
       this.props.handleAction(event.currentTarget.dataset.action);
     },
 
+    componentDidMount: function() {
+      var menuNode = this.getDOMNode();
+      var menuNodeRect = menuNode.getBoundingClientRect();
+      var listNodeCoords = this.props.getContainerCoordinates();
+
+      // Click offset to not display the menu right next to the area clicked.
+      var offset = 10;
+
+      if (this.props.eventPosY + menuNodeRect.height >=
+        listNodeCoords.top + listNodeCoords.height) {
+
+        // Position above click area.
+        menuNode.style.top = this.props.eventPosY - menuNodeRect.height
+          - offset + "px";
+      } else {
+        // Position below click area.
+        menuNode.style.top = this.props.eventPosY + offset + "px";
+      }
+    },
+
     render: function() {
       var cx = React.addons.classSet;
+      var dropdownClasses = cx({
+        "dropdown-menu": true,
+        "dropdown-menu-up": this.state.openDirUp
+      });
       let blockAction = this.props.blocked ? "unblock" : "block";
       let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
                                           : "block_contact_menu_button";
 
       return (
-        <ul className={cx({ "dropdown-menu": true,
-                            "dropdown-menu-up": this.state.openDirUp })}>
+        <ul className={dropdownClasses}>
           <li className={cx({ "dropdown-menu-item": true,
                               "disabled": this.props.blocked,
                               "video-call-item": true })}
               data-action="video-call"
               onClick={this.onItemClick}>
             <i className="icon icon-video-call" />
             {mozL10n.get("video_call_menu_button")}
           </li>
@@ -239,109 +240,123 @@ loop.contacts = (function(_, mozL10n) {
             {mozL10n.get("confirm_delete_contact_remove_button")}
           </li>
         </ul>
       );
     }
   });
 
   const ContactDetail = React.createClass({
-    getInitialState: function() {
-      return {
-        showMenu: false
-      };
-    },
-
     propTypes: {
       contact: React.PropTypes.object.isRequired,
+      getContainerCoordinates: React.PropTypes.func.isRequired,
       handleContactAction: React.PropTypes.func
     },
 
-    _onBodyClick: function() {
-      // Hide the menu after other click handlers have been invoked.
-      setTimeout(this.hideDropdownMenu, 10);
+    mixins: [
+      sharedMixins.DropdownMenuMixin()
+    ],
+
+    getInitialState: function() {
+      return {
+        eventPosY: 0
+      };
     },
 
-    showDropdownMenu: function() {
-      document.body.addEventListener("click", this._onBodyClick);
-      this.setState({showMenu: true});
+    handleShowDropdownClick: function(e) {
+      e.preventDefault();
+      e.stopPropagation();
+
+      this.setState({
+        eventPosY: e.pageY
+      });
+
+      this.toggleDropdownMenu();
     },
 
-    hideDropdownMenu: function() {
-      document.body.removeEventListener("click", this._onBodyClick);
+    hideDropdownMenuHandler: function() {
       // Since this call may be deferred, we need to guard it, for example in
       // case the contact was removed in the meantime.
       if (this.isMounted()) {
-        this.setState({showMenu: false});
+        this.hideDropdownMenu();
       }
     },
 
-    componentWillUnmount: function() {
-      document.body.removeEventListener("click", this._onBodyClick);
-    },
-
     shouldComponentUpdate: function(nextProps, nextState) {
       let currContact = this.props.contact;
       let nextContact = nextProps.contact;
       let currContactEmail = getPreferred(currContact, "email").value;
       let nextContactEmail = getPreferred(nextContact, "email").value;
       return (
         currContact.name[0] !== nextContact.name[0] ||
         currContact.blocked !== nextContact.blocked ||
         currContactEmail !== nextContactEmail ||
         nextState.showMenu !== this.state.showMenu
       );
     },
 
     handleAction: function(actionName) {
       if (this.props.handleContactAction) {
         this.props.handleContactAction(this.props.contact, actionName);
+        this.hideDropdownMenuHandler();
       }
     },
 
     canEdit: function() {
       // We cannot modify imported contacts.  For the moment, the check for
       // determining whether the contact is imported is based on its category.
       return this.props.contact.category[0] !== "google";
     },
 
+    /**
+     * Callback called when moving cursor away from the conversation entry.
+     * Will close the dropdown menu.
+     */
+    _handleMouseOut: function() {
+      if (this.state.showMenu) {
+        this.toggleDropdownMenu();
+      }
+    },
+
     render: function() {
       let names = getContactNames(this.props.contact);
       let email = getPreferred(this.props.contact, "email");
       let avatarSrc = navigator.mozLoop.getUserAvatar(email.value);
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
       let avatarCSSClass = cx({
         avatar: true,
         defaultAvatar: !avatarSrc
       });
-
       return (
-        <li className={contactCSSClass} onMouseLeave={this.hideDropdownMenu}>
+        <li className={contactCSSClass}
+            onMouseLeave={this._handleMouseOut}>
           <div className={avatarCSSClass}>
             {avatarSrc ? <img src={avatarSrc} /> : null}
           </div>
           <div className="details">
             <div className="username"><strong>{names.firstName}</strong> {names.lastName}
               <i className={cx({"icon icon-blocked": this.props.contact.blocked})} />
             </div>
             <div className="email">{email.value}</div>
           </div>
           <div className="icons">
             <i className="icon icon-contact-video-call"
                onClick={this.handleAction.bind(null, "video-call")} />
             <i className="icon icon-vertical-ellipsis icon-contact-menu-button"
-               onClick={this.showDropdownMenu} />
+               onClick={this.handleShowDropdownClick} />
           </div>
           {this.state.showMenu
             ? <ContactDropdown blocked={this.props.contact.blocked}
                                canEdit={this.canEdit()}
+                               eventPosY={this.state.eventPosY}
+                               getContainerCoordinates={this.props.getContainerCoordinates}
                                handleAction={this.handleAction} />
             : null
           }
         </li>
       );
     }
   });
 
@@ -631,16 +646,27 @@ loop.contacts = (function(_, mozL10n) {
       if (comp !== 0) {
         return comp;
       }
       // If names are equal, compare against unique ids to make sure we have
       // consistent ordering.
       return contact1._guid - contact2._guid;
     },
 
+    getCoordinates: function() {
+      // Returns coordinates for use by child elements to place menus etc that are absolutely positioned
+      var domNode = this.getDOMNode();
+      var domNodeRect = domNode.getBoundingClientRect();
+
+      return {
+        "top": domNodeRect.top,
+        "height": domNodeRect.height
+      };
+    },
+
     _renderFilterClearButton: function() {
       if (this.state.filter) {
         return (
           <button className="clear-search"
                   onClick={this._handleFilterClear} />
         );
       }
 
@@ -663,16 +689,17 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     _renderContactsList: function() {
       let cx = React.addons.classSet;
       let shownContacts = this._filterContactsList();
       let viewForItem = item => {
         return (
           <ContactDetail contact={item}
+                         getContainerCoordinates={this.getCoordinates}
                          handleContactAction={this.handleContactAction}
                          key={item._guid} />
         );
       };
 
       // If no contacts to show and filter is set, then none match the search.
       if (!shownContacts.available && !shownContacts.blocked &&
           this.state.filter) {
--- a/browser/components/loop/test/desktop-local/contacts_test.js
+++ b/browser/components/loop/test/desktop-local/contacts_test.js
@@ -169,17 +169,19 @@ describe("loop.contacts", function() {
       calls: {
         startDirectCall: sinon.stub(),
         clearCallInProgress: sinon.stub()
       },
       generateUUID: sandbox.stub()
     };
 
     fakeWindow = {
-      close: sandbox.stub()
+      close: sandbox.stub(),
+      addEventListener: function() {},
+      removeEventListener: function() {}
     };
     loop.shared.mixins.setRootObject(fakeWindow);
 
     notifications = new loop.shared.models.NotificationCollection();
 
     document.mozL10n.initialize(fakeMozLoop);
   });
 
@@ -671,17 +673,18 @@ describe("loop.contacts", function() {
             callback(null, [contact]);
           };
 
           view = mountTestComponent();
           node = view.getDOMNode();
 
           // Open the menu
           var menuButton = node.querySelector(".icon-contact-menu-button");
-          TestUtils.Simulate.click(menuButton);
+          var eventStub = {"pageY": 20};
+          TestUtils.Simulate.click(menuButton, eventStub);
 
           // Get the menu for use in the tests.
           contactMenu = node.querySelector(".contact > .dropdown-menu");
         });
 
         describe("Video Conversation button", function() {
           it("should call startDirectCall when the button is clicked", function() {
             TestUtils.Simulate.click(contactMenu.querySelector(".video-call-item"));
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -1024,42 +1024,47 @@
           React.createElement(Section, {name: "ContactDetail"}, 
             React.createElement(FramedExample, {cssClass: "fx-embedded-panel", 
                            dashed: true, 
                            height: 50, 
                            summary: "ContactDetail", 
                            width: 334}, 
               React.createElement("div", {className: "panel force-menu-show"}, 
                 React.createElement(ContactDetail, {contact: fakeManyContacts[0], 
+                               getContainerCoordinates: function() { return {"top": 0, "height": 0 }; }, 
                                handleContactAction: function() {}})
               )
             )
           ), 
 
           React.createElement(Section, {name: "ContactDropdown"}, 
             React.createElement(FramedExample, {cssClass: "fx-embedded-panel", 
                            dashed: true, 
                            height: 272, 
                            summary: "ContactDropdown not blocked can edit", 
                            width: 300}, 
              React.createElement("div", {className: "panel"}, 
                React.createElement(ContactDropdown, {blocked: false, 
                                 canEdit: true, 
+                                eventPosY: 0, 
+                                getContainerCoordinates: function() { return {"top": 0, "height": 0 }; }, 
                                 handleAction: function () {}})
              )
             ), 
             React.createElement(FramedExample, {cssClass: "fx-embedded-panel", 
                            dashed: true, 
                            height: 272, 
                            summary: "ContactDropdown blocked can't edit", 
                            width: 300}, 
              React.createElement("div", {className: "panel"}, 
                React.createElement(ContactDropdown, {blocked: true, 
-                 canEdit: false, 
-                 handleAction: function () {}})
+                                canEdit: false, 
+                                eventPosY: 0, 
+                                getContainerCoordinates: function() { return {"top": 0, "height": 0 }; }, 
+                                handleAction: function () {}})
              )
             )
           ), 
 
           React.createElement(Section, {name: "AcceptCallView"}, 
             React.createElement(FramedExample, {dashed: true, 
                            height: 272, 
                            summary: "Default / incoming video call", 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -1024,42 +1024,47 @@
           <Section name="ContactDetail">
             <FramedExample cssClass="fx-embedded-panel"
                            dashed={true}
                            height={50}
                            summary="ContactDetail"
                            width={334}>
               <div className="panel force-menu-show">
                 <ContactDetail contact={fakeManyContacts[0]}
+                               getContainerCoordinates={function() { return {"top": 0, "height": 0 }; }}
                                handleContactAction={function() {}} />
               </div>
             </FramedExample>
           </Section>
 
           <Section name="ContactDropdown">
             <FramedExample cssClass="fx-embedded-panel"
                            dashed={true}
                            height={272}
                            summary="ContactDropdown not blocked can edit"
                            width={300}>
              <div className="panel">
                <ContactDropdown blocked={false}
                                 canEdit={true}
+                                eventPosY={0}
+                                getContainerCoordinates={function() { return {"top": 0, "height": 0 }; }}
                                 handleAction={function () {}} />
              </div>
             </FramedExample>
             <FramedExample cssClass="fx-embedded-panel"
                            dashed={true}
                            height={272}
                            summary="ContactDropdown blocked can't edit"
                            width={300}>
              <div className="panel">
                <ContactDropdown blocked={true}
-                 canEdit={false}
-                 handleAction={function () {}} />
+                                canEdit={false}
+                                eventPosY={0}
+                                getContainerCoordinates={function() { return {"top": 0, "height": 0 }; }}
+                                handleAction={function () {}} />
              </div>
             </FramedExample>
           </Section>
 
           <Section name="AcceptCallView">
             <FramedExample dashed={true}
                            height={272}
                            summary="Default / incoming video call"