]> source.dussan.org Git - sonarqube.git/commitdiff
replace native buttons with Button component (#235)
authorStas Vilchik <stas.vilchik@sonarsource.com>
Mon, 14 May 2018 15:22:53 +0000 (17:22 +0200)
committerSonarTech <sonartech@sonarsource.com>
Mon, 14 May 2018 18:20:49 +0000 (20:20 +0200)
45 files changed:
server/sonar-web/src/main/js/app/styles/init/forms.css
server/sonar-web/src/main/js/app/utils/exposeLibraries.ts
server/sonar-web/src/main/js/apps/account/components/Password.js
server/sonar-web/src/main/js/apps/background-tasks/components/Search.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap
server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js
server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js
server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap
server/sonar-web/src/main/js/apps/permissions/project/components/PublicProjectDisclaimer.js
server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js
server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js
server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js
server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js
server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js
server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js
server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx
server/sonar-web/src/main/js/apps/settings/components/EmailForm.js
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js
server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.js
server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap
server/sonar-web/src/main/js/components/controls/GlobalMessages.js
server/sonar-web/src/main/js/components/controls/Toggle.js
server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js
server/sonar-web/src/main/js/components/controls/styles.css
server/sonar-web/src/main/js/components/issue/components/IssueMessage.js
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.js.snap
server/sonar-web/src/main/js/components/ui/buttons.css

index 98a484ef21e0222c58a023c3dfa5c7462b8a000e..696151b06bc15dfa87a8700e36c202c8c2d770d1 100644 (file)
@@ -125,264 +125,6 @@ select {
   line-height: var(--controlHeight);
 }
 
-button,
-.button,
-input[type='submit'],
-input[type='button'] {
-  display: inline-block;
-  vertical-align: baseline;
-  height: var(--controlHeight);
-  line-height: calc(var(--controlHeight) - 2px);
-  padding: 0 12px;
-  border: 1px solid var(--darkBlue);
-  border-radius: 2px;
-  box-sizing: border-box;
-  background: transparent;
-  color: var(--darkBlue);
-  font-weight: 600;
-  font-size: var(--smallFontSize);
-  text-align: center;
-  text-decoration: none;
-  cursor: pointer;
-  outline: none;
-  transition: border-color 0.2s ease;
-}
-
-button:hover,
-.button:hover,
-input[type='submit']:hover,
-input[type='button']:hover,
-button:focus,
-.button:focus,
-input[type='submit']:focus,
-input[type='button']:focus,
-button.button-active,
-.button.button-active,
-input[type='submit'].button-active,
-input[type='button'].button-active {
-  background: var(--darkBlue);
-  color: #fff;
-}
-
-button:active,
-.button:active,
-input[type='submit']:active,
-input[type='button']:active {
-  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
-}
-
-button.disabled,
-.button.disabled,
-input[type='submit'].disabled,
-input[type='button'].disabled,
-button:disabled,
-.button:disabled,
-input[type='submit']:disabled,
-input[type='button']:disabled,
-button:disabled:hover,
-.button:disabled:hover,
-input[type='submit']:disabled:hover,
-input[type='button']:disabled:hover,
-button:disabled:active,
-.button:disabled:active,
-input[type='submit']:disabled:active,
-input[type='button']:disabled:active,
-button:disabled:focus,
-.button:disabled:focus,
-input[type='submit']:disabled:focus,
-input[type='button']:disabled:focus {
-  color: var(--disableGrayText) !important;
-  border-color: var(--disableGrayBorder) !important;
-  background: var(--disableGrayBg) !important;
-  cursor: not-allowed !important;
-  box-shadow: none !important;
-}
-
-.button svg {
-  margin-top: calc((var(--controlHeight) - 16px - 2px) / 2);
-}
-
-.button-red,
-input[type='submit'].button-red {
-  border-color: var(--red);
-  color: var(--red);
-}
-
-.button-red:hover,
-input[type='submit'].button-red:hover,
-.button-red:focus,
-input[type='submit'].button-red:focus,
-.button-red.active,
-input[type='submit'].button-red.active {
-  background: var(--red);
-  color: #fff;
-}
-
-.button-success,
-input[type='submit'].button-success {
-  border-color: var(--green);
-  color: var(--green);
-}
-
-.button-success:hover,
-input[type='submit'].button-success:hover,
-.button-success:focus,
-input[type='submit'].button-success:focus,
-.button-success.active,
-input[type='submit'].button-success.active {
-  background: var(--green);
-  color: #fff;
-}
-
-.button-grey,
-input[type='submit'].button-grey {
-  border-color: var(--gray71);
-  color: var(--secondFontColor);
-}
-
-.button-grey:hover,
-input[type='submit'].button-grey:hover,
-.button-grey:focus,
-input[type='submit'].button-grey:focus,
-.button-grey.active,
-input[type='submit'].button-grey.active {
-  background: var(--gray71);
-  color: #ffffff;
-}
-
-.button-grey.button-active,
-input[type='submit'].button-grey.button-active {
-  background: var(--secondFontColor);
-  border-color: var(--secondFontColor);
-  color: #ffffff;
-}
-
-.button-clean,
-.button-clean:hover,
-.button-clean:focus {
-  padding: 0;
-  line-height: 1;
-  border: none;
-  background: transparent;
-  box-shadow: none;
-  color: var(--baseFontColor);
-}
-
-.button-clean path {
-  transition: opacity 0.3s ease;
-}
-
-.button-clean:hover path {
-  opacity: 0.8;
-}
-
-.button-link {
-  display: inline;
-  height: auto; /* Keep this to not inherit the height from .button */
-  margin: 0;
-  padding: 0;
-  border: none;
-  background: transparent;
-  color: var(--darkBlue);
-  font-weight: 400;
-  font-size: inherit;
-  line-height: inherit;
-  transition: all 0.2s ease;
-}
-
-.button-link svg {
-  margin-top: 0;
-}
-
-.button-link:hover,
-.button-link:focus {
-  background: transparent;
-  color: var(--blue);
-}
-
-.button-link:active {
-  box-shadow: none;
-  outline: thin dotted #ccc;
-}
-
-.button-link:disabled,
-.button-link:disabled:hover,
-.button-link:disabled:active,
-.button-link:disabled:focus {
-  color: var(--secondFontColor);
-  background: transparent !important;
-  cursor: default;
-}
-
-.button-small {
-  height: var(--smallControlHeight);
-  line-height: 18px;
-  padding: 0 6px;
-  font-size: 11px;
-}
-
-.button-small > svg {
-  margin-top: 2px;
-}
-
-.button-group {
-  display: inline-block;
-  vertical-align: middle;
-  font-size: 0;
-  white-space: nowrap;
-}
-
-.button-group > button,
-.button-group > .button {
-  position: relative;
-  z-index: var(--normalZIndex);
-  display: inline-block;
-  vertical-align: middle;
-  margin: 0;
-  cursor: pointer;
-}
-
-.button-group > button:hover:not(:disabled),
-.button-group > .button:hover:not(:disabled),
-.button-group > button:focus:not(:disabled),
-.button-group > .button:focus:not(:disabled),
-.button-group > button:active:not(:disabled),
-.button-group > .button:active:not(:disabled),
-.button-group > button.active:not(:disabled),
-.button-group > .button.active:not(:disabled) {
-  z-index: var(--aboveNormalZIndex);
-}
-
-.button-group > button:disabled,
-.button-group > .button:disabled {
-  z-index: var(--belowNormalZIndex);
-}
-
-.button-group > button:not(:first-child),
-.button-group > .button:not(:first-child) {
-  border-top-left-radius: 0;
-  border-bottom-left-radius: 0;
-}
-
-.button-group > button:not(:last-child):not(.dropdown-toggle),
-.button-group > .button:not(:last-child):not(.dropdown-toggle) {
-  border-top-right-radius: 0;
-  border-bottom-right-radius: 0;
-}
-
-.button-group > button + button,
-.button-group > button + .button,
-.button-group > .button + button,
-.button-group > .button + .button {
-  margin-left: -1px;
-}
-
-.button-group > a:not(.button) {
-  vertical-align: middle;
-  margin: 0 8px;
-  font-size: var(--smallFontSize);
-}
-
 .input-tiny {
   width: 60px !important;
 }
index b07d8c1f53b1f46b0aa9ef6345ec69fcc10183ca..df9cd6a4b7ff50be45833c6c3424c542dbf5c23e 100644 (file)
@@ -40,7 +40,7 @@ import SelectList from '../../components/SelectList/SelectList';
 import CoverageRating from '../../components/ui/CoverageRating';
 import DuplicationsRating from '../../components/ui/DuplicationsRating';
 import Level from '../../components/ui/Level';
-import { EditButton } from '../../components/ui/buttons';
+import { EditButton, Button, SubmitButton, ResetButtonLink } from '../../components/ui/buttons';
 import DeferredSpinner from '../../components/common/DeferredSpinner';
 import ReloadButton from '../../components/controls/ReloadButton';
 
@@ -53,6 +53,7 @@ const exposeLibraries = () => {
   global.SonarMeasures = measures;
   global.SonarRequest = { ...request, throwGlobalError, addGlobalSuccessMessage };
   global.SonarComponents = {
+    Button,
     CoverageRating,
     DateFormatter,
     DateFromNow,
@@ -68,9 +69,11 @@ const exposeLibraries = () => {
     ListFooter,
     Modal,
     ReloadButton,
+    ResetButtonLink,
     SearchBox,
     Select,
     SelectList,
+    SubmitButton,
     Tooltip
   };
 };
index 3c4b7ce7f1bb8644babbc4f6066aa17da0fb6be8..00c12a5e67aa80b9697dfc9111fcae5544fbba0a 100644 (file)
@@ -19,6 +19,7 @@
  */
 import React, { Component } from 'react';
 import { changePassword } from '../../../api/users';
+import { SubmitButton } from '../../../components/ui/buttons';
 import { translate } from '../../../helpers/l10n';
 
 export default class Password extends Component {
@@ -80,7 +81,7 @@ export default class Password extends Component {
 
           {errors &&
             errors.map((e, i) => (
-              <div key={i} className="alert alert-danger">
+              <div className="alert alert-danger" key={i}>
                 {e}
               </div>
             ))}
@@ -91,10 +92,10 @@ export default class Password extends Component {
               <em className="mandatory">*</em>
             </label>
             <input
-              ref={elem => (this.oldPassword = elem)}
               autoComplete="off"
               id="old_password"
               name="old_password"
+              ref={elem => (this.oldPassword = elem)}
               required={true}
               type="password"
             />
@@ -105,10 +106,10 @@ export default class Password extends Component {
               <em className="mandatory">*</em>
             </label>
             <input
-              ref={elem => (this.password = elem)}
               autoComplete="off"
               id="password"
               name="password"
+              ref={elem => (this.password = elem)}
               required={true}
               type="password"
             />
@@ -119,18 +120,18 @@ export default class Password extends Component {
               <em className="mandatory">*</em>
             </label>
             <input
-              ref={elem => (this.passwordConfirmation = elem)}
               autoComplete="off"
               id="password_confirmation"
               name="password_confirmation"
+              ref={elem => (this.passwordConfirmation = elem)}
               required={true}
               type="password"
             />
           </div>
           <div className="modal-field">
-            <button id="change-password" type="submit">
+            <SubmitButton id="change-password">
               {translate('my_profile.password.submit')}
-            </button>
+            </SubmitButton>
           </div>
         </form>
       </section>
index 11f7de80c0af42fd95d445da7ad3fbbf4a793622..e43c4adc54dde9d6b57b935dc4ff4eb27b666484 100644 (file)
@@ -26,6 +26,7 @@ import CurrentsFilter from './CurrentsFilter';
 import DateFilter from './DateFilter';
 import { DEFAULT_FILTERS } from './../constants';
 import SearchBox from '../../../components/controls/SearchBox';
+import { Button } from '../../../components/ui/buttons';
 import { translate } from '../../../helpers/l10n';
 
 export default class Search extends React.PureComponent {
@@ -39,17 +40,17 @@ export default class Search extends React.PureComponent {
     onReload: PropTypes.func.isRequired
   };
 
-  handleStatusChange(status /*: string */) {
+  handleStatusChange = (status /*: string */) => {
     this.props.onFilterUpdate({ status });
-  }
+  };
 
-  handleTypeChange(taskType /*: string */) {
+  handleTypeChange = (taskType /*: string */) => {
     this.props.onFilterUpdate({ taskType });
-  }
+  };
 
-  handleCurrentsChange(currents /*: string */) {
+  handleCurrentsChange = (currents /*: string */) => {
     this.props.onFilterUpdate({ currents });
-  }
+  };
 
   handleDateChange = (date /*: { maxExecutedAt?: Date; minSubmittedAt?: Date } */) => {
     this.props.onFilterUpdate(date);
@@ -59,16 +60,9 @@ export default class Search extends React.PureComponent {
     this.props.onFilterUpdate({ query });
   };
 
-  handleReload(e /*: Object */) {
-    e.target.blur();
-    this.props.onReload();
-  }
-
-  handleReset(e /*: Object */) {
-    e.preventDefault();
-    e.target.blur();
+  handleReset = () => {
     this.props.onFilterUpdate(DEFAULT_FILTERS);
-  }
+  };
 
   renderSearchBox() {
     const { component, query } = this.props;
@@ -106,16 +100,12 @@ export default class Search extends React.PureComponent {
         <ul className="bt-search-form">
           <li>
             <h6 className="bt-search-form-label">{translate('status')}</h6>
-            <StatusFilter value={status} onChange={this.handleStatusChange.bind(this)} />
+            <StatusFilter onChange={this.handleStatusChange} value={status} />
           </li>
           {types.length > 1 && (
             <li>
               <h6 className="bt-search-form-label">{translate('type')}</h6>
-              <TypesFilter
-                value={taskType}
-                types={types}
-                onChange={this.handleTypeChange.bind(this)}
-              />
+              <TypesFilter onChange={this.handleTypeChange} types={types} value={taskType} />
             </li>
           )}
           {!component && (
@@ -123,7 +113,7 @@ export default class Search extends React.PureComponent {
               <h6 className="bt-search-form-label">
                 {translate('background_tasks.currents_filter.ONLY_CURRENTS')}
               </h6>
-              <CurrentsFilter value={currents} onChange={this.handleCurrentsChange.bind(this)} />
+              <CurrentsFilter onChange={this.handleCurrentsChange} value={currents} />
             </li>
           )}
           <li>
@@ -138,12 +128,12 @@ export default class Search extends React.PureComponent {
           {this.renderSearchBox()}
 
           <li className="nowrap">
-            <button className="js-reload" onClick={this.handleReload.bind(this)} disabled={loading}>
+            <Button className="js-reload" disabled={loading} onClick={this.props.onReload}>
               {translate('reload')}
-            </button>{' '}
-            <button ref="resetButton" onClick={this.handleReset.bind(this)} disabled={loading}>
+            </Button>{' '}
+            <Button disabled={loading} onClick={this.handleReset}>
               {translate('reset_verb')}
-            </button>
+            </Button>
           </li>
         </ul>
       </section>
index b1dda0bb9fdba20892bd1f7254c1cb9b046d5b63..770c9587bac1cacb639326c0ed844b9769d8bfd1 100644 (file)
@@ -26,6 +26,7 @@ import { translate } from '../../../helpers/l10n';
 /*:: import type { Organization } from '../../../store/organizations/duck'; */
 import { getOrganizationByKey } from '../../../store/rootReducer';
 import { updateOrganization } from '../actions';
+import { SubmitButton } from '../../../components/ui/buttons';
 
 /*::
 type Props = {
@@ -115,14 +116,14 @@ class OrganizationEdit extends React.PureComponent {
                 <em className="mandatory">*</em>
               </label>
               <input
+                disabled={this.state.loading}
                 id="organization-name"
+                maxLength="64"
                 name="name"
+                onChange={e => this.setState({ name: e.target.value })}
                 required={true}
                 type="text"
-                maxLength="64"
                 value={this.state.name}
-                disabled={this.state.loading}
-                onChange={e => this.setState({ name: e.target.value })}
               />
               <div className="modal-field-description">
                 {translate('organization.name.description')}
@@ -131,13 +132,13 @@ class OrganizationEdit extends React.PureComponent {
             <div className="modal-field">
               <label htmlFor="organization-avatar">{translate('organization.avatar')}</label>
               <input
+                disabled={this.state.loading}
                 id="organization-avatar"
+                maxLength="256"
                 name="avatar"
+                onChange={this.handleAvatarInputChange}
                 type="text"
-                maxLength="256"
                 value={this.state.avatar}
-                disabled={this.state.loading}
-                onChange={this.handleAvatarInputChange}
               />
               <div className="modal-field-description">
                 {translate('organization.avatar.description')}
@@ -148,20 +149,20 @@ class OrganizationEdit extends React.PureComponent {
                     {translate('organization.avatar.preview')}
                     {':'}
                   </div>
-                  <img src={this.state.avatarImage} alt="" height={30} />
+                  <img alt="" height={30} src={this.state.avatarImage} />
                 </div>
               )}
             </div>
             <div className="modal-field">
               <label htmlFor="organization-description">{translate('description')}</label>
               <textarea
+                disabled={this.state.loading}
                 id="organization-description"
+                maxLength="256"
                 name="description"
+                onChange={e => this.setState({ description: e.target.value })}
                 rows="3"
-                maxLength="256"
                 value={this.state.description}
-                disabled={this.state.loading}
-                onChange={e => this.setState({ description: e.target.value })}
               />
               <div className="modal-field-description">
                 {translate('organization.description.description')}
@@ -170,22 +171,20 @@ class OrganizationEdit extends React.PureComponent {
             <div className="modal-field">
               <label htmlFor="organization-url">{translate('organization.url')}</label>
               <input
+                disabled={this.state.loading}
                 id="organization-url"
+                maxLength="256"
                 name="url"
+                onChange={e => this.setState({ url: e.target.value })}
                 type="text"
-                maxLength="256"
                 value={this.state.url}
-                disabled={this.state.loading}
-                onChange={e => this.setState({ url: e.target.value })}
               />
               <div className="modal-field-description">
                 {translate('organization.url.description')}
               </div>
             </div>
             <div className="modal-field">
-              <button type="submit" disabled={this.state.loading}>
-                {translate('save')}
-              </button>
+              <SubmitButton disabled={this.state.loading}>{translate('save')}</SubmitButton>
               {this.state.loading && <i className="spinner spacer-left" />}
             </div>
           </form>
index 9c18fd59ac8f571616075fa8bb1b9e8ff35cbe0f..0abba69f78e6f98489fa810ec7251c9c630d3ee5 100644 (file)
@@ -125,12 +125,11 @@ exports[`smoke test 1`] = `
       <div
         className="modal-field"
       >
-        <button
+        <SubmitButton
           disabled={false}
-          type="submit"
         >
           save
-        </button>
+        </SubmitButton>
       </div>
     </form>
   </div>
@@ -277,12 +276,11 @@ exports[`smoke test 2`] = `
       <div
         className="modal-field"
       >
-        <button
+        <SubmitButton
           disabled={false}
-          type="submit"
         >
           save
-        </button>
+        </SubmitButton>
       </div>
     </form>
   </div>
@@ -429,12 +427,11 @@ exports[`smoke test 3`] = `
       <div
         className="modal-field"
       >
-        <button
+        <SubmitButton
           disabled={true}
-          type="submit"
         >
           save
-        </button>
+        </SubmitButton>
         <i
           className="spinner spacer-left"
         />
index e2659f5026f6d8cd45459bfba362c7c0aa9be07c..90e04927efbb3e2054e439d87f60ff6ec2d34e7d 100644 (file)
@@ -23,6 +23,7 @@ import UsersSelectSearch from '../../../users/components/UsersSelectSearch';
 import { searchMembers } from '../../../../api/organizations';
 import Modal from '../../../../components/controls/Modal';
 import { translate } from '../../../../helpers/l10n';
+import { SubmitButton, ResetButtonLink, Button } from '../../../../components/ui/buttons';
 /*:: import type { Organization } from '../../../../store/organizations/duck'; */
 /*:: import type { Member } from '../../../../store/organizationsMembers/actions'; */
 
@@ -79,7 +80,7 @@ export default class AddMemberForm extends React.PureComponent {
   renderModal() {
     const header = translate('users.add');
     return (
-      <Modal key="add-member-modal" contentLabel={header} onRequestClose={this.closeForm}>
+      <Modal contentLabel={header} key="add-member-modal" onRequestClose={this.closeForm}>
         <header className="modal-head">
           <h2>{header}</h2>
         </header>
@@ -89,21 +90,19 @@ export default class AddMemberForm extends React.PureComponent {
               <label>{translate('users.search_description')}</label>
               <UsersSelectSearch
                 autoFocus={true}
-                selectedUser={this.state.selectedMember}
                 excludedUsers={this.props.memberLogins}
-                searchUsers={this.handleSearch}
                 handleValueChange={this.selectedMemberChange}
+                searchUsers={this.handleSearch}
+                selectedUser={this.state.selectedMember}
               />
             </div>
           </div>
           <footer className="modal-foot">
             <div>
-              <button type="submit" disabled={!this.state.selectedMember}>
+              <SubmitButton disabled={!this.state.selectedMember}>
                 {translate('organization.members.add_to_members')}
-              </button>
-              <button type="reset" className="button-link" onClick={this.closeForm}>
-                {translate('cancel')}
-              </button>
+              </SubmitButton>
+              <ResetButtonLink onClick={this.closeForm}>{translate('cancel')}</ResetButtonLink>
             </div>
           </footer>
         </form>
@@ -113,9 +112,9 @@ export default class AddMemberForm extends React.PureComponent {
 
   render() {
     const buttonComponent = (
-      <button key="add-member-button" onClick={this.openForm}>
+      <Button key="add-member-button" onClick={this.openForm}>
         {translate('organization.members.add')}
-      </button>
+      </Button>
     );
     if (this.state.open) {
       return [buttonComponent, this.renderModal()];
index 4339538beb12a3131db601fe564c0f888792ec55..bf15a60e97ab00ab7ec44521964c2c184a4f0dcc 100644 (file)
@@ -24,6 +24,7 @@ import { getUserGroups } from '../../../../api/users';
 import Modal from '../../../../components/controls/Modal';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
 import OrganizationGroupCheckbox from '../OrganizationGroupCheckbox';
+import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons';
 /*:: import type { Member } from '../../../../store/organizationsMembers/actions'; */
 /*:: import type { Organization, OrgGroup } from '../../../../store/organizations/duck'; */
 
@@ -141,10 +142,8 @@ export default class ManageMemberGroupsForm extends React.PureComponent {
           </div>
           <footer className="modal-foot">
             <div>
-              <button type="submit">{translate('save')}</button>
-              <button className="button-link" onClick={this.props.onClose} type="reset">
-                {translate('cancel')}
-              </button>
+              <SubmitButton>{translate('save')}</SubmitButton>
+              <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
             </div>
           </footer>
         </form>
index 343054e6be3d5963ae89cf6347917c43463e5a82..e1b48aa57c8092033effeeda6cc7af9c11bfd99e 100644 (file)
@@ -21,6 +21,7 @@
 import React from 'react';
 import Modal from '../../../../components/controls/Modal';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
+import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons';
 /*:: import type { Member } from '../../../../store/organizationsMembers/actions'; */
 /*:: import type { Organization } from '../../../../store/organizations/duck'; */
 
@@ -58,12 +59,10 @@ export default class RemoveMemberForm extends React.PureComponent {
           </div>
           <footer className="modal-foot">
             <div>
-              <button autoFocus={true} className="button-red" type="submit">
+              <SubmitButton autoFocus={true} className="button-red">
                 {translate('remove')}
-              </button>
-              <button className="button-link" onClick={this.props.onClose} type="reset">
-                {translate('cancel')}
-              </button>
+              </SubmitButton>
+              <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
             </div>
           </footer>
         </form>
index 9068270a83c374715e39eb07a0685f5c3bad891a..b2a32edcbef43a0d10be45f6cbdc36055bf1e30c 100644 (file)
@@ -27,7 +27,7 @@ jest.mock('react-dom');
 const memberLogins = ['admin'];
 
 it('should render and open the modal', () => {
-  const wrapper = shallow(<AddMemberForm memberLogins={memberLogins} addMember={jest.fn()} />);
+  const wrapper = shallow(<AddMemberForm addMember={jest.fn()} memberLogins={memberLogins} />);
   expect(wrapper).toMatchSnapshot();
   wrapper.setState({ open: true });
 
@@ -36,8 +36,8 @@ it('should render and open the modal', () => {
 });
 
 it('should correctly handle user interactions', () => {
-  const wrapper = shallow(<AddMemberForm memberLogins={memberLogins} addMember={jest.fn()} />);
-  click(wrapper.find('button'));
+  const wrapper = shallow(<AddMemberForm addMember={jest.fn()} memberLogins={memberLogins} />);
+  click(wrapper.find('Button'));
   expect(wrapper.state('open')).toBeTruthy();
   wrapper.instance().closeForm();
   expect(wrapper.state('open')).toBeFalsy();
index ac98116480433d163a85232fc9e53545eb0254ac..643417eae4ada4c4e8fb3baade6c4c0e6dd0ce0b 100644 (file)
@@ -1,21 +1,21 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should render and open the modal 1`] = `
-<button
+<Button
   key="add-member-button"
   onClick={[Function]}
 >
   organization.members.add
-</button>
+</Button>
 `;
 
 exports[`should render and open the modal 2`] = `
 Array [
-  <button
+  <Button
     onClick={[Function]}
   >
     organization.members.add
-  </button>,
+  </Button>,
   <Modal
     contentLabel="users.add"
     onRequestClose={[Function]}
@@ -56,19 +56,16 @@ Array [
         className="modal-foot"
       >
         <div>
-          <button
+          <SubmitButton
             disabled={true}
-            type="submit"
           >
             organization.members.add_to_members
-          </button>
-          <button
-            className="button-link"
+          </SubmitButton>
+          <ResetButtonLink
             onClick={[Function]}
-            type="reset"
           >
             cancel
-          </button>
+          </ResetButtonLink>
         </div>
       </footer>
     </form>
index 2eea5b660856193f3f7f744f646882535f2d57bc..43f0bcecb5a6788d00c3b5a6d44d24a235356292 100644 (file)
@@ -81,17 +81,12 @@ exports[`should render 1`] = `
       className="modal-foot"
     >
       <div>
-        <button
-          type="submit"
-        >
+        <SubmitButton>
           save
-        </button>
-        <button
-          className="button-link"
-          type="reset"
-        >
+        </SubmitButton>
+        <ResetButtonLink>
           cancel
-        </button>
+        </ResetButtonLink>
       </div>
     </footer>
   </form>
index eb235ae0e4ab63cb9ace709bd19a6c25789bd77d..ef1c3bfb9f17329b2c83d9f47aed2c4d83f5dfb7 100644 (file)
@@ -24,19 +24,15 @@ exports[`should render  1`] = `
       className="modal-foot"
     >
       <div>
-        <button
+        <SubmitButton
           autoFocus={true}
           className="button-red"
-          type="submit"
         >
           remove
-        </button>
-        <button
-          className="button-link"
-          type="reset"
-        >
+        </SubmitButton>
+        <ResetButtonLink>
           cancel
-        </button>
+        </ResetButtonLink>
       </div>
     </footer>
   </form>
index 032d7435a0d370b7f0620e37e28c8868fc7b0e38..a62f07d30f540b139780e889cb48dcd2a6103353 100644 (file)
@@ -21,6 +21,7 @@
 import React from 'react';
 import Modal from '../../../../components/controls/Modal';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
+import { Button, ResetButtonLink } from '../../../../components/ui/buttons';
 
 /*::
 type Props = {
@@ -36,13 +37,7 @@ type Props = {
 export default class PublicProjectDisclaimer extends React.PureComponent {
   /*:: props: Props; */
 
-  handleCancelClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  handleConfirmClick = (event /*: Event */) => {
-    event.preventDefault();
+  handleConfirmClick = () => {
     this.props.onConfirm();
     this.props.onClose();
   };
@@ -66,12 +61,10 @@ export default class PublicProjectDisclaimer extends React.PureComponent {
         </div>
 
         <footer className="modal-foot">
-          <button id="confirm-turn-to-public" onClick={this.handleConfirmClick}>
+          <Button id="confirm-turn-to-public" onClick={this.handleConfirmClick}>
             {translate('projects_role.turn_project_to_public', qualifier)}
-          </button>
-          <a href="#" onClick={this.handleCancelClick}>
-            {translate('cancel')}
-          </a>
+          </Button>
+          <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
         </footer>
       </Modal>
     );
index d2ae453f4ed9ab7f4d23a9a8f53710febb4cef70..5fe6e3a2d5ae3e7cf601d30dbaa16bf7a86f820a 100644 (file)
@@ -22,6 +22,7 @@ import PropTypes from 'prop-types';
 import { deleteProject, deletePortfolio } from '../../../api/components';
 import Modal from '../../../components/controls/Modal';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { Button, SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
 
 export default class Form extends React.PureComponent {
   static propTypes = {
@@ -42,8 +43,7 @@ export default class Form extends React.PureComponent {
     this.mounted = false;
   }
 
-  handleDeleteClick = event => {
-    event.preventDefault();
+  handleDeleteClick = () => {
     this.setState({ modalOpen: true });
   };
 
@@ -65,19 +65,14 @@ export default class Form extends React.PureComponent {
       .catch(this.stopLoading);
   };
 
-  handleCloseClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.closeModal();
-  };
-
   render() {
     const { component } = this.props;
 
     return (
       <div>
-        <button id="delete-project" className="button-red" onClick={this.handleDeleteClick}>
+        <Button className="button-red" id="delete-project" onClick={this.handleDeleteClick}>
           {translate('delete')}
-        </button>
+        </Button>
 
         {this.state.modalOpen && (
           <Modal contentLabel="project deletion" onRequestClose={this.closeModal}>
@@ -94,15 +89,15 @@ export default class Form extends React.PureComponent {
               </div>
               <div className="modal-foot">
                 {this.state.loading && <i className="js-modal-spinner spinner spacer-right" />}
-                <button
-                  id="delete-project-confirm"
+                <SubmitButton
                   className="button-red"
-                  disabled={this.state.loading}>
+                  disabled={this.state.loading}
+                  id="delete-project-confirm">
                   {translate('delete')}
-                </button>
-                <a href="#" className="js-modal-close" onClick={this.handleCloseClick}>
+                </SubmitButton>
+                <ResetButtonLink className="js-modal-close" onClick={this.closeModal}>
                   {translate('cancel')}
-                </a>
+                </ResetButtonLink>
               </div>
             </form>
           </Modal>
index 225772031b64fcae9c745bc0b4470f4ca09799ae..2bd1cc8960e49af8e6b8c5c27eb6ef9e3cd9175c 100644 (file)
 import React from 'react';
 import PropTypes from 'prop-types';
 import { translate } from '../../../helpers/l10n';
+import { SubmitButton } from '../../../components/ui/buttons';
 
 export default class BulkUpdateForm extends React.PureComponent {
   static propTypes = {
     onSubmit: PropTypes.func.isRequired
   };
 
-  handleSubmit(e) {
+  handleSubmit = e => {
     e.preventDefault();
-    this.refs.submit.blur();
 
     const replace = this.refs.replace.value;
     const by = this.refs.by.value;
 
     this.props.onSubmit(replace, by);
-  }
+  };
 
   render() {
     return (
-      <form onSubmit={this.handleSubmit.bind(this)}>
+      <form onSubmit={this.handleSubmit}>
         <div className="modal-field">
           <label htmlFor="bulk-update-replace">{translate('update_key.replace')}</label>
           <input
-            ref="replace"
             id="bulk-update-replace"
             name="replace"
-            type="text"
             placeholder={translate('update_key.replace_example')}
+            ref="replace"
             required={true}
+            type="text"
           />
         </div>
 
         <div className="modal-field">
           <label htmlFor="bulk-update-by">{translate('update_key.by')}</label>
           <input
-            ref="by"
             id="bulk-update-by"
             name="by"
-            type="text"
             placeholder={translate('update_key.by_example')}
+            ref="by"
             required={true}
+            type="text"
           />
-          <button ref="submit" id="bulk-update-see-results" className="big-spacer-left">
+          <SubmitButton className="big-spacer-left" id="bulk-update-see-results">
             {translate('update_key.see_results')}
-          </button>
+          </SubmitButton>
         </div>
       </form>
     );
index 368a6c4c91a9017dfbaf881595fc678a13ae39b0..e5f49173d6934eb9073af7e9552c76105673fe9a 100644 (file)
@@ -21,6 +21,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { some } from 'lodash';
 import { translateWithParameters, translate } from '../../../helpers/l10n';
+import { Button } from '../../../components/ui/buttons';
 
 export default class BulkUpdateResults extends React.PureComponent {
   static propTypes = {
@@ -28,12 +29,6 @@ export default class BulkUpdateResults extends React.PureComponent {
     onConfirm: PropTypes.func.isRequired
   };
 
-  handleConfirm(e) {
-    e.preventDefault();
-    e.target.blur();
-    this.props.onConfirm();
-  }
-
   render() {
     const { results, replace, by } = this.props;
     const isEmpty = results.length === 0;
@@ -41,15 +36,15 @@ export default class BulkUpdateResults extends React.PureComponent {
     const canUpdate = !isEmpty && !hasDuplications;
 
     return (
-      <div id="bulk-update-simulation" className="big-spacer-top">
+      <div className="big-spacer-top" id="bulk-update-simulation">
         {isEmpty && (
-          <div id="bulk-update-nothing" className="spacer-bottom">
+          <div className="spacer-bottom" id="bulk-update-nothing">
             {translateWithParameters('update_key.no_key_to_update', replace)}
           </div>
         )}
 
         {hasDuplications && (
-          <div id="bulk-update-duplicate" className="spacer-bottom">
+          <div className="spacer-bottom" id="bulk-update-duplicate">
             {translateWithParameters('update_key.cant_update_because_duplicate_keys', replace, by)}
           </div>
         )}
@@ -61,7 +56,7 @@ export default class BulkUpdateResults extends React.PureComponent {
         )}
 
         {!isEmpty && (
-          <table id="bulk-update-results" className="data zebra zebra-hover">
+          <table className="data zebra zebra-hover" id="bulk-update-results">
             <thead>
               <tr>
                 <th>{translate('update_key.old_key')}</th>
@@ -70,7 +65,7 @@ export default class BulkUpdateResults extends React.PureComponent {
             </thead>
             <tbody>
               {results.map(result => (
-                <tr key={result.key} data-key={result.key}>
+                <tr data-key={result.key} key={result.key}>
                   <td className="js-old-key">{result.key}</td>
                   <td className="js-new-key">
                     {result.duplicate && (
@@ -88,9 +83,9 @@ export default class BulkUpdateResults extends React.PureComponent {
 
         <div className="big-spacer-top">
           {canUpdate && (
-            <button id="bulk-update-confirm" onClick={this.handleConfirm.bind(this)}>
+            <Button id="bulk-update-confirm" onClick={this.props.onConfirm}>
               {translate('update_verb')}
-            </button>
+            </Button>
           )}
         </div>
       </div>
index 1cb10269e5d9984f34ba71fc81f6ea191e581e48..8c40fdb0a7e4491d13629f68ea38e5bc97ac7f2c 100644 (file)
@@ -21,6 +21,7 @@
 import React from 'react';
 import DateRangeInput from '../../../components/controls/DateRangeInput';
 import { translate } from '../../../helpers/l10n';
+import { Button } from '../../../components/ui/buttons';
 /*:: import type { RawQuery } from '../../../helpers/query'; */
 
 /*::
@@ -49,12 +50,12 @@ export default class ProjectActivityDateInput extends React.PureComponent {
           onChange={this.handleChange}
           value={{ from: this.props.from, to: this.props.to }}
         />
-        <button
+        <Button
           className="spacer-left"
-          onClick={this.handleResetClick}
-          disabled={this.props.from == null && this.props.to == null}>
+          disabled={this.props.from == null && this.props.to == null}
+          onClick={this.handleResetClick}>
           {translate('project_activity.reset_dates')}
-        </button>
+        </Button>
       </div>
     );
   }
index 3406b3b0c76274cf97ff1d15f637e9aed05ee214..d10a64ba95cf09c97be700ad19d4f9615fb3ab92 100644 (file)
@@ -11,12 +11,12 @@ exports[`should render correctly the date inputs 1`] = `
       }
     }
   />
-  <button
+  <Button
     className="spacer-left"
     disabled={false}
     onClick={[Function]}
   >
     project_activity.reset_dates
-  </button>
+  </Button>
 </div>
 `;
index ded7c2b733c157730127681b1852e3406f4808b6..e42126813e2b66404c9604c13d1257f220068c47 100644 (file)
@@ -21,6 +21,7 @@
 import React from 'react';
 import Modal from '../../../../components/controls/Modal';
 import { translate } from '../../../../helpers/l10n';
+import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons';
 /*:: import type { Analysis } from '../../types'; */
 
 /*::
@@ -102,10 +103,10 @@ export default class AddEventForm extends React.PureComponent {
               <i className="spinner" />
             ) : (
               <div>
-                <button type="submit">{translate('save')}</button>
-                <button className="button-link" onClick={this.props.onClose} type="reset">
+                <SubmitButton>{translate('save')}</SubmitButton>
+                <ResetButtonLink onClick={this.props.onClose}>
                   {translate('cancel')}
-                </button>
+                </ResetButtonLink>
               </div>
             )}
           </footer>
index 1de35bda7a6eabdffc13bca13458deccc93bf9aa..25465a67e9b86dd1bb66e1163df95966f8f11480 100644 (file)
@@ -21,6 +21,7 @@
 import React from 'react';
 import Modal from '../../../../components/controls/Modal';
 import { translate } from '../../../../helpers/l10n';
+import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons';
 /*:: import type { Event } from '../../types'; */
 
 /*::
@@ -107,11 +108,11 @@ export default class ChangeEventForm extends React.PureComponent {
             <div className="modal-field">
               <label>{translate('name')}</label>
               <input
-                value={this.state.name}
                 autoFocus={true}
                 disabled={this.state.processing}
-                type="text"
                 onChange={this.changeInput}
+                type="text"
+                value={this.state.name}
               />
             </div>
           </div>
@@ -121,10 +122,8 @@ export default class ChangeEventForm extends React.PureComponent {
               <i className="spinner" />
             ) : (
               <div>
-                <button type="submit">{translate('change_verb')}</button>
-                <button type="reset" className="button-link" onClick={this.closeForm}>
-                  {translate('cancel')}
-                </button>
+                <SubmitButton>{translate('change_verb')}</SubmitButton>
+                <ResetButtonLink onClick={this.closeForm}>{translate('cancel')}</ResetButtonLink>
               </div>
             )}
           </footer>
index e813985139acd1e6449463cd05ce0bb6fbe8b331..e0e5dd70d48f13104a7f452df3022d6591cb4847 100644 (file)
@@ -21,6 +21,7 @@
 import React from 'react';
 import Modal from '../../../../components/controls/Modal';
 import { translate } from '../../../../helpers/l10n';
+import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons';
 /*:: import type { Analysis } from '../../types'; */
 
 /*::
@@ -82,12 +83,12 @@ export default class RemoveAnalysisForm extends React.PureComponent {
               <i className="spinner" />
             ) : (
               <div>
-                <button autoFocus={true} className="button-red" type="submit">
+                <SubmitButton autoFocus={true} className="button-red">
                   {translate('delete')}
-                </button>
-                <button className="button-link" onClick={this.props.onClose} type="reset">
+                </SubmitButton>
+                <ResetButtonLink onClick={this.props.onClose}>
                   {translate('cancel')}
-                </button>
+                </ResetButtonLink>
               </div>
             )}
           </footer>
index 346c50b9ee5ec21ee70b7259fed37281dae4ff22..5b5db9eb199155695220d2221974d858a12c48b8 100644 (file)
@@ -21,6 +21,7 @@
 import React from 'react';
 import Modal from '../../../../components/controls/Modal';
 import { translate } from '../../../../helpers/l10n';
+import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons';
 /*:: import type { Event } from '../../types'; */
 
 /*::
@@ -96,12 +97,10 @@ export default class RemoveEventForm extends React.PureComponent {
               <i className="spinner" />
             ) : (
               <div>
-                <button type="submit" className="button-red" autoFocus={true}>
+                <SubmitButton autoFocus={true} className="button-red">
                   {translate('delete')}
-                </button>
-                <button type="reset" className="button-link" onClick={this.closeForm}>
-                  {translate('cancel')}
-                </button>
+                </SubmitButton>
+                <ResetButtonLink onClick={this.closeForm}>{translate('cancel')}</ResetButtonLink>
               </div>
             )}
           </footer>
index f45f0d984d915bcea9ffbff4b4cc4e25f45ad39c..2f2df1482962c6bf9a44c2ac242def9dc90edf65 100644 (file)
@@ -21,7 +21,7 @@ import * as React from 'react';
 import Modal from '../../../components/controls/Modal';
 import { isEmptyValue, getDefaultValue, getSettingValue } from '../utils';
 import { translate } from '../../../helpers/l10n';
-import { Button } from '../../../components/ui/buttons';
+import { Button, ResetButtonLink, SubmitButton } from '../../../components/ui/buttons';
 import { SettingValue, Definition } from '../../../api/settings';
 
 type Props = {
@@ -65,10 +65,8 @@ export default class DefinitionActions extends React.PureComponent<Props, State>
             <p>{translate('settings.reset_confirm.description')}</p>
           </div>
           <footer className="modal-foot">
-            <button className="button-red">{translate('reset_verb')}</button>
-            <button className="button-link" onClick={this.handleClose} type="reset">
-              {translate('cancel')}
-            </button>
+            <SubmitButton className="button-red">{translate('reset_verb')}</SubmitButton>
+            <ResetButtonLink onClick={this.handleClose}>{translate('cancel')}</ResetButtonLink>
           </footer>
         </form>
       </Modal>
index 0633d0cfbb9379aae8d347c34a18cc97e3ab95be..83b624c8e5f4c73fa528010b7d25b91c42c7c6f6 100644 (file)
@@ -23,6 +23,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { sendTestEmail } from '../../../api/settings';
 import { parseError } from '../../../helpers/request';
 import { getCurrentUser } from '../../../store/rootReducer';
+import { SubmitButton } from '../../../components/ui/buttons';
 
 class EmailForm extends React.PureComponent {
   constructor(props) {
@@ -37,22 +38,22 @@ class EmailForm extends React.PureComponent {
     };
   }
 
-  handleFormSubmit(e) {
-    e.preventDefault();
+  handleFormSubmit = event => {
+    event.preventDefault();
     this.setState({ success: false, error: null, loading: true });
     const { recipient, subject, message } = this.state;
     sendTestEmail(recipient, subject, message).then(
       () => this.setState({ success: true, loading: false }),
       error => parseError(error).then(message => this.setState({ error: message, loading: false }))
     );
-  }
+  };
 
   render() {
     return (
       <div className="huge-spacer-top">
         <h3 className="spacer-bottom">{translate('email_configuration.test.title')}</h3>
 
-        <form style={{ marginLeft: 201 }} onSubmit={e => this.handleFormSubmit(e)}>
+        <form onSubmit={this.handleFormSubmit} style={{ marginLeft: 201 }}>
           {this.state.success && (
             <div className="modal-field">
               <div className="alert alert-success">
@@ -77,12 +78,12 @@ class EmailForm extends React.PureComponent {
             </label>
             <input
               className="settings-large-input"
+              disabled={this.state.loading}
               id="test-email-to"
-              type="email"
+              onChange={e => this.setState({ recipient: e.target.value })}
               required={true}
+              type="email"
               value={this.state.recipient}
-              disabled={this.state.loading}
-              onChange={e => this.setState({ recipient: e.target.value })}
             />
           </div>
           <div className="modal-field">
@@ -91,11 +92,11 @@ class EmailForm extends React.PureComponent {
             </label>
             <input
               className="settings-large-input"
+              disabled={this.state.loading}
               id="test-email-subject"
+              onChange={e => this.setState({ subject: e.target.value })}
               type="text"
               value={this.state.subject}
-              disabled={this.state.loading}
-              onChange={e => this.setState({ subject: e.target.value })}
             />
           </div>
           <div className="modal-field">
@@ -105,20 +106,20 @@ class EmailForm extends React.PureComponent {
             </label>
             <textarea
               className="settings-large-input"
+              disabled={this.state.loading}
               id="test-email-title"
+              onChange={e => this.setState({ message: e.target.value })}
               required={true}
               rows="5"
               value={this.state.message}
-              disabled={this.state.loading}
-              onChange={e => this.setState({ message: e.target.value })}
             />
           </div>
 
           <div className="modal-field">
             {this.state.loading && <i className="spacer-right spinner" />}
-            <button disabled={this.state.loading}>
+            <SubmitButton disabled={this.state.loading}>
               {translate('email_configuration.test.send')}
-            </button>
+            </SubmitButton>
           </div>
         </form>
       </div>
index b2ed1582adab9dc9a6e191e218b0af43ca6812d7..e045d181f13c12e40a0911d8fc85938ea87e36d8 100644 (file)
@@ -20,6 +20,7 @@
 import React from 'react';
 import { translate } from '../../../../helpers/l10n';
 import { defaultInputPropTypes } from '../../propTypes';
+import { Button } from '../../../../components/ui/buttons';
 
 export default class InputForPassword extends React.PureComponent {
   static propTypes = defaultInputPropTypes;
@@ -40,11 +41,9 @@ export default class InputForPassword extends React.PureComponent {
     this.setState({ changing: true, value: e.target.value });
   }
 
-  handleChangeClick(e) {
-    e.preventDefault();
-    e.target.blur();
+  handleChangeClick = () => {
     this.setState({ changing: true });
-  }
+  };
 
   renderInput() {
     return (
@@ -73,7 +72,7 @@ export default class InputForPassword extends React.PureComponent {
     return (
       <div>
         <i className="big-spacer-right icon-lock icon-gray" />
-        <button onClick={e => this.handleChangeClick(e)}>{translate('change_verb')}</button>
+        <Button onClick={this.handleChangeClick}>{translate('change_verb')}</Button>
       </div>
     );
   }
index b30d3db8cc99e388f354569301ce889c6ca84b6a..935a500e979f4224f0278f002f15a4d0132a83e3 100644 (file)
@@ -25,7 +25,7 @@ import { click, change, submit } from '../../../../../helpers/testUtils';
 it('should render lock icon, but no form', () => {
   const onChange = jest.fn();
   const input = shallow(
-    <InputForPassword name="foo" value="bar" isDefault={false} onChange={onChange} />
+    <InputForPassword isDefault={false} name="foo" onChange={onChange} value="bar" />
   );
   expect(input.find('.icon-lock').length).toBe(1);
   expect(input.find('form').length).toBe(0);
@@ -34,9 +34,9 @@ it('should render lock icon, but no form', () => {
 it('should open form', () => {
   const onChange = jest.fn();
   const input = shallow(
-    <InputForPassword name="foo" value="bar" isDefault={false} onChange={onChange} />
+    <InputForPassword isDefault={false} name="foo" onChange={onChange} value="bar" />
   );
-  const button = input.find('button');
+  const button = input.find('Button');
   expect(button.length).toBe(1);
 
   click(button);
@@ -46,9 +46,9 @@ it('should open form', () => {
 it('should set value', () => {
   const onChange = jest.fn(() => Promise.resolve());
   const input = shallow(
-    <InputForPassword name="foo" value="bar" isDefault={false} onChange={onChange} />
+    <InputForPassword isDefault={false} name="foo" onChange={onChange} value="bar" />
   );
-  click(input.find('button'));
+  click(input.find('Button'));
   change(input.find('.js-password-input'), 'secret');
   submit(input.find('form'));
   expect(onChange).toBeCalledWith('secret');
index 0a7b75c580a7adf9adcecab962fa0416f6c50a3c..fef8d6876093cd694dc27b4bee5da464b61a8596 100644 (file)
@@ -20,6 +20,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { translate } from '../../../helpers/l10n';
+import { SubmitButton } from '../../../components/ui/buttons';
 
 export default class EncryptionForm extends React.PureComponent {
   static propTypes = {
@@ -46,8 +47,8 @@ export default class EncryptionForm extends React.PureComponent {
         <div className="spacer-bottom">{translate('encryption.form_intro')}</div>
 
         <form
-          id="encryption-form"
           className="big-spacer-bottom"
+          id="encryption-form"
           onSubmit={e => this.handleEncrypt(e)}>
           <textarea
             autoFocus={true}
@@ -59,7 +60,7 @@ export default class EncryptionForm extends React.PureComponent {
             value={this.state.value}
           />
           <div className="spacer-top">
-            <button>{translate('encryption.encrypt')}</button>
+            <SubmitButton>{translate('encryption.encrypt')}</SubmitButton>
           </div>
         </form>
 
@@ -68,10 +69,10 @@ export default class EncryptionForm extends React.PureComponent {
             {translate('encryption.encrypted_value')}
             {': '}
             <input
-              id="encrypted-value"
               className="input-clear input-code input-super-large"
-              type="text"
+              id="encrypted-value"
               readOnly={true}
+              type="text"
               value={this.props.encryptedValue}
             />
           </div>
@@ -83,7 +84,7 @@ export default class EncryptionForm extends React.PureComponent {
             dangerouslySetInnerHTML={{ __html: translate('encryption.form_note') }}
           />
           <form id="encryption-new-key-form" onSubmit={e => this.handleGenerateNewKey(e)}>
-            <button>{translate('encryption.generate_new_secret_key')}</button>
+            <SubmitButton>{translate('encryption.generate_new_secret_key')}</SubmitButton>
           </form>
         </div>
       </div>
index c818cecbcd6836e55e7dde6dd4a660b59a72263f..bec7d661859aa84ee17e502a5af83b0fe704ce17 100644 (file)
@@ -20,6 +20,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { translate } from '../../../helpers/l10n';
+import { SubmitButton } from '../../../components/ui/buttons';
 
 export default class GenerateSecretKeyForm extends React.PureComponent {
   static propTypes = {
@@ -27,10 +28,10 @@ export default class GenerateSecretKeyForm extends React.PureComponent {
     generateSecretKey: PropTypes.func.isRequired
   };
 
-  handleSubmit(e) {
+  handleSubmit = e => {
     e.preventDefault();
     this.props.generateSecretKey();
-  }
+  };
 
   render() {
     return (
@@ -40,10 +41,10 @@ export default class GenerateSecretKeyForm extends React.PureComponent {
             <div className="big-spacer-bottom">
               <h3 className="spacer-bottom">{translate('encryption.secret_key')}</h3>
               <input
-                id="secret-key"
                 className="input-large"
-                type="text"
+                id="secret-key"
                 readOnly={true}
+                type="text"
                 value={this.props.secretKey}
               />
             </div>
@@ -62,8 +63,8 @@ export default class GenerateSecretKeyForm extends React.PureComponent {
               dangerouslySetInnerHTML={{ __html: translate('ecryption.secret_key_description') }}
             />
 
-            <form id="generate-secret-key-form" onSubmit={e => this.handleSubmit(e)}>
-              <button>{translate('encryption.generate_secret_key')}s</button>
+            <form id="generate-secret-key-form" onSubmit={this.handleSubmit}>
+              <SubmitButton>{translate('encryption.generate_secret_key')}</SubmitButton>
             </form>
           </div>
         )}
index 618608b401e1e979700ca0a313c37f399931a832..11be750a41b77369828e5d5936cf6c383c210fba 100644 (file)
@@ -26,7 +26,7 @@ import {
   getOrganization
 } from '../../../api/organizations';
 import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
-import { DeleteButton } from '../../../components/ui/buttons';
+import { DeleteButton, SubmitButton } from '../../../components/ui/buttons';
 import { translate } from '../../../helpers/l10n';
 
 /*::
@@ -153,9 +153,9 @@ export default class NewOrganizationForm extends React.PureComponent {
         {loading ? (
           <i className="spinner text-middle" />
         ) : (
-          <button className="text-middle" disabled={!valid} type="submit">
+          <SubmitButton className="text-middle" disabled={!valid}>
             {translate('create')}
-          </button>
+          </SubmitButton>
         )}
         {!unique && (
           <span className="big-spacer-left text-danger text-middle">
index 0e7a96a5b466803bdcee7d4b5a5fef5a0dad887e..cde4629a0a80c983a6d54b6db0b7106f6271eab8 100644 (file)
@@ -20,7 +20,7 @@
 // @flow
 import React from 'react';
 import { createProject, deleteProject } from '../../../api/components';
-import { DeleteButton } from '../../../components/ui/buttons';
+import { DeleteButton, SubmitButton } from '../../../components/ui/buttons';
 import { translate } from '../../../helpers/l10n';
 
 /*::
@@ -123,8 +123,8 @@ export default class NewProjectForm extends React.PureComponent {
         <input
           autoFocus={true}
           className="input-large spacer-right text-middle"
-          minLength={1}
           maxLength={400}
+          minLength={1}
           onChange={this.handleProjectKeyChange}
           required={true}
           type="text"
@@ -133,9 +133,9 @@ export default class NewProjectForm extends React.PureComponent {
         {loading ? (
           <i className="spinner text-middle" />
         ) : (
-          <button className="text-middle" disabled={!valid}>
+          <SubmitButton className="text-middle" disabled={!valid}>
             {translate('Done')}
-          </button>
+          </SubmitButton>
         )}
         <div className="note spacer-top abs-width-300">
           {translate('onboarding.project_key_requirement')}
index e7a5f9b938f636c5ffbaf84fbe551d256657c6fc..af4bc9d654789260c2f9fa1c8b8254650e5c6a09 100644 (file)
@@ -27,6 +27,7 @@ import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessI
 import { getOrganizations } from '../../../api/organizations';
 import Select from '../../../components/controls/Select';
 import { translate } from '../../../helpers/l10n';
+import { Button } from '../../../components/ui/buttons';
 
 /*::
 type Props = {|
@@ -137,8 +138,7 @@ export default class OrganizationStep extends React.PureComponent {
     this.setState({ existingOrganization: value });
   };
 
-  handleContinueClick = (event /*: Event */) => {
-    event.preventDefault();
+  handleContinueClick = () => {
     const organization = this.getSelectedOrganization();
     if (organization) {
       this.props.onContinue(organization);
@@ -234,9 +234,9 @@ export default class OrganizationStep extends React.PureComponent {
         {this.getSelectedOrganization() != null &&
           !this.state.loading && (
             <div className="big-spacer-top">
-              <button className="js-continue" onClick={this.handleContinueClick} type="button">
+              <Button className="js-continue" onClick={this.handleContinueClick}>
                 {translate('continue')}
-              </button>
+              </Button>
             </div>
           )}
       </div>
index d29500382e02c165c744770342e7f8c9004caa36..6ef86cd7b3a7be440894ae5778af5da0bbfbd274 100644 (file)
@@ -24,7 +24,7 @@ import Step from './Step';
 import { getTokens, generateToken, revokeToken } from '../../../api/user-tokens';
 import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon';
 import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon';
-import { DeleteButton } from '../../../components/ui/buttons';
+import { DeleteButton, SubmitButton, Button } from '../../../components/ui/buttons';
 import { translate } from '../../../helpers/l10n';
 
 /*::
@@ -130,8 +130,7 @@ export default class TokenStep extends React.PureComponent {
     }
   };
 
-  handleContinueClick = (event /*: Event */) => {
-    event.preventDefault();
+  handleContinueClick = () => {
     const token = this.getToken();
     if (token) {
       this.props.onContinue(token);
@@ -184,9 +183,9 @@ export default class TokenStep extends React.PureComponent {
             {this.state.loading ? (
               <i className="spinner text-middle" />
             ) : (
-              <button className="text-middle" disabled={!this.state.tokenName} type="submit">
+              <SubmitButton className="text-middle" disabled={!this.state.tokenName}>
                 {translate('onboarding.token.generate')}
-              </button>
+              </SubmitButton>
             )}
           </form>
         </div>
@@ -263,9 +262,9 @@ export default class TokenStep extends React.PureComponent {
 
         {this.canContinue() && (
           <div className="big-spacer-top">
-            <button className="js-continue" onClick={this.handleContinueClick} type="button">
+            <Button className="js-continue" onClick={this.handleContinueClick}>
               {translate('continue')}
-            </button>
+            </Button>
           </div>
         )}
       </div>
index 01add62b5391b533513b0e358e66cb207b77539c..01a5b711a2a6877602f5e0f19ab420eb2b204a6a 100644 (file)
@@ -78,7 +78,7 @@ it('works with existing organization', async () => {
     .first()
     .prop('onChange')({ value: 'another' });
   wrapper.update();
-  click(wrapper.find('.js-continue'));
+  click(wrapper.find('[className="js-continue"]'));
   expect(onContinue).toBeCalledWith('another');
 });
 
@@ -98,6 +98,6 @@ it('works with new organization', async () => {
   click(wrapper.find('.js-new'));
   wrapper.find('NewOrganizationForm').prop('onDone')('new');
   wrapper.update();
-  click(wrapper.find('.js-continue'));
+  click(wrapper.find('[className="js-continue"]'));
   expect(onContinue).toBeCalledWith('new');
 });
index d336accbca714859c67f2632b2c01e336af8a53c..4f439b440b53a7d16b29ba929461aec3c7d4384a 100644 (file)
@@ -38,9 +38,9 @@ it('generates token', async () => {
     <TokenStep
       currentUser={currentUser}
       finished={false}
-      open={true}
       onContinue={jest.fn()}
       onOpen={jest.fn()}
+      open={true}
       stepNumber={1}
     />
   );
@@ -58,9 +58,9 @@ it('revokes token', async () => {
     <TokenStep
       currentUser={currentUser}
       finished={false}
-      open={true}
       onContinue={jest.fn()}
       onOpen={jest.fn()}
+      open={true}
       stepNumber={1}
     />
   );
@@ -80,15 +80,15 @@ it('continues', async () => {
     <TokenStep
       currentUser={currentUser}
       finished={false}
-      open={true}
       onContinue={onContinue}
       onOpen={jest.fn()}
+      open={true}
       stepNumber={1}
     />
   );
   await new Promise(setImmediate);
   wrapper.setState({ token: 'abcd1234', tokenName: 'my token' });
-  click(wrapper.find('.js-continue'));
+  click(wrapper.find('[className="js-continue"]'));
   expect(onContinue).toBeCalledWith('abcd1234');
 });
 
@@ -98,14 +98,14 @@ it('uses existing token', async () => {
     <TokenStep
       currentUser={currentUser}
       finished={false}
-      open={true}
       onContinue={onContinue}
       onOpen={jest.fn()}
+      open={true}
       stepNumber={1}
     />
   );
   await new Promise(setImmediate);
   wrapper.setState({ existingToken: 'abcd1234', selection: 'use-existing' });
-  click(wrapper.find('.js-continue'));
+  click(wrapper.find('[className="js-continue"]'));
   expect(onContinue).toBeCalledWith('abcd1234');
 });
index 1e75378df8bc86054199d9124f843640bc9cad48..a7f34e6336491e681c6d9193ebee5539ecb0d8f3 100644 (file)
@@ -19,13 +19,26 @@ exports[`creates new organization 1`] = `
       type="text"
       value=""
     />
-    <button
+    <SubmitButton
       className="text-middle"
       disabled={true}
-      type="submit"
     >
-      create
-    </button>
+      <Button
+        className="text-middle"
+        disabled={true}
+        preventDefault={false}
+        type="submit"
+      >
+        <button
+          className="button text-middle"
+          disabled={true}
+          onClick={[Function]}
+          type="submit"
+        >
+          create
+        </button>
+      </Button>
+    </SubmitButton>
     <div
       className="note spacer-top abs-width-300"
     >
@@ -215,13 +228,26 @@ exports[`deletes organization 3`] = `
       type="text"
       value=""
     />
-    <button
+    <SubmitButton
       className="text-middle"
       disabled={true}
-      type="submit"
     >
-      create
-    </button>
+      <Button
+        className="text-middle"
+        disabled={true}
+        preventDefault={false}
+        type="submit"
+      >
+        <button
+          className="button text-middle"
+          disabled={true}
+          onClick={[Function]}
+          type="submit"
+        >
+          create
+        </button>
+      </Button>
+    </SubmitButton>
     <div
       className="note spacer-top abs-width-300"
     >
index a6682fb787017cacf6d00f6246c3a3b6b7f64186..f2c1036adeb257e9ee3cfaa2ca45e7c7b45bf222 100644 (file)
@@ -26,12 +26,26 @@ exports[`creates new project 1`] = `
         type="text"
         value=""
       />
-      <button
+      <SubmitButton
         className="text-middle"
         disabled={true}
       >
-        Done
-      </button>
+        <Button
+          className="text-middle"
+          disabled={true}
+          preventDefault={false}
+          type="submit"
+        >
+          <button
+            className="button text-middle"
+            disabled={true}
+            onClick={[Function]}
+            type="submit"
+          >
+            Done
+          </button>
+        </Button>
+      </SubmitButton>
       <div
         className="note spacer-top abs-width-300"
       >
@@ -264,12 +278,26 @@ exports[`deletes project 3`] = `
         type="text"
         value=""
       />
-      <button
+      <SubmitButton
         className="text-middle"
         disabled={true}
       >
-        Done
-      </button>
+        <Button
+          className="text-middle"
+          disabled={true}
+          preventDefault={false}
+          type="submit"
+        >
+          <button
+            className="button text-middle"
+            disabled={true}
+            onClick={[Function]}
+            type="submit"
+          >
+            Done
+          </button>
+        </Button>
+      </SubmitButton>
       <div
         className="note spacer-top abs-width-300"
       >
index 58074b468a3d9a8add162ba4bab22fb4285e75cf..9b991a92305afc188841ca1261d54516301258a6 100644 (file)
@@ -67,13 +67,26 @@ exports[`generates token 1`] = `
                   type="text"
                   value=""
                 />
-                <button
+                <SubmitButton
                   className="text-middle"
                   disabled={true}
-                  type="submit"
                 >
-                  onboarding.token.generate
-                </button>
+                  <Button
+                    className="text-middle"
+                    disabled={true}
+                    preventDefault={false}
+                    type="submit"
+                  >
+                    <button
+                      className="button text-middle"
+                      disabled={true}
+                      onClick={[Function]}
+                      type="submit"
+                    >
+                      onboarding.token.generate
+                    </button>
+                  </Button>
+                </SubmitButton>
               </form>
             </div>
           </div>
@@ -299,13 +312,18 @@ exports[`generates token 3`] = `
         <div
           className="big-spacer-top"
         >
-          <button
+          <Button
             className="js-continue"
             onClick={[Function]}
-            type="button"
           >
-            continue
-          </button>
+            <button
+              className="button js-continue"
+              onClick={[Function]}
+              type="button"
+            >
+              continue
+            </button>
+          </Button>
         </div>
       </div>
     </div>
@@ -410,13 +428,18 @@ exports[`revokes token 1`] = `
         <div
           className="big-spacer-top"
         >
-          <button
+          <Button
             className="js-continue"
             onClick={[Function]}
-            type="button"
           >
-            continue
-          </button>
+            <button
+              className="button js-continue"
+              onClick={[Function]}
+              type="button"
+            >
+              continue
+            </button>
+          </Button>
         </div>
       </div>
     </div>
@@ -490,13 +513,18 @@ exports[`revokes token 2`] = `
         <div
           className="big-spacer-top"
         >
-          <button
+          <Button
             className="js-continue"
             onClick={[Function]}
-            type="button"
           >
-            continue
-          </button>
+            <button
+              className="button js-continue"
+              onClick={[Function]}
+              type="button"
+            >
+              continue
+            </button>
+          </Button>
         </div>
       </div>
     </div>
@@ -571,13 +599,26 @@ exports[`revokes token 3`] = `
                   type="text"
                   value=""
                 />
-                <button
+                <SubmitButton
                   className="text-middle"
                   disabled={true}
-                  type="submit"
                 >
-                  onboarding.token.generate
-                </button>
+                  <Button
+                    className="text-middle"
+                    disabled={true}
+                    preventDefault={false}
+                    type="submit"
+                  >
+                    <button
+                      className="button text-middle"
+                      disabled={true}
+                      onClick={[Function]}
+                      type="submit"
+                    >
+                      onboarding.token.generate
+                    </button>
+                  </Button>
+                </SubmitButton>
               </form>
             </div>
           </div>
index 6e5df20efcbf563a2f84be8e43118993b1e57b79..0d0782009b7f897b537f3ab446cf9a8b641d99cf 100644 (file)
@@ -21,6 +21,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import classNames from 'classnames';
 import { ERROR, SUCCESS } from '../../store/globalMessages/duck';
+import { Button } from '../ui/buttons';
 
 export default class GlobalMessages extends React.PureComponent {
   static propTypes = {
@@ -40,14 +41,13 @@ export default class GlobalMessages extends React.PureComponent {
       'process-spinner-success': message.level === SUCCESS
     });
     return (
-      <div key={message.id} className={className}>
+      <div className={className} key={message.id}>
         {message.message}
-        <button
+        <Button
           className="process-spinner-close"
-          type="button"
           onClick={() => this.props.closeGlobalMessage(message.id)}>
           <i className="icon-close" />
-        </button>
+        </Button>
       </div>
     );
   };
index b1b18b3a78b01dc6597ed0947831c8b619e92243..4cbabfece09b9448548619b077d3c27ad03d20fa 100644 (file)
@@ -20,6 +20,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import classNames from 'classnames';
+import { Button } from '../ui/buttons';
 import './styles.css';
 
 export default class Toggle extends React.PureComponent {
@@ -29,13 +30,11 @@ export default class Toggle extends React.PureComponent {
     onChange: PropTypes.func
   };
 
-  handleClick(e, value) {
-    e.preventDefault();
-    e.currentTarget.blur();
+  handleClick = value => {
     if (this.props.onChange) {
       this.props.onChange(!value);
     }
-  }
+  };
 
   render() {
     const { value } = this.props;
@@ -44,12 +43,12 @@ export default class Toggle extends React.PureComponent {
     const className = classNames('boolean-toggle', { 'boolean-toggle-on': booleanValue });
 
     return (
-      <button
+      <Button
         className={className}
         name={this.props.name}
-        onClick={e => this.handleClick(e, booleanValue)}>
+        onClick={() => this.handleClick(booleanValue)}>
         <div className="boolean-toggle-handle" />
-      </button>
+      </Button>
     );
   }
 }
index c371c10e7300a1f0b066e3bccee955b227c45805..3245074388a5cbb7c70ab63d7471e0dff471f723 100644 (file)
@@ -23,12 +23,12 @@ import Toggle from '../Toggle';
 import { click } from '../../../helpers/testUtils';
 
 function getSample(props) {
-  return <Toggle value={true} onChange={() => true} {...props} />;
+  return <Toggle onChange={() => true} value={true} {...props} />;
 }
 
 it('should render', () => {
   const Toggle = shallow(getSample());
-  expect(Toggle.is('button')).toBe(true);
+  expect(Toggle.is('Button')).toBe(true);
 });
 
 it('should call onChange', () => {
index a68db8e3879486d08d26156e05c98216a263ff5a..32fb0c19f36f575e964d0bd5bbec63d0a1d470fc 100644 (file)
@@ -81,7 +81,7 @@
   width: 70px;
 }
 
-.boolean-toggle {
+.button.boolean-toggle {
   display: inline-block;
   vertical-align: middle;
   width: 48px;
   transition: all 0.3s ease;
 }
 
-.boolean-toggle:hover {
+.button.boolean-toggle:hover {
   background-color: #fff;
 }
 
-.boolean-toggle:focus {
+.button.boolean-toggle:focus {
   border-color: var(--blue);
   background-color: #f6f6f6;
 }
   transition: transform 0.3s cubic-bezier(0.87, -0.41, 0.19, 1.44), border 0.3s ease;
 }
 
-.boolean-toggle-on {
+.button.boolean-toggle-on {
   border-color: var(--darkBlue);
   background-color: var(--darkBlue);
 }
 
-.boolean-toggle-on:hover {
+.button.boolean-toggle-on:hover {
   background-color: var(--darkBlue);
 }
 
-.boolean-toggle-on:focus {
+.button.boolean-toggle-on:focus {
   background-color: var(--darkBlue);
 }
 
-.boolean-toggle-on .boolean-toggle-handle {
+.button.boolean-toggle-on .boolean-toggle-handle {
   border-color: #f6f6f6;
   transform: translateX(var(--controlHeight));
 }
index 1f8a9cb2ae57f1d1ec2cd9427edd4851e5c9be8b..74bf39609ab1d28f7c1246ce03148b01562b1c3e 100644 (file)
@@ -22,6 +22,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import Tooltip from '../../controls/Tooltip';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { Button } from '../../ui/buttons';
 
 export default class IssueMessage extends React.PureComponent {
   /*:: props: {
@@ -36,8 +37,7 @@ export default class IssueMessage extends React.PureComponent {
     workspace: PropTypes.object.isRequired
   };
 
-  handleClick = (e /*: MouseEvent */) => {
-    e.preventDefault();
+  handleClick = () => {
     this.context.workspace.openRule({
       key: this.props.rule,
       organization: this.props.organization
@@ -48,9 +48,9 @@ export default class IssueMessage extends React.PureComponent {
     return (
       <div className="issue-message">
         {this.props.message}
-        <button
-          className="button-link issue-rule icon-ellipsis-h little-spacer-left"
+        <Button
           aria-label={translate('issue.rule_details')}
+          className="button-link issue-rule icon-ellipsis-h little-spacer-left"
           onClick={this.handleClick}
         />
         {this.props.engine && (
index 9bf92a16d1502e1a271e20819a66c4b1de5b301d..91587572e8845661ca86f8797354ea8368ff3213 100644 (file)
@@ -5,7 +5,7 @@ exports[`should render with the message and a link to open the rule 1`] = `
   className="issue-message"
 >
   Reduce the number of conditional operators (4) used in the expression
-  <button
+  <Button
     aria-label="issue.rule_details"
     className="button-link issue-rule icon-ellipsis-h little-spacer-left"
     onClick={[Function]}
index 3df0420fc49a38350e3d57c5b05d0f5e86fd93fc..ea2ff4b6b13b4ce361bc46826e0051390370b7b3 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+.button {
+  display: inline-block;
+  vertical-align: baseline;
+  height: var(--controlHeight);
+  line-height: calc(var(--controlHeight) - 2px);
+  padding: 0 12px;
+  border: 1px solid var(--darkBlue);
+  border-radius: 2px;
+  box-sizing: border-box;
+  background: transparent;
+  color: var(--darkBlue);
+  font-weight: 600;
+  font-size: var(--smallFontSize);
+  text-align: center;
+  text-decoration: none;
+  cursor: pointer;
+  outline: none;
+  transition: border-color 0.2s ease, box-shadow 0.2s ease;
+}
+
+.button:hover,
+.button.button-active {
+  background: var(--darkBlue);
+  color: #fff;
+}
+
+.button:active {
+  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.button:focus {
+  box-shadow: 0 0 0 3px rgba(35, 106, 151, 0.25);
+}
+
+.button.disabled,
+.button:disabled,
+.button:disabled:hover,
+.button:disabled:active,
+.button:disabled:focus {
+  color: var(--disableGrayText) !important;
+  border-color: var(--disableGrayBorder) !important;
+  background: var(--disableGrayBg) !important;
+  cursor: not-allowed !important;
+  box-shadow: none !important;
+}
+
+.button svg {
+  margin-top: calc((var(--controlHeight) - 16px - 2px) / 2);
+}
+
+/* #region .button-red */
+.button-red {
+  border-color: var(--red);
+  color: var(--red);
+}
+
+.button-red:hover,
+.button-red.active {
+  background: var(--red);
+  color: #fff;
+}
 
-/* use double selector .button-icon.button-icon to increase the specificity */
+.button-red:focus {
+  box-shadow: 0 0 0 3px rgba(212, 51, 63, 0.25);
+}
+/* #endregion */
+
+/* #region .button-success */
+.button-success {
+  border-color: var(--green);
+  color: var(--green);
+}
+
+.button-success:hover,
+.button-success.active {
+  background: var(--green);
+  color: #fff;
+}
+
+.button-success:focus {
+  box-shadow: 0 0 0 3px rgba(0, 170, 0, 0.25);
+}
+/* #endregion */
+
+/* #region .button-grey */
+.button-grey {
+  border-color: var(--gray71);
+  color: var(--secondFontColor);
+}
+
+.button-grey:hover,
+.button-grey.active {
+  background: var(--gray71);
+  color: #ffffff;
+}
+
+.button-grey:focus {
+  box-shadow: 0 0 0 3px rgba(180, 180, 180, 0.25);
+}
+/* #endregion */
+
+/* #region .button-link */
+.button-link {
+  display: inline;
+  height: auto; /* Keep this to not inherit the height from .button */
+  margin: 0;
+  padding: 0;
+  border: none;
+  background: transparent;
+  color: var(--darkBlue);
+  font-weight: 400;
+  font-size: inherit;
+  line-height: inherit;
+  transition: all 0.2s ease;
+}
+
+.button-link svg {
+  margin-top: 0;
+}
+
+.button-link:hover {
+  background: transparent;
+  color: var(--blue);
+}
+
+.button-link:active {
+  box-shadow: none;
+  outline: thin dotted #ccc;
+}
+
+.button-link:disabled,
+.button-link:disabled:hover,
+.button-link:disabled:active,
+.button-link:disabled:focus {
+  color: var(--secondFontColor);
+  background: transparent !important;
+  cursor: default;
+}
+/* #endregion */
+
+.button-small {
+  height: var(--smallControlHeight);
+  line-height: 18px;
+  padding: 0 6px;
+  font-size: 11px;
+}
+
+.button-small > svg {
+  margin-top: 2px;
+}
+
+/* #region .button-group */
+.button-group {
+  display: inline-block;
+  vertical-align: middle;
+  font-size: 0;
+  white-space: nowrap;
+}
+
+.button-group > button,
+.button-group > .button {
+  position: relative;
+  z-index: var(--normalZIndex);
+  display: inline-block;
+  vertical-align: middle;
+  margin: 0;
+  cursor: pointer;
+}
+
+.button-group > .button:hover:not(:disabled),
+.button-group > .button:focus:not(:disabled),
+.button-group > .button:active:not(:disabled),
+.button-group > .button.active:not(:disabled) {
+  z-index: var(--aboveNormalZIndex);
+}
+
+.button-group > .button:disabled {
+  z-index: var(--belowNormalZIndex);
+}
+
+.button-group > .button:not(:first-child) {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.button-group > .button:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.button-group > .button + .button {
+  margin-left: -1px;
+}
+
+.button-group > a:not(.button) {
+  vertical-align: middle;
+  margin: 0 8px;
+  font-size: var(--smallFontSize);
+}
+/* #endregion */
 
-.button-icon.button-icon {
+/* #region .button-icon */
+.button-icon {
   display: inline-flex;
   justify-content: center;
   align-items: center;
   color: inherit;
 }
 
-.button-icon.button-icon.button-small {
+.button-icon.button-small {
   width: var(--smallControlHeight);
   height: var(--smallControlHeight);
   padding: 0;
 }
 
-.button-icon.button-icon.button-small svg {
+.button-icon.button-small svg {
   margin-top: 0;
 }
 
-.button-icon.button-icon.button-tiny {
+.button-icon.button-tiny {
   width: var(--tinyControlHeight);
   height: var(--tinyControlHeight);
   padding: 0;
 }
 
-.button-icon.button-icon.button-tiny svg {
+.button-icon.button-tiny svg {
   margin-top: 0;
 }
 
-.button-icon.button-icon:hover,
-.button-icon.button-icon:focus {
+.button-icon:hover,
+.button-icon:focus {
   background-color: currentColor;
 }
 
-.button-icon.button-icon:hover svg,
-.button-icon.button-icon:focus svg {
+.button-icon:hover svg,
+.button-icon:focus svg {
   color: #fff;
 }
+/* #endregion */