diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index b61b84e1876a..c4632d87348a 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -3,6 +3,9 @@
// Using BEM syntax explained here: https://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/
+// CAUTION: these styles are often overridden by other components
+// if you make changes to these, you must check EVERY component that uses
+
.module-title-bar-drag-area {
-webkit-app-region: drag;
height: var(--title-bar-drag-area-height);
diff --git a/stylesheets/components/AddUserToAnotherGroupModal.scss b/stylesheets/components/AddUserToAnotherGroupModal.scss
index 248d1c7e86b1..3340993e8af5 100644
--- a/stylesheets/components/AddUserToAnotherGroupModal.scss
+++ b/stylesheets/components/AddUserToAnotherGroupModal.scss
@@ -1,12 +1,6 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
-div.AddUserToAnotherGroupModal__body {
- padding-left: 0;
- padding-bottom: 0;
- padding-right: 0;
-}
-
.AddUserToAnotherGroupModal {
&__main-body {
display: flex;
diff --git a/stylesheets/components/BadgeDialog.scss b/stylesheets/components/BadgeDialog.scss
index b6415bdce82e..a456d6b964d9 100644
--- a/stylesheets/components/BadgeDialog.scss
+++ b/stylesheets/components/BadgeDialog.scss
@@ -9,8 +9,7 @@
user-select: none;
- // We use this selector for specificity.
- &.module-Modal {
+ &__width-container {
max-width: 420px;
}
diff --git a/stylesheets/components/BadgeSustainerInstructionsDialog.scss b/stylesheets/components/BadgeSustainerInstructionsDialog.scss
index 11784b1275a6..55cbd21a4cac 100644
--- a/stylesheets/components/BadgeSustainerInstructionsDialog.scss
+++ b/stylesheets/components/BadgeSustainerInstructionsDialog.scss
@@ -4,8 +4,7 @@
.BadgeSustainerInstructionsDialog {
user-select: none;
- // We use this selector for specificity.
- &.module-Modal {
+ &__width-container {
max-width: 420px;
}
diff --git a/stylesheets/components/CallingSelectPresentingSourcesModal.scss b/stylesheets/components/CallingSelectPresentingSourcesModal.scss
index c7c62f1ea2b0..3d8ebf89a401 100644
--- a/stylesheets/components/CallingSelectPresentingSourcesModal.scss
+++ b/stylesheets/components/CallingSelectPresentingSourcesModal.scss
@@ -2,27 +2,23 @@
// SPDX-License-Identifier: AGPL-3.0-only
.module-CallingSelectPresentingSourcesModal {
- // specificity
- &.module-Modal {
+ &__width-container {
max-width: 665px;
position: relative;
- padding-bottom: 48px;
}
- &__button-footer {
+ // there's no module-class-name on the footer,
+ // so we have to reference it using the generic selector
+ .module-Modal__button-footer {
background-color: $color-gray-95;
- bottom: 0;
- margin-left: -16px;
- margin-top: 0;
- padding: 16px;
- position: absolute;
- width: 100%;
}
&__sources {
- margin-bottom: 20px;
- margin-left: -6px;
- margin-right: -6px;
+ margin-bottom: 34px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 12px;
&:last-child {
margin-bottom: 0;
@@ -38,9 +34,6 @@
border-radius: 4px;
border: 1px solid $color-gray-60;
- margin-bottom: 14px;
- margin-left: 6px;
- margin-right: 6px;
overflow: hidden;
padding: 8px;
text-align: center;
diff --git a/stylesheets/components/ChatColorPicker.scss b/stylesheets/components/ChatColorPicker.scss
index 86f9517440f6..3a676e1fd7df 100644
--- a/stylesheets/components/ChatColorPicker.scss
+++ b/stylesheets/components/ChatColorPicker.scss
@@ -71,8 +71,4 @@
height: 24px;
width: 24px;
}
-
- &__modal__body {
- overflow-x: hidden !important;
- }
}
diff --git a/stylesheets/components/ContactModal.scss b/stylesheets/components/ContactModal.scss
index 5b8f8c71897f..1aaa2b7673e0 100644
--- a/stylesheets/components/ContactModal.scss
+++ b/stylesheets/components/ContactModal.scss
@@ -7,6 +7,7 @@
flex-direction: column;
justify-content: center;
margin-top: 4px;
+ margin-bottom: 16px;
&__name {
@include font-title-2;
@@ -143,8 +144,3 @@
}
}
}
-
-.module-Modal.ContactModal__modal .ContactModal__modal__body {
- padding-left: 0;
- padding-right: 0;
-}
diff --git a/stylesheets/components/Modal.scss b/stylesheets/components/Modal.scss
index a3883c1225c1..1b0102bdd99a 100644
--- a/stylesheets/components/Modal.scss
+++ b/stylesheets/components/Modal.scss
@@ -4,6 +4,7 @@
.module-Modal {
@include popper-shadow();
border-radius: 8px;
+ overflow: hidden;
// We need this to be a number not divisible by 5 so that if we have sticky
// buttons the bottom doesn't bleed through by 1px.
max-height: 89vh;
@@ -23,9 +24,7 @@
align-items: center;
display: flex;
justify-content: space-between;
- margin-bottom: 1em;
- padding: 16px 16px 0 16px;
- position: sticky;
+ padding: 16px 16px 1em 16px;
&--with-back-button .module-Modal__title {
text-align: center;
@@ -132,16 +131,20 @@
@include scrollbar;
@include font-body-1;
margin: 0;
- padding: 16px;
+ overflow-y: overlay;
+ overflow-x: auto;
+ }
+
+ &--padded {
+ .module-Modal__body {
+ padding: 16px;
+ }
}
&--has-header {
.module-Modal__body {
padding-top: 0;
border-top: 1px solid transparent;
- // If there's a header, just the body scrolls
- overflow-y: overlay;
- overflow-x: auto;
&--scrolled {
@include light-theme {
@@ -155,65 +158,22 @@
}
}
- &--no-header {
- // If there's no header, the whole thing scrolls
- overflow-y: overlay;
- overflow-x: auto;
- }
-
&__button-footer {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
- margin-top: 8px;
+ align-items: center;
+ padding: 1em 16px 16px 16px;
+ gap: 8px;
.module-Button {
margin-left: 8px;
- margin-top: 8px;
}
&--one-button-per-line {
flex-direction: column;
align-items: flex-end;
}
-
- .module-Modal--sticky-buttons & {
- bottom: 0;
- display: flex;
- justify-content: flex-end;
- padding: 16px 0;
- position: sticky;
- right: 0;
- width: 100%;
- z-index: $z-index-above-popup;
-
- @include light-theme() {
- background: $color-white;
- }
-
- @include dark-theme() {
- background: $color-gray-80;
- }
- }
- }
-
- &--sticky-buttons {
- .module-Modal__body {
- padding-bottom: 0;
- }
- position: relative;
-
- .module-Modal__body--overflow {
- .module-Modal__button-footer {
- @include light-theme {
- border-top: 1px solid $color-gray-05;
- }
-
- @include dark-theme {
- border-top: 1px solid $color-gray-80;
- }
- }
- }
}
// Overrides for a modal with important message
@@ -251,6 +211,7 @@
margin-top: 27px;
flex-grow: 0;
flex-shrink: 0;
+ padding: 0 12px 4px 12px;
.module-Button {
flex-grow: 1;
diff --git a/stylesheets/components/SendStoryModal.scss b/stylesheets/components/SendStoryModal.scss
index 7e6f2ac17147..6b0ce1fdb77c 100644
--- a/stylesheets/components/SendStoryModal.scss
+++ b/stylesheets/components/SendStoryModal.scss
@@ -2,6 +2,27 @@
// SPDX-License-Identifier: AGPL-3.0-only
.SendStoryModal {
+ &__body {
+ // force
+ .module-Modal & {
+ padding-bottom: 0;
+ }
+ }
+
+ // don't re-layout buttons on wrap,
+ // since we have things beyond same-sized-rectangles in the footer
+ .module-Modal__button-footer {
+ &--one-button-per-line {
+ flex-direction: row;
+ align-items: center;
+ }
+ }
+
+ &__item--contact-or-conversation {
+ height: 52px;
+ padding: 0 6px;
+ }
+
&__top-bar {
align-items: center;
display: flex;
@@ -85,7 +106,6 @@
justify-content: space-between;
margin: 8px 0;
user-select: none;
- width: 100%;
}
&__info {
@@ -164,8 +184,9 @@
&__selected-lists {
@include font-body-2;
color: $color-gray-15;
- max-width: 280px;
+ padding-right: 16px;
user-select: none;
+ flex: 1;
}
&__ok {
@@ -212,11 +233,3 @@
}
}
}
-
-.module-Modal--sticky-buttons .SendStoryModal__button-footer {
- align-items: center;
- justify-content: space-between;
- padding-top: 0;
- padding-left: 16px;
- padding-right: 16px;
-}
diff --git a/stylesheets/components/StoriesSettingsModal.scss b/stylesheets/components/StoriesSettingsModal.scss
index 59e5fc82d9fd..c4e9dc4162af 100644
--- a/stylesheets/components/StoriesSettingsModal.scss
+++ b/stylesheets/components/StoriesSettingsModal.scss
@@ -2,9 +2,13 @@
// SPDX-License-Identifier: AGPL-3.0-only
.StoriesSettingsModal {
+ &__modal__body {
+ display: flex;
+ flex-direction: column;
+ }
+
&__conversation-list {
- .module-conversation-list,
- .module-conversation-list__item--contact-or-conversation {
+ .module-conversation-list {
padding-left: 0;
padding-right: 0;
}
@@ -194,20 +198,6 @@
overflow: hidden;
}
- &__search {
- &__container {
- margin-left: 0;
- margin-right: 0;
- }
- }
-
- &__tags {
- margin: 0 -4px;
-
- // Override .module-ContactPills
- padding: 0;
- }
-
&__name-story-avatar-container {
align-items: center;
display: flex;
diff --git a/ts/components/AddUserToAnotherGroupModal.tsx b/ts/components/AddUserToAnotherGroupModal.tsx
index 7663ee7fc34f..120f4b09decb 100644
--- a/ts/components/AddUserToAnotherGroupModal.tsx
+++ b/ts/components/AddUserToAnotherGroupModal.tsx
@@ -148,6 +148,7 @@ export const AddUserToAnotherGroupModal = ({
onClose={toggleAddUserToAnotherGroupModal}
title={i18n('AddUserToAnotherGroupModal__title')}
moduleClassName="AddUserToAnotherGroupModal"
+ padded={false}
>
= ({
onClose,
title,
}) => (
-
- {body}
-
+ {i18n('Confirmation--confirm')}
-
+ }
+ >
+ {body}
);
diff --git a/ts/components/Button.tsx b/ts/components/Button.tsx
index 93d3aa188f2b..fb6048081831 100644
--- a/ts/components/Button.tsx
+++ b/ts/components/Button.tsx
@@ -58,6 +58,7 @@ type PropsType = {
}
| {
type: 'submit';
+ form?: string;
}
) &
(
@@ -117,12 +118,14 @@ export const Button = React.forwardRef(
let onClick: undefined | MouseEventHandler;
let type: 'button' | 'submit';
+ let form;
if ('onClick' in props) {
({ onClick } = props);
type = 'button';
} else {
onClick = undefined;
({ type } = props);
+ ({ form } = props);
}
const sizeClassName = SIZE_CLASS_NAMES.get(size);
@@ -143,6 +146,7 @@ export const Button = React.forwardRef(
)}
disabled={disabled}
onClick={onClick}
+ form={form}
ref={ref}
style={style}
tabIndex={tabIndex}
diff --git a/ts/components/CallingDeviceSelection.tsx b/ts/components/CallingDeviceSelection.tsx
index f79e3b9dce12..00863bc4fa52 100644
--- a/ts/components/CallingDeviceSelection.tsx
+++ b/ts/components/CallingDeviceSelection.tsx
@@ -140,6 +140,7 @@ export const CallingDeviceSelection = ({
i18n={i18n}
theme={Theme.Dark}
onClose={toggleSettings}
+ padded={false}
>
source.isScreen
);
+ const footer = (
+ <>
+ setPresenting()} variant={ButtonVariant.Secondary}>
+ {i18n('cancel')}
+
+ setPresenting(sourceToPresent)}
+ >
+ {i18n('calling__SelectPresentingSourcesModal--confirm')}
+
+ >
+ );
+
return (
{i18n('calling__SelectPresentingSourcesModal--entireScreen')}
@@ -120,20 +135,6 @@ export const CallingSelectPresentingSourcesModal = ({
/>
))}
-
- setPresenting()}
- variant={ButtonVariant.Secondary}
- >
- {i18n('cancel')}
-
- setPresenting(sourceToPresent)}
- >
- {i18n('calling__SelectPresentingSourcesModal--confirm')}
-
-
);
};
diff --git a/ts/components/CaptchaDialog.tsx b/ts/components/CaptchaDialog.tsx
index f9329a948a16..b922d79e6f52 100644
--- a/ts/components/CaptchaDialog.tsx
+++ b/ts/components/CaptchaDialog.tsx
@@ -34,6 +34,16 @@ export function CaptchaDialog(props: Readonly): JSX.Element {
};
if (isClosing && !isPending) {
+ const footer = (
+ <>
+
+ {i18n('cancel')}
+
+
+ {i18n('CaptchaDialog--can_close__skip-verification')}
+
+ >
+ );
return (
): JSX.Element {
title={i18n('CaptchaDialog--can-close__title')}
onClose={() => setIsClosing(false)}
key="skip"
+ modalFooter={footer}
>
{i18n('CaptchaDialog--can-close__body')}
-
-
- {i18n('cancel')}
-
-
- {i18n('CaptchaDialog--can_close__skip-verification')}
-
-
);
}
@@ -71,6 +74,21 @@ export function CaptchaDialog(props: Readonly): JSX.Element {
}
};
+ const footer = (
+
+ {isPending ? (
+
+ ) : (
+ 'Continue'
+ )}
+
+ );
+
return (
): JSX.Element {
hasXButton
onClose={() => setIsClosing(true)}
key="primary"
+ modalFooter={footer}
>
{i18n('CaptchaDialog__first-paragraph')}
{i18n('CaptchaDialog__second-paragraph')}
-
-
- {isPending ? (
-
- ) : (
- 'Continue'
- )}
-
-
);
}
diff --git a/ts/components/ConfirmationDialog.tsx b/ts/components/ConfirmationDialog.tsx
index d89c6c307540..328e5d75399c 100644
--- a/ts/components/ConfirmationDialog.tsx
+++ b/ts/components/ConfirmationDialog.tsx
@@ -7,7 +7,7 @@ import { animated } from '@react-spring/web';
import { Button, ButtonVariant } from './Button';
import type { LocalizerType } from '../types/Util';
import { ModalHost } from './ModalHost';
-import { Modal, ModalWindow } from './Modal';
+import { ModalPage } from './Modal';
import type { Theme } from '../util/theme';
import { useAnimated } from '../hooks/useAnimated';
@@ -96,6 +96,34 @@ export const ConfirmationDialog = React.memo(
const hasActions = Boolean(actions.length);
+ const footer = (
+ <>
+
+ {cancelText || i18n('confirmation-dialog--Cancel')}
+
+ {actions.map((action, i) => (
+ {
+ action.action();
+ close();
+ }}
+ data-action={i}
+ variant={getButtonVariant(action.style)}
+ >
+ {action.text}
+
+ ))}
+ >
+ );
+
const modalName = `ConfirmationDialog.${dialogName}`;
return (
@@ -108,41 +136,17 @@ export const ConfirmationDialog = React.memo(
theme={theme}
>
-
{children}
-
-
- {cancelText || i18n('confirmation-dialog--Cancel')}
-
- {actions.map((action, i) => (
- {
- action.action();
- close();
- }}
- data-action={i}
- variant={getButtonVariant(action.style)}
- >
- {action.text}
-
- ))}
-
-
+
);
diff --git a/ts/components/CrashReportDialog.tsx b/ts/components/CrashReportDialog.tsx
index 8d370128720b..8f607fe1b7c1 100644
--- a/ts/components/CrashReportDialog.tsx
+++ b/ts/components/CrashReportDialog.tsx
@@ -33,6 +33,30 @@ export function CrashReportDialog(props: Readonly): JSX.Element {
uploadCrashReports();
};
+ const footer = (
+ <>
+
+ {i18n('CrashReportDialog__erase')}
+
+ button?.focus()}
+ variant={ButtonVariant.Primary}
+ >
+ {isPending ? (
+
+ ) : (
+ i18n('CrashReportDialog__submit')
+ )}
+
+ >
+ );
+
return (
): JSX.Element {
title={i18n('CrashReportDialog__title')}
hasXButton
onClose={eraseCrashReports}
+ modalFooter={footer}
>
{i18n('CrashReportDialog__body')}
-
-
- {i18n('CrashReportDialog__erase')}
-
- button?.focus()}
- variant={ButtonVariant.Primary}
- >
- {isPending ? (
-
- ) : (
- i18n('CrashReportDialog__submit')
- )}
-
-
);
}
diff --git a/ts/components/CustomizingPreferredReactionsModal.tsx b/ts/components/CustomizingPreferredReactionsModal.tsx
index 4a78198f28fd..562307c98c0e 100644
--- a/ts/components/CustomizingPreferredReactionsModal.tsx
+++ b/ts/components/CustomizingPreferredReactionsModal.tsx
@@ -104,6 +104,38 @@ export function CustomizingPreferredReactionsModal({
);
const canSave = !isSaving && hasChanged;
+ const footer = (
+ <>
+ {
+ resetDraftEmoji();
+ }}
+ onKeyDown={event => {
+ if (event.key === 'Enter' || event.key === 'Space') {
+ resetDraftEmoji();
+ }
+ }}
+ variant={ButtonVariant.SecondaryAffirmative}
+ >
+ {i18n('reset')}
+
+ {
+ savePreferredReactions();
+ }}
+ onKeyDown={event => {
+ if (event.key === 'Enter' || event.key === 'Space') {
+ savePreferredReactions();
+ }
+ }}
+ >
+ {i18n('save')}
+
+ >
+ );
+
return (
)}
-
- {
- resetDraftEmoji();
- }}
- onKeyDown={event => {
- if (event.key === 'Enter' || event.key === 'Space') {
- resetDraftEmoji();
- }
- }}
- variant={ButtonVariant.SecondaryAffirmative}
- >
- {i18n('reset')}
-
- {
- savePreferredReactions();
- }}
- onKeyDown={event => {
- if (event.key === 'Enter' || event.key === 'Space') {
- savePreferredReactions();
- }
- }}
- >
- {i18n('save')}
-
-
);
}
diff --git a/ts/components/ErrorModal.tsx b/ts/components/ErrorModal.tsx
index 981a9257beb3..43edb586e987 100644
--- a/ts/components/ErrorModal.tsx
+++ b/ts/components/ErrorModal.tsx
@@ -25,27 +25,23 @@ function focusRef(el: HTMLElement | null) {
export const ErrorModal = (props: PropsType): JSX.Element => {
const { buttonText, description, i18n, onClose, title } = props;
+ const footer = (
+
+ {buttonText || i18n('Confirmation--confirm')}
+
+ );
+
return (
- <>
-
- {description || i18n('ErrorModal--description')}
-
-
-
- {buttonText || i18n('Confirmation--confirm')}
-
-
- >
+
+ {description || i18n('ErrorModal--description')}
+
);
};
diff --git a/ts/components/Modal.stories.tsx b/ts/components/Modal.stories.tsx
index b30f56cdc93d..42f2a978c9c7 100644
--- a/ts/components/Modal.stories.tsx
+++ b/ts/components/Modal.stories.tsx
@@ -46,14 +46,15 @@ BareBonesLong.story = {
};
export const BareBonesLongWithButton = (): JSX.Element => (
-
+ Okay }
+ >
{LOREM_IPSUM}
{LOREM_IPSUM}
{LOREM_IPSUM}
{LOREM_IPSUM}
-
- Okay
-
);
@@ -68,11 +69,9 @@ export const TitleXButtonBodyAndButtonFooter = (): JSX.Element => (
title="Hello world"
onClose={onClose}
hasXButton
+ modalFooter={
Okay }
>
{LOREM_IPSUM}
-
- Okay
-
);
@@ -81,21 +80,27 @@ TitleXButtonBodyAndButtonFooter.story = {
};
export const LotsOfButtonsInTheFooter = (): JSX.Element => (
-
+
+ Okay X
+ Okay
+ Okay
+
+ This is a button with a fairly large amount of text
+
+ Okay
+
+ This is a button with a fairly large amount of text
+
+ Okay
+ >
+ }
+ >
Hello world!
-
- Okay
- Okay
- Okay
-
- This is a button with a fairly large amount of text
-
- Okay
-
- This is a button with a fairly large amount of text
-
- Okay
-
);
@@ -123,14 +128,17 @@ LongBodyWithTitle.story = {
};
export const LongBodyWithTitleAndButton = (): JSX.Element => (
-
+ Okay}
+ >
{LOREM_IPSUM}
{LOREM_IPSUM}
{LOREM_IPSUM}
{LOREM_IPSUM}
-
- Okay
-
);
@@ -160,19 +168,20 @@ LongBodyWithLongTitleAndXButton.story = {
export const WithStickyButtonsLongBody = (): JSX.Element => (
+ Okay
+ Okay
+ >
+ }
>
{LOREM_IPSUM}
{LOREM_IPSUM}
{LOREM_IPSUM}
{LOREM_IPSUM}
-
- Okay
- Okay
-
);
@@ -183,16 +192,17 @@ WithStickyButtonsLongBody.story = {
export const WithStickyButtonsShortBody = (): JSX.Element => (
+ Okay
+ Okay
+ >
+ }
>
{LOREM_IPSUM.slice(0, 140)}
-
- Okay
- Okay
-
);
@@ -203,25 +213,26 @@ WithStickyButtonsShortBody.story = {
export const StickyFooterLotsOfButtons = (): JSX.Element => (
+ Okay
+ Okay
+ Okay
+
+ This is a button with a fairly large amount of text
+
+ Okay
+
+ This is a button with a fairly large amount of text
+
+ Okay
+ >
+ }
>
{LOREM_IPSUM}
-
- Okay
- Okay
- Okay
-
- This is a button with a fairly large amount of text
-
- Okay
-
- This is a button with a fairly large amount of text
-
- Okay
-
);
diff --git a/ts/components/Modal.tsx b/ts/components/Modal.tsx
index aab2024ee9a6..cad8bd77d53c 100644
--- a/ts/components/Modal.tsx
+++ b/ts/components/Modal.tsx
@@ -20,7 +20,6 @@ import { useRefMerger } from '../hooks/useRefMerger';
type PropsType = {
children: ReactNode;
modalName: string;
- hasStickyButtons?: boolean;
hasXButton?: boolean;
i18n: LocalizerType;
modalFooter?: JSX.Element;
@@ -29,9 +28,10 @@ type PropsType = {
onClose?: () => void;
title?: ReactNode;
useFocusTrap?: boolean;
+ padded?: boolean;
};
-type ModalPropsType = PropsType & {
+export type ModalPropsType = PropsType & {
noMouseClose?: boolean;
theme?: Theme;
};
@@ -41,7 +41,6 @@ const BASE_CLASS_NAME = 'module-Modal';
export function Modal({
children,
modalName,
- hasStickyButtons,
hasXButton,
i18n,
modalFooter,
@@ -52,6 +51,7 @@ export function Modal({
theme,
title,
useFocusTrap,
+ padded = true,
}: Readonly): ReactElement {
const { close, modalStyles, overlayStyles } = useAnimated(onClose, {
getFrom: () => ({ opacity: 0, transform: 'translateY(48px)' }),
@@ -72,9 +72,8 @@ export function Modal({
useFocusTrap={useFocusTrap}
>
-
{children}
-
+
);
}
-export function ModalWindow({
+type ModalPageProps = Readonly<{
+ // should be the one provided by PagedModal
+ onClose: () => void;
+}> &
+ Omit, 'onClose'>;
+
+/**
+ * Represents a single instance (or page) of a modal window.
+ *
+ * It should not be used by itself, either wrap it with PagedModal,
+ * render it in a component that has PagedModal as an ancestor, or
+ * use Modal instead.
+ *
+ * It does not provide open/close animation.
+ *
+ * NOTE: When used in conjunction with PagedModal (almost always the case):
+ * onClose" handler should be the one provided by the parent PagedModal,
+ * not one that has any logic. If you have some logic to execute when the
+ * modal closes, pass it to PagedModal.
+ */
+export function ModalPage({
children,
- hasStickyButtons,
hasXButton,
i18n,
modalFooter,
moduleClassName,
onBackButtonClick,
- onClose = noop,
+ onClose,
title,
-}: Readonly): JSX.Element {
+ padded = true,
+}: ModalPageProps): JSX.Element {
const modalRef = useRef(null);
const refMerger = useRefMerger();
@@ -131,7 +151,7 @@ export function ModalWindow({
className={classNames(
getClassName(''),
getClassName(hasHeader ? '--has-header' : '--no-header'),
- hasStickyButtons && getClassName('--sticky-buttons')
+ padded && getClassName('--padded')
)}
ref={modalRef}
onClick={event => {
@@ -200,7 +220,7 @@ export function ModalWindow({
)}
- {modalFooter}
+ {modalFooter && {modalFooter} }
>
);
@@ -208,17 +228,12 @@ export function ModalWindow({
Modal.ButtonFooter = function ButtonFooter({
children,
- moduleClassName,
}: Readonly<{
children: ReactNode;
- moduleClassName?: string;
}>): ReactElement {
const [ref, hasWrapped] = useHasWrapped();
- const className = getClassNamesFor(
- BASE_CLASS_NAME,
- moduleClassName
- )('__button-footer');
+ const className = getClassNamesFor(BASE_CLASS_NAME)('__button-footer');
return (
);
};
+
+type PagedModalProps = Readonly<{
+ modalName: string;
+ children: RenderModalPage;
+ moduleClassName?: string;
+ onClose?: () => void;
+ useFocusTrap?: boolean;
+ noMouseClose?: boolean;
+ theme?: Theme;
+}>;
+
+/**
+ * Provides modal animation and click to close functionality to a
+ * ModalPage descendant.
+ *
+ * Useful when we want to swap between different ModalPages (possibly
+ * rendered by different components) without triggering an open/close
+ * transition animation.
+ */
+export function PagedModal({
+ modalName,
+ children,
+ moduleClassName,
+ noMouseClose,
+ onClose = noop,
+ theme,
+ useFocusTrap,
+}: PagedModalProps): ReactElement {
+ 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)' },
+ });
+
+ return (
+
+ {children(close)}
+
+ );
+}
+
+export type RenderModalPage = (onClose: () => void) => JSX.Element;
diff --git a/ts/components/NeedsScreenRecordingPermissionsModal.tsx b/ts/components/NeedsScreenRecordingPermissionsModal.tsx
index 21f1718ee0b1..e1f880e0d845 100644
--- a/ts/components/NeedsScreenRecordingPermissionsModal.tsx
+++ b/ts/components/NeedsScreenRecordingPermissionsModal.tsx
@@ -24,6 +24,26 @@ export const NeedsScreenRecordingPermissionsModal = ({
openSystemPreferencesAction,
toggleScreenRecordingPermissionsDialog,
}: PropsType): JSX.Element => {
+ const footer = (
+ <>
+
+ {i18n('calling__presenting--permission-cancel')}
+
+
{
+ openSystemPreferencesAction();
+ toggleScreenRecordingPermissionsDialog();
+ }}
+ variant={ButtonVariant.Primary}
+ >
+ {i18n('calling__presenting--permission-open')}
+
+ >
+ );
return (
{i18n('calling__presenting--macos-permission-description')}
@@ -38,24 +59,6 @@ export const NeedsScreenRecordingPermissionsModal = ({
{i18n('calling__presenting--permission-instruction-step2')}
{i18n('calling__presenting--permission-instruction-step3')}
-
-
- {i18n('calling__presenting--permission-cancel')}
-
- {
- openSystemPreferencesAction();
- toggleScreenRecordingPermissionsDialog();
- }}
- variant={ButtonVariant.Primary}
- >
- {i18n('calling__presenting--permission-open')}
-
-
);
};
diff --git a/ts/components/ProfileEditorModal.tsx b/ts/components/ProfileEditorModal.tsx
index 7fe4df37831a..37fa16b0e919 100644
--- a/ts/components/ProfileEditorModal.tsx
+++ b/ts/components/ProfileEditorModal.tsx
@@ -61,7 +61,6 @@ export const ProfileEditorModal = ({
<>
>(initialMyStoriesMemberUuids);
- let content: JSX.Element;
- if (page === Page.SetMyStoriesPrivacy) {
- content = (
- {
- let nextSelectedContacts = stagedMyStories.members;
+ let selectedNames: string | undefined;
+ if (page === Page.ChooseGroups) {
+ selectedNames = chosenGroupNames.join(', ');
+ } else {
+ selectedNames = selectedStoryNames
+ .map(listName => getStoryDistributionListName(i18n, listName, listName))
+ .join(', ');
+ }
- if (!stagedMyStories.isBlockList) {
+ const modalCommonProps: Pick = {
+ hasXButton: true,
+ i18n,
+ };
+
+ let modal: RenderModalPage;
+ if (page === Page.SetMyStoriesPrivacy) {
+ const footer = (
+ <>
+
+
+ setPage(Page.SendStory)}
+ variant={ButtonVariant.Secondary}
+ >
+ {i18n('cancel')}
+
+ {
+ if (stagedMyStories.isBlockList) {
+ if (stagedMyStories.members.length) {
+ onHideMyStoriesFrom(stagedMyStoriesMemberUuids);
+ } else {
+ setMyStoriesToAllSignalConnections();
+ }
+ } else {
+ onViewersUpdated(MY_STORIES_ID, stagedMyStoriesMemberUuids);
+ }
+
+ setSelectedContacts([]);
+ setPage(Page.SendStory);
+ }}
+ variant={ButtonVariant.Primary}
+ >
+ {i18n('save')}
+
+
+ >
+ );
+
+ modal = handleClose => (
+
+ {
+ let nextSelectedContacts = stagedMyStories.members;
+
+ if (!stagedMyStories.isBlockList) {
+ setStagedMyStories(myStories => ({
+ ...myStories,
+ isBlockList: true,
+ members: [],
+ }));
+ nextSelectedContacts = [];
+ }
+
+ setSelectedContacts(nextSelectedContacts);
+
+ setPage(Page.HideStoryFrom);
+ }}
+ onClickOnlyShareWith={() => {
+ if (!stagedMyStories.isBlockList) {
+ setSelectedContacts(stagedMyStories.members);
+ } else {
+ setStagedMyStories(myStories => ({
+ ...myStories,
+ isBlockList: false,
+ members: [],
+ }));
+ }
+
+ setPage(Page.AddViewer);
+ }}
+ setSelectedContacts={setSelectedContacts}
+ setMyStoriesToAllSignalConnections={() => {
setStagedMyStories(myStories => ({
...myStories,
isBlockList: true,
members: [],
}));
- nextSelectedContacts = [];
- }
-
- setSelectedContacts(nextSelectedContacts);
-
- setPage(Page.HideStoryFrom);
- }}
- onClickOnlyShareWith={() => {
- if (!stagedMyStories.isBlockList) {
- setSelectedContacts(stagedMyStories.members);
- } else {
- setStagedMyStories(myStories => ({
- ...myStories,
- isBlockList: false,
- members: [],
- }));
- }
-
- setPage(Page.AddViewer);
- }}
- setSelectedContacts={setSelectedContacts}
- setMyStoriesToAllSignalConnections={() => {
- setStagedMyStories(myStories => ({
- ...myStories,
- isBlockList: true,
- members: [],
- }));
- setSelectedContacts([]);
- }}
- toggleSignalConnectionsModal={toggleSignalConnectionsModal}
- />
+ setSelectedContacts([]);
+ }}
+ toggleSignalConnectionsModal={toggleSignalConnectionsModal}
+ />
+
);
} else if (page === Page.EditingDistributionList && listToEdit) {
- content = (
- (
+ setListIdToEdit(undefined)}
+ onClose={handleClose}
/>
);
} else if (
@@ -324,8 +382,8 @@ export const SendStoryModal = ({
page === Page.AddViewer ||
page === Page.HideStoryFrom
) {
- content = (
- (
+ {
+ if (listIdToEdit) {
+ if (
+ page === Page.AddViewer ||
+ page === Page.HideStoryFrom ||
+ page === Page.ChooseViewers
+ ) {
+ setPage(Page.EditingDistributionList);
+ } else {
+ setListIdToEdit(undefined);
+ }
+ } else if (page === Page.HideStoryFrom || page === Page.AddViewer) {
+ setSelectedContacts([]);
+ setStagedMyStories(initialMyStories);
+ setStagedMyStoriesMemberUuids(initialMyStoriesMemberUuids);
+ setPage(Page.SetMyStoriesPrivacy);
+ } else if (page === Page.ChooseViewers) {
+ setSelectedContacts([]);
+ setPage(Page.SendStory);
+ } else if (page === Page.NameStory) {
+ setPage(Page.ChooseViewers);
+ }
+ }}
selectedContacts={selectedContacts}
setSelectedContacts={setSelectedContacts}
/>
);
} else if (page === Page.ChooseGroups) {
- content = (
+ const footer = (
<>
+ {selectedNames}
+ {
+ toggleGroupsForStorySend(Array.from(chosenGroupIds));
+ setChosenGroupIds(new Set());
+ setPage(Page.SendStory);
+ }}
+ type="button"
+ />
+ >
+ );
+
+ modal = handleClose => (
+
)}
- >
+
);
} else {
- content = (
+ const footer = (
<>
+ {selectedNames}
+ {
+ onSend(Array.from(selectedListIds), Array.from(selectedGroupIds));
+ }}
+ type="button"
+ />
+ >
+ );
+ modal = handleClose => (
+
{i18n('stories')}
))}
- >
- );
- }
-
- let modalTitle: string;
- if (page === Page.SetMyStoriesPrivacy) {
- modalTitle = i18n('SendStoryModal__my-stories-privacy');
- } else if (page === Page.HideStoryFrom) {
- modalTitle = i18n('StoriesSettings__hide-story');
- } else if (page === Page.ChooseGroups) {
- modalTitle = i18n('SendStoryModal__choose-groups');
- } else if (page === Page.NameStory) {
- modalTitle = i18n('StoriesSettings__name-story');
- } else if (page === Page.ChooseViewers || page === Page.AddViewer) {
- modalTitle = i18n('StoriesSettings__choose-viewers');
- } else {
- modalTitle = i18n('SendStoryModal__title');
- }
-
- let selectedNames: string | undefined;
- if (page === Page.ChooseGroups) {
- selectedNames = chosenGroupNames.join(', ');
- } else {
- selectedNames = selectedStoryNames
- .map(listName => getStoryDistributionListName(i18n, listName, listName))
- .join(', ');
- }
-
- const hasBackButton = page !== Page.SendStory;
-
- let modalFooter: JSX.Element | undefined;
- if (
- page === Page.SendStory ||
- page === Page.ChooseGroups ||
- page === Page.SetMyStoriesPrivacy
- ) {
- modalFooter = (
-
- {page !== Page.SetMyStoriesPrivacy && (
- {selectedNames}
- )}
- {page === Page.ChooseGroups && (
- {
- toggleGroupsForStorySend(Array.from(chosenGroupIds));
- setChosenGroupIds(new Set());
- setPage(Page.SendStory);
- }}
- type="button"
- />
- )}
- {page === Page.SendStory && (
- {
- onSend(Array.from(selectedListIds), Array.from(selectedGroupIds));
- }}
- type="button"
- />
- )}
- {page === Page.SetMyStoriesPrivacy && (
- <>
-
-
- setPage(Page.SendStory)}
- variant={ButtonVariant.Secondary}
- >
- {i18n('cancel')}
-
- {
- if (stagedMyStories.isBlockList) {
- if (stagedMyStories.members.length) {
- onHideMyStoriesFrom(stagedMyStoriesMemberUuids);
- } else {
- setMyStoriesToAllSignalConnections();
- }
- } else {
- onViewersUpdated(MY_STORIES_ID, stagedMyStoriesMemberUuids);
- }
-
- setSelectedContacts([]);
- setPage(Page.SendStory);
- }}
- variant={ButtonVariant.Primary}
- >
- {i18n('save')}
-
-
- >
- )}
-
+
);
}
return (
<>
- {
- if (listIdToEdit) {
- if (
- page === Page.AddViewer ||
- page === Page.HideStoryFrom ||
- page === Page.ChooseViewers
- ) {
- setPage(Page.EditingDistributionList);
- } else {
- setListIdToEdit(undefined);
- }
- } else if (page === Page.SetMyStoriesPrivacy) {
- setSelectedContacts([]);
- setStagedMyStories(initialMyStories);
- setStagedMyStoriesMemberUuids(initialMyStoriesMemberUuids);
- setPage(Page.SendStory);
- } else if (
- page === Page.HideStoryFrom ||
- page === Page.AddViewer
- ) {
- setSelectedContacts([]);
- setStagedMyStories(initialMyStories);
- setStagedMyStoriesMemberUuids(initialMyStoriesMemberUuids);
- setPage(Page.SetMyStoriesPrivacy);
- } else if (page === Page.ChooseGroups) {
- setChosenGroupIds(new Set());
- setPage(Page.SendStory);
- } else if (page === Page.ChooseViewers) {
- setSelectedContacts([]);
- setPage(Page.SendStory);
- } else if (page === Page.NameStory) {
- setPage(Page.ChooseViewers);
- }
- }
- : undefined
- }
- onClose={onClose}
- title={modalTitle}
theme={Theme.Dark}
+ onClose={onClose}
>
- {content}
-
+ {modal}
+
{confirmRemoveGroupId && (
conversation.uuid);
}
+const modalCommonProps: Pick =
+ {
+ hasXButton: true,
+ moduleClassName: 'StoriesSettingsModal__modal',
+ };
+
export const StoriesSettingsModal = ({
candidateConversations,
distributionLists,
@@ -120,18 +127,34 @@ export const StoriesSettingsModal = ({
string | undefined
>();
- let content: JSX.Element | null;
+ let modal: RenderModalPage | null;
if (page !== Page.DistributionLists) {
- content = (
- (
+ {
onDistributionListCreated(name, uuids);
resetChooseViewersScreen();
}}
+ onBackButtonClick={() => {
+ if (page === Page.HideStoryFrom) {
+ resetChooseViewersScreen();
+ } else if (page === Page.NameStory) {
+ setPage(Page.ChooseViewers);
+ } else if (isChoosingViewers) {
+ resetChooseViewersScreen();
+ } else if (listToEdit) {
+ setListToEditId(undefined);
+ }
+ }}
onViewersUpdated={uuids => {
if (listToEditId && page === Page.AddViewer) {
onViewersUpdated(listToEditId, uuids);
@@ -147,14 +170,14 @@ export const StoriesSettingsModal = ({
resetChooseViewersScreen();
}
}}
- page={page}
selectedContacts={selectedContacts}
setSelectedContacts={setSelectedContacts}
/>
);
} else if (listToEdit) {
- content = (
- (
+ setListToEditId(undefined)}
+ onClose={onClose}
/>
);
} else {
@@ -172,8 +197,14 @@ export const StoriesSettingsModal = ({
list => list.id !== MY_STORIES_ID
);
- content = (
- <>
+ modal = onClose => (
+
{
@@ -244,61 +275,19 @@ export const StoriesSettingsModal = ({
))}
- >
+
);
}
- const isChoosingViewers =
- page === Page.ChooseViewers || page === Page.AddViewer;
-
- let modalTitle: string = i18n('StoriesSettings__title');
- if (page === Page.HideStoryFrom) {
- modalTitle = i18n('StoriesSettings__hide-story');
- } else if (page === Page.NameStory) {
- modalTitle = i18n('StoriesSettings__name-story');
- } else if (isChoosingViewers) {
- modalTitle = i18n('StoriesSettings__choose-viewers');
- } else if (listToEdit) {
- modalTitle = getStoryDistributionListName(
- i18n,
- listToEdit.id,
- listToEdit.name
- );
- }
-
- const hasBackButton = page !== Page.DistributionLists || listToEdit;
- const hasStickyButtons =
- isChoosingViewers || page === Page.NameStory || page === Page.HideStoryFrom;
-
return (
<>
- {
- if (page === Page.HideStoryFrom) {
- resetChooseViewersScreen();
- } else if (page === Page.NameStory) {
- setPage(Page.ChooseViewers);
- } else if (isChoosingViewers) {
- resetChooseViewersScreen();
- } else if (listToEdit) {
- setListToEditId(undefined);
- }
- }
- : undefined
- }
- onClose={hideStoriesSettings}
theme={Theme.Dark}
- title={modalTitle}
+ onClose={hideStoriesSettings}
>
- {content}
-
+ {modal}
+
{confirmDeleteListId && (
unknown;
setPage: (page: Page) => unknown;
setSelectedContacts: (contacts: Array) => unknown;
+ onBackButtonClick: (() => void) | undefined;
+ onClose: () => void;
} & Pick<
PropsType,
| 'getPreferredBadge'
@@ -339,18 +330,20 @@ type DistributionListSettingsPropsType = {
| 'toggleSignalConnectionsModal'
>;
-export const DistributionListSettings = ({
+export const DistributionListSettingsModal = ({
getPreferredBadge,
i18n,
listToEdit,
onRemoveMember,
onRepliesNReactionsChanged,
+ onBackButtonClick,
+ onClose,
setConfirmDeleteListId,
setMyStoriesToAllSignalConnections,
setPage,
setSelectedContacts,
toggleSignalConnectionsModal,
-}: DistributionListSettingsPropsType): JSX.Element => {
+}: DistributionListSettingsModalPropsType): JSX.Element => {
const [confirmRemoveMember, setConfirmRemoveMember] = useState<
| undefined
| {
@@ -362,8 +355,21 @@ export const DistributionListSettings = ({
const isMyStories = listToEdit.id === MY_STORIES_ID;
+ const modalTitle = getStoryDistributionListName(
+ i18n,
+ listToEdit.id,
+ listToEdit.name
+ );
+
return (
- <>
+
{!isMyStories && (
<>
@@ -521,7 +527,7 @@ export const DistributionListSettings = ({
{i18n('StoriesSettings__remove--body')}
)}
- >
+
);
};
@@ -630,24 +636,37 @@ export const EditMyStoriesPrivacy = ({
);
};
-type EditDistributionListPropsType = {
+type EditDistributionListModalPropsType = {
onCreateList: (name: string, viewerUuids: Array
) => unknown;
onViewersUpdated: (viewerUuids: Array) => unknown;
- page: Page;
+ page:
+ | Page.AddViewer
+ | Page.ChooseViewers
+ | Page.HideStoryFrom
+ | Page.NameStory;
selectedContacts: Array;
+ onClose: () => unknown;
setSelectedContacts: (contacts: Array) => unknown;
+ onBackButtonClick: () => void;
} & Pick;
-export const EditDistributionList = ({
+/**
+ *
+ * @param param0
+ * @returns
+ */
+export const EditDistributionListModal = ({
candidateConversations,
getPreferredBadge,
i18n,
onCreateList,
onViewersUpdated,
page,
+ onClose,
selectedContacts,
setSelectedContacts,
-}: EditDistributionListPropsType): JSX.Element | null => {
+ onBackButtonClick,
+}: EditDistributionListModalPropsType): JSX.Element => {
const [storyName, setStoryName] = useState('');
const [searchTerm, setSearchTerm] = useState('');
@@ -668,18 +687,6 @@ export const EditDistributionList = ({
};
}, [candidateConversations, normalizedSearchTerm, setFilteredConversations]);
- const isEditingDistributionList =
- page === Page.AddViewer ||
- page === Page.ChooseViewers ||
- page === Page.NameStory ||
- page === Page.HideStoryFrom;
-
- useEffect(() => {
- if (!isEditingDistributionList) {
- setSearchTerm('');
- }
- }, [isEditingDistributionList]);
-
const contactLookup = useMemo(() => {
const map = new Map();
candidateConversations.forEach(contact => {
@@ -720,8 +727,29 @@ export const EditDistributionList = ({
page === Page.ChooseViewers || page === Page.AddViewer;
if (page === Page.NameStory) {
+ const footer = (
+ {
+ onCreateList(storyName, Array.from(selectedConversationUuids));
+ setStoryName('');
+ }}
+ variant={ButtonVariant.Primary}
+ >
+ {i18n('done')}
+
+ );
+
return (
- <>
+
@@ -762,143 +790,137 @@ export const EditDistributionList = ({
))}
-
- {
- onCreateList(storyName, Array.from(selectedConversationUuids));
- setStoryName('');
- }}
- variant={ButtonVariant.Primary}
- >
- {i18n('done')}
-
-
- >
+
);
}
- if (
- page === Page.AddViewer ||
- page === Page.ChooseViewers ||
- page === Page.HideStoryFrom
- ) {
- const rowCount = filteredConversations.length;
- const getRow = (index: number): undefined | Row => {
- const contact = filteredConversations[index];
- if (!contact || !contact.uuid) {
- return undefined;
- }
+ const rowCount = filteredConversations.length;
+ const getRow = (index: number): undefined | Row => {
+ const contact = filteredConversations[index];
+ if (!contact || !contact.uuid) {
+ return undefined;
+ }
- const isSelected = selectedConversationUuids.has(UUID.cast(contact.uuid));
+ const isSelected = selectedConversationUuids.has(UUID.cast(contact.uuid));
- return {
- type: RowType.ContactCheckbox,
- contact,
- isChecked: isSelected,
- };
+ return {
+ type: RowType.ContactCheckbox,
+ contact,
+ isChecked: isSelected,
};
+ };
- return (
- <>
- {
- setSearchTerm(event.target.value);
- }}
- value={searchTerm}
- />
- {selectedContacts.length ? (
-
- {selectedContacts.map(contact => (
- toggleSelectedConversation(contact.id)}
- />
- ))}
-
- ) : undefined}
- {candidateConversations.length ? (
-
- {({ contentRect, measureRef }: MeasuredComponentProps) => (
-
-
{
- toggleSelectedConversation(conversationId);
- }}
- lookupConversationWithoutUuid={asyncShouldNeverBeCalled}
- showConversation={shouldNeverBeCalled}
- showUserNotFoundModal={shouldNeverBeCalled}
- setIsFetchingUUID={shouldNeverBeCalled}
- onSelectConversation={shouldNeverBeCalled}
- renderMessageSearchResult={() => {
- shouldNeverBeCalled();
- return
;
- }}
- rowCount={rowCount}
- shouldRecomputeRowHeights={false}
- showChooseGroupMembers={shouldNeverBeCalled}
- theme={ThemeType.dark}
- />
-
- )}
-
- ) : (
-
- {i18n('noContactsFound')}
-
- )}
- {isChoosingViewers && (
-
- {
- onViewersUpdated(Array.from(selectedConversationUuids));
- }}
- variant={ButtonVariant.Primary}
- >
- {page === Page.AddViewer ? i18n('done') : i18n('next2')}
-
-
- )}
- {page === Page.HideStoryFrom && (
-
- {
- onViewersUpdated(Array.from(selectedConversationUuids));
- }}
- variant={ButtonVariant.Primary}
- >
- {i18n('update')}
-
-
- )}
- >
+ let footer: JSX.Element | undefined;
+ if (isChoosingViewers) {
+ footer = (
+ {
+ onViewersUpdated(Array.from(selectedConversationUuids));
+ }}
+ variant={ButtonVariant.Primary}
+ >
+ {page === Page.AddViewer ? i18n('done') : i18n('next2')}
+
+ );
+ } else if (page === Page.HideStoryFrom) {
+ footer = (
+ {
+ onViewersUpdated(Array.from(selectedConversationUuids));
+ }}
+ variant={ButtonVariant.Primary}
+ >
+ {i18n('update')}
+
);
}
- return null;
+ return (
+
+ {
+ setSearchTerm(event.target.value);
+ }}
+ value={searchTerm}
+ />
+ {selectedContacts.length ? (
+
+ {selectedContacts.map(contact => (
+ toggleSelectedConversation(contact.id)}
+ />
+ ))}
+
+ ) : undefined}
+ {candidateConversations.length ? (
+
+ {({ contentRect, measureRef }: MeasuredComponentProps) => (
+
+
{
+ toggleSelectedConversation(conversationId);
+ }}
+ lookupConversationWithoutUuid={asyncShouldNeverBeCalled}
+ showConversation={shouldNeverBeCalled}
+ showUserNotFoundModal={shouldNeverBeCalled}
+ setIsFetchingUUID={shouldNeverBeCalled}
+ onSelectConversation={shouldNeverBeCalled}
+ renderMessageSearchResult={() => {
+ shouldNeverBeCalled();
+ return
;
+ }}
+ rowCount={rowCount}
+ shouldRecomputeRowHeights={false}
+ showChooseGroupMembers={shouldNeverBeCalled}
+ theme={ThemeType.dark}
+ />
+
+ )}
+
+ ) : (
+
+ {i18n('noContactsFound')}
+
+ )}
+
+ );
};
diff --git a/ts/components/conversation/ContactModal.tsx b/ts/components/conversation/ContactModal.tsx
index 827df068f4f2..8efaef420fc7 100644
--- a/ts/components/conversation/ContactModal.tsx
+++ b/ts/components/conversation/ContactModal.tsx
@@ -172,6 +172,7 @@ export const ContactModal = ({
hasXButton
i18n={i18n}
onClose={hideContactModal}
+ padded={false}
>
+
+ {i18n('DeliveryIssue--learnMore')}
+
+
+ {i18n('Confirmation--confirm')}
+
+ >
+ );
+
return (
@@ -60,24 +82,6 @@ export function DeliveryIssueDialog(props: PropsType): React.ReactElement {
/>
-
-
- {i18n('DeliveryIssue--learnMore')}
-
-
- {i18n('Confirmation--confirm')}
-
-
);
}
diff --git a/ts/components/conversation/conversation-details/ConversationNotificationsModal.tsx b/ts/components/conversation/conversation-details/ConversationNotificationsModal.tsx
index bc51ae421823..e35bfdbaaccb 100644
--- a/ts/components/conversation/conversation-details/ConversationNotificationsModal.tsx
+++ b/ts/components/conversation/conversation-details/ConversationNotificationsModal.tsx
@@ -47,11 +47,20 @@ export const ConversationNotificationsModal = ({
return (
+
+ {i18n('cancel')}
+
+
+ {i18n('mute')}
+
+ >
+ }
>
{muteOptions
.filter(x => x.value > 0)
@@ -67,14 +76,6 @@ export const ConversationNotificationsModal = ({
onChange={value => value && setMuteExpirationValue(option.value)}
/>
))}
-
-
- {i18n('cancel')}
-
-
- {i18n('mute')}
-
-
);
};
diff --git a/ts/components/conversation/conversation-details/EditConversationAttributesModal.tsx b/ts/components/conversation/conversation-details/EditConversationAttributesModal.tsx
index 41f057792c35..b628ac2a35dc 100644
--- a/ts/components/conversation/conversation-details/EditConversationAttributesModal.tsx
+++ b/ts/components/conversation/conversation-details/EditConversationAttributesModal.tsx
@@ -155,6 +155,7 @@ export const EditConversationAttributesModal: FunctionComponent = ({
} else {
content = (
)}
-
-
-
- {i18n('cancel')}
-
-
-
- {isRequestActive ? (
-
- ) : (
- i18n('save')
- )}
-
-
);
}
+ const modalFooter = (
+ <>
+
+ {i18n('cancel')}
+
+
+
+ {isRequestActive ? (
+
+ ) : (
+ i18n('save')
+ )}
+
+ >
+ );
+
return (
{content}
diff --git a/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx b/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx
index a2c2ec160c4d..d94651c2cbc3 100644
--- a/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx
+++ b/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx
@@ -152,7 +152,6 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper