MM-63911 Fix link copied behavior and style in get link modal (#30854)

* changed copy link behavior to match behavior elsewhere

* update timing, fix linting issues, fix style in modal header

* updated test

* updated test and snapshot

* fix linting error

* Fix snapshots

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
Этот коммит содержится в:
Matthew Birtch
2025-04-30 03:09:06 -04:00
коммит произвёл GitHub
родитель f09df430b9
Коммит a001367d43
8 изменённых файлов: 120 добавлений и 41 удалений

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 67 KiB

После

Ширина:  |  Высота:  |  Размер: 63 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 106 KiB

После

Ширина:  |  Высота:  |  Размер: 106 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 66 KiB

После

Ширина:  |  Высота:  |  Размер: 62 KiB

Просмотреть файл

@@ -36,11 +36,11 @@ exports[`components/GetLinkModal should have called onHide 1`] = `
closeLabel="Close"
id="getLinkModalLabel"
>
<h4
<h2
className="modal-title"
>
title
</h4>
</h2>
</ModalHeader>
<ModalBody
bsClass="modal-body"
@@ -71,26 +71,20 @@ exports[`components/GetLinkModal should have called onHide 1`] = `
/>
</button>
<button
className="btn btn-primary pull-left"
className="btn btn-primary btn-success pull-left"
data-copy-btn="true"
id="linkModalCopyLink"
onClick={[Function]}
type="button"
>
<MemoizedFormattedMessage
defaultMessage="Copy Link"
id="get_link.copy"
<i
className="icon icon-check"
/>
</button>
<p
className="alert alert-success alert--confirm"
>
<SuccessIcon />
<MemoizedFormattedMessage
defaultMessage=" Link copied"
defaultMessage="Copied"
id="get_link.clipboard"
/>
</p>
</button>
</ModalFooter>
</Modal>
`;
@@ -131,11 +125,11 @@ exports[`components/GetLinkModal should have called onHide 2`] = `
closeLabel="Close"
id="getLinkModalLabel"
>
<h4
<h2
className="modal-title"
>
title
</h4>
</h2>
</ModalHeader>
<ModalBody
bsClass="modal-body"
@@ -172,6 +166,9 @@ exports[`components/GetLinkModal should have called onHide 2`] = `
onClick={[Function]}
type="button"
>
<i
className="icon icon-link-variant"
/>
<MemoizedFormattedMessage
defaultMessage="Copy Link"
id="get_link.copy"
@@ -217,11 +214,11 @@ exports[`components/GetLinkModal should match snapshot when all props is set 1`]
closeLabel="Close"
id="getLinkModalLabel"
>
<h4
<h2
className="modal-title"
>
title
</h4>
</h2>
</ModalHeader>
<ModalBody
bsClass="modal-body"
@@ -263,6 +260,9 @@ exports[`components/GetLinkModal should match snapshot when all props is set 1`]
onClick={[Function]}
type="button"
>
<i
className="icon icon-link-variant"
/>
<MemoizedFormattedMessage
defaultMessage="Copy Link"
id="get_link.copy"
@@ -308,11 +308,11 @@ exports[`components/GetLinkModal should match snapshot when helpText is not set
closeLabel="Close"
id="getLinkModalLabel"
>
<h4
<h2
className="modal-title"
>
title
</h4>
</h2>
</ModalHeader>
<ModalBody
bsClass="modal-body"
@@ -349,6 +349,9 @@ exports[`components/GetLinkModal should match snapshot when helpText is not set
onClick={[Function]}
type="button"
>
<i
className="icon icon-link-variant"
/>
<MemoizedFormattedMessage
defaultMessage="Copy Link"
id="get_link.copy"

Просмотреть файл

@@ -4,6 +4,7 @@
import {shallow} from 'enzyme';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {act} from 'react-dom/test-utils';
import GetLinkModal from 'components/get_link_modal';
@@ -19,6 +20,14 @@ describe('components/GetLinkModal', () => {
link: 'https://mattermost.com',
};
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
test('should match snapshot when all props is set', () => {
const helpText = 'help text';
const props = {...requiredProps, helpText};
@@ -68,4 +77,44 @@ describe('components/GetLinkModal', () => {
wrapper.find('#linkModalTextArea').simulate('click');
expect(wrapper.state('copiedLink')).toBe(true);
});
test('should change button state when copying', () => {
const wrapper = mountWithIntl(
<GetLinkModal {...requiredProps}/>,
);
// Initial state
expect(wrapper.find('#linkModalCopyLink').text()).toContain('Copy Link');
expect(wrapper.find('#linkModalCopyLink').hasClass('btn-primary')).toBe(true);
expect(wrapper.find('#linkModalCopyLink').hasClass('btn-success')).toBe(false);
// After copying
wrapper.find('#linkModalCopyLink').simulate('click');
expect(wrapper.find('#linkModalCopyLink').text()).toContain('Copied');
expect(wrapper.find('#linkModalCopyLink').hasClass('btn-primary')).toBe(true);
expect(wrapper.find('#linkModalCopyLink').hasClass('btn-success')).toBe(true);
// After timeout
act(() => {
jest.advanceTimersByTime(1000);
});
wrapper.update();
expect(wrapper.find('#linkModalCopyLink').text()).toContain('Copy Link');
expect(wrapper.find('#linkModalCopyLink').hasClass('btn-primary')).toBe(true);
expect(wrapper.find('#linkModalCopyLink').hasClass('btn-success')).toBe(false);
});
test('should cleanup timeout on unmount', () => {
const wrapper = mountWithIntl(
<GetLinkModal {...requiredProps}/>,
);
wrapper.find('#linkModalCopyLink').simulate('click');
expect(wrapper.state('copiedLink')).toBe(true);
wrapper.unmount();
jest.advanceTimersByTime(1000);
// If we get here without errors, the timeout was properly cleaned up
});
});

Просмотреть файл

@@ -5,8 +5,6 @@ import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import SuccessIcon from 'components/widgets/icons/fa_success_icon';
type Props = {
show: boolean;
onHide: () => void;
@@ -22,6 +20,7 @@ type State = {
export default class GetLinkModal extends React.PureComponent<Props, State> {
private textAreaRef = React.createRef<HTMLTextAreaElement>();
private resetTimeout: NodeJS.Timeout | null = null;
public static defaultProps = {
helpText: null,
};
@@ -33,7 +32,16 @@ export default class GetLinkModal extends React.PureComponent<Props, State> {
};
}
public componentWillUnmount(): void {
if (this.resetTimeout) {
clearTimeout(this.resetTimeout);
}
}
public onHide = (): void => {
if (this.resetTimeout) {
clearTimeout(this.resetTimeout);
}
this.setState({copiedLink: false});
this.props.onHide();
};
@@ -47,6 +55,12 @@ export default class GetLinkModal extends React.PureComponent<Props, State> {
try {
this.setState({copiedLink: document.execCommand('copy')});
if (this.resetTimeout) {
clearTimeout(this.resetTimeout);
}
this.resetTimeout = setTimeout(() => {
this.setState({copiedLink: false});
}, 1000);
} catch (err) {
this.setState({copiedLink: false});
}
@@ -73,13 +87,26 @@ export default class GetLinkModal extends React.PureComponent<Props, State> {
id='linkModalCopyLink'
data-copy-btn='true'
type='button'
className='btn btn-primary pull-left'
className={`btn ${this.state.copiedLink ? 'btn-primary btn-success' : 'btn-primary'} pull-left`}
onClick={this.copyLink}
>
<FormattedMessage
id='get_link.copy'
defaultMessage='Copy Link'
/>
{this.state.copiedLink ? (
<>
<i className='icon icon-check'/>
<FormattedMessage
id='get_link.clipboard'
defaultMessage='Copied'
/>
</>
) : (
<>
<i className='icon icon-link-variant'/>
<FormattedMessage
id='get_link.copy'
defaultMessage='Copy Link'
/>
</>
)}
</button>
);
}
@@ -96,19 +123,6 @@ export default class GetLinkModal extends React.PureComponent<Props, State> {
/>
);
let copyLinkConfirm = null;
if (this.state.copiedLink) {
copyLinkConfirm = (
<p className='alert alert-success alert--confirm'>
<SuccessIcon/>
<FormattedMessage
id='get_link.clipboard'
defaultMessage=' Link copied'
/>
</p>
);
}
return (
<Modal
dialogClassName='a11y__modal'
@@ -122,7 +136,7 @@ export default class GetLinkModal extends React.PureComponent<Props, State> {
id='getLinkModalLabel'
closeButton={true}
>
<h4 className='modal-title'>{this.props.title}</h4>
<h2 className='modal-title'>{this.props.title}</h2>
</Modal.Header>
<Modal.Body>
{helpText}
@@ -141,7 +155,6 @@ export default class GetLinkModal extends React.PureComponent<Props, State> {
/>
</button>
{copyLink}
{copyLinkConfirm}
</Modal.Footer>
</Modal>
);

Просмотреть файл

@@ -230,6 +230,19 @@ button {
color: rgba(var(--center-channel-color-rgb), 0.32) !important;
opacity: 1;
}
&.btn-success {
background-color: var(--online-indicator);
color: var(--button-color-rgb);
&:hover {
background-color: var(--online-indicator);
}
&:active {
background-color: var(--online-indicator);
}
}
}
&.btn-secondary {

Просмотреть файл

@@ -328,6 +328,7 @@
background: transparent;
color: functions.v(center-channel-color);
font-size: 22px;
font-weight: 600;
line-height: 28px;
word-break: break-word;
}