Commit 701ad79b by Torkel Ödegaard Committed by GitHub

PageToolbar: Extracting navbar styles & layout into a modern emotion based component (#30588)

* Explore: Replaces navbar-button and overriden explore button css classes with ToolbarButton and cleans up scss & markup, removes ResponsiveButton

* Change live button text when paused

* For the dashboard toolbar button I need a transparent button so I refactored the states/variants into a new ToolbarButtonVariatn

* PageToolbar wip

* Progress

* Prgress

* Minor progress

* Fixed back button and responsive titles

* Fixed tv mode

* Updated

* support tv modes and playlist

* more progress

* Fixing lots of view states and responsive features

* Minor fixes

* review fixes

* Fixes to e2e tests

* Review fixes
parent 1c158744
...@@ -8,7 +8,7 @@ export const smokeTestScenario = { ...@@ -8,7 +8,7 @@ export const smokeTestScenario = {
skipScenario: false, skipScenario: false,
scenario: () => { scenario: () => {
e2e.flows.openDashboard(); e2e.flows.openDashboard();
e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click(); e2e.components.PageToolbar.item('Add panel').click();
e2e.pages.AddDashboard.addNewPanel().click(); e2e.pages.AddDashboard.addNewPanel().click();
e2e.components.DataSource.TestData.QueryTab.scenarioSelectContainer() e2e.components.DataSource.TestData.QueryTab.scenarioSelectContainer()
......
...@@ -38,7 +38,7 @@ e2e.scenario({ ...@@ -38,7 +38,7 @@ e2e.scenario({
); );
} }
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click(); e2e.components.PageToolbar.item('Dashboard settings').click();
e2e.components.TimeZonePicker.container() e2e.components.TimeZonePicker.container()
.should('be.visible') .should('be.visible')
......
...@@ -8,7 +8,7 @@ e2e.scenario({ ...@@ -8,7 +8,7 @@ e2e.scenario({
skipScenario: false, skipScenario: false,
scenario: () => { scenario: () => {
e2e.flows.openDashboard({ uid: '5SdHCadmz' }); e2e.flows.openDashboard({ uid: '5SdHCadmz' });
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click(); e2e.components.PageToolbar.item('Dashboard settings').click();
e2e.components.FolderPicker.container() e2e.components.FolderPicker.container()
.should('be.visible') .should('be.visible')
......
...@@ -50,7 +50,7 @@ e2e.scenario({ ...@@ -50,7 +50,7 @@ e2e.scenario({
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('p2').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('p2').should('be.visible').click();
e2e.pages.Dashboard.Toolbar.navBar().click(); e2e.components.PageToolbar.container().click();
e2e.components.DashboardLinks.dropDown().should('be.visible').click().wait('@tagsTemplatingSearch'); e2e.components.DashboardLinks.dropDown().should('be.visible').click().wait('@tagsTemplatingSearch');
......
...@@ -20,7 +20,7 @@ describe('Variables - Set options from ui', () => { ...@@ -20,7 +20,7 @@ describe('Variables - Set options from ui', () => {
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('A').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('A').should('be.visible').click();
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B').should('be.visible').click();
e2e.pages.Dashboard.Toolbar.navBar().click(); e2e.components.PageToolbar.container().click();
e2e().wait('@query'); e2e().wait('@query');
...@@ -77,7 +77,7 @@ describe('Variables - Set options from ui', () => { ...@@ -77,7 +77,7 @@ describe('Variables - Set options from ui', () => {
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A').should('be.visible').click();
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B').should('be.visible').click();
e2e.pages.Dashboard.Toolbar.navBar().click(); e2e.components.PageToolbar.container().click();
e2e().wait('@query'); e2e().wait('@query');
e2e().wait(500); e2e().wait(500);
...@@ -132,7 +132,7 @@ describe('Variables - Set options from ui', () => { ...@@ -132,7 +132,7 @@ describe('Variables - Set options from ui', () => {
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A + B').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A + B').should('be.visible').click();
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('A').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('A').should('be.visible').click();
e2e.pages.Dashboard.Toolbar.navBar().click(); e2e.components.PageToolbar.container().click();
e2e().wait('@query'); e2e().wait('@query');
e2e().wait(500); e2e().wait(500);
......
...@@ -185,7 +185,7 @@ function copyExistingDashboard() { ...@@ -185,7 +185,7 @@ function copyExistingDashboard() {
} }
function saveDashboard(saveVariables: boolean) { function saveDashboard(saveVariables: boolean) {
e2e.pages.Dashboard.Toolbar.toolbarItems('Save dashboard').should('be.visible').click(); e2e.components.PageToolbar.item('Save dashboard').should('be.visible').click();
if (saveVariables) { if (saveVariables) {
e2e.pages.SaveDashboardModal.saveVariables().should('exist').click({ force: true }); e2e.pages.SaveDashboardModal.saveVariables().should('exist').click({ force: true });
...@@ -212,7 +212,7 @@ function validateTextboxAndMarkup(value: string) { ...@@ -212,7 +212,7 @@ function validateTextboxAndMarkup(value: string) {
} }
function validateVariable(value: string) { function validateVariable(value: string) {
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').should('be.visible').click(); e2e.components.PageToolbar.item('Dashboard settings').should('be.visible').click();
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').should('be.visible').click(); e2e.pages.Dashboard.Settings.General.sectionItems('Variables').should('be.visible').click();
...@@ -245,7 +245,7 @@ function changeTextBoxInput() { ...@@ -245,7 +245,7 @@ function changeTextBoxInput() {
} }
function changeQueryInput() { function changeQueryInput() {
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').should('be.visible').click(); e2e.components.PageToolbar.item('Dashboard settings').should('be.visible').click();
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').should('be.visible').click(); e2e.pages.Dashboard.Settings.General.sectionItems('Variables').should('be.visible').click();
......
...@@ -56,8 +56,8 @@ export const Components = { ...@@ -56,8 +56,8 @@ export const Components = {
}, },
OptionsPane: { OptionsPane: {
content: 'Panel editor option pane content', content: 'Panel editor option pane content',
close: 'Dashboard navigation bar button Close options pane', close: 'Page toolbar button Close options pane',
open: 'Dashboard navigation bar button Open options pane', open: 'Page toolbar button Open options pane',
select: 'Panel editor option pane select', select: 'Panel editor option pane select',
tab: (title: string) => `Panel editor option pane tab ${title}`, tab: (title: string) => `Panel editor option pane tab ${title}`,
}, },
...@@ -123,6 +123,10 @@ export const Components = { ...@@ -123,6 +123,10 @@ export const Components = {
calculationsLabel: 'Transform calculations label', calculationsLabel: 'Transform calculations label',
}, },
}, },
PageToolbar: {
container: () => '.page-toolbar',
item: (tooltip: string) => `Page toolbar button ${tooltip}`,
},
QueryEditorToolbarItem: { QueryEditorToolbarItem: {
button: (title: string) => `QueryEditor toolbar item button ${title}`, button: (title: string) => `QueryEditor toolbar item button ${title}`,
}, },
......
...@@ -34,10 +34,6 @@ export const Pages = { ...@@ -34,10 +34,6 @@ export const Pages = {
}, },
Dashboard: { Dashboard: {
url: (uid: string) => `/d/${uid}`, url: (uid: string) => `/d/${uid}`,
Toolbar: {
toolbarItems: (button: string) => `Dashboard navigation bar button ${button}`,
navBar: () => '.navbar',
},
SubMenu: { SubMenu: {
submenuItem: 'Dashboard template variables submenu item', submenuItem: 'Dashboard template variables submenu item',
submenuItemLabels: (item: string) => `Dashboard template variables submenu Label ${item}`, submenuItemLabels: (item: string) => `Dashboard template variables submenu Label ${item}`,
......
...@@ -59,7 +59,7 @@ export const addDashboard = (config?: Partial<AddDashboardConfig>) => { ...@@ -59,7 +59,7 @@ export const addDashboard = (config?: Partial<AddDashboardConfig>) => {
e2e.pages.AddDashboard.visit(); e2e.pages.AddDashboard.visit();
if (annotations.length > 0 || variables.length > 0) { if (annotations.length > 0 || variables.length > 0) {
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click(); e2e.components.PageToolbar.item('Dashboard settings').click();
addAnnotations(annotations); addAnnotations(annotations);
fullConfig.variables = addVariables(variables); fullConfig.variables = addVariables(variables);
...@@ -69,7 +69,7 @@ export const addDashboard = (config?: Partial<AddDashboardConfig>) => { ...@@ -69,7 +69,7 @@ export const addDashboard = (config?: Partial<AddDashboardConfig>) => {
setDashboardTimeRange(timeRange); setDashboardTimeRange(timeRange);
e2e.pages.Dashboard.Toolbar.toolbarItems('Save dashboard').click(); e2e.components.PageToolbar.item('Save dashboard').click();
e2e.pages.SaveDashboardAsModal.newName().clear().type(title); e2e.pages.SaveDashboardAsModal.newName().clear().type(title);
e2e.pages.SaveDashboardAsModal.save().click(); e2e.pages.SaveDashboardAsModal.save().click();
e2e.flows.assertSuccessNotification(); e2e.flows.assertSuccessNotification();
......
...@@ -99,7 +99,7 @@ export const configurePanel = (config: PartialAddPanelConfig | PartialEditPanelC ...@@ -99,7 +99,7 @@ export const configurePanel = (config: PartialAddPanelConfig | PartialEditPanelC
e2e.components.Panels.Panel.title(panelTitle).click(); e2e.components.Panels.Panel.title(panelTitle).click();
e2e.components.Panels.Panel.headerItems('Edit').click(); e2e.components.Panels.Panel.headerItems('Edit').click();
} else { } else {
e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click(); e2e.components.PageToolbar.item('Add panel').click();
e2e.pages.AddDashboard.addNewPanel().click(); e2e.pages.AddDashboard.addNewPanel().click();
} }
} }
......
...@@ -33,7 +33,7 @@ const quickDelete = (uid: string) => { ...@@ -33,7 +33,7 @@ const quickDelete = (uid: string) => {
const uiDelete = (uid: string, title: string) => { const uiDelete = (uid: string, title: string) => {
e2e.pages.Dashboard.visit(uid); e2e.pages.Dashboard.visit(uid);
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click(); e2e.components.PageToolbar.item('Dashboard settings').click();
e2e.pages.Dashboard.Settings.General.deleteDashBoard().click(); e2e.pages.Dashboard.Settings.General.deleteDashBoard().click();
e2e.pages.ConfirmModal.delete().click(); e2e.pages.ConfirmModal.delete().click();
e2e.flows.assertSuccessNotification(); e2e.flows.assertSuccessNotification();
......
import { e2e } from '../index'; import { e2e } from '../index';
export const saveDashboard = () => { export const saveDashboard = () => {
e2e.pages.Dashboard.Toolbar.toolbarItems('Save dashboard').click(); e2e.components.PageToolbar.item('Save dashboard').click();
e2e.pages.SaveDashboardModal.save().click(); e2e.pages.SaveDashboardModal.save().click();
......
...@@ -4,4 +4,4 @@ import { setTimeRange, TimeRangeConfig } from './setTimeRange'; ...@@ -4,4 +4,4 @@ import { setTimeRange, TimeRangeConfig } from './setTimeRange';
export { TimeRangeConfig }; export { TimeRangeConfig };
export const setDashboardTimeRange = (config: TimeRangeConfig) => export const setDashboardTimeRange = (config: TimeRangeConfig) =>
e2e.pages.Dashboard.Toolbar.navBar().within(() => setTimeRange(config)); e2e.components.PageToolbar.container().within(() => setTimeRange(config));
import React from 'react'; import React from 'react';
import { ToolbarButton, ButtonGroup, useTheme, VerticalGroup, HorizontalGroup } from '@grafana/ui'; import { ToolbarButton, ButtonGroup, VerticalGroup, HorizontalGroup } from '@grafana/ui';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { ToolbarButtonRow } from './ToolbarButtonRow'; import { ToolbarButtonRow } from './ToolbarButtonRow';
import { ToolbarButtonVariant } from './ToolbarButton'; import { ToolbarButtonVariant } from './ToolbarButton';
import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas';
export default { export default {
title: 'Buttons/ToolbarButton', title: 'Buttons/ToolbarButton',
...@@ -12,11 +13,10 @@ export default { ...@@ -12,11 +13,10 @@ export default {
}; };
export const List = () => { export const List = () => {
const theme = useTheme();
const variants: ToolbarButtonVariant[] = ['default', 'active', 'primary', 'destructive']; const variants: ToolbarButtonVariant[] = ['default', 'active', 'primary', 'destructive'];
return ( return (
<div style={{ background: theme.colors.dashboardBg, padding: '32px' }}> <DashboardStoryCanvas>
<VerticalGroup> <VerticalGroup>
Button states Button states
<ToolbarButtonRow> <ToolbarButtonRow>
...@@ -47,6 +47,22 @@ export const List = () => { ...@@ -47,6 +47,22 @@ export const List = () => {
))} ))}
</ToolbarButtonRow> </ToolbarButtonRow>
<br /> <br />
disabled
<ToolbarButtonRow>
<ToolbarButton icon="sync" disabled>
Disabled
</ToolbarButton>
</ToolbarButtonRow>
<br />
Variants
<ToolbarButtonRow>
{variants.map((variant) => (
<ToolbarButton icon="sync" tooltip="Sync" variant={variant} key={variant}>
{variant}
</ToolbarButton>
))}
</ToolbarButtonRow>
<br />
Wrapped in noSpacing ButtonGroup Wrapped in noSpacing ButtonGroup
<ButtonGroup> <ButtonGroup>
<ToolbarButton icon="clock-nine" tooltip="Time picker"> <ToolbarButton icon="clock-nine" tooltip="Time picker">
...@@ -76,6 +92,6 @@ export const List = () => { ...@@ -76,6 +92,6 @@ export const List = () => {
</ButtonGroup> </ButtonGroup>
</HorizontalGroup> </HorizontalGroup>
</VerticalGroup> </VerticalGroup>
</div> </DashboardStoryCanvas>
); );
}; };
...@@ -7,6 +7,7 @@ import { Tooltip } from '../Tooltip/Tooltip'; ...@@ -7,6 +7,7 @@ import { Tooltip } from '../Tooltip/Tooltip';
import { Icon } from '../Icon/Icon'; import { Icon } from '../Icon/Icon';
import { getPropertiesForVariant } from './Button'; import { getPropertiesForVariant } from './Button';
import { isString } from 'lodash'; import { isString } from 'lodash';
import { selectors } from '@grafana/e2e-selectors';
export interface Props extends ButtonHTMLAttributes<HTMLButtonElement> { export interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
/** Icon name */ /** Icon name */
...@@ -31,7 +32,20 @@ export type ToolbarButtonVariant = 'default' | 'primary' | 'destructive' | 'acti ...@@ -31,7 +32,20 @@ export type ToolbarButtonVariant = 'default' | 'primary' | 'destructive' | 'acti
export const ToolbarButton = forwardRef<HTMLButtonElement, Props>( export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
( (
{ tooltip, icon, className, children, imgSrc, fullWidth, isOpen, narrow, variant = 'default', iconOnly, ...rest }, {
tooltip,
icon,
className,
children,
imgSrc,
fullWidth,
isOpen,
narrow,
variant = 'default',
iconOnly,
'aria-label': ariaLabel,
...rest
},
ref ref
) => { ) => {
const styles = useStyles(getStyles); const styles = useStyles(getStyles);
...@@ -54,10 +68,10 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>( ...@@ -54,10 +68,10 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
}); });
const body = ( const body = (
<button ref={ref} className={buttonStyles} {...rest}> <button ref={ref} className={buttonStyles} aria-label={getButttonAriaLabel(ariaLabel, tooltip)} {...rest}>
{renderIcon(icon)} {renderIcon(icon)}
{imgSrc && <img className={styles.img} src={imgSrc} />} {imgSrc && <img className={styles.img} src={imgSrc} />}
{children && !iconOnly && <span className={contentStyles}>{children}</span>} {children && !iconOnly && <div className={contentStyles}>{children}</div>}
{isOpen === false && <Icon name="angle-down" />} {isOpen === false && <Icon name="angle-down" />}
{isOpen === true && <Icon name="angle-up" />} {isOpen === true && <Icon name="angle-up" />}
</button> </button>
...@@ -73,6 +87,10 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>( ...@@ -73,6 +87,10 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
} }
); );
function getButttonAriaLabel(ariaLabel: string | undefined, tooltip: string | undefined) {
return ariaLabel ? ariaLabel : tooltip ? selectors.components.PageToolbar.item(tooltip) : undefined;
}
function renderIcon(icon: IconName | React.ReactNode) { function renderIcon(icon: IconName | React.ReactNode) {
if (!icon) { if (!icon) {
return null; return null;
...@@ -100,16 +118,13 @@ const getStyles = (theme: GrafanaTheme) => { ...@@ -100,16 +118,13 @@ const getStyles = (theme: GrafanaTheme) => {
line-height: ${theme.height.md - 2}px; line-height: ${theme.height.md - 2}px;
font-weight: ${theme.typography.weight.semibold}; font-weight: ${theme.typography.weight.semibold};
border: 1px solid ${theme.colors.border2}; border: 1px solid ${theme.colors.border2};
&:focus { &:focus {
outline: none; outline: none;
} }
&[disabled], &[disabled],
&:disabled { &:disabled {
cursor: not-allowed; cursor: not-allowed;
opacity: 0.5; opacity: 0.5;
&:hover { &:hover {
color: ${theme.colors.textWeak}; color: ${theme.colors.textWeak};
background: ${theme.colors.bg1}; background: ${theme.colors.bg1};
...@@ -119,7 +134,6 @@ const getStyles = (theme: GrafanaTheme) => { ...@@ -119,7 +134,6 @@ const getStyles = (theme: GrafanaTheme) => {
default: css` default: css`
color: ${theme.colors.textWeak}; color: ${theme.colors.textWeak};
background-color: ${theme.colors.bg1}; background-color: ${theme.colors.bg1};
&:hover { &:hover {
color: ${theme.colors.text}; color: ${theme.colors.text};
background: ${styleMixins.hoverColor(theme.colors.bg1, theme)}; background: ${styleMixins.hoverColor(theme.colors.bg1, theme)};
...@@ -129,7 +143,6 @@ const getStyles = (theme: GrafanaTheme) => { ...@@ -129,7 +143,6 @@ const getStyles = (theme: GrafanaTheme) => {
color: ${theme.palette.orangeDark}; color: ${theme.palette.orangeDark};
border-color: ${theme.palette.orangeDark}; border-color: ${theme.palette.orangeDark};
background-color: transparent; background-color: transparent;
&:hover { &:hover {
color: ${theme.colors.text}; color: ${theme.colors.text};
background: ${styleMixins.hoverColor(theme.colors.bg1, theme)}; background: ${styleMixins.hoverColor(theme.colors.bg1, theme)};
...@@ -156,15 +169,15 @@ const getStyles = (theme: GrafanaTheme) => { ...@@ -156,15 +169,15 @@ const getStyles = (theme: GrafanaTheme) => {
`, `,
content: css` content: css`
flex-grow: 1; flex-grow: 1;
`,
contentWithIcon: css`
display: none; display: none;
padding-left: ${theme.spacing.sm};
@media only screen and (min-width: ${theme.breakpoints.md}) { @media ${styleMixins.mediaUp(theme.breakpoints.md)} {
display: block; display: block;
} }
`, `,
contentWithIcon: css`
padding-left: ${theme.spacing.sm};
`,
contentWithRightIcon: css` contentWithRightIcon: css`
padding-right: ${theme.spacing.xs}; padding-right: ${theme.spacing.xs};
`, `,
......
...@@ -11,7 +11,6 @@ export interface Props<T> extends HTMLAttributes<HTMLButtonElement> { ...@@ -11,7 +11,6 @@ export interface Props<T> extends HTMLAttributes<HTMLButtonElement> {
className?: string; className?: string;
options: Array<SelectableValue<T>>; options: Array<SelectableValue<T>>;
value?: SelectableValue<T>; value?: SelectableValue<T>;
maxMenuHeight?: number;
onChange: (item: SelectableValue<T>) => void; onChange: (item: SelectableValue<T>) => void;
tooltipContent?: PopoverContent; tooltipContent?: PopoverContent;
narrow?: boolean; narrow?: boolean;
......
...@@ -10,7 +10,7 @@ import * as MonoIcon from './assets'; ...@@ -10,7 +10,7 @@ import * as MonoIcon from './assets';
import { customIcons } from './custom'; import { customIcons } from './custom';
import { SvgProps } from './assets/types'; import { SvgProps } from './assets/types';
const alwaysMonoIcons = ['grafana', 'favorite', 'heart-break', 'heart']; const alwaysMonoIcons = ['grafana', 'favorite', 'heart-break', 'heart', 'panel-add'];
export interface IconProps extends React.HTMLAttributes<HTMLDivElement> { export interface IconProps extends React.HTMLAttributes<HTMLDivElement> {
name: IconName; name: IconName;
......
import React, { FunctionComponent } from 'react'; import React, { FunctionComponent } from 'react';
import { SvgProps } from './types'; import { SvgProps } from './types';
export const PanelAdd: FunctionComponent<SvgProps> = ({ size, ...rest }) => { export const PanelAdd: FunctionComponent<SvgProps> = ({ ...rest }) => {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
enableBackground="new 0 0 117.8 64" enableBackground="new 0 0 117.8 64"
viewBox="0 0 117.8 64" viewBox="0 0 117.8 64"
xmlSpace="preserve" xmlSpace="preserve"
width={size} width={24}
height={size} height={24}
{...rest} {...rest}
> >
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="96.4427" y1="83.7013" x2="96.4427" y2="-9.4831"> <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="96.4427" y1="83.7013" x2="96.4427" y2="-9.4831">
......
...@@ -179,6 +179,8 @@ const getMenuStyles = (theme: GrafanaTheme) => { ...@@ -179,6 +179,8 @@ const getMenuStyles = (theme: GrafanaTheme) => {
color: ${linkColor}; color: ${linkColor};
display: flex; display: flex;
cursor: pointer; cursor: pointer;
padding: 5px 12px 5px 10px;
&:hover { &:hover {
color: ${linkColorHover}; color: ${linkColorHover};
text-decoration: none; text-decoration: none;
...@@ -186,7 +188,6 @@ const getMenuStyles = (theme: GrafanaTheme) => { ...@@ -186,7 +188,6 @@ const getMenuStyles = (theme: GrafanaTheme) => {
`, `,
item: css` item: css`
background: none; background: none;
padding: 5px 12px 5px 10px;
border-left: 2px solid transparent; border-left: 2px solid transparent;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
......
import React from 'react';
import { ToolbarButton, VerticalGroup } from '@grafana/ui';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { PageToolbar } from './PageToolbar';
import { StoryExample } from '../../utils/storybook/StoryExample';
import { action } from '@storybook/addon-actions';
import { IconButton } from '../IconButton/IconButton';
export default {
title: 'Layout/PageToolbar',
component: PageToolbar,
decorators: [withCenteredStory],
parameters: {},
};
export const Examples = () => {
return (
<VerticalGroup>
<StoryExample name="With non clickable title">
<PageToolbar pageIcon="bell" title="Dashboard">
<ToolbarButton icon="panel-add" />
<ToolbarButton icon="sync">Sync</ToolbarButton>
</PageToolbar>
</StoryExample>
<StoryExample name="With clickable title and parent">
<PageToolbar
pageIcon="apps"
title="A very long dashboard name"
parent="A long folder name"
onClickTitle={() => action('Title clicked')}
onClickParent={() => action('Parent clicked')}
leftItems={[
<IconButton name="share-alt" size="lg" key="share" />,
<IconButton name="favorite" iconType="mono" size="lg" key="favorite" />,
]}
>
<ToolbarButton icon="panel-add" />
<ToolbarButton icon="share-alt" />
<ToolbarButton icon="sync">Sync</ToolbarButton>
<ToolbarButton icon="cog">Settings </ToolbarButton>
</PageToolbar>
</StoryExample>
<StoryExample name="Go back version">
<PageToolbar title="Service overview / Edit panel" onGoBack={() => action('Go back')}>
<ToolbarButton icon="cog" />
<ToolbarButton icon="save" />
<ToolbarButton>Discard</ToolbarButton>
<ToolbarButton>Apply</ToolbarButton>
</PageToolbar>
</StoryExample>
</VerticalGroup>
);
};
import React, { FC, ReactNode } from 'react';
import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { useStyles } from '../../themes/ThemeContext';
import { IconName } from '../../types';
import { Icon } from '../Icon/Icon';
import { styleMixins } from '../../themes';
import { IconButton } from '../IconButton/IconButton';
import { selectors } from '@grafana/e2e-selectors';
export interface Props {
pageIcon?: IconName;
title: string;
parent?: string;
onGoBack?: () => void;
onClickTitle?: () => void;
onClickParent?: () => void;
leftItems?: ReactNode[];
children?: ReactNode;
className?: string;
isFullscreen?: boolean;
}
/** @alpha */
export const PageToolbar: FC<Props> = React.memo(
({
title,
parent,
pageIcon,
onGoBack,
children,
onClickTitle,
onClickParent,
leftItems,
isFullscreen,
className,
}) => {
const styles = useStyles(getStyles);
/**
* .page-toolbar css class is used for some legacy css view modes (TV/Kiosk) and
* media queries for mobile view when toolbar needs left padding to make room
* for mobile menu icon. This logic hopefylly can be changed when we move to a full react
* app and change how the app side menu & mobile menu is rendered.
*/
const mainStyle = cx(
'page-toolbar',
styles.toolbar,
{
['page-toolbar--fullscreen']: isFullscreen,
},
className
);
return (
<div className={mainStyle}>
<div className={styles.toolbarLeft}>
{pageIcon && !onGoBack && (
<div className={styles.pageIcon}>
<Icon name={pageIcon} size="lg" />
</div>
)}
{onGoBack && (
<div className={styles.goBackButton}>
<IconButton
name="arrow-left"
tooltip="Go back (Esc)"
tooltipPlacement="bottom"
size="xxl"
surface="dashboard"
aria-label={selectors.components.BackButton.backArrow}
onClick={onGoBack}
/>
</div>
)}
<div className={styles.titleWrapper}>
{parent && onClickParent && (
<button onClick={onClickParent} className={cx(styles.titleLink, styles.parentLink)}>
{parent} <span className={styles.parentIcon}>/</span>
</button>
)}
{onClickTitle && (
<button onClick={onClickTitle} className={styles.titleLink}>
{title}
</button>
)}
{!onClickTitle && <div className={styles.titleText}>{title}</div>}
</div>
{leftItems?.map((child, index) => (
<div className={styles.leftActionItem} key={index}>
{child}
</div>
))}
</div>
<div className={styles.spacer}></div>
{React.Children.toArray(children)
.filter(Boolean)
.map((child, index) => {
return (
<div className={styles.actionWrapper} key={index}>
{child}
</div>
);
})}
</div>
);
}
);
PageToolbar.displayName = 'PageToolbar';
const getStyles = (theme: GrafanaTheme) => {
const { spacing, typography } = theme;
const titleStyles = `
font-size: ${typography.size.lg};
padding-left: ${spacing.sm};
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 240px;
// clear default button styles
background: none;
border: none;
@media ${styleMixins.mediaUp(theme.breakpoints.xl)} {
max-width: unset;
}
`;
return {
toolbar: css`
display: flex;
background: ${theme.colors.dashboardBg};
justify-content: flex-end;
flex-wrap: wrap;
padding: 0 ${spacing.md} ${spacing.sm} ${spacing.md};
`,
toolbarLeft: css`
display: flex;
flex-grow: 1;
min-width: 0;
`,
spacer: css`
flex-grow: 1;
`,
pageIcon: css`
padding-top: ${spacing.sm};
align-items: center;
display: none;
@media ${styleMixins.mediaUp(theme.breakpoints.md)} {
display: flex;
}
`,
titleWrapper: css`
display: flex;
align-items: center;
padding-top: ${spacing.sm};
padding-right: ${spacing.sm};
min-width: 0;
overflow: hidden;
`,
goBackButton: css`
position: relative;
top: 8px;
`,
parentIcon: css`
margin-left: 4px;
`,
titleText: css`
${titleStyles};
`,
titleLink: css`
${titleStyles};
`,
parentLink: css`
display: none;
@media ${styleMixins.mediaUp(theme.breakpoints.md)} {
display: inline-block;
}
`,
actionWrapper: css`
padding-left: ${spacing.sm};
padding-top: ${spacing.sm};
`,
leftActionItem: css`
display: none;
height: 40px;
position: relative;
top: 5px;
align-items: center;
padding-left: ${spacing.xs};
@media ${styleMixins.mediaUp(theme.breakpoints.md)} {
display: flex;
}
`,
};
};
...@@ -83,7 +83,6 @@ export class RefreshPicker extends PureComponent<Props> { ...@@ -83,7 +83,6 @@ export class RefreshPicker extends PureComponent<Props> {
value={selectedValue} value={selectedValue}
options={options} options={options}
onChange={this.onChangeSelect as any} onChange={this.onChangeSelect as any}
maxMenuHeight={380}
variant={variant} variant={variant}
/> />
)} )}
......
...@@ -186,7 +186,9 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -186,7 +186,9 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
const getLabelStyles = stylesFactory((theme: GrafanaTheme) => { const getLabelStyles = stylesFactory((theme: GrafanaTheme) => {
return { return {
container: css` container: css`
display: inline-block; display: flex;
align-items: center;
white-space: nowrap;
`, `,
utc: css` utc: css`
color: ${theme.palette.orange}; color: ${theme.palette.orange};
......
...@@ -45,6 +45,7 @@ export { ModalHeader } from './Modal/ModalHeader'; ...@@ -45,6 +45,7 @@ export { ModalHeader } from './Modal/ModalHeader';
export { ModalTabsHeader } from './Modal/ModalTabsHeader'; export { ModalTabsHeader } from './Modal/ModalTabsHeader';
export { ModalTabContent } from './Modal/ModalTabContent'; export { ModalTabContent } from './Modal/ModalTabContent';
export { ModalsProvider, ModalRoot, ModalsController } from './Modal/ModalsContext'; export { ModalsProvider, ModalRoot, ModalsController } from './Modal/ModalsContext';
export { PageToolbar } from './PageLayout/PageToolbar';
// Renderless // Renderless
export { SetInterval } from './SetInterval/SetInterval'; export { SetInterval } from './SetInterval/SetInterval';
......
...@@ -34,6 +34,10 @@ export function listItemSelected(theme: GrafanaTheme): string { ...@@ -34,6 +34,10 @@ export function listItemSelected(theme: GrafanaTheme): string {
`; `;
} }
export function mediaUp(breakpoint: string) {
return `only screen and (min-width: ${breakpoint})`;
}
export const focusCss = (theme: GrafanaTheme) => ` export const focusCss = (theme: GrafanaTheme) => `
outline: 2px dotted transparent; outline: 2px dotted transparent;
outline-offset: 2px; outline-offset: 2px;
......
// Libaries // Libaries
import React, { PureComponent, FC, ReactNode } from 'react'; import React, { PureComponent, FC, ReactNode } from 'react';
import { connect, MapDispatchToProps } from 'react-redux'; import { connect, MapDispatchToProps } from 'react-redux';
import { css } from 'emotion';
// Utils & Services // Utils & Services
import { appEvents } from 'app/core/app_events'; import { appEvents } from 'app/core/app_events';
import { PlaylistSrv } from 'app/features/playlist/playlist_srv'; import { PlaylistSrv } from 'app/features/playlist/playlist_srv';
// Components // Components
import { DashNavButton } from './DashNavButton'; import { DashNavButton } from './DashNavButton';
import { DashNavTimeControls } from './DashNavTimeControls'; import { DashNavTimeControls } from './DashNavTimeControls';
import { Icon, ModalsController } from '@grafana/ui'; import { ButtonGroup, ModalsController, ToolbarButton, PageToolbar } from '@grafana/ui';
import { textUtil } from '@grafana/data'; import { textUtil } from '@grafana/data';
import { BackButton } from 'app/core/components/BackButton/BackButton';
// State // State
import { updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
...@@ -126,11 +124,23 @@ class DashNav extends PureComponent<Props> { ...@@ -126,11 +124,23 @@ class DashNav extends PureComponent<Props> {
}); });
} }
isInKioskMode() {
return !!this.props.location.query.kiosk;
}
isPlaylistRunning() {
return this.playlistSrv.isPlaying;
}
renderLeftActionsButton() { renderLeftActionsButton() {
const { dashboard } = this.props; const { dashboard } = this.props;
const { canStar, canShare, isStarred } = dashboard.meta; const { canStar, canShare, isStarred } = dashboard.meta;
const buttons: ReactNode[] = []; const buttons: ReactNode[] = [];
if (this.isInKioskMode() || this.isPlaylistRunning()) {
return [];
}
if (canStar) { if (canStar) {
buttons.push( buttons.push(
<DashNavButton <DashNavButton
...@@ -172,74 +182,49 @@ class DashNav extends PureComponent<Props> { ...@@ -172,74 +182,49 @@ class DashNav extends PureComponent<Props> {
return buttons; return buttons;
} }
renderDashboardTitleSearchButton() { renderPlaylistControls() {
const { dashboard, isFullscreen } = this.props;
const folderSymbol = css`
margin-right: 0 4px;
`;
const mainIconClassName = css`
margin-right: 8px;
margin-bottom: 3px;
`;
const folderTitle = dashboard.meta.folderTitle;
const haveFolder = (dashboard.meta.folderId ?? 0) > 0;
return ( return (
<> <ButtonGroup key="playlist-buttons">
<div> <ToolbarButton tooltip="Go to previous dashboard" icon="backward" onClick={this.onPlaylistPrev} narrow />
<div className="navbar-page-btn"> <ToolbarButton onClick={this.onPlaylistStop}>Stop playlist</ToolbarButton>
{!isFullscreen && <Icon name="apps" size="lg" className={mainIconClassName} />} <ToolbarButton tooltip="Go to next dashboard" icon="forward" onClick={this.onPlaylistNext} narrow />
{haveFolder && ( </ButtonGroup>
<>
<a className="navbar-page-btn__folder" onClick={this.onFolderNameClick}>
{folderTitle} <span className={folderSymbol}>/</span>
</a>
</>
)}
<a onClick={this.onDashboardNameClick}>{dashboard.title}</a>
</div>
</div>
<div className="navbar-buttons navbar-buttons--actions">{this.renderLeftActionsButton()}</div>
<div className="navbar__spacer" />
</>
);
}
renderBackButton() {
return (
<div className="navbar-edit">
<BackButton surface="dashboard" onClick={this.onClose} />
</div>
); );
} }
renderRightActionsButton() { renderRightActionsButton() {
const { dashboard, onAddPanel } = this.props; const { dashboard, onAddPanel, location, updateTimeZoneForSession, isFullscreen } = this.props;
const { canEdit, showSettings } = dashboard.meta; const { canEdit, showSettings } = dashboard.meta;
const { snapshot } = dashboard; const { snapshot } = dashboard;
const snapshotUrl = snapshot && snapshot.originalUrl; const snapshotUrl = snapshot && snapshot.originalUrl;
const buttons: ReactNode[] = []; const buttons: ReactNode[] = [];
if (canEdit) { const tvButton = (
buttons.push( <ToolbarButton tooltip="Cycle view mode" icon="monitor" onClick={this.onToggleTVMode} key="tv-button" />
<DashNavButton );
classSuffix="save" const timeControls = (
tooltip="Add panel" <DashNavTimeControls
icon="panel-add" dashboard={dashboard}
onClick={onAddPanel} location={location}
iconType="mono" onChangeTimeZone={updateTimeZoneForSession}
iconSize="xl" key="time-controls"
key="button-panel-add"
/> />
); );
if (this.isPlaylistRunning()) {
return [this.renderPlaylistControls(), timeControls];
}
if (this.isInKioskMode()) {
return [timeControls, tvButton];
}
if (canEdit && !isFullscreen) {
buttons.push(<ToolbarButton tooltip="Add panel" icon="panel-add" onClick={onAddPanel} key="button-panel-add" />);
buttons.push( buttons.push(
<ModalsController key="button-save"> <ModalsController key="button-save">
{({ showModal, hideModal }) => ( {({ showModal, hideModal }) => (
<DashNavButton <ToolbarButton
tooltip="Save dashboard" tooltip="Save dashboard"
classSuffix="save"
icon="save" icon="save"
onClick={() => { onClick={() => {
showModal(SaveDashboardModalProxy, { showModal(SaveDashboardModalProxy, {
...@@ -255,10 +240,9 @@ class DashNav extends PureComponent<Props> { ...@@ -255,10 +240,9 @@ class DashNav extends PureComponent<Props> {
if (snapshotUrl) { if (snapshotUrl) {
buttons.push( buttons.push(
<DashNavButton <ToolbarButton
tooltip="Open original dashboard" tooltip="Open original dashboard"
classSuffix="snapshot-origin" onClick={() => this.gotoSnapshotOrigin(snapshotUrl)}
href={textUtil.sanitizeUrl(snapshotUrl)}
icon="link" icon="link"
key="button-snapshot" key="button-snapshot"
/> />
...@@ -267,67 +251,40 @@ class DashNav extends PureComponent<Props> { ...@@ -267,67 +251,40 @@ class DashNav extends PureComponent<Props> {
if (showSettings) { if (showSettings) {
buttons.push( buttons.push(
<DashNavButton <ToolbarButton tooltip="Dashboard settings" icon="cog" onClick={this.onOpenSettings} key="button-settings" />
tooltip="Dashboard settings"
classSuffix="settings"
icon="cog"
onClick={this.onOpenSettings}
key="button-settings"
/>
); );
} }
this.addCustomContent(customRightActions, buttons); this.addCustomContent(customRightActions, buttons);
if (!dashboard.timepicker.hidden) {
buttons.push(timeControls);
}
buttons.push(tvButton);
return buttons; return buttons;
} }
gotoSnapshotOrigin(snapshotUrl: string) {
window.location.href = textUtil.sanitizeUrl(snapshotUrl);
}
render() { render() {
const { dashboard, location, isFullscreen, updateTimeZoneForSession } = this.props; const { dashboard, isFullscreen } = this.props;
const onGoBack = isFullscreen ? this.onClose : undefined;
return ( return (
<div className="navbar"> <PageToolbar
{isFullscreen && this.renderBackButton()} pageIcon={isFullscreen ? undefined : 'apps'}
{this.renderDashboardTitleSearchButton()} title={dashboard.title}
parent={dashboard.meta.folderTitle}
{this.playlistSrv.isPlaying && ( onClickTitle={this.onDashboardNameClick}
<div className="navbar-buttons navbar-buttons--playlist"> onClickParent={this.onFolderNameClick}
<DashNavButton onGoBack={onGoBack}
tooltip="Go to previous dashboard" leftItems={this.renderLeftActionsButton()}
classSuffix="tight" >
icon="step-backward" {this.renderRightActionsButton()}
onClick={this.onPlaylistPrev} </PageToolbar>
/>
<DashNavButton
tooltip="Stop playlist"
classSuffix="tight"
icon="square-shape"
onClick={this.onPlaylistStop}
/>
<DashNavButton
tooltip="Go to next dashboard"
classSuffix="tight"
icon="forward"
onClick={this.onPlaylistNext}
/>
</div>
)}
<div className="navbar-buttons navbar-buttons--actions">{this.renderRightActionsButton()}</div>
<div className="navbar-buttons navbar-buttons--tv">
<DashNavButton tooltip="Cycle view mode" classSuffix="tv" icon="monitor" onClick={this.onToggleTVMode} />
</div>
{!dashboard.timepicker.hidden && (
<div className="navbar-buttons">
<DashNavTimeControls
dashboard={dashboard}
location={location}
onChangeTimeZone={updateTimeZoneForSession}
/>
</div>
)}
</div>
); );
} }
} }
......
...@@ -18,13 +18,6 @@ interface Props { ...@@ -18,13 +18,6 @@ interface Props {
noBorder?: boolean; noBorder?: boolean;
} }
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
noBorderContainer: css`
padding: 0 ${theme.spacing.xs};
display: flex;
`,
}));
export const DashNavButton: FunctionComponent<Props> = ({ export const DashNavButton: FunctionComponent<Props> = ({
icon, icon,
iconType, iconType,
...@@ -62,7 +55,7 @@ export const DashNavButton: FunctionComponent<Props> = ({ ...@@ -62,7 +55,7 @@ export const DashNavButton: FunctionComponent<Props> = ({
<button <button
className={`btn navbar-button navbar-button--${classSuffix}`} className={`btn navbar-button navbar-button--${classSuffix}`}
onClick={onClick} onClick={onClick}
aria-label={selectors.pages.Dashboard.Toolbar.toolbarItems(tooltip)} aria-label={selectors.components.PageToolbar.item(tooltip)}
> >
{icon && <Icon name={icon} type={iconType} size={iconSize || 'lg'} />} {icon && <Icon name={icon} type={iconType} size={iconSize || 'lg'} />}
{children} {children}
...@@ -76,3 +69,10 @@ export const DashNavButton: FunctionComponent<Props> = ({ ...@@ -76,3 +69,10 @@ export const DashNavButton: FunctionComponent<Props> = ({
</Tooltip> </Tooltip>
); );
}; };
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
noBorderContainer: css`
padding: 0 ${theme.spacing.xs};
display: flex;
`,
}));
import React from 'react'; import React from 'react';
import tinycolor from 'tinycolor2';
import { css } from 'emotion'; import { css } from 'emotion';
import { CSSTransition } from 'react-transition-group'; import { CSSTransition } from 'react-transition-group';
import { useTheme, Tooltip, stylesFactory, selectThemeVariant, ButtonGroup, ToolbarButton } from '@grafana/ui'; import { useTheme, Tooltip, stylesFactory, ButtonGroup, ToolbarButton } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const bgColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.dark1 }, theme.type);
const orangeLighter = tinycolor(theme.palette.orangeDark).lighten(10).toString();
const pulseTextColor = tinycolor(theme.palette.orangeDark).desaturate(90).toString();
return {
isLive: css`
label: isLive;
border-color: ${theme.palette.orangeDark};
color: ${theme.palette.orangeDark};
background: transparent;
&:focus {
background: transparent;
border-color: ${theme.palette.orangeDark};
color: ${theme.palette.orangeDark};
}
&:hover {
background-color: ${bgColor};
}
&:active,
&:hover {
border-color: ${orangeLighter};
color: ${orangeLighter};
}
`,
isPaused: css`
label: isPaused;
border-color: ${theme.palette.orangeDark};
background: transparent;
animation: pulse 3s ease-out 0s infinite normal forwards;
&:focus {
background: transparent;
border-color: ${theme.palette.orangeDark};
}
&:hover {
background-color: ${bgColor};
}
&:active,
&:hover {
border-color: ${orangeLighter};
}
@keyframes pulse {
0% {
color: ${pulseTextColor};
}
50% {
color: ${theme.palette.orangeDark};
}
100% {
color: ${pulseTextColor};
}
}
`,
stopButtonEnter: css`
label: stopButtonEnter;
width: 0;
opacity: 0;
overflow: hidden;
`,
stopButtonEnterActive: css`
label: stopButtonEnterActive;
opacity: 1;
width: 32px;
`,
stopButtonExit: css`
label: stopButtonExit;
width: 32px;
opacity: 1;
overflow: hidden;
`,
stopButtonExitActive: css`
label: stopButtonExitActive;
opacity: 0;
width: 0;
`,
};
});
type LiveTailButtonProps = { type LiveTailButtonProps = {
splitted: boolean; splitted: boolean;
start: () => void; start: () => void;
...@@ -92,6 +13,7 @@ type LiveTailButtonProps = { ...@@ -92,6 +13,7 @@ type LiveTailButtonProps = {
isLive: boolean; isLive: boolean;
isPaused: boolean; isPaused: boolean;
}; };
export function LiveTailButton(props: LiveTailButtonProps) { export function LiveTailButton(props: LiveTailButtonProps) {
const { start, pause, resume, isLive, isPaused, stop, splitted } = props; const { start, pause, resume, isLive, isPaused, stop, splitted } = props;
const theme = useTheme(); const theme = useTheme();
...@@ -134,3 +56,30 @@ export function LiveTailButton(props: LiveTailButtonProps) { ...@@ -134,3 +56,30 @@ export function LiveTailButton(props: LiveTailButtonProps) {
</ButtonGroup> </ButtonGroup>
); );
} }
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
stopButtonEnter: css`
label: stopButtonEnter;
width: 0;
opacity: 0;
overflow: hidden;
`,
stopButtonEnterActive: css`
label: stopButtonEnterActive;
opacity: 1;
width: 32px;
`,
stopButtonExit: css`
label: stopButtonExit;
width: 32px;
opacity: 1;
overflow: hidden;
`,
stopButtonExitActive: css`
label: stopButtonExitActive;
opacity: 0;
width: 0;
`,
};
});
...@@ -76,7 +76,6 @@ export const UnconnectedReturnToDashboardButton: FC<Props> = ({ ...@@ -76,7 +76,6 @@ export const UnconnectedReturnToDashboardButton: FC<Props> = ({
data-testid="returnButtonWithChanges" data-testid="returnButtonWithChanges"
options={[{ label: 'Return to panel with changes', value: '' }]} options={[{ label: 'Return to panel with changes', value: '' }]}
onChange={() => returnToPanel({ withChanges: true })} onChange={() => returnToPanel({ withChanges: true })}
maxMenuHeight={380}
/> />
</ButtonGroup> </ButtonGroup>
); );
......
.navbar-buttons--zoom {
display: none;
}
.navbar-page-btn {
max-width: 200px;
}
.navbar-buttons--tv,
.navbar-buttons--actions {
display: none;
}
// Media queries // Media queries
// --------------------- // ---------------------
...@@ -21,34 +8,3 @@ ...@@ -21,34 +8,3 @@
font-size: 16px; font-size: 16px;
} }
} }
@include media-breakpoint-up(sm) {
.navbar-page-btn {
max-width: 250px;
}
}
@include media-breakpoint-up(md) {
.navbar-buttons--tv,
.navbar-buttons--actions {
display: flex;
}
.navbar-page-btn {
max-width: 325px;
}
}
@include media-breakpoint-up(lg) {
.navbar-buttons--zoom {
display: flex;
}
.navbar-page-btn {
max-width: 450px;
}
}
@include media-breakpoint-up(xl) {
.navbar-page-btn {
max-width: 600px;
}
}
.navbar {
position: relative;
z-index: $zindex-navbar-fixed;
height: $navbarHeight;
padding: 0 16px 0 60px;
display: flex;
flex-grow: 0;
flex-shrink: 0;
border-bottom: 1px solid transparent;
transition-duration: 350ms;
transition-timing-function: ease-in-out;
transition-property: box-shadow, border-bottom;
@include media-breakpoint-up(md) {
padding-left: $dashboard-padding;
margin-left: 0;
}
&--edit {
background: $panel-bg;
border-bottom: $panel-border;
box-shadow: 0 0 10px $dashboard-bg;
}
}
@mixin navbar-alt-look() {
background: $page-header-bg;
box-shadow: $search-shadow;
border-bottom: $navbarBorder;
}
.panel-in-fullscreen,
.panel-in-fullscreen.view-mode--tv {
.navbar {
padding-left: $navbar-padding;
}
.navbar-button--add-panel,
.navbar-button--star,
.navbar-button--tv {
display: none;
}
}
.navbar-page-btn { .navbar-page-btn {
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
...@@ -75,35 +31,6 @@ ...@@ -75,35 +31,6 @@
} }
} }
.navbar-page-btn__folder {
display: none;
padding-right: 4px;
@include media-breakpoint-up(lg) {
display: inline-block;
}
}
.navbar-buttons {
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: 10px;
&--close {
display: none;
margin-right: 0;
}
&--zoom {
margin-right: 0;
}
}
.navbar__spacer {
flex-grow: 1;
}
.navbar-button { .navbar-button {
background-color: $panel-bg; background-color: $panel-bg;
......
...@@ -22,10 +22,6 @@ ...@@ -22,10 +22,6 @@
} }
.panel-in-fullscreen { .panel-in-fullscreen {
.sidemenu {
display: none;
}
.search-container { .search-container {
left: 0 !important; left: 0 !important;
} }
......
...@@ -190,10 +190,10 @@ li.sidemenu-org-switcher { ...@@ -190,10 +190,10 @@ li.sidemenu-org-switcher {
} }
img { img {
width: 30px; width: 26px;
position: relative; position: relative;
top: 5px; top: 5px;
left: 4px; left: 8px;
} }
} }
...@@ -234,11 +234,12 @@ li.sidemenu-org-switcher { ...@@ -234,11 +234,12 @@ li.sidemenu-org-switcher {
} }
.sidemenu__logo_small_breakpoint { .sidemenu__logo_small_breakpoint {
padding: 14px 10px 26px 13px; padding: 13px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: baseline; align-items: baseline;
cursor: pointer;
.fa-bars { .fa-bars {
font-size: 25px; font-size: 25px;
......
...@@ -2,41 +2,10 @@ ...@@ -2,41 +2,10 @@
.react-resizable-handle, .react-resizable-handle,
.add-row-panel-hint, .add-row-panel-hint,
.dash-row-menu-container, .dash-row-menu-container,
.navbar-buttons--actions,
.panel-info-corner--info, .panel-info-corner--info,
.panel-info-corner--links { .panel-info-corner--links {
display: none; display: none;
} }
.navbar-page-btn {
i {
display: none;
}
i.navbar-page-btn__folder-icon {
display: inline-block;
opacity: inherit;
}
}
.navbar-button--zoom {
display: none;
}
}
.view-mode--playlist {
@extend .view-mode--inactive;
}
// https://github.com/grafana/grafana/issues/18114
.view-mode--tv.panel-in-fullscreen {
.navbar {
padding-left: $navbar-padding;
}
.navbar-page-btn {
transform: none;
}
} }
.view-mode--tv { .view-mode--tv {
...@@ -60,8 +29,12 @@ ...@@ -60,8 +29,12 @@
} }
} }
.navbar { .page-toolbar {
padding-left: $side-menu-width; padding-left: $side-menu-width;
&--fullscreen {
padding-left: $space-md;
}
} }
.submenu-controls { .submenu-controls {
...@@ -73,15 +46,21 @@ ...@@ -73,15 +46,21 @@
@extend .view-mode--tv; @extend .view-mode--tv;
.sidemenu, .sidemenu,
.navbar { .page-toolbar {
display: none; display: none;
} }
.scroll-canvas--dashboard {
height: 100%;
}
.submenu-controls { .submenu-controls {
display: none; display: none;
} }
} }
@include media-breakpoint-down(sm) {
div.page-toolbar {
padding-left: 53px;
&--fullscreen {
padding-left: $space-md;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment