diff --git a/e2e/shared/smokeTestScenario.ts b/e2e/shared/smokeTestScenario.ts index 00e3ecb9cf2..5b7171c6880 100644 --- a/e2e/shared/smokeTestScenario.ts +++ b/e2e/shared/smokeTestScenario.ts @@ -8,7 +8,7 @@ export const smokeTestScenario = { skipScenario: false, scenario: () => { e2e.flows.openDashboard(); - e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click(); + e2e.components.PageToolbar.item('Add panel').click(); e2e.pages.AddDashboard.addNewPanel().click(); e2e.components.DataSource.TestData.QueryTab.scenarioSelectContainer() diff --git a/e2e/suite1/specs/dashboard-time-zone.spec.ts b/e2e/suite1/specs/dashboard-time-zone.spec.ts index 0bb93b1ad08..7b4f99464a9 100644 --- a/e2e/suite1/specs/dashboard-time-zone.spec.ts +++ b/e2e/suite1/specs/dashboard-time-zone.spec.ts @@ -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() .should('be.visible') diff --git a/e2e/suite1/specs/select-focus.spec.ts b/e2e/suite1/specs/select-focus.spec.ts index 8c0cae407fb..77d7b6a1c0e 100644 --- a/e2e/suite1/specs/select-focus.spec.ts +++ b/e2e/suite1/specs/select-focus.spec.ts @@ -8,7 +8,7 @@ e2e.scenario({ skipScenario: false, scenario: () => { e2e.flows.openDashboard({ uid: '5SdHCadmz' }); - e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click(); + e2e.components.PageToolbar.item('Dashboard settings').click(); e2e.components.FolderPicker.container() .should('be.visible') diff --git a/e2e/suite1/specs/templating-dashboard-links-and-variables.ts b/e2e/suite1/specs/templating-dashboard-links-and-variables.ts index b1fa3b42883..926f63f98ae 100644 --- a/e2e/suite1/specs/templating-dashboard-links-and-variables.ts +++ b/e2e/suite1/specs/templating-dashboard-links-and-variables.ts @@ -50,7 +50,7 @@ e2e.scenario({ 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'); diff --git a/e2e/suite1/specs/variables/set-options-from-ui.ts b/e2e/suite1/specs/variables/set-options-from-ui.ts index 77275ff9894..3f0e6b0aaef 100644 --- a/e2e/suite1/specs/variables/set-options-from-ui.ts +++ b/e2e/suite1/specs/variables/set-options-from-ui.ts @@ -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('B').should('be.visible').click(); - e2e.pages.Dashboard.Toolbar.navBar().click(); + e2e.components.PageToolbar.container().click(); e2e().wait('@query'); @@ -77,7 +77,7 @@ describe('Variables - Set options from ui', () => { e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A').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(500); @@ -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.submenuItemValueDropDownOptionTexts('A').should('be.visible').click(); - e2e.pages.Dashboard.Toolbar.navBar().click(); + e2e.components.PageToolbar.container().click(); e2e().wait('@query'); e2e().wait(500); diff --git a/e2e/suite1/specs/variables/textbox-variables.ts b/e2e/suite1/specs/variables/textbox-variables.ts index 7e353947783..f9ced2945df 100644 --- a/e2e/suite1/specs/variables/textbox-variables.ts +++ b/e2e/suite1/specs/variables/textbox-variables.ts @@ -185,7 +185,7 @@ function copyExistingDashboard() { } 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) { e2e.pages.SaveDashboardModal.saveVariables().should('exist').click({ force: true }); @@ -212,7 +212,7 @@ function validateTextboxAndMarkup(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(); @@ -245,7 +245,7 @@ function changeTextBoxInput() { } 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(); diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index a25d1164a4f..29e80da67ce 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -56,8 +56,8 @@ export const Components = { }, OptionsPane: { content: 'Panel editor option pane content', - close: 'Dashboard navigation bar button Close options pane', - open: 'Dashboard navigation bar button Open options pane', + close: 'Page toolbar button Close options pane', + open: 'Page toolbar button Open options pane', select: 'Panel editor option pane select', tab: (title: string) => `Panel editor option pane tab ${title}`, }, @@ -123,6 +123,10 @@ export const Components = { calculationsLabel: 'Transform calculations label', }, }, + PageToolbar: { + container: () => '.page-toolbar', + item: (tooltip: string) => `Page toolbar button ${tooltip}`, + }, QueryEditorToolbarItem: { button: (title: string) => `QueryEditor toolbar item button ${title}`, }, diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 88cbb7edc2a..204b56a191b 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -34,10 +34,6 @@ export const Pages = { }, Dashboard: { url: (uid: string) => `/d/${uid}`, - Toolbar: { - toolbarItems: (button: string) => `Dashboard navigation bar button ${button}`, - navBar: () => '.navbar', - }, SubMenu: { submenuItem: 'Dashboard template variables submenu item', submenuItemLabels: (item: string) => `Dashboard template variables submenu Label ${item}`, diff --git a/packages/grafana-e2e/src/flows/addDashboard.ts b/packages/grafana-e2e/src/flows/addDashboard.ts index 7a74218ecab..a94a679db2d 100644 --- a/packages/grafana-e2e/src/flows/addDashboard.ts +++ b/packages/grafana-e2e/src/flows/addDashboard.ts @@ -59,7 +59,7 @@ export const addDashboard = (config?: Partial) => { e2e.pages.AddDashboard.visit(); if (annotations.length > 0 || variables.length > 0) { - e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click(); + e2e.components.PageToolbar.item('Dashboard settings').click(); addAnnotations(annotations); fullConfig.variables = addVariables(variables); @@ -69,7 +69,7 @@ export const addDashboard = (config?: Partial) => { 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.save().click(); e2e.flows.assertSuccessNotification(); diff --git a/packages/grafana-e2e/src/flows/configurePanel.ts b/packages/grafana-e2e/src/flows/configurePanel.ts index fb65ecb157d..0c4673c36ad 100644 --- a/packages/grafana-e2e/src/flows/configurePanel.ts +++ b/packages/grafana-e2e/src/flows/configurePanel.ts @@ -99,7 +99,7 @@ export const configurePanel = (config: PartialAddPanelConfig | PartialEditPanelC e2e.components.Panels.Panel.title(panelTitle).click(); e2e.components.Panels.Panel.headerItems('Edit').click(); } else { - e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click(); + e2e.components.PageToolbar.item('Add panel').click(); e2e.pages.AddDashboard.addNewPanel().click(); } } diff --git a/packages/grafana-e2e/src/flows/deleteDashboard.ts b/packages/grafana-e2e/src/flows/deleteDashboard.ts index a5a34f5d030..9bb90a6412e 100644 --- a/packages/grafana-e2e/src/flows/deleteDashboard.ts +++ b/packages/grafana-e2e/src/flows/deleteDashboard.ts @@ -33,7 +33,7 @@ const quickDelete = (uid: string) => { const uiDelete = (uid: string, title: string) => { 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.ConfirmModal.delete().click(); e2e.flows.assertSuccessNotification(); diff --git a/packages/grafana-e2e/src/flows/saveDashboard.ts b/packages/grafana-e2e/src/flows/saveDashboard.ts index 94541fbd858..37d5238f431 100644 --- a/packages/grafana-e2e/src/flows/saveDashboard.ts +++ b/packages/grafana-e2e/src/flows/saveDashboard.ts @@ -1,7 +1,7 @@ import { e2e } from '../index'; export const saveDashboard = () => { - e2e.pages.Dashboard.Toolbar.toolbarItems('Save dashboard').click(); + e2e.components.PageToolbar.item('Save dashboard').click(); e2e.pages.SaveDashboardModal.save().click(); diff --git a/packages/grafana-e2e/src/flows/setDashboardTimeRange.ts b/packages/grafana-e2e/src/flows/setDashboardTimeRange.ts index c061fcdc4fd..9b04412fd0c 100644 --- a/packages/grafana-e2e/src/flows/setDashboardTimeRange.ts +++ b/packages/grafana-e2e/src/flows/setDashboardTimeRange.ts @@ -4,4 +4,4 @@ import { setTimeRange, TimeRangeConfig } from './setTimeRange'; export { TimeRangeConfig }; export const setDashboardTimeRange = (config: TimeRangeConfig) => - e2e.pages.Dashboard.Toolbar.navBar().within(() => setTimeRange(config)); + e2e.components.PageToolbar.container().within(() => setTimeRange(config)); diff --git a/packages/grafana-ui/src/components/Button/ToolbarButton.story.tsx b/packages/grafana-ui/src/components/Button/ToolbarButton.story.tsx index 6f1dbfa0a99..d20f90cde72 100644 --- a/packages/grafana-ui/src/components/Button/ToolbarButton.story.tsx +++ b/packages/grafana-ui/src/components/Button/ToolbarButton.story.tsx @@ -1,8 +1,9 @@ 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 { ToolbarButtonRow } from './ToolbarButtonRow'; import { ToolbarButtonVariant } from './ToolbarButton'; +import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas'; export default { title: 'Buttons/ToolbarButton', @@ -12,11 +13,10 @@ export default { }; export const List = () => { - const theme = useTheme(); const variants: ToolbarButtonVariant[] = ['default', 'active', 'primary', 'destructive']; return ( -
+ Button states @@ -47,6 +47,22 @@ export const List = () => { ))}
+ disabled + + + Disabled + + +
+ Variants + + {variants.map((variant) => ( + + {variant} + + ))} + +
Wrapped in noSpacing ButtonGroup @@ -76,6 +92,6 @@ export const List = () => {
-
+ ); }; diff --git a/packages/grafana-ui/src/components/Button/ToolbarButton.tsx b/packages/grafana-ui/src/components/Button/ToolbarButton.tsx index 6b7506a8974..f7db756cbfe 100644 --- a/packages/grafana-ui/src/components/Button/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/Button/ToolbarButton.tsx @@ -7,6 +7,7 @@ import { Tooltip } from '../Tooltip/Tooltip'; import { Icon } from '../Icon/Icon'; import { getPropertiesForVariant } from './Button'; import { isString } from 'lodash'; +import { selectors } from '@grafana/e2e-selectors'; export interface Props extends ButtonHTMLAttributes { /** Icon name */ @@ -31,7 +32,20 @@ export type ToolbarButtonVariant = 'default' | 'primary' | 'destructive' | 'acti export const ToolbarButton = forwardRef( ( - { 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 ) => { const styles = useStyles(getStyles); @@ -54,10 +68,10 @@ export const ToolbarButton = forwardRef( }); const body = ( - @@ -73,6 +87,10 @@ export const ToolbarButton = forwardRef( } ); +function getButttonAriaLabel(ariaLabel: string | undefined, tooltip: string | undefined) { + return ariaLabel ? ariaLabel : tooltip ? selectors.components.PageToolbar.item(tooltip) : undefined; +} + function renderIcon(icon: IconName | React.ReactNode) { if (!icon) { return null; @@ -100,16 +118,13 @@ const getStyles = (theme: GrafanaTheme) => { line-height: ${theme.height.md - 2}px; font-weight: ${theme.typography.weight.semibold}; border: 1px solid ${theme.colors.border2}; - &:focus { outline: none; } - &[disabled], &:disabled { cursor: not-allowed; opacity: 0.5; - &:hover { color: ${theme.colors.textWeak}; background: ${theme.colors.bg1}; @@ -119,7 +134,6 @@ const getStyles = (theme: GrafanaTheme) => { default: css` color: ${theme.colors.textWeak}; background-color: ${theme.colors.bg1}; - &:hover { color: ${theme.colors.text}; background: ${styleMixins.hoverColor(theme.colors.bg1, theme)}; @@ -129,7 +143,6 @@ const getStyles = (theme: GrafanaTheme) => { color: ${theme.palette.orangeDark}; border-color: ${theme.palette.orangeDark}; background-color: transparent; - &:hover { color: ${theme.colors.text}; background: ${styleMixins.hoverColor(theme.colors.bg1, theme)}; @@ -156,14 +169,14 @@ const getStyles = (theme: GrafanaTheme) => { `, content: css` flex-grow: 1; - display: none; - - @media only screen and (min-width: ${theme.breakpoints.md}) { - display: block; - } `, contentWithIcon: css` + display: none; padding-left: ${theme.spacing.sm}; + + @media ${styleMixins.mediaUp(theme.breakpoints.md)} { + display: block; + } `, contentWithRightIcon: css` padding-right: ${theme.spacing.xs}; diff --git a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx index 785648345b6..eb65c4bd7ca 100644 --- a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx +++ b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx @@ -11,7 +11,6 @@ export interface Props extends HTMLAttributes { className?: string; options: Array>; value?: SelectableValue; - maxMenuHeight?: number; onChange: (item: SelectableValue) => void; tooltipContent?: PopoverContent; narrow?: boolean; diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx index c531dcb90a4..f7c60eacdc1 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.tsx @@ -10,7 +10,7 @@ import * as MonoIcon from './assets'; import { customIcons } from './custom'; 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 { name: IconName; diff --git a/packages/grafana-ui/src/components/Icon/assets/PanelAdd.tsx b/packages/grafana-ui/src/components/Icon/assets/PanelAdd.tsx index d56ea7971da..72dbc8332a0 100644 --- a/packages/grafana-ui/src/components/Icon/assets/PanelAdd.tsx +++ b/packages/grafana-ui/src/components/Icon/assets/PanelAdd.tsx @@ -1,15 +1,15 @@ import React, { FunctionComponent } from 'react'; import { SvgProps } from './types'; -export const PanelAdd: FunctionComponent = ({ size, ...rest }) => { +export const PanelAdd: FunctionComponent = ({ ...rest }) => { return ( diff --git a/packages/grafana-ui/src/components/Menu/Menu.tsx b/packages/grafana-ui/src/components/Menu/Menu.tsx index 29d3444bff3..cc3b5d0a695 100644 --- a/packages/grafana-ui/src/components/Menu/Menu.tsx +++ b/packages/grafana-ui/src/components/Menu/Menu.tsx @@ -179,6 +179,8 @@ const getMenuStyles = (theme: GrafanaTheme) => { color: ${linkColor}; display: flex; cursor: pointer; + padding: 5px 12px 5px 10px; + &:hover { color: ${linkColorHover}; text-decoration: none; @@ -186,7 +188,6 @@ const getMenuStyles = (theme: GrafanaTheme) => { `, item: css` background: none; - padding: 5px 12px 5px 10px; border-left: 2px solid transparent; cursor: pointer; white-space: nowrap; diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx new file mode 100644 index 00000000000..d2d927ababf --- /dev/null +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx @@ -0,0 +1,53 @@ +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 ( + + + + + Sync + + + + action('Title clicked')} + onClickParent={() => action('Parent clicked')} + leftItems={[ + , + , + ]} + > + + + Sync + Settings + + + + action('Go back')}> + + + Discard + Apply + + + + ); +}; diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx new file mode 100644 index 00000000000..893d9e0e913 --- /dev/null +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx @@ -0,0 +1,202 @@ +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 = 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 ( +
+
+ {pageIcon && !onGoBack && ( +
+ +
+ )} + {onGoBack && ( +
+ +
+ )} +
+ {parent && onClickParent && ( + + )} + {onClickTitle && ( + + )} + {!onClickTitle &&
{title}
} +
+ {leftItems?.map((child, index) => ( +
+ {child} +
+ ))} +
+
+ {React.Children.toArray(children) + .filter(Boolean) + .map((child, index) => { + return ( +
+ {child} +
+ ); + })} +
+ ); + } +); + +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; + } + `, + }; +}; diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx index af0dbc8fefe..ed18fe8842f 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx @@ -83,7 +83,6 @@ export class RefreshPicker extends PureComponent { value={selectedValue} options={options} onChange={this.onChangeSelect as any} - maxMenuHeight={380} variant={variant} /> )} diff --git a/packages/grafana-ui/src/components/TimePicker/TimeRangePicker.tsx b/packages/grafana-ui/src/components/TimePicker/TimeRangePicker.tsx index 457075c7efd..14a92c80f25 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimeRangePicker.tsx @@ -186,7 +186,9 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { const getLabelStyles = stylesFactory((theme: GrafanaTheme) => { return { container: css` - display: inline-block; + display: flex; + align-items: center; + white-space: nowrap; `, utc: css` color: ${theme.palette.orange}; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index e6062c01aef..49bb8f21ee4 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -45,6 +45,7 @@ export { ModalHeader } from './Modal/ModalHeader'; export { ModalTabsHeader } from './Modal/ModalTabsHeader'; export { ModalTabContent } from './Modal/ModalTabContent'; export { ModalsProvider, ModalRoot, ModalsController } from './Modal/ModalsContext'; +export { PageToolbar } from './PageLayout/PageToolbar'; // Renderless export { SetInterval } from './SetInterval/SetInterval'; diff --git a/packages/grafana-ui/src/themes/mixins.ts b/packages/grafana-ui/src/themes/mixins.ts index 49c3d0142e9..f09be644839 100644 --- a/packages/grafana-ui/src/themes/mixins.ts +++ b/packages/grafana-ui/src/themes/mixins.ts @@ -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) => ` outline: 2px dotted transparent; outline-offset: 2px; diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 4023fad0ea0..1b583aab1de 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -1,16 +1,14 @@ // Libaries import React, { PureComponent, FC, ReactNode } from 'react'; import { connect, MapDispatchToProps } from 'react-redux'; -import { css } from 'emotion'; // Utils & Services import { appEvents } from 'app/core/app_events'; import { PlaylistSrv } from 'app/features/playlist/playlist_srv'; // Components import { DashNavButton } from './DashNavButton'; import { DashNavTimeControls } from './DashNavTimeControls'; -import { Icon, ModalsController } from '@grafana/ui'; +import { ButtonGroup, ModalsController, ToolbarButton, PageToolbar } from '@grafana/ui'; import { textUtil } from '@grafana/data'; -import { BackButton } from 'app/core/components/BackButton/BackButton'; // State import { updateLocation } from 'app/core/actions'; import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; @@ -126,11 +124,23 @@ class DashNav extends PureComponent { }); } + isInKioskMode() { + return !!this.props.location.query.kiosk; + } + + isPlaylistRunning() { + return this.playlistSrv.isPlaying; + } + renderLeftActionsButton() { const { dashboard } = this.props; const { canStar, canShare, isStarred } = dashboard.meta; - const buttons: ReactNode[] = []; + + if (this.isInKioskMode() || this.isPlaylistRunning()) { + return []; + } + if (canStar) { buttons.push( { return buttons; } - renderDashboardTitleSearchButton() { - 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; - + renderPlaylistControls() { return ( - <> -
-
- {!isFullscreen && } - {haveFolder && ( - <> - - {folderTitle} / - - - )} - {dashboard.title} -
-
-
{this.renderLeftActionsButton()}
-
- - ); - } - - renderBackButton() { - return ( -
- -
+ + + Stop playlist + + ); } renderRightActionsButton() { - const { dashboard, onAddPanel } = this.props; + const { dashboard, onAddPanel, location, updateTimeZoneForSession, isFullscreen } = this.props; const { canEdit, showSettings } = dashboard.meta; const { snapshot } = dashboard; const snapshotUrl = snapshot && snapshot.originalUrl; - const buttons: ReactNode[] = []; - if (canEdit) { - buttons.push( - - ); + const tvButton = ( + + ); + const timeControls = ( + + ); + + if (this.isPlaylistRunning()) { + return [this.renderPlaylistControls(), timeControls]; + } + + if (this.isInKioskMode()) { + return [timeControls, tvButton]; + } + + if (canEdit && !isFullscreen) { + buttons.push(); buttons.push( {({ showModal, hideModal }) => ( - { showModal(SaveDashboardModalProxy, { @@ -255,10 +240,9 @@ class DashNav extends PureComponent { if (snapshotUrl) { buttons.push( - this.gotoSnapshotOrigin(snapshotUrl)} icon="link" key="button-snapshot" /> @@ -267,67 +251,40 @@ class DashNav extends PureComponent { if (showSettings) { buttons.push( - + ); } this.addCustomContent(customRightActions, buttons); + + if (!dashboard.timepicker.hidden) { + buttons.push(timeControls); + } + + buttons.push(tvButton); return buttons; } + gotoSnapshotOrigin(snapshotUrl: string) { + window.location.href = textUtil.sanitizeUrl(snapshotUrl); + } + render() { - const { dashboard, location, isFullscreen, updateTimeZoneForSession } = this.props; + const { dashboard, isFullscreen } = this.props; + const onGoBack = isFullscreen ? this.onClose : undefined; return ( -
- {isFullscreen && this.renderBackButton()} - {this.renderDashboardTitleSearchButton()} - - {this.playlistSrv.isPlaying && ( -
- - - -
- )} - -
{this.renderRightActionsButton()}
- -
- -
- - {!dashboard.timepicker.hidden && ( -
- -
- )} -
+ + {this.renderRightActionsButton()} + ); } } diff --git a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx index 9eca8d3a64f..ae2908c3733 100644 --- a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx @@ -18,13 +18,6 @@ interface Props { noBorder?: boolean; } -const getStyles = stylesFactory((theme: GrafanaTheme) => ({ - noBorderContainer: css` - padding: 0 ${theme.spacing.xs}; - display: flex; - `, -})); - export const DashNavButton: FunctionComponent = ({ icon, iconType, @@ -62,7 +55,7 @@ export const DashNavButton: FunctionComponent = ({