diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-05-14 17:22:53 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-05-14 20:20:49 +0200 |
commit | 2c91ba5af11b8e5955b65d4f0f76ea0e8917afbd (patch) | |
tree | 2bd822b92f062b3117df2f54339a47469517c23b | |
parent | 7fb43549fb85a0f79e61106ab770338d868b033d (diff) | |
download | sonarqube-2c91ba5af11b8e5955b65d4f0f76ea0e8917afbd.tar.gz sonarqube-2c91ba5af11b8e5955b65d4f0f76ea0e8917afbd.zip |
replace native buttons with Button component (#235)
45 files changed, 568 insertions, 574 deletions
diff --git a/server/sonar-web/src/main/js/app/styles/init/forms.css b/server/sonar-web/src/main/js/app/styles/init/forms.css index 98a484ef21e..696151b06bc 100644 --- a/server/sonar-web/src/main/js/app/styles/init/forms.css +++ b/server/sonar-web/src/main/js/app/styles/init/forms.css @@ -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; } diff --git a/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts b/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts index b07d8c1f53b..df9cd6a4b7f 100644 --- a/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts +++ b/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts @@ -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 }; }; diff --git a/server/sonar-web/src/main/js/apps/account/components/Password.js b/server/sonar-web/src/main/js/apps/account/components/Password.js index 3c4b7ce7f1b..00c12a5e67a 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Password.js +++ b/server/sonar-web/src/main/js/apps/account/components/Password.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Search.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Search.js index 11f7de80c0a..e43c4adc54d 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Search.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Search.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js index b1dda0bb9fd..770c9587bac 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap index 9c18fd59ac8..0abba69f78e 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap @@ -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" /> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js index e2659f5026f..90e04927efb 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js @@ -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()]; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js index 4339538beb1..bf15a60e97a 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js index 343054e6be3..e1b48aa57c8 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js index 9068270a83c..b2a32edcbef 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js @@ -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(); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap index ac981164804..643417eae4a 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap @@ -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> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap index 2eea5b66085..43f0bcecb5a 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap @@ -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> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap index eb235ae0e4a..ef1c3bfb9f1 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap @@ -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> diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/PublicProjectDisclaimer.js b/server/sonar-web/src/main/js/apps/permissions/project/components/PublicProjectDisclaimer.js index 032d7435a0d..a62f07d30f5 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/PublicProjectDisclaimer.js +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/PublicProjectDisclaimer.js @@ -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> ); diff --git a/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js b/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js index d2ae453f4ed..5fe6e3a2d5a 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js +++ b/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js index 225772031b6..2bd1cc8960e 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js +++ b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js @@ -20,50 +20,50 @@ 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> ); diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js index 368a6c4c91a..e5f49173d69 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js +++ b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js index 1cb10269e5d..8c40fdb0a7e 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js @@ -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> ); } diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap index 3406b3b0c76..d10a64ba95c 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap @@ -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> `; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js index ded7c2b733c..e42126813e2 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js index 1de35bda7a6..25465a67e9b 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js index e813985139a..e0e5dd70d48 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js index 346c50b9ee5..5b5db9eb199 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx index f45f0d984d9..2f2df148296 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx @@ -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> diff --git a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js index 0633d0cfbb9..83b624c8e5f 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js +++ b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js index b2ed1582ada..e045d181f13 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js @@ -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> ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js index b30d3db8cc9..935a500e979 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js @@ -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'); diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.js b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.js index 0a7b75c580a..fef8d687609 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.js +++ b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.js b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.js index c818cecbcd6..bec7d661859 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.js +++ b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.js @@ -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> )} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js index 618608b401e..11be750a41b 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewOrganizationForm.js @@ -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"> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js index 0e7a96a5b46..cde4629a0a8 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/NewProjectForm.js @@ -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')} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js index e7a5f9b938f..af4bc9d6547 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js index d29500382e0..6ef86cd7b3a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/TokenStep.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js index 01add62b539..01a5b711a2a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js @@ -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'); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js index d336accbca7..4f439b440b5 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js @@ -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'); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap index 1e75378df8b..a7f34e63364 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap @@ -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" > diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap index a6682fb7870..f2c1036adeb 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap @@ -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" > diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap index 58074b468a3..9b991a92305 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap @@ -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> diff --git a/server/sonar-web/src/main/js/components/controls/GlobalMessages.js b/server/sonar-web/src/main/js/components/controls/GlobalMessages.js index 6e5df20efcb..0d0782009b7 100644 --- a/server/sonar-web/src/main/js/components/controls/GlobalMessages.js +++ b/server/sonar-web/src/main/js/components/controls/GlobalMessages.js @@ -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> ); }; diff --git a/server/sonar-web/src/main/js/components/controls/Toggle.js b/server/sonar-web/src/main/js/components/controls/Toggle.js index b1b18b3a78b..4cbabfece09 100644 --- a/server/sonar-web/src/main/js/components/controls/Toggle.js +++ b/server/sonar-web/src/main/js/components/controls/Toggle.js @@ -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> ); } } diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js index c371c10e730..3245074388a 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js +++ b/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js @@ -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', () => { diff --git a/server/sonar-web/src/main/js/components/controls/styles.css b/server/sonar-web/src/main/js/components/controls/styles.css index a68db8e3879..32fb0c19f36 100644 --- a/server/sonar-web/src/main/js/components/controls/styles.css +++ b/server/sonar-web/src/main/js/components/controls/styles.css @@ -81,7 +81,7 @@ width: 70px; } -.boolean-toggle { +.button.boolean-toggle { display: inline-block; vertical-align: middle; width: 48px; @@ -95,11 +95,11 @@ 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; } @@ -114,20 +114,20 @@ 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)); } diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js index 1f8a9cb2ae5..74bf39609ab 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js +++ b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js @@ -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 && ( diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.js.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.js.snap index 9bf92a16d15..91587572e88 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.js.snap +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.js.snap @@ -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]} diff --git a/server/sonar-web/src/main/js/components/ui/buttons.css b/server/sonar-web/src/main/js/components/ui/buttons.css index 3df0420fc49..ea2ff4b6b13 100644 --- a/server/sonar-web/src/main/js/components/ui/buttons.css +++ b/server/sonar-web/src/main/js/components/ui/buttons.css @@ -17,10 +17,209 @@ * 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; @@ -32,32 +231,33 @@ 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 */ |