MM-56324 Channel Header UI Update (#26383)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
54d7011aba
Коммит
4ba736f018
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -21,8 +21,6 @@ import Markdown from 'components/markdown';
|
||||
import OverlayTrigger from 'components/overlay_trigger';
|
||||
import type {BaseOverlayTrigger} from 'components/overlay_trigger';
|
||||
import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
|
||||
import {statusDropdownMessages} from 'components/status_dropdown/status_dropdown';
|
||||
import StatusIcon from 'components/status_icon';
|
||||
import Timestamp from 'components/timestamp';
|
||||
import Tooltip from 'components/tooltip';
|
||||
import Popover from 'components/widgets/popover';
|
||||
@@ -241,6 +239,7 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
<CustomStatusText
|
||||
text={customStatus?.text}
|
||||
className='custom-emoji__text'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -270,7 +269,7 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
<span tabIndex={0}>
|
||||
<FormattedMessage
|
||||
id='channel_header.channelHasGuests'
|
||||
defaultMessage='This channel has guests'
|
||||
defaultMessage='Channel has guests'
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -308,14 +307,10 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
let dmHeaderIconStatus: ReactNode;
|
||||
let dmHeaderTextStatus: ReactNode;
|
||||
if (isDirect && !dmUser?.delete_at && !dmUser?.is_bot) {
|
||||
dmHeaderIconStatus = (<StatusIcon status={channel.status}/>);
|
||||
|
||||
dmHeaderTextStatus = (
|
||||
<span className='header-status__text'>
|
||||
<FormattedMessage {...statusDropdownMessages[channel.status!].name}/>
|
||||
{this.renderCustomStatus()}
|
||||
</span>
|
||||
);
|
||||
@@ -345,11 +340,11 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
const channelFilesIconClass = classNames('channel-header__icon channel-header__icon--wide channel-header__icon--left', {
|
||||
const channelFilesIconClass = classNames('channel-header__icon channel-header__icon--left btn btn-icon btn-xs ', {
|
||||
'channel-header__icon--active': rhsState === RHSStates.CHANNEL_FILES,
|
||||
});
|
||||
const channelFilesIcon = <i className='icon icon-file-text-outline'/>;
|
||||
const pinnedIconClass = classNames('channel-header__icon channel-header__icon--wide channel-header__icon--left', {
|
||||
const pinnedIconClass = classNames('channel-header__icon channel-header__icon--wide channel-header__icon--left btn btn-icon btn-xs', {
|
||||
'channel-header__icon--active': rhsState === RHSStates.PIN,
|
||||
});
|
||||
const pinnedIcon = this.props.pinnedPostsCount ? (
|
||||
@@ -374,7 +369,7 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
|
||||
let memberListButton = null;
|
||||
if (!isDirect) {
|
||||
const membersIconClass = classNames('member-rhs__trigger channel-header__icon channel-header__icon--left channel-header__icon--wide', {
|
||||
const membersIconClass = classNames('member-rhs__trigger channel-header__icon channel-header__icon--wide channel-header__icon--left btn btn-icon btn-xs', {
|
||||
'channel-header__icon--active': rhsState === RHSStates.CHANNEL_MEMBERS,
|
||||
});
|
||||
const membersIcon = this.props.memberCount ? (
|
||||
@@ -428,7 +423,7 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
id='header-popover'
|
||||
popoverStyle='info'
|
||||
popoverSize='lg'
|
||||
style={{transform: `translate(${this.state.leftOffset}px, ${this.state.topOffset}px)`, maxWidth: this.state.channelHeaderPoverWidth}}
|
||||
style={{transform: `translate(${this.state.leftOffset}px, ${this.state.topOffset}px)`, maxWidth: this.state.channelHeaderPoverWidth + 16}}
|
||||
placement='bottom'
|
||||
className={classNames('channel-header__popover', {'chanel-header__popover--lhs_offset': this.props.hasMoreThanOneTeam})}
|
||||
>
|
||||
@@ -450,28 +445,7 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
className='channel-header__description'
|
||||
dir='auto'
|
||||
>
|
||||
{dmHeaderIconStatus}
|
||||
{dmHeaderTextStatus}
|
||||
{memberListButton}
|
||||
|
||||
<HeaderIconWrapper
|
||||
iconComponent={pinnedIcon}
|
||||
ariaLabel={true}
|
||||
buttonClass={pinnedIconClass}
|
||||
buttonId={'channelHeaderPinButton'}
|
||||
onClick={this.showPinnedPosts}
|
||||
tooltipKey={'pinnedPosts'}
|
||||
/>
|
||||
{this.props.isFileAttachmentsEnabled &&
|
||||
<HeaderIconWrapper
|
||||
iconComponent={channelFilesIcon}
|
||||
ariaLabel={true}
|
||||
buttonClass={channelFilesIconClass}
|
||||
buttonId={'channelHeaderFilesButton'}
|
||||
onClick={this.showChannelFiles}
|
||||
tooltipKey={'channelFiles'}
|
||||
/>
|
||||
}
|
||||
{hasGuestsText}
|
||||
<div
|
||||
className='header-popover-text-measurer'
|
||||
@@ -557,30 +531,9 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
headerTextContainer = (
|
||||
<div
|
||||
id='channelHeaderDescription'
|
||||
className='channel-header__description light'
|
||||
className='channel-header__description'
|
||||
>
|
||||
{dmHeaderIconStatus}
|
||||
{dmHeaderTextStatus}
|
||||
{memberListButton}
|
||||
|
||||
<HeaderIconWrapper
|
||||
iconComponent={pinnedIcon}
|
||||
ariaLabel={true}
|
||||
buttonClass={pinnedIconClass}
|
||||
buttonId={'channelHeaderPinButton'}
|
||||
onClick={this.showPinnedPosts}
|
||||
tooltipKey={'pinnedPosts'}
|
||||
/>
|
||||
{this.props.isFileAttachmentsEnabled &&
|
||||
<HeaderIconWrapper
|
||||
iconComponent={channelFilesIcon}
|
||||
ariaLabel={true}
|
||||
buttonClass={channelFilesIconClass}
|
||||
buttonId={'channelHeaderFilesButton'}
|
||||
onClick={this.showChannelFiles}
|
||||
tooltipKey={'channelFiles'}
|
||||
/>
|
||||
}
|
||||
{hasGuestsText}
|
||||
{editMessage}
|
||||
</div>
|
||||
@@ -607,7 +560,7 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
<button
|
||||
id='toggleMute'
|
||||
onClick={this.unmute}
|
||||
className={'style--none color--link channel-header__mute inactive'}
|
||||
className={'channel-header__mute inactive btn btn-icon btn-xs'}
|
||||
aria-label={formatMessage({id: 'generic_icons.muted', defaultMessage: 'Muted Icon'})}
|
||||
>
|
||||
<i className={'icon icon-bell-off-outline'}/>
|
||||
@@ -635,15 +588,36 @@ class ChannelHeader extends React.PureComponent<Props, State> {
|
||||
<div
|
||||
className='channel-header__title dropdown'
|
||||
>
|
||||
<div>
|
||||
<ChannelHeaderTitle
|
||||
dmUser={dmUser}
|
||||
gmMembers={gmMembers}
|
||||
<ChannelHeaderTitle
|
||||
dmUser={dmUser}
|
||||
gmMembers={gmMembers}
|
||||
/>
|
||||
<div
|
||||
className='channel-header__icons'
|
||||
>
|
||||
{muteTrigger}
|
||||
{memberListButton}
|
||||
<HeaderIconWrapper
|
||||
iconComponent={pinnedIcon}
|
||||
ariaLabel={true}
|
||||
buttonClass={pinnedIconClass}
|
||||
buttonId={'channelHeaderPinButton'}
|
||||
onClick={this.showPinnedPosts}
|
||||
tooltipKey={'pinnedPosts'}
|
||||
/>
|
||||
{this.props.isFileAttachmentsEnabled &&
|
||||
<HeaderIconWrapper
|
||||
iconComponent={channelFilesIcon}
|
||||
ariaLabel={true}
|
||||
buttonClass={channelFilesIconClass}
|
||||
buttonId={'channelHeaderFilesButton'}
|
||||
onClick={this.showChannelFiles}
|
||||
tooltipKey={'channelFiles'}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
{muteTrigger}
|
||||
{headerTextContainer}
|
||||
</div>
|
||||
{headerTextContainer}
|
||||
</div>
|
||||
</div>
|
||||
<ChannelHeaderPlug
|
||||
|
||||
@@ -3,19 +3,24 @@
|
||||
|
||||
import classNames from 'classnames';
|
||||
import type {ReactNode} from 'react';
|
||||
import React, {memo, useState} from 'react';
|
||||
import React, {memo, useState, useRef, useEffect} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {getIsRhsOpen} from 'selectors/rhs';
|
||||
|
||||
import {ChannelHeaderDropdown} from 'components/channel_header_dropdown';
|
||||
import ProfilePicture from 'components/profile_picture';
|
||||
import SharedChannelIndicator from 'components/shared_channel_indicator';
|
||||
import ArchiveIcon from 'components/widgets/icons/archive_icon';
|
||||
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
|
||||
import BotTag from 'components/widgets/tag/bot_tag';
|
||||
import WithTooltip from 'components/with_tooltip';
|
||||
|
||||
import {Constants} from 'utils/constants';
|
||||
|
||||
@@ -33,13 +38,33 @@ const ChannelHeaderTitle = ({
|
||||
gmMembers,
|
||||
}: Props) => {
|
||||
const [titleMenuOpen, setTitleMenuOpen] = useState(false);
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const intl = useIntl();
|
||||
const channel = useSelector(getCurrentChannel);
|
||||
|
||||
const headerItemRef = useRef<HTMLElement | null>(null);
|
||||
const isRHSOpen = useSelector(getIsRhsOpen);
|
||||
|
||||
useEffect(() => {
|
||||
enableToolTipIfNeeded();
|
||||
|
||||
// Re-check on window resize
|
||||
const handleResize = () => enableToolTipIfNeeded();
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [channel, gmMembers, dmUser, isRHSOpen]);
|
||||
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const enableToolTipIfNeeded = () => {
|
||||
const element = headerItemRef.current;
|
||||
const isTooltip = element && element.offsetWidth < element.scrollWidth;
|
||||
setShowTooltip(isTooltip as boolean);
|
||||
};
|
||||
|
||||
const isDirect = (channel.type === Constants.DM_CHANNEL);
|
||||
const isGroup = (channel.type === Constants.GM_CHANNEL);
|
||||
const channelIsArchived = channel.delete_at !== 0;
|
||||
@@ -71,8 +96,13 @@ const ChannelHeaderTitle = ({
|
||||
return (
|
||||
<div
|
||||
id='channelHeaderDropdownButton'
|
||||
className='channel-header__top channel-header__bot'
|
||||
className='channel-header__bot'
|
||||
>
|
||||
<ChannelHeaderTitleFavorite/>
|
||||
<ProfilePicture
|
||||
src={Client4.getProfilePictureUrl(dmUser.id, dmUser.last_picture_update)}
|
||||
size='sm'
|
||||
/>
|
||||
<strong
|
||||
role='heading'
|
||||
aria-level={2}
|
||||
@@ -85,33 +115,63 @@ const ChannelHeaderTitle = ({
|
||||
</span>
|
||||
</strong>
|
||||
<BotTag/>
|
||||
<ChannelHeaderTitleFavorite/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className='channel-header__top'>
|
||||
<ChannelHeaderTitleFavorite/>
|
||||
{isDirect && dmUser && ( // Check if it's a DM and dmUser is provided
|
||||
<ProfilePicture
|
||||
src={Client4.getProfilePictureUrl(dmUser.id, dmUser.last_picture_update)}
|
||||
size='sm'
|
||||
status={channel.status}
|
||||
/>
|
||||
)}
|
||||
<MenuWrapper onToggle={setTitleMenuOpen}>
|
||||
<div
|
||||
id='channelHeaderDropdownButton'
|
||||
className='channel-header__top'
|
||||
>
|
||||
<button
|
||||
className={classNames('channel-header__trigger style--none', {active: titleMenuOpen})}
|
||||
aria-label={intl.formatMessage({id: 'channel_header.menuAriaLabel', defaultMessage: 'Channel Menu'}).toLowerCase()}
|
||||
>
|
||||
<strong
|
||||
role='heading'
|
||||
aria-level={2}
|
||||
id='channelHeaderTitle'
|
||||
className='heading'
|
||||
>
|
||||
<span>
|
||||
{archivedIcon}
|
||||
{channelTitle}
|
||||
{sharedIcon}
|
||||
</span>
|
||||
</strong>
|
||||
{showTooltip ? (
|
||||
<WithTooltip
|
||||
id='channelHeaderTooltip'
|
||||
placement='bottom'
|
||||
title={channelTitle as string}
|
||||
>
|
||||
<strong
|
||||
role='heading'
|
||||
aria-level={2}
|
||||
id='channelHeaderTitle'
|
||||
className='heading'
|
||||
ref={headerItemRef}
|
||||
>
|
||||
<span>
|
||||
{archivedIcon}
|
||||
{channelTitle}
|
||||
{sharedIcon}
|
||||
</span>
|
||||
</strong>
|
||||
</WithTooltip>
|
||||
) : (
|
||||
<strong
|
||||
role='heading'
|
||||
aria-level={2}
|
||||
id='channelHeaderTitle'
|
||||
className='heading'
|
||||
ref={headerItemRef}
|
||||
>
|
||||
<span>
|
||||
{archivedIcon}
|
||||
{channelTitle}
|
||||
{sharedIcon}
|
||||
</span>
|
||||
</strong>
|
||||
)}
|
||||
<span
|
||||
id='channelHeaderDropdownIcon'
|
||||
className='icon icon-chevron-down header-dropdown-chevron-icon'
|
||||
@@ -120,8 +180,7 @@ const ChannelHeaderTitle = ({
|
||||
</div>
|
||||
<ChannelHeaderDropdown/>
|
||||
</MenuWrapper>
|
||||
<ChannelHeaderTitleFavorite/>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ const ChannelHeaderTitleFavorite = () => {
|
||||
id='toggleFavorite'
|
||||
ref={toggleFavoriteRef}
|
||||
onClick={toggleFavoriteCallback}
|
||||
className={classNames('style--none color--link channel-header__favorites', {active: isFavorite, inactive: !isFavorite})}
|
||||
className={classNames('channel-header__favorites btn btn-icon btn-xs', {active: isFavorite, inactive: !isFavorite})}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<i className={classNames('icon', {'icon-star': isFavorite, 'icon-star-outline': !isFavorite})}/>
|
||||
|
||||
Ссылка в новой задаче
Block a user