Mm 62677 modal focus second part (#30099)

* MM-62312 - modal focus management; revamp quick switch channel modal!

* get quick switch test working

* configure the generic modal to accept refs to focus within and onhide to the origin element

* apply pr feedback, get modal element get autofocus, use id instead of ref

* update more direct channels modal to use generic modal

* fix unit tests and snapshots

* fix unit tests

* fix modal margin top to fit in smaller screens

* fix e2e test

* remove unnecesary onexited extra call

* fix e2e tests

* set correct label

* fix snapshots

* create helper function for sending custom focus event

* migrate quick switch modal to use new approach to focus

* migrate more direct channels modal to new approach

* fix snapshots

* fix types

* fix modal closing behavior

* fix snapshots

* fix cypress tests

* remove only

* MM-62677 - migrate modals, invite modal work

* user settings modal

* fix snapshots

* finish user settings migration

* migrate confirm modal to use generic modal

* notification preferences migration

* implement focus back to trigger to channel notifications modal

* fix test snapshots

* initial self code review

* fix CI errors, translation and some types

* add modal location param and adjust test

* fix cypress test text

* fix cypress test text

* fix e2e test for invitation modal

* fix e2e test selector

* adjust modal height

* temp

* fix e2e tests

* fix snapshot

* fix e2e tests

* fix snapshots

* fix snapshots

* fix snapshots

* fix e2e tests

* update snapshots

* fix snapshots

* fix linter

* Implement PR feedback

* fix e2e tests

* adjust styling for channel notifications modal

* more fixes to e2e tests

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Pablo Vélez
2025-03-10 23:20:59 +01:00
коммит произвёл GitHub
родитель 1c6a130787
Коммит 661f7f6a83
78 изменённых файлов: 3698 добавлений и 2826 удалений

Просмотреть файл

@@ -43,8 +43,6 @@ export function openInvitationsModal(timeout = 1): ActionFunc {
dispatch(openModal({
modalId: ModalIdentifiers.INVITATION,
dialogType: InvitationModal,
dialogProps: {
},
}));
}, timeout);
return {data: true};

Просмотреть файл

@@ -766,6 +766,7 @@ exports[`SystemUserDetail should show manage user settings button as activated 1
</button>
<button
class="manageUserSettingsBtn btn btn-tertiary"
id="manageUserSettingsBtn"
>
Manage User Settings
</button>

Просмотреть файл

@@ -302,6 +302,7 @@ export class SystemUserDetail extends PureComponent<Props, State> {
dialogProps: {
user: this.state.user,
onConfirm: this.openUserSettingsModal,
focusOriginElement: 'manageUserSettingsBtn',
},
});
};
@@ -318,6 +319,7 @@ export class SystemUserDetail extends PureComponent<Props, State> {
adminMode: true,
isContentProductSettings: true,
userID: this.state.user.id,
focusOriginElement: 'manageUserSettingsBtn',
},
});
};
@@ -448,6 +450,7 @@ export class SystemUserDetail extends PureComponent<Props, State> {
<button
className='manageUserSettingsBtn btn btn-tertiary'
onClick={this.openConfirmEditUserSettingsModal}
id='manageUserSettingsBtn'
>
<FormattedMessage
id='admin.user_item.manageSettings'

Просмотреть файл

@@ -8,15 +8,22 @@ import type {UserProfile} from '@mattermost/types/users';
import ConfirmModalRedux from 'components/confirm_modal_redux';
import {focusElement} from 'utils/a11y_utils';
import {getDisplayName} from 'utils/utils';
type Props = {
user: UserProfile;
onConfirm: () => void;
onExited: () => void;
focusOriginElement: string;
}
export default function ConfirmManageUserSettingsModal(props: Props) {
const handleExited = () => {
props.onExited?.();
focusElement(props.focusOriginElement, true, true);
};
const title = (
<FormattedMessage
id='userSettings.adminMode.modal_header'
@@ -49,7 +56,7 @@ export default function ConfirmManageUserSettingsModal(props: Props) {
message={message}
confirmButtonText={confirmButtonText}
onConfirm={props.onConfirm}
onExited={props.onExited}
onExited={handleExited}
/>
);
}

Просмотреть файл

@@ -134,6 +134,7 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
adminMode: true,
isContentProductSettings: true,
userID: user.id,
focusOriginElement: menuButtonId,
},
}));
}
@@ -144,6 +145,7 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
dialogProps: {
user,
onConfirm: onConfirmManageUserSettingsClick,
focusOriginElement: menuButtonId,
},
}));
}, [user]);

Просмотреть файл

@@ -135,7 +135,6 @@ function NotificationFromMembersModal(props: Props) {
backdrop={true}
show={show}
onExited={handleOnClose}
aria-modal='true'
modalHeaderText={modalHeaderText}
>
<MembersContainer>

Просмотреть файл

@@ -2,7 +2,6 @@
exports[`components/BrowseChannels should match snapshot and state 1`] = `
<GenericModal
aria-labelledby="browseChannelsModalLabel"
aria-modal={true}
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={false}
@@ -40,8 +39,11 @@ exports[`components/BrowseChannels should match snapshot and state 1`] = `
id="more_channels.title"
/>
}
modalLocation="center"
onExited={[Function]}
show={true}
showCloseButton={true}
showHeader={true}
>
<injectIntl(SearchableChannelList)
canShowArchivedChannels={true}

Просмотреть файл

@@ -372,7 +372,6 @@ export default class BrowseChannels extends React.PureComponent<Props, State> {
return (
<GenericModal
id='browseChannelsModal'
aria-labelledby='browseChannelsModalLabel'
onExited={this.handleExit}
compassDesign={true}
modalHeaderText={title}

Просмотреть файл

@@ -147,6 +147,7 @@ const ChannelHeaderTitle = ({
id='channelHeaderDropdownButton'
>
<button
id='channelHeaderMenuButton'
className={classNames('channel-header__trigger style--none', {active: titleMenuOpen})}
aria-label={
(isDirect && currentUser.id === dmUser?.id) ? personalChannelHeaderAriaLabel.toLowerCase() : othersChannelHeaderAriaLabel.toLowerCase()

Просмотреть файл

@@ -152,6 +152,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with no plugin i
"update_at": 0,
"username": "some-user",
},
"focusOriginElement": "channelHeaderMenuButton",
}
}
dialogType={
@@ -280,7 +281,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with no plugin i
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "channel_header.menuAriaLabel",
"focusOriginElement": "channelHeaderMenuButton",
"isExistingChannel": true,
}
}
@@ -985,6 +986,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with plugins 1`]
"update_at": 0,
"username": "some-user",
},
"focusOriginElement": "channelHeaderMenuButton",
}
}
dialogType={
@@ -1113,7 +1115,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with plugins 1`]
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "channel_header.menuAriaLabel",
"focusOriginElement": "channelHeaderMenuButton",
"isExistingChannel": true,
}
}

Просмотреть файл

@@ -137,6 +137,7 @@ export default class ChannelHeaderDropdown extends React.PureComponent<Props> {
dialogProps={{
channel,
currentUser: user,
focusOriginElement: 'channelHeaderMenuButton',
}}
text={localizeMessage({id: 'navbar.preferences', defaultMessage: 'Notification Preferences'})}
/>
@@ -167,7 +168,7 @@ export default class ChannelHeaderDropdown extends React.PureComponent<Props> {
show={channel.type === Constants.GM_CHANNEL && !isArchived && !isGroupConstrained}
modalId={ModalIdentifiers.CREATE_DM_CHANNEL}
dialogType={MoreDirectChannels}
dialogProps={{isExistingChannel: true, focusOriginElement: 'channel_header.menuAriaLabel'}}
dialogProps={{isExistingChannel: true, focusOriginElement: 'channelHeaderMenuButton'}}
text={localizeMessage({id: 'navbar.addMembers', defaultMessage: 'Add Members'})}
/>
</ChannelPermissionGate>

Просмотреть файл

@@ -131,7 +131,7 @@ const ChannelInfoRhs = ({
const openNotificationSettings = () => actions.openModal({
modalId: ModalIdentifiers.CHANNEL_NOTIFICATIONS,
dialogType: ChannelNotificationsModal,
dialogProps: {channel, currentUser},
dialogProps: {channel, currentUser, focusOriginElement: 'channelInfoRHSNotificationSettings'},
});
const gmUsers = channelMembers.filter((user) => {

Просмотреть файл

@@ -68,10 +68,11 @@ interface MenuItemProps {
opensSubpanel?: boolean;
badge?: string | number | JSX.Element;
onClick: () => void;
id?: string;
}
function MenuItem(props: MenuItemProps) {
const {icon, text, opensSubpanel, badge, onClick} = props;
const {icon, text, opensSubpanel, badge, onClick, id} = props;
const hasRightSide = (badge !== undefined) || opensSubpanel;
return (
@@ -79,6 +80,7 @@ function MenuItem(props: MenuItemProps) {
onClick={onClick}
aria-label={text}
type='button'
id={id || ''}
>
<Icon>{icon}</Icon>
<MenuItemText>{text}</MenuItemText>
@@ -148,6 +150,7 @@ export default function Menu(props: MenuProps) {
>
{showNotificationPreferences && (
<MenuItem
id='channelInfoRHSNotificationSettings'
icon={<i className='icon icon-bell-outline'/>}
text={formatMessage({
id: 'channel_info_rhs.menu.notification_preferences',

Просмотреть файл

@@ -460,10 +460,9 @@ export class ChannelInviteModal extends React.PureComponent<Props, State> {
this.props.actions.closeModal(ModalIdentifiers.CHANNEL_INVITE);
};
const InviteModalLink = (props: {inviteAsGuest?: boolean; children: React.ReactNode}) => {
const InviteModalLink = (props: {inviteAsGuest?: boolean; children: React.ReactNode; id?: string}) => {
return (
<ToggleModalButton
id='inviteGuest'
className={`${props.inviteAsGuest ? 'invite-as-guest' : ''} btn btn-link`}
modalId={ModalIdentifiers.INVITATION}
dialogType={InvitationModal}
@@ -471,8 +470,10 @@ export class ChannelInviteModal extends React.PureComponent<Props, State> {
channelToInvite: this.props.channel,
initialValue: this.state.term,
inviteAsGuest: props.inviteAsGuest,
focusOriginElement: 'customNoOptionsMessageLink',
}}
onClick={closeMembersInviteModal}
id={props.id}
>
{props.children}
</ToggleModalButton>
@@ -480,13 +481,15 @@ export class ChannelInviteModal extends React.PureComponent<Props, State> {
};
const customNoOptionsMessage = (
<div className='custom-no-options-message'>
<div
className='custom-no-options-message'
>
<FormattedMessage
id='channel_invite.no_options_message'
defaultMessage='No matches found - <InvitationModalLink>Invite them to the team</InvitationModalLink>'
values={{
InvitationModalLink: (chunks: string) => (
<InviteModalLink>
<InviteModalLink id='customNoOptionsMessageLink'>
{chunks}
</InviteModalLink>
),

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -1,17 +1,61 @@
.modal .channel-notifications-settings-modal {
overflow: hidden;
.modal .ChannelNotificationModal {
width: 100%;
max-width: 600px;
margin-top: -24px;
margin-top: 5vh !important;
overflow-y: hidden;
.modal-content {
display: flex;
overflow: hidden;
max-height: calc(100vh - 240px);
flex-direction: column;
border: var(--border-default);
border-radius: var(--radius-l);
box-shadow: var(--elevation-6);
overflow-y: hidden;
.GenericModal__wrapper {
display: flex;
overflow: hidden;
max-height: calc(100vh - 160px);
flex-direction: column;
border: var(--border-default);
border-radius: var(--radius-l);
box-shadow: var(--elevation-6);
.modal-header {
padding: 0 !important;
}
.modal-body {
display: flex;
width: 100%;
max-width: 1024px;
min-height: 150px;
flex-direction: column;
gap: 24px;
overflow-x: hidden;
overflow-y: auto;
main {
display: flex;
width: 100%;
max-width: 1024px;
min-height: 150px;
flex-direction: column;
padding: 28px 32px;
gap: 24px;
overflow-x: hidden;
overflow-y: auto;
}
}
.modal-footer {
display: flex;
width: 100%;
max-width: 1024px;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
}
}
&__ctr {
@@ -19,35 +63,6 @@
place-items: center;
}
.modal-header {
border: none;
border-bottom: var(--border-light);
background: none;
}
&__body {
display: flex;
width: 100%;
max-width: 1024px;
min-height: 150px;
flex-direction: column;
padding: 28px 32px;
gap: 24px;
overflow-x: hidden;
overflow-y: auto;
}
&__footer {
display: flex;
width: 100%;
max-width: 1024px;
align-items: center;
justify-content: flex-end;
padding: 24px 32px;
border-top: var(--border-light);
gap: 8px;
}
&__reset-btn {
display: flex;
align-items: center;
@@ -97,4 +112,11 @@
max-height: calc(100vh - 160px);
}
}
@media screen and (max-height: 600px) {
.modal-content, .GenericModal__wrapper {
max-height: 85vh !important;
}
}
}

Просмотреть файл

@@ -2,11 +2,11 @@
// See LICENSE.txt for license information.
import React, {useCallback, useState} from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, useIntl} from 'react-intl';
import type {OnChangeValue} from 'react-select';
import {BellOffOutlineIcon} from '@mattermost/compass-icons/components';
import {GenericModal} from '@mattermost/components';
import type {Channel, ChannelMembership, ChannelNotifyProps} from '@mattermost/types/channels';
import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
@@ -18,6 +18,7 @@ import ModalSection from 'components/widgets/modals/components/modal_section';
import RadioSettingItem from 'components/widgets/modals/components/radio_setting_item';
import type {Option} from 'components/widgets/modals/components/react_select_item';
import {focusElement} from 'utils/a11y_utils';
import {NotificationLevels, DesktopSound, IgnoreChannelMentions} from 'utils/constants';
import {convertDesktopSoundNotifyPropFromUserToDesktop, DesktopNotificationSounds, getValueOfNotificationSoundsSelect, stopTryNotificationRing, tryNotificationSound} from 'utils/notification_sounds';
@@ -44,6 +45,8 @@ export type Props = PropsFromRedux & {
* Object with info about current user
*/
currentUser: UserProfile;
focusOriginElement?: string;
};
export default function ChannelNotificationsModal(props: Props) {
@@ -66,6 +69,13 @@ export default function ChannelNotificationsModal(props: Props) {
setShow(false);
}
function handleExited() {
if (props.focusOriginElement) {
focusElement(props.focusOriginElement, true);
}
props.onExited?.();
}
const handleChange = useCallback((values: Record<string, string>) => {
setSettings((prevSettings) => ({...prevSettings, ...values}));
}, []);
@@ -87,7 +97,7 @@ export default function ChannelNotificationsModal(props: Props) {
function handleResetToDefaultClicked(channelNotifyPropsDefaultedToUserNotifyProps: ChannelMembership['notify_props'], sectionName: SectionName) {
if (sectionName === SectionName.Mobile) {
const desktopAndMobileSettingsDifferent = areDesktopAndMobileSettingsDifferent(
const desktopAndMobileSettingsDiff = areDesktopAndMobileSettingsDifferent(
props.collapsedReplyThreads,
settings.desktop,
settings.desktop_threads,
@@ -95,7 +105,7 @@ export default function ChannelNotificationsModal(props: Props) {
channelNotifyPropsDefaultedToUserNotifyProps.push_threads,
);
setDesktopAndMobileSettingDifferent(desktopAndMobileSettingsDifferent);
setDesktopAndMobileSettingDifferent(desktopAndMobileSettingsDiff);
}
setSettings({...settings, ...channelNotifyPropsDefaultedToUserNotifyProps});
@@ -277,7 +287,7 @@ export default function ChannelNotificationsModal(props: Props) {
const desktopAndMobileNotificationSectionContent = settings.mark_unread === 'all' ? (
<>
<div className='channel-notifications-settings-modal__divider'/>
<div className='ChannelNotificationModal__divider'/>
<ModalSection
title={formatMessage({
id: 'channel_notifications.desktopNotificationsTitle',
@@ -297,7 +307,7 @@ export default function ChannelNotificationsModal(props: Props) {
})}
content={desktopNotificationsSectionContent}
/>
<div className='channel-notifications-settings-modal__divider'/>
<div className='ChannelNotificationModal__divider'/>
<ModalSection
title={formatMessage({
id: 'channel_notifications.mobileNotificationsTitle',
@@ -344,32 +354,70 @@ export default function ChannelNotificationsModal(props: Props) {
/>
);
const footerContent = (
<footer className='ChannelNotificationModal__footer'>
{serverError &&
<span
role='alert'
className='ChannelNotificationModal__server-error'
>
{serverError}
</span>
}
<button
className='btn btn-tertiary btn-md'
onClick={handleHide}
>
<FormattedMessage
id='generic_btn.cancel'
defaultMessage='Cancel'
/>
</button>
<button
className='btn btn-primary btn-md'
onClick={handleSave}
>
<FormattedMessage
id='generic_btn.save'
defaultMessage='Save'
/>
</button>
</footer>
);
const headerText = (
<ModalHeader
id={'channelNotificationModalLabel'}
title={formatMessage({
id: 'channel_notifications.preferences',
defaultMessage: 'Notification Preferences',
})}
subtitle={props.channel.display_name}
/>
);
return (
<Modal
dialogClassName='a11y__modal channel-notifications-settings-modal'
<GenericModal
id='channelNotificationModal'
className='a11y__modal ChannelNotificationModal modal--overflow'
show={show}
onHide={handleHide}
onExited={props.onExited}
role='none'
aria-labelledby='channelNotificationModalLabel'
style={{display: 'flex', placeItems: 'center'}}
onExited={handleExited}
ariaLabel='channelNotificationModalLabel'
compassDesign={true}
footerContent={footerContent}
modalHeaderText={headerText}
bodyPadding={false}
footerDivider={true}
>
<ModalHeader
id={'channelNotificationModalLabel'}
title={formatMessage({
id: 'channel_notifications.preferences',
defaultMessage: 'Notification Preferences',
})}
subtitle={props.channel.display_name}
handleClose={handleHide}
/>
<main className='channel-notifications-settings-modal__body'>
<fieldset aria-labelledby='channel-notifications-settings-modal-legend'>
<main className='ChannelNotificationModal__body'>
<fieldset aria-labelledby='ChannelNotificationModal-legend'>
<ModalSection
title={
<legend
style={{all: 'unset'}}
id='channel-notifications-settings-modal-legend'
id='ChannelNotificationModal-legend'
>
{formatMessage({
id: 'channel_notifications.muteAndIgnore',
@@ -383,7 +431,7 @@ export default function ChannelNotificationsModal(props: Props) {
{desktopAndMobileNotificationSectionContent}
{props.collapsedReplyThreads &&
<>
<div className='channel-notifications-settings-modal__divider'/>
<div className='ChannelNotificationModal__divider'/>
<ModalSection
title={formatMessage({
id: 'channel_notifications.autoFollowThreadsTitle',
@@ -398,53 +446,60 @@ export default function ChannelNotificationsModal(props: Props) {
</>
}
</main>
<footer className='channel-notifications-settings-modal__footer'>
{serverError &&
<span
role='alert'
className='channel-notifications-settings-modal__server-error'
>
{serverError}
</span>
}
<button
className='btn btn-tertiary btn-md'
onClick={handleHide}
>
<FormattedMessage
id='generic_btn.cancel'
defaultMessage='Cancel'
/>
</button>
<button
className='btn btn-primary btn-md'
onClick={handleSave}
>
<FormattedMessage
id='generic_btn.save'
defaultMessage='Save'
/>
</button>
</footer>
</Modal>
</GenericModal>
);
}
function getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMembership['notify_props']): Required<Omit<ChannelMembership['notify_props'], 'email'>> {
function getStateFromNotifyProps(
currentUserNotifyProps: UserNotifyProps,
channelMemberNotifyProps?: ChannelMembership['notify_props'],
): Required<Omit<ChannelMembership['notify_props'], 'email'>> {
return {
mark_unread: channelMemberNotifyProps?.mark_unread ?? NotificationLevels.ALL,
ignore_channel_mentions: getInitialValuesOfIgnoreChannelMentions(channelMemberNotifyProps?.mark_unread, channelMemberNotifyProps?.ignore_channel_mentions, currentUserNotifyProps?.channel),
desktop: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop']>(NotificationLevels.ALL, channelMemberNotifyProps?.desktop, currentUserNotifyProps?.desktop),
desktop_threads: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop_threads']>(NotificationLevels.ALL, channelMemberNotifyProps?.desktop_threads, currentUserNotifyProps?.desktop_threads),
desktop_sound: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop_sound']>(DesktopSound.ON, channelMemberNotifyProps?.desktop_sound, convertDesktopSoundNotifyPropFromUserToDesktop(currentUserNotifyProps?.desktop_sound)),
desktop_notification_sound: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop_notification_sound']>(DesktopNotificationSounds.BING, channelMemberNotifyProps?.desktop_notification_sound, currentUserNotifyProps?.desktop_notification_sound),
push: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['push']>(NotificationLevels.ALL, channelMemberNotifyProps?.push, currentUserNotifyProps?.push),
push_threads: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['push_threads']>(NotificationLevels.ALL, channelMemberNotifyProps?.push_threads, currentUserNotifyProps?.push_threads),
ignore_channel_mentions: getInitialValuesOfIgnoreChannelMentions(
channelMemberNotifyProps?.mark_unread,
channelMemberNotifyProps?.ignore_channel_mentions,
currentUserNotifyProps?.channel,
),
desktop: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop']>(
NotificationLevels.ALL,
channelMemberNotifyProps?.desktop,
currentUserNotifyProps?.desktop,
),
desktop_threads: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop_threads']>(
NotificationLevels.ALL,
channelMemberNotifyProps?.desktop_threads,
currentUserNotifyProps?.desktop_threads,
),
desktop_sound: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop_sound']>(
DesktopSound.ON,
channelMemberNotifyProps?.desktop_sound,
convertDesktopSoundNotifyPropFromUserToDesktop(currentUserNotifyProps?.desktop_sound),
),
desktop_notification_sound: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop_notification_sound']>(
DesktopNotificationSounds.BING,
channelMemberNotifyProps?.desktop_notification_sound,
currentUserNotifyProps?.desktop_notification_sound,
),
push: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['push']>(
NotificationLevels.ALL,
channelMemberNotifyProps?.push,
currentUserNotifyProps?.push,
),
push_threads: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['push_threads']>(
NotificationLevels.ALL,
channelMemberNotifyProps?.push_threads,
currentUserNotifyProps?.push_threads,
),
channel_auto_follow_threads: channelMemberNotifyProps?.channel_auto_follow_threads ?? 'off',
};
}
export function getInitialValuesOfChannelNotifyProps<KeyInNotifyProps>(defaultValue: NonNullable<KeyInNotifyProps>, channelMemberNotifyProp: KeyInNotifyProps | undefined = undefined, userNotifyProp: KeyInNotifyProps | undefined = undefined) {
export function getInitialValuesOfChannelNotifyProps<KeyInNotifyProps>(
defaultValue: NonNullable<KeyInNotifyProps>,
channelMemberNotifyProp: KeyInNotifyProps | undefined = undefined,
userNotifyProp: KeyInNotifyProps | undefined = undefined,
) {
let value = defaultValue;
// Check if channel_member's notify_prop is defined for the selected notify_prop
@@ -470,7 +525,10 @@ export function getInitialValuesOfIgnoreChannelMentions(
userNotifyPropForChannel: UserNotifyProps['channel'],
): NonNullable<ChannelMembership['notify_props']['ignore_channel_mentions']> {
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
if (markUnread === NotificationLevels.MENTION || (userNotifyPropForChannel && userNotifyPropForChannel === 'false')) {
if (
markUnread === NotificationLevels.MENTION ||
(userNotifyPropForChannel && userNotifyPropForChannel === 'false')
) {
ignoreChannelMentionsDefault = IgnoreChannelMentions.ON;
}
@@ -533,8 +591,10 @@ export function createChannelNotifyPropsFromSelectedSettings(
// Otherwise, we should use the CHANNEL's new desktop_notification_sound setting as is
channelNotifyProps.desktop_notification_sound = savedChannelNotifyProps.desktop_notification_sound;
}
} else if (savedChannelNotifyProps.desktop_notification_sound === DesktopNotificationSounds.BING || savedChannelNotifyProps.desktop_notification_sound === DesktopNotificationSounds.DEFAULT) {
// If USER's desktop_notification_sound setting is not defined and CHANNEL's new desktop_notification_sound setting is either BING or DEFAULT, then save it as default
} else if (
savedChannelNotifyProps.desktop_notification_sound === DesktopNotificationSounds.BING ||
savedChannelNotifyProps.desktop_notification_sound === DesktopNotificationSounds.DEFAULT
) {
channelNotifyProps.desktop_notification_sound = DesktopNotificationSounds.DEFAULT;
} else {
// Otherwise, we should use the CHANNEL's new desktop_notification_sound setting as is
@@ -600,6 +660,5 @@ export function areDesktopAndMobileSettingsDifferent(
if (push === NotificationLevels.DEFAULT || push === desktop) {
return isCollapsedThreadsEnabled && desktopThreads !== pushThreads;
}
return true;
}

Просмотреть файл

@@ -3,7 +3,7 @@
exports[`ResetToDefaultButton should render if section name is valid 1`] = `
<div>
<button
class="channel-notifications-settings-modal__reset-btn"
class="ChannelNotificationModal__reset-btn"
data-testid="resetToDefaultButton-desktop"
>
<i

Просмотреть файл

@@ -83,7 +83,7 @@ export default function ResetToDefaultButton(props: Props) {
return (
<button
className='channel-notifications-settings-modal__reset-btn'
className='ChannelNotificationModal__reset-btn'
onClick={handleOnClick}
data-testid={`resetToDefaultButton-${props.sectionName}`}
>

Просмотреть файл

@@ -22,5 +22,5 @@
.modal-header .modal-title {
font-family: Metropolis, sans-serif;
font-weight: 600;
font-weight: 600 !important;
}

Просмотреть файл

@@ -1,3 +1,11 @@
.modal-confirm .modal-footer .checkbox {
margin-right: auto;
.ConfirmModal {
&__footer {
display: flex;
justify-content: end;
padding: 32px 0;
.checkbox {
margin-right: auto;
}
}
}

Просмотреть файл

@@ -3,9 +3,10 @@
import classNames from 'classnames';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {GenericModal} from '@mattermost/components';
import './confirm_modal.scss';
type Props = {
@@ -167,45 +168,40 @@ export default class ConfirmModal extends React.Component<Props, State> {
}
return (
<Modal
<GenericModal
id={classNames('confirmModal', this.props.id)}
className={'modal-confirm ' + this.props.modalClass}
dialogClassName='a11y__modal'
className={`ConfirmModal a11y__modal ${this.props.modalClass}`}
show={this.props.show}
onHide={this.handleCancel}
onExited={this.props.onExited}
role='none'
aria-modal={true}
aria-labelledby='confirmModalLabel'
aria-describedby='confirmModalBody'
data-testid={this.props.id}
compassDesign={true}
modalHeaderText={this.props.title}
>
<Modal.Header closeButton={true}>
<Modal.Title
componentClass='h1'
id='confirmModalLabel'
<div
data-testid={this.props.id}
>
<div
className='ConfirmModal__body'
id='confirmModalBody'
>
{this.props.title}
</Modal.Title>
</Modal.Header>
<Modal.Body id='confirmModalBody'>
{this.props.message}
{!this.props.checkboxInFooter && checkbox}
</Modal.Body>
<Modal.Footer>
{this.props.checkboxInFooter && checkbox}
{cancelButton}
<button
autoFocus={true}
type='button'
className={this.props.confirmButtonClass}
onClick={this.handleConfirm}
id='confirmModalButton'
>
{this.props.confirmButtonText}
</button>
</Modal.Footer>
</Modal>
{this.props.message}
{!this.props.checkboxInFooter && checkbox}
</div>
<div className='ConfirmModal__footer'>
{this.props.checkboxInFooter && checkbox}
{cancelButton}
<button
autoFocus={true}
type='button'
className={this.props.confirmButtonClass}
onClick={this.handleConfirm}
id='confirmModalButton'
>
{this.props.confirmButtonText}
</button>
</div>
</div>
</GenericModal>
);
}
}

Просмотреть файл

@@ -27,8 +27,11 @@ exports[`components/drafts/draft_actions/delete_draft_modal should have called o
isDeleteModal={true}
keyboardEscape={true}
modalHeaderText="Delete draft"
modalLocation="center"
onExited={[MockFunction]}
show={true}
showCloseButton={true}
showHeader={true}
>
<MemoizedFormattedMessage
defaultMessage="Are you sure you want to delete this draft to <strong>{displayName}</strong>?"
@@ -58,6 +61,7 @@ exports[`components/drafts/draft_actions/delete_draft_modal should have called o
isDeleteModal={true}
keyboardEscape={true}
modalHeaderText="Delete draft"
modalLocation="center"
onExited={
[MockFunction] {
"calls": Array [
@@ -72,6 +76,8 @@ exports[`components/drafts/draft_actions/delete_draft_modal should have called o
}
}
show={true}
showCloseButton={true}
showHeader={true}
>
<MemoizedFormattedMessage
defaultMessage="Are you sure you want to delete this draft to <strong>{displayName}</strong>?"

Просмотреть файл

@@ -25,8 +25,11 @@ exports[`components/drafts/draft_actions/send_draft_modal should have called onC
id="genericModal"
keyboardEscape={true}
modalHeaderText="Send message now"
modalLocation="center"
onExited={[MockFunction]}
show={true}
showCloseButton={true}
showHeader={true}
>
<MemoizedFormattedMessage
defaultMessage="Are you sure you want to send this message to <strong>{displayName}</strong>?"
@@ -54,6 +57,7 @@ exports[`components/drafts/draft_actions/send_draft_modal should have called onE
id="genericModal"
keyboardEscape={true}
modalHeaderText="Send message now"
modalLocation="center"
onExited={
[MockFunction] {
"calls": Array [
@@ -68,6 +72,8 @@ exports[`components/drafts/draft_actions/send_draft_modal should have called onE
}
}
show={true}
showCloseButton={true}
showHeader={true}
>
<MemoizedFormattedMessage
defaultMessage="Are you sure you want to send this message to <strong>{displayName}</strong>?"

Просмотреть файл

@@ -46,7 +46,7 @@ describe('components/drafts/draft_actions/delete_draft_modal', () => {
<DeleteDraftModal {...baseProps}/>,
);
wrapper.find(GenericModal).first().props().onExited();
wrapper.find(GenericModal).first().props().onExited?.();
expect(baseProps.onExited).toHaveBeenCalledTimes(1);
expect(wrapper).toMatchSnapshot();
});

Просмотреть файл

@@ -46,7 +46,7 @@ describe('components/drafts/draft_actions/send_draft_modal', () => {
<SendDraftModal {...baseProps}/>,
);
wrapper.find(GenericModal).first().props().onExited();
wrapper.find(GenericModal).first().props().onExited?.();
expect(baseProps.onExited).toHaveBeenCalledTimes(1);
expect(wrapper).toMatchSnapshot();
});

Просмотреть файл

@@ -140,7 +140,6 @@ export default class EditCategoryModal extends React.PureComponent<Props, State>
return (
<GenericModal
id='editCategoryModal'
ariaLabel={localizeMessage({id: 'rename_category_modal.renameCategory', defaultMessage: 'Rename Category'})}
modalHeaderText={modalHeaderText}
confirmButtonText={editButtonText}
compassDesign={true}

Просмотреть файл

@@ -35,7 +35,7 @@ const SettingsButton = (props: Props): JSX.Element | null => {
size={'sm'}
icon={'settings-outline'}
onClick={(): void => {
props.actions.openModal({modalId: ModalIdentifiers.USER_SETTINGS, dialogType: UserSettingsModal, dialogProps: {isContentProductSettings: true}});
props.actions.openModal({modalId: ModalIdentifiers.USER_SETTINGS, dialogType: UserSettingsModal, dialogProps: {isContentProductSettings: true, focusOriginElement: 'settings_button'}});
}}
inverted={true}
compact={true}

Просмотреть файл

@@ -497,46 +497,34 @@ exports[`components/integrations/AbstractOutgoingOAuthConnection should match sn
show={false}
title="Save Outgoing OAuth Connection"
>
<Modal
animation={true}
aria-describedby="confirmModalBody"
aria-labelledby="confirmModalLabel"
aria-modal={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
className="modal-confirm "
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
<GenericModal
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={true}
bodyPadding={true}
className="ConfirmModal a11y__modal "
compassDesign={true}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
keyboardEscape={true}
modalHeaderText="Save Outgoing OAuth Connection"
modalLocation="center"
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
showCloseButton={true}
showHeader={true}
>
<Modal
animation={true}
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
bsClass="modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign ConfirmModal a11y__modal "
dialogComponentClass={[Function]}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
@@ -550,15 +538,43 @@ exports[`components/integrations/AbstractOutgoingOAuthConnection should match sn
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
transition={[Function]}
/>
</Modal>
>
<Modal
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={false}
transition={[Function]}
/>
</Modal>
</GenericModal>
</ConfirmModal>
</div>
</AbstractOutgoingOAuthConnection>
@@ -1084,46 +1100,34 @@ exports[`components/integrations/AbstractOutgoingOAuthConnection should match sn
show={false}
title="Save Outgoing OAuth Connection"
>
<Modal
animation={true}
aria-describedby="confirmModalBody"
aria-labelledby="confirmModalLabel"
aria-modal={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
className="modal-confirm "
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
<GenericModal
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={true}
bodyPadding={true}
className="ConfirmModal a11y__modal "
compassDesign={true}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
keyboardEscape={true}
modalHeaderText="Save Outgoing OAuth Connection"
modalLocation="center"
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
showCloseButton={true}
showHeader={true}
>
<Modal
animation={true}
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
bsClass="modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign ConfirmModal a11y__modal "
dialogComponentClass={[Function]}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
@@ -1137,15 +1141,43 @@ exports[`components/integrations/AbstractOutgoingOAuthConnection should match sn
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
transition={[Function]}
/>
</Modal>
>
<Modal
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={false}
transition={[Function]}
/>
</Modal>
</GenericModal>
</ConfirmModal>
</div>
</AbstractOutgoingOAuthConnection>

Просмотреть файл

@@ -486,46 +486,34 @@ exports[`components/integrations/AddOutgoingOAuthConnection should match snapsho
show={false}
title="Save Outgoing OAuth Connection"
>
<Modal
animation={true}
aria-describedby="confirmModalBody"
aria-labelledby="confirmModalLabel"
aria-modal={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
className="modal-confirm "
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
<GenericModal
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={true}
bodyPadding={true}
className="ConfirmModal a11y__modal "
compassDesign={true}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
keyboardEscape={true}
modalHeaderText="Save Outgoing OAuth Connection"
modalLocation="center"
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
showCloseButton={true}
showHeader={true}
>
<Modal
animation={true}
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
bsClass="modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign ConfirmModal a11y__modal "
dialogComponentClass={[Function]}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
@@ -539,15 +527,43 @@ exports[`components/integrations/AddOutgoingOAuthConnection should match snapsho
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
transition={[Function]}
/>
</Modal>
>
<Modal
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={false}
transition={[Function]}
/>
</Modal>
</GenericModal>
</ConfirmModal>
</div>
</AbstractOutgoingOAuthConnection>

Просмотреть файл

@@ -548,45 +548,38 @@ https://myothersite.com/api/v2"
/>
}
>
<Modal
animation={true}
aria-describedby="confirmModalBody"
aria-labelledby="confirmModalLabel"
aria-modal={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
className="modal-confirm integrations-backstage-modal"
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
<GenericModal
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={true}
bodyPadding={true}
className="ConfirmModal a11y__modal integrations-backstage-modal"
compassDesign={true}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
keyboardEscape={true}
modalHeaderText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Edit Outgoing OAuth Connection"
id="update_outgoing_oauth_connection.confirm"
/>
}
modalLocation="center"
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
showCloseButton={true}
showHeader={true}
>
<Modal
animation={true}
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
bsClass="modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign ConfirmModal a11y__modal integrations-backstage-modal"
dialogComponentClass={[Function]}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
@@ -600,15 +593,42 @@ https://myothersite.com/api/v2"
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
transition={[Function]}
/>
</Modal>
>
<Modal
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={false}
transition={[Function]}
/>
</Modal>
</GenericModal>
</ConfirmModal>
</div>
</form>
@@ -648,46 +668,34 @@ https://myothersite.com/api/v2"
show={false}
title="Save Outgoing OAuth Connection"
>
<Modal
animation={true}
aria-describedby="confirmModalBody"
aria-labelledby="confirmModalLabel"
aria-modal={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
className="modal-confirm "
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
<GenericModal
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={true}
bodyPadding={true}
className="ConfirmModal a11y__modal "
compassDesign={true}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
keyboardEscape={true}
modalHeaderText="Save Outgoing OAuth Connection"
modalLocation="center"
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
showCloseButton={true}
showHeader={true}
>
<Modal
animation={true}
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
bsClass="modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign ConfirmModal a11y__modal "
dialogComponentClass={[Function]}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
@@ -701,15 +709,43 @@ https://myothersite.com/api/v2"
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
transition={[Function]}
/>
</Modal>
>
<Modal
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={false}
transition={[Function]}
/>
</Modal>
</GenericModal>
</ConfirmModal>
</div>
</AbstractOutgoingOAuthConnection>
@@ -1268,45 +1304,38 @@ https://myothersite.com/api/v2"
/>
}
>
<Modal
animation={true}
aria-describedby="confirmModalBody"
aria-labelledby="confirmModalLabel"
aria-modal={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
className="modal-confirm integrations-backstage-modal"
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
<GenericModal
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={true}
bodyPadding={true}
className="ConfirmModal a11y__modal integrations-backstage-modal"
compassDesign={true}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
keyboardEscape={true}
modalHeaderText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Edit Outgoing OAuth Connection"
id="update_outgoing_oauth_connection.confirm"
/>
}
modalLocation="center"
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
showCloseButton={true}
showHeader={true}
>
<Modal
animation={true}
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
bsClass="modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign ConfirmModal a11y__modal integrations-backstage-modal"
dialogComponentClass={[Function]}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
@@ -1320,15 +1349,42 @@ https://myothersite.com/api/v2"
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
transition={[Function]}
/>
</Modal>
>
<Modal
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={false}
transition={[Function]}
/>
</Modal>
</GenericModal>
</ConfirmModal>
</div>
</form>
@@ -1368,46 +1424,34 @@ https://myothersite.com/api/v2"
show={false}
title="Save Outgoing OAuth Connection"
>
<Modal
animation={true}
aria-describedby="confirmModalBody"
aria-labelledby="confirmModalLabel"
aria-modal={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
className="modal-confirm "
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
<GenericModal
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={true}
bodyPadding={true}
className="ConfirmModal a11y__modal "
compassDesign={true}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
keyboardEscape={true}
modalHeaderText="Save Outgoing OAuth Connection"
modalLocation="center"
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
showCloseButton={true}
showHeader={true}
>
<Modal
animation={true}
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
bsClass="modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign ConfirmModal a11y__modal "
dialogComponentClass={[Function]}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
@@ -1421,15 +1465,43 @@ https://myothersite.com/api/v2"
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
transition={[Function]}
/>
</Modal>
>
<Modal
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={false}
transition={[Function]}
/>
</Modal>
</GenericModal>
</ConfirmModal>
</div>
</AbstractOutgoingOAuthConnection>
@@ -1987,45 +2059,38 @@ https://myothersite.com/api/v2"
/>
}
>
<Modal
animation={true}
aria-describedby="confirmModalBody"
aria-labelledby="confirmModalLabel"
aria-modal={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
className="modal-confirm integrations-backstage-modal"
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
<GenericModal
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={true}
bodyPadding={true}
className="ConfirmModal a11y__modal integrations-backstage-modal"
compassDesign={true}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
keyboardEscape={true}
modalHeaderText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Edit Outgoing OAuth Connection"
id="update_outgoing_oauth_connection.confirm"
/>
}
modalLocation="center"
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
showCloseButton={true}
showHeader={true}
>
<Modal
animation={true}
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
bsClass="modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign ConfirmModal a11y__modal integrations-backstage-modal"
dialogComponentClass={[Function]}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
@@ -2039,15 +2104,42 @@ https://myothersite.com/api/v2"
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
transition={[Function]}
/>
</Modal>
>
<Modal
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={false}
transition={[Function]}
/>
</Modal>
</GenericModal>
</ConfirmModal>
</div>
</form>
@@ -2087,46 +2179,34 @@ https://myothersite.com/api/v2"
show={false}
title="Save Outgoing OAuth Connection"
>
<Modal
animation={true}
aria-describedby="confirmModalBody"
aria-labelledby="confirmModalLabel"
aria-modal={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
className="modal-confirm "
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
<GenericModal
autoCloseOnCancelButton={true}
autoCloseOnConfirmButton={true}
bodyPadding={true}
className="ConfirmModal a11y__modal "
compassDesign={true}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
keyboardEscape={true}
modalHeaderText="Save Outgoing OAuth Connection"
modalLocation="center"
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
showCloseButton={true}
showHeader={true}
>
<Modal
animation={true}
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
bsClass="modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign ConfirmModal a11y__modal "
dialogComponentClass={[Function]}
enforceFocus={true}
id="confirmModal"
keyboard={true}
manager={
ModalManager {
@@ -2140,15 +2220,43 @@ https://myothersite.com/api/v2"
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="none"
show={false}
transition={[Function]}
/>
</Modal>
>
<Modal
autoFocus={true}
backdrop={true}
backdropClassName="modal-backdrop"
backdropTransition={[Function]}
containerClassName="modal-open"
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onEntering={[Function]}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={false}
transition={[Function]}
/>
</Modal>
</GenericModal>
</ConfirmModal>
</div>
</AbstractOutgoingOAuthConnection>

Просмотреть файл

@@ -12,5 +12,13 @@
justify-content: center;
margin: auto;
transform: translate(-50%, -50%) !important;
}
&__header {
padding-left: 0 !important;
}
&__footer {
padding-right: 0 !important;
}
}

Просмотреть файл

@@ -48,6 +48,7 @@ const defaultProps: Props = deepFreeze({
townSquareDisplayName: '',
onExited: jest.fn(),
roleForTrackFlow: {started_by_role: General.SYSTEM_USER_ROLE},
focusOriginElement: 'elementId',
});
let props = defaultProps;

Просмотреть файл

@@ -2,9 +2,9 @@
// See LICENSE.txt for license information.
import React from 'react';
import {Modal} from 'react-bootstrap';
import {defineMessages} from 'react-intl';
import {GenericModal} from '@mattermost/components';
import type {Channel} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
@@ -16,6 +16,8 @@ import {isEmail} from 'mattermost-redux/utils/helpers';
import {trackEvent} from 'actions/telemetry_actions';
import {focusElement} from 'utils/a11y_utils';
import {InviteType} from './invite_as';
import InviteView, {initializeInviteState} from './invite_view';
import type {InviteState} from './invite_view';
@@ -81,6 +83,7 @@ export type Props = {
initialValue?: string;
inviteAsGuest?: boolean;
roleForTrackFlow: {started_by_role: string};
focusOriginElement: string;
}
export const View = {
@@ -128,6 +131,11 @@ export default class InvitationModal extends React.PureComponent<Props, State> {
this.setState({show: false});
};
handleExit = () => {
focusElement(this.props.focusOriginElement, true);
this.props.onExited?.();
};
toggleCustomMessage = () => {
this.setState((state) => ({
...state,
@@ -416,21 +424,21 @@ export default class InvitationModal extends React.PureComponent<Props, State> {
}
return (
<Modal
<GenericModal
id='invitationModal'
data-testid='invitationModal'
dialogClassName='a11y__modal modal--overflow'
className='InvitationModal'
dataTestId='invitationModal'
className='InvitationModal a11y__modal modal--overflow'
show={this.state.show}
onHide={this.handleHide}
onExited={this.props.onExited}
role='none'
onExited={this.handleExit}
backdrop={this.getBackdrop()}
aria-modal='true'
aria-labelledby='invitation_modal_title'
ariaLabelledby='invitation_modal_title'
compassDesign={true}
showCloseButton={false}
showHeader={false}
>
{view}
</Modal>
</GenericModal>
);
}
}

Просмотреть файл

@@ -115,7 +115,7 @@ export default function InviteAs(props: Props) {
clickCallback={closeInviteModal}
tooltipMessage={hasPriorTrial ? formatMessage({id: 'free.professional_feature.upgrade', defaultMessage: 'Upgrade'}) : undefined}
// the secondary back button first closes the restridted feature modal and then opens back the invitation modal
// the secondary back button first closes the restricted feature modal and then opens back the invitation modal
customSecondaryButtonInModal={hasPriorTrial ? undefined : {
msg: formatMessage({id: 'free.professional_feature.back', defaultMessage: 'Back'}),
action: () => {

Просмотреть файл

@@ -11,9 +11,14 @@
}
}
&__header {
padding-left: 0 !important;
}
&__footer {
display: flex;
justify-content: space-between;
padding-right: 0 !important;
// to override clearfix rule in clearfix.less
&::before,

Просмотреть файл

@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import classNames from 'classnames';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
@@ -86,7 +87,7 @@ export default function ResultView(props: Props) {
/>
)}
</Modal.Body>
<Modal.Footer className={props.footerClass}>
<Modal.Footer className={classNames('InviteView__footer', props.footerClass)}>
<button
onClick={props.inviteMore}
className='btn btn-tertiary ResultView__inviteMore'

Просмотреть файл

@@ -99,7 +99,7 @@ describe('components/learn_more_trial_modal/learn_more_trial_modal', () => {
</Provider>,
);
wrapper.find(GenericModal).props().onExited();
wrapper.find(GenericModal).props().onExited?.();
expect(mockOnClose).toHaveBeenCalled();
});
@@ -116,7 +116,7 @@ describe('components/learn_more_trial_modal/learn_more_trial_modal', () => {
</Provider>,
);
wrapper.find(GenericModal).props().onExited();
wrapper.find(GenericModal).props().onExited?.();
expect(mockOnExited).toHaveBeenCalled();
});

Просмотреть файл

@@ -278,6 +278,11 @@ exports[`components/Menu should match snapshot with id 1`] = `
teamId="team-id"
>
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "sidebarDropdownMenuButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),
@@ -485,6 +490,11 @@ exports[`components/Menu should match snapshot with most of the thing disabled 1
teamId="team-id"
>
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "sidebarDropdownMenuButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),
@@ -714,6 +724,7 @@ exports[`components/Menu should match snapshot with most of the thing disabled i
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "userAccountMenuButton2",
"isContentProductSettings": false,
}
}
@@ -738,6 +749,7 @@ exports[`components/Menu should match snapshot with most of the thing disabled i
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "userAccountMenuButton3",
"isContentProductSettings": true,
}
}
@@ -799,6 +811,11 @@ exports[`components/Menu should match snapshot with most of the thing disabled i
teamId="team-id"
>
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "sidebarDropdownMenuButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),
@@ -1105,6 +1122,11 @@ exports[`components/Menu should match snapshot with most of the thing enabled 1`
teamId="team-id"
>
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "sidebarDropdownMenuButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),
@@ -1334,6 +1356,7 @@ exports[`components/Menu should match snapshot with most of the thing enabled in
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "userAccountMenuButton2",
"isContentProductSettings": false,
}
}
@@ -1358,6 +1381,7 @@ exports[`components/Menu should match snapshot with most of the thing enabled in
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "userAccountMenuButton3",
"isContentProductSettings": true,
}
}
@@ -1419,6 +1443,11 @@ exports[`components/Menu should match snapshot with most of the thing enabled in
teamId="team-id"
>
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "sidebarDropdownMenuButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),
@@ -1727,6 +1756,11 @@ exports[`components/Menu should match snapshot with plugins 1`] = `
teamId="team-id"
>
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "sidebarDropdownMenuButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),
@@ -1973,6 +2007,7 @@ exports[`components/Menu should match snapshot with plugins in mobile 1`] = `
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "userAccountMenuButton2",
"isContentProductSettings": false,
}
}
@@ -1997,6 +2032,7 @@ exports[`components/Menu should match snapshot with plugins in mobile 1`] = `
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "userAccountMenuButton3",
"isContentProductSettings": true,
}
}
@@ -2058,6 +2094,11 @@ exports[`components/Menu should match snapshot with plugins in mobile 1`] = `
teamId="team-id"
>
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "sidebarDropdownMenuButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),

Просмотреть файл

@@ -94,7 +94,7 @@ export class MainMenu extends React.PureComponent<Props> {
handleKeyDown = (e: KeyboardEvent): void => {
if (cmdOrCtrlPressed(e) && e.shiftKey && isKeyPressed(e, Constants.KeyCodes.A)) {
e.preventDefault();
this.props.actions.openModal({modalId: ModalIdentifiers.USER_SETTINGS, dialogType: UserSettingsModal, dialogProps: {isContentProductSettings: true}});
this.props.actions.openModal({modalId: ModalIdentifiers.USER_SETTINGS, dialogType: UserSettingsModal, dialogProps: {isContentProductSettings: true, focusOriginElement: 'userAccountMenuButton'}});
}
};
@@ -174,6 +174,7 @@ export class MainMenu extends React.PureComponent<Props> {
})}
icon={this.props.mobile && <i className='fa fa-user-plus'/>}
onClick={() => trackEvent('ui', 'click_sidebar_team_dropdown_invite_people')}
dialogProps={{focusOriginElement: 'sidebarDropdownMenuButton'}}
/>
);
}
@@ -220,7 +221,7 @@ export class MainMenu extends React.PureComponent<Props> {
id='profileSettings'
modalId={ModalIdentifiers.USER_SETTINGS}
dialogType={UserSettingsModal}
dialogProps={{isContentProductSettings: false}}
dialogProps={{isContentProductSettings: false, focusOriginElement: 'userAccountMenuButton2'}}
text={formatMessage({id: 'navbar_dropdown.profileSettings', defaultMessage: 'Profile'})}
icon={<i className='fa fa-user'/>}
/>
@@ -228,7 +229,7 @@ export class MainMenu extends React.PureComponent<Props> {
id='accountSettings'
modalId={ModalIdentifiers.USER_SETTINGS}
dialogType={UserSettingsModal}
dialogProps={{isContentProductSettings: true}}
dialogProps={{isContentProductSettings: true, focusOriginElement: 'userAccountMenuButton3'}}
text={formatMessage({id: 'navbar_dropdown.accountSettings', defaultMessage: 'Settings'})}
icon={<i className='fa fa-cog'/>}
/>

Просмотреть файл

@@ -16,10 +16,13 @@ exports[`components/MoreDirectChannels should exclude deleted users if there is
id="more_direct_channels.title"
/>
}
modalLocation="top"
onEntered={[Function]}
onExited={[Function]}
onHide={[Function]}
show={true}
showCloseButton={true}
showHeader={true}
>
<div
role="application"
@@ -262,10 +265,13 @@ exports[`components/MoreDirectChannels should match snapshot 1`] = `
id="more_direct_channels.title"
/>
}
modalLocation="top"
onEntered={[Function]}
onExited={[Function]}
onHide={[Function]}
show={true}
showCloseButton={true}
showHeader={true}
>
<div
role="application"

Просмотреть файл

@@ -21,7 +21,6 @@ import List from './list';
import {USERS_PER_PAGE} from './list/list';
import {isGroupChannel, optionValue} from './types';
import type {OptionValue} from './types';
import './more_direct_channels.scss';
export type Props = {
currentUserId: string;
@@ -296,6 +295,7 @@ export default class MoreDirectChannels extends React.PureComponent<Props, State
compassDesign={true}
bodyPadding={false}
onEntered={this.loadModalData}
modalLocation={'top'}
>
<div role='application'>
{body}

Просмотреть файл

@@ -4,10 +4,12 @@ exports[`components/marketplace/ doesn't show web marketplace banner in FeatureF
<Modal
animation={true}
aria-label="App Marketplace"
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal GenericModal__compassDesign marketplace-modal streamlined-marketplace"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign marketplace-modal streamlined-marketplace"
dialogComponentClass={[Function]}
enforceFocus={true}
id="marketplace-modal"
@@ -32,7 +34,7 @@ exports[`components/marketplace/ doesn't show web marketplace banner in FeatureF
show={true}
>
<div
className="GenericModal__wrapper-enter-key-press-catcher"
className="GenericModal__wrapper GenericModal__wrapper-enter-key-press-catcher"
onKeyDown={[Function]}
tabIndex={0}
>
@@ -94,10 +96,12 @@ exports[`components/marketplace/ hides search, shows web marketplace banner in F
<Modal
animation={true}
aria-label="App Marketplace"
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal GenericModal__compassDesign marketplace-modal streamlined-marketplace with-web-marketplace-link"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign marketplace-modal streamlined-marketplace with-web-marketplace-link"
dialogComponentClass={[Function]}
enforceFocus={true}
id="marketplace-modal"
@@ -122,7 +126,7 @@ exports[`components/marketplace/ hides search, shows web marketplace banner in F
show={true}
>
<div
className="GenericModal__wrapper-enter-key-press-catcher"
className="GenericModal__wrapper GenericModal__wrapper-enter-key-press-catcher"
onKeyDown={[Function]}
tabIndex={0}
>
@@ -185,10 +189,12 @@ exports[`components/marketplace/ should render default 1`] = `
<Modal
animation={true}
aria-label="App Marketplace"
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogComponentClass={[Function]}
enforceFocus={true}
id="marketplace-modal"
@@ -213,7 +219,7 @@ exports[`components/marketplace/ should render default 1`] = `
show={true}
>
<div
className="GenericModal__wrapper-enter-key-press-catcher"
className="GenericModal__wrapper GenericModal__wrapper-enter-key-press-catcher"
onKeyDown={[Function]}
tabIndex={0}
>
@@ -327,10 +333,12 @@ exports[`components/marketplace/ should render with error banner 1`] = `
<Modal
animation={true}
aria-label="App Marketplace"
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogComponentClass={[Function]}
enforceFocus={true}
id="marketplace-modal"
@@ -355,7 +363,7 @@ exports[`components/marketplace/ should render with error banner 1`] = `
show={true}
>
<div
className="GenericModal__wrapper-enter-key-press-catcher"
className="GenericModal__wrapper GenericModal__wrapper-enter-key-press-catcher"
onKeyDown={[Function]}
tabIndex={0}
>
@@ -486,10 +494,12 @@ exports[`components/marketplace/ should render with no plugins available 1`] = `
<Modal
animation={true}
aria-label="App Marketplace"
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogComponentClass={[Function]}
enforceFocus={true}
id="marketplace-modal"
@@ -514,7 +524,7 @@ exports[`components/marketplace/ should render with no plugins available 1`] = `
show={true}
>
<div
className="GenericModal__wrapper-enter-key-press-catcher"
className="GenericModal__wrapper GenericModal__wrapper-enter-key-press-catcher"
onKeyDown={[Function]}
tabIndex={0}
>
@@ -636,10 +646,12 @@ exports[`components/marketplace/ should render with plugins available 1`] = `
<Modal
animation={true}
aria-label="App Marketplace"
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogComponentClass={[Function]}
enforceFocus={true}
id="marketplace-modal"
@@ -664,7 +676,7 @@ exports[`components/marketplace/ should render with plugins available 1`] = `
show={true}
>
<div
className="GenericModal__wrapper-enter-key-press-catcher"
className="GenericModal__wrapper GenericModal__wrapper-enter-key-press-catcher"
onKeyDown={[Function]}
tabIndex={0}
>
@@ -804,10 +816,12 @@ exports[`components/marketplace/ should render with plugins installed 1`] = `
<Modal
animation={true}
aria-label="App Marketplace"
aria-labelledby="genericModalLabel"
aria-modal="true"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogClassName="GenericModal__location--center a11y__modal GenericModal GenericModal__compassDesign marketplace-modal"
dialogComponentClass={[Function]}
enforceFocus={true}
id="marketplace-modal"
@@ -832,7 +846,7 @@ exports[`components/marketplace/ should render with plugins installed 1`] = `
show={true}
>
<div
className="GenericModal__wrapper-enter-key-press-catcher"
className="GenericModal__wrapper GenericModal__wrapper-enter-key-press-catcher"
onKeyDown={[Function]}
tabIndex={0}
>

Просмотреть файл

@@ -72,6 +72,7 @@ const LessThanMaxFreeUsers = ({pluginButtons}: {pluginButtons: React.ReactNode})
modalId={ModalIdentifiers.INVITATION}
dialogType={InvitationModal}
onClick={() => trackEvent('channel_intro_message', 'click_invite_button')}
dialogProps={{focusOriginElement: 'browseOrAddChannelMenuButton'}}
>
<i
className='icon-email-plus-outline'

Просмотреть файл

@@ -753,11 +753,12 @@ function createFavoriteButton(isFavorite: boolean, toggleFavorite: () => void, c
function createNotificationPreferencesButton(channel: Channel, currentUser: UserProfileType) {
return (
<ToggleModalButton
id='channelIntroNotificationPreferencesButton'
modalId={ModalIdentifiers.CHANNEL_NOTIFICATIONS}
ariaLabel={Utils.localizeMessage({id: 'intro_messages.notificationPreferences', defaultMessage: 'Notification Preferences'})}
className={'action-button'}
dialogType={ChannelNotificationsModal}
dialogProps={{channel, currentUser}}
dialogProps={{channel, currentUser, focusOriginElement: 'channelIntroNotificationPreferencesButton'}}
>
<BellRingOutlineIcon size={24}/>
<FormattedMessage

Просмотреть файл

@@ -23,8 +23,11 @@ exports[`ProductNoticesModal Match snapshot for single notice 1`] = `
title
</span>
}
modalLocation="center"
onExited={[Function]}
show={true}
showCloseButton={true}
showHeader={true}
>
<span
className="productNotices__helpText"
@@ -75,8 +78,11 @@ exports[`ProductNoticesModal Match snapshot for user notice 1`] = `
title
</span>
}
modalLocation="center"
onExited={[Function]}
show={true}
showCloseButton={true}
showHeader={true}
>
<span
className="productNotices__helpText"
@@ -147,8 +153,11 @@ exports[`ProductNoticesModal Should match snapshot for system admin notice 1`] =
for sysadmin
</span>
}
modalLocation="center"
onExited={[Function]}
show={true}
showCloseButton={true}
showHeader={true}
>
<span
className="productNotices__helpText"

Просмотреть файл

@@ -25,6 +25,7 @@ exports[`components/QuickSwitchModal should match snapshot 1`] = `
</h2>
</div>
}
modalLocation="center"
modalSubheaderText={
<div
className="channel-switcher__hint"
@@ -44,6 +45,8 @@ exports[`components/QuickSwitchModal should match snapshot 1`] = `
onExited={[Function]}
onHide={[Function]}
show={true}
showCloseButton={true}
showHeader={true}
>
<div
className="channel-switcher__suggestion-box"

Просмотреть файл

@@ -32,6 +32,11 @@ exports[`components/sidebar/invite_members_button should match snapshot 1`] = `
<ToggleModalButton
ariaLabel="Invite Members"
className="intro-links color--link cursor--pointer"
dialogProps={
Object {
"focusOriginElement": "inviteMembersButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),

Просмотреть файл

@@ -46,6 +46,7 @@ const InviteMembersButton = (props: Props): JSX.Element | null => {
modalId={ModalIdentifiers.INVITATION}
dialogType={InvitationModal}
onClick={handleButtonClick}
dialogProps={{focusOriginElement: 'inviteMembersButton'}}
>
<div
className='SidebarChannelNavigator__inviteMembersLhsButton'

Просмотреть файл

@@ -122,6 +122,7 @@ export default class Sidebar extends React.PureComponent<Props, State> {
dialogType: UserSettingsModal,
dialogProps: {
isContentProductSettings: true,
focusOriginElement: 'sidebar.tsx',
},
});
}
@@ -158,6 +159,7 @@ export default class Sidebar extends React.PureComponent<Props, State> {
this.props.actions.openModal({
modalId: ModalIdentifiers.INVITATION,
dialogType: InvitationModal,
dialogProps: {focusOriginElement: 'browseOrAddChannelMenuButton'},
});
trackEvent('ui', 'ui_channels_dropdown_invite_people');
};

Просмотреть файл

@@ -61,6 +61,7 @@ const SidebarHeader = (props: Props) => {
aria-haspopup='menu'
aria-expanded={menuToggled}
aria-controls='sidebarDropdownMenu'
id='sidebarDropdownMenuButton'
>
<span className='title'>{currentTeam.display_name}</span>
<i

Просмотреть файл

@@ -135,7 +135,7 @@ describe('components/three_days_left_trial_modal/three_days_left_trial_modal', (
</Provider>,
);
wrapper.find(GenericModal).props().onExited();
wrapper.find(GenericModal).props().onExited?.();
expect(mockOnExited).toHaveBeenCalled();
});

Просмотреть файл

@@ -130,7 +130,7 @@ describe('components/trial_benefits_modal/trial_benefits_modal', () => {
</Provider>,
);
wrapper.find(GenericModal).props().onExited();
wrapper.find(GenericModal).props().onExited?.();
expect(mockOnClose).toHaveBeenCalled();
});
@@ -147,7 +147,7 @@ describe('components/trial_benefits_modal/trial_benefits_modal', () => {
</Provider>,
);
wrapper.find(GenericModal).props().onExited();
wrapper.find(GenericModal).props().onExited?.();
expect(mockOnExited).toHaveBeenCalled();
});

Просмотреть файл

@@ -35,7 +35,7 @@ export default function UserAccountProfileMenuItem(props: Props) {
dispatch(openModal({
modalId: ModalIdentifiers.USER_SETTINGS,
dialogType: UserSettingsModal,
dialogProps: {isContentProductSettings: false},
dialogProps: {isContentProductSettings: false, focusOriginElement: 'userAccountMenuButton'},
}));
}

Просмотреть файл

@@ -0,0 +1,89 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
.UserSettingsModal {
.GenericModal__wrapper {
display: flex;
overflow: hidden;
max-height: 90vh;
flex-direction: column;
border: var(--border-default);
border-radius: var(--radius-l);
box-shadow: var(--elevation-6);
.modal-body {
display: flex;
width: 100%;
max-width: 1024px;
min-height: 150px;
flex-direction: column;
padding: 28px 32px;
gap: 24px;
overflow-x: hidden;
overflow-y: auto;
main {
display: flex;
width: 100%;
max-width: 1024px;
min-height: 150px;
flex-direction: column;
gap: 24px;
overflow-x: hidden;
overflow-y: auto;
}
}
}
&__headerWrapper{
display: flex;
justify-content: space-between;
.adminModeBadge {
display: flex;
width: 86px;
min-width: 86px;
height: 22px;
align-items: center;
border-radius: 4px;
margin-top: 10px;
margin-bottom: auto;
margin-left: 24px;
background: var(--error-text);
color: white;
font-size: 12px;
font-weight: 600;
gap: 6px;
span {
margin: 0 auto;
}
}
}
@media screen and (max-width: 768px) {
max-width: 100%;
margin: 0;
.modal-content {
display: flex;
height: 100vh;
max-height: unset;
flex-direction: column;
border-radius: unset;
}
}
@media screen and (max-height: 900px) and (min-width: 768px) {
.modal-content {
max-height: 90vh;
}
}
@media screen and (max-height: 600px) {
.modal-content, .GenericModal__wrapper {
max-height: 85vh !important;
}
}
}

Просмотреть файл

@@ -2,11 +2,11 @@
// See LICENSE.txt for license information.
import React from 'react';
import {Modal} from 'react-bootstrap';
import ReactDOM from 'react-dom';
import {FormattedMessage, injectIntl} from 'react-intl';
import type {IntlShape} from 'react-intl';
import {GenericModal} from '@mattermost/components';
import type {PreferencesType} from '@mattermost/types/preferences';
import type {UserProfile} from '@mattermost/types/users';
@@ -18,6 +18,7 @@ import UserSettings from 'components/user_settings';
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
import SmartLoader from 'components/widgets/smart_loader';
import {focusElement} from 'utils/a11y_utils';
import Constants from 'utils/constants';
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
import {stopTryNotificationRing} from 'utils/notification_sounds';
@@ -26,6 +27,8 @@ import {getDisplayName} from 'utils/utils';
import type {PluginConfiguration} from 'types/plugins/user_settings';
import './user_settings_modal.scss';
export type OwnProps = {
userID?: string;
adminMode?: boolean;
@@ -35,15 +38,16 @@ export type OwnProps = {
}
export type Props = OwnProps & {
onExited: () => void;
intl: IntlShape;
pluginSettings: {[pluginId: string]: PluginConfiguration};
user?: UserProfile;
onExited: () => void;
focusOriginElement?: string;
actions: {
sendVerificationEmail: (email: string) => Promise<ActionResult>;
getUserPreferences: (userID: string) => Promise<unknown>;
getUser: (userID: string) => Promise<unknown>;
};
pluginSettings: {[pluginId: string]: PluginConfiguration};
user?: UserProfile;
}
type State = {
@@ -54,13 +58,13 @@ type State = {
show: boolean;
resendStatus: string;
loading: boolean;
}
};
class UserSettingsModal extends React.PureComponent<Props, State> {
private requireConfirm: boolean;
private customConfirmAction: ((handleConfirm: () => void) => void) | null;
private modalBodyRef: React.RefObject<Modal>;
private afterConfirm: (() => void) | null;
private modalBodyRef: React.RefObject<HTMLDivElement>;
constructor(props: Props) {
super(props);
@@ -107,7 +111,6 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
if (!this.props.userPreferences) {
this.props.actions.getUserPreferences(this.props.userID);
}
if (!this.props.user) {
this.props.actions.getUser(this.props.userID);
}
@@ -124,8 +127,11 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
componentDidUpdate(prevProps: Props, prevState: State) {
if (this.state.active_tab !== prevState.active_tab) {
const el = ReactDOM.findDOMNode(this.modalBodyRef.current) as any;
el.scrollTop = 0;
// Scroll to top if user changes tabs
if (this.modalBodyRef.current) {
const el = this.modalBodyRef.current as HTMLDivElement;
el.scrollTop = 0;
}
}
}
@@ -134,6 +140,7 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
};
handleKeyDown = (e: KeyboardEvent) => {
// Ctrl+Shift+A (or Cmd+Shift+A) to close
if (cmdOrCtrlPressed(e) && e.shiftKey && isKeyPressed(e, Constants.KeyCodes.A)) {
e.preventDefault();
this.handleHide();
@@ -149,10 +156,7 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
// Cancel any ongoing notification sound, if any (from DesktopNotificationSettings)
stopTryNotificationRing();
this.setState({
show: false,
});
this.setState({show: false});
};
// called after the dialog is fully hidden and faded out
@@ -161,6 +165,9 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
active_tab: this.props.isContentProductSettings ? 'notifications' : 'profile',
active_section: '',
});
if (this.props.focusOriginElement) {
focusElement(this.props.focusOriginElement, true);
}
this.props.onExited();
};
@@ -180,7 +187,6 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
showConfirmModal: false,
enforceFocus: true,
});
this.requireConfirm = false;
this.customConfirmAction = null;
@@ -195,7 +201,6 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
showConfirmModal: false,
enforceFocus: true,
});
this.afterConfirm = null;
};
@@ -248,62 +253,62 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
if (!skipConfirm && this.requireConfirm) {
this.showConfirmModal(() => this.updateSection(section, true));
} else {
this.setState({
active_section: section ?? '',
});
this.setState({active_section: section ?? ''});
}
};
getUserSettingsTabs = () => {
const {formatMessage} = this.props.intl;
return [
{
name: 'notifications',
uiName: this.props.intl.formatMessage({id: 'user.settings.modal.notifications', defaultMessage: 'Notifications'}),
uiName: formatMessage({id: 'user.settings.modal.notifications', defaultMessage: 'Notifications'}),
icon: 'icon icon-bell-outline',
iconTitle: this.props.intl.formatMessage({id: 'user.settings.notifications.icon', defaultMessage: 'Notification Settings Icon'}),
iconTitle: formatMessage({id: 'user.settings.notifications.icon', defaultMessage: 'Notification Settings Icon'}),
},
{
name: 'display',
uiName: this.props.intl.formatMessage({id: 'user.settings.modal.display', defaultMessage: 'Display'}),
uiName: formatMessage({id: 'user.settings.modal.display', defaultMessage: 'Display'}),
icon: 'icon icon-eye-outline',
iconTitle: this.props.intl.formatMessage({id: 'user.settings.display.icon', defaultMessage: 'Display Settings Icon'}),
iconTitle: formatMessage({id: 'user.settings.display.icon', defaultMessage: 'Display Settings Icon'}),
},
{
name: 'sidebar',
uiName: this.props.intl.formatMessage({id: 'user.settings.modal.sidebar', defaultMessage: 'Sidebar'}),
uiName: formatMessage({id: 'user.settings.modal.sidebar', defaultMessage: 'Sidebar'}),
icon: 'icon icon-dock-left',
iconTitle: this.props.intl.formatMessage({id: 'user.settings.sidebar.icon', defaultMessage: 'Sidebar Settings Icon'}),
iconTitle: formatMessage({id: 'user.settings.sidebar.icon', defaultMessage: 'Sidebar Settings Icon'}),
},
{
name: 'advanced',
uiName: this.props.intl.formatMessage({id: 'user.settings.modal.advanced', defaultMessage: 'Advanced'}),
uiName: formatMessage({id: 'user.settings.modal.advanced', defaultMessage: 'Advanced'}),
icon: 'icon icon-tune',
iconTitle: this.props.intl.formatMessage({id: 'user.settings.advance.icon', defaultMessage: 'Advanced Settings Icon'}),
iconTitle: formatMessage({id: 'user.settings.advance.icon', defaultMessage: 'Advanced Settings Icon'}),
},
];
};
getProfileSettingsTab = () => {
const {formatMessage} = this.props.intl;
return [
{
name: 'profile',
uiName: this.props.intl.formatMessage({id: 'user.settings.modal.profile', defaultMessage: 'Profile'}),
uiName: formatMessage({id: 'user.settings.modal.profile', defaultMessage: 'Profile'}),
icon: 'icon icon-settings-outline',
iconTitle: this.props.intl.formatMessage({id: 'user.settings.profile.icon', defaultMessage: 'Profile Settings Icon'}),
iconTitle: formatMessage({id: 'user.settings.profile.icon', defaultMessage: 'Profile Settings Icon'}),
},
{
name: 'security',
uiName: this.props.intl.formatMessage({id: 'user.settings.modal.security', defaultMessage: 'Security'}),
uiName: formatMessage({id: 'user.settings.modal.security', defaultMessage: 'Security'}),
icon: 'icon icon-lock-outline',
iconTitle: this.props.intl.formatMessage({id: 'user.settings.security.icon', defaultMessage: 'Security Settings Icon'}),
iconTitle: formatMessage({id: 'user.settings.security.icon', defaultMessage: 'Security Settings Icon'}),
},
];
};
getPluginsSettingsTab = () => {
return Object.values(this.props.pluginSettings).map((v) => {
const className = v.icon ? `icon ${v.icon}` : 'icon icon-power-plug-outline';
const useURL = v.icon && (isValidUrl(v.icon) || v.icon.startsWith('/'));
const className = v.icon ? `icon ${v.icon}` : 'icon icon-power-plug-outline';
return {
name: v.id,
uiName: v.uiName,
@@ -317,60 +322,58 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
const {formatMessage} = this.props.intl;
let modalTitle: string;
if (this.props.adminMode && this.props.user) {
modalTitle = formatMessage({
id: 'userSettings.adminMode.modal_header',
defaultMessage: "{userDisplayName}'s Settings",
}, {
userDisplayName: getDisplayName(this.props.user),
});
modalTitle = formatMessage(
{id: 'userSettings.adminMode.modal_header', defaultMessage: "{userDisplayName}'s Settings"},
{userDisplayName: getDisplayName(this.props.user)},
);
} else if (this.props.isContentProductSettings) {
modalTitle = formatMessage({id: 'global_header.productSettings', defaultMessage: 'Settings'});
} else {
modalTitle = this.props.isContentProductSettings ? formatMessage({
id: 'global_header.productSettings',
defaultMessage: 'Settings',
}) : formatMessage({
id: 'user.settings.modal.title',
defaultMessage: 'Profile',
});
modalTitle = formatMessage({id: 'user.settings.modal.title', defaultMessage: 'Profile'});
}
const headerTitle = (
<div
className='UserSettingsModal__headerWrapper'
id='accountSettingsHeader'
>
<span
id='accountSettingsModalLabel'
className='modal-header__title'
>
{modalTitle}
</span>
{this.props.adminMode && (
<div className='adminModeBadge'>
<FormattedMessage
id='userSettings.adminMode.admin_mode_badge'
defaultMessage='Admin Mode'
/>
</div>
)}
</div>
);
return (
<Modal
<GenericModal
id='accountSettingsModal'
dialogClassName='a11y__modal settings-modal'
className='a11y__modal settings-modal UserSettingsModal'
show={this.state.show}
onHide={this.handleHide}
onExited={this.handleHidden}
ariaLabel={modalTitle}
compassDesign={true}
enforceFocus={this.state.enforceFocus}
aria-label={modalTitle}
role='none'
bodyPadding={false}
modalHeaderText={headerTitle}
modalLocation='top'
>
<Modal.Header
id='accountSettingsHeader'
closeButton={true}
<div
ref={this.modalBodyRef}
>
<Modal.Title
componentClass='h2'
id='accountSettingsModalLabel'
className='modal-header__title'
>
{modalTitle}
</Modal.Title>
{
this.props.adminMode &&
<div className='adminModeBadge'>
<FormattedMessage
id='userSettings.adminMode.admin_mode_badge'
defaultMessage='Admin Mode'
/>
</div>
}
</Modal.Header>
<Modal.Body ref={this.modalBodyRef}>
{
this.props.adminMode &&
{/* Admin Mode: wait for user + preferences */}
{this.props.adminMode &&
<SmartLoader
loading={this.props.adminMode && (!this.props.userPreferences || !this.props.user)}
className='loadingIndicator'
@@ -380,40 +383,41 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
</SmartLoader>
}
{
!this.state.loading && this.props.user &&
<div className='settings-table'>
<div className='settings-links'>
<SettingsSidebar
tabs={this.props.isContentProductSettings ? this.getUserSettingsTabs() : this.getProfileSettingsTab()}
pluginTabs={this.props.isContentProductSettings ? this.getPluginsSettingsTab() : []}
activeTab={this.state.active_tab}
updateTab={this.updateTab}
/>
{/* Show content only when not loading */}
{!this.state.loading && this.props.user &&
<>
<div className='settings-table'>
<div className='settings-links'>
<SettingsSidebar
tabs={this.props.isContentProductSettings ? this.getUserSettingsTabs() : this.getProfileSettingsTab()}
pluginTabs={this.props.isContentProductSettings ? this.getPluginsSettingsTab() : []}
activeTab={this.state.active_tab}
updateTab={this.updateTab}
/>
</div>
<div className='settings-content minimize-settings'>
<UserSettings
activeTab={this.state.active_tab}
activeSection={this.state.active_section}
updateSection={this.updateSection}
updateTab={this.updateTab}
closeModal={this.closeModal}
collapseModal={this.collapseModal}
setRequireConfirm={(requireConfirm?: boolean, customConfirmAction?: () => () => void) => {
this.requireConfirm = requireConfirm || false;
this.customConfirmAction = customConfirmAction || null;
}}
pluginSettings={this.props.pluginSettings}
user={this.props.user}
adminMode={this.props.adminMode}
userPreferences={this.props.userPreferences}
/>
</div>
</div>
<div className='settings-content minimize-settings'>
<UserSettings
activeTab={this.state.active_tab}
activeSection={this.state.active_section}
updateSection={this.updateSection}
updateTab={this.updateTab}
closeModal={this.closeModal}
collapseModal={this.collapseModal}
setRequireConfirm={
(requireConfirm?: boolean, customConfirmAction?: () => () => void) => {
this.requireConfirm = requireConfirm!;
this.customConfirmAction = customConfirmAction!;
}
}
pluginSettings={this.props.pluginSettings}
user={this.props.user}
adminMode={this.props.adminMode}
userPreferences={this.props.userPreferences}
/>
</div>
</div>
</>
}
</Modal.Body>
</div>
<ConfirmModal
title={formatMessage({id: 'user.settings.modal.confirmTitle', defaultMessage: 'Discard Changes?'})}
message={formatMessage({
@@ -428,7 +432,7 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
onConfirm={this.handleConfirm}
onCancel={this.handleCancelConfirmation}
/>
</Modal>
</GenericModal>
);
}
}

Просмотреть файл

@@ -24,17 +24,15 @@ function ModalHeader({id, title, subtitle, handleClose}: Props) {
</h1>
<div className='mm-modal-header__vertical-divider'/>
<p className='mm-modal-header__subtitle'>{subtitle}</p>
<div
className='mm-modal-header__ctr'
onClick={handleClose}
>
{handleClose && <div className='mm-modal-header__ctr'>
<button
className='btn btn-icon'
onClick={handleClose}
aria-label={intl.formatMessage({id: 'modal.header_close', defaultMessage: 'Close'})}
>
<i className='icon icon-close'/>
</button>
</div>
</div>}
</header>
);
}

Просмотреть файл

@@ -67,6 +67,7 @@ exports[`plugins/MainMenuActions should match snapshot in mobile view with some
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "userAccountMenuButton2",
"isContentProductSettings": false,
}
}
@@ -91,6 +92,7 @@ exports[`plugins/MainMenuActions should match snapshot in mobile view with some
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "userAccountMenuButton3",
"isContentProductSettings": true,
}
}
@@ -152,6 +154,11 @@ exports[`plugins/MainMenuActions should match snapshot in mobile view with some
teamId="someteamid"
>
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "sidebarDropdownMenuButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),
@@ -479,6 +486,11 @@ exports[`plugins/MainMenuActions should match snapshot in web view 1`] = `
teamId="someteamid"
>
<MenuItemToggleModalRedux
dialogProps={
Object {
"focusOriginElement": "sidebarDropdownMenuButton",
}
}
dialogType={
Object {
"$$typeof": Symbol(react.memo),

Просмотреть файл

@@ -86,24 +86,6 @@
.modal-header {
border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
.adminModeBadge {
display: flex;
width: 86px;
min-width: 86px;
height: 22px;
align-items: center;
padding: 2px var(--spacing-xxxs, 6px);
border-radius: 4px;
margin-top: 10px;
margin-bottom: auto;
margin-left: 24px;
background: var(--error-text);
color: white;
font-size: 12px;
font-weight: 600;
gap: 6px;
}
}
li {

Просмотреть файл

@@ -22,6 +22,9 @@ export function focusElement(
keyboardOnly = true,
resetOriginElement = false,
) {
if (!elementOrId) {
return;
}
let target: HTMLElement | null = null;
if (typeof elementOrId === 'string') {