diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.tsx index 67d5ebc9292..aa75364c1d3 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.tsx @@ -3,7 +3,7 @@ import { GrafanaTheme } from '@grafana/data'; import { getLabelStyles } from './Label'; import { useTheme, stylesFactory } from '../../themes'; import { css, cx } from 'emotion'; -import { getFocusCss } from './commonStyles'; +import { focusCss } from '../../themes/mixins'; export interface CheckboxProps extends Omit, 'value'> { label?: string; @@ -41,7 +41,7 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme) => { height: 100%; opacity: 0; &:focus + span { - ${getFocusCss(theme)} + ${focusCss(theme)} } /** diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx index 2c9678046b4..16b71feeaf2 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { useTheme, stylesFactory } from '../../../themes'; import { GrafanaTheme } from '@grafana/data'; import { css, cx } from 'emotion'; -import { getFocusCss, getPropertiesForButtonSize } from '../commonStyles'; +import { getPropertiesForButtonSize } from '../commonStyles'; +import { focusCss } from '../../../themes/mixins'; export type RadioButtonSize = 'sm' | 'md'; @@ -56,7 +57,7 @@ const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButt } &:focus + label { - ${getFocusCss(theme)}; + ${focusCss(theme)}; z-index: 3; } diff --git a/packages/grafana-ui/src/components/Forms/commonStyles.ts b/packages/grafana-ui/src/components/Forms/commonStyles.ts index bfba2967dc7..a9df4372d92 100644 --- a/packages/grafana-ui/src/components/Forms/commonStyles.ts +++ b/packages/grafana-ui/src/components/Forms/commonStyles.ts @@ -1,17 +1,11 @@ import { css } from 'emotion'; import { GrafanaTheme } from '@grafana/data'; import { StyleProps } from '../Button'; - -export const getFocusCss = (theme: GrafanaTheme) => ` - outline: 2px dotted transparent; - outline-offset: 2px; - box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${theme.colors.formFocusOutline}; - transition: all 0.2s cubic-bezier(0.19, 1, 0.22, 1); -`; +import { focusCss } from '../../themes/mixins'; export const getFocusStyle = (theme: GrafanaTheme) => css` &:focus { - ${getFocusCss(theme)} + ${focusCss(theme)} } `; diff --git a/packages/grafana-ui/src/components/Icon/Icon.story.tsx b/packages/grafana-ui/src/components/Icon/Icon.story.tsx index 720632c068b..99aebb5a96f 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.story.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.story.tsx @@ -10,7 +10,7 @@ import { useTheme, selectThemeVariant } from '../../themes'; import mdx from './Icon.mdx'; export default { - title: 'Docs Overview//Icon', + title: 'Docs Overview/Icon', component: Icon, decorators: [withCenteredStory], parameters: { @@ -60,7 +60,7 @@ const IconWrapper: React.FC<{ name: IconName }> = ({ name }) => { const icons = getAvailableIcons().sort((a, b) => a.localeCompare(b)); -export const simple = () => { +export const iconsOverview = () => { const [filter, setFilter] = useState(''); const searchIcon = (event: ChangeEvent) => { diff --git a/packages/grafana-ui/src/components/Select/InputControl.tsx b/packages/grafana-ui/src/components/Select/InputControl.tsx index c7a6b533830..8d1f6c7937f 100644 --- a/packages/grafana-ui/src/components/Select/InputControl.tsx +++ b/packages/grafana-ui/src/components/Select/InputControl.tsx @@ -1,10 +1,11 @@ import React from 'react'; import { useTheme } from '../../themes/ThemeContext'; -import { getFocusCss, sharedInputStyle } from '../Forms/commonStyles'; +import { sharedInputStyle } from '../Forms/commonStyles'; import { getInputStyles } from '../Input/Input'; -import { cx, css } from 'emotion'; +import { css, cx } from 'emotion'; import { stylesFactory } from '../../themes'; import { GrafanaTheme } from '@grafana/data'; +import { focusCss } from '../../themes/mixins'; interface InputControlProps { /** Show an icon as a prefix in the input */ @@ -25,7 +26,7 @@ const getInputControlStyles = stylesFactory( sharedInputStyle(theme, invalid), focused && css` - ${getFocusCss(theme)} + ${focusCss(theme)} `, disabled && styles.inputDisabled, css` diff --git a/packages/grafana-ui/src/components/Switch/Switch.tsx b/packages/grafana-ui/src/components/Switch/Switch.tsx index 8db64260d3a..31a01a0ee28 100644 --- a/packages/grafana-ui/src/components/Switch/Switch.tsx +++ b/packages/grafana-ui/src/components/Switch/Switch.tsx @@ -3,7 +3,7 @@ import { css, cx } from 'emotion'; import uniqueId from 'lodash/uniqueId'; import { GrafanaTheme } from '@grafana/data'; import { stylesFactory, useTheme } from '../../themes'; -import { getFocusCss } from '../Forms/commonStyles'; +import { focusCss } from '../../themes/mixins'; export interface SwitchProps extends Omit, 'value'> { value?: boolean; @@ -40,7 +40,7 @@ export const getSwitchStyles = stylesFactory((theme: GrafanaTheme) => { } &:focus + label { - ${getFocusCss(theme)}; + ${focusCss(theme)}; } } diff --git a/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.tsx b/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.tsx index 55b441ed396..6da6ed54224 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.tsx @@ -4,8 +4,9 @@ import { css, cx } from 'emotion'; import { dateTime, DateTime, dateTimeAsMoment, GrafanaTheme } from '@grafana/data'; import { useTheme, Icon } from '../../index'; import { stylesFactory } from '../../themes'; -import { inputSizes, getFocusCss } from '../Forms/commonStyles'; +import { inputSizes } from '../Forms/commonStyles'; import { FormInputSize } from '../Forms/types'; +import { focusCss } from '../../themes/mixins'; interface Props { onChange: (value: DateTime) => void; @@ -76,7 +77,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { height: ${theme.spacing.formInputHeight}px; &:focus { - ${getFocusCss(theme)} + ${focusCss(theme)} } } `, diff --git a/packages/grafana-ui/src/themes/mixins.ts b/packages/grafana-ui/src/themes/mixins.ts index 3fefaf4842d..1eaac5a1471 100644 --- a/packages/grafana-ui/src/themes/mixins.ts +++ b/packages/grafana-ui/src/themes/mixins.ts @@ -39,3 +39,10 @@ export function listItemSelected(theme: GrafanaTheme): string { color: ${theme.colors.textStrong}; `; } + +export const focusCss = (theme: GrafanaTheme) => ` + outline: 2px dotted transparent; + outline-offset: 2px; + box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${theme.colors.formFocusOutline}; + transition: all 0.2s cubic-bezier(0.19, 1, 0.22, 1); +`; diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx index 4fb01efd4a6..ee1e05bb1dc 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useState } from 'react'; import { css } from 'emotion'; import { GrafanaTheme, PanelPlugin, PanelPluginMeta } from '@grafana/data'; -import { CustomScrollbar, useTheme, stylesFactory, Icon, Input } from '@grafana/ui'; +import { useTheme, stylesFactory, Icon, Input } from '@grafana/ui'; import { changePanelPlugin } from '../../state/actions'; import { StoreState } from 'app/types'; import { PanelModel } from '../../state/PanelModel'; @@ -61,29 +61,24 @@ export const VisualizationTabUnconnected = React.forwardRef -
- - setSearchQuery(e.currentTarget.value)} - onKeyPress={onKeyPress} - prefix={} - suffix={suffix} - placeholder="Filter visualisations" - ref={ref} - /> - -
-
- - {}} - /> - -
+ + setSearchQuery(e.currentTarget.value)} + onKeyPress={onKeyPress} + prefix={} + suffix={suffix} + placeholder="Filter visualisations" + ref={ref} + /> + + + {}} + /> ); } @@ -96,22 +91,11 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { wrapper: css` display: flex; flex-direction: column; - flex-grow: 1; - max-height: 100%; - `, - search: css` - flex-grow: 0; - flex-shrink: 1; `, searchClear: css` color: ${theme.palette.gray60}; cursor: pointer; `, - visList: css` - flex-grow: 1; - height: 100%; - overflow: hidden; - `, }; }); diff --git a/public/app/features/dashboard/panel_editor/VizTypePicker.tsx b/public/app/features/dashboard/panel_editor/VizTypePicker.tsx index dc42cf9dc1c..8d636de22d3 100644 --- a/public/app/features/dashboard/panel_editor/VizTypePicker.tsx +++ b/public/app/features/dashboard/panel_editor/VizTypePicker.tsx @@ -95,6 +95,8 @@ export const VizTypePicker: React.FC = ({ searchQuery, onTypeChange, curr ); }; +VizTypePicker.displayName = 'VizTypePicker'; + const getStyles = stylesFactory((theme: GrafanaTheme) => { return { grid: css` diff --git a/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx b/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx index 793b39854d5..00d307cc918 100644 --- a/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx +++ b/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { GrafanaTheme, PanelPluginMeta } from '@grafana/data'; -import { stylesFactory, useTheme } from '@grafana/ui'; +import { stylesFactory, useTheme, styleMixins } from '@grafana/ui'; import { css, cx } from 'emotion'; import { e2e } from '@grafana/e2e'; +import { PanelPluginBadge } from '../../plugins/PluginSignatureBadge'; interface Props { isCurrent: boolean; @@ -16,32 +17,50 @@ const VizTypePickerPlugin: React.FC = ({ isCurrent, plugin, onClick, disa const styles = getStyles(theme); const cssClass = cx({ [styles.item]: true, - [styles.current]: isCurrent, [styles.disabled]: disabled, + [styles.current]: isCurrent, }); return ( -
{} : onClick} - title={plugin.name} - aria-label={e2e.components.PluginVisualization.selectors.item(plugin.name)} - > -
{plugin.name}
- +
+
{} : onClick} title={plugin.name}> +
+
+
+ {plugin.name} +
+ +
+
+
+ +
); }; +VizTypePickerPlugin.displayName = 'VizTypePickerPlugin'; + const getStyles = stylesFactory((theme: GrafanaTheme) => { return { - item: css` + wrapper: css` + position: relative; + `, + bg: css` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; background: ${theme.colors.bg2}; border: 1px solid ${theme.colors.border2}; border-radius: 3px; - height: 100px; - width: 100%; - max-width: 200px; + transform: scale(1); + transform-origin: center; + transition: all 0.1s ease-in; + z-index: 0; + `, + item: css` flex-shrink: 0; flex-direction: column; text-align: center; @@ -51,23 +70,35 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { align-items: center; justify-content: center; padding-bottom: 6px; + height: 100px; + width: 100%; + max-width: 200px; + position: relative; &:hover { - box-shadow: 0 0 4px ${theme.palette.blue95}; - border: 1px solid ${theme.palette.blue95}; + > div:first-child { + transform: scale(1.05); + border-color: ${theme.colors.formFocusOutline}; + } } `, + itemContent: css` + position: relative; + z-index: 1; + `, current: css` label: currentVisualizationItem; - box-shadow: 0 0 6px ${theme.palette.orange} !important; - border: 1px solid ${theme.palette.orange} !important; + pointer-events: none; + > div:first-child { + ${styleMixins.focusCss(theme)}; + } `, disabled: css` opacity: 0.2; filter: grayscale(1); cursor: default; + pointer-events: none; &:hover { - box-shadow: none; border: 1px solid ${theme.colors.border2}; } `, @@ -76,15 +107,20 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { overflow: hidden; white-space: nowrap; font-size: ${theme.typography.size.sm}; - display: flex; - flex-direction: column; - align-self: center; + text-align: center; height: 23px; font-weight: ${theme.typography.weight.semibold}; + padding: 0 10px; + width: 100%; `, img: css` height: 55px; `, + badge: css` + position: absolute; + bottom: ${theme.spacing.xs}; + right: ${theme.spacing.xs}; + `, }; }); diff --git a/public/app/features/plugins/PluginSignatureBadge.tsx b/public/app/features/plugins/PluginSignatureBadge.tsx index 4c686b806c1..deb34df3cb7 100644 --- a/public/app/features/plugins/PluginSignatureBadge.tsx +++ b/public/app/features/plugins/PluginSignatureBadge.tsx @@ -1,6 +1,12 @@ import React from 'react'; -import { Icon, stylesFactory, useTheme, IconName, Tooltip } from '@grafana/ui'; -import { GrafanaTheme, PluginSignatureStatus, getColorFromHexRgbOrName } from '@grafana/data'; +import { Icon, IconName, stylesFactory, Tooltip, useTheme } from '@grafana/ui'; +import { + getColorFromHexRgbOrName, + GrafanaTheme, + PanelPluginMeta, + PluginSignatureStatus, + PluginState, +} from '@grafana/data'; import { css } from 'emotion'; import tinycolor from 'tinycolor2'; @@ -23,6 +29,25 @@ export const PluginSignatureBadge: React.FC = ({ status }) => { ); }; +interface PanelPluginBadgeProps { + plugin: PanelPluginMeta; +} +export const PanelPluginBadge: React.FC = ({ plugin }) => { + const theme = useTheme(); + const display = getPanelStateBadgeDisplayModel(plugin); + const styles = getStyles(theme, display); + + if (plugin.state !== PluginState.deprecated && plugin.state !== PluginState.alpha) { + return null; + } + return ( +
+ + {display.text} +
+ ); +}; + interface DisplayModel { text: string; icon: IconName; @@ -55,6 +80,25 @@ function getSignatureDisplayModel(signature: PluginSignatureStatus): DisplayMode return { text: 'Unsigned', icon: 'exclamation-triangle', color: 'red', tooltip: 'Unsigned external plugin' }; } +function getPanelStateBadgeDisplayModel(panel: PanelPluginMeta): DisplayModel { + switch (panel.state) { + case PluginState.deprecated: + return { + text: 'Deprecated', + icon: 'exclamation-triangle', + color: 'red', + tooltip: `${panel.name} panel is deprecated`, + }; + } + + return { + text: 'Alpha', + icon: 'rocket', + color: 'blue', + tooltip: `${panel.name} panel is experimental`, + }; +} + const getStyles = stylesFactory((theme: GrafanaTheme, model: DisplayModel) => { let sourceColor = getColorFromHexRgbOrName(model.color); let borderColor = '';