mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
MM-69131: Keep app__body off backstage routes to fix dark-theme styling (#36928)
* MM-69131: Keep app__body off backstage routes to fix dark-theme styling Centralizing app__body ownership on WithUserTheme (MM-67913) made the class persist on backstage routes (integrations, custom emoji), which render a static light surface. Under dark themes this leaked themed colors in, making text and form inputs illegible. useAppBodyClass now takes the current pathname and omits app__body on backstage routes via isBackstageRoute, while keeping it persisted across channels, products, and plugins so the original white-flash fix still holds. * MM-69131: Address theme provider review feedback Keep backstage route detection local to the theme provider and let the app body class hook read routing context directly. --------- Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Hanzo Dev
parent
91669295da
commit
bcd703977f
@@ -1,9 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createMemoryHistory} from 'history';
|
||||
import React from 'react';
|
||||
import {Router} from 'react-router-dom';
|
||||
|
||||
import {render, renderHook} from 'tests/react_testing_utils';
|
||||
import {act, render, renderHook} from 'tests/react_testing_utils';
|
||||
|
||||
import {useAppBodyClass, WithUserTheme} from './theme_context';
|
||||
|
||||
@@ -12,8 +14,30 @@ describe('useAppBodyClass', () => {
|
||||
document.body.classList.remove('app__body');
|
||||
});
|
||||
|
||||
it('adds app__body to the document body while mounted', () => {
|
||||
const {unmount} = renderHook(() => useAppBodyClass());
|
||||
function renderUseAppBodyClass(pathname: string) {
|
||||
const history = createMemoryHistory({initialEntries: [pathname]});
|
||||
const view = renderHook(() => useAppBodyClass(), {
|
||||
wrapper: ({children}) => (
|
||||
<Router history={history}>
|
||||
{children}
|
||||
</Router>
|
||||
),
|
||||
});
|
||||
|
||||
return {...view, history};
|
||||
}
|
||||
|
||||
it.each([
|
||||
'/myteam/channels/town-square',
|
||||
'/myteam/messages/@someone',
|
||||
'/myteam/channels/integrations',
|
||||
'/admin_console/integrations/bot_accounts',
|
||||
'/create_team',
|
||||
'/select_team',
|
||||
'/plug/some-plugin',
|
||||
'/',
|
||||
])('adds app__body to the document body for themed route %s while mounted', (pathname) => {
|
||||
const {unmount} = renderUseAppBodyClass(pathname);
|
||||
|
||||
expect(document.body.classList.contains('app__body')).toBe(true);
|
||||
|
||||
@@ -21,6 +45,36 @@ describe('useAppBodyClass', () => {
|
||||
|
||||
expect(document.body.classList.contains('app__body')).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'/myteam/integrations',
|
||||
'/myteam/integrations/',
|
||||
'/myteam/integrations/bots',
|
||||
'/myteam/integrations/incoming_webhooks',
|
||||
'/myteam/emoji',
|
||||
'/myteam/emoji/add',
|
||||
'/my-team_1/integrations',
|
||||
])('does not add app__body on backstage route %s', (pathname) => {
|
||||
renderUseAppBodyClass(pathname);
|
||||
|
||||
expect(document.body.classList.contains('app__body')).toBe(false);
|
||||
});
|
||||
|
||||
it('toggles app__body as the route changes', () => {
|
||||
const {history} = renderUseAppBodyClass('/myteam/channels/town-square');
|
||||
|
||||
expect(document.body.classList.contains('app__body')).toBe(true);
|
||||
|
||||
act(() => {
|
||||
history.push('/myteam/integrations');
|
||||
});
|
||||
expect(document.body.classList.contains('app__body')).toBe(false);
|
||||
|
||||
act(() => {
|
||||
history.push('/myteam/channels/town-square');
|
||||
});
|
||||
expect(document.body.classList.contains('app__body')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WithUserTheme', () => {
|
||||
@@ -28,12 +82,20 @@ describe('WithUserTheme', () => {
|
||||
document.body.classList.remove('app__body');
|
||||
});
|
||||
|
||||
it('adds app__body to the document body while mounted', () => {
|
||||
const {unmount} = render(
|
||||
<WithUserTheme>
|
||||
<div>{'Themed content'}</div>
|
||||
</WithUserTheme>,
|
||||
function renderWithUserTheme(pathname: string) {
|
||||
const history = createMemoryHistory({initialEntries: [pathname]});
|
||||
|
||||
return render(
|
||||
<Router history={history}>
|
||||
<WithUserTheme>
|
||||
<div>{'Themed content'}</div>
|
||||
</WithUserTheme>
|
||||
</Router>,
|
||||
);
|
||||
}
|
||||
|
||||
it('adds app__body to the document body for a themed route while mounted', () => {
|
||||
const {unmount} = renderWithUserTheme('/myteam/channels/town-square');
|
||||
|
||||
expect(document.body.classList.contains('app__body')).toBe(true);
|
||||
|
||||
@@ -41,4 +103,10 @@ describe('WithUserTheme', () => {
|
||||
|
||||
expect(document.body.classList.contains('app__body')).toBe(false);
|
||||
});
|
||||
|
||||
it('does not add app__body on a backstage route', () => {
|
||||
renderWithUserTheme('/myteam/integrations');
|
||||
|
||||
expect(document.body.classList.contains('app__body')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,12 +2,22 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useContext, useEffect} from 'react';
|
||||
import {useLocation} from 'react-router-dom';
|
||||
|
||||
import {TEAM_NAME_PATH_PATTERN} from 'utils/path';
|
||||
|
||||
export const ThemeContext = React.createContext({
|
||||
startUsingUserTheme: () => {},
|
||||
stopUsingUserTheme: () => {},
|
||||
});
|
||||
|
||||
// Exclude System Console's integrations pages from team backstage routes.
|
||||
const BACKSTAGE_ROUTE_PATTERN = new RegExp(`^/(?!admin_console)${TEAM_NAME_PATH_PATTERN}/(?:integrations|emoji)(?:/|$)`);
|
||||
|
||||
function isBackstageRoute(pathname: string): boolean {
|
||||
return BACKSTAGE_ROUTE_PATTERN.test(pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* useUserTheme makes it so that the app will apply the user's theme instead of the default one for as long as the
|
||||
* calling component remains mounted.
|
||||
@@ -25,18 +35,25 @@ export function useUserTheme() {
|
||||
}
|
||||
|
||||
/**
|
||||
* useAppBodyClass manages the `app__body` class on the document body for as long as the calling component
|
||||
* remains mounted. That class is required for much of our CSS to apply the user's theme, so it should be used
|
||||
* wherever those should be used.
|
||||
* useAppBodyClass manages the `app__body` class on the document body for the current route while the calling
|
||||
* component remains mounted. That class is required for much of our CSS to apply the user's theme, so it is
|
||||
* omitted on backstage routes (e.g. integrations, custom emoji) that intentionally render a static light theme.
|
||||
*/
|
||||
export function useAppBodyClass() {
|
||||
const {pathname} = useLocation();
|
||||
const themed = !isBackstageRoute(pathname);
|
||||
|
||||
useEffect(() => {
|
||||
if (!themed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
document.body.classList.add('app__body');
|
||||
|
||||
return () => {
|
||||
document.body.classList.remove('app__body');
|
||||
};
|
||||
}, []);
|
||||
}, [themed]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user