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,
}),
};
}