Refactored and cleaned up Modal and friends
This commit is contained in:
parent
f64426fbe0
commit
00a720faa9
31 changed files with 853 additions and 787 deletions
|
@ -3,6 +3,9 @@
|
||||||
|
|
||||||
// Using BEM syntax explained here: https://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/
|
// 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 <Modal.../>
|
||||||
|
|
||||||
.module-title-bar-drag-area {
|
.module-title-bar-drag-area {
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
height: var(--title-bar-drag-area-height);
|
height: var(--title-bar-drag-area-height);
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
div.AddUserToAnotherGroupModal__body {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.AddUserToAnotherGroupModal {
|
.AddUserToAnotherGroupModal {
|
||||||
&__main-body {
|
&__main-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -9,8 +9,7 @@
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
// We use this selector for specificity.
|
&__width-container {
|
||||||
&.module-Modal {
|
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
.BadgeSustainerInstructionsDialog {
|
.BadgeSustainerInstructionsDialog {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
// We use this selector for specificity.
|
&__width-container {
|
||||||
&.module-Modal {
|
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,27 +2,23 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
.module-CallingSelectPresentingSourcesModal {
|
.module-CallingSelectPresentingSourcesModal {
|
||||||
// specificity
|
&__width-container {
|
||||||
&.module-Modal {
|
|
||||||
max-width: 665px;
|
max-width: 665px;
|
||||||
position: relative;
|
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;
|
background-color: $color-gray-95;
|
||||||
bottom: 0;
|
|
||||||
margin-left: -16px;
|
|
||||||
margin-top: 0;
|
|
||||||
padding: 16px;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__sources {
|
&__sources {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 34px;
|
||||||
margin-left: -6px;
|
display: flex;
|
||||||
margin-right: -6px;
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -38,9 +34,6 @@
|
||||||
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid $color-gray-60;
|
border: 1px solid $color-gray-60;
|
||||||
margin-bottom: 14px;
|
|
||||||
margin-left: 6px;
|
|
||||||
margin-right: 6px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -71,8 +71,4 @@
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__modal__body {
|
|
||||||
overflow-x: hidden !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
@include font-title-2;
|
@include font-title-2;
|
||||||
|
@ -143,8 +144,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-Modal.ContactModal__modal .ContactModal__modal__body {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
.module-Modal {
|
.module-Modal {
|
||||||
@include popper-shadow();
|
@include popper-shadow();
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
// We need this to be a number not divisible by 5 so that if we have sticky
|
// 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.
|
// buttons the bottom doesn't bleed through by 1px.
|
||||||
max-height: 89vh;
|
max-height: 89vh;
|
||||||
|
@ -23,9 +24,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 1em;
|
padding: 16px 16px 1em 16px;
|
||||||
padding: 16px 16px 0 16px;
|
|
||||||
position: sticky;
|
|
||||||
|
|
||||||
&--with-back-button .module-Modal__title {
|
&--with-back-button .module-Modal__title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -132,16 +131,20 @@
|
||||||
@include scrollbar;
|
@include scrollbar;
|
||||||
@include font-body-1;
|
@include font-body-1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
overflow-y: overlay;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--padded {
|
||||||
|
.module-Modal__body {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&--has-header {
|
&--has-header {
|
||||||
.module-Modal__body {
|
.module-Modal__body {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
border-top: 1px solid transparent;
|
border-top: 1px solid transparent;
|
||||||
// If there's a header, just the body scrolls
|
|
||||||
overflow-y: overlay;
|
|
||||||
overflow-x: auto;
|
|
||||||
|
|
||||||
&--scrolled {
|
&--scrolled {
|
||||||
@include light-theme {
|
@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 {
|
&__button-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 8px;
|
align-items: center;
|
||||||
|
padding: 1em 16px 16px 16px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
.module-Button {
|
.module-Button {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--one-button-per-line {
|
&--one-button-per-line {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-end;
|
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
|
// Overrides for a modal with important message
|
||||||
|
@ -251,6 +211,7 @@
|
||||||
margin-top: 27px;
|
margin-top: 27px;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
padding: 0 12px 4px 12px;
|
||||||
|
|
||||||
.module-Button {
|
.module-Button {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
|
@ -2,6 +2,27 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
.SendStoryModal {
|
.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 {
|
&__top-bar {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -85,7 +106,6 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
|
@ -164,8 +184,9 @@
|
||||||
&__selected-lists {
|
&__selected-lists {
|
||||||
@include font-body-2;
|
@include font-body-2;
|
||||||
color: $color-gray-15;
|
color: $color-gray-15;
|
||||||
max-width: 280px;
|
padding-right: 16px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__ok {
|
&__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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,9 +2,13 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
.StoriesSettingsModal {
|
.StoriesSettingsModal {
|
||||||
|
&__modal__body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
&__conversation-list {
|
&__conversation-list {
|
||||||
.module-conversation-list,
|
.module-conversation-list {
|
||||||
.module-conversation-list__item--contact-or-conversation {
|
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
@ -194,20 +198,6 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__search {
|
|
||||||
&__container {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__tags {
|
|
||||||
margin: 0 -4px;
|
|
||||||
|
|
||||||
// Override .module-ContactPills
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__name-story-avatar-container {
|
&__name-story-avatar-container {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -148,6 +148,7 @@ export const AddUserToAnotherGroupModal = ({
|
||||||
onClose={toggleAddUserToAnotherGroupModal}
|
onClose={toggleAddUserToAnotherGroupModal}
|
||||||
title={i18n('AddUserToAnotherGroupModal__title')}
|
title={i18n('AddUserToAnotherGroupModal__title')}
|
||||||
moduleClassName="AddUserToAnotherGroupModal"
|
moduleClassName="AddUserToAnotherGroupModal"
|
||||||
|
padded={false}
|
||||||
>
|
>
|
||||||
<div className="AddUserToAnotherGroupModal__main-body">
|
<div className="AddUserToAnotherGroupModal__main-body">
|
||||||
<SearchInput
|
<SearchInput
|
||||||
|
|
|
@ -21,10 +21,15 @@ export const Alert: FunctionComponent<PropsType> = ({
|
||||||
onClose,
|
onClose,
|
||||||
title,
|
title,
|
||||||
}) => (
|
}) => (
|
||||||
<Modal modalName="Alert" i18n={i18n} onClose={onClose} title={title}>
|
<Modal
|
||||||
{body}
|
modalName="Alert"
|
||||||
<Modal.ButtonFooter>
|
i18n={i18n}
|
||||||
|
onClose={onClose}
|
||||||
|
title={title}
|
||||||
|
modalFooter={
|
||||||
<Button onClick={onClose}>{i18n('Confirmation--confirm')}</Button>
|
<Button onClick={onClose}>{i18n('Confirmation--confirm')}</Button>
|
||||||
</Modal.ButtonFooter>
|
}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -58,6 +58,7 @@ type PropsType = {
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'submit';
|
type: 'submit';
|
||||||
|
form?: string;
|
||||||
}
|
}
|
||||||
) &
|
) &
|
||||||
(
|
(
|
||||||
|
@ -117,12 +118,14 @@ export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
|
||||||
|
|
||||||
let onClick: undefined | MouseEventHandler<HTMLButtonElement>;
|
let onClick: undefined | MouseEventHandler<HTMLButtonElement>;
|
||||||
let type: 'button' | 'submit';
|
let type: 'button' | 'submit';
|
||||||
|
let form;
|
||||||
if ('onClick' in props) {
|
if ('onClick' in props) {
|
||||||
({ onClick } = props);
|
({ onClick } = props);
|
||||||
type = 'button';
|
type = 'button';
|
||||||
} else {
|
} else {
|
||||||
onClick = undefined;
|
onClick = undefined;
|
||||||
({ type } = props);
|
({ type } = props);
|
||||||
|
({ form } = props);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizeClassName = SIZE_CLASS_NAMES.get(size);
|
const sizeClassName = SIZE_CLASS_NAMES.get(size);
|
||||||
|
@ -143,6 +146,7 @@ export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
|
||||||
)}
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
form={form}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={style}
|
style={style}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
|
|
|
@ -140,6 +140,7 @@ export const CallingDeviceSelection = ({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
theme={Theme.Dark}
|
theme={Theme.Dark}
|
||||||
onClose={toggleSettings}
|
onClose={toggleSettings}
|
||||||
|
padded={false}
|
||||||
>
|
>
|
||||||
<div className="module-calling-device-selection">
|
<div className="module-calling-device-selection">
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -82,6 +82,20 @@ export const CallingSelectPresentingSourcesModal = ({
|
||||||
source => source.isScreen
|
source => source.isScreen
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const footer = (
|
||||||
|
<>
|
||||||
|
<Button onClick={() => setPresenting()} variant={ButtonVariant.Secondary}>
|
||||||
|
{i18n('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={!sourceToPresent}
|
||||||
|
onClick={() => setPresenting(sourceToPresent)}
|
||||||
|
>
|
||||||
|
{i18n('calling__SelectPresentingSourcesModal--confirm')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="CallingSelectPresentingSourcesModal"
|
modalName="CallingSelectPresentingSourcesModal"
|
||||||
|
@ -93,6 +107,7 @@ export const CallingSelectPresentingSourcesModal = ({
|
||||||
}}
|
}}
|
||||||
theme={Theme.Dark}
|
theme={Theme.Dark}
|
||||||
title={i18n('calling__SelectPresentingSourcesModal--title')}
|
title={i18n('calling__SelectPresentingSourcesModal--title')}
|
||||||
|
modalFooter={footer}
|
||||||
>
|
>
|
||||||
<div className="module-CallingSelectPresentingSourcesModal__title">
|
<div className="module-CallingSelectPresentingSourcesModal__title">
|
||||||
{i18n('calling__SelectPresentingSourcesModal--entireScreen')}
|
{i18n('calling__SelectPresentingSourcesModal--entireScreen')}
|
||||||
|
@ -120,20 +135,6 @@ export const CallingSelectPresentingSourcesModal = ({
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Modal.ButtonFooter moduleClassName="module-CallingSelectPresentingSourcesModal">
|
|
||||||
<Button
|
|
||||||
onClick={() => setPresenting()}
|
|
||||||
variant={ButtonVariant.Secondary}
|
|
||||||
>
|
|
||||||
{i18n('cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled={!sourceToPresent}
|
|
||||||
onClick={() => setPresenting(sourceToPresent)}
|
|
||||||
>
|
|
||||||
{i18n('calling__SelectPresentingSourcesModal--confirm')}
|
|
||||||
</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,6 +34,16 @@ export function CaptchaDialog(props: Readonly<PropsType>): JSX.Element {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isClosing && !isPending) {
|
if (isClosing && !isPending) {
|
||||||
|
const footer = (
|
||||||
|
<>
|
||||||
|
<Button onClick={onCancelClick} variant={ButtonVariant.Secondary}>
|
||||||
|
{i18n('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onSkipClick} variant={ButtonVariant.Destructive}>
|
||||||
|
{i18n('CaptchaDialog--can_close__skip-verification')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="CaptchaDialog"
|
modalName="CaptchaDialog"
|
||||||
|
@ -42,18 +52,11 @@ export function CaptchaDialog(props: Readonly<PropsType>): JSX.Element {
|
||||||
title={i18n('CaptchaDialog--can-close__title')}
|
title={i18n('CaptchaDialog--can-close__title')}
|
||||||
onClose={() => setIsClosing(false)}
|
onClose={() => setIsClosing(false)}
|
||||||
key="skip"
|
key="skip"
|
||||||
|
modalFooter={footer}
|
||||||
>
|
>
|
||||||
<section>
|
<section>
|
||||||
<p>{i18n('CaptchaDialog--can-close__body')}</p>
|
<p>{i18n('CaptchaDialog--can-close__body')}</p>
|
||||||
</section>
|
</section>
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button onClick={onCancelClick} variant={ButtonVariant.Secondary}>
|
|
||||||
{i18n('cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onSkipClick} variant={ButtonVariant.Destructive}>
|
|
||||||
{i18n('CaptchaDialog--can_close__skip-verification')}
|
|
||||||
</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -71,21 +74,7 @@ export function CaptchaDialog(props: Readonly<PropsType>): JSX.Element {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const footer = (
|
||||||
<Modal
|
|
||||||
modalName="CaptchaDialog.pending"
|
|
||||||
moduleClassName="module-Modal--important"
|
|
||||||
i18n={i18n}
|
|
||||||
title={i18n('CaptchaDialog__title')}
|
|
||||||
hasXButton
|
|
||||||
onClose={() => setIsClosing(true)}
|
|
||||||
key="primary"
|
|
||||||
>
|
|
||||||
<section>
|
|
||||||
<p>{i18n('CaptchaDialog__first-paragraph')}</p>
|
|
||||||
<p>{i18n('CaptchaDialog__second-paragraph')}</p>
|
|
||||||
</section>
|
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button
|
<Button
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
onClick={onContinueClick}
|
onClick={onContinueClick}
|
||||||
|
@ -98,7 +87,23 @@ export function CaptchaDialog(props: Readonly<PropsType>): JSX.Element {
|
||||||
'Continue'
|
'Continue'
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.ButtonFooter>
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
modalName="CaptchaDialog.pending"
|
||||||
|
moduleClassName="module-Modal--important"
|
||||||
|
i18n={i18n}
|
||||||
|
title={i18n('CaptchaDialog__title')}
|
||||||
|
hasXButton
|
||||||
|
onClose={() => setIsClosing(true)}
|
||||||
|
key="primary"
|
||||||
|
modalFooter={footer}
|
||||||
|
>
|
||||||
|
<section>
|
||||||
|
<p>{i18n('CaptchaDialog__first-paragraph')}</p>
|
||||||
|
<p>{i18n('CaptchaDialog__second-paragraph')}</p>
|
||||||
|
</section>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { animated } from '@react-spring/web';
|
||||||
import { Button, ButtonVariant } from './Button';
|
import { Button, ButtonVariant } from './Button';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import { ModalHost } from './ModalHost';
|
import { ModalHost } from './ModalHost';
|
||||||
import { Modal, ModalWindow } from './Modal';
|
import { ModalPage } from './Modal';
|
||||||
import type { Theme } from '../util/theme';
|
import type { Theme } from '../util/theme';
|
||||||
import { useAnimated } from '../hooks/useAnimated';
|
import { useAnimated } from '../hooks/useAnimated';
|
||||||
|
|
||||||
|
@ -96,28 +96,8 @@ export const ConfirmationDialog = React.memo(
|
||||||
|
|
||||||
const hasActions = Boolean(actions.length);
|
const hasActions = Boolean(actions.length);
|
||||||
|
|
||||||
const modalName = `ConfirmationDialog.${dialogName}`;
|
const footer = (
|
||||||
|
<>
|
||||||
return (
|
|
||||||
<ModalHost
|
|
||||||
modalName={modalName}
|
|
||||||
noMouseClose={noMouseClose}
|
|
||||||
onClose={close}
|
|
||||||
onTopOfEverything={onTopOfEverything}
|
|
||||||
overlayStyles={overlayStyles}
|
|
||||||
theme={theme}
|
|
||||||
>
|
|
||||||
<animated.div style={modalStyles}>
|
|
||||||
<ModalWindow
|
|
||||||
modalName={modalName}
|
|
||||||
hasXButton={hasXButton}
|
|
||||||
i18n={i18n}
|
|
||||||
moduleClassName={moduleClassName}
|
|
||||||
onClose={cancelAndClose}
|
|
||||||
title={title}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
ref={focusRef}
|
ref={focusRef}
|
||||||
|
@ -141,8 +121,32 @@ export const ConfirmationDialog = React.memo(
|
||||||
{action.text}
|
{action.text}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Modal.ButtonFooter>
|
</>
|
||||||
</ModalWindow>
|
);
|
||||||
|
|
||||||
|
const modalName = `ConfirmationDialog.${dialogName}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalHost
|
||||||
|
modalName={modalName}
|
||||||
|
noMouseClose={noMouseClose}
|
||||||
|
onClose={close}
|
||||||
|
onTopOfEverything={onTopOfEverything}
|
||||||
|
overlayStyles={overlayStyles}
|
||||||
|
theme={theme}
|
||||||
|
>
|
||||||
|
<animated.div style={modalStyles}>
|
||||||
|
<ModalPage
|
||||||
|
modalName={modalName}
|
||||||
|
hasXButton={hasXButton}
|
||||||
|
i18n={i18n}
|
||||||
|
moduleClassName={moduleClassName}
|
||||||
|
onClose={cancelAndClose}
|
||||||
|
title={title}
|
||||||
|
modalFooter={footer}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ModalPage>
|
||||||
</animated.div>
|
</animated.div>
|
||||||
</ModalHost>
|
</ModalHost>
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,17 +33,8 @@ export function CrashReportDialog(props: Readonly<PropsType>): JSX.Element {
|
||||||
uploadCrashReports();
|
uploadCrashReports();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const footer = (
|
||||||
<Modal
|
<>
|
||||||
modalName="CrashReportDialog"
|
|
||||||
moduleClassName="module-Modal--important"
|
|
||||||
i18n={i18n}
|
|
||||||
title={i18n('CrashReportDialog__title')}
|
|
||||||
hasXButton
|
|
||||||
onClose={eraseCrashReports}
|
|
||||||
>
|
|
||||||
<section>{i18n('CrashReportDialog__body')}</section>
|
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button
|
<Button
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
onClick={onEraseClick}
|
onClick={onEraseClick}
|
||||||
|
@ -63,7 +54,20 @@ export function CrashReportDialog(props: Readonly<PropsType>): JSX.Element {
|
||||||
i18n('CrashReportDialog__submit')
|
i18n('CrashReportDialog__submit')
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.ButtonFooter>
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
modalName="CrashReportDialog"
|
||||||
|
moduleClassName="module-Modal--important"
|
||||||
|
i18n={i18n}
|
||||||
|
title={i18n('CrashReportDialog__title')}
|
||||||
|
hasXButton
|
||||||
|
onClose={eraseCrashReports}
|
||||||
|
modalFooter={footer}
|
||||||
|
>
|
||||||
|
<section>{i18n('CrashReportDialog__body')}</section>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,38 @@ export function CustomizingPreferredReactionsModal({
|
||||||
);
|
);
|
||||||
const canSave = !isSaving && hasChanged;
|
const canSave = !isSaving && hasChanged;
|
||||||
|
|
||||||
|
const footer = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
disabled={!canReset}
|
||||||
|
onClick={() => {
|
||||||
|
resetDraftEmoji();
|
||||||
|
}}
|
||||||
|
onKeyDown={event => {
|
||||||
|
if (event.key === 'Enter' || event.key === 'Space') {
|
||||||
|
resetDraftEmoji();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant={ButtonVariant.SecondaryAffirmative}
|
||||||
|
>
|
||||||
|
{i18n('reset')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={!canSave}
|
||||||
|
onClick={() => {
|
||||||
|
savePreferredReactions();
|
||||||
|
}}
|
||||||
|
onKeyDown={event => {
|
||||||
|
if (event.key === 'Enter' || event.key === 'Space') {
|
||||||
|
savePreferredReactions();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n('save')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="CustomizingPreferredReactionsModal"
|
modalName="CustomizingPreferredReactionsModal"
|
||||||
|
@ -114,6 +146,7 @@ export function CustomizingPreferredReactionsModal({
|
||||||
cancelCustomizePreferredReactionsModal();
|
cancelCustomizePreferredReactionsModal();
|
||||||
}}
|
}}
|
||||||
title={i18n('CustomizingPreferredReactions__title')}
|
title={i18n('CustomizingPreferredReactions__title')}
|
||||||
|
modalFooter={footer}
|
||||||
>
|
>
|
||||||
<div className="module-CustomizingPreferredReactionsModal__small-emoji-picker-wrapper">
|
<div className="module-CustomizingPreferredReactionsModal__small-emoji-picker-wrapper">
|
||||||
<ReactionPickerPicker
|
<ReactionPickerPicker
|
||||||
|
@ -163,35 +196,6 @@ export function CustomizingPreferredReactionsModal({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button
|
|
||||||
disabled={!canReset}
|
|
||||||
onClick={() => {
|
|
||||||
resetDraftEmoji();
|
|
||||||
}}
|
|
||||||
onKeyDown={event => {
|
|
||||||
if (event.key === 'Enter' || event.key === 'Space') {
|
|
||||||
resetDraftEmoji();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
variant={ButtonVariant.SecondaryAffirmative}
|
|
||||||
>
|
|
||||||
{i18n('reset')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled={!canSave}
|
|
||||||
onClick={() => {
|
|
||||||
savePreferredReactions();
|
|
||||||
}}
|
|
||||||
onKeyDown={event => {
|
|
||||||
if (event.key === 'Enter' || event.key === 'Space') {
|
|
||||||
savePreferredReactions();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n('save')}
|
|
||||||
</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,27 +25,23 @@ function focusRef(el: HTMLElement | null) {
|
||||||
export const ErrorModal = (props: PropsType): JSX.Element => {
|
export const ErrorModal = (props: PropsType): JSX.Element => {
|
||||||
const { buttonText, description, i18n, onClose, title } = props;
|
const { buttonText, description, i18n, onClose, title } = props;
|
||||||
|
|
||||||
|
const footer = (
|
||||||
|
<Button onClick={onClose} ref={focusRef} variant={ButtonVariant.Secondary}>
|
||||||
|
{buttonText || i18n('Confirmation--confirm')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="ErrorModal"
|
modalName="ErrorModal"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={title || i18n('ErrorModal--title')}
|
title={title || i18n('ErrorModal--title')}
|
||||||
|
modalFooter={footer}
|
||||||
>
|
>
|
||||||
<>
|
|
||||||
<div className="module-error-modal__description">
|
<div className="module-error-modal__description">
|
||||||
{description || i18n('ErrorModal--description')}
|
{description || i18n('ErrorModal--description')}
|
||||||
</div>
|
</div>
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button
|
|
||||||
onClick={onClose}
|
|
||||||
ref={focusRef}
|
|
||||||
variant={ButtonVariant.Secondary}
|
|
||||||
>
|
|
||||||
{buttonText || i18n('Confirmation--confirm')}
|
|
||||||
</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,14 +46,15 @@ BareBonesLong.story = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BareBonesLongWithButton = (): JSX.Element => (
|
export const BareBonesLongWithButton = (): JSX.Element => (
|
||||||
<Modal modalName="test" i18n={i18n}>
|
<Modal
|
||||||
|
modalName="test"
|
||||||
|
i18n={i18n}
|
||||||
|
modalFooter={<Button onClick={noop}>Okay</Button>}
|
||||||
|
>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -68,11 +69,9 @@ export const TitleXButtonBodyAndButtonFooter = (): JSX.Element => (
|
||||||
title="Hello world"
|
title="Hello world"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
hasXButton
|
hasXButton
|
||||||
|
modalFooter={<Button onClick={noop}>Okay</Button>}
|
||||||
>
|
>
|
||||||
{LOREM_IPSUM}
|
{LOREM_IPSUM}
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -81,21 +80,27 @@ TitleXButtonBodyAndButtonFooter.story = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LotsOfButtonsInTheFooter = (): JSX.Element => (
|
export const LotsOfButtonsInTheFooter = (): JSX.Element => (
|
||||||
<Modal modalName="test" i18n={i18n} onClose={onClose}>
|
<Modal
|
||||||
|
modalName="test"
|
||||||
|
i18n={i18n}
|
||||||
|
onClose={onClose}
|
||||||
|
modalFooter={
|
||||||
|
<>
|
||||||
|
<Button onClick={noop}>Okay X</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
<Button onClick={noop}>
|
||||||
|
This is a button with a fairly large amount of text
|
||||||
|
</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
<Button onClick={noop}>
|
||||||
|
This is a button with a fairly large amount of text
|
||||||
|
</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
Hello world!
|
Hello world!
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>
|
|
||||||
This is a button with a fairly large amount of text
|
|
||||||
</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>
|
|
||||||
This is a button with a fairly large amount of text
|
|
||||||
</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -123,14 +128,17 @@ LongBodyWithTitle.story = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LongBodyWithTitleAndButton = (): JSX.Element => (
|
export const LongBodyWithTitleAndButton = (): JSX.Element => (
|
||||||
<Modal modalName="test" i18n={i18n} title="Hello world" onClose={onClose}>
|
<Modal
|
||||||
|
modalName="test"
|
||||||
|
i18n={i18n}
|
||||||
|
title="Hello world"
|
||||||
|
onClose={onClose}
|
||||||
|
modalFooter={<Button onClick={noop}>Okay</Button>}
|
||||||
|
>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -160,19 +168,20 @@ LongBodyWithLongTitleAndXButton.story = {
|
||||||
export const WithStickyButtonsLongBody = (): JSX.Element => (
|
export const WithStickyButtonsLongBody = (): JSX.Element => (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="test"
|
modalName="test"
|
||||||
hasStickyButtons
|
|
||||||
hasXButton
|
hasXButton
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
modalFooter={
|
||||||
|
<>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -183,16 +192,17 @@ WithStickyButtonsLongBody.story = {
|
||||||
export const WithStickyButtonsShortBody = (): JSX.Element => (
|
export const WithStickyButtonsShortBody = (): JSX.Element => (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="test"
|
modalName="test"
|
||||||
hasStickyButtons
|
|
||||||
hasXButton
|
hasXButton
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
modalFooter={
|
||||||
|
<>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<p>{LOREM_IPSUM.slice(0, 140)}</p>
|
<p>{LOREM_IPSUM.slice(0, 140)}</p>
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -203,25 +213,26 @@ WithStickyButtonsShortBody.story = {
|
||||||
export const StickyFooterLotsOfButtons = (): JSX.Element => (
|
export const StickyFooterLotsOfButtons = (): JSX.Element => (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="test"
|
modalName="test"
|
||||||
hasStickyButtons
|
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title="OK"
|
title="OK"
|
||||||
|
modalFooter={
|
||||||
|
<>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
<Button onClick={noop}>
|
||||||
|
This is a button with a fairly large amount of text
|
||||||
|
</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
<Button onClick={noop}>
|
||||||
|
This is a button with a fairly large amount of text
|
||||||
|
</Button>
|
||||||
|
<Button onClick={noop}>Okay</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<p>{LOREM_IPSUM}</p>
|
<p>{LOREM_IPSUM}</p>
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>
|
|
||||||
This is a button with a fairly large amount of text
|
|
||||||
</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
<Button onClick={noop}>
|
|
||||||
This is a button with a fairly large amount of text
|
|
||||||
</Button>
|
|
||||||
<Button onClick={noop}>Okay</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { useRefMerger } from '../hooks/useRefMerger';
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
modalName: string;
|
modalName: string;
|
||||||
hasStickyButtons?: boolean;
|
|
||||||
hasXButton?: boolean;
|
hasXButton?: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
modalFooter?: JSX.Element;
|
modalFooter?: JSX.Element;
|
||||||
|
@ -29,9 +28,10 @@ type PropsType = {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
useFocusTrap?: boolean;
|
useFocusTrap?: boolean;
|
||||||
|
padded?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ModalPropsType = PropsType & {
|
export type ModalPropsType = PropsType & {
|
||||||
noMouseClose?: boolean;
|
noMouseClose?: boolean;
|
||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
};
|
};
|
||||||
|
@ -41,7 +41,6 @@ const BASE_CLASS_NAME = 'module-Modal';
|
||||||
export function Modal({
|
export function Modal({
|
||||||
children,
|
children,
|
||||||
modalName,
|
modalName,
|
||||||
hasStickyButtons,
|
|
||||||
hasXButton,
|
hasXButton,
|
||||||
i18n,
|
i18n,
|
||||||
modalFooter,
|
modalFooter,
|
||||||
|
@ -52,6 +51,7 @@ export function Modal({
|
||||||
theme,
|
theme,
|
||||||
title,
|
title,
|
||||||
useFocusTrap,
|
useFocusTrap,
|
||||||
|
padded = true,
|
||||||
}: Readonly<ModalPropsType>): ReactElement {
|
}: Readonly<ModalPropsType>): ReactElement {
|
||||||
const { close, modalStyles, overlayStyles } = useAnimated(onClose, {
|
const { close, modalStyles, overlayStyles } = useAnimated(onClose, {
|
||||||
getFrom: () => ({ opacity: 0, transform: 'translateY(48px)' }),
|
getFrom: () => ({ opacity: 0, transform: 'translateY(48px)' }),
|
||||||
|
@ -72,9 +72,8 @@ export function Modal({
|
||||||
useFocusTrap={useFocusTrap}
|
useFocusTrap={useFocusTrap}
|
||||||
>
|
>
|
||||||
<animated.div style={modalStyles}>
|
<animated.div style={modalStyles}>
|
||||||
<ModalWindow
|
<ModalPage
|
||||||
modalName={modalName}
|
modalName={modalName}
|
||||||
hasStickyButtons={hasStickyButtons}
|
|
||||||
hasXButton={hasXButton}
|
hasXButton={hasXButton}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
modalFooter={modalFooter}
|
modalFooter={modalFooter}
|
||||||
|
@ -82,25 +81,46 @@ export function Modal({
|
||||||
onBackButtonClick={onBackButtonClick}
|
onBackButtonClick={onBackButtonClick}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
title={title}
|
title={title}
|
||||||
|
padded={padded}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ModalWindow>
|
</ModalPage>
|
||||||
</animated.div>
|
</animated.div>
|
||||||
</ModalHost>
|
</ModalHost>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ModalWindow({
|
type ModalPageProps = Readonly<{
|
||||||
|
// should be the one provided by PagedModal
|
||||||
|
onClose: () => void;
|
||||||
|
}> &
|
||||||
|
Omit<Readonly<PropsType>, '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,
|
children,
|
||||||
hasStickyButtons,
|
|
||||||
hasXButton,
|
hasXButton,
|
||||||
i18n,
|
i18n,
|
||||||
modalFooter,
|
modalFooter,
|
||||||
moduleClassName,
|
moduleClassName,
|
||||||
onBackButtonClick,
|
onBackButtonClick,
|
||||||
onClose = noop,
|
onClose,
|
||||||
title,
|
title,
|
||||||
}: Readonly<PropsType>): JSX.Element {
|
padded = true,
|
||||||
|
}: ModalPageProps): JSX.Element {
|
||||||
const modalRef = useRef<HTMLDivElement | null>(null);
|
const modalRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const refMerger = useRefMerger();
|
const refMerger = useRefMerger();
|
||||||
|
@ -131,7 +151,7 @@ export function ModalWindow({
|
||||||
className={classNames(
|
className={classNames(
|
||||||
getClassName(''),
|
getClassName(''),
|
||||||
getClassName(hasHeader ? '--has-header' : '--no-header'),
|
getClassName(hasHeader ? '--has-header' : '--no-header'),
|
||||||
hasStickyButtons && getClassName('--sticky-buttons')
|
padded && getClassName('--padded')
|
||||||
)}
|
)}
|
||||||
ref={modalRef}
|
ref={modalRef}
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
|
@ -200,7 +220,7 @@ export function ModalWindow({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Measure>
|
</Measure>
|
||||||
{modalFooter}
|
{modalFooter && <Modal.ButtonFooter>{modalFooter}</Modal.ButtonFooter>}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -208,17 +228,12 @@ export function ModalWindow({
|
||||||
|
|
||||||
Modal.ButtonFooter = function ButtonFooter({
|
Modal.ButtonFooter = function ButtonFooter({
|
||||||
children,
|
children,
|
||||||
moduleClassName,
|
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
moduleClassName?: string;
|
|
||||||
}>): ReactElement {
|
}>): ReactElement {
|
||||||
const [ref, hasWrapped] = useHasWrapped<HTMLDivElement>();
|
const [ref, hasWrapped] = useHasWrapped<HTMLDivElement>();
|
||||||
|
|
||||||
const className = getClassNamesFor(
|
const className = getClassNamesFor(BASE_CLASS_NAME)('__button-footer');
|
||||||
BASE_CLASS_NAME,
|
|
||||||
moduleClassName
|
|
||||||
)('__button-footer');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -232,3 +247,55 @@ Modal.ButtonFooter = function ButtonFooter({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<ModalHost
|
||||||
|
modalName={modalName}
|
||||||
|
moduleClassName={moduleClassName}
|
||||||
|
noMouseClose={noMouseClose}
|
||||||
|
onClose={close}
|
||||||
|
overlayStyles={overlayStyles}
|
||||||
|
theme={theme}
|
||||||
|
useFocusTrap={useFocusTrap}
|
||||||
|
>
|
||||||
|
<animated.div style={modalStyles}>{children(close)}</animated.div>
|
||||||
|
</ModalHost>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RenderModalPage = (onClose: () => void) => JSX.Element;
|
||||||
|
|
|
@ -24,21 +24,8 @@ export const NeedsScreenRecordingPermissionsModal = ({
|
||||||
openSystemPreferencesAction,
|
openSystemPreferencesAction,
|
||||||
toggleScreenRecordingPermissionsDialog,
|
toggleScreenRecordingPermissionsDialog,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
return (
|
const footer = (
|
||||||
<Modal
|
<>
|
||||||
modalName="NeedsScreenRecordingPermissionsModal"
|
|
||||||
i18n={i18n}
|
|
||||||
title={i18n('calling__presenting--permission-title')}
|
|
||||||
theme={Theme.Dark}
|
|
||||||
onClose={toggleScreenRecordingPermissionsDialog}
|
|
||||||
>
|
|
||||||
<p>{i18n('calling__presenting--macos-permission-description')}</p>
|
|
||||||
<ol style={{ paddingLeft: 16 }}>
|
|
||||||
<li>{i18n('calling__presenting--permission-instruction-step1')}</li>
|
|
||||||
<li>{i18n('calling__presenting--permission-instruction-step2')}</li>
|
|
||||||
<li>{i18n('calling__presenting--permission-instruction-step3')}</li>
|
|
||||||
</ol>
|
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={toggleScreenRecordingPermissionsDialog}
|
onClick={toggleScreenRecordingPermissionsDialog}
|
||||||
ref={focusRef}
|
ref={focusRef}
|
||||||
|
@ -55,7 +42,23 @@ export const NeedsScreenRecordingPermissionsModal = ({
|
||||||
>
|
>
|
||||||
{i18n('calling__presenting--permission-open')}
|
{i18n('calling__presenting--permission-open')}
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.ButtonFooter>
|
</>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
modalName="NeedsScreenRecordingPermissionsModal"
|
||||||
|
i18n={i18n}
|
||||||
|
title={i18n('calling__presenting--permission-title')}
|
||||||
|
theme={Theme.Dark}
|
||||||
|
onClose={toggleScreenRecordingPermissionsDialog}
|
||||||
|
modalFooter={footer}
|
||||||
|
>
|
||||||
|
<p>{i18n('calling__presenting--macos-permission-description')}</p>
|
||||||
|
<ol style={{ paddingLeft: 16 }}>
|
||||||
|
<li>{i18n('calling__presenting--permission-instruction-step1')}</li>
|
||||||
|
<li>{i18n('calling__presenting--permission-instruction-step2')}</li>
|
||||||
|
<li>{i18n('calling__presenting--permission-instruction-step3')}</li>
|
||||||
|
</ol>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,7 +61,6 @@ export const ProfileEditorModal = ({
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
modalName="ProfileEditorModal"
|
modalName="ProfileEditorModal"
|
||||||
hasStickyButtons
|
|
||||||
hasXButton
|
hasXButton
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={toggleProfileEditor}
|
onClose={toggleProfileEditor}
|
||||||
|
|
|
@ -19,13 +19,14 @@ import { Checkbox } from './Checkbox';
|
||||||
import { ConfirmationDialog } from './ConfirmationDialog';
|
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||||
import { ContextMenu } from './ContextMenu';
|
import { ContextMenu } from './ContextMenu';
|
||||||
import {
|
import {
|
||||||
DistributionListSettings,
|
DistributionListSettingsModal,
|
||||||
EditDistributionList,
|
EditDistributionListModal,
|
||||||
EditMyStoriesPrivacy,
|
EditMyStoriesPrivacy,
|
||||||
Page as StoriesSettingsPage,
|
Page as StoriesSettingsPage,
|
||||||
} from './StoriesSettingsModal';
|
} from './StoriesSettingsModal';
|
||||||
import { MY_STORIES_ID, getStoryDistributionListName } from '../types/Stories';
|
import { MY_STORIES_ID, getStoryDistributionListName } from '../types/Stories';
|
||||||
import { Modal } from './Modal';
|
import type { RenderModalPage, ModalPropsType } from './Modal';
|
||||||
|
import { PagedModal, ModalPage } from './Modal';
|
||||||
import { StoryDistributionListName } from './StoryDistributionListName';
|
import { StoryDistributionListName } from './StoryDistributionListName';
|
||||||
import { Theme } from '../util/theme';
|
import { Theme } from '../util/theme';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
|
@ -254,9 +255,63 @@ export const SendStoryModal = ({
|
||||||
Array<UUIDStringType>
|
Array<UUIDStringType>
|
||||||
>(initialMyStoriesMemberUuids);
|
>(initialMyStoriesMemberUuids);
|
||||||
|
|
||||||
let content: JSX.Element;
|
let selectedNames: string | undefined;
|
||||||
|
if (page === Page.ChooseGroups) {
|
||||||
|
selectedNames = chosenGroupNames.join(', ');
|
||||||
|
} else {
|
||||||
|
selectedNames = selectedStoryNames
|
||||||
|
.map(listName => getStoryDistributionListName(i18n, listName, listName))
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalCommonProps: Pick<ModalPropsType, 'hasXButton' | 'i18n'> = {
|
||||||
|
hasXButton: true,
|
||||||
|
i18n,
|
||||||
|
};
|
||||||
|
|
||||||
|
let modal: RenderModalPage;
|
||||||
if (page === Page.SetMyStoriesPrivacy) {
|
if (page === Page.SetMyStoriesPrivacy) {
|
||||||
content = (
|
const footer = (
|
||||||
|
<>
|
||||||
|
<div />
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={() => setPage(Page.SendStory)}
|
||||||
|
variant={ButtonVariant.Secondary}
|
||||||
|
>
|
||||||
|
{i18n('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
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')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
modal = handleClose => (
|
||||||
|
<ModalPage
|
||||||
|
modalName="SendStoryModal__my-stories-privacy"
|
||||||
|
title={i18n('SendStoryModal__my-stories-privacy')}
|
||||||
|
modalFooter={footer}
|
||||||
|
onClose={handleClose}
|
||||||
|
{...modalCommonProps}
|
||||||
|
>
|
||||||
<EditMyStoriesPrivacy
|
<EditMyStoriesPrivacy
|
||||||
hasDisclaimerAbove
|
hasDisclaimerAbove
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
@ -302,10 +357,11 @@ export const SendStoryModal = ({
|
||||||
}}
|
}}
|
||||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||||
/>
|
/>
|
||||||
|
</ModalPage>
|
||||||
);
|
);
|
||||||
} else if (page === Page.EditingDistributionList && listToEdit) {
|
} else if (page === Page.EditingDistributionList && listToEdit) {
|
||||||
content = (
|
modal = handleClose => (
|
||||||
<DistributionListSettings
|
<DistributionListSettingsModal
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
listToEdit={listToEdit}
|
listToEdit={listToEdit}
|
||||||
|
@ -316,6 +372,8 @@ export const SendStoryModal = ({
|
||||||
setPage={setPage}
|
setPage={setPage}
|
||||||
setSelectedContacts={setSelectedContacts}
|
setSelectedContacts={setSelectedContacts}
|
||||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||||
|
onBackButtonClick={() => setListIdToEdit(undefined)}
|
||||||
|
onClose={handleClose}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -324,8 +382,8 @@ export const SendStoryModal = ({
|
||||||
page === Page.AddViewer ||
|
page === Page.AddViewer ||
|
||||||
page === Page.HideStoryFrom
|
page === Page.HideStoryFrom
|
||||||
) {
|
) {
|
||||||
content = (
|
modal = handleClose => (
|
||||||
<EditDistributionList
|
<EditDistributionListModal
|
||||||
candidateConversations={candidateConversations}
|
candidateConversations={candidateConversations}
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
@ -350,13 +408,60 @@ export const SendStoryModal = ({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
page={page}
|
page={page}
|
||||||
|
onClose={handleClose}
|
||||||
|
onBackButtonClick={() => {
|
||||||
|
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}
|
selectedContacts={selectedContacts}
|
||||||
setSelectedContacts={setSelectedContacts}
|
setSelectedContacts={setSelectedContacts}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (page === Page.ChooseGroups) {
|
} else if (page === Page.ChooseGroups) {
|
||||||
content = (
|
const footer = (
|
||||||
<>
|
<>
|
||||||
|
<div className="SendStoryModal__selected-lists">{selectedNames}</div>
|
||||||
|
<button
|
||||||
|
aria-label={i18n('SendStoryModal__ok')}
|
||||||
|
className="SendStoryModal__ok"
|
||||||
|
disabled={!chosenGroupIds.size}
|
||||||
|
onClick={() => {
|
||||||
|
toggleGroupsForStorySend(Array.from(chosenGroupIds));
|
||||||
|
setChosenGroupIds(new Set());
|
||||||
|
setPage(Page.SendStory);
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
modal = handleClose => (
|
||||||
|
<ModalPage
|
||||||
|
modalName="SendStoryModal__choose-groups"
|
||||||
|
title={i18n('SendStoryModal__choose-groups')}
|
||||||
|
modalFooter={footer}
|
||||||
|
onClose={handleClose}
|
||||||
|
{...modalCommonProps}
|
||||||
|
>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
disabled={groupConversations.length === 0}
|
disabled={groupConversations.length === 0}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
@ -429,11 +534,32 @@ export const SendStoryModal = ({
|
||||||
{i18n('noContactsFound')}
|
{i18n('noContactsFound')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</ModalPage>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = (
|
const footer = (
|
||||||
<>
|
<>
|
||||||
|
<div className="SendStoryModal__selected-lists">{selectedNames}</div>
|
||||||
|
<button
|
||||||
|
aria-label={i18n('SendStoryModal__send')}
|
||||||
|
className="SendStoryModal__send"
|
||||||
|
disabled={!selectedListIds.size && !selectedGroupIds.size}
|
||||||
|
onClick={() => {
|
||||||
|
onSend(Array.from(selectedListIds), Array.from(selectedGroupIds));
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
modal = handleClose => (
|
||||||
|
<ModalPage
|
||||||
|
modalName="SendStoryModal__title"
|
||||||
|
title={i18n('SendStoryModal__title')}
|
||||||
|
moduleClassName="SendStoryModal"
|
||||||
|
modalFooter={footer}
|
||||||
|
onClose={handleClose}
|
||||||
|
{...modalCommonProps}
|
||||||
|
>
|
||||||
<div className="SendStoryModal__top-bar">
|
<div className="SendStoryModal__top-bar">
|
||||||
{i18n('stories')}
|
{i18n('stories')}
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
|
@ -649,159 +775,19 @@ export const SendStoryModal = ({
|
||||||
)}
|
)}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
))}
|
))}
|
||||||
</>
|
</ModalPage>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = (
|
|
||||||
<Modal.ButtonFooter moduleClassName="SendStoryModal">
|
|
||||||
{page !== Page.SetMyStoriesPrivacy && (
|
|
||||||
<div className="SendStoryModal__selected-lists">{selectedNames}</div>
|
|
||||||
)}
|
|
||||||
{page === Page.ChooseGroups && (
|
|
||||||
<button
|
|
||||||
aria-label={i18n('SendStoryModal__ok')}
|
|
||||||
className="SendStoryModal__ok"
|
|
||||||
disabled={!chosenGroupIds.size}
|
|
||||||
onClick={() => {
|
|
||||||
toggleGroupsForStorySend(Array.from(chosenGroupIds));
|
|
||||||
setChosenGroupIds(new Set());
|
|
||||||
setPage(Page.SendStory);
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{page === Page.SendStory && (
|
|
||||||
<button
|
|
||||||
aria-label={i18n('SendStoryModal__send')}
|
|
||||||
className="SendStoryModal__send"
|
|
||||||
disabled={!selectedListIds.size && !selectedGroupIds.size}
|
|
||||||
onClick={() => {
|
|
||||||
onSend(Array.from(selectedListIds), Array.from(selectedGroupIds));
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{page === Page.SetMyStoriesPrivacy && (
|
|
||||||
<>
|
|
||||||
<div />
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
onClick={() => setPage(Page.SendStory)}
|
|
||||||
variant={ButtonVariant.Secondary}
|
|
||||||
>
|
|
||||||
{i18n('cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
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')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<PagedModal
|
||||||
modalName="SendStoryModal"
|
modalName="SendStoryModal"
|
||||||
hasStickyButtons
|
|
||||||
hasXButton
|
|
||||||
i18n={i18n}
|
|
||||||
modalFooter={modalFooter}
|
|
||||||
onBackButtonClick={
|
|
||||||
hasBackButton
|
|
||||||
? () => {
|
|
||||||
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}
|
theme={Theme.Dark}
|
||||||
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
{content}
|
{modal}
|
||||||
</Modal>
|
</PagedModal>
|
||||||
{confirmRemoveGroupId && (
|
{confirmRemoveGroupId && (
|
||||||
<ConfirmationDialog
|
<ConfirmationDialog
|
||||||
dialogName="SendStoryModal.confirmRemoveGroupId"
|
dialogName="SendStoryModal.confirmRemoveGroupId"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { Row } from './ConversationList';
|
import type { Row } from './ConversationList';
|
||||||
import type { StoryDistributionListWithMembersDataType } from '../types/Stories';
|
import type { StoryDistributionListWithMembersDataType } from '../types/Stories';
|
||||||
import type { UUIDStringType } from '../types/UUID';
|
import type { UUIDStringType } from '../types/UUID';
|
||||||
|
import type { RenderModalPage, ModalPropsType } from './Modal';
|
||||||
import { Avatar, AvatarSize } from './Avatar';
|
import { Avatar, AvatarSize } from './Avatar';
|
||||||
import { Button, ButtonVariant } from './Button';
|
import { Button, ButtonVariant } from './Button';
|
||||||
import { Checkbox } from './Checkbox';
|
import { Checkbox } from './Checkbox';
|
||||||
|
@ -22,7 +23,7 @@ import { ConversationList, RowType } from './ConversationList';
|
||||||
import { Input } from './Input';
|
import { Input } from './Input';
|
||||||
import { Intl } from './Intl';
|
import { Intl } from './Intl';
|
||||||
import { MY_STORIES_ID, getStoryDistributionListName } from '../types/Stories';
|
import { MY_STORIES_ID, getStoryDistributionListName } from '../types/Stories';
|
||||||
import { Modal } from './Modal';
|
import { PagedModal, ModalPage } from './Modal';
|
||||||
import { SearchInput } from './SearchInput';
|
import { SearchInput } from './SearchInput';
|
||||||
import { StoryDistributionListName } from './StoryDistributionListName';
|
import { StoryDistributionListName } from './StoryDistributionListName';
|
||||||
import { Theme } from '../util/theme';
|
import { Theme } from '../util/theme';
|
||||||
|
@ -80,6 +81,12 @@ function filterConversations(
|
||||||
).filter(conversation => conversation.uuid);
|
).filter(conversation => conversation.uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const modalCommonProps: Pick<ModalPropsType, 'hasXButton' | 'moduleClassName'> =
|
||||||
|
{
|
||||||
|
hasXButton: true,
|
||||||
|
moduleClassName: 'StoriesSettingsModal__modal',
|
||||||
|
};
|
||||||
|
|
||||||
export const StoriesSettingsModal = ({
|
export const StoriesSettingsModal = ({
|
||||||
candidateConversations,
|
candidateConversations,
|
||||||
distributionLists,
|
distributionLists,
|
||||||
|
@ -120,18 +127,34 @@ export const StoriesSettingsModal = ({
|
||||||
string | undefined
|
string | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
let content: JSX.Element | null;
|
let modal: RenderModalPage | null;
|
||||||
|
|
||||||
if (page !== Page.DistributionLists) {
|
if (page !== Page.DistributionLists) {
|
||||||
content = (
|
const isChoosingViewers =
|
||||||
<EditDistributionList
|
page === Page.ChooseViewers || page === Page.AddViewer;
|
||||||
|
|
||||||
|
modal = onClose => (
|
||||||
|
<EditDistributionListModal
|
||||||
candidateConversations={candidateConversations}
|
candidateConversations={candidateConversations}
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
page={page}
|
||||||
|
onClose={onClose}
|
||||||
onCreateList={(name, uuids) => {
|
onCreateList={(name, uuids) => {
|
||||||
onDistributionListCreated(name, uuids);
|
onDistributionListCreated(name, uuids);
|
||||||
resetChooseViewersScreen();
|
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 => {
|
onViewersUpdated={uuids => {
|
||||||
if (listToEditId && page === Page.AddViewer) {
|
if (listToEditId && page === Page.AddViewer) {
|
||||||
onViewersUpdated(listToEditId, uuids);
|
onViewersUpdated(listToEditId, uuids);
|
||||||
|
@ -147,14 +170,14 @@ export const StoriesSettingsModal = ({
|
||||||
resetChooseViewersScreen();
|
resetChooseViewersScreen();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
page={page}
|
|
||||||
selectedContacts={selectedContacts}
|
selectedContacts={selectedContacts}
|
||||||
setSelectedContacts={setSelectedContacts}
|
setSelectedContacts={setSelectedContacts}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (listToEdit) {
|
} else if (listToEdit) {
|
||||||
content = (
|
modal = onClose => (
|
||||||
<DistributionListSettings
|
<DistributionListSettingsModal
|
||||||
|
key="settings-modal"
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
listToEdit={listToEdit}
|
listToEdit={listToEdit}
|
||||||
|
@ -165,6 +188,8 @@ export const StoriesSettingsModal = ({
|
||||||
setPage={setPage}
|
setPage={setPage}
|
||||||
setSelectedContacts={setSelectedContacts}
|
setSelectedContacts={setSelectedContacts}
|
||||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||||
|
onBackButtonClick={() => setListToEditId(undefined)}
|
||||||
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -172,8 +197,14 @@ export const StoriesSettingsModal = ({
|
||||||
list => list.id !== MY_STORIES_ID
|
list => list.id !== MY_STORIES_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
content = (
|
modal = onClose => (
|
||||||
<>
|
<ModalPage
|
||||||
|
modalName="StoriesSettingsModal__list"
|
||||||
|
i18n={i18n}
|
||||||
|
onClose={onClose}
|
||||||
|
title={i18n('StoriesSettings__title')}
|
||||||
|
{...modalCommonProps}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="StoriesSettingsModal__list"
|
className="StoriesSettingsModal__list"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -244,61 +275,19 @@ export const StoriesSettingsModal = ({
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</>
|
</ModalPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<PagedModal
|
||||||
modalName="StoriesSettingsModal"
|
modalName="StoriesSettingsModal"
|
||||||
hasStickyButtons={hasStickyButtons}
|
|
||||||
hasXButton
|
|
||||||
i18n={i18n}
|
|
||||||
moduleClassName="StoriesSettingsModal__modal"
|
|
||||||
onBackButtonClick={
|
|
||||||
hasBackButton
|
|
||||||
? () => {
|
|
||||||
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}
|
theme={Theme.Dark}
|
||||||
title={modalTitle}
|
onClose={hideStoriesSettings}
|
||||||
>
|
>
|
||||||
{content}
|
{modal}
|
||||||
</Modal>
|
</PagedModal>
|
||||||
{confirmDeleteListId && (
|
{confirmDeleteListId && (
|
||||||
<ConfirmationDialog
|
<ConfirmationDialog
|
||||||
dialogName="StoriesSettings.deleteList"
|
dialogName="StoriesSettings.deleteList"
|
||||||
|
@ -324,12 +313,14 @@ export const StoriesSettingsModal = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type DistributionListSettingsPropsType = {
|
type DistributionListSettingsModalPropsType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
listToEdit: StoryDistributionListWithMembersDataType;
|
listToEdit: StoryDistributionListWithMembersDataType;
|
||||||
setConfirmDeleteListId: (id: string) => unknown;
|
setConfirmDeleteListId: (id: string) => unknown;
|
||||||
setPage: (page: Page) => unknown;
|
setPage: (page: Page) => unknown;
|
||||||
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
||||||
|
onBackButtonClick: (() => void) | undefined;
|
||||||
|
onClose: () => void;
|
||||||
} & Pick<
|
} & Pick<
|
||||||
PropsType,
|
PropsType,
|
||||||
| 'getPreferredBadge'
|
| 'getPreferredBadge'
|
||||||
|
@ -339,18 +330,20 @@ type DistributionListSettingsPropsType = {
|
||||||
| 'toggleSignalConnectionsModal'
|
| 'toggleSignalConnectionsModal'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const DistributionListSettings = ({
|
export const DistributionListSettingsModal = ({
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
i18n,
|
i18n,
|
||||||
listToEdit,
|
listToEdit,
|
||||||
onRemoveMember,
|
onRemoveMember,
|
||||||
onRepliesNReactionsChanged,
|
onRepliesNReactionsChanged,
|
||||||
|
onBackButtonClick,
|
||||||
|
onClose,
|
||||||
setConfirmDeleteListId,
|
setConfirmDeleteListId,
|
||||||
setMyStoriesToAllSignalConnections,
|
setMyStoriesToAllSignalConnections,
|
||||||
setPage,
|
setPage,
|
||||||
setSelectedContacts,
|
setSelectedContacts,
|
||||||
toggleSignalConnectionsModal,
|
toggleSignalConnectionsModal,
|
||||||
}: DistributionListSettingsPropsType): JSX.Element => {
|
}: DistributionListSettingsModalPropsType): JSX.Element => {
|
||||||
const [confirmRemoveMember, setConfirmRemoveMember] = useState<
|
const [confirmRemoveMember, setConfirmRemoveMember] = useState<
|
||||||
| undefined
|
| undefined
|
||||||
| {
|
| {
|
||||||
|
@ -362,8 +355,21 @@ export const DistributionListSettings = ({
|
||||||
|
|
||||||
const isMyStories = listToEdit.id === MY_STORIES_ID;
|
const isMyStories = listToEdit.id === MY_STORIES_ID;
|
||||||
|
|
||||||
|
const modalTitle = getStoryDistributionListName(
|
||||||
|
i18n,
|
||||||
|
listToEdit.id,
|
||||||
|
listToEdit.name
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ModalPage
|
||||||
|
modalName="DistributionListSettingsModal"
|
||||||
|
i18n={i18n}
|
||||||
|
onBackButtonClick={onBackButtonClick}
|
||||||
|
onClose={onClose}
|
||||||
|
title={modalTitle}
|
||||||
|
{...modalCommonProps}
|
||||||
|
>
|
||||||
{!isMyStories && (
|
{!isMyStories && (
|
||||||
<>
|
<>
|
||||||
<div className="StoriesSettingsModal__list StoriesSettingsModal__list--no-pointer">
|
<div className="StoriesSettingsModal__list StoriesSettingsModal__list--no-pointer">
|
||||||
|
@ -521,7 +527,7 @@ export const DistributionListSettings = ({
|
||||||
{i18n('StoriesSettings__remove--body')}
|
{i18n('StoriesSettings__remove--body')}
|
||||||
</ConfirmationDialog>
|
</ConfirmationDialog>
|
||||||
)}
|
)}
|
||||||
</>
|
</ModalPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -630,24 +636,37 @@ export const EditMyStoriesPrivacy = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type EditDistributionListPropsType = {
|
type EditDistributionListModalPropsType = {
|
||||||
onCreateList: (name: string, viewerUuids: Array<UUIDStringType>) => unknown;
|
onCreateList: (name: string, viewerUuids: Array<UUIDStringType>) => unknown;
|
||||||
onViewersUpdated: (viewerUuids: Array<UUIDStringType>) => unknown;
|
onViewersUpdated: (viewerUuids: Array<UUIDStringType>) => unknown;
|
||||||
page: Page;
|
page:
|
||||||
|
| Page.AddViewer
|
||||||
|
| Page.ChooseViewers
|
||||||
|
| Page.HideStoryFrom
|
||||||
|
| Page.NameStory;
|
||||||
selectedContacts: Array<ConversationType>;
|
selectedContacts: Array<ConversationType>;
|
||||||
|
onClose: () => unknown;
|
||||||
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
||||||
|
onBackButtonClick: () => void;
|
||||||
} & Pick<PropsType, 'candidateConversations' | 'getPreferredBadge' | 'i18n'>;
|
} & Pick<PropsType, 'candidateConversations' | 'getPreferredBadge' | 'i18n'>;
|
||||||
|
|
||||||
export const EditDistributionList = ({
|
/**
|
||||||
|
*
|
||||||
|
* @param param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const EditDistributionListModal = ({
|
||||||
candidateConversations,
|
candidateConversations,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
i18n,
|
i18n,
|
||||||
onCreateList,
|
onCreateList,
|
||||||
onViewersUpdated,
|
onViewersUpdated,
|
||||||
page,
|
page,
|
||||||
|
onClose,
|
||||||
selectedContacts,
|
selectedContacts,
|
||||||
setSelectedContacts,
|
setSelectedContacts,
|
||||||
}: EditDistributionListPropsType): JSX.Element | null => {
|
onBackButtonClick,
|
||||||
|
}: EditDistributionListModalPropsType): JSX.Element => {
|
||||||
const [storyName, setStoryName] = useState('');
|
const [storyName, setStoryName] = useState('');
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
|
||||||
|
@ -668,18 +687,6 @@ export const EditDistributionList = ({
|
||||||
};
|
};
|
||||||
}, [candidateConversations, normalizedSearchTerm, setFilteredConversations]);
|
}, [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 contactLookup = useMemo(() => {
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
candidateConversations.forEach(contact => {
|
candidateConversations.forEach(contact => {
|
||||||
|
@ -720,8 +727,29 @@ export const EditDistributionList = ({
|
||||||
page === Page.ChooseViewers || page === Page.AddViewer;
|
page === Page.ChooseViewers || page === Page.AddViewer;
|
||||||
|
|
||||||
if (page === Page.NameStory) {
|
if (page === Page.NameStory) {
|
||||||
|
const footer = (
|
||||||
|
<Button
|
||||||
|
disabled={!storyName}
|
||||||
|
onClick={() => {
|
||||||
|
onCreateList(storyName, Array.from(selectedConversationUuids));
|
||||||
|
setStoryName('');
|
||||||
|
}}
|
||||||
|
variant={ButtonVariant.Primary}
|
||||||
|
>
|
||||||
|
{i18n('done')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ModalPage
|
||||||
|
modalName="StoriesSettings__name-story"
|
||||||
|
title={i18n('StoriesSettings__name-story')}
|
||||||
|
modalFooter={footer}
|
||||||
|
i18n={i18n}
|
||||||
|
onBackButtonClick={onBackButtonClick}
|
||||||
|
onClose={onClose}
|
||||||
|
{...modalCommonProps}
|
||||||
|
>
|
||||||
<div className="StoriesSettingsModal__name-story-avatar-container">
|
<div className="StoriesSettingsModal__name-story-avatar-container">
|
||||||
<div className="StoriesSettingsModal__list__avatar--private StoriesSettingsModal__list__avatar--private--large" />
|
<div className="StoriesSettingsModal__list__avatar--private StoriesSettingsModal__list__avatar--private--large" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -762,27 +790,10 @@ export const EditDistributionList = ({
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Modal.ButtonFooter>
|
</ModalPage>
|
||||||
<Button
|
|
||||||
disabled={!storyName}
|
|
||||||
onClick={() => {
|
|
||||||
onCreateList(storyName, Array.from(selectedConversationUuids));
|
|
||||||
setStoryName('');
|
|
||||||
}}
|
|
||||||
variant={ButtonVariant.Primary}
|
|
||||||
>
|
|
||||||
{i18n('done')}
|
|
||||||
</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
page === Page.AddViewer ||
|
|
||||||
page === Page.ChooseViewers ||
|
|
||||||
page === Page.HideStoryFrom
|
|
||||||
) {
|
|
||||||
const rowCount = filteredConversations.length;
|
const rowCount = filteredConversations.length;
|
||||||
const getRow = (index: number): undefined | Row => {
|
const getRow = (index: number): undefined | Row => {
|
||||||
const contact = filteredConversations[index];
|
const contact = filteredConversations[index];
|
||||||
|
@ -799,8 +810,48 @@ export const EditDistributionList = ({
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let footer: JSX.Element | undefined;
|
||||||
|
if (isChoosingViewers) {
|
||||||
|
footer = (
|
||||||
|
<Button
|
||||||
|
disabled={selectedContacts.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
onViewersUpdated(Array.from(selectedConversationUuids));
|
||||||
|
}}
|
||||||
|
variant={ButtonVariant.Primary}
|
||||||
|
>
|
||||||
|
{page === Page.AddViewer ? i18n('done') : i18n('next2')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
} else if (page === Page.HideStoryFrom) {
|
||||||
|
footer = (
|
||||||
|
<Button
|
||||||
|
disabled={selectedContacts.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
onViewersUpdated(Array.from(selectedConversationUuids));
|
||||||
|
}}
|
||||||
|
variant={ButtonVariant.Primary}
|
||||||
|
>
|
||||||
|
{i18n('update')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ModalPage
|
||||||
|
modalName={`EditDistributionListModal__${page}`}
|
||||||
|
i18n={i18n}
|
||||||
|
modalFooter={footer}
|
||||||
|
onBackButtonClick={onBackButtonClick}
|
||||||
|
onClose={onClose}
|
||||||
|
title={
|
||||||
|
page === Page.HideStoryFrom
|
||||||
|
? i18n('StoriesSettings__hide-story')
|
||||||
|
: i18n('StoriesSettings__choose-viewers')
|
||||||
|
}
|
||||||
|
padded={page !== Page.ChooseViewers && page !== Page.AddViewer}
|
||||||
|
{...modalCommonProps}
|
||||||
|
>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
disabled={candidateConversations.length === 0}
|
disabled={candidateConversations.length === 0}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
@ -819,7 +870,7 @@ export const EditDistributionList = ({
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
acceptedMessageRequest={contact.acceptedMessageRequest}
|
||||||
avatarPath={contact.avatarPath}
|
avatarPath={contact.avatarPath}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
firstName={contact.systemGivenName ?? contact.firstName}
|
firstName={contact.firstName}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
id={contact.id}
|
id={contact.id}
|
||||||
isMe={contact.isMe}
|
isMe={contact.isMe}
|
||||||
|
@ -870,35 +921,6 @@ export const EditDistributionList = ({
|
||||||
{i18n('noContactsFound')}
|
{i18n('noContactsFound')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isChoosingViewers && (
|
</ModalPage>
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button
|
|
||||||
disabled={selectedContacts.length === 0}
|
|
||||||
onClick={() => {
|
|
||||||
onViewersUpdated(Array.from(selectedConversationUuids));
|
|
||||||
}}
|
|
||||||
variant={ButtonVariant.Primary}
|
|
||||||
>
|
|
||||||
{page === Page.AddViewer ? i18n('done') : i18n('next2')}
|
|
||||||
</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
)}
|
|
||||||
{page === Page.HideStoryFrom && (
|
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button
|
|
||||||
disabled={selectedContacts.length === 0}
|
|
||||||
onClick={() => {
|
|
||||||
onViewersUpdated(Array.from(selectedConversationUuids));
|
|
||||||
}}
|
|
||||||
variant={ButtonVariant.Primary}
|
|
||||||
>
|
|
||||||
{i18n('update')}
|
|
||||||
</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -172,6 +172,7 @@ export const ContactModal = ({
|
||||||
hasXButton
|
hasXButton
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={hideContactModal}
|
onClose={hideContactModal}
|
||||||
|
padded={false}
|
||||||
>
|
>
|
||||||
<div className="ContactModal">
|
<div className="ContactModal">
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|
|
@ -31,12 +31,34 @@ export function DeliveryIssueDialog(props: PropsType): React.ReactElement {
|
||||||
// Focus first button after initial render, restore focus on teardown
|
// Focus first button after initial render, restore focus on teardown
|
||||||
const [focusRef] = useRestoreFocus();
|
const [focusRef] = useRestoreFocus();
|
||||||
|
|
||||||
|
const footer = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={learnMoreAboutDeliveryIssue}
|
||||||
|
size={ButtonSize.Medium}
|
||||||
|
variant={ButtonVariant.Secondary}
|
||||||
|
>
|
||||||
|
{i18n('DeliveryIssue--learnMore')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
ref={focusRef}
|
||||||
|
size={ButtonSize.Medium}
|
||||||
|
variant={ButtonVariant.Primary}
|
||||||
|
className="module-delivery-issue-dialog__close-button"
|
||||||
|
>
|
||||||
|
{i18n('Confirmation--confirm')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="DeliveryIssueDialog"
|
modalName="DeliveryIssueDialog"
|
||||||
hasXButton={false}
|
hasXButton={false}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
modalFooter={footer}
|
||||||
>
|
>
|
||||||
<section>
|
<section>
|
||||||
<div className="module-delivery-issue-dialog__image">
|
<div className="module-delivery-issue-dialog__image">
|
||||||
|
@ -60,24 +82,6 @@ export function DeliveryIssueDialog(props: PropsType): React.ReactElement {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button
|
|
||||||
onClick={learnMoreAboutDeliveryIssue}
|
|
||||||
size={ButtonSize.Medium}
|
|
||||||
variant={ButtonVariant.Secondary}
|
|
||||||
>
|
|
||||||
{i18n('DeliveryIssue--learnMore')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={onClose}
|
|
||||||
ref={focusRef}
|
|
||||||
size={ButtonSize.Medium}
|
|
||||||
variant={ButtonVariant.Primary}
|
|
||||||
className="module-delivery-issue-dialog__close-button"
|
|
||||||
>
|
|
||||||
{i18n('Confirmation--confirm')}
|
|
||||||
</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,11 +47,20 @@ export const ConversationNotificationsModal = ({
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="ConversationNotificationsModal"
|
modalName="ConversationNotificationsModal"
|
||||||
hasStickyButtons
|
|
||||||
hasXButton
|
hasXButton
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={i18n('muteNotificationsTitle')}
|
title={i18n('muteNotificationsTitle')}
|
||||||
|
modalFooter={
|
||||||
|
<>
|
||||||
|
<Button onClick={onClose} variant={ButtonVariant.Secondary}>
|
||||||
|
{i18n('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onMuteChange} variant={ButtonVariant.Primary}>
|
||||||
|
{i18n('mute')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{muteOptions
|
{muteOptions
|
||||||
.filter(x => x.value > 0)
|
.filter(x => x.value > 0)
|
||||||
|
@ -67,14 +76,6 @@ export const ConversationNotificationsModal = ({
|
||||||
onChange={value => value && setMuteExpirationValue(option.value)}
|
onChange={value => value && setMuteExpirationValue(option.value)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Modal.ButtonFooter>
|
|
||||||
<Button onClick={onClose} variant={ButtonVariant.Secondary}>
|
|
||||||
{i18n('cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onMuteChange} variant={ButtonVariant.Primary}>
|
|
||||||
{i18n('mute')}
|
|
||||||
</Button>
|
|
||||||
</Modal.ButtonFooter>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -155,6 +155,7 @@ export const EditConversationAttributesModal: FunctionComponent<PropsType> = ({
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<form
|
<form
|
||||||
|
id="edit-conversation-form"
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
className="module-EditConversationAttributesModal"
|
className="module-EditConversationAttributesModal"
|
||||||
>
|
>
|
||||||
|
@ -199,8 +200,12 @@ export const EditConversationAttributesModal: FunctionComponent<PropsType> = ({
|
||||||
{i18n('updateGroupAttributes__error-message')}
|
{i18n('updateGroupAttributes__error-message')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
<Modal.ButtonFooter>
|
const modalFooter = (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
disabled={isRequestActive}
|
disabled={isRequestActive}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
@ -211,6 +216,7 @@ export const EditConversationAttributesModal: FunctionComponent<PropsType> = ({
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
form="edit-conversation-form"
|
||||||
variant={ButtonVariant.Primary}
|
variant={ButtonVariant.Primary}
|
||||||
disabled={!canSubmit}
|
disabled={!canSubmit}
|
||||||
>
|
>
|
||||||
|
@ -220,19 +226,17 @@ export const EditConversationAttributesModal: FunctionComponent<PropsType> = ({
|
||||||
i18n('save')
|
i18n('save')
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.ButtonFooter>
|
</>
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="EditConversationAttributesModal"
|
modalName="EditConversationAttributesModal"
|
||||||
hasStickyButtons
|
|
||||||
hasXButton
|
hasXButton
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={i18n('updateGroupAttributes__title')}
|
title={i18n('updateGroupAttributes__title')}
|
||||||
|
modalFooter={modalFooter}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -152,7 +152,6 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
||||||
{this.isEditingAvatar && (
|
{this.isEditingAvatar && (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="LeftPaneSetGroupMetadataHelper.AvatarEditor"
|
modalName="LeftPaneSetGroupMetadataHelper.AvatarEditor"
|
||||||
hasStickyButtons
|
|
||||||
hasXButton
|
hasXButton
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={toggleComposeEditingAvatar}
|
onClose={toggleComposeEditingAvatar}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue