mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
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:
parent
6905df9f97
commit
f815c20fca
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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', () => {
|
||||
|
36
webapp/boards/src/components/rhsChannelBoardsToggleIcon.tsx
Normal file
36
webapp/boards/src/components/rhsChannelBoardsToggleIcon.tsx
Normal 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
|
@ -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',
|
||||
|
@ -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}`}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
@ -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'
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user