Bug 1183638 - Loop conversation list panel refresh
authorAndrei Oprea <andrei.br92@gmail.com>
Fri, 14 Aug 2015 11:15:22 -0700
changeset 257881 5c906eb9b8d9618f57f2647c5ed17233cb98e87f
parent 257880 a303d8119dc83902d4462a3ab579ac2250f81057
child 257882 6b2f7170700f60fc65324e7dd6cc94b2ae49a19b
push id29232
push userkwierso@gmail.com
push dateFri, 14 Aug 2015 23:21:05 +0000
treeherdermozilla-central@6bb8ca8a857c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1183638
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 1183638 - Loop conversation list panel refresh
browser/components/loop/content/css/contacts.css
browser/components/loop/content/css/panel.css
browser/components/loop/content/js/panel.js
browser/components/loop/content/js/panel.jsx
browser/components/loop/content/shared/img/empty_conversations.svg
browser/components/loop/jar.mn
browser/components/loop/test/desktop-local/panel_test.js
browser/components/loop/ui/ui-showcase.css
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
browser/locales/en-US/chrome/browser/loop/loop.properties
--- a/browser/components/loop/content/css/contacts.css
+++ b/browser/components/loop/content/css/contacts.css
@@ -160,40 +160,16 @@ html {
   background-image: url("../shared/img/avatars.svg#green-avatar");
   background-color: #56B397;
 }
 
 .contact > .avatar > img {
   width: 100%;
 }
 
-.contact-list-empty {
-  background-image: url("../shared/img/empty_contacts.svg");
-  background-repeat: no-repeat;
-  background-position: top center;
-  padding-top: 28%;
-  padding-bottom: 5%;
-  text-align: center;
-  color: #4a4a4a;
-  font-weight: lighter;
-}
-
-.panel-text-medium,
-.panel-text-large {
-  margin: 3px;
-}
-
-.panel-text-medium {
-  font-size: 1.6rem;
-}
-
-.panel-text-large {
-  font-size: 2.2rem;
-}
-
 .contact > .details > .username {
   font-size: 1.3rem;
   line-height: 20px;
   color: #000;
 }
 
 .contact.blocked > .details > .username {
   color: #d74345;
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -269,40 +269,82 @@ html[dir="rtl"] .tab-view li:nth-child(2
 .content-area input:not(.pristine):invalid {
   border: 0.1rem solid #d13f1a;
 }
 
 .content-area input:focus {
   border: 0.1rem solid #5cccee;
 }
 
+/* Rooms and contacts shared CSS */
+
+.contact-list-empty,
+.room-list-empty {
+  background-image: url("../shared/img/empty_contacts.svg");
+  background-repeat: no-repeat;
+  background-position: top center;
+  padding-top: 19%;
+  padding-bottom: 3%;
+  text-align: center;
+  color: #4a4a4a;
+  font-weight: lighter;
+}
+
+.contact-list-empty {
+  padding-top: 27%;
+}
+
+.room-list-empty {
+  margin: 5% 15px;
+  background-image: url("../shared/img/empty_conversations.svg");
+}
+
+.panel-text-medium,
+.panel-text-large {
+  margin: 3px 0;
+}
+
+.panel-text-medium {
+  font-size: 1.6rem;
+}
+
+.panel-text-large {
+  font-size: 2.2rem;
+}
+
+
 /* Rooms */
 .rooms {
   min-height: 100px;
-  padding: 0 1rem;
 }
 
 .rooms > h1 {
   font-weight: bold;
-  color: #999;
+  color: #666;
   padding: .5rem 0;
   height: 3rem;
   line-height: 3rem;
+  font-size: 1.1rem;
+  margin: 0 15px;
+}
+
+.new-room-view {
+  display: flex;
+  flex-direction: column;
 }
 
 .new-room-view > .context-checkbox-checked {
   background-color: #dbf7ff;
 }
 
 .new-room-view > .context {
-  margin: .5rem 0 .5rem;
+  flex: 1;
   border-radius: 3px 3px 0 0;
-  padding: .5rem 1rem ;
-  margin-left: -1rem;
-  margin-right: -1rem;
+  margin: 1rem 0 .5rem;
+  padding: 1rem 15px;
 }
 
 .new-room-view > .context > .context-enabled {
   margin-bottom: .5rem;
   display: block;
 }
 
 .new-room-view > .context > .context-enabled > input {
@@ -325,77 +367,61 @@ html[dir="rtl"] .tab-view li:nth-child(2
 }
 
 .new-room-view > .context > .checkbox-wrapper > label {
   color: #333;
   font-size: 1.1rem;
 }
 
 .new-room-view > .btn {
+  flex: 1;
   height: 3rem;
   display: block;
   font-size: 1.2rem;
-  margin: 0 auto 1rem;
-  width: 100%;
+  margin: 0 15px 1rem 15px;
   padding: .5rem 1rem;
   border-radius: 4px;
 }
 
 /* Remove when bug 1142671 is backed out. */
 .new-room-view > .context.hide + .new-room-button {
   border-radius: 3px;
   margin-top: 0.5rem;
 }
 
 .room-list {
   max-height: 335px; /* XXX better computation needed */
   min-height: 7px;
   overflow: auto;
-  border-top: 1px solid #ccc;
-  border-bottom: 1px solid #ccc;
-  margin-left: -1rem;
-  margin-right: -1rem;
 }
 
 .room-list:empty {
   border-bottom-width: 0;
 }
 
 .room-list > .room-entry {
-  padding: .2rem 1rem;
+  padding: .2rem 15px;
   /* Always show the default pointer, even over the text part of the entry. */
   cursor: default;
 }
 
 .room-list > .room-entry > h2 {
   display: inline-block;
-  font-size: 1rem;
-  color: #777;
+  font-size: 1.3rem;
+  line-height: 2.4rem;
+  color: #000;
   /* See .room-entry-context-item for the margin/size reductions. */
   width: calc(100% - 1rem - 16px);
 }
 
 .room-list > .room-entry.room-active > h2 {
   font-weight: bold;
   color: #000;
 }
 
-.room-list > .room-entry > h2 > .room-notification {
-  display: none;
-  background: #00a0ec;
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
-  -moz-margin-end: .3rem;
-}
-
-.room-list > .room-entry.room-active > h2 > .room-notification {
-  display: inline-block;
-}
-
 .room-list > .room-entry:hover {
   background: #dbf7ff;
 }
 
 .room-list > .room-entry > p {
   margin: 0;
   padding: .2rem 0;
 }
@@ -457,16 +483,33 @@ html[dir="rtl"] .tab-view li:nth-child(2
 }
 
 /* keep the various room-entry row pieces aligned with each other */
 .room-list > .room-entry > h2 > button,
 .room-list > .room-entry > h2 > span {
   vertical-align: middle;
 }
 
+.room-list > .room-entry > h2:before {
+  content: "";
+  display: inline-block;
+  background-image: url("../shared/img/icons-14x14.svg#hello");
+  background-repeat: no-repeat;
+  background-size: cover;
+  width: 13px;
+  height: 13px;
+  -moz-margin-end: 1rem;
+  margin-bottom: -3px;
+}
+
+.room-list > .room-entry.room-active > h2:before {
+  background-image: url("../shared/img/icons-14x14.svg#hello-active");
+}
+
+
 /* Keep ".room-list > .room-entry > h2" in sync with these. */
 .room-entry-context-item {
   display: inline-block;
   vertical-align: middle;
   -moz-margin-start: 1rem;
   height: 16px;
 }
 
@@ -739,17 +782,17 @@ html[dir="rtl"] .generate-url-spinner {
   content: "";
   display: inline-block;
   width: 16px;
   height: 16px;
   vertical-align: bottom;
   background-repeat: no-repeat;
   background-size: cover;
   -moz-margin-end: .2rem;
-  margin-bottom: -2px;
+  margin-bottom: -3px;
 }
 
 .dropdown-menu-item.status-available:before,
 .dropdown-menu-item.status-unavailable:before {
   margin-bottom: 2px;
 }
 
 html[dir="rtl"] .dropdown-menu-item.status-available:before,
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -575,17 +575,16 @@ loop.panel = (function(_, mozL10n) {
         "copy-link": true,
         "checked": this.state.urlCopied
       });
 
       return (
         React.createElement("div", {className: roomClasses, onClick: this.handleClickEntry, 
              onMouseLeave: this.handleMouseLeave}, 
           React.createElement("h2", null, 
-            React.createElement("span", {className: "room-notification"}), 
             this.props.room.decryptedContext.roomName, 
             React.createElement("button", {className: copyButtonClasses, 
               onClick: this.handleCopyButtonClick, 
               title: mozL10n.get("rooms_list_copy_url_tooltip")}), 
             React.createElement("button", {className: "delete-link", 
               onClick: this.handleDeleteButtonClick, 
               title: mozL10n.get("rooms_list_delete_tooltip")})
           ), 
@@ -647,54 +646,72 @@ loop.panel = (function(_, mozL10n) {
         this.closeWindow();
       }
     },
 
     _onStoreStateChanged: function() {
       this.setState(this.props.store.getStoreState());
     },
 
-    _getListHeading: function() {
-      var numRooms = this.state.rooms.length;
-      if (numRooms === 0) {
-        return mozL10n.get("rooms_list_no_current_conversations");
-      }
-      return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
-    },
-
     _getUserDisplayName: function() {
       return this.props.userProfile && this.props.userProfile.email ||
         mozL10n.get("display_name_guest");
     },
 
+    _renderNoRoomsView: function() {
+      return (
+        React.createElement("div", {className: "room-list"}, 
+          React.createElement("div", {className: "room-list-empty"}, 
+            React.createElement("p", {className: "panel-text-large"}, 
+              mozL10n.get("no_conversations_message_heading")
+            ), 
+            React.createElement("p", {className: "panel-text-medium"}, 
+              mozL10n.get("no_conversations_start_message")
+            )
+          ), 
+          this._renderNewRoomButton()
+        )
+      );
+    },
+
+    _renderNewRoomButton: function() {
+      return (
+        React.createElement(NewRoomView, {dispatcher: this.props.dispatcher, 
+          mozLoop: this.props.mozLoop, 
+          pendingOperation: this.state.pendingCreation ||
+                            this.state.pendingInitialRetrieval, 
+          userDisplayName: this._getUserDisplayName()})
+      );
+    },
+
     render: function() {
       if (this.state.error) {
         // XXX Better end user reporting of errors.
         console.error("RoomList error", this.state.error);
       }
 
+      if (!this.state.rooms.length) {
+        return this._renderNoRoomsView();
+      }
+
       return (
         React.createElement("div", {className: "rooms"}, 
-          React.createElement("h1", null, this._getListHeading()), 
+          React.createElement("h1", null, mozL10n.get("rooms_list_recent_conversations")), 
           React.createElement("div", {className: "room-list"}, 
             this.state.rooms.map(function(room, i) {
               return (
                 React.createElement(RoomEntry, {
                   dispatcher: this.props.dispatcher, 
                   key: room.roomToken, 
                   mozLoop: this.props.mozLoop, 
                   room: room})
               );
             }, this)
           ), 
-          React.createElement(NewRoomView, {dispatcher: this.props.dispatcher, 
-            mozLoop: this.props.mozLoop, 
-            pendingOperation: this.state.pendingCreation ||
-              this.state.pendingInitialRetrieval, 
-            userDisplayName: this._getUserDisplayName()})
+          this._renderNewRoomButton()
         )
       );
     }
   });
 
   /**
    * Used for creating a new room with or without context.
    */
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -575,17 +575,16 @@ loop.panel = (function(_, mozL10n) {
         "copy-link": true,
         "checked": this.state.urlCopied
       });
 
       return (
         <div className={roomClasses} onClick={this.handleClickEntry}
              onMouseLeave={this.handleMouseLeave}>
           <h2>
-            <span className="room-notification" />
             {this.props.room.decryptedContext.roomName}
             <button className={copyButtonClasses}
               onClick={this.handleCopyButtonClick}
               title={mozL10n.get("rooms_list_copy_url_tooltip")} />
             <button className="delete-link"
               onClick={this.handleDeleteButtonClick}
               title={mozL10n.get("rooms_list_delete_tooltip")} />
           </h2>
@@ -647,54 +646,72 @@ loop.panel = (function(_, mozL10n) {
         this.closeWindow();
       }
     },
 
     _onStoreStateChanged: function() {
       this.setState(this.props.store.getStoreState());
     },
 
-    _getListHeading: function() {
-      var numRooms = this.state.rooms.length;
-      if (numRooms === 0) {
-        return mozL10n.get("rooms_list_no_current_conversations");
-      }
-      return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
-    },
-
     _getUserDisplayName: function() {
       return this.props.userProfile && this.props.userProfile.email ||
         mozL10n.get("display_name_guest");
     },
 
+    _renderNoRoomsView: function() {
+      return (
+        <div className="room-list">
+          <div className="room-list-empty">
+            <p className="panel-text-large">
+              {mozL10n.get("no_conversations_message_heading")}
+            </p>
+            <p className="panel-text-medium">
+              {mozL10n.get("no_conversations_start_message")}
+            </p>
+          </div>
+          {this._renderNewRoomButton()}
+        </div>
+      );
+    },
+
+    _renderNewRoomButton: function() {
+      return (
+        <NewRoomView dispatcher={this.props.dispatcher}
+          mozLoop={this.props.mozLoop}
+          pendingOperation={this.state.pendingCreation ||
+                            this.state.pendingInitialRetrieval}
+          userDisplayName={this._getUserDisplayName()} />
+      );
+    },
+
     render: function() {
       if (this.state.error) {
         // XXX Better end user reporting of errors.
         console.error("RoomList error", this.state.error);
       }
 
+      if (!this.state.rooms.length) {
+        return this._renderNoRoomsView();
+      }
+
       return (
         <div className="rooms">
-          <h1>{this._getListHeading()}</h1>
+          <h1>{mozL10n.get("rooms_list_recent_conversations")}</h1>
           <div className="room-list">{
             this.state.rooms.map(function(room, i) {
               return (
                 <RoomEntry
                   dispatcher={this.props.dispatcher}
                   key={room.roomToken}
                   mozLoop={this.props.mozLoop}
                   room={room} />
               );
             }, this)
           }</div>
-          <NewRoomView dispatcher={this.props.dispatcher}
-            mozLoop={this.props.mozLoop}
-            pendingOperation={this.state.pendingCreation ||
-              this.state.pendingInitialRetrieval}
-            userDisplayName={this._getUserDisplayName()} />
+          {this._renderNewRoomButton()}
         </div>
       );
     }
   });
 
   /**
    * Used for creating a new room with or without context.
    */
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/img/empty_conversations.svg
@@ -0,0 +1,1 @@
+<svg width="119" height="70" viewBox="0 0 119 70" xmlns="http://www.w3.org/2000/svg"><g fill="#D8D8D8"><path d="M96.767 5c12.017 0 21.763 8.244 21.763 18.41 0 5.061-2.416 9.647-6.323 12.975.679 2.303 2.022 5.429 4.677 8.481-.454.773-7.931-1.954-13.201-3.994-2.175.613-4.497.949-6.916.949-12.02 0-21.766-8.243-21.766-18.41 0-10.166 9.746-18.41 21.766-18.41z" fill-opacity=".8"/><path d="M21.763 5c-12.017 0-21.763 8.244-21.763 18.41 0 5.061 2.416 9.647 6.323 12.975-.679 2.303-2.022 5.429-4.677 8.481.454.773 7.931-1.954 13.201-3.994 2.175.613 4.497.949 6.916.949 12.02 0 21.766-8.243 21.766-18.41 0-10.166-9.746-18.41-21.766-18.41z" fill-opacity=".8"/><path d="M56.742 4c-18.631 0-33.742 12.782-33.742 28.543 0 7.847 3.747 14.957 9.803 20.117-1.052 3.571-3.135 8.418-7.251 13.149.704 1.199 12.296-3.03 20.467-6.193 3.372.951 6.973 1.471 10.723 1.471 18.637 0 33.746-12.78 33.746-28.544 0-15.761-15.11-28.543-33.746-28.543z" stroke="#FBFBFB" stroke-width="4"/></g></svg>
\ No newline at end of file
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -69,16 +69,17 @@ browser.jar:
   content/browser/loop/shared/img/vivo.png                      (content/shared/img/vivo.png)
   content/browser/loop/shared/img/vivo@2x.png                   (content/shared/img/vivo@2x.png)
   content/browser/loop/shared/img/02.png                        (content/shared/img/02.png)
   content/browser/loop/shared/img/02@2x.png                     (content/shared/img/02@2x.png)
   content/browser/loop/shared/img/telefonica.png                (content/shared/img/telefonica.png)
   content/browser/loop/shared/img/telefonica@2x.png             (content/shared/img/telefonica@2x.png)
   content/browser/loop/shared/img/ellipsis-v.svg                (content/shared/img/ellipsis-v.svg)
   content/browser/loop/shared/img/empty_contacts.svg            (content/shared/img/empty_contacts.svg)
+  content/browser/loop/shared/img/empty_conversations.svg       (content/shared/img/empty_conversations.svg)
   content/browser/loop/shared/img/avatars.svg                   (content/shared/img/avatars.svg)
 
   # Shared scripts
   content/browser/loop/shared/js/actions.js             (content/shared/js/actions.js)
   content/browser/loop/shared/js/conversationStore.js   (content/shared/js/conversationStore.js)
   content/browser/loop/shared/js/store.js               (content/shared/js/store.js)
   content/browser/loop/shared/js/roomStates.js          (content/shared/js/roomStates.js)
   content/browser/loop/shared/js/fxOSActiveRoomStore.js (content/shared/js/fxOSActiveRoomStore.js)
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -794,16 +794,30 @@ describe("loop.panel", function() {
       roomStore.setStoreState({pendingCreation: true});
 
       sinon.assert.notCalled(fakeWindow.close);
 
       roomStore.setStoreState({pendingCreation: false});
 
       sinon.assert.calledOnce(fakeWindow.close);
     });
+
+    it("should render the no rooms view when no rooms available", function() {
+      var view = createTestComponent();
+      var node = view.getDOMNode();
+
+      expect(node.querySelectorAll(".room-list-empty").length).to.eql(1);
+    });
+
+    it("should call mozL10n.get for room empty strings", function() {
+      var view = createTestComponent();
+
+      sinon.assert.calledWithExactly(document.mozL10n.get,
+                                     "no_conversations_message_heading");
+    });
   });
 
   describe("loop.panel.NewRoomView", function() {
     var roomStore, dispatcher, fakeEmail, dispatch;
 
     beforeEach(function() {
       fakeEmail = "fakeEmail@example.com";
       dispatcher = new loop.Dispatcher();
--- a/browser/components/loop/ui/ui-showcase.css
+++ b/browser/components/loop/ui/ui-showcase.css
@@ -66,16 +66,17 @@ body {
 .showcase > section .comp.dashed,
 .showcase > section .comp > iframe.dashed
 {
   border: 1px dashed #ccc;
 }
 
 .showcase > section > .example {
   margin-bottom: 6em;
+  background: #fbfbfb;
 }
 
 .showcase > section > h2 {
   font-size: 1.5em;
   font-weight: bold;
   margin: 1.5em 0;
 }
 
@@ -89,20 +90,16 @@ body {
   text-align: left;
 }
 
 .showcase > section .example > h3 a {
   text-decoration: none;
   color: #555;
 }
 
-.showcase .checkbox-wrapper label {
-  font-weight: bold;
-}
-
 .showcase .checkbox.checked {
   background-image: url("../content/shared/img/check.svg#check-blue");
 }
 
 .showcase p.note {
   margin: 0;
   padding: 0;
   color: #666;
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -436,16 +436,28 @@
 
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStores[0],
     textChatStore: textChatStore
   });
 
   // Local mocks
+  var mockMozLoopNoRooms = _.cloneDeep(navigator.mozLoop);
+  mockMozLoopNoRooms.rooms.getAll = function(version, callback) {
+    callback(null, []);
+  };
+
+  var roomStoreNoRooms = new loop.store.RoomStore(new loop.Dispatcher(), {
+    mozLoop: mockMozLoopNoRooms,
+    activeRoomStore: new loop.store.ActiveRoomStore(new loop.Dispatcher(), {
+      mozLoop: mockMozLoopNoRooms,
+      sdkDriver: mockSDK
+    })
+  });
 
   var mockMozLoopLoggedIn = _.cloneDeep(navigator.mozLoop);
   mockMozLoopLoggedIn.userProfile = {
     email: "text@example.com",
     uid: "0354b278a381d3cb408bb46ffc01266"
   };
 
   var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
@@ -710,16 +722,31 @@
                            roomStore: roomStore, 
                            selectedTab: "rooms"})
               )
             ), 
 
             React.createElement(FramedExample, {cssClass: "fx-embedded-panel", 
                            dashed: true, 
                            height: 410, 
+                           summary: "Room list tab (no rooms)", 
+                           width: 332}, 
+              React.createElement("div", {className: "panel"}, 
+                React.createElement(PanelView, {client: mockClient, 
+                           dispatcher: dispatcher, 
+                           mozLoop: mockMozLoopNoRooms, 
+                           notifications: notifications, 
+                           roomStore: roomStoreNoRooms, 
+                           selectedTab: "rooms"})
+              )
+            ), 
+
+            React.createElement(FramedExample, {cssClass: "fx-embedded-panel", 
+                           dashed: true, 
+                           height: 410, 
                            summary: "Contact list tab", 
                            width: 332}, 
               React.createElement("div", {className: "panel"}, 
                 React.createElement(PanelView, {client: mockClient, 
                            dispatcher: dispatcher, 
                            mozLoop: mockMozLoopLoggedIn, 
                            notifications: notifications, 
                            roomStore: roomStore, 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -436,16 +436,28 @@
 
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStores[0],
     textChatStore: textChatStore
   });
 
   // Local mocks
+  var mockMozLoopNoRooms = _.cloneDeep(navigator.mozLoop);
+  mockMozLoopNoRooms.rooms.getAll = function(version, callback) {
+    callback(null, []);
+  };
+
+  var roomStoreNoRooms = new loop.store.RoomStore(new loop.Dispatcher(), {
+    mozLoop: mockMozLoopNoRooms,
+    activeRoomStore: new loop.store.ActiveRoomStore(new loop.Dispatcher(), {
+      mozLoop: mockMozLoopNoRooms,
+      sdkDriver: mockSDK
+    })
+  });
 
   var mockMozLoopLoggedIn = _.cloneDeep(navigator.mozLoop);
   mockMozLoopLoggedIn.userProfile = {
     email: "text@example.com",
     uid: "0354b278a381d3cb408bb46ffc01266"
   };
 
   var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
@@ -710,16 +722,31 @@
                            roomStore={roomStore}
                            selectedTab="rooms" />
               </div>
             </FramedExample>
 
             <FramedExample cssClass="fx-embedded-panel"
                            dashed={true}
                            height={410}
+                           summary="Room list tab (no rooms)"
+                           width={332}>
+              <div className="panel">
+                <PanelView client={mockClient}
+                           dispatcher={dispatcher}
+                           mozLoop={mockMozLoopNoRooms}
+                           notifications={notifications}
+                           roomStore={roomStoreNoRooms}
+                           selectedTab="rooms" />
+              </div>
+            </FramedExample>
+
+            <FramedExample cssClass="fx-embedded-panel"
+                           dashed={true}
+                           height={410}
                            summary="Contact list tab"
                            width={332}>
               <div className="panel">
                 <PanelView client={mockClient}
                            dispatcher={dispatcher}
                            mozLoop={mockMozLoopLoggedIn}
                            notifications={notifications}
                            roomStore={roomStore}
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -132,16 +132,22 @@ import_contacts_success_message={{total}
 ## importing_contacts_button once contacts have been imported once.
 sync_contacts_button=Sync Contacts
 ## LOCALIZATION NOTE(no_contacts_message_heading): Title shown when user has no
 ## contacts in his address book
 no_contacts_message_heading=No contacts yet
 ## LOCALIZATION NOTE(no_contacts_import_or_add): Subheading inviting the user
 ## to add people to his contact list
 no_contacts_import_or_add=Import or add someone
+## LOCALIZATION NOTE(no_conversations_message_heading): Title shown when user
+## has no conversations available.
+no_conversations_message_heading=There are no conversations yet
+## LOCALIZATION NOTE(no_converastions_start_message): Subheading inviting the
+## user to start a new conversation.
+no_conversations_start_message=start a new conversation!
 
 ## LOCALIZATION NOTE(import_failed_description simple): Displayed when an import of
 ## contacts fails. This is displayed in the error field.
 import_failed_description_simple=Sorry, contact import failed
 import_failed_description_some=Some contacts could not be imported
 import_failed_support_button=Help
 
 ## LOCALIZATION NOTE(remove_contact_menu_button2): Displayed in the contact list in
@@ -303,25 +309,21 @@ feedback_request_button=Leave Feedback
 help_label=Help
 tour_label=Tour
 
 ## LOCALIZATION NOTE(rooms_default_room_name_template): {{conversationLabel}}
 ## will be replaced by a number. For example "Conversation 1" or "Conversation 12".
 rooms_default_room_name_template=Conversation {{conversationLabel}}
 rooms_leave_button_label=Leave
 rooms_list_copy_url_tooltip=Copy Link
-## LOCALIZATION NOTE (rooms_list_current_conversations):
-## Semicolon-separated list of plural forms. See:
-## http://developer.mozilla.org/en/docs/Localization_and_Plurals
-## We prefer to have no number in the string, but if you need it for your
-## language please use {{num}}.
-rooms_list_current_conversations=Current conversation;Current conversations
+## LOCALIZATION NOTE (rooms_list_recent_conversations): String is in all caps
+## for emphasis reasons, it is a heading. Proceed as appropriate for locale.
+rooms_list_recent_conversations=RECENT CONVERSATIONS
 rooms_list_delete_tooltip=Delete conversation
 rooms_list_deleteConfirmation_label=Are you sure?
-rooms_list_no_current_conversations=No current conversations
 rooms_change_failed_label=Conversation cannot be updated
 rooms_new_room_button_label=Start a conversation
 rooms_only_occupant_label=You're the first one here.
 rooms_panel_title=Choose a conversation or start a new one
 rooms_room_full_label=There are already two people in this conversation.
 rooms_room_full_call_to_action_nonFx_label=Download {{brandShortname}} to start your own
 rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} ยป
 rooms_room_joined_label=Someone has joined the conversation!