diff --git a/packages/grafana-ui/src/components/Forms/Input/Input.tsx b/packages/grafana-ui/src/components/Forms/Input/Input.tsx index c5e6f9bdf10..06df80b0275 100644 --- a/packages/grafana-ui/src/components/Forms/Input/Input.tsx +++ b/packages/grafana-ui/src/components/Forms/Input/Input.tsx @@ -211,7 +211,7 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false }: StyleDe }); export const Input = React.forwardRef((props, ref) => { - const { addonAfter, addonBefore, prefix, suffix, invalid, loading, size = 'auto', ...restProps } = props; + const { className, addonAfter, addonBefore, prefix, suffix, invalid, loading, size = 'auto', ...restProps } = props; /** * Prefix & suffix are positioned absolutely within inputWrapper. We use client rects below to apply correct padding to the input * when prefix/suffix is larger than default (28px = 16px(icon) + 12px(left/right paddings)). @@ -224,7 +224,7 @@ export const Input = React.forwardRef((props, ref) => { const styles = getInputStyles({ theme, invalid: !!invalid }); return ( -
+
{!!addonBefore &&
{addonBefore}
}
diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx index b986e6de678..f1098407ead 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.tsx @@ -13,11 +13,13 @@ export interface IconProps { const getIconStyles = stylesFactory(() => { return { icon: css` - display: inline-block; + display: inline-flex; width: 16px; + align-items: center; height: 16px; text-align: center; font-size: 14px; + &:before { vertical-align: middle; } diff --git a/packages/grafana-ui/src/components/Icon/types.ts b/packages/grafana-ui/src/components/Icon/types.ts index 23d1ca12408..ce31ea64515 100644 --- a/packages/grafana-ui/src/components/Icon/types.ts +++ b/packages/grafana-ui/src/components/Icon/types.ts @@ -89,6 +89,7 @@ export type IconType = | 'times-circle-o' | 'check-circle-o' | 'ban' + | 'remove' | 'arrow-left' | 'arrow-right' | 'arrow-up' diff --git a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx index 40abf161d6c..1820e2078cb 100644 --- a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx +++ b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx @@ -24,6 +24,8 @@ const getTabsBarStyles = stylesFactory((theme: GrafanaTheme, hideBorder = false) position: relative; top: 1px; display: flex; + // Sometimes TabsBar is rendered without any tabs, and should preserve height + height: 41px; `, }; }); diff --git a/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap b/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap index 15abf6f0af2..db07518ab12 100644 --- a/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap +++ b/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap @@ -97,7 +97,7 @@ exports[`ServerStats Should render table with stats 1`] = ` className="page-header__tabs" >
  • void; href?: string; + children?: React.ReactNode; } -export const DashNavButton: FunctionComponent = ({ icon, tooltip, classSuffix, onClick, href }) => { +export const DashNavButton: FunctionComponent = ({ icon, tooltip, classSuffix, onClick, href, children }) => { if (onClick) { return ( - + ); } return ( - + - + {icon && } + {children} ); diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx index c80338a90ba..98fd49b6b09 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx @@ -1,24 +1,48 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useState, CSSProperties } from 'react'; +import Transition from 'react-transition-group/Transition'; import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin } from '@grafana/data'; import { DashboardModel, PanelModel } from '../../state'; -import { CustomScrollbar, stylesFactory, Tab, TabContent, TabsBar, useTheme, Container } from '@grafana/ui'; +import { + CustomScrollbar, + stylesFactory, + Tab, + TabContent, + TabsBar, + useTheme, + Container, + Forms, + Icon, +} from '@grafana/ui'; import { DefaultFieldConfigEditor, OverrideFieldConfigEditor } from './FieldConfigEditor'; import { AngularPanelOptions } from './AngularPanelOptions'; import { css } from 'emotion'; import { GeneralPanelOptions } from './GeneralPanelOptions'; import { PanelOptionsEditor } from './PanelOptionsEditor'; +import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton'; export const OptionsPaneContent: React.FC<{ plugin?: PanelPlugin; panel: PanelModel; data: PanelData; dashboard: DashboardModel; + onClose: () => void; onFieldConfigsChange: (config: FieldConfigSource) => void; onPanelOptionsChanged: (options: any) => void; onPanelConfigChange: (configKey: string, value: any) => void; -}> = ({ plugin, panel, data, onFieldConfigsChange, onPanelOptionsChanged, onPanelConfigChange, dashboard }) => { +}> = ({ + plugin, + panel, + data, + onFieldConfigsChange, + onPanelOptionsChanged, + onPanelConfigChange, + onClose, + dashboard, +}) => { const theme = useTheme(); const styles = getStyles(theme); + const [activeTab, setActiveTab] = useState('defaults'); + const [isSearching, setSearchMode] = useState(false); const renderFieldOptions = useCallback( (plugin: PanelPlugin) => { @@ -105,16 +129,75 @@ export const OptionsPaneContent: React.FC<{ [data, plugin, panel, onFieldConfigsChange] ); - const [activeTab, setActiveTab] = useState('defaults'); + const renderSearchInput = useCallback(() => { + const defaultStyles = { + transition: 'width 50ms ease-in-out', + width: '50%', + display: 'flex', + }; + + const transitionStyles: { [str: string]: CSSProperties } = { + entered: { width: '100%' }, + }; + + return ( + + {state => { + return ( +
    +
    + } + ref={elem => elem && elem.focus()} + placeholder="Search all options" + suffix={ + setSearchMode(false)} className={styles.searchRemoveIcon} /> + } + /> +
    +
    + ); + }} +
    + ); + }, []); return (
    {plugin && (
    - - setActiveTab('defaults')} /> - setActiveTab('overrides')} /> - setActiveTab('panel')} /> + + {isSearching && renderSearchInput()} + {!isSearching && ( + <> + setActiveTab('defaults')} /> + setActiveTab('overrides')} + /> + setActiveTab('panel')} /> +
    +
    + setSearchMode(true)} + /> +
    +
    + +
    + + )} @@ -135,11 +218,26 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { display: flex; flex-direction: column; height: 100%; + padding-top: ${theme.spacing.sm}; `, panelOptionsPane: css` height: 100%; width: 100%; - border-bottom: none; + `, + tabsBar: css` + padding-right: ${theme.spacing.sm}; + `, + searchWrapper: css` + display: flex; + flex-grow: 1; + flex-direction: row-reverse; + `, + searchInput: css` + color: ${theme.colors.textWeak}; + flex-grow: 1; + `, + searchRemoveIcon: css` + cursor: pointer; `, tabContent: css` padding: 0; @@ -150,6 +248,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { background: ${theme.colors.pageBg}; border-left: 1px solid ${theme.colors.pageHeaderBorder}; `, + tabsButton: css``, legacyOptions: css` label: legacy-options; .panel-options-grid { diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 338dbfd1f22..5b041daa1df 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin, SelectableValue } from '@grafana/data'; -import { Forms, stylesFactory, Button } from '@grafana/ui'; +import { Forms, stylesFactory, Icon } from '@grafana/ui'; import { css, cx } from 'emotion'; import config from 'app/core/config'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -25,6 +25,7 @@ import { PanelEditorUIState, setDiscardChanges } from './state/reducers'; import { getPanelEditorTabs } from './state/selectors'; import { getPanelStateById } from '../../state/selectors'; import { OptionsPaneContent } from './OptionsPaneContent'; +import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton'; enum Pane { Right, @@ -155,28 +156,31 @@ export class PanelEditorUnconnected extends PureComponent { onDragStarted={this.onDragStarted} onDragFinished={size => this.onDragFinished(Pane.Top, size)} > -
    - - {({ width, height }) => { - if (width < 3 || height < 3) { - return null; - } - return ( -
    -
    - +
    + {this.renderToolbar(styles)} +
    + + {({ width, height }) => { + if (width < 3 || height < 3) { + return null; + } + return ( +
    +
    + +
    -
    - ); - }} - + ); + }} + +
    @@ -185,9 +189,8 @@ export class PanelEditorUnconnected extends PureComponent { ); } - renderToolbar() { + renderToolbar(styles: any) { const { dashboard, location, uiState } = this.props; - const styles = getStyles(config.theme); return (
    @@ -197,9 +200,9 @@ export class PanelEditorUnconnected extends PureComponent {
    - +
    {
    -
    -
    + {!uiState.isPanelOptionsVisible && ( +
    + + Show options + +
    + )}
    ); } - renderOptionsPane() { + renderOptionsPane(styles: any) { const { plugin, dashboard, data, panel } = this.props; if (!plugin) { @@ -237,6 +243,7 @@ export class PanelEditorUnconnected extends PureComponent { dashboard={dashboard} data={data} panel={panel} + onClose={this.onTogglePanelOptions} onFieldConfigsChange={this.onFieldConfigChange} onPanelOptionsChanged={this.onPanelOptionsChanged} onPanelConfigChange={this.onPanelConfigChanged} @@ -259,14 +266,14 @@ export class PanelEditorUnconnected extends PureComponent { onDragFinished={size => this.onDragFinished(Pane.Right, size)} > {this.renderHorizontalSplit(styles)} - {this.renderOptionsPane()} + {this.renderOptionsPane(styles)} ); } render() { const { initDone, uiState } = this.props; - const styles = getStyles(config.theme); + const styles = getStyles(config.theme, this.props); if (!initDone) { return null; @@ -275,10 +282,7 @@ export class PanelEditorUnconnected extends PureComponent { return (
    - {this.renderToolbar()} -
    - {uiState.isPanelOptionsVisible ? this.renderWithOptionsPane(styles) : this.renderHorizontalSplit(styles)} -
    + {uiState.isPanelOptionsVisible ? this.renderWithOptionsPane(styles) : this.renderHorizontalSplit(styles)}
    ); @@ -313,7 +317,8 @@ export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEdi /* * Styles */ -const getStyles = stylesFactory((theme: GrafanaTheme) => { +const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => { + const { uiState } = props; const handleColor = theme.colors.blueLight; const paneSpaceing = theme.spacing.md; @@ -347,16 +352,18 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { display: flex; flex-direction: column; `, - panesWrapper: css` + mainPaneWrapper: css` + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + padding-right: ${uiState.isPanelOptionsVisible ? 0 : paneSpaceing}; + `, + panelWrapper: css` flex: 1 1 0; min-height: 0; - width: 100%; - position: relative; - `, - panelWrapper: css` width: 100%; padding-left: ${paneSpaceing}; - height: 100%; `, resizerV: cx( resizer, @@ -384,6 +391,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { toolbar: css` display: flex; padding: ${theme.spacing.sm}; + padding-right: 0; justify-content: space-between; `, toolbarLeft: css` diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx index c95776c2f25..e09ee3eb784 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx @@ -71,7 +71,7 @@ const getPanelEditorTabsStyles = stylesFactory(() => { height: 100%; `, tabBar: css` - padding-left: ${theme.spacing.sm}; + padding-left: ${theme.spacing.md}; `, tabContent: css` padding: 0; @@ -80,6 +80,7 @@ const getPanelEditorTabsStyles = stylesFactory(() => { flex-grow: 1; min-height: 0; background: ${theme.colors.panelBg}; + border-right: 1px solid ${theme.colors.pageHeaderBorder}; .toolbar { background: transparent;