diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index b61b84e18..c4632d873 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 248d1c7e8..3340993e8 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 b6415bdce..a456d6b96 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 11784b127..55cbd21a4 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 c7c62f1ea..3d8ebf89a 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 86f951744..3a676e1fd 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 5b8f8c718..1aaa2b767 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 a3883c122..1b0102bdd 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 7e6f2ac17..6b0ce1fdb 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 59e5fc82d..c4e9dc416 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 7663ee7fc..120f4b09d 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 93d3aa188..fb6048081 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 f79e3b9dc..00863bc4f 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} >
+ + + ); + return (
{i18n('calling__SelectPresentingSourcesModal--entireScreen')} @@ -120,20 +135,6 @@ export const CallingSelectPresentingSourcesModal = ({ /> ))}
- - - -
); }; diff --git a/ts/components/CaptchaDialog.tsx b/ts/components/CaptchaDialog.tsx index f9329a948..b922d79e6 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 = ( + <> + + + + ); return ( ): JSX.Element { title={i18n('CaptchaDialog--can-close__title')} onClose={() => setIsClosing(false)} key="skip" + modalFooter={footer} >

{i18n('CaptchaDialog--can-close__body')}

- - - -
); } @@ -71,6 +74,21 @@ export function CaptchaDialog(props: Readonly): JSX.Element { } }; + const footer = ( + + ); + return ( ): JSX.Element { hasXButton onClose={() => setIsClosing(true)} key="primary" + modalFooter={footer} >

{i18n('CaptchaDialog__first-paragraph')}

{i18n('CaptchaDialog__second-paragraph')}

- - -
); } diff --git a/ts/components/ConfirmationDialog.tsx b/ts/components/ConfirmationDialog.tsx index d89c6c307..328e5d753 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 = ( + <> + + {actions.map((action, i) => ( + + ))} + + ); + const modalName = `ConfirmationDialog.${dialogName}`; return ( @@ -108,41 +136,17 @@ export const ConfirmationDialog = React.memo( theme={theme} > - {children} - - - {actions.map((action, i) => ( - - ))} - - + ); diff --git a/ts/components/CrashReportDialog.tsx b/ts/components/CrashReportDialog.tsx index 8d3701287..8f607fe1b 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 = ( + <> + + + + ); + return ( ): JSX.Element { title={i18n('CrashReportDialog__title')} hasXButton onClose={eraseCrashReports} + modalFooter={footer} >
{i18n('CrashReportDialog__body')}
- - - -
); } diff --git a/ts/components/CustomizingPreferredReactionsModal.tsx b/ts/components/CustomizingPreferredReactionsModal.tsx index 4a78198f2..562307c98 100644 --- a/ts/components/CustomizingPreferredReactionsModal.tsx +++ b/ts/components/CustomizingPreferredReactionsModal.tsx @@ -104,6 +104,38 @@ export function CustomizingPreferredReactionsModal({ ); const canSave = !isSaving && hasChanged; + const footer = ( + <> + + + + ); + return (
)} - - - -
); } diff --git a/ts/components/ErrorModal.tsx b/ts/components/ErrorModal.tsx index 981a9257b..43edb586e 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 = ( + + ); + return ( - <> -
- {description || i18n('ErrorModal--description')} -
- - - - +
+ {description || i18n('ErrorModal--description')} +
); }; diff --git a/ts/components/Modal.stories.tsx b/ts/components/Modal.stories.tsx index b30f56cdc..42f2a978c 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}

- - -
); @@ -68,11 +69,9 @@ export const TitleXButtonBodyAndButtonFooter = (): JSX.Element => ( title="Hello world" onClose={onClose} hasXButton + modalFooter={} > {LOREM_IPSUM} - - -
); @@ -81,21 +80,27 @@ TitleXButtonBodyAndButtonFooter.story = { }; export const LotsOfButtonsInTheFooter = (): JSX.Element => ( - + + + + + + + + + + } + > Hello world! - - - - - - - - - ); @@ -123,14 +128,17 @@ LongBodyWithTitle.story = { }; export const LongBodyWithTitleAndButton = (): JSX.Element => ( - + Okay} + >

{LOREM_IPSUM}

{LOREM_IPSUM}

{LOREM_IPSUM}

{LOREM_IPSUM}

- - -
); @@ -160,19 +168,20 @@ LongBodyWithLongTitleAndXButton.story = { export const WithStickyButtonsLongBody = (): JSX.Element => ( + + + + } >

{LOREM_IPSUM}

{LOREM_IPSUM}

{LOREM_IPSUM}

{LOREM_IPSUM}

- - - -
); @@ -183,16 +192,17 @@ WithStickyButtonsLongBody.story = { export const WithStickyButtonsShortBody = (): JSX.Element => ( + + + + } >

{LOREM_IPSUM.slice(0, 140)}

- - - -
); @@ -203,25 +213,26 @@ WithStickyButtonsShortBody.story = { export const StickyFooterLotsOfButtons = (): JSX.Element => ( + + + + + + + + + } >

{LOREM_IPSUM}

- - - - - - - - -
); diff --git a/ts/components/Modal.tsx b/ts/components/Modal.tsx index aab2024ee..cad8bd77d 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 21f1718ee..e1f880e0d 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 = ( + <> + + + + ); return (

{i18n('calling__presenting--macos-permission-description')}

    @@ -38,24 +59,6 @@ export const NeedsScreenRecordingPermissionsModal = ({
  1. {i18n('calling__presenting--permission-instruction-step2')}
  2. {i18n('calling__presenting--permission-instruction-step3')}
- - - -
); }; diff --git a/ts/components/ProfileEditorModal.tsx b/ts/components/ProfileEditorModal.tsx index 7fe4df378..37fa16b0e 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 = ( + <> +
+
+ + +
+ + ); + + 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}
+ - -
- - )} - + ); } 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 => ( + ))} - + ); } - 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 = ( + + ); + return ( - <> +
@@ -762,143 +790,137 @@ export const EditDistributionList = ({
))} - - - - +
); } - 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 && ( - - - - )} - {page === Page.HideStoryFrom && ( - - - - )} - + let footer: JSX.Element | undefined; + if (isChoosingViewers) { + footer = ( + + ); + } else if (page === Page.HideStoryFrom) { + footer = ( + ); } - 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 827df068f..8efaef420 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} >
+ + + + ); + return (
@@ -60,24 +82,6 @@ export function DeliveryIssueDialog(props: PropsType): React.ReactElement { />
- - - -
); } diff --git a/ts/components/conversation/conversation-details/ConversationNotificationsModal.tsx b/ts/components/conversation/conversation-details/ConversationNotificationsModal.tsx index bc51ae421..e35bfdbaa 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 ( + + + + } > {muteOptions .filter(x => x.value > 0) @@ -67,14 +76,6 @@ export const ConversationNotificationsModal = ({ onChange={value => value && setMuteExpirationValue(option.value)} /> ))} - - - - ); }; diff --git a/ts/components/conversation/conversation-details/EditConversationAttributesModal.tsx b/ts/components/conversation/conversation-details/EditConversationAttributesModal.tsx index 41f057792..b628ac2a3 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 = (
@@ -199,40 +200,43 @@ export const EditConversationAttributesModal: FunctionComponent = ({ {i18n('updateGroupAttributes__error-message')}
)} - - - - - - ); } + const modalFooter = ( + <> + + + + + ); + return ( {content} diff --git a/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx b/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx index a2c2ec160..d94651c2c 100644 --- a/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx +++ b/ts/components/leftPane/LeftPaneSetGroupMetadataHelper.tsx @@ -152,7 +152,6 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper