diff --git a/packages/grafana-data/src/themes/createTheme.ts b/packages/grafana-data/src/themes/createTheme.ts index 13b28e4ba9c..428539cfebf 100644 --- a/packages/grafana-data/src/themes/createTheme.ts +++ b/packages/grafana-data/src/themes/createTheme.ts @@ -55,6 +55,7 @@ export function createTheme(options: NewThemeOptions = {}): GrafanaTheme2 { zIndex: { ...zIndex, }, + flags: {}, }; return { diff --git a/packages/grafana-data/src/themes/types.ts b/packages/grafana-data/src/themes/types.ts index 0857a5da886..ede4c180126 100644 --- a/packages/grafana-data/src/themes/types.ts +++ b/packages/grafana-data/src/themes/types.ts @@ -30,6 +30,8 @@ export interface GrafanaTheme2 { visualization: ThemeVisualizationColors; transitions: ThemeTransitions; v1: GrafanaTheme; + /** feature flags that might impact component looks */ + flags: { topnav?: boolean }; } /** @alpha */ diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index e7cd5730fc0..76da13d75bc 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -169,6 +169,9 @@ export class GrafanaBootConfig implements GrafanaConfig { } overrideFeatureTogglesFromUrl(this); + + // Special feature toggle that impact theme/component looks + this.theme2.flags.topnav = this.featureToggles.topnav; } } diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx index 1a6e9f5b672..c1fdfd7ddc2 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx @@ -127,6 +127,27 @@ const getStyles = (theme: GrafanaTheme2) => { const primaryVariant = getPropertiesForVariant(theme, 'primary', 'solid'); const destructiveVariant = getPropertiesForVariant(theme, 'destructive', 'solid'); + const defaultOld = css` + color: ${theme.colors.text.secondary}; + background-color: ${theme.colors.background.primary}; + + &:hover { + color: ${theme.colors.text.primary}; + background: ${theme.colors.background.secondary}; + } + `; + + const defaultTopNav = css` + color: ${theme.colors.text.secondary}; + background-color: transparent; + border: none; + + &:hover { + color: ${theme.colors.text.primary}; + background: ${theme.colors.background.secondary}; + } + `; + return { button: css` label: toolbar-button; @@ -172,15 +193,7 @@ const getStyles = (theme: GrafanaTheme2) => { } } `, - default: css` - color: ${theme.colors.text.secondary}; - background-color: ${theme.colors.background.primary}; - - &:hover { - color: ${theme.colors.text.primary}; - background: ${theme.colors.background.secondary}; - } - `, + default: theme.flags.topnav ? defaultTopNav : defaultOld, active: css` color: ${theme.v1.palette.orangeDark}; border-color: ${theme.v1.palette.orangeDark}; diff --git a/public/app/AppWrapper.tsx b/public/app/AppWrapper.tsx index 14ade91ddf7..838ac06bd4a 100644 --- a/public/app/AppWrapper.tsx +++ b/public/app/AppWrapper.tsx @@ -115,7 +115,7 @@ export class AppWrapper extends React.Component - + { - return reactDirective(provideTheme(component), options); + return reactDirective(provideTheme(component, config.theme2), options); }, ]); } diff --git a/public/app/core/components/AppChrome/Breadcrumbs.tsx b/public/app/core/components/AppChrome/Breadcrumbs.tsx index c7bc80ec1d8..f77d3640ba0 100644 --- a/public/app/core/components/AppChrome/Breadcrumbs.tsx +++ b/public/app/core/components/AppChrome/Breadcrumbs.tsx @@ -60,6 +60,7 @@ const getStyles = (theme: GrafanaTheme2) => { breadcrumbs: css({ display: 'flex', alignItems: 'center', + flexWrap: 'nowrap', fontWeight: theme.typography.fontWeightMedium, }), breadcrumb: css({ @@ -72,6 +73,7 @@ const getStyles = (theme: GrafanaTheme2) => { }), breadcrumbLink: css({ color: theme.colors.text.primary, + whiteSpace: 'nowrap', '&:hover': { textDecoration: 'underline', }, diff --git a/public/app/core/components/AppChrome/NavToolbar.tsx b/public/app/core/components/AppChrome/NavToolbar.tsx index d0ca6b346d6..4ac72ae4a89 100644 --- a/public/app/core/components/AppChrome/NavToolbar.tsx +++ b/public/app/core/components/AppChrome/NavToolbar.tsx @@ -2,9 +2,10 @@ import { css } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2, NavModelItem } from '@grafana/data'; -import { IconButton, ToolbarButton, useStyles2 } from '@grafana/ui'; +import { Icon, IconButton, ToolbarButton, useStyles2 } from '@grafana/ui'; import { Breadcrumbs } from './Breadcrumbs'; +import { NavToolbarSeparator } from './NavToolbarSeparator'; import { TOP_BAR_LEVEL_HEIGHT } from './types'; export interface Props { @@ -32,10 +33,12 @@ export function NavToolbar({ -
-
+
{actions} - + {actions && } + + +
); @@ -55,16 +58,14 @@ const getStyles = (theme: GrafanaTheme2) => { alignItems: 'center', paddingRight: theme.spacing(1), }), - leftActions: css({ + actions: css({ display: 'flex', alignItems: 'center', + flexWrap: 'nowrap', + justifyContent: 'flex-end', + paddingLeft: theme.spacing(1), flexGrow: 1, - gap: theme.spacing(2), - }), - rightActions: css({ - display: 'flex', - alignItems: 'center', - gap: theme.spacing(2), + gap: theme.spacing(0.5), }), }; }; diff --git a/public/app/core/components/AppChrome/NavToolbarSeparator.tsx b/public/app/core/components/AppChrome/NavToolbarSeparator.tsx new file mode 100644 index 00000000000..72b328ba2d9 --- /dev/null +++ b/public/app/core/components/AppChrome/NavToolbarSeparator.tsx @@ -0,0 +1,38 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { useStyles2 } from '@grafana/ui'; + +export interface Props { + leftActionsSeparator?: boolean; +} + +export function NavToolbarSeparator({ leftActionsSeparator }: Props) { + const styles = useStyles2(getStyles); + + if (leftActionsSeparator) { + return
; + } + + if (config.featureToggles.topnav) { + return
; + } + + return null; +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + leftActionsSeparator: css({ + display: 'flex', + flexGrow: 1, + }), + line: css({ + width: 1, + backgroundColor: theme.colors.border.medium, + height: 24, + }), + }; +}; diff --git a/public/app/core/services/ModalManager.ts b/public/app/core/services/ModalManager.ts index cdd756f8f36..a84880998d9 100644 --- a/public/app/core/services/ModalManager.ts +++ b/public/app/core/services/ModalManager.ts @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { textUtil } from '@grafana/data'; -import { CopyPanelEvent } from '@grafana/runtime'; +import { config, CopyPanelEvent } from '@grafana/runtime'; import { ConfirmModal, ConfirmModalProps } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import { copyPanel } from 'app/features/dashboard/utils/panel'; @@ -32,7 +32,7 @@ export class ModalManager { }, }; - const elem = React.createElement(provideTheme(AngularModalProxy), modalProps); + const elem = React.createElement(provideTheme(AngularModalProxy, config.theme2), modalProps); this.reactModalRoot.appendChild(this.reactModalNode); ReactDOM.render(elem, this.reactModalNode); } @@ -83,7 +83,7 @@ export class ModalManager { props, }; - const elem = React.createElement(provideTheme(AngularModalProxy), modalProps); + const elem = React.createElement(provideTheme(AngularModalProxy, config.theme2), modalProps); this.reactModalRoot.appendChild(this.reactModalNode); ReactDOM.render(elem, this.reactModalNode); } diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx index e714ab27b09..eab12a8c089 100644 --- a/public/app/core/utils/ConfigProvider.tsx +++ b/public/app/core/utils/ConfigProvider.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { createTheme } from '@grafana/data'; +import { GrafanaTheme2 } from '@grafana/data'; import { config, GrafanaBootConfig, ThemeChangedEvent } from '@grafana/runtime'; import { ThemeContext } from '@grafana/ui'; @@ -16,8 +16,8 @@ export const provideConfig = (component: React.ComponentType) => { return ConfigProvider; }; -export const ThemeProvider = ({ children }: { children: React.ReactNode }) => { - const [theme, setTheme] = useState(getCurrentUserTheme()); +export const ThemeProvider = ({ children, value }: { children: React.ReactNode; value: GrafanaTheme2 }) => { + const [theme, setTheme] = useState(value); useEffect(() => { const sub = appEvents.subscribe(ThemeChangedEvent, (event) => { @@ -31,14 +31,8 @@ export const ThemeProvider = ({ children }: { children: React.ReactNode }) => { return {children}; }; -function getCurrentUserTheme() { - return createTheme({ - colors: { - mode: config.bootData.user.lightTheme ? 'light' : 'dark', - }, - }); -} - -export const provideTheme = (component: React.ComponentType) => { - return provideConfig((props: any) => {React.createElement(component, { ...props })}); +export const provideTheme = (component: React.ComponentType, theme: GrafanaTheme2) => { + return provideConfig((props: any) => ( + {React.createElement(component, { ...props })} + )); }; diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index d306ad6022b..653a4f71747 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -6,6 +6,7 @@ import { locationUtil, textUtil } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { ButtonGroup, ModalsController, ToolbarButton, PageToolbar, useForceUpdate } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; +import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbarSeparator'; import config from 'app/core/config'; import { toggleKioskMode } from 'app/core/navigation/kiosk'; import { DashboardCommentsModal } from 'app/features/dashboard/components/DashboardComments/DashboardCommentsModal'; @@ -109,7 +110,7 @@ export const DashNav = React.memo((props) => { return playlistSrv.isPlaying; }; - const renderLeftActionsButton = () => { + const renderLeftActions = () => { const { dashboard, kioskMode } = props; const { canStar, canShare, isStarred } = dashboard.meta; const buttons: ReactNode[] = []; @@ -199,7 +200,7 @@ export const DashNav = React.memo((props) => { ); }; - const renderRightActionsButton = () => { + const renderRightActions = () => { const { dashboard, onAddPanel, isFullscreen, kioskMode } = props; const { canSave, canEdit, showSettings } = dashboard.meta; const { snapshot } = dashboard; @@ -279,7 +280,13 @@ export const DashNav = React.memo((props) => { return ( } + actions={ + <> + {renderLeftActions()} + + {renderRightActions()} + + } /> ); } @@ -292,9 +299,9 @@ export const DashNav = React.memo((props) => { titleHref={titleHref} parentHref={parentHref} onGoBack={onGoBack} - leftItems={renderLeftActionsButton()} + leftItems={renderLeftActions()} > - {renderRightActionsButton()} + {renderRightActions()} ); }); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 71f95318fa3..26281d8e9f9 100644 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -58,7 +58,7 @@ import { ThresholdManager } from './threshold_manager'; import { TimeRegionManager } from './time_region_manager'; import { isLegacyGraphHoverEvent } from './utils'; -const LegendWithThemeProvider = provideTheme(Legend); +const LegendWithThemeProvider = provideTheme(Legend, config.theme2); class GraphElement { ctrl: GraphCtrl;