Commit 3bd9e1dd by Dominik Prokop Committed by GitHub

NewPanelEditor: vis picker UI update (#23894)

* Fix storybook

* Add deprecated/alpha badge to panels in viz picker, fix long title display

* Move getFocusCss to mixins

* Updated hover/active state of vis picker item

* try fixing e2e

* Add removed label for e2e to be happy happy happy
parent 6dfad3b3
......@@ -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<HTMLProps<HTMLInputElement>, 'value'> {
label?: string;
......@@ -41,7 +41,7 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme) => {
height: 100%;
opacity: 0;
&:focus + span {
${getFocusCss(theme)}
${focusCss(theme)}
}
/**
......
......@@ -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;
}
......
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)}
}
`;
......
......@@ -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<HTMLInputElement>) => {
......
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`
......
......@@ -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<HTMLProps<HTMLInputElement>, 'value'> {
value?: boolean;
......@@ -40,7 +40,7 @@ export const getSwitchStyles = stylesFactory((theme: GrafanaTheme) => {
}
&:focus + label {
${getFocusCss(theme)};
${focusCss(theme)};
}
}
......
......@@ -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)}
}
}
`,
......
......@@ -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);
`;
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<HTMLInputElement, Pr
return (
<div className={styles.wrapper}>
<div className={styles.search}>
<Field>
<Input
value={searchQuery}
onChange={e => setSearchQuery(e.currentTarget.value)}
onKeyPress={onKeyPress}
prefix={<Icon name="filter" className={styles.icon} />}
suffix={suffix}
placeholder="Filter visualisations"
ref={ref}
/>
</Field>
</div>
<div className={styles.visList}>
<CustomScrollbar>
<VizTypePicker
current={plugin.meta}
onTypeChange={onPluginTypeChange}
searchQuery={searchQuery}
onClose={() => {}}
/>
</CustomScrollbar>
</div>
<Field>
<Input
value={searchQuery}
onChange={e => setSearchQuery(e.currentTarget.value)}
onKeyPress={onKeyPress}
prefix={<Icon name="filter" className={styles.icon} />}
suffix={suffix}
placeholder="Filter visualisations"
ref={ref}
/>
</Field>
<VizTypePicker
current={plugin.meta}
onTypeChange={onPluginTypeChange}
searchQuery={searchQuery}
onClose={() => {}}
/>
</div>
);
}
......@@ -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;
`,
};
});
......
......@@ -95,6 +95,8 @@ export const VizTypePicker: React.FC<Props> = ({ searchQuery, onTypeChange, curr
);
};
VizTypePicker.displayName = 'VizTypePicker';
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
grid: css`
......
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<Props> = ({ isCurrent, plugin, onClick, disa
const styles = getStyles(theme);
const cssClass = cx({
[styles.item]: true,
[styles.current]: isCurrent,
[styles.disabled]: disabled,
[styles.current]: isCurrent,
});
return (
<div
className={cssClass}
onClick={disabled ? () => {} : onClick}
title={plugin.name}
aria-label={e2e.components.PluginVisualization.selectors.item(plugin.name)}
>
<div className={styles.name}>{plugin.name}</div>
<img className={styles.img} src={plugin.info.logos.small} />
<div className={styles.wrapper} aria-label={e2e.components.PluginVisualization.selectors.item(plugin.name)}>
<div className={cssClass} onClick={disabled ? () => {} : onClick} title={plugin.name}>
<div className={styles.bg} />
<div className={styles.itemContent}>
<div className={styles.name} title={plugin.name}>
{plugin.name}
</div>
<img className={styles.img} src={plugin.info.logos.small} />
</div>
</div>
<div className={styles.badge}>
<PanelPluginBadge plugin={plugin} />
</div>
</div>
);
};
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};
`,
};
});
......
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<Props> = ({ status }) => {
);
};
interface PanelPluginBadgeProps {
plugin: PanelPluginMeta;
}
export const PanelPluginBadge: React.FC<PanelPluginBadgeProps> = ({ 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 (
<div className={styles.wrapper}>
<Icon name={display.icon} size="sm" />
<span>{display.text}</span>
</div>
);
};
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 = '';
......
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