Bug 1183619 - add a new contact form visual refresh, ui-r=vicky, r=dmose

This commit is contained in:
David Critchley 2015-08-10 15:37:24 -07:00
parent d4e320ed46
commit 4ce7f6413b
8 changed files with 176 additions and 79 deletions

View File

@ -2,6 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
font-size: 10px;
font-family: sans-serif; /* XXX will be changed to a system font in bug 1191398 */
}
.contact-import-spinner {
display: none;
}
@ -281,8 +286,26 @@ html[dir="rtl"] .contact > .dropdown-menu {
top: auto;
}
.contact-form > .button-group {
margin-top: 1rem;
.contact-form {
padding: 14px; /* Override based on spacing in Mockup */
}
/* This will effect the header displayed at the top of the contact details form
*/
.contact-form header {
text-align: center;
font-size: 1.3rem;
font-weight: 500;
color: #4a4a4a;
}
.contact-form .form-content-container {
height: 24.1rem; /* This height is needed to keep the panel height at 400px and
the bottom elements at the bottom of the panel
Can likely go away if we switched this pane to flexbox model */
padding-top: 4px; /* Based on spacing in Mockup
replaced margin-top
See http://stackoverflow.com/questions/6204670/css-clean-solution-to-the-margin-collapse-issue-when-floating-an-element
*/
}
.contacts-gravatar-promo {

View File

@ -15,6 +15,8 @@ body {
.panel {
/* hide the extra margin space that the panel resizer now wants to show */
overflow: hidden;
font: menu;
background-color: #fbfbfb;
}
/* Notifications displayed over tabs */
@ -83,8 +85,6 @@ body {
.tab-view > li {
display: inline-block;
vertical-align: middle;
line-height: 1.2rem;
text-align: center;
padding: 0;
cursor: pointer;
@ -252,20 +252,26 @@ html[dir="rtl"] .tab-view li:nth-child(2).selected ~ .slide-bar {
display: block;
width: 100%;
outline: none;
border-radius: 2px;
margin: 5px 0;
border: 1px solid #ccc;
height: 24px;
padding: 0 10px;
}
.content-area input:invalid {
border-radius: 4px;
margin: 10px 0;
border: 1px solid #c3c3c3;
height: 2.6rem;
padding: 0 6px;
font-size: 1.1rem;
color: #4a4a4a;
box-shadow: none;
}
.content-area input::-moz-placeholder {
color: #999;
}
.content-area input:not(.pristine):invalid {
border-color: #d74345;
box-shadow: 0 0 4px #c43c3e;
border: 0.1rem solid #d13f1a;
}
.content-area input:focus {
border: 0.1rem solid #5cccee;
}
/* Rooms */
@ -475,11 +481,17 @@ html[dir="rtl"] .tab-view li:nth-child(2).selected ~ .slide-bar {
display: flex;
flex-direction: row;
width: 100%;
padding-top: 6px;
}
.button-group > .button {
flex: 1;
margin: 0 7px;
margin: 0 5px;
min-height: 3rem;
font-size: 1.2rem;
line-height: 1rem;
font-weight: 300;
border-radius: 4px;
}
.button-group > .button:first-child {
@ -494,11 +506,11 @@ html[dir="rtl"] .tab-view li:nth-child(2).selected ~ .slide-bar {
padding: 2px 5px;
background-color: #fbfbfb;
color: #333;
border: 1px solid #c1c1c1;
border-radius: 2px;
min-height: 26px;
font-size: 1.2rem;
line-height: 1.2rem;
border: none;
}
.button:hover {
@ -507,27 +519,39 @@ html[dir="rtl"] .tab-view li:nth-child(2).selected ~ .slide-bar {
.button:hover:active {
background-color: #ccc;
color: #111;
color: #fff;
}
.button.button-accept {
background-color: #5bc0a4;
border-color: #5bc0a4;
color: #fff;
}
.button.button-accept:hover {
background-color: #47b396;
border-color: #47b396;
background-color: #00a9dc;
color: #fff;
}
.button.button-accept:hover,
.button.button-accept:hover:active {
background-color: #3aa689;
border-color: #3aa689;
background-color: #5cccee;
color: #fff;
}
.button.button-cancel {
background-color: #ebebeb;
border: 0;
color: #000;
width: 105px; /* based on fixed width of Cancel button from mockup */
flex: 0 0 auto;
}
.button.button-cancel:hover,
.button.button-cancel:hover:active {
background-color: #dcd6d6;
color: #000;
}
.button.button-cancel:disabled {
background-color: #ebebeb;
color: #c3c3c3;
}
.button-close {
background-color: transparent;
background-image: url(../shared/img/icons-10x10.svg#close);

View File

@ -740,31 +740,40 @@ loop.contacts = (function(_, mozL10n) {
render: function() {
let cx = React.addons.classSet;
let phoneOrEmailRequired = !this.state.email && !this.state.tel;
let contactFormMode = "contact-form-mode-" + this.props.mode;
let contentAreaClassesLiteral = {
"content-area": true,
"contact-form": true
};
contentAreaClassesLiteral[contactFormMode] = true;
let contentAreaClasses = cx(contentAreaClassesLiteral);
return (
React.createElement("div", {className: "content-area contact-form"},
React.createElement("div", {className: contentAreaClasses},
React.createElement("header", null, this.props.mode == "add"
? mozL10n.get("add_contact_button")
? mozL10n.get("add_contact_title")
: mozL10n.get("edit_contact_title")),
React.createElement("label", null, mozL10n.get("edit_contact_name_label")),
React.createElement("input", {className: cx({pristine: this.state.pristine}),
pattern: "\\s*\\S.*",
ref: "name",
required: true,
type: "text",
valueLink: this.linkState("name")}),
React.createElement("label", null, mozL10n.get("edit_contact_email_label")),
React.createElement("input", {className: cx({pristine: this.state.pristine}),
ref: "email",
required: phoneOrEmailRequired,
type: "email",
valueLink: this.linkState("email")}),
React.createElement("label", null, mozL10n.get("new_contact_fxos_phone_placeholder")),
React.createElement("input", {className: cx({pristine: this.state.pristine}),
ref: "tel",
required: phoneOrEmailRequired,
type: "tel",
valueLink: this.linkState("tel")}),
React.createElement("div", {className: cx({"form-content-container": true})},
React.createElement("input", {className: cx({pristine: this.state.pristine}),
pattern: "\\s*\\S.*",
placeholder: mozL10n.get("contact_form_name_placeholder"),
ref: "name",
required: true,
type: "text",
valueLink: this.linkState("name")}),
React.createElement("input", {className: cx({pristine: this.state.pristine}),
placeholder: mozL10n.get("contact_form_email_placeholder"),
ref: "email",
required: phoneOrEmailRequired,
type: "email",
valueLink: this.linkState("email")}),
React.createElement("input", {className: cx({pristine: this.state.pristine}),
placeholder: mozL10n.get("contact_form_fxos_phone_placeholder"),
ref: "tel",
required: phoneOrEmailRequired,
type: "tel",
valueLink: this.linkState("tel")})
),
React.createElement(ButtonGroup, null,
React.createElement(Button, {additionalClass: "button-cancel",
caption: mozL10n.get("cancel_button"),

View File

@ -740,31 +740,40 @@ loop.contacts = (function(_, mozL10n) {
render: function() {
let cx = React.addons.classSet;
let phoneOrEmailRequired = !this.state.email && !this.state.tel;
let contactFormMode = "contact-form-mode-" + this.props.mode;
let contentAreaClassesLiteral = {
"content-area": true,
"contact-form": true
};
contentAreaClassesLiteral[contactFormMode] = true;
let contentAreaClasses = cx(contentAreaClassesLiteral);
return (
<div className="content-area contact-form">
<div className={contentAreaClasses}>
<header>{this.props.mode == "add"
? mozL10n.get("add_contact_button")
? mozL10n.get("add_contact_title")
: mozL10n.get("edit_contact_title")}</header>
<label>{mozL10n.get("edit_contact_name_label")}</label>
<input className={cx({pristine: this.state.pristine})}
pattern="\s*\S.*"
ref="name"
required
type="text"
valueLink={this.linkState("name")} />
<label>{mozL10n.get("edit_contact_email_label")}</label>
<input className={cx({pristine: this.state.pristine})}
ref="email"
required={phoneOrEmailRequired}
type="email"
valueLink={this.linkState("email")} />
<label>{mozL10n.get("new_contact_fxos_phone_placeholder")}</label>
<input className={cx({pristine: this.state.pristine})}
ref="tel"
required={phoneOrEmailRequired}
type="tel"
valueLink={this.linkState("tel")} />
<div className={cx({"form-content-container": true})}>
<input className={cx({pristine: this.state.pristine})}
pattern="\s*\S.*"
placeholder={mozL10n.get("contact_form_name_placeholder")}
ref="name"
required
type="text"
valueLink={this.linkState("name")} />
<input className={cx({pristine: this.state.pristine})}
placeholder={mozL10n.get("contact_form_email_placeholder")}
ref="email"
required={phoneOrEmailRequired}
type="email"
valueLink={this.linkState("email")} />
<input className={cx({pristine: this.state.pristine})}
placeholder={mozL10n.get("contact_form_fxos_phone_placeholder")}
ref="tel"
required={phoneOrEmailRequired}
type="tel"
valueLink={this.linkState("tel")} />
</div>
<ButtonGroup>
<Button additionalClass="button-cancel"
caption={mozL10n.get("cancel_button")}

View File

@ -8,7 +8,8 @@ describe("loop.contacts", function() {
var expect = chai.expect;
var TestUtils = React.addons.TestUtils;
var fakeAddContactButtonText = "Fake Add Contact";
var fakeAddContactButtonText = "Fake Add Contact Button";
var fakeAddContactTitleText = "Fake Add Contact Title";
var fakeEditContactButtonText = "Fake Edit Contact";
var fakeDoneButtonText = "Fake Done";
// The fake contacts array is copied each time mozLoop.contacts.getAll() is called.
@ -86,7 +87,9 @@ describe("loop.contacts", function() {
navigator.mozLoop = {
getStrings: function(entityName) {
var textContentValue = "fakeText";
if (entityName == "add_contact_button") {
if (entityName == "add_contact_title") {
textContentValue = fakeAddContactTitleText;
} else if (entityName == "add_contact_button") {
textContentValue = fakeAddContactButtonText;
} else if (entityName == "edit_contact_title") {
textContentValue = fakeEditContactButtonText;
@ -400,7 +403,7 @@ describe("loop.contacts", function() {
var header = view.getDOMNode().querySelector("header");
expect(header).to.not.equal(null);
expect(header.textContent).to.eql(fakeAddContactButtonText);
expect(header.textContent).to.eql(fakeAddContactTitleText);
});
it("should render name input", function() {

View File

@ -18,6 +18,7 @@
var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
var PanelView = loop.panel.PanelView;
var SignInRequestView = loop.panel.SignInRequestView;
var ContactDetailsForm = loop.contacts.ContactDetailsForm;
var ContactDropdown = loop.contacts.ContactDropdown;
var ContactDetail = loop.contacts.ContactDetail;
// 1.2. Conversation Window
@ -787,6 +788,18 @@
notifications: new loop.shared.models.NotificationCollection([{level: "error", message: "Import error"}]),
roomStore: roomStore,
selectedTab: "contacts"})
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel", dashed: true, height: 400,
summary: "Contact Form - Add", width: 332},
React.createElement("div", {className: "panel"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: mockMozLoopLoggedIn,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts_add",
userProfile: {email: "test@example.com"}})
)
)
),

View File

@ -18,6 +18,7 @@
var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
var PanelView = loop.panel.PanelView;
var SignInRequestView = loop.panel.SignInRequestView;
var ContactDetailsForm = loop.contacts.ContactDetailsForm;
var ContactDropdown = loop.contacts.ContactDropdown;
var ContactDetail = loop.contacts.ContactDetail;
// 1.2. Conversation Window
@ -788,6 +789,18 @@
roomStore={roomStore}
selectedTab="contacts" />
</Example>
<FramedExample cssClass="fx-embedded-panel" dashed={true} height={400}
summary="Contact Form - Add" width={332}>
<div className="panel">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={mockMozLoopLoggedIn}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts_add"
userProfile={{email: "test@example.com"}} />
</div>
</FramedExample>
</Section>
<Section name="Availability Dropdown">

View File

@ -91,13 +91,13 @@ contacts_search_placesholder=Search…
## LOCALIZATION NOTE (new_contact_button): This is the button to open the
## new contact sub-panel.
new_contact_button=New Contact
## LOCALIZATION NOTE (new_contact_name_placeholder, new_contact_email_placeholder):
## These are the placeholders for the fields for entering a new contact. Click
## the 'New Contact' button to see the fields.
new_contact_name_placeholder=Name
new_contact_email_placeholder=Email
new_contact_fxos_phone_placeholder=Firefox OS Phone
new_contact_phone_placeholder2=Phone
## LOCALIZATION NOTE (contact_form_*_placeholder):
## These are the placeholders for the inputs for entering or editing a contact
## Click the 'New Contact' button to see the fields.
contact_form_name_placeholder=Name
contact_form_email_placeholder=Email
contact_form_fxos_phone_placeholder=Firefox OS Phone
contact_form_phone_placeholder2=Phone
contacts_blocked_contacts=Blocked Contacts
@ -105,6 +105,9 @@ contacts_blocked_contacts=Blocked Contacts
## This is the button to actually add the new contact. Click the 'New Contact'
## button to see the fields.
add_contact_button=Add Contact
## LOCALIZATION NOTE(add_contact_title): This is the subtitle of the add contact
## panel. It is displayed when Add Contact is selected.
add_contact_title=Add Contact
### LOCALIZATION NOTE (valid_email_text_description): This is displayed when
### the user enters an invalid email address, preventing the addition of the
### contact.