diff --git a/.betterer.results b/.betterer.results index 3e3882d44ee..de30c17fdc1 100644 --- a/.betterer.results +++ b/.betterer.results @@ -4568,10 +4568,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "4"], [0, 0, 0, "Styles should be written using objects.", "5"] ], - "public/app/features/live/LiveConnectionWarning.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], "public/app/features/live/centrifuge/LiveDataStream.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -4965,13 +4961,10 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/components/PluginDetailsPage.tsx:5381": [ [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], "public/app/features/plugins/admin/components/PluginDetailsSignature.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], diff --git a/packages/grafana-runtime/src/index.ts b/packages/grafana-runtime/src/index.ts index b7bf8246a93..c8b1723ef9d 100644 --- a/packages/grafana-runtime/src/index.ts +++ b/packages/grafana-runtime/src/index.ts @@ -51,5 +51,6 @@ export { } from './analytics/plugins/eventProperties'; export { usePluginInteractionReporter } from './analytics/plugins/usePluginInteractionReporter'; export { setReturnToPreviousHook, useReturnToPrevious } from './utils/returnToPrevious'; +export { setChromeHeaderHeightHook, useChromeHeaderHeight } from './utils/chromeHeaderHeight'; export { type EmbeddedDashboardProps, EmbeddedDashboard, setEmbeddedDashboard } from './components/EmbeddedDashboard'; export { hasPermission, hasPermissionInMetadata, hasAllPermissions, hasAnyPermission } from './utils/rbac'; diff --git a/packages/grafana-runtime/src/utils/chromeHeaderHeight.ts b/packages/grafana-runtime/src/utils/chromeHeaderHeight.ts new file mode 100644 index 00000000000..561c9fcb56e --- /dev/null +++ b/packages/grafana-runtime/src/utils/chromeHeaderHeight.ts @@ -0,0 +1,18 @@ +type ChromeHeaderHeightHook = () => number; + +let chromeHeaderHeightHook: ChromeHeaderHeightHook | undefined = undefined; + +export const setChromeHeaderHeightHook = (hook: ChromeHeaderHeightHook) => { + chromeHeaderHeightHook = hook; +}; + +export const useChromeHeaderHeight = () => { + if (!chromeHeaderHeightHook) { + if (process.env.NODE_ENV !== 'production') { + throw new Error('useChromeHeaderHeight hook not found in @grafana/runtime'); + } + console.error('useChromeHeaderHeight hook not found'); + } + + return chromeHeaderHeightHook?.(); +}; diff --git a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts index 59d68dd7b3c..bf7cdb6bf22 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts @@ -5,6 +5,9 @@ import { GrafanaTheme2, ThemeTypographyVariant } from '@grafana/data'; import { getFocusStyles } from '../mixins'; export function getElementStyles(theme: GrafanaTheme2) { + // TODO can we get the feature toggle in a better way? + const isBodyScrolling = window.grafanaBootData?.settings.featureToggles.bodyScrolling; + return css({ html: { MsOverflowStyle: 'scrollbar', @@ -23,10 +26,26 @@ export function getElementStyles(theme: GrafanaTheme2) { body: { height: '100%', width: '100%', - position: 'absolute', + position: isBodyScrolling ? 'unset' : 'absolute', color: theme.colors.text.primary, backgroundColor: theme.colors.background.canvas, - ...getVariantStyles(theme.typography.body), + ...theme.typography.body, + ...(isBodyScrolling && { + // react select tries prevent scrolling by setting overflow/padding-right on the body + // Need type assertion here due to the use of !important + // see https://github.com/frenic/csstype/issues/114#issuecomment-697201978 + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + overflowY: 'scroll !important' as 'scroll', + paddingRight: '0 !important', + '@media print': { + overflow: 'visible', + }, + '@page': { + margin: 0, + size: 'auto', + padding: 0, + }, + }), }, 'h1, .h1': getVariantStyles(theme.typography.h1), diff --git a/packages/grafana-ui/src/themes/GlobalStyles/page.ts b/packages/grafana-ui/src/themes/GlobalStyles/page.ts index ba0446ab82b..8c8c3e0449e 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/page.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/page.ts @@ -5,27 +5,42 @@ import { GrafanaTheme2 } from '@grafana/data'; export function getPageStyles(theme: GrafanaTheme2) { const maxWidthBreakpoint = theme.breakpoints.values.xxl + theme.spacing.gridSize * 2 + theme.components.sidemenu.width; + const isBodyScrolling = window.grafanaBootData?.settings.featureToggles.bodyScrolling; return css({ - '.grafana-app': { - display: 'flex', - alignItems: 'stretch', - position: 'absolute', - width: '100%', - height: '100%', - top: 0, - left: 0, - }, + '.grafana-app': isBodyScrolling + ? { + display: 'flex', + flexDirection: 'column', + minHeight: '100svh', + } + : { + display: 'flex', + alignItems: 'stretch', + position: 'absolute', + width: '100%', + height: '100%', + top: 0, + left: 0, + }, - '.main-view': { - position: 'relative', - display: 'flex', - flexDirection: 'column', - flexGrow: 1, - height: '100%', - flex: '1 1 0', - minWidth: 0, - }, + '.main-view': isBodyScrolling + ? { + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + position: 'relative', + minWidth: 0, + } + : { + position: 'relative', + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + height: '100%', + flex: '1 1 0', + minWidth: 0, + }, '.page-scrollbar-content': { display: 'flex', diff --git a/public/app/app.ts b/public/app/app.ts index 89460a9fc03..a6d2f7512a8 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -39,6 +39,7 @@ import { setPluginExtensionsHook, setPluginComponentHook, setCurrentUser, + setChromeHeaderHeightHook, } from '@grafana/runtime'; import { setPanelDataErrorView } from '@grafana/runtime/src/components/PanelDataErrorView'; import { setPanelRenderer } from '@grafana/runtime/src/components/PanelRenderer'; @@ -54,7 +55,7 @@ import appEvents from './core/app_events'; import { AppChromeService } from './core/components/AppChrome/AppChromeService'; import { getAllOptionEditors, getAllStandardFieldConfigs } from './core/components/OptionsUI/registry'; import { PluginPage } from './core/components/Page/PluginPage'; -import { GrafanaContextType, useReturnToPreviousInternal } from './core/context/GrafanaContext'; +import { GrafanaContextType, useChromeHeaderHeight, useReturnToPreviousInternal } from './core/context/GrafanaContext'; import { initIconCache } from './core/icons/iconBundle'; import { initializeI18n } from './core/internationalization'; import { setMonacoEnv } from './core/monacoEnv'; @@ -255,6 +256,7 @@ export class GrafanaApp { }; setReturnToPreviousHook(useReturnToPreviousInternal); + setChromeHeaderHeightHook(useChromeHeaderHeight); const root = createRoot(document.getElementById('reactRoot')!); root.render( diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index 09c07cfb945..3a76c66f005 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { PropsWithChildren, useEffect } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { locationSearchToObject, locationService } from '@grafana/runtime'; +import { config, locationSearchToObject, locationService } from '@grafana/runtime'; import { useStyles2, LinkButton, useTheme2 } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange'; @@ -26,10 +26,11 @@ export function AppChrome({ children }: Props) { const state = chrome.useState(); const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV; const theme = useTheme2(); - const styles = useStyles2(getStyles); + const styles = useStyles2(getStyles, searchBarHidden); const dockedMenuBreakpoint = theme.breakpoints.values.xl; const dockedMenuLocalStorageState = store.getBool(DOCKED_LOCAL_STORAGE_KEY, true); + const menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen; useMediaQueryChange({ breakpoint: dockedMenuBreakpoint, onChange: (e) => { @@ -102,10 +103,15 @@ export function AppChrome({ children }: Props) { )}
- {!state.chromeless && state.megaMenuDocked && state.megaMenuOpen && ( + {menuDockedAndOpen && ( chrome.setMegaMenuOpen(false)} /> )} -
+
{children}
@@ -119,14 +125,14 @@ export function AppChrome({ children }: Props) { ); } -const getStyles = (theme: GrafanaTheme2) => { +const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean) => { return { content: css({ display: 'flex', flexDirection: 'column', paddingTop: TOP_BAR_LEVEL_HEIGHT * 2, flexGrow: 1, - height: '100%', + height: config.featureToggles.bodyScrolling ? 'auto' : '100%', }), contentNoSearchBar: css({ paddingTop: TOP_BAR_LEVEL_HEIGHT, @@ -134,16 +140,31 @@ const getStyles = (theme: GrafanaTheme2) => { contentChromeless: css({ paddingTop: 0, }), - dockedMegaMenu: css({ - background: theme.colors.background.primary, - borderRight: `1px solid ${theme.colors.border.weak}`, - display: 'none', - zIndex: theme.zIndex.navbarFixed, + dockedMegaMenu: css( + config.featureToggles.bodyScrolling + ? { + background: theme.colors.background.primary, + borderRight: `1px solid ${theme.colors.border.weak}`, + display: 'none', + position: 'fixed', + height: `calc(100% - ${searchBarHidden ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2}px)`, + zIndex: theme.zIndex.navbarFixed, - [theme.breakpoints.up('xl')]: { - display: 'block', - }, - }), + [theme.breakpoints.up('xl')]: { + display: 'block', + }, + } + : { + background: theme.colors.background.primary, + borderRight: `1px solid ${theme.colors.border.weak}`, + display: 'none', + zIndex: theme.zIndex.navbarFixed, + + [theme.breakpoints.up('xl')]: { + display: 'block', + }, + } + ), topNav: css({ display: 'flex', position: 'fixed', @@ -153,37 +174,58 @@ const getStyles = (theme: GrafanaTheme2) => { background: theme.colors.background.primary, flexDirection: 'column', }), - panes: css({ - label: 'page-panes', - display: 'flex', - height: '100%', - width: '100%', - flexGrow: 1, - minHeight: 0, - flexDirection: 'column', - [theme.breakpoints.up('md')]: { - flexDirection: 'row', - }, - }), - pageContainer: css({ - label: 'page-container', - display: 'flex', - flexDirection: 'column', - flexGrow: 1, - minHeight: 0, - minWidth: 0, - overflow: 'auto', - '@media print': { - overflow: 'visible', - }, - '@page': { - margin: 0, - size: 'auto', - padding: 0, - }, + panes: css( + config.featureToggles.bodyScrolling + ? { + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + label: 'page-panes', + } + : { + label: 'page-panes', + display: 'flex', + height: '100%', + width: '100%', + flexGrow: 1, + minHeight: 0, + flexDirection: 'column', + [theme.breakpoints.up('md')]: { + flexDirection: 'row', + }, + } + ), + pageContainerMenuDocked: css({ + paddingLeft: '300px', }), + pageContainer: css( + config.featureToggles.bodyScrolling + ? { + label: 'page-container', + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + } + : { + label: 'page-container', + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + minHeight: 0, + minWidth: 0, + overflow: 'auto', + '@media print': { + overflow: 'visible', + }, + '@page': { + margin: 0, + size: 'auto', + padding: 0, + }, + } + ), skipLink: css({ - position: 'absolute', + position: 'fixed', top: -1000, ':focus': { diff --git a/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx b/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx index 4e0476fdc94..0cd67650137 100644 --- a/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx @@ -11,6 +11,7 @@ jest.mock('@grafana/runtime', () => ({ post: postMock, }), config: { + ...jest.requireActual('@grafana/runtime').config, loginError: false, buildInfo: { version: 'v1.0', diff --git a/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx b/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx index 6ddd6b56cab..85500867968 100644 --- a/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx +++ b/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx @@ -9,6 +9,7 @@ jest.mock('@grafana/runtime', () => ({ post: postMock, }), config: { + ...jest.requireActual('@grafana/runtime').config, buildInfo: { version: 'v1.0', commit: '1', diff --git a/public/app/core/components/Login/LoginLayout.tsx b/public/app/core/components/Login/LoginLayout.tsx index 4a65789c6d7..9ad2737b3e1 100644 --- a/public/app/core/components/Login/LoginLayout.tsx +++ b/public/app/core/components/Login/LoginLayout.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'; import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; import { Branding } from '../Branding/Branding'; @@ -36,7 +37,9 @@ export const LoginLayout = ({ children, branding, isChangingPassword }: React.Pr return (
@@ -93,6 +96,9 @@ export const getLoginStyles = (theme: GrafanaTheme2) => { alignItems: 'center', justifyContent: 'center', }), + containerBodyScrolling: css({ + flex: 1, + }), loginAnim: css({ ['&:before']: { opacity: 1, diff --git a/public/app/core/components/Login/LoginPage.test.tsx b/public/app/core/components/Login/LoginPage.test.tsx index 6301119c9d6..4d11ead0435 100644 --- a/public/app/core/components/Login/LoginPage.test.tsx +++ b/public/app/core/components/Login/LoginPage.test.tsx @@ -13,6 +13,7 @@ jest.mock('@grafana/runtime', () => ({ post: postMock, }), config: { + ...jest.requireActual('@grafana/runtime').config, auth: { disableLogin: false, }, diff --git a/public/app/core/components/NativeScrollbar.tsx b/public/app/core/components/NativeScrollbar.tsx index 3b60c1df7bc..d0eb36adab7 100644 --- a/public/app/core/components/NativeScrollbar.tsx +++ b/public/app/core/components/NativeScrollbar.tsx @@ -1,28 +1,33 @@ import { css, cx } from '@emotion/css'; import { useEffect, useRef } from 'react'; +import { config } from '@grafana/runtime'; import { CustomScrollbar, useStyles2 } from '@grafana/ui'; type Props = Parameters[0]; // Shim to provide API-compatibility for Page's scroll-related props +// when bodyScrolling is enabled, this is a no-op +// TODO remove this shim completely when bodyScrolling is enabled export default function NativeScrollbar({ children, scrollRefCallback, scrollTop, divId }: Props) { const styles = useStyles2(getStyles); const ref = useRef(null); useEffect(() => { - if (ref.current && scrollRefCallback) { + if (!config.featureToggles.bodyScrolling && ref.current && scrollRefCallback) { scrollRefCallback(ref.current); } }, [ref, scrollRefCallback]); useEffect(() => { - if (ref.current && scrollTop != null) { + if (!config.featureToggles.bodyScrolling && ref.current && scrollTop != null) { ref.current?.scrollTo(0, scrollTop); } }, [scrollTop]); - return ( + return config.featureToggles.bodyScrolling ? ( + children + ) : ( // Set the .scrollbar-view class to help e2e tests find this, like in CustomScrollbar
{children} diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx index 4ca28917377..9056a7dfca5 100644 --- a/public/app/core/components/Page/Page.tsx +++ b/public/app/core/components/Page/Page.tsx @@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css'; import { useLayoutEffect } from 'react'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; @@ -98,14 +99,24 @@ Page.Contents = PageContents; const getStyles = (theme: GrafanaTheme2) => { return { - wrapper: css({ - label: 'page-wrapper', - height: '100%', - display: 'flex', - flex: '1 1 0', - flexDirection: 'column', - minHeight: 0, - }), + wrapper: css( + config.featureToggles.bodyScrolling + ? { + label: 'page-wrapper', + display: 'flex', + flex: '1 1 0', + flexDirection: 'column', + position: 'relative', + } + : { + label: 'page-wrapper', + height: '100%', + display: 'flex', + flex: '1 1 0', + flexDirection: 'column', + minHeight: 0, + } + ), pageContent: css({ label: 'page-content', flexGrow: 1, diff --git a/public/app/core/components/Page/types.ts b/public/app/core/components/Page/types.ts index 6c4e1294e34..f1df40ab280 100644 --- a/public/app/core/components/Page/types.ts +++ b/public/app/core/components/Page/types.ts @@ -22,14 +22,14 @@ export interface PageProps extends HTMLAttributes { /** Control the page layout. */ layout?: PageLayoutType; /** + * @deprecated this will be removed when bodyScrolling is enabled by default * Can be used to get the scroll container element to access scroll position * */ - // Probably will deprecate this in the future in favor of just scrolling document.body directly scrollRef?: RefCallback; /** + * @deprecated this will be removed when bodyScrolling is enabled by default * Can be used to update the current scroll position * */ - // Probably will deprecate this in the future in favor of just scrolling document.body directly scrollTop?: number; } diff --git a/public/app/core/context/GrafanaContext.ts b/public/app/core/context/GrafanaContext.ts index 1f3bf86372b..b3920f47969 100644 --- a/public/app/core/context/GrafanaContext.ts +++ b/public/app/core/context/GrafanaContext.ts @@ -1,7 +1,7 @@ import { createContext, useCallback, useContext } from 'react'; import { GrafanaConfig } from '@grafana/data'; -import { LocationService, locationService, BackendSrv } from '@grafana/runtime'; +import { LocationService, locationService, BackendSrv, config } from '@grafana/runtime'; import { AppChromeService } from '../components/AppChrome/AppChromeService'; import { NewFrontendAssetsChecker } from '../services/NewFrontendAssetsChecker'; @@ -41,3 +41,18 @@ export function useReturnToPreviousInternal() { [chrome] ); } + +const SINGLE_HEADER_BAR_HEIGHT = 40; + +export function useChromeHeaderHeight() { + const { chrome } = useGrafana(); + const { kioskMode, searchBarHidden, chromeless } = chrome.useState(); + + if (kioskMode || chromeless || !config.featureToggles.bodyScrolling) { + return 0; + } else if (searchBarHidden) { + return SINGLE_HEADER_BAR_HEIGHT; + } else { + return SINGLE_HEADER_BAR_HEIGHT * 2; + } +} diff --git a/public/app/core/navigation/GrafanaRouteLoading.tsx b/public/app/core/navigation/GrafanaRouteLoading.tsx index 5ca45723fee..374087a3958 100644 --- a/public/app/core/navigation/GrafanaRouteLoading.tsx +++ b/public/app/core/navigation/GrafanaRouteLoading.tsx @@ -1,6 +1,7 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; import { BouncingLoader } from '../components/BouncingLoader/BouncingLoader'; @@ -9,7 +10,12 @@ export function GrafanaRouteLoading() { const styles = useStyles2(getStyles); return ( -
+
); @@ -24,4 +30,12 @@ const getStyles = (theme: GrafanaTheme2) => ({ justifyContent: 'center', alignItems: 'center', }), + loadingPageBodyScrolling: css({ + backgroundColor: theme.colors.background.primary, + flex: 1, + flexDrection: 'column', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }), }); diff --git a/public/app/features/connections/Connections.test.tsx b/public/app/features/connections/Connections.test.tsx index ad70acaa15c..1d752fda505 100644 --- a/public/app/features/connections/Connections.test.tsx +++ b/public/app/features/connections/Connections.test.tsx @@ -15,6 +15,10 @@ import { ROUTE_BASE_ID, ROUTES } from './constants'; jest.mock('app/core/services/context_srv'); jest.mock('app/features/datasources/api'); +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + useChromeHeaderHeight: jest.fn(), +})); const renderPage = ( path = `/${ROUTE_BASE_ID}`, diff --git a/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx b/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx index 78ac0623cc0..027831a24ed 100644 --- a/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx +++ b/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx @@ -12,6 +12,11 @@ import { AddNewConnection } from './ConnectData'; jest.mock('app/features/datasources/api'); +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + useChromeHeaderHeight: jest.fn(), +})); + const renderPage = (plugins: CatalogPlugin[] = []): RenderResult => { return render( diff --git a/public/app/features/connections/tabs/ConnectData/Search/Search.tsx b/public/app/features/connections/tabs/ConnectData/Search/Search.tsx index 6cd79c7f9f3..75508adffeb 100644 --- a/public/app/features/connections/tabs/ConnectData/Search/Search.tsx +++ b/public/app/features/connections/tabs/ConnectData/Search/Search.tsx @@ -2,16 +2,17 @@ import { css } from '@emotion/css'; import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; +import { config, useChromeHeaderHeight } from '@grafana/runtime'; import { Icon, Input, useStyles2 } from '@grafana/ui'; import { t } from 'app/core/internationalization'; -const getStyles = (theme: GrafanaTheme2) => ({ +const getStyles = (theme: GrafanaTheme2, headerHeight: number) => ({ searchContainer: css({ display: 'flex', justifyContent: 'space-between', position: 'sticky', - top: 0, + top: headerHeight, backgroundColor: theme.colors.background.primary, zIndex: 2, padding: theme.spacing(2, 0), @@ -26,7 +27,8 @@ export interface Props { } export const Search = ({ onChange, value }: Props) => { - const styles = useStyles2(getStyles); + const chromeHeaderHeight = useChromeHeaderHeight(); + const styles = useStyles2(getStyles, config.featureToggles.bodyScrolling ? chromeHeaderHeight ?? 0 : 0); return (
diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx index c7c4fbdae59..785a1f36f7b 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx @@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { config } from '@grafana/runtime'; import { SceneComponentProps } from '@grafana/scenes'; import { Button, ToolbarButton, useStyles2 } from '@grafana/ui'; @@ -32,7 +33,13 @@ export function PanelEditorRenderer({ model }: SceneComponentProps) return ( <> -
+
@@ -176,6 +183,11 @@ function getStyles(theme: GrafanaTheme2) { minHeight: 0, width: '100%', }), + content: css({ + position: 'absolute', + width: '100%', + height: '100%', + }), body: css({ label: 'body', flexGrow: 1, diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index b2902fdad03..27e356bb9fa 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -68,6 +68,7 @@ export type Props = Themeable2 & export interface State { editPanel: PanelModel | null; viewPanel: PanelModel | null; + editView: string | null; updateScrollTop?: number; rememberScrollTop?: number; showLoadingState: boolean; @@ -87,6 +88,7 @@ export class UnthemedDashboardPage extends PureComponent { getCleanState(): State { return { + editView: null, editPanel: null, viewPanel: null, showLoadingState: false, @@ -194,6 +196,13 @@ export class UnthemedDashboardPage extends PureComponent { this.props.notifyApp(createErrorNotification(`Panel not found`)); locationService.partial({ editPanel: null, viewPanel: null }); } + + if (config.featureToggles.bodyScrolling) { + // Update window scroll position + if (this.state.updateScrollTop !== undefined && this.state.updateScrollTop !== prevState.updateScrollTop) { + window.scrollTo(0, this.state.updateScrollTop); + } + } } updateLiveTimer = () => { @@ -209,6 +218,7 @@ export class UnthemedDashboardPage extends PureComponent { const urlEditPanelId = queryParams.editPanel; const urlViewPanelId = queryParams.viewPanel; + const urlEditView = queryParams.editview; if (!dashboard) { return state; @@ -216,13 +226,33 @@ export class UnthemedDashboardPage extends PureComponent { const updatedState = { ...state }; + if (config.featureToggles.bodyScrolling) { + // Entering settings view + if (!state.editView && urlEditView) { + updatedState.editView = urlEditView; + updatedState.rememberScrollTop = window.scrollY; + updatedState.updateScrollTop = 0; + } + + // Leaving settings view + else if (state.editView && !urlEditView) { + updatedState.updateScrollTop = state.rememberScrollTop; + updatedState.editView = null; + } + } + // Entering edit mode if (!state.editPanel && urlEditPanelId) { const panel = dashboard.getPanelByUrlId(urlEditPanelId); if (panel) { if (dashboard.canEditPanel(panel)) { updatedState.editPanel = panel; - updatedState.rememberScrollTop = state.scrollElement?.scrollTop; + updatedState.rememberScrollTop = config.featureToggles.bodyScrolling + ? window.scrollY + : state.scrollElement?.scrollTop; + if (config.featureToggles.bodyScrolling) { + updatedState.updateScrollTop = 0; + } } else { updatedState.editPanelAccessDenied = true; } @@ -244,7 +274,9 @@ export class UnthemedDashboardPage extends PureComponent { // Should move this state out of dashboard in the future dashboard.initViewPanel(panel); updatedState.viewPanel = panel; - updatedState.rememberScrollTop = state.scrollElement?.scrollTop; + updatedState.rememberScrollTop = config.featureToggles.bodyScrolling + ? window.scrollY + : state.scrollElement?.scrollTop; updatedState.updateScrollTop = 0; } else { updatedState.panelNotFound = true; diff --git a/public/app/features/live/LiveConnectionWarning.tsx b/public/app/features/live/LiveConnectionWarning.tsx index 67e2e3a3b0e..fcc9f4f7511 100644 --- a/public/app/features/live/LiveConnectionWarning.tsx +++ b/public/app/features/live/LiveConnectionWarning.tsx @@ -62,18 +62,18 @@ export class LiveConnectionWarning extends PureComponent { const getStyle = stylesFactory((theme: GrafanaTheme2) => { return { - foot: css` - position: absolute; - bottom: 0px; - left: 0px; - right: 0px; - z-index: 10000; - cursor: wait; - margin: 16px; - `, - warn: css` - max-width: 400px; - margin: auto; - `, + foot: css({ + position: 'fixed', + bottom: 0, + left: 0, + right: 0, + zIndex: 10000, + cursor: 'wait', + margin: theme.spacing(2), + }), + warn: css({ + maxWidth: '400px', + margin: 'auto', + }), }; }); diff --git a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx index 932b3dc875a..1c31ffb7b15 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx @@ -131,9 +131,11 @@ export function PluginDetailsBody({ plugin, queryParams, pageId }: Props): JSX.E } export const getStyles = (theme: GrafanaTheme2) => ({ - container: css({ - height: '100%', - }), + container: config.featureToggles.bodyScrolling + ? css({}) + : css({ + height: '100%', + }), readme: css({ '& img': { maxWidth: '100%', diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx index 1e33c01ebec..3eabac1e295 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx @@ -99,20 +99,24 @@ export function PluginDetailsPage({ export const getStyles = (theme: GrafanaTheme2) => { return { - alert: css` - margin-bottom: ${theme.spacing(2)}; - `, - subtitle: css` - display: flex; - flex-direction: column; - gap: ${theme.spacing(1)}; - `, + alert: css({ + marginBottom: theme.spacing(2), + }), + subtitle: css({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + }), // Needed due to block formatting context - tabContent: css` - overflow: auto; - height: 100%; - padding-left: 5px; - `, + tabContent: config.featureToggles.bodyScrolling + ? css({ + paddingLeft: '5px', + }) + : css({ + overflow: 'auto', + height: '100%', + paddingLeft: '5px', + }), }; }; diff --git a/public/app/features/trails/DataTrail.tsx b/public/app/features/trails/DataTrail.tsx index d8b243a109b..2168b2682f8 100644 --- a/public/app/features/trails/DataTrail.tsx +++ b/public/app/features/trails/DataTrail.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { AdHocVariableFilter, GrafanaTheme2, PageLayoutType, VariableHide, urlUtil } from '@grafana/data'; -import { locationService } from '@grafana/runtime'; +import { locationService, useChromeHeaderHeight } from '@grafana/runtime'; import { AdHocFiltersVariable, DataSourceVariable, @@ -212,7 +212,8 @@ export class DataTrail extends SceneObjectBase { static Component = ({ model }: SceneComponentProps) => { const { controls, topScene, history, settings, metric } = model.useState(); - const styles = useStyles2(getStyles); + const chromeHeaderHeight = useChromeHeaderHeight(); + const styles = useStyles2(getStyles, chromeHeaderHeight ?? 0); const showHeaderForFirstTimeUsers = getTrailStore().recent.length < 2; return ( @@ -266,7 +267,7 @@ function getVariableSet(initialDS?: string, metric?: string, initialFilters?: Ad }); } -function getStyles(theme: GrafanaTheme2) { +function getStyles(theme: GrafanaTheme2, chromeHeaderHeight: number) { return { container: css({ flexGrow: 1, @@ -290,7 +291,7 @@ function getStyles(theme: GrafanaTheme2) { position: 'sticky', background: theme.isDark ? theme.colors.background.canvas : theme.colors.background.primary, zIndex: theme.zIndex.navbarFixed, - top: 0, + top: chromeHeaderHeight, }), }; }