{
+ if (isOpen) {
+ setDockedComponentId(undefined);
+ } else {
+ setDockedComponentId(getComponentIdFromComponentMeta(components[0].pluginId, components[0]));
+ }
+ }}
+ />
+
+ >
);
}
@@ -62,13 +67,16 @@ export function ExtensionToolbarItem() {
);
return (
-
-
-
+ <>
+
+
+
+
+ >
);
}
diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx
index d7045bb5960..3dedb1ea0d5 100644
--- a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx
+++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx
@@ -13,8 +13,6 @@ import { setBookmark } from 'app/core/reducers/navBarTree';
import { usePatchUserPreferencesMutation } from 'app/features/preferences/api/index';
import { useDispatch, useSelector } from 'app/types';
-import { TOP_BAR_LEVEL_HEIGHT } from '../types';
-
import { MegaMenuHeader } from './MegaMenuHeader';
import { MegaMenuItem } from './MegaMenuItem';
import { usePinnedItems } from './hooks';
@@ -140,8 +138,8 @@ const getStyles = (theme: GrafanaTheme2) => {
content: css({
display: 'flex',
flexDirection: 'column',
- height: `calc(100% - ${TOP_BAR_LEVEL_HEIGHT}px)`,
minHeight: 0,
+ flexGrow: 1,
position: 'relative',
}),
mobileHeader: css({
diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenuHeader.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenuHeader.tsx
index 92062891de7..91f476bdef1 100644
--- a/public/app/core/components/AppChrome/MegaMenu/MegaMenuHeader.tsx
+++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenuHeader.tsx
@@ -7,7 +7,7 @@ import { t } from 'app/core/internationalization';
import { Branding } from '../../Branding/Branding';
import { OrganizationSwitcher } from '../OrganizationSwitcher/OrganizationSwitcher';
-import { TOP_BAR_LEVEL_HEIGHT } from '../types';
+import { getChromeHeaderLevelHeight } from '../TopBar/useChromeHeaderHeight';
export interface Props {
handleMegaMenu: () => void;
@@ -78,8 +78,8 @@ const getStyles = (theme: GrafanaTheme2) => ({
gap: theme.spacing(1),
justifyContent: 'space-between',
padding: theme.spacing(0, 1, 0, 0.75),
- height: TOP_BAR_LEVEL_HEIGHT,
- minHeight: TOP_BAR_LEVEL_HEIGHT,
+ height: getChromeHeaderLevelHeight(),
+ flexShrink: 0,
}),
img: css({
alignSelf: 'center',
diff --git a/public/app/core/components/AppChrome/NavToolbar/NavToolbarSeparator.tsx b/public/app/core/components/AppChrome/NavToolbar/NavToolbarSeparator.tsx
index d2bb32e22cd..b9c65fc8c70 100644
--- a/public/app/core/components/AppChrome/NavToolbar/NavToolbarSeparator.tsx
+++ b/public/app/core/components/AppChrome/NavToolbar/NavToolbarSeparator.tsx
@@ -30,6 +30,9 @@ const getStyles = (theme: GrafanaTheme2) => {
height: 24,
flexShrink: 0,
flexGrow: 0,
+ [theme.breakpoints.down('sm')]: {
+ display: 'none',
+ },
}),
};
};
diff --git a/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx b/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx
index f1fa0a35902..a7e5e6ac5fe 100644
--- a/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx
+++ b/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx
@@ -1,10 +1,7 @@
-import { css } from '@emotion/css';
import { useMemo, useState } from 'react';
-import { GrafanaTheme2 } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
-import { Menu, Dropdown, useStyles2, ToolbarButton } from '@grafana/ui';
-import { useMediaQueryMinWidth } from 'app/core/hooks/useMediaQueryMinWidth';
+import { Menu, Dropdown, ToolbarButton } from '@grafana/ui';
import { useSelector } from 'app/types';
import { t } from '../../../internationalization';
@@ -15,13 +12,14 @@ import { findCreateActions } from './utils';
export interface Props {}
export const QuickAdd = ({}: Props) => {
- const styles = useStyles2(getStyles);
const navBarTree = useSelector((state) => state.navBarTree);
const [isOpen, setIsOpen] = useState(false);
-
const createActions = useMemo(() => findCreateActions(navBarTree), [navBarTree]);
- const isSmallScreen = !useMediaQueryMinWidth('sm');
- const showQuickAdd = createActions.length > 0 && !isSmallScreen;
+ const showQuickAdd = createActions.length > 0;
+
+ if (!showQuickAdd) {
+ return null;
+ }
const MenuActions = () => {
return (
@@ -43,29 +41,12 @@ export const QuickAdd = ({}: Props) => {
-
+
>
) : null;
};
-
-const getStyles = (theme: GrafanaTheme2) => ({
- buttonContent: css({
- alignItems: 'center',
- display: 'flex',
- }),
- buttonText: css({
- [theme.breakpoints.down('md')]: {
- display: 'none',
- },
- }),
- separator: css({
- [theme.breakpoints.down('sm')]: {
- display: 'none',
- },
- }),
-});
diff --git a/public/app/core/components/AppChrome/TopBar/InviteUserButton.tsx b/public/app/core/components/AppChrome/TopBar/InviteUserButton.tsx
index a788f798fc6..452e899ca47 100644
--- a/public/app/core/components/AppChrome/TopBar/InviteUserButton.tsx
+++ b/public/app/core/components/AppChrome/TopBar/InviteUserButton.tsx
@@ -1,5 +1,5 @@
import { reportInteraction } from '@grafana/runtime';
-import { Button, Stack } from '@grafana/ui';
+import { Box, Button } from '@grafana/ui';
import { config } from 'app/core/config';
import { t } from 'app/core/internationalization';
import { contextSrv } from 'app/core/services/context_srv';
@@ -10,8 +10,7 @@ import { NavToolbarSeparator } from '../NavToolbar/NavToolbarSeparator';
export function InviteUserButton() {
return config.externalUserMngLinkUrl && contextSrv.hasPermission(AccessControlAction.OrgUsersAdd) ? (
-
-
+
+
) : null;
}
diff --git a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx
index 7a3b05b946e..5db53ff730e 100644
--- a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx
+++ b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx
@@ -3,11 +3,14 @@ import { cloneDeep } from 'lodash';
import { memo } from 'react';
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
+import { Components } from '@grafana/e2e-selectors';
+import { ScopesContextValue } from '@grafana/runtime';
import { Dropdown, Icon, Stack, ToolbarButton, useStyles2 } from '@grafana/ui';
import { config } from 'app/core/config';
import { MEGA_MENU_TOGGLE_ID } from 'app/core/constants';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { contextSrv } from 'app/core/core';
+import { useMediaQueryMinWidth } from 'app/core/hooks/useMediaQueryMinWidth';
import { t } from 'app/core/internationalization';
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
import { useSelector } from 'app/types';
@@ -18,20 +21,26 @@ import { buildBreadcrumbs } from '../../Breadcrumbs/utils';
import { ExtensionToolbarItem } from '../ExtensionSidebar/ExtensionToolbarItem';
import { HistoryContainer } from '../History/HistoryContainer';
import { enrichHelpItem } from '../MegaMenu/utils';
+import { NavToolbarSeparator } from '../NavToolbar/NavToolbarSeparator';
import { QuickAdd } from '../QuickAdd/QuickAdd';
-import { TOP_BAR_LEVEL_HEIGHT } from '../types';
import { InviteUserButton } from './InviteUserButton';
import { ProfileButton } from './ProfileButton';
import { SignInLink } from './SignInLink';
+import { SingleTopBarActions } from './SingleTopBarActions';
import { TopNavBarMenu } from './TopNavBarMenu';
import { TopSearchBarCommandPaletteTrigger } from './TopSearchBarCommandPaletteTrigger';
+import { getChromeHeaderLevelHeight } from './useChromeHeaderHeight';
interface Props {
sectionNav: NavModelItem;
pageNav?: NavModelItem;
onToggleMegaMenu(): void;
onToggleKioskMode(): void;
+ actions?: React.ReactNode;
+ breadcrumbActions?: React.ReactNode;
+ scopes?: ScopesContextValue | undefined;
+ showToolbarLevel: boolean;
}
export const SingleTopBar = memo(function SingleTopBar({
@@ -39,60 +48,79 @@ export const SingleTopBar = memo(function SingleTopBar({
onToggleKioskMode,
pageNav,
sectionNav,
+ scopes,
+ actions,
+ breadcrumbActions,
+ showToolbarLevel,
}: Props) {
const { chrome } = useGrafana();
const state = chrome.useState();
const menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen;
const styles = useStyles2(getStyles, menuDockedAndOpen);
const navIndex = useSelector((state) => state.navIndex);
-
const helpNode = cloneDeep(navIndex['help']);
const enrichedHelpNode = helpNode ? enrichHelpItem(helpNode) : undefined;
const profileNode = navIndex['profile'];
const homeNav = useSelector((state) => state.navIndex)[HOME_NAV_ID];
const breadcrumbs = buildBreadcrumbs(sectionNav, pageNav, homeNav);
const unifiedHistoryEnabled = config.featureToggles.unifiedHistory;
+ const isSmallScreen = !useMediaQueryMinWidth('sm');
return (
-
-
- {!menuDockedAndOpen && (
-
- )}
-
-
+ <>
+
+
+ {!menuDockedAndOpen && (
+
+ )}
+
+ {!showToolbarLevel && breadcrumbActions}
+
-
-
- {unifiedHistoryEnabled && }
-
- {enrichedHelpNode && (
- } placement="bottom-end">
-
-
- )}
- {!contextSrv.user.isSignedIn && }
- {config.featureToggles.inviteUserExperimental && }
- {config.featureToggles.extensionSidebar && }
- {profileNode && }
-
-
+
+
+ {unifiedHistoryEnabled && !isSmallScreen && }
+ {!isSmallScreen && }
+ {enrichedHelpNode && (
+ } placement="bottom-end">
+
+
+ )}
+
+ {config.featureToggles.inviteUserExperimental && !isSmallScreen && }
+ {config.featureToggles.extensionSidebar && !isSmallScreen && }
+ {!showToolbarLevel && actions}
+ {!contextSrv.user.isSignedIn && }
+ {profileNode && }
+
+
+ {showToolbarLevel && (
+
+ )}
+ >
);
});
const getStyles = (theme: GrafanaTheme2, menuDockedAndOpen: boolean) => ({
layout: css({
- height: TOP_BAR_LEVEL_HEIGHT,
+ height: getChromeHeaderLevelHeight(),
display: 'flex',
gap: theme.spacing(2),
alignItems: 'center',
@@ -100,12 +128,6 @@ const getStyles = (theme: GrafanaTheme2, menuDockedAndOpen: boolean) => ({
paddingLeft: menuDockedAndOpen ? theme.spacing(3.5) : theme.spacing(0.75),
borderBottom: `1px solid ${theme.colors.border.weak}`,
justifyContent: 'space-between',
-
- [theme.breakpoints.up('lg')]: {
- gridTemplateColumns: '2fr minmax(550px, 1fr)',
- display: 'grid',
- justifyContent: 'flex-start',
- },
}),
breadcrumbsWrapper: css({
display: 'flex',
diff --git a/public/app/core/components/AppChrome/TopBar/SingleTopBarActions.tsx b/public/app/core/components/AppChrome/TopBar/SingleTopBarActions.tsx
index 363327f23a1..9e7e0ce7c7c 100644
--- a/public/app/core/components/AppChrome/TopBar/SingleTopBarActions.tsx
+++ b/public/app/core/components/AppChrome/TopBar/SingleTopBarActions.tsx
@@ -1,41 +1,34 @@
import { css } from '@emotion/css';
-import { PropsWithChildren } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Components } from '@grafana/e2e-selectors';
-import { useScopes } from '@grafana/runtime';
+import { ScopesContextValue } from '@grafana/runtime';
import { Stack, useStyles2 } from '@grafana/ui';
import { ScopesSelector } from 'app/features/scopes/selector/ScopesSelector';
-import { TOP_BAR_LEVEL_HEIGHT } from '../types';
+import { NavToolbarSeparator } from '../NavToolbar/NavToolbarSeparator';
-export function SingleTopBarActions({ children }: PropsWithChildren) {
+import { getChromeHeaderLevelHeight } from './useChromeHeaderHeight';
+
+export interface Props {
+ actions?: React.ReactNode;
+ breadcrumbActions?: React.ReactNode;
+ scopes?: ScopesContextValue | undefined;
+}
+
+export function SingleTopBarActions({ actions, breadcrumbActions, scopes }: Props) {
const styles = useStyles2(getStyles);
- const scopes = useScopes();
-
- const scopesRender = scopes?.state.enabled ? : undefined;
- const childrenRender = children ? (
-
- {children}
-
- ) : undefined;
return (
- {scopesRender ? (
-
- {scopesRender}
- {children}
+
+ {scopes?.state.enabled ? : undefined}
+
+ {breadcrumbActions}
+ {breadcrumbActions && actions && }
+ {actions}
- ) : (
- childrenRender
- )}
+
);
}
@@ -47,7 +40,7 @@ const getStyles = (theme: GrafanaTheme2) => {
backgroundColor: theme.colors.background.primary,
borderBottom: `1px solid ${theme.colors.border.weak}`,
display: 'flex',
- height: TOP_BAR_LEVEL_HEIGHT,
+ height: getChromeHeaderLevelHeight(),
padding: theme.spacing(0, 1, 0, 2),
}),
};
diff --git a/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx b/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx
index d33464c2503..d878d835a05 100644
--- a/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx
+++ b/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx
@@ -1,6 +1,6 @@
-import { css } from '@emotion/css';
+import { css, cx } from '@emotion/css';
import { useKBar, VisualState } from 'kbar';
-import { useMemo } from 'react';
+import React, { useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
@@ -10,31 +10,36 @@ import { useMediaQueryMinWidth } from 'app/core/hooks/useMediaQueryMinWidth';
import { t } from 'app/core/internationalization';
import { getModKey } from 'app/core/utils/browser';
-export function TopSearchBarCommandPaletteTrigger() {
+import { NavToolbarSeparator } from '../NavToolbar/NavToolbarSeparator';
+
+export const TopSearchBarCommandPaletteTrigger = React.memo(() => {
const { query: kbar } = useKBar((kbarState) => ({
kbarSearchQuery: kbarState.searchQuery,
kbarIsOpen: kbarState.visualState === VisualState.showing,
}));
- const isSmallScreen = !useMediaQueryMinWidth('lg');
+ const isLargeScreen = useMediaQueryMinWidth('lg');
const onOpenSearch = () => {
kbar.toggle();
};
- if (isSmallScreen) {
+ if (!isLargeScreen) {
return (
-
+ <>
+
+
+ >
);
}
return ;
-}
+});
interface PretendTextInputProps {
onClick: () => void;
@@ -56,11 +61,10 @@ function PretendTextInput({ onClick }: PretendTextInputProps) {
-
{`${modKey}+k`}
@@ -72,7 +76,15 @@ const getStyles = (theme: GrafanaTheme2) => {
const baseStyles = getInputStyles({ theme });
return {
- wrapper: baseStyles.wrapper,
+ wrapper: cx(
+ baseStyles.wrapper,
+ css({
+ width: 'auto',
+ minWidth: 140,
+ maxWidth: 350,
+ flexGrow: 1,
+ })
+ ),
inputWrapper: baseStyles.inputWrapper,
prefix: baseStyles.prefix,
suffix: css([
diff --git a/public/app/core/components/AppChrome/TopBar/useChromeHeaderHeight.ts b/public/app/core/components/AppChrome/TopBar/useChromeHeaderHeight.ts
new file mode 100644
index 00000000000..fa3555741b6
--- /dev/null
+++ b/public/app/core/components/AppChrome/TopBar/useChromeHeaderHeight.ts
@@ -0,0 +1,99 @@
+import { useEffect, useState } from 'react';
+
+import { config, useScopes } from '@grafana/runtime';
+import { useGrafana } from 'app/core/context/GrafanaContext';
+import { useMediaQueryMinWidth } from 'app/core/hooks/useMediaQueryMinWidth';
+
+import { AppChromeState } from '../AppChromeService';
+import { useExtensionSidebarContext } from '../ExtensionSidebar/ExtensionSidebarProvider';
+
+/**
+ * Returns the current header levels given current app chrome state, scopes and screen size.
+ */
+export function useChromeHeaderLevels() {
+ const { chrome } = useGrafana();
+ const state = chrome.state.getValue();
+ const scopes = useScopes();
+
+ const isLargeScreen = useMediaQueryMinWidth('xl');
+
+ const [headerLevels, setHeaderLevels] = useState(
+ getHeaderLevelsGivenState(state, scopes?.state.enabled, isLargeScreen)
+ );
+
+ // Subscribe to chrome state changes and update header height
+ useEffect(() => {
+ const unsub = chrome.state.subscribe((state) => {
+ const newLevels = getHeaderLevelsGivenState(state, scopes?.state.enabled, isLargeScreen);
+ if (newLevels !== headerLevels) {
+ setHeaderLevels(newLevels);
+ }
+ });
+
+ return () => unsub.unsubscribe();
+ }, [chrome, headerLevels, scopes, isLargeScreen]);
+
+ return headerLevels;
+}
+
+function getHeaderLevelsGivenState(
+ chromeState: AppChromeState,
+ scopesEnabled: boolean | undefined = false,
+ isLargeScreen: boolean
+) {
+ // No levels when chromeless or kiosk mode
+ if (chromeState.kioskMode || chromeState.chromeless) {
+ return 0;
+ }
+
+ // Always use two levels scopes is enabled
+ if (scopesEnabled) {
+ return 2;
+ }
+
+ // No actions we can always use 1 level
+ if (!chromeState.actions) {
+ return 1;
+ }
+
+ // We have actions
+ // If mega menu docked always use two levels
+ // If scenes disabled always use two levels (mainly because of the time range picker)
+ if (chromeState.megaMenuDocked || !config.featureToggles.dashboardScene) {
+ return 2;
+ }
+
+ // If screen is large and unifiedNavbars is not disabled then we can use 1 level
+ if (isLargeScreen && config.featureToggles.unifiedNavbars) {
+ return 1;
+ }
+
+ return 2;
+}
+
+/**
+ * Translates header levels to header height but also takes the
+ * sidebar into account as header height can be treated as zero when the sidebar is open
+ * this should be better named as useStickyTopPadding or something as that is what is's used for
+ */
+export function useChromeHeaderHeight() {
+ const levels = useChromeHeaderLevels();
+
+ // if the extension sidebar is open, the inner pane will be scrollable, thus we need to set the header height to 0
+ const { isOpen: isExtensionSidebarOpen } = useExtensionSidebarContext();
+
+ if (isExtensionSidebarOpen) {
+ return 0;
+ }
+
+ return levels * getChromeHeaderLevelHeight();
+}
+
+/**
+ * Can replace with constant once unifiedNavbars feature toggle is removed
+ **/
+export function getChromeHeaderLevelHeight() {
+ // Waiting with switch to 48 until we have a story for scopes
+ // return config.featureToggles.unifiedNavbars ? 48 : 40;
+ return 40;
+}
diff --git a/public/app/core/components/AppChrome/types.ts b/public/app/core/components/AppChrome/types.ts
index 1882e24d9a8..6cf72c936f5 100644
--- a/public/app/core/components/AppChrome/types.ts
+++ b/public/app/core/components/AppChrome/types.ts
@@ -1,5 +1,4 @@
import { NavModelItem } from '@grafana/data';
-export const TOP_BAR_LEVEL_HEIGHT = 40;
export interface ToolbarUpdateProps {
pageNav?: NavModelItem;
diff --git a/public/app/core/context/GrafanaContext.ts b/public/app/core/context/GrafanaContext.ts
index 8ce1dcacd0e..1f3bf86372b 100644
--- a/public/app/core/context/GrafanaContext.ts
+++ b/public/app/core/context/GrafanaContext.ts
@@ -1,12 +1,9 @@
import { createContext, useCallback, useContext } from 'react';
-import { useObservable } from 'react-use';
-import { of } from 'rxjs';
import { GrafanaConfig } from '@grafana/data';
import { LocationService, locationService, BackendSrv } from '@grafana/runtime';
import { AppChromeService } from '../components/AppChrome/AppChromeService';
-import { useExtensionSidebarContext } from '../components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider';
import { NewFrontendAssetsChecker } from '../services/NewFrontendAssetsChecker';
import { KeybindingSrv } from '../services/keybindingSrv';
@@ -44,11 +41,3 @@ export function useReturnToPreviousInternal() {
[chrome]
);
}
-
-export function useChromeHeaderHeight() {
- const { chrome } = useGrafana();
- // if the extension sidebar is open, the inner pane will be scrollable, thus we need to set the header height to 0
- const { isOpen: isExtensionSidebarOpen } = useExtensionSidebarContext();
-
- return useObservable(isExtensionSidebarOpen ? of(0) : chrome.headerHeightObservable, 0);
-}
diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx
index 431dfed0d1b..a778dd364f9 100644
--- a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx
+++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx
@@ -7,6 +7,7 @@ import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/Pan
import { DashboardScene } from '../scene/DashboardScene';
import { useLayoutCategory } from '../scene/layouts-shared/DashboardLayoutSelector';
+import { EditSchemaV2Button } from '../scene/new-toolbar/actions/EditSchemaV2Button';
import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
export class DashboardEditableElement implements EditableDashboardElement {
@@ -53,19 +54,22 @@ export class DashboardEditableElement implements EditableDashboardElement {
public renderActions(): ReactNode {
return (
-
+ <>
+
+
+ >
);
}
}
diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx
index 003ea1b438b..e33e6acd112 100644
--- a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx
+++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx
@@ -11,7 +11,6 @@ import {
Dropdown,
Icon,
Menu,
- Stack,
ToolbarButton,
ToolbarButtonRow,
useStyles2,
@@ -38,7 +37,8 @@ import { isLibraryPanel } from '../utils/utils';
import { DashboardScene } from './DashboardScene';
import { GoToSnapshotOriginButton } from './GoToSnapshotOriginButton';
import ManagedDashboardNavBarBadge from './ManagedDashboardNavBarBadge';
-import { ToolbarActionsNew } from './new-toolbar/ToolbarActionsNew';
+import { LeftActions } from './new-toolbar/LeftActions';
+import { RightActions } from './new-toolbar/RightActions';
interface Props {
dashboard: DashboardScene;
@@ -47,12 +47,14 @@ interface Props {
export const NavToolbarActions = memo(({ dashboard }) => {
const hasNewToolbar = config.featureToggles.dashboardNewLayouts && config.featureToggles.newDashboardSharingComponent;
- const actions = hasNewToolbar ? (
-
+ return hasNewToolbar ? (
+ }
+ actions={}
+ />
) : (
-
+ } />
);
- return ;
});
NavToolbarActions.displayName = 'NavToolbarActions';
@@ -68,7 +70,6 @@ export function ToolbarActions({ dashboard }: Props) {
const canSaveAs = contextSrv.hasEditPermissionInFolders;
const toolbarActions: ToolbarAction[] = [];
- const leftActions: ToolbarAction[] = [];
const styles = useStyles2(getStyles);
const isEditingPanel = Boolean(editPanel);
const isViewingPanel = Boolean(viewPanelScene);
@@ -593,16 +594,7 @@ export function ToolbarActions({ dashboard }: Props) {
},
});
- const rightActionsElements: ReactNode[] = renderActionElements(toolbarActions);
- const leftActionsElements: ReactNode[] = renderActionElements(leftActions);
- const hasActionsToLeftAndRight = leftActionsElements.length > 0;
-
- return (
-
- {leftActionsElements.length > 0 && {leftActionsElements}}
- {rightActionsElements}
-
- );
+ return {renderActionElements(toolbarActions)};
}
function renderActionElements(toolbarActions: ToolbarAction[]) {
diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/LeftActions.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/LeftActions.tsx
index 5c083673521..ec37d446145 100644
--- a/public/app/features/dashboard-scene/scene/new-toolbar/LeftActions.tsx
+++ b/public/app/features/dashboard-scene/scene/new-toolbar/LeftActions.tsx
@@ -1,6 +1,4 @@
-import { css } from '@emotion/css';
-
-import { ToolbarButtonRow, useStyles2 } from '@grafana/ui';
+import { ToolbarButtonRow } from '@grafana/ui';
import { dynamicDashNavActions } from '../../utils/registerDynamicDashNavAction';
import { DashboardScene } from '../DashboardScene';
@@ -12,7 +10,6 @@ import { StarButton } from './actions/StarButton';
import { getDynamicActions, renderActionElements } from './utils';
export const LeftActions = ({ dashboard }: { dashboard: DashboardScene }) => {
- const styles = useStyles2(getStyles);
const { editview, editPanel, isEditing, uid, meta, viewPanelScene } = dashboard.useState();
const hasEditView = Boolean(editview);
@@ -54,8 +51,6 @@ export const LeftActions = ({ dashboard }: { dashboard: DashboardScene }) => {
group: 'actions',
condition: isSnapshot && !isEditingDashboard,
},
- // This adds the presence indicators in enterprise
- ...getDynamicActions(dynamicDashNavActions.right, 'right-dynamic', !isEditingPanel && !isEditingDashboard),
],
dashboard
);
@@ -64,15 +59,5 @@ export const LeftActions = ({ dashboard }: { dashboard: DashboardScene }) => {
return null;
}
- return (
-
- {elements}
-
- );
+ return {elements};
};
-
-const getStyles = () => ({
- container: css({
- flex: 1,
- }),
-});
diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/RightActions.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/RightActions.tsx
index b809a9fb90a..428906c1ab0 100644
--- a/public/app/features/dashboard-scene/scene/new-toolbar/RightActions.tsx
+++ b/public/app/features/dashboard-scene/scene/new-toolbar/RightActions.tsx
@@ -1,9 +1,11 @@
import { css } from '@emotion/css';
+import { GrafanaTheme2 } from '@grafana/data';
import { ToolbarButtonRow, useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/services/context_srv';
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
+import { dynamicDashNavActions } from '../../utils/registerDynamicDashNavAction';
import { isLibraryPanel } from '../../utils/utils';
import { DashboardScene } from '../DashboardScene';
@@ -12,7 +14,6 @@ import { DashboardSettingsButton } from './actions/DashboardSettingsButton';
import { DiscardLibraryPanelButton } from './actions/DiscardLibraryPanelButton';
import { DiscardPanelButton } from './actions/DiscardPanelButton';
import { EditDashboardSwitch } from './actions/EditDashboardSwitch';
-import { EditSchemaV2Button } from './actions/EditSchemaV2Button';
import { ExportDashboardButton } from './actions/ExportDashboardButton';
import { MakeDashboardEditableButton } from './actions/MakeDashboardEditableButton';
import { PlayListNextButton } from './actions/PlayListNextButton';
@@ -22,12 +23,12 @@ import { SaveDashboard } from './actions/SaveDashboard';
import { SaveLibraryPanelButton } from './actions/SaveLibraryPanelButton';
import { ShareDashboardButton } from './actions/ShareDashboardButton';
import { UnlinkLibraryPanelButton } from './actions/UnlinkLibraryPanelButton';
-import { renderActionElements } from './utils';
+import { getDynamicActions, renderActionElements } from './utils';
export const RightActions = ({ dashboard }: { dashboard: DashboardScene }) => {
- const styles = useStyles2(getStyles);
const { editPanel, editable, editview, isEditing, uid, meta, viewPanelScene } = dashboard.useState();
const { isPlaying } = playlistSrv.useState();
+ const styles = useStyles2(getStyles);
const isEditable = Boolean(editable);
const canSave = Boolean(meta.canSave);
@@ -44,12 +45,15 @@ export const RightActions = ({ dashboard }: { dashboard: DashboardScene }) => {
const showPanelButtons = isEditingPanel && !hasEditView && !isViewingPanel;
const showPlayButtons = isPlaying && isShowingDashboard && !isEditingDashboard;
- const showShareButton = hasUid && !isSnapshot && !isPlaying;
+ const showShareButton = hasUid && !isSnapshot && !isPlaying && !isEditingPanel;
return (
{renderActionElements(
[
+ // This adds the presence indicators in enterprise
+ // Leaving group empty here as these are sometimes not rendered leaving separators with blank space between them
+ ...getDynamicActions(dynamicDashNavActions.right, '', !isEditingPanel && !isEditingDashboard),
{
key: 'play-list-previous-button',
component: PlayListPreviousButton,
@@ -98,12 +102,6 @@ export const RightActions = ({ dashboard }: { dashboard: DashboardScene }) => {
group: 'panel',
condition: showPanelButtons && isEditingLibraryPanel,
},
- {
- key: 'edit-schema-v2-button',
- component: EditSchemaV2Button,
- group: 'dashboard',
- condition: isEditingAndShowingDashboard && hasUid,
- },
{
key: 'dashboard-settings',
component: DashboardSettingsButton,
@@ -120,13 +118,20 @@ export const RightActions = ({ dashboard }: { dashboard: DashboardScene }) => {
key: 'make-dashboard-editable-button',
component: MakeDashboardEditableButton,
group: 'save-edit',
- condition: !isEditing && dashboard.canEditDashboard() && !isViewingPanel && !isEditable,
+ condition: !isEditing && dashboard.canEditDashboard() && !isViewingPanel && !isEditable && !isPlaying,
},
{
key: 'edit-dashboard-switch',
component: EditDashboardSwitch,
group: 'save-edit',
- condition: dashboard.canEditDashboard() && !isEditingLibraryPanel && !isViewingPanel && isEditable,
+ condition:
+ dashboard.canEditDashboard() &&
+ !isEditingPanel &&
+ !isEditingLibraryPanel &&
+ !isViewingPanel &&
+ isEditable &&
+ !isPlaying &&
+ !isEditingPanel,
},
{
key: 'new-export-dashboard-button',
@@ -147,8 +152,6 @@ export const RightActions = ({ dashboard }: { dashboard: DashboardScene }) => {
);
};
-const getStyles = () => ({
- container: css({
- flex: 1,
- }),
+const getStyles = (theme: GrafanaTheme2) => ({
+ container: css({ paddingLeft: theme.spacing(0.5) }),
});
diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditSchemaV2Button.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditSchemaV2Button.tsx
index fc18a235bdc..3b0f729f711 100644
--- a/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditSchemaV2Button.tsx
+++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditSchemaV2Button.tsx
@@ -1,10 +1,12 @@
-import { Icon, ToolbarButton } from '@grafana/ui';
+import { Button, Icon } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { ToolbarActionProps } from '../types';
export const EditSchemaV2Button = ({ dashboard }: ToolbarActionProps) => (
- }
onClick={() => dashboard.openV2SchemaEditor()}
diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/utils.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/utils.tsx
index 936faf0e054..aaa5fcbf6a2 100644
--- a/public/app/features/dashboard-scene/scene/new-toolbar/utils.tsx
+++ b/public/app/features/dashboard-scene/scene/new-toolbar/utils.tsx
@@ -17,7 +17,7 @@ export function renderActionElements(toolbarActions: ToolbarAction[], dashboard:
continue;
}
- if (lastGroup && lastGroup !== action.group) {
+ if (action.group && lastGroup && lastGroup !== action.group) {
actionElements.push();
}
diff --git a/public/app/features/explore/Logs/LogsNavigation.tsx b/public/app/features/explore/Logs/LogsNavigation.tsx
index aae833cffc6..64a306c8192 100644
--- a/public/app/features/explore/Logs/LogsNavigation.tsx
+++ b/public/app/features/explore/Logs/LogsNavigation.tsx
@@ -6,7 +6,7 @@ import { AbsoluteTimeRange, GrafanaTheme2, LogsSortOrder } from '@grafana/data';
import { config, reportInteraction } from '@grafana/runtime';
import { DataQuery, TimeZone } from '@grafana/schema';
import { Button, Icon, Spinner, useTheme2 } from '@grafana/ui';
-import { TOP_BAR_LEVEL_HEIGHT } from 'app/core/components/AppChrome/types';
+import { getChromeHeaderLevelHeight } from 'app/core/components/AppChrome/TopBar/useChromeHeaderHeight';
import { t, Trans } from 'app/core/internationalization';
import { LogsNavigationPages } from './LogsNavigationPages';
@@ -231,7 +231,8 @@ function LogsNavigation({
export default memo(LogsNavigation);
const getStyles = (theme: GrafanaTheme2, oldestLogsFirst: boolean) => {
- const navContainerHeight = `calc(100vh - 2*${theme.spacing(2)} - 2*${TOP_BAR_LEVEL_HEIGHT}px)`;
+ const navContainerHeight = `calc(100vh - 2*${theme.spacing(2)} - 2*${getChromeHeaderLevelHeight()}px)`;
+
return {
navContainer: css({
maxHeight: navContainerHeight,
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index f3e36bbbf61..468ef976a60 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -5837,7 +5837,7 @@
"title": "Scenes"
},
"search": {
- "placeholderCommandPalette": "Search or jump to..."
+ "placeholderCommandPalette": "Search..."
},
"search-dashboards": {
"title": "Search dashboards"