mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
6dfad3b352
commit
3bd9e1dda3
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)}
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -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>) => {
|
||||
|
@ -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`
|
||||
|
@ -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);
|
||||
`;
|
||||
|
@ -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<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`
|
||||
|
@ -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<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};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -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<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 = '';
|
||||
|
Loading…
Reference in New Issue
Block a user