Icons: Adds custom icon support ands new panel and interpolation icons (#30277)

* Icons: Adds custom icon support ands new panel and interpolation icons

* Removed icon files

* updated snapshot

* Updates
This commit is contained in:
Torkel Ödegaard 2021-01-14 15:07:19 +01:00 committed by GitHub
parent a9338542b6
commit d10dbc70a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 188 additions and 68 deletions

View File

@ -5,6 +5,7 @@ export interface SelectableValue<T = any> {
label?: string;
value?: T;
imgUrl?: string;
icon?: string;
description?: string;
[key: string]: any;
}

View File

@ -11,6 +11,7 @@ export interface RadioButtonProps {
size?: RadioButtonSize;
disabled?: boolean;
name?: string;
description?: string;
active: boolean;
id: string;
onChange: () => void;
@ -101,6 +102,7 @@ export const RadioButton: React.FC<RadioButtonProps> = ({
onChange,
id,
name = undefined,
description,
fullWidth,
}) => {
const theme = useTheme();
@ -117,7 +119,7 @@ export const RadioButton: React.FC<RadioButtonProps> = ({
checked={active}
name={name}
/>
<label className={cx(styles.radioLabel)} htmlFor={id}>
<label className={cx(styles.radioLabel)} htmlFor={id} title={description}>
{children}
</label>
</>

View File

@ -16,33 +16,7 @@ export default {
const sizes: RadioButtonSize[] = ['sm', 'md'];
export const Simple = () => {
const [selected, setSelected] = useState('graphite');
const BEHAVIOUR_GROUP = 'Behaviour props';
const disabled = boolean('Disabled', false, BEHAVIOUR_GROUP);
const disabledItem = select('Disabled item', ['', 'graphite', 'prometheus', 'elastic'], '', BEHAVIOUR_GROUP);
const VISUAL_GROUP = 'Visual options';
const size = select<RadioButtonSize>('Size', sizes, 'md', VISUAL_GROUP);
const options = [
{ label: 'Prometheus', value: 'prometheus' },
{ label: 'Graphite', value: 'graphite' },
{ label: 'Elastic', value: 'elastic' },
];
return (
<RadioButtonGroup
options={options}
disabled={disabled}
disabledOptions={[disabledItem]}
value={selected}
onChange={v => setSelected(v!)}
size={size}
/>
);
};
export const FullWidth = () => {
export const RadioButtons = () => {
const [selected, setSelected] = useState('elastic');
const BEHAVIOUR_GROUP = 'Behaviour props';
const disabled = boolean('Disabled', false, BEHAVIOUR_GROUP);
@ -52,21 +26,45 @@ export const FullWidth = () => {
const options = [
{ label: 'Prometheus', value: 'prometheus' },
{ label: 'Graphite', value: 'graphite' },
{ label: 'Graphite', value: 'graphite', icon: 'cloud' },
{ label: 'Elastic', value: 'elastic' },
];
const optionsWithOnlyIcons = [
{ description: 'Prometheus', value: 'prometheus', icon: 'gf-interpolation-linear' },
{ description: 'Graphite', value: 'graphite', icon: 'gf-interpolation-smooth' },
{ description: 'Elastic', value: 'elastic', icon: 'gf-interpolation-step-after' },
];
return (
<div style={{ width: '100%' }}>
<RadioButtonGroup
options={options}
disabled={disabled}
disabledOptions={[disabledItem]}
value={selected}
onChange={v => setSelected(v!)}
size={size}
fullWidth
/>
<div style={{ marginBottom: '32px' }}>
<h5>Full width</h5>
<RadioButtonGroup
options={options}
disabled={disabled}
disabledOptions={[disabledItem]}
value={selected}
onChange={v => setSelected(v!)}
size={size}
fullWidth
/>
</div>
<div style={{ marginBottom: '32px' }}>
<h5>Auto width</h5>
<RadioButtonGroup
options={options}
disabled={disabled}
disabledOptions={[disabledItem]}
value={selected}
onChange={v => setSelected(v!)}
size={size}
/>
</div>
<div style={{ marginBottom: '32px' }}>
<h5>With only icons and descriptions</h5>
<RadioButtonGroup options={optionsWithOnlyIcons} value={selected} onChange={v => setSelected(v!)} size={size} />
</div>
</div>
);
};

View File

@ -4,6 +4,7 @@ import uniqueId from 'lodash/uniqueId';
import { SelectableValue } from '@grafana/data';
import { RadioButtonSize, RadioButton } from './RadioButton';
import { Icon } from '../../Icon/Icon';
import { IconName } from '../../../types/icon';
const getRadioButtonGroupStyles = () => {
return {
@ -85,8 +86,9 @@ export function RadioButtonGroup<T>({
id={`option-${o.value}-${id}`}
name={groupName.current}
fullWidth={fullWidth}
description={o.description}
>
{o.icon && <Icon name={o.icon} className={styles.icon} />}
{o.icon && <Icon name={o.icon as IconName} className={styles.icon} />}
{o.label}
</RadioButton>
);

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { ComponentType } from 'react';
import { css, cx } from 'emotion';
import { GrafanaTheme, toPascalCase } from '@grafana/data';
import { stylesFactory } from '../../themes/stylesFactory';
@ -7,6 +7,8 @@ import { IconName, IconType, IconSize } from '../../types/icon';
//@ts-ignore
import * as DefaultIcon from '@iconscout/react-unicons';
import * as MonoIcon from './assets';
import { customIcons } from './custom';
import { SvgProps } from './assets/types';
const alwaysMonoIcons = ['grafana', 'favorite', 'heart-break', 'heart'];
@ -34,6 +36,24 @@ const getIconStyles = stylesFactory((theme: GrafanaTheme) => {
};
});
function getIconComponent(name: string, type: string): ComponentType<SvgProps> {
if (alwaysMonoIcons.includes(name)) {
type = 'mono';
}
if (name?.startsWith('gf-')) {
return customIcons[name];
}
const iconName = type === 'default' ? `Uil${toPascalCase(name)}` : toPascalCase(name);
/* Unicons don't have type definitions */
//@ts-ignore
const Component = type === 'default' ? DefaultIcon[iconName] : MonoIcon[iconName];
return Component ?? customIcons.notFoundDummy;
}
export const Icon = React.forwardRef<HTMLDivElement, IconProps>(
({ size = 'md', type = 'default', name, className, style, ...divElementProps }, ref) => {
const theme = useTheme();
@ -41,24 +61,11 @@ export const Icon = React.forwardRef<HTMLDivElement, IconProps>(
const svgSize = getSvgSize(size);
/* Temporary solution to display also font awesome icons */
const isFontAwesome = name?.includes('fa-');
if (isFontAwesome) {
if (name?.startsWith('fa-')) {
return <i className={cx(name, className)} {...divElementProps} style={style} />;
}
if (alwaysMonoIcons.includes(name)) {
type = 'mono';
}
const iconName = type === 'default' ? `Uil${toPascalCase(name)}` : toPascalCase(name);
/* Unicons don't have type definitions */
//@ts-ignore
const Component = type === 'default' ? DefaultIcon[iconName] : MonoIcon[iconName];
if (!Component) {
return <div />;
}
const Component = getIconComponent(name, type);
return (
<div className={styles.container} {...divElementProps} ref={ref}>

View File

@ -0,0 +1,78 @@
import React, { FC, ComponentType } from 'react';
import { SvgProps } from '../assets/types';
const InterpolationLinear: FC<SvgProps> = ({ size, ...rest }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28.5 20" width={'30px'} height={size} {...rest}>
<g id="Layer_2" data-name="Layer 2">
<g id="Icons">
<circle cx="14.17" cy="2.67" r="2.67" />
<circle cx="25.83" cy="17.33" r="2.67" />
<rect x="19.25" y="-1.21" width="1.5" height="22.42" transform="translate(-1.79 15.03) rotate(-39.57)" />
<circle cx="2.67" cy="17.33" r="2.67" />
<rect x="-2.71" y="9.25" width="22.42" height="1.5" transform="translate(-4.62 10.18) rotate(-50.44)" />
</g>
</g>
</svg>
);
};
const InterpolationSmooth: FC<SvgProps> = ({ size, ...rest }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28.34 20" width={'30px'} height={size} {...rest}>
<g id="Layer_2" data-name="Layer 2">
<g id="Icons">
<circle cx="14.17" cy="2.67" r="2.67" />
<circle cx="2.67" cy="17.33" r="2.67" />
<path d="M3.42,17.33H1.92c0-6.46,4.39-15.41,12.64-15.41v1.5C7.29,3.42,3.42,11.5,3.42,17.33Z" />
<circle cx="25.67" cy="17.33" r="2.67" />
<path d="M26.42,17.33h-1.5c0-5.83-3.87-13.91-11.14-13.91V1.92C22,1.92,26.42,10.87,26.42,17.33Z" />
</g>
</g>
</svg>
);
};
const InterpolationStepBefore: FC<SvgProps> = ({ size, ...rest }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28.34 20" width={'30px'} height={size} {...rest}>
<g id="Layer_2" data-name="Layer 2">
<g id="Icons">
<circle cx="14.17" cy="2.67" r="2.67" />
<circle cx="2.67" cy="17.33" r="2.67" />
<circle cx="25.67" cy="17.33" r="2.67" />
<polygon points="3.42 17.33 1.92 17.33 1.92 1.92 13.78 1.92 13.78 3.42 3.42 3.42 3.42 17.33" />
<polygon points="25.67 18.08 13.42 18.08 13.42 2.67 14.92 2.67 14.92 16.58 25.67 16.58 25.67 18.08" />
</g>
</g>
</svg>
);
};
const InterpolationStepAfter: FC<SvgProps> = ({ size, ...rest }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28.34 20" width={'30px'} height={size} {...rest}>
<g id="Layer_2" data-name="Layer 2">
<g id="Icons">
<circle cx="14.17" cy="2.67" r="2.67" />
<circle cx="25.67" cy="17.33" r="2.67" />
<circle cx="2.67" cy="17.33" r="2.67" />
<polygon points="26.42 17.33 24.92 17.33 24.92 3.42 14.56 3.42 14.56 1.92 26.42 1.92 26.42 17.33" />
<polygon points="14.92 18.08 2.67 18.08 2.67 16.58 13.42 16.58 13.42 2.67 14.92 2.67 14.92 18.08" />
</g>
</g>
</svg>
);
};
const IconNotFound: FC<SvgProps> = ({ ...rest }) => {
return <svg {...rest} />;
};
export const customIcons: Record<string, ComponentType<SvgProps>> = {
'gf-interpolation-linear': InterpolationLinear,
'gf-interpolation-smooth': InterpolationSmooth,
'gf-interpolation-step-before': InterpolationStepBefore,
'gf-interpolation-step-after': InterpolationStepAfter,
notFoundDummy: IconNotFound,
};

View File

@ -142,10 +142,10 @@ export const graphFieldOptions = {
] as Array<SelectableValue<DrawStyle>>,
lineInterpolation: [
{ label: 'Linear', value: LineInterpolation.Linear },
{ label: 'Smooth', value: LineInterpolation.Smooth },
{ label: 'Step Before', value: LineInterpolation.StepBefore },
{ label: 'Step After', value: LineInterpolation.StepAfter },
{ description: 'Linear', value: LineInterpolation.Linear, icon: 'gf-interpolation-linear' },
{ description: 'Smooth', value: LineInterpolation.Smooth, icon: 'gf-interpolation-smooth' },
{ description: 'Step before', value: LineInterpolation.StepBefore, icon: 'gf-interpolation-step-before' },
{ description: 'Step after', value: LineInterpolation.StepAfter, icon: 'gf-interpolation-step-after' },
] as Array<SelectableValue<LineInterpolation>>,
showPoints: [

View File

@ -121,7 +121,11 @@ export type IconName =
| 'sort-amount-down'
| 'cloud'
| 'draggabledots'
| 'folder-upload';
| 'folder-upload'
| 'gf-interpolation-linear'
| 'gf-interpolation-smooth'
| 'gf-interpolation-step-before'
| 'gf-interpolation-step-after';
export const getAvailableIcons = (): IconName[] => [
'fa fa-spinner',
@ -241,4 +245,8 @@ export const getAvailableIcons = (): IconName[] => [
'cloud',
'draggabledots',
'folder-upload',
'gf-interpolation-linear',
'gf-interpolation-smooth',
'gf-interpolation-step-before',
'gf-interpolation-step-after',
];

View File

@ -7,6 +7,7 @@ const setup = (propOverrides?: object) => {
{
link: {
text: 'Hello',
icon: 'cloud',
url: '/asd',
},
},

View File

@ -8,14 +8,12 @@ export interface Props {
onClick?: () => void;
}
const TopSectionItem: FC<Props> = props => {
const { link, onClick } = props;
const TopSectionItem: FC<Props> = ({ link, onClick }) => {
return (
<div className="sidemenu-item dropdown">
<a className="sidemenu-link" href={link.url} target={link.target} onClick={onClick}>
<span className="icon-circle sidemenu-icon">
<Icon name={link.icon as any} size="xl" />
{link.icon && <Icon name={link.icon as any} size="xl" />}
{link.img && <img src={link.img} />}
</span>
</a>

View File

@ -4,6 +4,7 @@ exports[`Render should render component 1`] = `
<TopSectionItem
link={
Object {
"icon": "cloud",
"text": "Hello",
"url": "/asd",
}
@ -20,15 +21,38 @@ exports[`Render should render component 1`] = `
className="icon-circle sidemenu-icon"
>
<Icon
name="cloud"
size="xl"
>
<div />
<div
className="css-1vzus6i-Icon"
>
<ni
className="css-sr6nr"
color="currentColor"
size={24}
>
<svg
className="css-sr6nr"
fill="currentColor"
height={24}
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18.42,9.21a7,7,0,0,0-13.36,1.9A4,4,0,0,0,6,19H17a5,5,0,0,0,1.42-9.79ZM17,17H6a2,2,0,0,1,0-4,1,1,0,0,0,1-1,5,5,0,0,1,9.73-1.61,1,1,0,0,0,.78.66A3,3,0,0,1,17,17Z"
/>
</svg>
</ni>
</div>
</Icon>
</span>
</a>
<SideMenuDropDown
link={
Object {
"icon": "cloud",
"text": "Hello",
"url": "/asd",
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 82.48 77.59"><defs><style>.cls-1{fill:url(#linear-gradient);}.cls-2{fill:url(#linear-gradient-2);}.cls-3{fill:#84aff1;}</style><linearGradient id="linear-gradient" y1="12" x2="82.48" y2="12" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2cc0c"/><stop offset="1" stop-color="#ff9830"/></linearGradient><linearGradient id="linear-gradient-2" x1="41.14" y1="77.98" x2="41.91" y2="13.73" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1f60c4" stop-opacity="0"/><stop offset="1" stop-color="#3865ab"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Icons"><path class="cls-1" d="M33.26,24a2,2,0,0,1-.83-.18l-14.7-6.66-15,5.71a2,2,0,1,1-1.42-3.74l15.8-6a2,2,0,0,1,1.53,0l14.16,6.41,15-16A2,2,0,0,1,50,3.16L65.15,9.73,79.38.33a2,2,0,1,1,2.2,3.34l-15.13,10a2,2,0,0,1-1.9.16L49.72,7.39l-15,16A2,2,0,0,1,33.26,24Z"/><polygon class="cls-2" points="80.48 17.72 65.35 33.72 49.22 21.72 33.26 33.72 17.8 30.72 2 43.72 2.11 43.72 2.11 77.58 80.69 77.58 80.48 17.72"/><path class="cls-3" d="M2.11,45.72H2A2,2,0,0,1,.73,42.18l15.8-13a2,2,0,0,1,1.65-.42l14.59,2.83L48,20.12a2,2,0,0,1,2.4,0l14.7,10.94L79,16.35a2,2,0,1,1,2.9,2.75L66.8,35.1a2,2,0,0,1-2.64.23L49.23,24.22,34.46,35.32a2,2,0,0,1-1.58.37L18.34,32.87l-14.6,12A2,2,0,0,1,2.11,45.72Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -10,8 +10,8 @@
"url": "https://grafana.com"
},
"logos": {
"small": "img/icn-graph-panel.svg",
"large": "img/icn-graph-panel.svg"
"small": "img/icn-timeseries-panel.svg",
"large": "img/icn-timeseries-panel.svg"
}
}
}

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 82.59 82.5"><defs><style>.cls-1{fill:#3865ab;}.cls-2{fill:#84aff1;}.cls-3{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" y1="21.17" x2="82.59" y2="21.17" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2cc0c"/><stop offset="1" stop-color="#ff9830"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><rect class="cls-1" x="73.22" y="19.61" width="8" height="62.89" rx="1"/><rect class="cls-1" x="1.78" y="53.61" width="8" height="28.89" rx="1"/><path class="cls-2" d="M8.78,82.5h-6a1,1,0,0,1-1-1V71.61h8V81.5A1,1,0,0,1,8.78,82.5Z"/><path class="cls-2" d="M80.22,82.5h-6a1,1,0,0,1-1-1V46.61h8V81.5A1,1,0,0,1,80.22,82.5Z"/><rect class="cls-1" x="58.93" y="49.61" width="8" height="32.89" rx="1"/><path class="cls-2" d="M65.93,82.5h-6a1,1,0,0,1-1-1V64.61h8V81.5A1,1,0,0,1,65.93,82.5Z"/><rect class="cls-1" x="44.64" y="38.61" width="8" height="43.89" rx="1"/><path class="cls-2" d="M51.64,82.5h-6a1,1,0,0,1-1-1V75.61h8V81.5A1,1,0,0,1,51.64,82.5Z"/><rect class="cls-1" x="30.36" y="27.61" width="8" height="54.89" rx="1"/><path class="cls-2" d="M37.36,82.5h-6a1,1,0,0,1-1-1V42.61h8V81.5A1,1,0,0,1,37.36,82.5Z"/><rect class="cls-1" x="16.07" y="37.61" width="8" height="44.89" rx="1"/><path class="cls-2" d="M23.07,82.5h-6a1,1,0,0,1-1-1V55.61h8V81.5A1,1,0,0,1,23.07,82.5Z"/><path class="cls-3" d="M2,42.33a2,2,0,0,1-1.44-.61,2,2,0,0,1,0-2.83l26-25a2,2,0,0,1,2.2-.39L54.56,25,79.18.58A2,2,0,0,1,82,3.42L56.41,28.75a2,2,0,0,1-2.22.41L28.42,17.71l-25,24.06A2,2,0,0,1,2,42.33Z"/></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 85.58 85.59"><defs><style>.cls-1{fill:#3865ab;}.cls-2{fill:#84aff1;}.cls-3{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="18.52" y1="47.29" x2="83.59" y2="47.29" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2cc0c"/><stop offset="1" stop-color="#ff9830"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Icons"><path class="cls-1" d="M83.58,78.59H7V2A2,2,0,0,0,3,2V78.59H2a2,2,0,0,0,0,4H3v1a2,2,0,0,0,4,0v-1H83.58a2,2,0,1,0,0-4Z"/><circle class="cls-2" cx="13.43" cy="68.2" r="2.34"/><path class="cls-3" d="M23.42,69.87a2.34,2.34,0,1,1-2.34-2.34A2.34,2.34,0,0,1,23.42,69.87Zm6-6.89a2.34,2.34,0,1,0,2.34,2.34A2.34,2.34,0,0,0,29.39,63ZM20.85,48.79a2.34,2.34,0,1,0,2.34,2.34A2.33,2.33,0,0,0,20.85,48.79Zm15.43,3.4a2.34,2.34,0,1,0,2.33,2.34A2.34,2.34,0,0,0,36.28,52.19ZM43.92,58a2.34,2.34,0,1,0,2.34,2.34A2.34,2.34,0,0,0,43.92,58Zm10.15-6.29a2.34,2.34,0,1,0,2.34,2.34A2.34,2.34,0,0,0,54.07,51.72Zm8.59-14.66A2.34,2.34,0,1,0,65,39.4,2.34,2.34,0,0,0,62.66,37.06ZM81.25,22.38a2.34,2.34,0,1,0,2.34,2.34A2.34,2.34,0,0,0,81.25,22.38Z"/><circle class="cls-2" cx="26.69" cy="56.59" r="2.34"/><circle class="cls-2" cx="36.16" cy="43.03" r="2.34"/><circle class="cls-2" cx="43.66" cy="52.15" r="2.34"/><circle class="cls-2" cx="54.37" cy="42.4" r="2.34"/><circle class="cls-2" cx="44.59" cy="20.16" r="2.34"/><circle class="cls-2" cx="79.25" cy="45.71" r="2.34"/><circle class="cls-2" cx="79.25" cy="4.34" r="2.34"/><circle class="cls-2" cx="73.25" cy="16.98" r="2.34"/><circle class="cls-2" cx="60.71" cy="4.34" r="2.34"/><circle class="cls-2" cx="59.66" cy="27.08" r="2.34"/><circle class="cls-2" cx="16.36" cy="59.58" r="2.34"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB