MM-51798: Fix Boards+Playbooks channel header RHS button styling (#23276)

* fix playbook icon

* fix boards icon

* lint

* fix test
This commit is contained in:
Caleb Roseland 2023-05-09 03:39:34 -05:00 committed by GitHub
parent 6905df9f97
commit f815c20fca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 165 additions and 21 deletions

View File

@ -0,0 +1,90 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// ***************************************************************
// Stage: @prod
// Group: @boards
describe('channels > channel header', {testIsolation: true}, () => {
let testTeam: {name: string};
let testUser: {username: string};
before(() => {
cy.apiInitSetup().then(({team, user}) => {
testTeam = team;
testUser = user;
// # Login as testUser
cy.apiLogin(testUser);
});
});
describe('App Bar enabled', () => {
it('webapp should hide the Boards channel header button', () => {
cy.apiAdminLogin();
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: true}});
// # Login as testUser
cy.apiLogin(testUser);
// # Navigate directly to a channel
cy.visit(`/${testTeam.name}/channels/town-square`);
// * Verify channel header button is not showing
cy.get('#channel-header').within(() => {
cy.get('[data-testid="boardsIcon"]').should('not.exist');
});
});
});
describe('App Bar disabled', () => {
beforeEach(() => {
cy.apiAdminLogin();
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: false}});
// # Login as testUser
cy.apiLogin(testUser);
});
it('webapp should show the Boards channel header button', () => {
// # Navigate directly to a channel
cy.visit(`/${testTeam.name}/channels/town-square`);
// * Verify channel header button is showing
cy.get('#channel-header').within(() => {
cy.get('#incidentIcon').should('exist');
});
});
it('tooltip text should show "Boards" for Boards channel header button', () => {
// # Navigate directly to a channel
cy.visit(`/${testTeam.name}/channels/town-square`);
// # Hover over the channel header icon
cy.get('#channel-header').within(() => {
cy.get('[data-testid="boardsIcon"]').trigger('mouseover');
});
// * Verify tooltip text
cy.get('#pluginTooltip').contains('Boards');
});
it('webapp should make the Boards channel header button active when opened', () => {
// # Navigate directly to a channel
cy.visit(`/${testTeam.name}/channels/town-square`);
cy.get('#channel-header').within(() => {
// # Click the channel header button
cy.get('[data-testid="boardsIcon"]').as('icon').click();
// * Verify channel header button is showing active className
cy.get('@icon').parent().
should('have.class', 'channel-header__icon--active-inverted');
});
});
});
});

View File

@ -93,6 +93,20 @@ describe('channels > channel header', {testIsolation: true}, () => {
// * Verify tooltip text
cy.get('#pluginTooltip').contains('Playbooks');
});
it('webapp should make the Playbook channel header button active when opened', () => {
// # Navigate directly to a non-playbook run channel
cy.visit(`/${testTeam.name}/channels/town-square`);
cy.get('#channel-header').within(() => {
// # Click the channel header button
cy.get('#incidentIcon').click();
// * Verify channel header button is showing active className
cy.get('#incidentIcon').parent().
should('have.class', 'channel-header__icon--active-inverted');
});
});
});
describe('description text', () => {

View File

@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ElementRef, useRef} from 'react'
import {useSelector} from 'react-redux'
import {GlobalState} from '@mattermost/types/store'
import FocalboardIcon from 'src/widgets/icons/logo'
type Props = {
boardsRhsId: string
}
type ViewsState = {views: {rhs: {isSidebarOpen: boolean, pluggableId: string}, rhsSuppressed: boolean}}
const RhsChannelBoardsToggleIcon = ({boardsRhsId}: Props) => {
const iconRef = useRef<ElementRef<typeof FocalboardIcon>>(null)
const isOpen = useSelector(({views: {rhs, rhsSuppressed}}: GlobalState & ViewsState) => (
rhs.isSidebarOpen &&
!rhsSuppressed &&
rhs.pluggableId === boardsRhsId
))
// If it has been mounted, we know our parent is always a button.
const parent = iconRef?.current ? iconRef?.current?.parentNode as HTMLButtonElement : null
parent?.classList.toggle('channel-header__icon--active-inverted', isOpen)
return (
<FocalboardIcon
ref={iconRef}
/>
)
}
export default RhsChannelBoardsToggleIcon

View File

@ -74,6 +74,7 @@ import './plugin.scss'
import CreateBoardFromTemplate from 'src/components/createBoardFromTemplate'
import CloudUpgradeNudge from './components/cloudUpgradeNudge/cloudUpgradeNudge'
import RhsChannelBoardsToggle from './components/rhsChannelBoardsToggleIcon'
function getSubpath(siteURL: string): string {
const url = new URL(siteURL)
@ -365,10 +366,15 @@ export default class Plugin {
</ErrorBoundary>
)
const {rhsId, toggleRHSPlugin} = this.registry.registerRightHandSidebarComponent(component, title)
this.rhsId = rhsId
const {id, toggleRHSPlugin} = this.registry.registerRightHandSidebarComponent(component, title)
this.rhsId = id
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(<FocalboardIcon/>, () => mmStore.dispatch(toggleRHSPlugin), 'Boards', 'Boards')
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(
() => <RhsChannelBoardsToggle boardsRhsId={id}/>,
() => mmStore.dispatch(toggleRHSPlugin),
'Boards',
'Boards'
)
this.registry.registerProduct(
'/boards',

View File

@ -8,10 +8,14 @@ type Props = {
className?: string
}
export default function CompassIcon(props: Props): JSX.Element {
export default React.forwardRef<HTMLElement, Props>(({icon, className, ...props}, ref) => {
// All compass icon classes start with icon,
// so not expecting that prefix in props.
return (
<i className={`CompassIcon icon-${props.icon}${props.className === undefined ? '' : ` ${props.className}`}`}/>
<i
{...props}
ref={ref}
className={`CompassIcon icon-${icon}${className === undefined ? '' : ` ${className}`}`}
/>
)
}
})

View File

@ -6,11 +6,13 @@ import React from 'react'
import './logo.scss'
import CompassIcon from './compassIcon'
export default function LogoIcon(): JSX.Element {
export default React.forwardRef<HTMLElement>((_, ref) => {
return (
<CompassIcon
ref={ref}
data-testid='boardsIcon'
icon='product-boards'
className='boards-rhs-icon'
/>
)
}
})

View File

@ -4,8 +4,6 @@
import React from 'react';
import styled from 'styled-components';
export type Ref = HTMLElement;
interface Props {
id?: string;
}
@ -14,7 +12,7 @@ const Icon = styled.i`
font-size: 22px;
`;
const PlaybooksProductIcon = React.forwardRef<Ref, Props>((props: Props, forwardedRef) => (
const PlaybooksProductIcon = React.forwardRef<HTMLElement, Props>((props: Props, forwardedRef) => (
<Icon
id={props?.id}
ref={forwardedRef}

View File

@ -1,27 +1,21 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License for license information.
import React, {useRef} from 'react';
import React, {ElementRef, useRef} from 'react';
import {FormattedMessage} from 'react-intl';
import {useSelector} from 'react-redux';
import PlaybooksProductIcon, {Ref as PlaybookRunIconRef} from 'src/components/assets/icons/playbooks_product_icon';
import PlaybooksProductIcon from 'src/components/assets/icons/playbooks_product_icon';
import {isPlaybookRunRHSOpen} from 'src/selectors';
export const ChannelHeaderButton = () => {
const myRef = useRef<PlaybookRunIconRef>(null);
const myRef = useRef<ElementRef<typeof PlaybooksProductIcon>>(null);
const isRHSOpen = useSelector(isPlaybookRunRHSOpen);
// If it has been mounted, we know our parent is always a button.
const parent = myRef?.current ? myRef?.current?.parentNode as HTMLButtonElement : null;
if (parent) {
if (isRHSOpen) {
parent.classList.add('channel-header__icon--active');
} else {
parent.classList.remove('channel-header__icon--active');
}
}
parent?.classList.toggle('channel-header__icon--active-inverted', isRHSOpen);
return (
<PlaybooksProductIcon