Modernize forward message modal
* ModalHost is replaced with Modal. * The CSS is simplified down to unify the theme palette
This commit is contained in:
parent
91399deb26
commit
4ff36f46c4
3 changed files with 135 additions and 269 deletions
|
@ -2,27 +2,12 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
.module-ForwardMessageModal {
|
.module-ForwardMessageModal {
|
||||||
$padding: 16px;
|
|
||||||
@include popper-shadow();
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-block: 0;
|
margin-block: 0;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
max-height: 90vh;
|
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
|
|
||||||
@include light-theme() {
|
|
||||||
background: $color-white;
|
|
||||||
color: $color-gray-90;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include dark-theme() {
|
|
||||||
background: $color-gray-75;
|
|
||||||
color: $color-gray-05;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-list {
|
.module-conversation-list {
|
||||||
// remove horizontal padding so ListTiles extend to the edges
|
// remove horizontal padding so ListTiles extend to the edges
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -43,6 +28,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@include font-body-1-bold;
|
||||||
|
|
||||||
&--edit {
|
&--edit {
|
||||||
border-bottom: 1px solid $color-gray-15;
|
border-bottom: 1px solid $color-gray-15;
|
||||||
|
@ -51,73 +37,6 @@
|
||||||
border-color: $color-gray-60;
|
border-color: $color-gray-60;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--close {
|
|
||||||
@include button-reset;
|
|
||||||
position: absolute;
|
|
||||||
inset-inline-end: 16px;
|
|
||||||
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg('../images/icons/v3/x/x.svg', $color-gray-75);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg('../images/icons/v3/x/x.svg', $color-gray-15);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include keyboard-mode {
|
|
||||||
&:focus {
|
|
||||||
background-color: $color-ultramarine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--back {
|
|
||||||
@include button-reset;
|
|
||||||
|
|
||||||
height: 24px;
|
|
||||||
inset-inline-start: 16px;
|
|
||||||
position: absolute;
|
|
||||||
width: 24px;
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v3/chevron/chevron-left.svg',
|
|
||||||
$color-gray-60
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include keyboard-mode {
|
|
||||||
&:focus {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v3/chevron/chevron-left.svg',
|
|
||||||
$color-ultramarine
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v3/chevron/chevron-left.svg',
|
|
||||||
$color-gray-25
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@include dark-keyboard-mode {
|
|
||||||
&:hover {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v3/chevron/chevron-left.svg',
|
|
||||||
$color-ultramarine-light
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
@include font-body-1-bold;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__list-wrapper {
|
&__list-wrapper {
|
||||||
|
@ -176,26 +95,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable vertical scrolling on the modal pages
|
||||||
|
// since the elements inside are scrollable themselves
|
||||||
|
.module-Modal__body {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
@include font-body-2;
|
@include font-body-2;
|
||||||
align-items: center;
|
|
||||||
border-bottom-left-radius: 8px;
|
|
||||||
border-bottom-right-radius: 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 0;
|
|
||||||
padding: $padding;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
background-color: $color-gray-02;
|
|
||||||
color: $color-gray-60;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include dark-theme() {
|
|
||||||
background: $color-gray-65;
|
|
||||||
color: $color-gray-25;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable cursor since images are non-clickable
|
// Disable cursor since images are non-clickable
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
|
|
||||||
&__back-button {
|
&__back-button {
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
@ -68,16 +69,7 @@
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: $color-gray-45;
|
box-shadow: 0 0 0 2px $color-ultramarine;
|
||||||
&::before {
|
|
||||||
background-color: $color-ultramarine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
background: $color-gray-45;
|
|
||||||
&::before {
|
|
||||||
background-color: $color-ultramarine;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,6 @@ import React, {
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import type { MeasuredComponentProps } from 'react-measure';
|
import type { MeasuredComponentProps } from 'react-measure';
|
||||||
import Measure from 'react-measure';
|
import Measure from 'react-measure';
|
||||||
import { animated } from '@react-spring/web';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { AttachmentList } from './conversation/AttachmentList';
|
import { AttachmentList } from './conversation/AttachmentList';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
|
@ -25,11 +22,9 @@ import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { LocalizerType, ThemeType } from '../types/Util';
|
import type { LocalizerType, ThemeType } from '../types/Util';
|
||||||
import type { SmartCompositionTextAreaProps } from '../state/smart/CompositionTextArea';
|
import type { SmartCompositionTextAreaProps } from '../state/smart/CompositionTextArea';
|
||||||
import { ModalHost } from './ModalHost';
|
|
||||||
import { SearchInput } from './SearchInput';
|
import { SearchInput } from './SearchInput';
|
||||||
import { StagedLinkPreview } from './conversation/StagedLinkPreview';
|
import { StagedLinkPreview } from './conversation/StagedLinkPreview';
|
||||||
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
||||||
import { useAnimated } from '../hooks/useAnimated';
|
|
||||||
import {
|
import {
|
||||||
shouldNeverBeCalled,
|
shouldNeverBeCalled,
|
||||||
asyncShouldNeverBeCalled,
|
asyncShouldNeverBeCalled,
|
||||||
|
@ -46,6 +41,7 @@ import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
import type { HydratedBodyRangesType } from '../types/BodyRange';
|
import type { HydratedBodyRangesType } from '../types/BodyRange';
|
||||||
import { BodyRange } from '../types/BodyRange';
|
import { BodyRange } from '../types/BodyRange';
|
||||||
import { UserText } from './UserText';
|
import { UserText } from './UserText';
|
||||||
|
import { Modal } from './Modal';
|
||||||
|
|
||||||
export type DataPropsType = {
|
export type DataPropsType = {
|
||||||
candidateConversations: ReadonlyArray<ConversationType>;
|
candidateConversations: ReadonlyArray<ConversationType>;
|
||||||
|
@ -212,24 +208,13 @@ export function ForwardMessagesModal({
|
||||||
[contactLookup, selectedContacts, setSelectedContacts]
|
[contactLookup, selectedContacts, setSelectedContacts]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { close, modalStyles, overlayStyles } = useAnimated(onClose, {
|
|
||||||
getFrom: () => ({ opacity: 0, transform: 'translateY(48px)' }),
|
|
||||||
getTo: isOpen =>
|
|
||||||
isOpen
|
|
||||||
? { opacity: 1, transform: 'translateY(0px)' }
|
|
||||||
: {
|
|
||||||
opacity: 0,
|
|
||||||
transform: 'translateY(48px)',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleBackOrClose = useCallback(() => {
|
const handleBackOrClose = useCallback(() => {
|
||||||
if (isEditingMessage) {
|
if (isEditingMessage) {
|
||||||
setIsEditingMessage(false);
|
setIsEditingMessage(false);
|
||||||
} else {
|
} else {
|
||||||
close();
|
onClose();
|
||||||
}
|
}
|
||||||
}, [isEditingMessage, close, setIsEditingMessage]);
|
}, [isEditingMessage, onClose, setIsEditingMessage]);
|
||||||
|
|
||||||
const rowCount = filteredConversations.length;
|
const rowCount = filteredConversations.length;
|
||||||
const getRow = (index: number): undefined | Row => {
|
const getRow = (index: number): undefined | Row => {
|
||||||
|
@ -263,6 +248,38 @@ export function ForwardMessagesModal({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const footer = (
|
||||||
|
<div className="module-ForwardMessageModal__footer">
|
||||||
|
<div>
|
||||||
|
{selectedContacts.map((contact, index) => {
|
||||||
|
return (
|
||||||
|
<Fragment key={contact.id}>
|
||||||
|
<UserText text={contact.title} />
|
||||||
|
{index < selectedContacts.length - 1 ? ', ' : ''}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{isEditingMessage || !isLonelyDraftEditable ? (
|
||||||
|
<Button
|
||||||
|
aria-label={i18n('icu:ForwardMessageModal--continue')}
|
||||||
|
className="module-ForwardMessageModal__send-button module-ForwardMessageModal__send-button--forward"
|
||||||
|
aria-disabled={!canForwardMessages}
|
||||||
|
onClick={forwardMessages}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
aria-label={i18n('icu:forwardMessage')}
|
||||||
|
className="module-ForwardMessageModal__send-button module-ForwardMessageModal__send-button--continue"
|
||||||
|
disabled={!hasContactsSelected}
|
||||||
|
onClick={() => setIsEditingMessage(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{cannotMessage && (
|
{cannotMessage && (
|
||||||
|
@ -275,155 +292,101 @@ export function ForwardMessagesModal({
|
||||||
{i18n('icu:GroupV2--cannot-send')}
|
{i18n('icu:GroupV2--cannot-send')}
|
||||||
</ConfirmationDialog>
|
</ConfirmationDialog>
|
||||||
)}
|
)}
|
||||||
<ModalHost
|
<Modal
|
||||||
modalName="ForwardMessageModal"
|
modalName="ForwardMessageModal"
|
||||||
noMouseClose
|
hasXButton
|
||||||
onEscape={handleBackOrClose}
|
i18n={i18n}
|
||||||
onClose={close}
|
onClose={onClose}
|
||||||
overlayStyles={overlayStyles}
|
onBackButtonClick={isEditingMessage ? handleBackOrClose : undefined}
|
||||||
|
moduleClassName="module-ForwardMessageModal"
|
||||||
|
title={i18n('icu:ForwardMessageModal__title')}
|
||||||
useFocusTrap={false}
|
useFocusTrap={false}
|
||||||
|
padded={false}
|
||||||
|
modalFooter={footer}
|
||||||
>
|
>
|
||||||
<animated.div
|
{isEditingMessage && lonelyDraft != null ? (
|
||||||
className="module-ForwardMessageModal"
|
<ForwardMessageEditor
|
||||||
style={modalStyles}
|
draft={lonelyDraft}
|
||||||
>
|
linkPreview={lonelyLinkPreview}
|
||||||
<div
|
onChange={(messageBody, bodyRanges) => {
|
||||||
className={classNames('module-ForwardMessageModal__header', {
|
onChange([{ ...lonelyDraft, messageBody, bodyRanges }]);
|
||||||
'module-ForwardMessageModal__header--edit': isEditingMessage,
|
}}
|
||||||
})}
|
onChangeAttachments={attachments => {
|
||||||
>
|
onChange([{ ...lonelyDraft, attachments }]);
|
||||||
{isEditingMessage ? (
|
}}
|
||||||
<button
|
removeLinkPreview={removeLinkPreview}
|
||||||
aria-label={i18n('icu:back')}
|
theme={theme}
|
||||||
className="module-ForwardMessageModal__header--back"
|
i18n={i18n}
|
||||||
onClick={() => setIsEditingMessage(false)}
|
RenderCompositionTextArea={RenderCompositionTextArea}
|
||||||
type="button"
|
onSubmit={forwardMessages}
|
||||||
>
|
/>
|
||||||
|
) : (
|
||||||
</button>
|
<div className="module-ForwardMessageModal__main-body">
|
||||||
) : (
|
<SearchInput
|
||||||
<button
|
disabled={candidateConversations.length === 0}
|
||||||
aria-label={i18n('icu:close')}
|
|
||||||
className="module-ForwardMessageModal__header--close"
|
|
||||||
onClick={close}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<h1>{i18n('icu:ForwardMessageModal__title')}</h1>
|
|
||||||
</div>
|
|
||||||
{isEditingMessage && lonelyDraft != null ? (
|
|
||||||
<ForwardMessageEditor
|
|
||||||
draft={lonelyDraft}
|
|
||||||
linkPreview={lonelyLinkPreview}
|
|
||||||
onChange={(messageBody, bodyRanges) => {
|
|
||||||
onChange([{ ...lonelyDraft, messageBody, bodyRanges }]);
|
|
||||||
}}
|
|
||||||
onChangeAttachments={attachments => {
|
|
||||||
onChange([{ ...lonelyDraft, attachments }]);
|
|
||||||
}}
|
|
||||||
removeLinkPreview={removeLinkPreview}
|
|
||||||
theme={theme}
|
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
RenderCompositionTextArea={RenderCompositionTextArea}
|
placeholder={i18n('icu:contactSearchPlaceholder')}
|
||||||
onSubmit={forwardMessages}
|
onChange={event => {
|
||||||
|
setSearchTerm(event.target.value);
|
||||||
|
}}
|
||||||
|
ref={inputRef}
|
||||||
|
value={searchTerm}
|
||||||
/>
|
/>
|
||||||
) : (
|
{candidateConversations.length ? (
|
||||||
<div className="module-ForwardMessageModal__main-body">
|
<Measure bounds>
|
||||||
<SearchInput
|
{({ contentRect, measureRef }: MeasuredComponentProps) => (
|
||||||
disabled={candidateConversations.length === 0}
|
<div
|
||||||
i18n={i18n}
|
className="module-ForwardMessageModal__list-wrapper"
|
||||||
placeholder={i18n('icu:contactSearchPlaceholder')}
|
ref={measureRef}
|
||||||
onChange={event => {
|
>
|
||||||
setSearchTerm(event.target.value);
|
<ConversationList
|
||||||
}}
|
dimensions={contentRect.bounds}
|
||||||
ref={inputRef}
|
getPreferredBadge={getPreferredBadge}
|
||||||
value={searchTerm}
|
getRow={getRow}
|
||||||
/>
|
i18n={i18n}
|
||||||
{candidateConversations.length ? (
|
onClickArchiveButton={shouldNeverBeCalled}
|
||||||
<Measure bounds>
|
onClickContactCheckbox={(
|
||||||
{({ contentRect, measureRef }: MeasuredComponentProps) => (
|
conversationId: string,
|
||||||
<div
|
disabledReason:
|
||||||
className="module-ForwardMessageModal__list-wrapper"
|
| undefined
|
||||||
ref={measureRef}
|
| ContactCheckboxDisabledReason
|
||||||
>
|
) => {
|
||||||
<ConversationList
|
if (
|
||||||
dimensions={contentRect.bounds}
|
disabledReason !==
|
||||||
getPreferredBadge={getPreferredBadge}
|
ContactCheckboxDisabledReason.MaximumContactsSelected
|
||||||
getRow={getRow}
|
) {
|
||||||
i18n={i18n}
|
toggleSelectedConversation(conversationId);
|
||||||
onClickArchiveButton={shouldNeverBeCalled}
|
}
|
||||||
onClickContactCheckbox={(
|
}}
|
||||||
conversationId: string,
|
lookupConversationWithoutUuid={asyncShouldNeverBeCalled}
|
||||||
disabledReason:
|
showConversation={shouldNeverBeCalled}
|
||||||
| undefined
|
showUserNotFoundModal={shouldNeverBeCalled}
|
||||||
| ContactCheckboxDisabledReason
|
setIsFetchingUUID={shouldNeverBeCalled}
|
||||||
) => {
|
onSelectConversation={shouldNeverBeCalled}
|
||||||
if (
|
blockConversation={shouldNeverBeCalled}
|
||||||
disabledReason !==
|
removeConversation={shouldNeverBeCalled}
|
||||||
ContactCheckboxDisabledReason.MaximumContactsSelected
|
onOutgoingAudioCallInConversation={shouldNeverBeCalled}
|
||||||
) {
|
onOutgoingVideoCallInConversation={shouldNeverBeCalled}
|
||||||
toggleSelectedConversation(conversationId);
|
renderMessageSearchResult={() => {
|
||||||
}
|
shouldNeverBeCalled();
|
||||||
}}
|
return <div />;
|
||||||
lookupConversationWithoutUuid={asyncShouldNeverBeCalled}
|
}}
|
||||||
showConversation={shouldNeverBeCalled}
|
rowCount={rowCount}
|
||||||
showUserNotFoundModal={shouldNeverBeCalled}
|
shouldRecomputeRowHeights={false}
|
||||||
setIsFetchingUUID={shouldNeverBeCalled}
|
showChooseGroupMembers={shouldNeverBeCalled}
|
||||||
onSelectConversation={shouldNeverBeCalled}
|
theme={theme}
|
||||||
blockConversation={shouldNeverBeCalled}
|
/>
|
||||||
removeConversation={shouldNeverBeCalled}
|
</div>
|
||||||
onOutgoingAudioCallInConversation={shouldNeverBeCalled}
|
)}
|
||||||
onOutgoingVideoCallInConversation={shouldNeverBeCalled}
|
</Measure>
|
||||||
renderMessageSearchResult={() => {
|
) : (
|
||||||
shouldNeverBeCalled();
|
<div className="module-ForwardMessageModal__no-candidate-contacts">
|
||||||
return <div />;
|
{i18n('icu:noContactsFound')}
|
||||||
}}
|
</div>
|
||||||
rowCount={rowCount}
|
)}
|
||||||
shouldRecomputeRowHeights={false}
|
|
||||||
showChooseGroupMembers={shouldNeverBeCalled}
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Measure>
|
|
||||||
) : (
|
|
||||||
<div className="module-ForwardMessageModal__no-candidate-contacts">
|
|
||||||
{i18n('icu:noContactsFound')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="module-ForwardMessageModal__footer">
|
|
||||||
<div>
|
|
||||||
{selectedContacts.map((contact, index) => {
|
|
||||||
return (
|
|
||||||
<Fragment key={contact.id}>
|
|
||||||
<UserText text={contact.title} />
|
|
||||||
{index < selectedContacts.length - 1 ? ', ' : ''}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{isEditingMessage || !isLonelyDraftEditable ? (
|
|
||||||
<Button
|
|
||||||
aria-label={i18n('icu:ForwardMessageModal--continue')}
|
|
||||||
className="module-ForwardMessageModal__send-button module-ForwardMessageModal__send-button--forward"
|
|
||||||
aria-disabled={!canForwardMessages}
|
|
||||||
onClick={forwardMessages}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
aria-label={i18n('icu:forwardMessage')}
|
|
||||||
className="module-ForwardMessageModal__send-button module-ForwardMessageModal__send-button--continue"
|
|
||||||
disabled={!hasContactsSelected}
|
|
||||||
onClick={() => setIsEditingMessage(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</animated.div>
|
)}
|
||||||
</ModalHost>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue