Commit 4b59e95c authored by Devin Binnie's avatar Devin Binnie Committed by Harrison Healey
Browse files

[MM-24708] Added What's New modal for Channel Sidebar Phase 2 changes (#5883)



* Genericized new modal design into component

* Architecture and functionality for What's New modal

* Added tests for generic modal and lint/type fixes

* Styling of what's new modal

* Update redux to use new constants

* Removed debug code

* PR feedback

* Bug fix and moved to ModalController

* Use React.ReactNode instead of string | JSX.Element

* Address feedback

* Add example gif

* Remove fixed height on What's New image and fix modal size on mobile
Co-authored-by: default avatarHarrison Healey <harrisonmhealey@gmail.com>
parent 613b74da
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/GenericModal should match snapshot for base case 1`] = `
<Modal
animation={true}
aria-labelledby="genericModalLabel"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal"
dialogComponentClass={[Function]}
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="dialog"
show={true}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
/>
<form>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<div
className="GenericModal__header"
>
<h1
id="genericModalLabel"
>
Modal Header Text
</h1>
</div>
<div
className="GenericModal__body"
/>
</ModalBody>
<ModalFooter
bsClass="modal-footer"
componentClass="div"
/>
</form>
</Modal>
`;
exports[`components/GenericModal should match snapshot with both buttons 1`] = `
<Modal
animation={true}
aria-labelledby="genericModalLabel"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal"
dialogComponentClass={[Function]}
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="dialog"
show={true}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
/>
<form>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<div
className="GenericModal__header"
>
<h1
id="genericModalLabel"
>
Modal Header Text
</h1>
</div>
<div
className="GenericModal__body"
/>
</ModalBody>
<ModalFooter
bsClass="modal-footer"
componentClass="div"
>
<button
className="GenericModal__button cancel"
onClick={[Function]}
type="button"
>
<FormattedMessage
defaultMessage="Cancel"
id="generic_modal.cancel"
values={Object {}}
/>
</button>
<button
className="GenericModal__button confirm undefined"
onClick={[Function]}
type="submit"
>
<FormattedMessage
defaultMessage="Confirm"
id="generic_modal.confirm"
values={Object {}}
/>
</button>
</ModalFooter>
</form>
</Modal>
`;
exports[`components/GenericModal should match snapshot with disabled confirm button 1`] = `
<Modal
animation={true}
aria-labelledby="genericModalLabel"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal"
dialogComponentClass={[Function]}
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="dialog"
show={true}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
/>
<form>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<div
className="GenericModal__header"
>
<h1
id="genericModalLabel"
>
Modal Header Text
</h1>
</div>
<div
className="GenericModal__body"
/>
</ModalBody>
<ModalFooter
bsClass="modal-footer"
componentClass="div"
>
<button
className="GenericModal__button confirm undefined disabled"
disabled={true}
onClick={[Function]}
type="submit"
>
<FormattedMessage
defaultMessage="Confirm"
id="generic_modal.confirm"
values={Object {}}
/>
</button>
</ModalFooter>
</form>
</Modal>
`;
@charset 'UTF-8';
.modal {
.edit-category {
&.modal-dialog {
margin-top: calc(50vh - 240px);
}
.modal-body {
max-height: 100%;
padding: 0;
.form-control {
border: 2px solid var(--button-bg);
box-sizing: border-box;
border-radius: 4px;
height: 40px;
}
.input-clear {
font-style: normal;
font-weight: normal;
font-size: 18px;
line-height: 16px;
color: var(--center-channel-color);
opacity: 0.48;
right: 36px;
top: 11px;
width: 16px;
height: 16px;
}
}
.modal-content {
border-radius: 8px;
}
.modal-header {
background: transparent;
border: none;
min-height: 0;
padding: 0;
.close {
color: var(--center-channel-color-56);
width: 4rem;
height: 4rem;
font-size: 32px;
font-weight: 400;
display: flex;
align-items: center;
justify-content: center;
right: 4px;
top: 6px;
border-radius: 4px;
&:hover {
background-color: v(center-channel-color-08);
color: v(center-channel-color-72);
}
&:active {
background-color: v(button-bg-08);
color: v(button-bg);
}
}
}
.modal-footer {
border: none;
padding: 12px 24px 24px 24px;
border-radius: 4px;
}
}
}
.edit-category__header {
padding: 24px 24px 16px 24px;
h1 {
font-weight: 600;
font-size: 20px;
line-height: 28px;
margin: 0;
}
}
.edit-category__body {
padding: 0 24px;
position: relative;
width: 100%;
}
.delete-category__body {
padding: 12px 24px 16px 24px;
position: relative;
width: 100%;
}
.edit-category__helpText {
font-size: 12px;
line-height: 40px;
color: var(--center-channel-color-56);
}
.edit_category__button {
padding: 12px 16px;
line-height: 12px;
font-size: 14px;
font-weight: 600;
border: none;
&.cancel {
background: var(--center-channel-bg);
color: var(--button-bg);
}
&.create {
background: var(--button-bg);
border-radius: 4px;
color: var(--button-color);
&.disabled {
color: var(--center-channel-color-64);
background: var(--center-channel-color-08);
}
}
&.delete {
color: var(--button-color);
border-radius: 4px;
background: var(--dnd-indicator);
}
.GenericModal__button.delete {
background: var(--dnd-indicator) !important; // TODO: Figure out a way around important flag
}
.delete-category__helpText {
font-size: 14px;
line-height: 20px;
color: var(--center-channel-color);
}
......@@ -2,7 +2,6 @@
// See LICENSE.txt for license information.
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {ChannelCategory} from 'mattermost-redux/types/channel_categories';
......@@ -10,6 +9,7 @@ import {ChannelCategory} from 'mattermost-redux/types/channel_categories';
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
import '../category_modal.scss';
import GenericModal from 'components/generic_modal';
type Props = {
category: ChannelCategory;
......@@ -32,81 +32,39 @@ export default class DeleteCategoryModal extends React.PureComponent<Props, Stat
};
}
handleCancel = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
this.onHide();
}
handleConfirm = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
handleConfirm = () => {
this.props.actions.deleteCategory(this.props.category.id);
this.onHide();
}
onHide = () => {
this.setState({show: false}, this.props.onHide);
}
render() {
return (
<Modal
dialogClassName='a11y__modal edit-category'
show={this.state.show}
onHide={this.onHide}
onExited={this.onHide}
enforceFocus={true}
restoreFocus={true}
role='dialog'
aria-labelledby='editCategoryModalLabel'
<GenericModal
onHide={this.props.onHide}
modalHeaderText={(
<FormattedMessage
id='delete_category_modal.deleteCategory'
defaultMessage='Delete this category?'
/>
)}
handleConfirm={this.handleConfirm}
confirmButtonText={(
<FormattedMessage
id='delete_category_modal.delete'
defaultMessage='Delete'
/>
)}
confirmButtonClassName={'delete'}
>
<Modal.Header
closeButton={true}
/>
<Modal.Body>
<div className='edit-category__header'>
<h1 id='editCategoryModalLabel'>
<FormattedMessage
id='delete_category_modal.deleteCategory'
defaultMessage='Delete this category?'
/>
</h1>
</div>
<div className='delete-category__body'>
<span className='delete-category__helpText'>
<FormattedMarkdownMessage
id='delete_category_modal.helpText'
defaultMessage='Channels in **{category_name}** move back to the Channels and Direct Messages categories. You are not removed from any channels.'
values={{
category_name: this.props.category.display_name,
}}
/>
</span>
</div>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='edit_category__button cancel'
onClick={this.handleCancel}
>
<FormattedMessage
id='edit_category_modal.cancel'
defaultMessage='Cancel'
/>
</button>
<button
autoFocus={true}
type='button'
className={'edit_category__button delete'}
onClick={this.handleConfirm}
>
<FormattedMessage
id='delete_category_modal.delete'
defaultMessage='Delete'
/>
</button>
</Modal.Footer>
</Modal>
<span className='delete-category__helpText'>
<FormattedMarkdownMessage
id='delete_category_modal.helpText'
defaultMessage='Channels in **{category_name}** move back to the Channels and Direct Messages categories. You are not removed from any channels.'
values={{
category_name: this.props.category.display_name,
}}
/>
</span>
</GenericModal>
);
}
}
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import classNames from 'classnames';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {ChannelCategory} from 'mattermost-redux/types/channel_categories';
......@@ -13,6 +11,7 @@ import QuickInput from 'components/quick_input';
import {localizeMessage} from 'utils/utils';
import '../category_modal.scss';
import GenericModal from 'components/generic_modal';
type Props = {
onHide: () => void;
......@@ -28,7 +27,6 @@ type Props = {
type State = {
categoryName: string;
show: boolean;
}
export default class EditCategoryModal extends React.PureComponent<Props, State> {
......@@ -37,7 +35,6 @@ export default class EditCategoryModal extends React.PureComponent<Props, State>
this.state = {
categoryName: props.initialCategoryName || '',
show: true,
};
}
......@@ -45,30 +42,26 @@ export default class EditCategoryModal extends React.PureComponent<Props, State>
this.setState({categoryName: ''});
}
handleChange = (e: any) => {
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({categoryName: e.target.value});
}
handleCancel = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
handleCancel = () => {
this.handleClear();
this.onHide();
}
handleConfirm = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
handleConfirm = () => {
if (this.props.categoryId) {
this.props.actions.renameCategory(this.props.categoryId, this.state.categoryName);
} else {
this.props.actions.createCategory(this.props.currentTeamId, this.state.categoryName, this.props.channelIdsToAdd);
trackEvent('ui', 'ui_sidebar_created_category');
}
this.onHide();
}
onHide = () => {
this.setState({show: false}, this.props.onHide);
isConfirmDisabled = () => {
return !this.state.categoryName ||
(Boolean(this.props.initialCategoryName) && this.props.initialCategoryName === this.state.categoryName);
}
getText = () => {
......@@ -107,70 +100,32 @@ export default class EditCategoryModal extends React.PureComponent<Props, State>
const {modalHeaderText, editButtonText} = this.getText();
return (
<Modal
dialogClassName='a11y__modal edit-category'
show={this.state.show}
onHide={this.onHide}
onExited={this.onHide}
enforceFocus={true}
restoreFocus={true}
role='dialog'
aria-labelledby='editCategoryModalLabel'
<GenericModal
onHide={this.props.onHide}
modalHeaderText={modalHeaderText}
handleConfirm={this.handleConfirm}
handleCancel={this.handleCancel}
confirmButtonText={editButtonText}
isConfirmDisabled={this.isConfirmDisabled()}
>
<Modal.Header
closeButton={true}
<QuickInput
autoFocus={true}
className='form-control filter-textbox'
type='text'
value={this.state.categoryName}
placeholder={localizeMessage('edit_category_modal.placeholder', 'Choose a category name')}
clearable={true}
onClear={this.handleClear}
onChange={this.handleChange}
maxLength={22}
/>
<form>
<Modal.Body>
<div className='edit-category__header'>
<h1 id='editCategoryModalLabel'>
{modalHeaderText}
</h1>
</div>
<div className='edit-category__body'>
<QuickInput
autoFocus={true}
className='form-control filter-textbox'
type='text'
value={this.state.categoryName}
placeholder={localizeMessage('edit_category_modal.placeholder', 'Choose a category name')}
clearable={true}
onClear={this.handleClear}
onChange={this.handleChange}
maxLength={22}
/>
<span className='edit-category__helpText'>
<FormattedMessage
id='edit_category_modal.helpText'
defaultMessage='You can drag channels into categories to organize your sidebar.'
/>
</span>