MM-51798: Fix Boards+Playbooks channel header RHS button styling (#23276)
* fix playbook icon * fix boards icon * lint * fix test
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
6905df9f97
Коммит
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
|
// * Verify tooltip text
|
||||||
cy.get('#pluginTooltip').contains('Playbooks');
|
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', () => {
|
describe('description text', () => {
|
||||||
|
|||||||
36
webapp/boards/src/components/rhsChannelBoardsToggleIcon.tsx
Обычный файл
36
webapp/boards/src/components/rhsChannelBoardsToggleIcon.tsx
Обычный файл
@@ -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 CreateBoardFromTemplate from 'src/components/createBoardFromTemplate'
|
||||||
|
|
||||||
import CloudUpgradeNudge from './components/cloudUpgradeNudge/cloudUpgradeNudge'
|
import CloudUpgradeNudge from './components/cloudUpgradeNudge/cloudUpgradeNudge'
|
||||||
|
import RhsChannelBoardsToggle from './components/rhsChannelBoardsToggleIcon'
|
||||||
|
|
||||||
function getSubpath(siteURL: string): string {
|
function getSubpath(siteURL: string): string {
|
||||||
const url = new URL(siteURL)
|
const url = new URL(siteURL)
|
||||||
@@ -365,10 +366,15 @@ export default class Plugin {
|
|||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
|
|
||||||
const {rhsId, toggleRHSPlugin} = this.registry.registerRightHandSidebarComponent(component, title)
|
const {id, toggleRHSPlugin} = this.registry.registerRightHandSidebarComponent(component, title)
|
||||||
this.rhsId = rhsId
|
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(
|
this.registry.registerProduct(
|
||||||
'/boards',
|
'/boards',
|
||||||
|
|||||||
@@ -8,10 +8,14 @@ type Props = {
|
|||||||
className?: string
|
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,
|
// All compass icon classes start with icon,
|
||||||
// so not expecting that prefix in props.
|
// so not expecting that prefix in props.
|
||||||
return (
|
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 './logo.scss'
|
||||||
import CompassIcon from './compassIcon'
|
import CompassIcon from './compassIcon'
|
||||||
|
|
||||||
export default function LogoIcon(): JSX.Element {
|
export default React.forwardRef<HTMLElement>((_, ref) => {
|
||||||
return (
|
return (
|
||||||
<CompassIcon
|
<CompassIcon
|
||||||
|
ref={ref}
|
||||||
|
data-testid='boardsIcon'
|
||||||
icon='product-boards'
|
icon='product-boards'
|
||||||
className='boards-rhs-icon'
|
className='boards-rhs-icon'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export type Ref = HTMLElement;
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
@@ -14,7 +12,7 @@ const Icon = styled.i`
|
|||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PlaybooksProductIcon = React.forwardRef<Ref, Props>((props: Props, forwardedRef) => (
|
const PlaybooksProductIcon = React.forwardRef<HTMLElement, Props>((props: Props, forwardedRef) => (
|
||||||
<Icon
|
<Icon
|
||||||
id={props?.id}
|
id={props?.id}
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
|
|||||||
@@ -1,27 +1,21 @@
|
|||||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See License for license information.
|
// See License for license information.
|
||||||
|
|
||||||
import React, {useRef} from 'react';
|
import React, {ElementRef, useRef} from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import {useSelector} from 'react-redux';
|
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';
|
import {isPlaybookRunRHSOpen} from 'src/selectors';
|
||||||
|
|
||||||
export const ChannelHeaderButton = () => {
|
export const ChannelHeaderButton = () => {
|
||||||
const myRef = useRef<PlaybookRunIconRef>(null);
|
const myRef = useRef<ElementRef<typeof PlaybooksProductIcon>>(null);
|
||||||
const isRHSOpen = useSelector(isPlaybookRunRHSOpen);
|
const isRHSOpen = useSelector(isPlaybookRunRHSOpen);
|
||||||
|
|
||||||
// If it has been mounted, we know our parent is always a button.
|
// If it has been mounted, we know our parent is always a button.
|
||||||
const parent = myRef?.current ? myRef?.current?.parentNode as HTMLButtonElement : null;
|
const parent = myRef?.current ? myRef?.current?.parentNode as HTMLButtonElement : null;
|
||||||
if (parent) {
|
parent?.classList.toggle('channel-header__icon--active-inverted', isRHSOpen);
|
||||||
if (isRHSOpen) {
|
|
||||||
parent.classList.add('channel-header__icon--active');
|
|
||||||
} else {
|
|
||||||
parent.classList.remove('channel-header__icon--active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlaybooksProductIcon
|
<PlaybooksProductIcon
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user