[MM-61966] "Browse or Create channel" menu on the right of team drop-down should use a11y supported menu (#29739)

Этот коммит содержится в:
M-ZubairAhmed
2025-01-22 10:40:43 +05:30
коммит произвёл GitHub
родитель c205398040
Коммит 1373c05931
55 изменённых файлов: 449 добавлений и 546 удалений

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

@@ -94,11 +94,8 @@ describe('Leave an archived channel', () => {
// # Archive the channel
cy.uiArchiveChannel();
// # Click on add channel button
cy.get('#SidebarContainer .AddChannelDropdown_dropdownButton').click();
// # Click on browse channels
cy.get('#showMoreChannels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
// # More channels modal opens
cy.get('#browseChannelsModal').should('be.visible');
@@ -141,11 +138,8 @@ describe('Leave an archived channel', () => {
cy.uiArchiveChannel();
});
// # Click on add channel button
cy.get('#SidebarContainer .AddChannelDropdown_dropdownButton').click();
// # Click on browse channels
cy.get('#showMoreChannels').click();
// # Click on browse channels from menu
cy.uiBrowseOrCreateChannel('Browse channels');
// # More channels modal opens
cy.get('#browseChannelsModal').should('be.visible').then(() => {
@@ -194,11 +188,8 @@ describe('Leave an archived channel', () => {
cy.url().should('include', `/${testTeam.name}/channels/${archivedPublicChannel1.name}`);
});
// # Click on add channel button
cy.get('#SidebarContainer .AddChannelDropdown_dropdownButton').click();
// # Click on browse channels
cy.get('#showMoreChannels').click();
// # Click on browse channels from menu
cy.uiBrowseOrCreateChannel('Browse channels');
// # More channels modal opens
cy.get('#browseChannelsModal').should('be.visible').then(() => {
@@ -248,11 +239,8 @@ describe('Leave an archived channel', () => {
cy.get('#confirmModal').should('be.visible');
cy.get('#confirmModalButton').click();
// # Click on add channel button
cy.get('#SidebarContainer .AddChannelDropdown_dropdownButton').click();
// # Click on browse channels
cy.get('#showMoreChannels').click();
// # Click on browse channels from menu
cy.uiBrowseOrCreateChannel('Browse channels');
// # More channels modal opens
cy.get('#browseChannelsModal').should('be.visible').then(() => {
@@ -283,11 +271,8 @@ describe('Leave an archived channel', () => {
cy.uiArchiveChannel();
});
// # Click on add channel button
cy.get('#SidebarContainer .AddChannelDropdown_dropdownButton').click();
// # Click on browse channels
cy.get('#showMoreChannels').click();
// # Click on browse channels from menu
cy.uiBrowseOrCreateChannel('Browse channels');
// # More channels modal opens and lands on all channels
cy.get('#browseChannelsModal').should('be.visible').then(() => {
@@ -318,11 +303,8 @@ describe('Leave an archived channel', () => {
// # Archive the channel
cy.uiArchiveChannel();
// # Click on add channel button
cy.get('#SidebarContainer .AddChannelDropdown_dropdownButton').click();
// # Click on browse channels
cy.get('#showMoreChannels').click();
// # Click on browse channels from menu
cy.uiBrowseOrCreateChannel('Browse channels');
// # Modal should not contain the created channel
cy.findByText(channelType.all).should('be.visible').click();

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

@@ -70,7 +70,7 @@ describe('Channels', () => {
cy.visit(`/${testTeam.name}/channels/town-square`);
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
cy.get('#browseChannelsModal').should('be.visible').within(() => {
// * Dropdown should be visible, defaulting to "All Channels"
@@ -113,7 +113,7 @@ describe('Channels', () => {
});
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
cy.get('#browseChannelsModal').should('be.visible');
@@ -167,7 +167,7 @@ describe('Channels', () => {
});
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
cy.get('#browseChannelsModal').should('be.visible').within(() => {
// * Verify that channel has zero members
@@ -233,7 +233,7 @@ describe('Channels', () => {
});
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
// * Dropdown should be visible, defaulting to "All channels"
cy.get('#menuWrapper').should('be.visible').within((el) => {
@@ -288,7 +288,7 @@ function verifyBrowseChannelsModalWithArchivedSelection(isEnabled, testUser, tes
function verifyBrowseChannelsModal(isEnabled) {
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
// * Verify that the browse channels modal is open and with or without option to view archived channels
cy.get('#browseChannelsModal').should('be.visible').within(() => {

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

@@ -66,7 +66,7 @@ describe('browse public channels', () => {
cy.visit(`/${testTeam.name}/channels/town-square`);
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
// * Assert that the browse channel modal is visible
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').then(() => {
@@ -108,7 +108,7 @@ describe('browse public channels', () => {
cy.visit(`/${testTeam.name}/channels/town-square`);
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
// * Assert the moreChannelsModel is visible
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').then(() => {

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

@@ -53,7 +53,7 @@ describe('Channel routing', () => {
it('MM-T884_2 Creating new channel validates against two user IDs being used as channel name', () => {
// # click on create public channel
cy.uiBrowseOrCreateChannel('Create new channel').click();
cy.uiBrowseOrCreateChannel('Create new channel');
// * Verify that the new channel modal is visible
cy.get('#new-channel-modal').should('be.visible').within(() => {
@@ -73,7 +73,7 @@ describe('Channel routing', () => {
it('MM-T884_3 Creating a new channel validates against gm-like names being used as channel name', () => {
// # click on create public channel
cy.uiBrowseOrCreateChannel('Create new channel').click();
cy.uiBrowseOrCreateChannel('Create new channel');
// * Verify that the new channel modal is visible
cy.findByRole('dialog', {name: 'Create a new channel'}).within(() => {

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

@@ -139,11 +139,8 @@ function createCategoryFromSidebarMenu() {
// # Start with a new category
const categoryName = `category-${getRandomId()}`;
// # Click on the sidebar menu dropdown
cy.uiGetLHSAddChannelButton().click();
// # Click on create category link
cy.findByText('Create new category').should('be.visible').click();
// # Click on the sidebar menu dropdown and select Create Category
cy.uiBrowseOrCreateChannel('Create new category');
// # Verify that Create Category modal has shown up.
// # Wait for a while until the modal has fully loaded, especially during first-time access.

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

@@ -22,11 +22,8 @@ describe('Category sorting', () => {
});
it('MM-T3916 Create Category character limit', () => {
// # Click on the sidebar menu dropdown
cy.uiGetLHSAddChannelButton().click();
// # Click on create category link
cy.findByText('Create new category').should('be.visible').click();
// # Click on the sidebar menu dropdown and select Create Category
cy.uiBrowseOrCreateChannel('Create new category');
// # Add a name 26 characters in length e.g `abcdefghijklmnopqrstuvwxyz`
cy.get('#editCategoryModal').should('be.visible').wait(TIMEOUTS.HALF_SEC).within(() => {

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

@@ -89,11 +89,8 @@ function createCategoryFromSidebarMenu() {
// # Start with a new category
const categoryName = `category-${getRandomId()}`;
// # Click on the sidebar menu dropdown
cy.uiGetLHSAddChannelButton().click();
// # Click on create category link
cy.findByText('Create new category').should('be.visible').click();
// # Click on the sidebar menu dropdown and select Create Category
cy.uiBrowseOrCreateChannel('Create new category');
// # Verify that Create Category modal has shown up.
// # Wait for a while until the modal has fully loaded, especially during first-time access.

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

@@ -61,11 +61,8 @@ describe('Channel sidebar', () => {
cy.get('#channelHeaderTitle').should('contain', 'Town Square');
cy.url().should('include', `/${teamName}/channels/town-square`);
// # Click the New Channel Dropdown button
cy.uiGetLHSAddChannelButton().should('be.visible').click();
// # Click the Browse channels dropdown item
cy.get('.AddChannelDropdown .MenuItem:contains(Browse channels) button').should('be.visible').click();
// # Click on the sidebar menu dropdown and select browse channels
cy.uiBrowseOrCreateChannel('Browse channels');
// * Verify that the more channels modal is visible
cy.get('#browseChannelsModal').should('be.visible');

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

@@ -104,7 +104,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
cy.reload();
// * Verify the aria-label in more public channels button
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
// * Verify the accessibility support in More Channels Dialog
cy.findByRole('dialog', {name: 'Browse Channels'}).within(() => {
@@ -140,7 +140,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
it.skip('MM-T1468 Accessibility Support in Add people to Channel Dialog screen', () => {
// # Add atleast 5 users
for (let i = 0; i < 5; i++) {
cy.apiCreateUser().then(({user}) => { // eslint-disable-line
cy.apiCreateUser().then(({user}) => { // eslint-disable-line
cy.apiAddUserToTeam(testTeam.id, user.id);
});
}

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

@@ -234,7 +234,7 @@ context('ldap', () => {
// # Go to team page to look for this channel in public channel directory
cy.visit(`/${testTeam.name}`);
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
// * Search private channel name and make sure it is still visible
cy.get('#searchChannelsTextbox').type(testChannel.display_name);
@@ -456,7 +456,7 @@ context('ldap', () => {
cy.visit(`/${testTeam.name}/channels/off-topic`);
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
// * Search public channel and ensure it appears in the list
cy.get('#searchChannelsTextbox').type(publicChannel.display_name);
@@ -473,7 +473,7 @@ context('ldap', () => {
cy.visit(`/${testTeam.name}/channels/off-topic`);
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.uiBrowseOrCreateChannel('Browse channels');
// * Search private channel name and make sure it isn't there in public channel directory
cy.get('#searchChannelsTextbox').type(publicChannel.display_name);

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

@@ -148,7 +148,7 @@ describe('Team Permissions', () => {
cy.visit(`/${testTeam.name}/channels/town-square`);
// # Click on create new channel at LHS
cy.uiBrowseOrCreateChannel('Create new channel').click();
cy.uiBrowseOrCreateChannel('Create new channel');
// * Verify that the create private channel is disabled
cy.findByRole('dialog', {name: 'Create a new channel'}).find('#public-private-selector-button-P').should('have.class', 'disabled');

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

@@ -105,7 +105,7 @@ function verifyCreatePublicChannel(teamName, testUsers) {
cy.visit(`/${teamName}/channels/town-square`);
// # Click on create new channel at LHS
cy.uiBrowseOrCreateChannel('Create new channel').click();
cy.uiBrowseOrCreateChannel('Create new channel');
cy.findByRole('dialog', {name: 'Create a new channel'}).within(() => {
// * Verify if creating a public channel is disabled or not

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

@@ -77,7 +77,7 @@ describe('Channel', () => {
*/
function verifyExistingChannelError(newChannelName, makePrivate = false) {
// Click on '+' button for Public or Private Channel
cy.uiBrowseOrCreateChannel('Create new channel').click();
cy.uiBrowseOrCreateChannel('Create new channel');
if (makePrivate) {
cy.get('#public-private-selector-button-P').click();

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

@@ -11,7 +11,7 @@ Cypress.Commands.add('uiCreateChannel', ({
name = '',
createBoard = '',
}) => {
cy.uiBrowseOrCreateChannel('Create new channel').click();
cy.uiBrowseOrCreateChannel('Create new channel');
cy.get('#new-channel-modal').should('be.visible');
if (isPrivate) {

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

@@ -13,11 +13,8 @@ import {getRandomId} from '../../utils';
* cy.uiCreateSidebarCategory();
*/
function uiCreateSidebarCategory(categoryName: string = `category-${getRandomId()}`): ChainableT<any> {
// # Click the New Category/Channel Dropdown button
cy.uiGetLHSAddChannelButton().click();
// # Click the Create new category dropdown item
cy.get('.AddChannelDropdown').should('be.visible').contains('.MenuItem', 'Create new category').click();
// # Click on the sidebar menu dropdown and select Create Category
cy.uiBrowseOrCreateChannel('Create new category');
cy.findByRole('dialog', {name: 'Rename Category'}).should('be.visible').within(() => {
// # Fill in the category name and click 'Create'

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

@@ -31,8 +31,7 @@ Cypress.Commands.add('uiOpenTeamMenu', (item = '') => {
});
Cypress.Commands.add('uiGetLHSAddChannelButton', () => {
return cy.uiGetLHS().
find('.AddChannelDropdown_dropdownButton');
return cy.uiGetLHS().findByRole('button', {name: 'Browse or create channels'});
});
Cypress.Commands.add('uiGetLHSTeamMenu', () => {
@@ -88,15 +87,16 @@ Cypress.Commands.add('uiGetLhsSection', (section) => {
parent();
});
Cypress.Commands.add('uiBrowseOrCreateChannel', (item) => {
cy.get('.AddChannelDropdown_dropdownButton').
should('be.visible').
click();
cy.get('.dropdown-menu').should('be.visible');
Cypress.Commands.add('uiBrowseOrCreateChannel', (menuitem) => {
cy.uiGetLHSAddChannelButton().should('be.visible').click();
if (item) {
cy.findByRole('menuitem', {name: item});
}
cy.get('#browserOrAddChannelMenu').
should('exist').and('be.visible').
within(() => {
if (menuitem) {
cy.findByText(menuitem).should('exist').click();
}
});
});
Cypress.Commands.add('uiAddDirectMessage', () => {

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

@@ -3,13 +3,6 @@
import {ActionTypes} from 'utils/constants';
export function setAddChannelDropdown(open: boolean) {
return {
type: ActionTypes.ADD_CHANNEL_DROPDOWN_TOGGLE,
open,
};
}
export function setAddChannelCtaDropdown(open: boolean) {
return {
type: ActionTypes.ADD_CHANNEL_CTA_DROPDOWN_TOGGLE,

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

@@ -8,3 +8,4 @@ export {SubMenu} from './sub_menu';
export {MenuItem as Item} from './menu_item';
export type {FirstMenuItemProps} from './menu_item';
export {MenuItemSeparator as Separator} from './menu_item_separator';
export {openMenu, dismissMenu} from './menu_utils';

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

@@ -34,6 +34,8 @@ import {MenuContext, useMenuContextValue} from './menu_context';
import './menu.scss';
export const ELEMENT_ID_FOR_MENU_BACKDROP = 'backdropForMenuComponent';
const MENU_OPEN_ANIMATION_DURATION = 150;
const MENU_CLOSE_ANIMATION_DURATION = 100;
@@ -56,8 +58,13 @@ type MenuButtonTooltipProps = {
}
type MenuProps = {
/**
* ID is mandatory as it is used in mobileWebview to open modal equivalent to menu
*/
id: string;
'aria-label'?: string;
'aria-labelledby'?: string;
/**
* @warning Make the styling of your components such a way that they don't need this handler
@@ -244,9 +251,10 @@ export function Menu(props: Props) {
disableAutoFocusItem={disableAutoFocusItem} // This is not anti-pattern, see handleMenuButtonMouseDown
MenuListProps={{
id: props.menu.id,
'aria-label': props.menu?.['aria-label'] ?? '',
'aria-label': props.menu?.['aria-label'],
'aria-labelledby': props.menu?.['aria-labelledby'],
style: {
width: props.menu.width || 'inherit',
width: props.menu?.width,
},
}}
TransitionProps={{
@@ -257,6 +265,11 @@ export function Menu(props: Props) {
exit: MENU_CLOSE_ANIMATION_DURATION,
},
}}
slotProps={{
backdrop: {
id: ELEMENT_ID_FOR_MENU_BACKDROP,
},
}}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error This exists in source code of mui, but its types are missing
onTransitionExited={providerValue.handleClosed}

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

@@ -0,0 +1,29 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ELEMENT_ID_FOR_MENU_BACKDROP} from './menu';
/**
* Since the menu component requires actual interaction with the button
* of the menu for opening the menus, we trigger it from here by clicking on the menu button
*/
export function openMenu(buttonId: string) {
const menuButton = document.getElementById(buttonId);
if (!menuButton) {
return;
}
menuButton.click();
}
/**
* Dismisses the menu by clicking on the backdrop
*/
export function dismissMenu() {
const menuOverlay = document.getElementById(ELEMENT_ID_FOR_MENU_BACKDROP);
if (!menuOverlay) {
return;
}
menuOverlay.click();
}

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

@@ -13,7 +13,7 @@ import {isShowOnboardingCompleteProfileTour} from 'selectors/views/onboarding_ta
import {OnboardingTasksName, TaskNameMapToSteps} from './constants';
import {useHandleOnBoardingTaskData} from './onboarding_tasks_manager';
const translate = {x: 0, y: -2};
const translate = {x: 1, y: -9};
export const CompleteYourProfileTour = () => {
const dispatch = useDispatch();
@@ -43,7 +43,7 @@ export const CompleteYourProfileTour = () => {
</p>
);
const overlayPunchOut = useMeasurePunchouts(['userAccountMenu'], [], {y: -6, height: 6, x: 0, width: 0});
const overlayPunchOut = useMeasurePunchouts(['userAccountMenu'], [], {x: -2.5, y: -2.5, width: 5, height: 5});
const onDismiss = (e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
@@ -58,13 +58,12 @@ export const CompleteYourProfileTour = () => {
overlayPunchOut={overlayPunchOut}
step={steps.STARTED}
placement='left-start'
pulsatingDotPlacement='left'
pulsatingDotPlacement='left-end'
pulsatingDotTranslate={translate}
handleDismiss={onDismiss}
singleTip={true}
showOptOut={false}
interactivePunchOut={true}
zIndex={1058}
/>
);
};

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

@@ -29,6 +29,7 @@ import Phone from 'components/common/svg_images_components/phone_svg';
import Security from 'components/common/svg_images_components/security_svg';
import Sunglasses from 'components/common/svg_images_components/sunglasses_svg';
import LearnMoreTrialModal from 'components/learn_more_trial_modal/learn_more_trial_modal';
import {openMenu} from 'components/menu';
import {
AutoTourStatus,
FINISHED,
@@ -37,9 +38,9 @@ import {
TTNameMapToATStatusKey,
TutorialTourName,
} from 'components/tours';
import {ELEMENT_ID_FOR_USER_ACCOUNT_MENU_BUTTON} from 'components/user_account_menu/user_account_menu';
import {ModalIdentifiers, TELEMETRY_CATEGORIES} from 'utils/constants';
import {clickOnMenuButton, MenuButtonIds} from 'utils/keyboard';
import type {GlobalState} from 'types/store';
@@ -239,7 +240,7 @@ export const useHandleOnBoardingTaskTrigger = () => {
break;
}
case OnboardingTasksName.COMPLETE_YOUR_PROFILE: {
clickOnMenuButton(MenuButtonIds.userAccountMenu);
openMenu(ELEMENT_ID_FOR_USER_ACCOUNT_MENU_BUTTON);
dispatch(setShowOnboardingCompleteProfileTour(true));
handleSaveData(taskName, TaskNameMapToSteps[taskName].STARTED, true);
if (inAdminConsole) {

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

@@ -8,10 +8,10 @@ import {useSelector} from 'react-redux';
import {makeGetCustomStatus, showPostHeaderUpdateStatusButton, isCustomStatusEnabled} from 'selectors/views/custom_status';
import CustomStatusEmoji from 'components/custom_status/custom_status_emoji';
import {openMenu} from 'components/menu';
import {ELEMENT_ID_FOR_USER_ACCOUNT_MENU_BUTTON} from 'components/user_account_menu/user_account_menu';
import EmojiIcon from 'components/widgets/icons/emoji_icon';
import {clickOnMenuButton, MenuButtonIds} from 'utils/keyboard';
import type {GlobalState} from 'types/store';
interface ComponentProps {
@@ -51,7 +51,7 @@ const PostHeaderCustomStatus = (props: ComponentProps) => {
const updateStatus = (event: React.MouseEvent) => {
event.preventDefault();
clickOnMenuButton(MenuButtonIds.userAccountMenu);
openMenu(ELEMENT_ID_FOR_USER_ACCOUNT_MENU_BUTTON);
};
return (

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

@@ -18,7 +18,6 @@ exports[`components/sidebar should match snapshot 1`] = `
showMoreChannelsModal={[Function]}
showNewChannelModal={[Function]}
unreadFilterEnabled={false}
userGroupsEnabled={false}
/>
<div
aria-label="channel navigator region"
@@ -61,7 +60,6 @@ exports[`components/sidebar should match snapshot when direct channels modal is
showMoreChannelsModal={[Function]}
showNewChannelModal={[Function]}
unreadFilterEnabled={false}
userGroupsEnabled={false}
/>
<div
aria-label="channel navigator region"
@@ -108,7 +106,6 @@ exports[`components/sidebar should match snapshot when more channels modal is op
showMoreChannelsModal={[Function]}
showNewChannelModal={[Function]}
unreadFilterEnabled={false}
userGroupsEnabled={false}
/>
<div
aria-label="channel navigator region"

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

@@ -1,177 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {useIntl} from 'react-intl';
import {trackEvent} from 'actions/telemetry_actions';
import {CreateAndJoinChannelsTour, InvitePeopleTour} from 'components/tours/onboarding_tour';
import Menu from 'components/widgets/menu/menu';
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
import WithTooltip from 'components/with_tooltip';
type Props = {
canCreateChannel: boolean;
canJoinPublicChannel: boolean;
userGroupsEnabled: boolean;
showMoreChannelsModal: () => void;
showCreateUserGroupModal: () => void;
invitePeopleModal: () => void;
showNewChannelModal: () => void;
showCreateCategoryModal: () => void;
handleOpenDirectMessagesModal: (e: Event) => void;
unreadFilterEnabled: boolean;
showCreateTutorialTip: boolean;
showInviteTutorialTip: boolean;
isAddChannelOpen: boolean;
openAddChannelOpen: (open: boolean) => void;
canCreateCustomGroups: boolean;
};
const AddChannelDropdown = ({
canCreateChannel,
canJoinPublicChannel,
showMoreChannelsModal,
showCreateUserGroupModal,
invitePeopleModal,
showNewChannelModal,
showCreateCategoryModal,
handleOpenDirectMessagesModal,
unreadFilterEnabled,
showCreateTutorialTip,
showInviteTutorialTip,
isAddChannelOpen,
openAddChannelOpen,
canCreateCustomGroups,
}: Props) => {
const intl = useIntl();
const renderDropdownItems = () => {
const invitePeople = (
<Menu.Group>
<Menu.ItemAction
id='invitePeople'
onClick={invitePeopleModal}
icon={<i className='icon-account-plus-outline'/>}
text={intl.formatMessage({id: 'sidebar_left.add_channel_dropdown.invitePeople', defaultMessage: 'Invite people'})}
extraText={intl.formatMessage({id: 'sidebar_left.add_channel_dropdown.invitePeopleExtraText', defaultMessage: 'Add people to the team'})}
/>
{showInviteTutorialTip && <InvitePeopleTour/>}
</Menu.Group>
);
let joinPublicChannel;
if (canJoinPublicChannel) {
joinPublicChannel = (
<Menu.ItemAction
id='showMoreChannels'
onClick={showMoreChannelsModal}
icon={<i className='icon-globe'/>}
text={intl.formatMessage({id: 'sidebar_left.add_channel_dropdown.browseChannels', defaultMessage: 'Browse channels'})}
/>
);
}
let createChannel;
if (canCreateChannel) {
createChannel = (
<Menu.ItemAction
id='showNewChannel'
onClick={showNewChannelModal}
icon={<i className='icon-plus'/>}
text={intl.formatMessage({id: 'sidebar_left.add_channel_dropdown.createNewChannel', defaultMessage: 'Create new channel'})}
/>
);
}
let createCategory;
if (!unreadFilterEnabled) {
createCategory = (
<Menu.Group>
<Menu.ItemAction
id='createCategory'
onClick={showCreateCategoryModal}
icon={<i className='icon-folder-plus-outline'/>}
text={intl.formatMessage({id: 'sidebar_left.add_channel_dropdown.createCategory', defaultMessage: 'Create new category'})}
/>
</Menu.Group>);
}
const createDirectMessage = (
<Menu.ItemAction
id={'openDirectMessageMenuItem'}
onClick={handleOpenDirectMessagesModal}
icon={<i className='icon-account-outline'/>}
text={intl.formatMessage({id: 'sidebar.openDirectMessage', defaultMessage: 'Open a direct message'})}
/>
);
let createUserGroup;
if (canCreateCustomGroups) {
createUserGroup = (
<Menu.ItemAction
id={'createUserGroup'}
onClick={showCreateUserGroupModal}
icon={<i className='icon-account-multiple-plus-outline'/>}
text={intl.formatMessage({id: 'sidebar.createUserGroup', defaultMessage: 'Create New User Group'})}
/>
);
}
return (
<>
<Menu.Group>
{createChannel}
{joinPublicChannel}
{createDirectMessage}
{showCreateTutorialTip && <CreateAndJoinChannelsTour/>}
{createUserGroup}
</Menu.Group>
{createCategory}
{invitePeople}
</>
);
};
const trackOpen = (opened: boolean) => {
openAddChannelOpen(opened);
if (opened) {
trackEvent('ui', 'ui_add_channel_dropdown_opened');
}
};
if (!(canCreateChannel || canJoinPublicChannel)) {
return null;
}
return (
<MenuWrapper
className='AddChannelDropdown'
onToggle={trackOpen}
open={isAddChannelOpen}
>
<WithTooltip
title={intl.formatMessage({
id: 'sidebar_left.add_channel_dropdown.browseOrCreateChannels',
defaultMessage: 'Browse or create channels',
})}
>
<button
className={'AddChannelDropdown_dropdownButton'}
aria-label={intl.formatMessage({id: 'sidebar_left.add_channel_dropdown.dropdownAriaLabel', defaultMessage: 'Add Channel Dropdown'})}
>
<i className='icon-plus'/>
</button>
</WithTooltip>
<Menu
id='AddChannelDropdown'
ariaLabel={intl.formatMessage({id: 'sidebar_left.add_channel_dropdown.dropdownAriaLabel', defaultMessage: 'Add Channel Dropdown'})}
>
{renderDropdownItems()}
</Menu>
</MenuWrapper>
);
};
export default AddChannelDropdown;

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

@@ -4,11 +4,11 @@
import {shallow} from 'enzyme';
import React from 'react';
import BrowserOrAddChannelMenu from 'components/sidebar/sidebar_header/sidebar_browse_or_add_channel_menu';
import ChannelNavigator from './channel_navigator';
import type {Props} from './channel_navigator';
import AddChannelDropdown from '../add_channel_dropdown';
let props: Props;
describe('Components/ChannelNavigator', () => {
@@ -23,8 +23,8 @@ describe('Components/ChannelNavigator', () => {
};
});
it('should not show AddChannelDropdown', () => {
it('should not show BrowserOrAddChannelMenu', () => {
const wrapper = shallow(<ChannelNavigator {...props}/>);
expect(wrapper.find(AddChannelDropdown).length).toBe(0);
expect(wrapper.find(BrowserOrAddChannelMenu).length).toBe(0);
});
});

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

@@ -29,7 +29,6 @@ import Sidebar from './sidebar';
function mapStateToProps(state: GlobalState) {
const currentTeam = getCurrentTeam(state);
const unreadFilterEnabled = isUnreadFilterEnabled(state);
const userGroupsEnabled = isCustomGroupsEnabled(state);
let canCreatePublicChannel = false;
let canCreatePrivateChannel = false;
@@ -41,7 +40,7 @@ function mapStateToProps(state: GlobalState) {
canJoinPublicChannel = haveICurrentChannelPermission(state, Permissions.JOIN_PUBLIC_CHANNELS);
}
const canCreateCustomGroups = haveISystemPermission(state, {permission: Permissions.CREATE_CUSTOM_GROUP}) && isCustomGroupsEnabled(state);
const canCreateCustomGroups = isCustomGroupsEnabled(state) && haveISystemPermission(state, {permission: Permissions.CREATE_CUSTOM_GROUP});
return {
teamId: currentTeam ? currentTeam.id : '',
@@ -52,7 +51,6 @@ function mapStateToProps(state: GlobalState) {
unreadFilterEnabled,
isMobileView: getIsMobileView(state),
isKeyBoardShortcutModalOpen: isModalOpen(state, ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL),
userGroupsEnabled,
canCreateCustomGroups,
rhsState: getRhsState(state),
rhsOpen: getIsRhsOpen(state),

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

@@ -48,7 +48,6 @@ type Props = {
unreadFilterEnabled: boolean;
isMobileView: boolean;
isKeyBoardShortcutModalOpen: boolean;
userGroupsEnabled: boolean;
canCreateCustomGroups: boolean;
rhsState?: RhsState;
rhsOpen?: boolean;
@@ -180,8 +179,8 @@ export default class Sidebar extends React.PureComponent<Props, State> {
trackEvent('ui', 'ui_channels_create_user_group');
};
handleOpenMoreDirectChannelsModal = (e: Event) => {
e.preventDefault();
handleOpenMoreDirectChannelsModal = (e?: Event) => {
e?.preventDefault();
if (this.state.showDirectChannelsModal) {
this.hideMoreDirectChannelsModal();
} else {
@@ -248,7 +247,6 @@ export default class Sidebar extends React.PureComponent<Props, State> {
canJoinPublicChannel={this.props.canJoinPublicChannel}
handleOpenDirectMessagesModal={this.handleOpenMoreDirectChannelsModal}
unreadFilterEnabled={this.props.unreadFilterEnabled}
userGroupsEnabled={this.props.userGroupsEnabled}
canCreateCustomGroups={this.props.canCreateCustomGroups}
/>
)}

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

@@ -2,4 +2,5 @@
// See LICENSE.txt for license information.
import SidebarHeader from './sidebar_header';
export default SidebarHeader;

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

@@ -0,0 +1,176 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {
PlusIcon,
AccountPlusOutlineIcon,
FolderPlusOutlineIcon,
AccountMultiplePlusOutlineIcon,
GlobeIcon,
AccountOutlineIcon,
} from '@mattermost/compass-icons/components';
import * as Menu from 'components/menu';
import {OnboardingTourSteps} from 'components/tours';
import {useShowOnboardingTutorialStep, CreateAndJoinChannelsTour, InvitePeopleTour} from 'components/tours/onboarding_tour';
export const ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU = 'browseOrAddChannelMenuButton';
type Props = {
canCreateChannel: boolean;
onCreateNewChannelClick: () => void;
canJoinPublicChannel: boolean;
onBrowseChannelClick: () => void;
onOpenDirectMessageClick: () => void;
canCreateCustomGroups: boolean;
onCreateNewUserGroupClick: () => void;
unreadFilterEnabled: boolean;
onCreateNewCategoryClick: () => void;
onInvitePeopleClick: () => void;
};
export default function SidebarBrowserOrAddChannelMenu(props: Props) {
const {formatMessage} = useIntl();
const showCreateAndJoinChannelsTutorialTip = useShowOnboardingTutorialStep(OnboardingTourSteps.CREATE_AND_JOIN_CHANNELS);
const showInvitePeopleTutorialTip = useShowOnboardingTutorialStep(OnboardingTourSteps.INVITE_PEOPLE);
let createNewChannelMenuItem: JSX.Element | null = null;
if (props.canCreateChannel) {
createNewChannelMenuItem = (
<Menu.Item
id='createNewChannelMenuItem'
onClick={props.onCreateNewChannelClick}
leadingElement={<PlusIcon size={18}/>}
labels={(
<FormattedMessage
id='sidebarLeft.browserOrCreateChannelMenu.createNewChannelMenuItem.primaryLabel'
defaultMessage='Create new channel'
/>
)}
trailingElements={showCreateAndJoinChannelsTutorialTip && <CreateAndJoinChannelsTour/>}
/>
);
}
let browseChannelsMenuItem: JSX.Element | null = null;
if (props.canJoinPublicChannel) {
browseChannelsMenuItem = (
<Menu.Item
id='browseChannelsMenuItem'
onClick={props.onBrowseChannelClick}
leadingElement={<GlobeIcon size={18}/>}
labels={(
<FormattedMessage
id='sidebarLeft.browserOrCreateChannelMenu.browseChannelsMenuItem.primaryLabel'
defaultMessage='Browse channels'
/>
)}
/>
);
}
const createDirectMessageMenuItem = (
<Menu.Item
id='openDirectMessageMenuItem'
onClick={props.onOpenDirectMessageClick}
leadingElement={<AccountOutlineIcon size={18}/>}
labels={(
<FormattedMessage
id='sidebarLeft.browserOrCreateChannelMenu.openDirectMessageMenuItem.primaryLabel'
defaultMessage='Open a direct message'
/>
)}
/>
);
let createUserGroupMenuItem: JSX.Element | null = null;
if (props.canCreateCustomGroups) {
createUserGroupMenuItem = (
<Menu.Item
id='createUserGroupMenuItem'
onClick={props.onCreateNewUserGroupClick}
leadingElement={<AccountMultiplePlusOutlineIcon size={18}/>}
labels={(
<FormattedMessage
id='sidebarLeft.browserOrCreateChannelMenu.createUserGroupMenuItem.primaryLabel'
defaultMessage='Create new user group'
/>
)}
/>
);
}
let createNewCategoryMenuItem: JSX.Element | null = null;
if (!props.unreadFilterEnabled) {
createNewCategoryMenuItem = (
<Menu.Item
id='createCategoryMenuItem'
onClick={props.onCreateNewCategoryClick}
leadingElement={<FolderPlusOutlineIcon size={18}/>}
labels={(
<FormattedMessage
id='sidebarLeft.browserOrCreateChannelMenu.createCategoryMenuItem.primaryLabel'
defaultMessage='Create new category'
/>
)}
/>
);
}
const invitePeopleMenuItem = (
<Menu.Item
id='invitePeopleMenuItem'
onClick={props.onInvitePeopleClick}
leadingElement={<AccountPlusOutlineIcon size={18}/>}
labels={(
<>
<FormattedMessage
id='sidebarLeft.browserOrCreateChannelMenu.invitePeopleMenuItem.primaryLabel'
defaultMessage='Invite people'
/>
<FormattedMessage
id='sidebarLeft.browserOrCreateChannelMenu.invitePeopleMenuItem.secondaryLabel'
defaultMessage='Add people to the team'
/>
</>
)}
trailingElements={showInvitePeopleTutorialTip && <InvitePeopleTour/>}
/>
);
return (
<Menu.Container
menuButton={{
id: ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU,
'aria-label': formatMessage({
id: 'sidebarLeft.browserOrCreateChannelMenuButton.label',
defaultMessage: 'Browse or create channels',
}),
class: 'btn btn-icon btn-sm btn-tertiary btn-inverted btn-round',
children: <PlusIcon size={18}/>,
}}
menuButtonTooltip={{
text: formatMessage({id: 'sidebarLeft.browserOrCreateChannelMenuButton.label', defaultMessage: 'Browse or create channels'}),
}}
menu={{
id: 'browserOrAddChannelMenu',
'aria-labelledby': ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU,
}}
>
{createNewChannelMenuItem}
{browseChannelsMenuItem}
{createDirectMessageMenuItem}
{createUserGroupMenuItem}
{Boolean(createNewCategoryMenuItem) &&
<Menu.Separator/>
}
{createNewCategoryMenuItem}
<Menu.Separator/>
{invitePeopleMenuItem}
</Menu.Container>
);
}

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

@@ -1,3 +1,13 @@
.sidebarHeaderContainer {
display: flex;
height: 55px;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
padding: 0 16px;
}
.sidebarHeader {
display: flex;
margin: 0;

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

@@ -9,11 +9,10 @@ import IconButton from '@mattermost/compass-components/components/icon-button';
import {CloudProducts} from 'utils/constants';
import {FileSizes} from 'utils/file_utils';
import BrowserOrAddChannelMenu from './sidebar_browse_or_add_channel_menu';
import SidebarHeader from './sidebar_header';
import type {Props} from './sidebar_header';
import AddChannelDropdown from '../add_channel_dropdown';
let props: Props;
const mockDispatch = jest.fn();
@@ -37,7 +36,6 @@ describe('Components/SidebarHeader', () => {
handleOpenDirectMessagesModal: jest.fn(),
unreadFilterEnabled: true,
showCreateUserGroupModal: jest.fn(),
userGroupsEnabled: false,
canCreateCustomGroups: true,
};
@@ -130,9 +128,9 @@ describe('Components/SidebarHeader', () => {
};
});
it('should show AddChannelDropdown', () => {
it('should show BrowserOrAddChannelMenu', () => {
const wrapper = shallow(<SidebarHeader {...props}/>);
expect(wrapper.find(AddChannelDropdown).length).toBe(1);
expect(wrapper.find(BrowserOrAddChannelMenu).length).toBe(1);
});
it('should embed teams menu dropdown into heading', () => {

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

@@ -1,58 +1,20 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import styled from 'styled-components';
import React, {useState} from 'react';
import {useSelector} from 'react-redux';
import Flex from '@mattermost/compass-components/utilities/layout/Flex'; // eslint-disable-line no-restricted-imports
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
import {setAddChannelDropdown} from 'actions/views/add_channel_dropdown';
import {isAddChannelDropdownOpen} from 'selectors/views/add_channel_dropdown';
import useGetUsageDeltas from 'components/common/hooks/useGetUsageDeltas';
import CompassThemeProvider from 'components/compass_theme_provider/compass_theme_provider';
import MainMenu from 'components/main_menu';
import AddChannelDropdown from 'components/sidebar/add_channel_dropdown';
import {OnboardingTourSteps} from 'components/tours';
import {useShowOnboardingTutorialStep} from 'components/tours/onboarding_tour';
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
import WithTooltip from 'components/with_tooltip';
import SidebarBrowseOrAddChannelMenu from './sidebar_browse_or_add_channel_menu';
import './sidebar_header.scss';
import type {GlobalState} from 'types/store';
type SidebarHeaderContainerProps = {
id?: string;
}
const SidebarHeaderContainer = styled(Flex).attrs(() => ({
element: 'header',
row: true,
justify: 'space-between',
alignment: 'center',
}))<SidebarHeaderContainerProps>`
height: 55px;
padding: 0 16px;
gap: 8px;
.dropdown-menu {
position: absolute;
transform: translate(0, 0);
margin-left: 0;
min-width: 210px;
}
#SidebarContainer & .AddChannelDropdown_dropdownButton {
border-radius: 16px;
font-size: 18px;
}
`;
export type Props = {
showNewChannelModal: () => void;
showMoreChannelsModal: () => void;
@@ -61,23 +23,14 @@ export type Props = {
showCreateCategoryModal: () => void;
canCreateChannel: boolean;
canJoinPublicChannel: boolean;
handleOpenDirectMessagesModal: (e: Event) => void;
handleOpenDirectMessagesModal: () => void;
unreadFilterEnabled: boolean;
userGroupsEnabled: boolean;
canCreateCustomGroups: boolean;
}
const SidebarHeader = (props: Props) => {
const dispatch = useDispatch();
const currentTeam = useSelector((state: GlobalState) => getCurrentTeam(state));
const showCreateTutorialTip = useShowOnboardingTutorialStep(OnboardingTourSteps.CREATE_AND_JOIN_CHANNELS);
const showInviteTutorialTip = useShowOnboardingTutorialStep(OnboardingTourSteps.INVITE_PEOPLE);
const currentTeam = useSelector(getCurrentTeam);
const usageDeltas = useGetUsageDeltas();
const isAddChannelOpen = useSelector(isAddChannelDropdownOpen);
const theme = useSelector(getTheme);
const openAddChannelOpen = useCallback((open: boolean) => {
dispatch(setAddChannelDropdown(open));
}, []);
const [menuToggled, setMenuToggled] = useState(false);
@@ -90,48 +43,44 @@ const SidebarHeader = (props: Props) => {
}
return (
<CompassThemeProvider theme={theme}>
<SidebarHeaderContainer
id={'sidebar-header-container'}
<header
id='sidebar-header-container'
className='sidebarHeaderContainer'
>
<MenuWrapper
onToggle={handleMenuToggle}
className='SidebarHeaderMenuWrapper test-team-header'
>
<MenuWrapper
onToggle={handleMenuToggle}
className='SidebarHeaderMenuWrapper test-team-header'
<WithTooltip
title={currentTeam.description ? currentTeam.description : currentTeam.display_name}
>
<WithTooltip
title={currentTeam.description ? currentTeam.description : currentTeam.display_name}
>
<h1 className='sidebarHeader'>
<button className='style--none sidebar-header'>
<span className='title'>{currentTeam.display_name}</span>
<i className='icon icon-chevron-down'/>
</button>
</h1>
</WithTooltip>
<MainMenu
id='sidebarDropdownMenu'
usageDeltaTeams={usageDeltas.teams.active}
/>
</MenuWrapper>
<AddChannelDropdown
showNewChannelModal={props.showNewChannelModal}
showMoreChannelsModal={props.showMoreChannelsModal}
invitePeopleModal={props.invitePeopleModal}
showCreateCategoryModal={props.showCreateCategoryModal}
canCreateChannel={props.canCreateChannel}
canJoinPublicChannel={props.canJoinPublicChannel}
handleOpenDirectMessagesModal={props.handleOpenDirectMessagesModal}
unreadFilterEnabled={props.unreadFilterEnabled}
showCreateTutorialTip={showCreateTutorialTip}
showInviteTutorialTip={showInviteTutorialTip}
isAddChannelOpen={isAddChannelOpen}
openAddChannelOpen={openAddChannelOpen}
canCreateCustomGroups={props.canCreateCustomGroups}
showCreateUserGroupModal={props.showCreateUserGroupModal}
userGroupsEnabled={props.userGroupsEnabled}
<h1 className='sidebarHeader'>
<button className='style--none sidebar-header'>
<span className='title'>{currentTeam.display_name}</span>
<i className='icon icon-chevron-down'/>
</button>
</h1>
</WithTooltip>
<MainMenu
id='sidebarDropdownMenu'
usageDeltaTeams={usageDeltas.teams.active}
/>
</SidebarHeaderContainer>
</CompassThemeProvider>
</MenuWrapper>
{(props.canCreateChannel || props.canJoinPublicChannel) && (
<SidebarBrowseOrAddChannelMenu
canCreateChannel={props.canCreateChannel}
onCreateNewChannelClick={props.showNewChannelModal}
canJoinPublicChannel={props.canJoinPublicChannel}
onBrowseChannelClick={props.showMoreChannelsModal}
onOpenDirectMessageClick={props.handleOpenDirectMessagesModal}
canCreateCustomGroups={props.canCreateCustomGroups}
onCreateNewUserGroupClick={props.showCreateUserGroupModal}
unreadFilterEnabled={props.unreadFilterEnabled}
onCreateNewCategoryClick={props.showCreateCategoryModal}
onInvitePeopleClick={props.invitePeopleModal}
/>
)}
</header>
);
};

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

@@ -76,13 +76,13 @@ exports[`components/threading/channel_threads/thread_footer should report total
<ProfilePopoverController
src="/api/v4/users/5/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="5"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -147,13 +147,13 @@ exports[`components/threading/channel_threads/thread_footer should report total
<ProfilePopoverController
src="/api/v4/users/4/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="4"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -218,13 +218,13 @@ exports[`components/threading/channel_threads/thread_footer should report total
<ProfilePopoverController
src="/api/v4/users/3/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="3"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -587,13 +587,13 @@ exports[`components/threading/channel_threads/thread_footer should show unread i
<ProfilePopoverController
src="/api/v4/users/5/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="5"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -658,13 +658,13 @@ exports[`components/threading/channel_threads/thread_footer should show unread i
<ProfilePopoverController
src="/api/v4/users/4/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="4"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -729,13 +729,13 @@ exports[`components/threading/channel_threads/thread_footer should show unread i
<ProfilePopoverController
src="/api/v4/users/3/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="3"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}

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

@@ -8,11 +8,12 @@ import {savePreferences} from 'mattermost-redux/actions/preferences';
import {getCurrentRelativeTeamUrl} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentUserId, isCurrentUserGuestUser} from 'mattermost-redux/selectors/entities/users';
import {setAddChannelDropdown} from 'actions/views/add_channel_dropdown';
import {close as closeLhs, open as openLhs} from 'actions/views/lhs';
import {switchToChannels} from 'actions/views/onboarding_tasks';
import {openMenu, dismissMenu} from 'components/menu';
import {OnboardingTaskCategory, OnboardingTaskList, OnboardingTasksName} from 'components/onboarding_tasks';
import {ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU} from 'components/sidebar/sidebar_header/sidebar_browse_or_add_channel_menu';
import {getHistory} from 'utils/browser_history';
@@ -51,11 +52,11 @@ export const useHandleNavigationAndExtraActions = (tourCategory: string) => {
break;
}
case OnboardingTourSteps.CREATE_AND_JOIN_CHANNELS : {
dispatch(setAddChannelDropdown(true));
openMenu(ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU);
break;
}
case OnboardingTourSteps.INVITE_PEOPLE : {
dispatch(setAddChannelDropdown(true));
openMenu(ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU);
break;
}
case OnboardingTourSteps.SEND_MESSAGE : {
@@ -107,11 +108,11 @@ export const useHandleNavigationAndExtraActions = (tourCategory: string) => {
if (tourCategory === TutorialTourName.ONBOARDING_TUTORIAL_STEP) {
switch (lastStep) {
case OnboardingTourSteps.CREATE_AND_JOIN_CHANNELS : {
dispatch(setAddChannelDropdown(false));
dismissMenu();
break;
}
case OnboardingTourSteps.INVITE_PEOPLE : {
dispatch(setAddChannelDropdown(false));
dismissMenu();
break;
}
default:

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

@@ -8,7 +8,7 @@ import {useMeasurePunchouts} from '@mattermost/components';
import OnboardingTourTip from './onboarding_tour_tip';
const translate = {x: 0, y: 70};
const translate = {x: -3, y: 13};
export const CreateAndJoinChannelsTour = () => {
const title = (
@@ -26,7 +26,7 @@ export const CreateAndJoinChannelsTour = () => {
</p>
);
const overlayPunchOut = useMeasurePunchouts(['showMoreChannels', 'showNewChannel'], [], {y: -8, height: 16, x: 0, width: 0});
const overlayPunchOut = useMeasurePunchouts(['browserOrAddChannelMenu'], [], {x: -2.5, y: -2.5, width: 5, height: 5});
return (
<OnboardingTourTip

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

@@ -8,7 +8,7 @@ import {useMeasurePunchouts} from '@mattermost/components';
import OnboardingTourTip from './onboarding_tour_tip';
const translate = {x: 0, y: -18};
const translate = {x: -3, y: -25};
export const InvitePeopleTour = () => {
const title = (
@@ -26,7 +26,7 @@ export const InvitePeopleTour = () => {
</p>
);
const overlayPunchOut = useMeasurePunchouts(['invitePeople'], [], {y: -8, height: 16, x: 0, width: 0});
const overlayPunchOut = useMeasurePunchouts(['browserOrAddChannelMenu'], [], {x: -2.5, y: -2.5, width: 5, height: 5});
return (
<OnboardingTourTip

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

@@ -29,6 +29,8 @@ import './user_account_menu.scss';
type Props = PropsFromRedux;
export const ELEMENT_ID_FOR_USER_ACCOUNT_MENU_BUTTON = 'userAccountMenuButton';
export default function UserAccountMenu(props: Props) {
const {formatMessage} = useIntl();
@@ -46,7 +48,7 @@ export default function UserAccountMenu(props: Props) {
return (
<Menu.Container
menuButton={{
id: 'userAccountMenuButton',
id: ELEMENT_ID_FOR_USER_ACCOUNT_MENU_BUTTON,
class: classNames('userAccountMenu_menuButton', {
withCustomStatus: isCustomStatusSet,
}),

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

@@ -30,13 +30,13 @@ exports[`components/widgets/users/Avatars should fetch missing users 1`] = `
<ProfilePopoverController
src="/api/v4/users/1/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="1"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -101,13 +101,13 @@ exports[`components/widgets/users/Avatars should fetch missing users 1`] = `
<ProfilePopoverController
src="/api/v4/users/6/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="6"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -172,13 +172,13 @@ exports[`components/widgets/users/Avatars should fetch missing users 1`] = `
<ProfilePopoverController
src="/api/v4/users/7/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="7"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -302,13 +302,13 @@ exports[`components/widgets/users/Avatars should properly count overflow 1`] = `
<ProfilePopoverController
src="/api/v4/users/1/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="1"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -373,13 +373,13 @@ exports[`components/widgets/users/Avatars should properly count overflow 1`] = `
<ProfilePopoverController
src="/api/v4/users/2/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="2"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -444,13 +444,13 @@ exports[`components/widgets/users/Avatars should properly count overflow 1`] = `
<ProfilePopoverController
src="/api/v4/users/3/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="3"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -572,13 +572,13 @@ exports[`components/widgets/users/Avatars should support userIds 1`] = `
<ProfilePopoverController
src="/api/v4/users/1/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="1"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -643,13 +643,13 @@ exports[`components/widgets/users/Avatars should support userIds 1`] = `
<ProfilePopoverController
src="/api/v4/users/2/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="2"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
@@ -714,13 +714,13 @@ exports[`components/widgets/users/Avatars should support userIds 1`] = `
<ProfilePopoverController
src="/api/v4/users/3/image?_=0"
triggerComponentAs="button"
triggerComponentClass="style--none rounded-button"
triggerComponentClass="style--none btn-round"
userId="3"
>
<button
aria-expanded="false"
aria-haspopup="dialog"
className="style--none rounded-button"
className="style--none btn-round"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}

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

@@ -63,7 +63,7 @@ function UserAvatar({
return (
<ProfilePopover<HTMLButtonElement>
triggerComponentAs='button'
triggerComponentClass='style--none rounded-button'
triggerComponentClass='style--none btn-round'
userId={userId}
src={profilePictureURL}
>

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

@@ -1,7 +1,7 @@
@use 'utils/variables';
.tooltipContainer {
z-index: variables.$z-index-popover;
z-index: variables.$z-index-tooltip;
max-width: 220px;
padding: 4px 8px;
border-radius: 4px;

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

@@ -5128,12 +5128,8 @@
"shortcuts.team_nav.switcher.mac": "Navigate to a specific team:\t⌘|⌥|[1-9]",
"sidebar_left.add_channel_cta_dropdown.dropdownAriaLabel": "Add Channel Dropdown",
"sidebar_left.add_channel_dropdown.browseChannels": "Browse channels",
"sidebar_left.add_channel_dropdown.browseOrCreateChannels": "Browse or create channels",
"sidebar_left.add_channel_dropdown.createCategory": "Create new category",
"sidebar_left.add_channel_dropdown.createNewChannel": "Create new channel",
"sidebar_left.add_channel_dropdown.dropdownAriaLabel": "Add Channel Dropdown",
"sidebar_left.add_channel_dropdown.invitePeople": "Invite people",
"sidebar_left.add_channel_dropdown.invitePeopleExtraText": "Add people to the team",
"sidebar_left.addChannelsCta": "Add channels",
"sidebar_left.channel_filter.filterByUnread": "Filter by unread",
"sidebar_left.channel_filter.filterUnreadAria": "unreads filter",
@@ -5192,7 +5188,6 @@
"sidebar_right_menu.flagged": "Saved messages",
"sidebar_right_menu.recentMentions": "Recent Mentions",
"sidebar.createDirectMessage": "Write a direct message",
"sidebar.createUserGroup": "Create New User Group",
"sidebar.directchannel.you": "{displayname} (you)",
"sidebar.menu.item.notSelected": "not selected",
"sidebar.menu.item.selected": "selected",
@@ -5211,6 +5206,14 @@
"sidebar.types.favorites": "FAVORITES",
"sidebar.types.unreads": "UNREADS",
"sidebar.unreads": "More unreads",
"sidebarLeft.browserOrCreateChannelMenu.browseChannelsMenuItem.primaryLabel": "Browse channels",
"sidebarLeft.browserOrCreateChannelMenu.createCategoryMenuItem.primaryLabel": "Create new category",
"sidebarLeft.browserOrCreateChannelMenu.createNewChannelMenuItem.primaryLabel": "Create new channel",
"sidebarLeft.browserOrCreateChannelMenu.createUserGroupMenuItem.primaryLabel": "Create new user group",
"sidebarLeft.browserOrCreateChannelMenu.invitePeopleMenuItem.primaryLabel": "Invite people",
"sidebarLeft.browserOrCreateChannelMenu.invitePeopleMenuItem.secondaryLabel": "Add people to the team",
"sidebarLeft.browserOrCreateChannelMenu.openDirectMessageMenuItem.primaryLabel": "Open a direct message",
"sidebarLeft.browserOrCreateChannelMenuButton.label": "Browse or create channels",
"signup_team_system_console": "Go to System Console",
"signup_team.guest_without_channels": "Your guest account has no channels assigned. Please contact an administrator.",
"signup_team.join_open": "Teams you can join: ",

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

@@ -1,21 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {combineReducers} from 'redux';
import {ActionTypes} from 'utils/constants';
import type {MMAction} from 'types/store';
export function isOpen(state = false, action: MMAction) {
switch (action.type) {
case ActionTypes.ADD_CHANNEL_DROPDOWN_TOGGLE:
return action.open;
default:
return state;
}
}
export default combineReducers({
isOpen,
});

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

@@ -4,7 +4,6 @@
import {combineReducers} from 'redux';
import addChannelCtaDropdown from './add_channel_cta_dropdown';
import addChannelDropdown from './add_channel_dropdown';
import admin from './admin';
import announcementBar from './announcement_bar';
import browser from './browser';
@@ -49,7 +48,6 @@ export default combineReducers({
marketplace,
textbox,
channelSidebar,
addChannelDropdown,
addChannelCtaDropdown,
onboardingTasks,
threads,

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

@@ -21,7 +21,7 @@
}
}
.rounded-button {
.btn-round {
border-radius: 50%;
}
@@ -51,6 +51,10 @@ button {
outline: none !important;
transition: all 0.15s ease;
&.btn-round {
border-radius: 50%;
}
&.btn-icon {
width: 40px;
min-width: 40px;
@@ -298,6 +302,20 @@ button {
}
}
}
&.btn-tertiary.btn-inverted {
background-color: rgba(var(--sidebar-text-rgb), 0.12);
color: rgba(var(--sidebar-text-rgb), 1);
&:hover {
background-color: rgb(var(--sidebar-text-rgb), 0.16);
}
&:active,
&[aria-expanded="true"][aria-haspopup="true"] {
background-color: rgb(var(--sidebar-text-rgb), 0.24);
outline: none;
}
}
&.btn-quaternary {
background: transparent;

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

@@ -45,6 +45,7 @@
.app__body {
.modal {
z-index: variables.$z-index-modal;
color: rgb(var(--center-channel-color-rgb));
.modal-content {

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

@@ -161,7 +161,6 @@ $sidebarOpacityAnimationDuration: 0.15s;
&.desktop {
display: block;
.AddChannelDropdown_dropdownButton,
.SidebarFilters_filterButton,
.SidebarChannelNavigator_backButton,
.SidebarChannelNavigator_inviteUsers,
@@ -178,7 +177,6 @@ $sidebarOpacityAnimationDuration: 0.15s;
&.webapp {
display: flex;
.AddChannelDropdown_dropdownButton,
.SidebarChannelNavigator_inviteUsers,
.SidebarChannelNavigator_jumpToButton,
.SidebarFilters_filterButton {
@@ -228,7 +226,6 @@ $sidebarOpacityAnimationDuration: 0.15s;
}
}
.AddChannelDropdown_dropdownButton,
.SidebarChannelNavigator_inviteUsers,
.SidebarChannelNavigator_jumpToButton,
.SidebarFilters_filterButtontton,
@@ -331,35 +328,6 @@ $sidebarOpacityAnimationDuration: 0.15s;
color: var(--sidebar-bg);
}
.AddChannelDropdown {
height: 30px;
.dropdown-menu {
min-width: 250px;
margin-top: 0;
line-height: 19px;
span {
color: rgba(var(--center-channel-color-rgb), 0.9);
&:not(.MenuItem__help-text) {
white-space: nowrap;
}
}
button {
padding: 1px 16px;
}
i {
color: rgba(var(--center-channel-color-rgb), 0.64);
font-size: 16px;
line-height: 16px;
}
}
}
.AddChannelDropdown_dropdownButton,
.SidebarChannelNavigator_inviteUsers {
z-index: 1;
width: 28px;
@@ -372,18 +340,6 @@ $sidebarOpacityAnimationDuration: 0.15s;
font-size: 20px;
}
.AddChannelDropdown_dropdownButton:hover,
.AddChannelDropdown_dropdownButton:active,
.AddChannelDropdown_dropdownButton:focus {
background-color: rgba(var(--sidebar-text-rgb), 0.16);
color: var(--sidebar-text);
cursor: pointer;
}
.AddChannelDropdown_dropdownButton:focus {
outline-style: none;
}
.sidebar-header {
display: flex;
overflow: hidden;
@@ -408,11 +364,6 @@ $sidebarOpacityAnimationDuration: 0.15s;
}
}
.AddChannelDropdown_dropdownButton {
background-color: rgba(var(--sidebar-text-rgb), 0.08);
color: rgba(var(--sidebar-text-rgb), 0.64);
}
.SidebarMenu {
opacity: 1;

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

@@ -368,7 +368,6 @@
.post,
.SidebarMenu,
.AddChannelDropdown,
.GlobalThreads {
.Menu {
position: fixed;
@@ -1555,8 +1554,7 @@
}
}
.SidebarMenu,
.AddChannelDropdown {
.SidebarMenu {
display: block !important;
.Menu {

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

@@ -37,12 +37,33 @@ $elevation-4: var(--elevation-4);
$elevation-5: var(--elevation-5);
$elevation-6: var(--elevation-6);
// Z-Index
// Central place to manage z-index values for the app, providing a consistent way to manage stacking order.
$z-index-popover: 1070;
$z-index-user-group-popover: 1060;
$z-index-tour-tips: 1058;
$z-index-menu: 1054;
// ********************
// Z-Index Management
// Central place to manage z-index values for the app, providing a consistent way to manage the stacking order.
// Great care should be taken before making changes, followed by extensive UI testing.
// Note : not all defined here are being imported, check usages
// ********************
// Since they can be used on any modal, menu or popover for now they are highest
$z-index-tooltip: 1350;
// Tour tips also open on menus, so they should be higher than menus
// Since tippy requires passing zIndex as prop, we have it as constant DEFAULT_Z_INDEX_TOUR_TIPS_POPOVER
$z-index-tour-tips-popover: 1300;
// Since tour tips are in package/components, we have also defined this in its own scss file
// backdrop are dark screen behind the tour tips with a cutout for the highlighted element
$z-index-tour-tips-backdrop: 1250;
// Since menus can appear on either popovers or modal, make it higher than them
$z-index-menu: 1100;
// Modals require attention and should be layered above other popovers
$z-index-modal: 1050;
$z-index-popover: 1200;
// Lower than popover because user group popover can open user's popover over it
$z-index-user-group-popover: 1150;
$z-index-global-header: 99;
$z-index-channel-header: 15;

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

@@ -3,10 +3,6 @@
import type {GlobalState} from 'types/store';
export function isAddChannelDropdownOpen(state: GlobalState) {
return state.views.addChannelDropdown.isOpen;
}
export function isAddChannelCtaDropdownOpen(state: GlobalState) {
return state.views.addChannelCtaDropdown.isOpen;
}

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

@@ -194,10 +194,6 @@ export type ViewsState = {
lastSelectedChannel: string;
};
addChannelDropdown: {
isOpen: boolean;
};
addChannelCtaDropdown: {
isOpen: boolean;
};

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

@@ -250,7 +250,6 @@ export const ActionTypes = keyMirror({
INCREMENT_EMOJI_PICKER_PAGE: null,
SET_RECENT_SKIN: null,
ADD_CHANNEL_DROPDOWN_TOGGLE: null,
ADD_CHANNEL_CTA_DROPDOWN_TOGGLE: null,
SHOW_ONBOARDING_TASK_COMPLETION: null,

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

@@ -33,20 +33,3 @@ export function isKeyPressed(event: React.KeyboardEvent | KeyboardEvent, key: [s
// used for different language keyboards to detect the position of keys
return event.keyCode === key[1];
}
export const MenuButtonIds = {
userAccountMenu: 'userAccountMenuButton',
} as const;
/**
* Since the menu component requires actual interaction with the button
* of the menu for opening the menus, we trigger it from here
*/
export function clickOnMenuButton(buttonId: string) {
const menuButton = document.getElementById(buttonId);
if (!menuButton) {
return;
}
menuButton.click();
}

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

@@ -1,3 +1,5 @@
$z-index-tour-tips-backdrop: 1250;
.tour-tip {
display: flex;
@@ -222,7 +224,6 @@
&__overlay {
position: fixed;
z-index: 999;
top: 0;
right: 0;
bottom: 0;
@@ -357,7 +358,7 @@
&__backdrop {
position: absolute;
z-index: 100;
z-index: $z-index-tour-tips-backdrop;
top: 0;
left: 0;
width: 100%;

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

@@ -21,6 +21,9 @@ import './tour_tip.scss';
export type TourTipEventSource = 'next' | 'prev' | 'dismiss' | 'jump' | 'skipped' | 'open' | 'punchOut'
// If this needs to alter, change in _variables $z-index-tour-tips-popover as well
const DEFAULT_Z_INDEX_TOUR_TIPS_POPOVER = 1300;
type Props = {
show: boolean;
screen: JSX.Element;
@@ -84,7 +87,7 @@ export const TourTip = ({
placement = 'right-start',
showOptOut = true,
width = 352,
zIndex = 999,
zIndex = DEFAULT_Z_INDEX_TOUR_TIPS_POPOVER,
hideBackdrop = false,
tippyBlueStyle = false,
}: Props) => {