Convert `<Button>` to Typescript (#27492)

This commit is contained in:
Renaud Chaput 2023-10-23 09:43:00 +02:00 committed by GitHub
parent ab0fb81479
commit 9d45a444f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 86 additions and 118 deletions

View File

@ -1,7 +1,7 @@
import { render, fireEvent, screen } from '@testing-library/react'; import { render, fireEvent, screen } from '@testing-library/react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import Button from '../button'; import { Button } from '../button';
describe('<Button />', () => { describe('<Button />', () => {
it('renders a button element', () => { it('renders a button element', () => {

View File

@ -15,7 +15,7 @@ import { VerifiedBadge } from 'mastodon/components/verified_badge';
import { me } from '../initial_state'; import { me } from '../initial_state';
import { Avatar } from './avatar'; import { Avatar } from './avatar';
import Button from './button'; import { Button } from './button';
import { FollowersCounter } from './counters'; import { FollowersCounter } from './counters';
import { DisplayName } from './display_name'; import { DisplayName } from './display_name';
import { IconButton } from './icon_button'; import { IconButton } from './icon_button';

View File

@ -1,58 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import classNames from 'classnames';
export default class Button extends PureComponent {
static propTypes = {
text: PropTypes.node,
type: PropTypes.string,
onClick: PropTypes.func,
disabled: PropTypes.bool,
block: PropTypes.bool,
secondary: PropTypes.bool,
className: PropTypes.string,
title: PropTypes.string,
children: PropTypes.node,
};
static defaultProps = {
type: 'button',
};
handleClick = (e) => {
if (!this.props.disabled && this.props.onClick) {
this.props.onClick(e);
}
};
setRef = (c) => {
this.node = c;
};
focus() {
this.node.focus();
}
render () {
const className = classNames('button', this.props.className, {
'button-secondary': this.props.secondary,
'button--block': this.props.block,
});
return (
<button
className={className}
disabled={this.props.disabled}
onClick={this.handleClick}
ref={this.setRef}
title={this.props.title}
type={this.props.type}
>
{this.props.text || this.props.children}
</button>
);
}
}

View File

@ -0,0 +1,58 @@
import { useCallback } from 'react';
import classNames from 'classnames';
interface BaseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
block?: boolean;
secondary?: boolean;
text?: JSX.Element;
}
interface PropsWithChildren extends BaseProps {
text?: never;
}
interface PropsWithText extends BaseProps {
text: JSX.Element;
children: never;
}
type Props = PropsWithText | PropsWithChildren;
export const Button: React.FC<Props> = ({
text,
type = 'button',
onClick,
disabled,
block,
secondary,
className,
title,
children,
...props
}) => {
const handleClick = useCallback<React.MouseEventHandler<HTMLButtonElement>>(
(e) => {
if (!disabled && onClick) {
onClick(e);
}
},
[disabled, onClick],
);
return (
<button
className={classNames('button', className, {
'button-secondary': secondary,
'button--block': block,
})}
disabled={disabled}
onClick={handleClick}
title={title}
type={type}
{...props}
>
{text ?? children}
</button>
);
};

View File

@ -11,7 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { Avatar } from 'mastodon/components/avatar'; import { Avatar } from 'mastodon/components/avatar';
import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge'; import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters'; import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button'; import { IconButton } from 'mastodon/components/icon_button';

View File

@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { revealAccount } from 'mastodon/actions/accounts'; import { revealAccount } from 'mastodon/actions/accounts';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { domain } from 'mastodon/initial_state'; import { domain } from 'mastodon/initial_state';
const mapDispatchToProps = (dispatch, { accountId }) => ({ const mapDispatchToProps = (dispatch, { accountId }) => ({

View File

@ -14,7 +14,7 @@ import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/
import AutosuggestInput from '../../../components/autosuggest_input'; import AutosuggestInput from '../../../components/autosuggest_input';
import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import Button from '../../../components/button'; import { Button } from '../../../components/button';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import LanguageDropdown from '../containers/language_dropdown_container'; import LanguageDropdown from '../containers/language_dropdown_container';
import PollButtonContainer from '../containers/poll_button_container'; import PollButtonContainer from '../containers/poll_button_container';

View File

@ -17,7 +17,7 @@ import {
} from 'mastodon/actions/accounts'; } from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import { Avatar } from 'mastodon/components/avatar'; import { Avatar } from 'mastodon/components/avatar';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { DisplayName } from 'mastodon/components/display_name'; import { DisplayName } from 'mastodon/components/display_name';
import { ShortNumber } from 'mastodon/components/short_number'; import { ShortNumber } from 'mastodon/components/short_number';
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state'; import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';

View File

@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { toServerSideType } from 'mastodon/utils/filters'; import { toServerSideType } from 'mastodon/utils/filters';
const mapStateToProps = (state, { filterId }) => ({ const mapStateToProps = (state, { filterId }) => ({

View File

@ -4,7 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { ShortNumber } from 'mastodon/components/short_number'; import { ShortNumber } from 'mastodon/components/short_number';
const messages = defineMessages({ const messages = defineMessages({
@ -76,4 +76,4 @@ HashtagHeader.propTypes = {
disabled: PropTypes.bool, disabled: PropTypes.bool,
onClick: PropTypes.func, onClick: PropTypes.func,
intl: PropTypes.object, intl: PropTypes.object,
}; };

View File

@ -11,7 +11,7 @@ import { throttle, escapeRegExp } from 'lodash';
import { openModal, closeModal } from 'mastodon/actions/modal'; import { openModal, closeModal } from 'mastodon/actions/modal';
import api from 'mastodon/api'; import api from 'mastodon/api';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { registrationsOpen, sso_redirect } from 'mastodon/initial_state'; import { registrationsOpen, sso_redirect } from 'mastodon/initial_state';

View File

@ -6,7 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { changeListEditorTitle, submitListEditor } from 'mastodon/actions/lists'; import { changeListEditorTitle, submitListEditor } from 'mastodon/actions/lists';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
const messages = defineMessages({ const messages = defineMessages({
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' }, label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },

View File

@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import { requestBrowserPermission } from 'mastodon/actions/notifications'; import { requestBrowserPermission } from 'mastodon/actions/notifications';
import { changeSetting } from 'mastodon/actions/settings'; import { changeSetting } from 'mastodon/actions/settings';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button'; import { IconButton } from 'mastodon/components/icon_button';

View File

@ -7,7 +7,7 @@ import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import Option from './components/option'; import Option from './components/option';

View File

@ -11,7 +11,7 @@ import { createSelector } from 'reselect';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import { fetchAccount } from 'mastodon/actions/accounts'; import { fetchAccount } from 'mastodon/actions/accounts';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import Option from './components/option'; import Option from './components/option';

View File

@ -7,7 +7,7 @@ import { OrderedSet } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container'; import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container';

View File

@ -11,7 +11,7 @@ import {
muteAccount, muteAccount,
blockAccount, blockAccount,
} from 'mastodon/actions/accounts'; } from 'mastodon/actions/accounts';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
const mapStateToProps = () => ({}); const mapStateToProps = () => ({});

View File

@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { followAccount } from 'mastodon/actions/accounts'; import { followAccount } from 'mastodon/actions/accounts';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { IconButton } from 'mastodon/components/icon_button'; import { IconButton } from 'mastodon/components/icon_button';
import Option from 'mastodon/features/report/components/option'; import Option from 'mastodon/features/report/components/option';
import { languages as preloadedLanguages } from 'mastodon/initial_state'; import { languages as preloadedLanguages } from 'mastodon/initial_state';

View File

@ -8,7 +8,7 @@ import { connect } from 'react-redux';
import { blockAccount } from '../../../actions/accounts'; import { blockAccount } from '../../../actions/accounts';
import { closeModal } from '../../../actions/modal'; import { closeModal } from '../../../actions/modal';
import { initReport } from '../../../actions/reports'; import { initReport } from '../../../actions/reports';
import Button from '../../../components/button'; import { Button } from '../../../components/button';
import { makeGetAccount } from '../../../selectors'; import { makeGetAccount } from '../../../selectors';
const makeMapStateToProps = () => { const makeMapStateToProps = () => {
@ -51,10 +51,6 @@ class BlockModal extends PureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
componentDidMount() {
this.button.focus();
}
handleClick = () => { handleClick = () => {
this.props.onClose(); this.props.onClose();
this.props.onConfirm(this.props.account); this.props.onConfirm(this.props.account);
@ -69,10 +65,6 @@ class BlockModal extends PureComponent {
this.props.onClose(); this.props.onClose();
}; };
setRef = (c) => {
this.button = c;
};
render () { render () {
const { account } = this.props; const { account } = this.props;
@ -95,7 +87,7 @@ class BlockModal extends PureComponent {
<Button onClick={this.handleSecondary} className='confirmation-modal__secondary-button'> <Button onClick={this.handleSecondary} className='confirmation-modal__secondary-button'>
<FormattedMessage id='confirmations.block.block_and_report' defaultMessage='Block & Report' /> <FormattedMessage id='confirmations.block.block_and_report' defaultMessage='Block & Report' />
</Button> </Button>
<Button onClick={this.handleClick} ref={this.setRef}> <Button onClick={this.handleClick} autoFocus>
<FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' /> <FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' />
</Button> </Button>
</div> </div>

View File

@ -16,7 +16,7 @@ import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdo
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { Avatar } from '../../../components/avatar'; import { Avatar } from '../../../components/avatar';
import Button from '../../../components/button'; import { Button } from '../../../components/button';
import { DisplayName } from '../../../components/display_name'; import { DisplayName } from '../../../components/display_name';
import { RelativeTimestamp } from '../../../components/relative_timestamp'; import { RelativeTimestamp } from '../../../components/relative_timestamp';
import StatusContent from '../../../components/status_content'; import StatusContent from '../../../components/status_content';
@ -55,10 +55,6 @@ class BoostModal extends ImmutablePureComponent {
...WithRouterPropTypes, ...WithRouterPropTypes,
}; };
componentDidMount() {
this.button.focus();
}
handleReblog = () => { handleReblog = () => {
this.props.onReblog(this.props.status, this.props.privacy); this.props.onReblog(this.props.status, this.props.privacy);
this.props.onClose(); this.props.onClose();
@ -76,10 +72,6 @@ class BoostModal extends ImmutablePureComponent {
return document.getElementsByClassName('modal-root__container')[0]; return document.getElementsByClassName('modal-root__container')[0];
}; };
setRef = (c) => {
this.button = c;
};
render () { render () {
const { status, privacy, intl } = this.props; const { status, privacy, intl } = this.props;
const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
@ -133,7 +125,7 @@ class BoostModal extends ImmutablePureComponent {
onChange={this.props.onChangeBoostPrivacy} onChange={this.props.onChangeBoostPrivacy}
/> />
)} )}
<Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} /> <Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} autoFocus />
</div> </div>
</div> </div>
); );

View File

@ -7,7 +7,7 @@ import classNames from 'classnames';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import { autoPlayGif } from 'mastodon/initial_state'; import { autoPlayGif } from 'mastodon/initial_state';

View File

@ -3,7 +3,7 @@ import { PureComponent } from 'react';
import { injectIntl, FormattedMessage } from 'react-intl'; import { injectIntl, FormattedMessage } from 'react-intl';
import Button from '../../../components/button'; import { Button } from '../../../components/button';
class ConfirmationModal extends PureComponent { class ConfirmationModal extends PureComponent {
@ -22,10 +22,6 @@ class ConfirmationModal extends PureComponent {
closeWhenConfirm: true, closeWhenConfirm: true,
}; };
componentDidMount() {
this.button.focus();
}
handleClick = () => { handleClick = () => {
if (this.props.closeWhenConfirm) { if (this.props.closeWhenConfirm) {
this.props.onClose(); this.props.onClose();
@ -42,10 +38,6 @@ class ConfirmationModal extends PureComponent {
this.props.onClose(); this.props.onClose();
}; };
setRef = (c) => {
this.button = c;
};
render () { render () {
const { message, confirm, secondary } = this.props; const { message, confirm, secondary } = this.props;
@ -62,7 +54,7 @@ class ConfirmationModal extends PureComponent {
{secondary !== undefined && ( {secondary !== undefined && (
<Button text={secondary} onClick={this.handleSecondary} className='confirmation-modal__secondary-button' /> <Button text={secondary} onClick={this.handleSecondary} className='confirmation-modal__secondary-button' />
)} )}
<Button text={confirm} onClick={this.handleClick} ref={this.setRef} /> <Button text={confirm} onClick={this.handleClick} autoFocus />
</div> </div>
</div> </div>
); );

View File

@ -16,7 +16,7 @@ import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js'; import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js';
import Button from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { GIFV } from 'mastodon/components/gifv'; import { GIFV } from 'mastodon/components/gifv';
import { IconButton } from 'mastodon/components/icon_button'; import { IconButton } from 'mastodon/components/icon_button';
import Audio from 'mastodon/features/audio'; import Audio from 'mastodon/features/audio';

View File

@ -10,7 +10,7 @@ import Toggle from 'react-toggle';
import { muteAccount } from '../../../actions/accounts'; import { muteAccount } from '../../../actions/accounts';
import { closeModal } from '../../../actions/modal'; import { closeModal } from '../../../actions/modal';
import { toggleHideNotifications, changeMuteDuration } from '../../../actions/mutes'; import { toggleHideNotifications, changeMuteDuration } from '../../../actions/mutes';
import Button from '../../../components/button'; import { Button } from '../../../components/button';
const messages = defineMessages({ const messages = defineMessages({
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' }, minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
@ -63,10 +63,6 @@ class MuteModal extends PureComponent {
onChangeMuteDuration: PropTypes.func.isRequired, onChangeMuteDuration: PropTypes.func.isRequired,
}; };
componentDidMount() {
this.button.focus();
}
handleClick = () => { handleClick = () => {
this.props.onClose(); this.props.onClose();
this.props.onConfirm(this.props.account, this.props.notifications, this.props.muteDuration); this.props.onConfirm(this.props.account, this.props.notifications, this.props.muteDuration);
@ -76,10 +72,6 @@ class MuteModal extends PureComponent {
this.props.onClose(); this.props.onClose();
}; };
setRef = (c) => {
this.button = c;
};
toggleNotifications = () => { toggleNotifications = () => {
this.props.onToggleNotifications(); this.props.onToggleNotifications();
}; };
@ -134,7 +126,7 @@ class MuteModal extends PureComponent {
<Button onClick={this.handleCancel} className='mute-modal__cancel-button'> <Button onClick={this.handleCancel} className='mute-modal__cancel-button'>
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' /> <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
</Button> </Button>
<Button onClick={this.handleClick} ref={this.setRef}> <Button onClick={this.handleClick} autoFocus>
<FormattedMessage id='confirmations.mute.confirm' defaultMessage='Mute' /> <FormattedMessage id='confirmations.mute.confirm' defaultMessage='Mute' />
</Button> </Button>
</div> </div>