Commit c5102332 authored by Asaad Mahmood's avatar Asaad Mahmood Committed by Joram Wilander
Browse files

PLT-6905 - Updating channel header design (#6789)

* PLT-6905 - Updating channel header design

* Updating border-radius

* Updating radius for wide icons

* Updating trigger for overlay

* Updating UI for channel header

* Updating channel header sizing

* Updating channel header css

* Updating sidebar css

* Updating status icons

* Adjusting border

* Updating comment

* Removing type from status icon

* Fixing UI issues for the channel header/sidebar

* make "Add a channel description" open the channel header modal

* Updating status and opacity

* Updating stauts icon positioning

* Updating description and heading size

* Updating UI changes

* Updating UI changes

* add a focused class to the parent div .search__form and then remove when hover away

* Fix active state for pinned posts icon

* Updating UI changes

* Update channel header text
parent 4cd4783e
......@@ -31,6 +31,7 @@ export default function TableChart(props) {
<tr key={'table-entry-' + item.name}>
<td>
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='top'
overlay={tooltip}
......
......@@ -180,6 +180,7 @@ export default class ChangeUrlModal extends React.Component {
<div className='col-sm-10'>
<div className={urlClass}>
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='top'
overlay={urlTooltip}
......
......@@ -64,6 +64,7 @@ export default class ChannelHeader extends React.Component {
this.hideLeaveChannelModal = this.hideLeaveChannelModal.bind(this);
const state = this.getStateFromStores();
state.showEditChannelHeaderModal = false;
state.showEditChannelPurposeModal = false;
state.showMembersModal = false;
state.showRenameChannelModal = false;
......@@ -90,7 +91,8 @@ export default class ChannelHeader extends React.Component {
enableFormatting: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
isBusy: WebrtcStore.isBusy(),
isFavorite: channel && ChannelUtils.isFavoriteChannel(channel),
showLeaveChannelModal: false
showLeaveChannelModal: false,
pinsOpen: SearchStore.getIsPinnedPosts()
};
}
......@@ -281,7 +283,8 @@ export default class ChannelHeader extends React.Component {
render() {
const flagIcon = Constants.FLAG_ICON_SVG;
const pinIcon = Constants.PIN_ICON;
const pinIcon = Constants.PIN_ICON_SVG;
const mentionsIcon = Constants.MENTIONS_ICON_SVG;
if (!this.validState()) {
// Use an empty div to make sure the header's height stays constant
......@@ -300,6 +303,15 @@ export default class ChannelHeader extends React.Component {
</Tooltip>
);
const pinnedPostTooltip = (
<Tooltip id='pinnedPostTooltip'>
<FormattedMessage
id='channel_header.pinnedPosts'
defaultMessage='Pinned Posts'
/>
</Tooltip>
);
const flaggedTooltip = (
<Tooltip
id='flaggedTooltip'
......@@ -382,13 +394,14 @@ export default class ChannelHeader extends React.Component {
);
webrtc = (
<div className='webrtc__header'>
<div className='webrtc__header channel-header__icon'>
<a
href='#'
onClick={() => this.initWebrtc(otherUserId, !isOffline)}
disabled={isOffline}
>
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={Constants.WEBRTC_TIME_DELAY}
placement='bottom'
overlay={webrtcTooltip}
......@@ -754,11 +767,62 @@ export default class ChannelHeader extends React.Component {
}
}
let headerText;
if (this.state.enableFormatting) {
headerText = TextFormatting.formatText(channel.header, {singleline: true, mentionHighlight: false, siteURL: getSiteURL()});
let headerTextContainer;
if (channel.header) {
let headerTextElement;
if (this.state.enableFormatting) {
headerTextElement = (
<div
onClick={Utils.handleFormattedTextClick}
className='channel-header__description'
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.header, {singleline: true, mentionHighlight: false, siteURL: getSiteURL()})}}
/>
);
} else {
headerTextElement = (
<div
onClick={Utils.handleFormattedTextClick}
className='channel-header__description'
>
{channel.header}
</div>
);
}
headerTextContainer = (
<OverlayTrigger
trigger={'click'}
placement='bottom'
rootClose={true}
overlay={popoverContent}
ref='headerOverlay'
>
{headerTextElement}
</OverlayTrigger>
);
} else {
headerText = channel.header;
headerTextContainer = (
<a
href='#'
className='channel-header__description light'
onClick={() => this.setState({showEditChannelHeaderModal: true})}
>
<FormattedMessage
id='channel_header.addChannelHeader'
defaultMessage='Add a channel description'
/>
</a>
);
}
let editHeaderModal;
if (this.state.showEditChannelHeaderModal) {
editHeaderModal = (
<EditChannelHeaderModal
onHide={() => this.setState({showEditChannelHeaderModal: false})}
channel={channel}
/>
);
}
let toggleFavoriteTooltip;
......@@ -784,6 +848,7 @@ export default class ChannelHeader extends React.Component {
const toggleFavorite = (
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={toggleFavoriteTooltip}
......@@ -792,7 +857,7 @@ export default class ChannelHeader extends React.Component {
id='toggleFavorite'
href='#'
onClick={this.toggleFavorite}
className='channel-header__favorites'
className={'channel-header__favorites ' + (this.state.isFavorite ? 'active' : 'inactive')}
>
<i className={'icon fa ' + (this.state.isFavorite ? 'fa-star' : 'fa-star-o')}/>
</a>
......@@ -822,19 +887,23 @@ export default class ChannelHeader extends React.Component {
const leaveChannelModal = this.createLeaveChannelModal();
let pinnedIconClass = 'channel-header__icon';
if (this.state.pinsOpen) {
pinnedIconClass += ' active';
}
return (
<div
id='channel-header'
className='channel-header'
className='channel-header alt'
>
<table className='channel-header alt'>
<table>
<tbody>
<tr>
<th>
<div className='channel-header__info'>
{webrtc}
{toggleFavorite}
<div className='dropdown'>
<div className='channel-header__title dropdown'>
<a
id='channelHeaderDropdown'
href='#'
......@@ -844,7 +913,7 @@ export default class ChannelHeader extends React.Component {
aria-expanded='true'
>
<strong className='heading'>{channelTitle} </strong>
<span className='fa fa-chevron-down header-dropdown__icon'/>
<span className='fa fa-angle-down header-dropdown__icon'/>
</a>
<ul
className='dropdown-menu'
......@@ -854,35 +923,33 @@ export default class ChannelHeader extends React.Component {
{dropdownContents}
</ul>
</div>
<OverlayTrigger
trigger={'click'}
placement='bottom'
rootClose={true}
overlay={popoverContent}
ref='headerOverlay'
>
<div
onClick={Utils.handleFormattedTextClick}
className='description'
dangerouslySetInnerHTML={{__html: headerText}}
/>
</OverlayTrigger>
{headerTextContainer}
</div>
</th>
<th className='header-list__right'>
<th>
{webrtc}
</th>
<th>
{popoverListMembers}
<a
href='#'
type='button'
id='pinnedPostsButton'
className='pinned-posts-button'
onClick={this.getPinnedPosts}
</th>
<th>
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={pinnedPostTooltip}
>
<span
dangerouslySetInnerHTML={{__html: pinIcon}}
aria-hidden='true'
/>
</a>
<div
className={pinnedIconClass}
onClick={this.getPinnedPosts}
>
<span
className='icon icon__pin'
dangerouslySetInnerHTML={{__html: pinIcon}}
aria-hidden='true'
/>
</div>
</OverlayTrigger>
</th>
<th className='search-bar__container'>
<NavbarSearchBox
......@@ -891,47 +958,44 @@ export default class ChannelHeader extends React.Component {
/>
</th>
<th>
<div className='dropdown channel-header__links search-btns'>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={recentMentionsTooltip}
>
<a
id='searchMentions'
href='#'
type='button'
onClick={this.searchMentions}
>
{'@'}
</a>
</OverlayTrigger>
</div>
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={recentMentionsTooltip}
>
<div className='channel-header__icon icon--hidden'>
<span
className='icon icon__mentions'
dangerouslySetInnerHTML={{__html: mentionsIcon}}
aria-hidden='true'
/>
</div>
</OverlayTrigger>
</th>
<th>
<div className='dropdown channel-header__links search-btns'>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={flaggedTooltip}
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={flaggedTooltip}
>
<div
className='channel-header__icon icon--hidden'
onClick={this.getFlagged}
>
<a
id='flaggedPostsButton'
href='#'
type='button'
onClick={this.getFlagged}
>
<span
className='icon icon__flag'
dangerouslySetInnerHTML={{__html: flagIcon}}
/>
</a>
</OverlayTrigger>
</div>
<span
className='icon icon__flag'
dangerouslySetInnerHTML={{__html: flagIcon}}
/>
</div>
</OverlayTrigger>
</th>
</tr>
</tbody>
</table>
{editHeaderModal}
{editPurposeModal}
{channelMembersModal}
{leaveChannelModal}
......
......@@ -29,6 +29,8 @@ export default class ChannelInfoModal extends React.Component {
render() {
let channel = this.props.channel;
let channelIcon;
const globeIcon = Constants.GLOBE_ICON_SVG;
const lockIcon = Constants.LOCK_ICON_SVG;
if (!channel) {
const notFound = Utils.localizeMessage('channel_info.notFound', 'No Channel Found');
......@@ -43,9 +45,19 @@ export default class ChannelInfoModal extends React.Component {
}
if (channel.type === 'O') {
channelIcon = (<span className='fa fa-globe'/>);
channelIcon = (
<span
className='icon icon__globe icon--body'
dangerouslySetInnerHTML={{__html: globeIcon}}
/>
);
} else if (channel.type === 'P') {
channelIcon = (<span className='fa fa-lock'/>);
channelIcon = (
<span
className='icon icon__globe icon--body'
dangerouslySetInnerHTML={{__html: lockIcon}}
/>
);
}
const channelURL = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
......
......@@ -178,6 +178,7 @@ export default class TeamUrl extends React.Component {
<div className='col-sm-11'>
<div className='input-group input-group--limit'>
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='top'
overlay={urlTooltip}
......
......@@ -105,6 +105,7 @@ export default class FileAttachment extends React.Component {
if (this.props.compactDisplay) {
filenameOverlay = (
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={1000}
placement='top'
overlay={<Tooltip id='file-name__tooltip'>{fileName}</Tooltip>}
......@@ -126,6 +127,7 @@ export default class FileAttachment extends React.Component {
} else {
filenameOverlay = (
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={1000}
placement='top'
overlay={<Tooltip id='file-name__tooltip'>{Utils.localizeMessage('file_attachment.download', 'Download') + ' "' + fileName + '"'}</Tooltip>}
......
......@@ -279,6 +279,7 @@ export default class Navbar extends React.Component {
createDropdown(channel, channelTitle, isSystemAdmin, isTeamAdmin, isChannelAdmin, isDirect, isGroup, popoverContent) {
const isAdmin = isSystemAdmin || isTeamAdmin;
const infoIcon = Constants.INFO_ICON_SVG;
if (channel) {
let viewInfoOption;
......@@ -599,7 +600,13 @@ export default class Navbar extends React.Component {
className='description'
rootClose={true}
>
<div className='pull-right description info-popover'/>
<div className='pull-right description navbar-right__icon info-popover'>
<span
className='icon icon__info'
dangerouslySetInnerHTML={{__html: infoIcon}}
aria-hidden='true'
/>
</div>
</OverlayTrigger>
<a
href='#'
......@@ -652,6 +659,8 @@ export default class Navbar extends React.Component {
createCollapseButtons(currentId) {
var buttons = [];
const menuIcon = Constants.MENU_ICON_SVG;
if (currentId == null) {
buttons.push(
<button
......@@ -688,9 +697,11 @@ export default class Navbar extends React.Component {
defaultMessage='Toggle sidebar'
/>
</span>
<span className='icon-bar'/>
<span className='icon-bar'/>
<span className='icon-bar'/>
<span
className='icon icon__menu'
dangerouslySetInnerHTML={{__html: menuIcon}}
aria-hidden='true'
/>
<NotifyCounts/>
</button>
);
......@@ -699,7 +710,7 @@ export default class Navbar extends React.Component {
<button
key='navbar-toggle-menu'
type='button'
className='navbar-toggle menu-toggle pull-right'
className='navbar-toggle navbar-right__icon menu-toggle pull-right'
data-toggle='collapse'
data-target='#sidebar-nav'
onClick={this.toggleRightSidebar}
......@@ -917,13 +928,18 @@ export default class Navbar extends React.Component {
var collapseButtons = this.createCollapseButtons(currentId);
const searchIcon = Constants.SEARCH_ICON_SVG;
const searchButton = (
<button
type='button'
className='navbar-toggle navbar-search pull-right'
className='navbar-toggle navbar-right__icon navbar-search pull-right'
onClick={this.showSearch}
>
<span className='fa fa-search icon-search icon--white'/>
<span
className='icon icon__search'
dangerouslySetInnerHTML={{__html: searchIcon}}
aria-hidden='true'
/>
</button>
);
......
......@@ -21,7 +21,7 @@ import {canManageMembers} from 'utils/channel_utils.jsx';
import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import {Popover, Overlay} from 'react-bootstrap';
import {Tooltip, OverlayTrigger, Popover, Overlay} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
......@@ -94,6 +94,7 @@ export default class PopoverListMembers extends React.Component {
const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
const isTeamAdmin = TeamStore.isTeamAdminForCurrentTeam();
const isChannelAdmin = ChannelStore.isChannelAdminForCurrentChannel();
const membersIcon = Constants.MEMBERS_ICON_SVG;
if (members && teamMembers) {
members.sort((a, b) => {
......@@ -104,20 +105,9 @@ export default class PopoverListMembers extends React.Component {
});
members.forEach((m, i) => {
let button = '';
let messageIcon = '';
if (currentUserId !== m.id && this.props.channel.type !== Constants.DM_CHANNEl) {
button = (
<a
href='#'
className='btn-message'
onClick={(e) => this.handleShowDirectChannel(m, e)}
>
<FormattedMessage
id='members_popover.msg'
defaultMessage='Message'
/>
</a>
);
messageIcon = Constants.MESSAGE_ICON_SVG;
}
let name = '';
......@@ -129,12 +119,13 @@ export default class PopoverListMembers extends React.Component {
popoverHtml.push(
<div
className='more-modal__row'
onClick={(e) => this.handleShowDirectChannel(m, e)}
key={'popover-member-' + i}
>
<ProfilePicture
src={Client4.getProfilePictureUrl(m.id, m.last_picture_update)}
width='26'
height='26'
width='32'
height='32'
/>
<div className='more-modal__details'>
<div
......@@ -146,7 +137,11 @@ export default class PopoverListMembers extends React.Component {
<div
className='more-modal__actions'
>
{button}
<span
className='icon icon__message'
dangerouslySetInnerHTML={{__html: messageIcon}}
aria-hidden='true'
/>
</div>
</div>
);
......@@ -175,21 +170,15 @@ export default class PopoverListMembers extends React.Component {
popoverHtml.push(
<div
className='more-modal__row'
className='more-modal__row more-modal__row--button'
key={'popover-member-more'}
>
<div className='more-modal__details text-center'>
<div
className='more-modal__name'
>
<a
href='#'
onClick={this.showMembersModal}
>
{membersName}
</a>
</div>
</div>
<button
className='btn btn-primary'
onClick={this.showMembersModal}
>
{membersName}
</button>
</div>
);
}
......@@ -239,23 +228,40 @@ export default class PopoverListMembers extends React.Component {
);
}
const channelMembersTooltip = (
<Tooltip id='channelMembersTooltip'>
<FormattedMessage
id='channel_header.channelMembers'
defaultMessage='Members'
/>
</Tooltip>
);
return (
<div className='member-popover__container'>
<div
id='member_popover'
className='member-popover__trigger'
ref='member_popover_target'
onClick={(e) => {
this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover});
this.props.actions.getProfilesInChannel(this.props.channel.id, 0);
}}
<div className='channel-header__icon wide'>
<OverlayTrigger
trigger={['hover', 'focus']}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={channelMembersTooltip}
>
{countText}
<span
className='fa fa-user'
aria-hidden='true'
/>
</div>
<div
id='member_popover'
className='member-popover__trigger'
ref='member_popover_target'
onClick={(e) => {
this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover});
this.props.actions.getProfilesInChannel(this.props.channel.id, 0);
}}
>
<span className='icon__text'>{countText}</span>
<span
className='icon icon__members'