Part1: Unicons implementation (#23197)

* Create a new Icon component

* Update icons in main sidebar

* Update icons in Useful links and in react components on  main site

* Update icons in Useful links and in main top navigation

* Adjust sizing

* Update panel navigation and timepicker

* Update icons in Panel menu

* NewPanelEditor: Fixed so that test alert rule works in new edit mode (#23179)

* Update icons in add panel widget

* Resolve merge conflict

* Fix part of the test errors and type errors

* Fix storybook errors

* Update getAvailableIcons import in storybook knobs

* Fix import path

* Fix SyntaxError: Cannot use import statement outside a module in test environment error

* Remove dynamic imports

* Remove types as using @ts-ignore

* Update snapshot test

* Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax

* Remove color prop from icon, remove color implemetation in mono icons

* Update navbar styling

* Move toPascalCase to utils/string

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
Ivana 2020-04-05 13:11:01 +02:00
parent 3fae28be52
commit d6292ac15b
74 changed files with 561 additions and 1729 deletions

View File

@ -1,3 +1,4 @@
import { camelCase } from 'lodash';
const specialChars = ['(', '[', '{', '}', ']', ')', '|', '*', '+', '-', '.', '?', '<', '>', '#', '&', '^', '$'];
export const escapeStringForRegex = (value: string) => {
@ -89,3 +90,8 @@ export function toFloatOrUndefined(value: string): number | undefined {
const v = parseFloat(value);
return isNaN(v) ? undefined : v;
}
export const toPascalCase = (string: string) => {
const str = camelCase(string);
return str.charAt(0).toUpperCase() + str.substring(1);
};

View File

@ -31,6 +31,7 @@
"@grafana/data": "7.0.0-pre.0",
"@grafana/slate-react": "0.22.9-grafana",
"@grafana/tsconfig": "^1.0.0-rc1",
"@iconscout/react-unicons": "^1.0.0",
"@torkelo/react-select": "3.0.8",
"@types/react-color": "3.0.1",
"@types/react-select": "3.0.8",

View File

@ -40,7 +40,7 @@ export const ButtonCascader: React.FC<ButtonCascaderProps> = props => {
expandIcon={null}
>
<button className="gf-form-label gf-form-label--btn" disabled={props.disabled}>
{props.children} <Icon name="caret-down" />
{props.children} <Icon name="angle-down" />
</button>
</RCCascader>
);

View File

@ -212,7 +212,7 @@ export class Cascader extends React.PureComponent<CascaderProps, CascaderState>
value={activeLabel}
onKeyDown={this.onInputKeyDown}
onChange={() => {}}
suffix={focusCascade ? <Icon name="caret-up" /> : <Icon name="caret-down" />}
suffix={focusCascade ? <Icon name="angle-up" /> : <Icon name="angle-down" />}
/>
</div>
</RCCascader>

View File

@ -123,7 +123,7 @@ export const Collapse: FunctionComponent<Props> = ({ isOpen, label, loading, col
<div className={panelClass}>
<div className={headerClass} onClick={onClickToggle}>
<div className={headerButtonsClass}>
<Icon name={isOpen ? 'caret-up' : 'caret-down'} />
<Icon name={isOpen ? 'angle-up' : 'angle-down'} />
</div>
<div className={cx([style.headerLabel])}>{label}</div>
</div>

View File

@ -11,7 +11,7 @@ const getKnobs = () => {
body: text('Body', 'Are you sure you want to delete this user?'),
confirm: text('Confirm', 'Delete'),
visible: boolean('Visible', true),
icon: select('Icon', ['exclamation-triangle', 'power-off', 'cog', 'lock'], 'exclamation-triangle'),
icon: select('Icon', ['exclamation-triangle', 'power', 'cog', 'lock'], 'exclamation-triangle'),
};
};

View File

@ -1,7 +1,7 @@
import React, { FC, useContext } from 'react';
import { css } from 'emotion';
import { Modal } from '../Modal/Modal';
import { IconType } from '../Icon/types';
import { IconName } from '../../types';
import { Button } from '../Button';
import { stylesFactory, ThemeContext } from '../../themes';
import { GrafanaTheme } from '@grafana/data';
@ -22,7 +22,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
`,
}));
const defaultIcon: IconType = 'exclamation-triangle';
const defaultIcon: IconName = 'exclamation-triangle';
interface Props {
isOpen: boolean;
@ -30,7 +30,7 @@ interface Props {
body: React.ReactNode;
confirmText: string;
dismissText?: string;
icon?: IconType;
icon?: IconName;
onConfirm(): void;
onDismiss(): void;
}

View File

@ -42,10 +42,10 @@ export const DataLinksListItem: FC<DataLinksListItemProps> = ({
<HorizontalGroup>
<div onClick={onEdit} className={styles.action}>
<Icon name="pencil" />
<Icon name="pen" />
</div>
<div onClick={onRemove} className={cx(styles.action, styles.remove)}>
<Icon name="trash" />
<Icon name="trash-alt" />
</div>
</HorizontalGroup>
</HorizontalGroup>

View File

@ -26,7 +26,7 @@ export const FieldConfigItemHeaderTitle: React.FC<FieldConfigItemHeaderTitleProp
<div className={styles.header}>
<Forms.Label description={description}>{title}</Forms.Label>
<div className={styles.remove} onClick={() => onRemove()} aria-label="FieldConfigItemHeaderTitle remove button">
<Icon name="trash" />
<Icon name="trash-alt" />
</div>
</div>
{children}

View File

@ -4,7 +4,7 @@ import { withCenteredStory } from '../../../utils/storybook/withCenteredStory';
import { Input } from './Input';
import { Button } from '../../Button';
import mdx from './Input.mdx';
import { getAvailableIcons, IconType } from '../../Icon/types';
import { getAvailableIcons, IconName } from '../../../types';
import { KeyValue } from '@grafana/data';
import { Icon } from '../../Icon/Icon';
import { Field } from '../Field';
@ -59,11 +59,11 @@ export const simple = () => {
const suffix = select('Suffix', prefixSuffixOpts, null, VISUAL_GROUP);
let prefixEl: any = prefix;
if (prefix && prefix.match(/icon-/g)) {
prefixEl = <Icon name={prefix.replace(/icon-/g, '') as IconType} />;
prefixEl = <Icon name={prefix.replace(/icon-/g, '') as IconName} />;
}
let suffixEl: any = suffix;
if (suffix && suffix.match(/icon-/g)) {
suffixEl = <Icon name={suffix.replace(/icon-/g, '') as IconType} />;
suffixEl = <Icon name={suffix.replace(/icon-/g, '') as IconName} />;
}
const CONTAINER_GROUP = 'Container options';

View File

@ -3,7 +3,6 @@ import { GrafanaTheme } from '@grafana/data';
import { css, cx } from 'emotion';
import { getFocusStyle, inputSizes, sharedInputStyle } from '../commonStyles';
import { stylesFactory, useTheme } from '../../../themes';
import { Icon } from '../../Icon/Icon';
import { useClientRect } from '../../../utils/useClientRect';
import { FormInputSize } from '../types';
@ -246,7 +245,7 @@ export const Input = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
{(suffix || loading) && (
<div className={styles.suffix} ref={suffixRef}>
{loading && <Icon name="spinner" className={cx('fa-spin', styles.loadingIndicator)} />}
{loading && <i className={cx('fa fa-spinner fa-spin', styles.loadingIndicator)} />}
{suffix}
</div>
)}

View File

@ -1,28 +0,0 @@
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
import { Icon } from './Icon';
<Meta title="MDX|Icon" component={Icon} />
# Icon
Grafana's wrapper component over [Font Awesome](https://fontawesome.com/) icons
### Changing icon size
By default `Icon` has width and height of `16px` and font-size of `14px`. Pass `className` to control icon's size:
```jsx
import { css } from 'emotion';
const customIconSize = css`
width: 20px;
height: 20px;
font-size: 18px;
`;
<Icon name="check" className={customIconSize} />
```
<Props of={Icon} />

View File

@ -1,103 +0,0 @@
import React, { ChangeEvent, useState } from 'react';
import { css } from 'emotion';
import { Forms } from '../index';
import { Icon } from './Icon';
import { getAvailableIcons, IconType } from './types';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { useTheme, selectThemeVariant } from '../../themes';
import mdx from './Icon.mdx';
export default {
title: 'General/Icon',
component: Icon,
decorators: [withCenteredStory],
parameters: {
docs: {
page: mdx,
},
},
};
const IconWrapper: React.FC<{ name: IconType }> = ({ name }) => {
const theme = useTheme();
const borderColor = selectThemeVariant(
{
light: theme.colors.gray5,
dark: theme.colors.dark6,
},
theme.type
);
return (
<div
className={css`
width: 150px;
padding: 12px;
border: 1px solid ${borderColor};
text-align: center;
&:hover {
background: ${borderColor};
}
`}
>
<Icon
name={name}
className={css`
font-size: 18px;
`}
/>
<div
className={css`
padding-top: 16px;
word-break: break-all;
font-family: ${theme.typography.fontFamily.monospace};
font-size: ${theme.typography.size.xs};
`}
>
{name}
</div>
</div>
);
};
const icons = getAvailableIcons().sort((a, b) => a.localeCompare(b));
export const simple = () => {
const [filter, setFilter] = useState('');
const searchIcon = (event: ChangeEvent<HTMLInputElement>) => {
setFilter(event.target.value);
};
return (
<div
className={css`
display: flex;
flex-direction: column;
width: 100%;
`}
>
<Forms.Field
className={css`
width: 300px;
`}
>
<Forms.Input onChange={searchIcon} placeholder="Search icons by name" />
</Forms.Field>
<div
className={css`
display: flex;
flex-wrap: wrap;
`}
>
{icons
.filter(val => val.includes(filter))
.map(i => {
return <IconWrapper name={i} key={i} />;
})}
</div>
</div>
);
};

View File

@ -1,36 +1,78 @@
import React from 'react';
import { cx, css } from 'emotion';
import { css, cx } from 'emotion';
import { GrafanaTheme, toPascalCase } from '@grafana/data';
import { stylesFactory } from '../../themes';
import { IconType } from './types';
import { useTheme } from '../../themes/ThemeContext';
import { IconName, IconType } from '../../types';
import { ComponentSize } from '../../types/size';
//@ts-ignore
import * as DefaultIcon from '@iconscout/react-unicons';
import * as MonoIcon from './assets';
export interface IconProps {
name: IconType;
interface IconProps extends React.HTMLAttributes<HTMLDivElement> {
name: IconName;
size?: ComponentSize | 'xl' | 'xxl';
type?: IconType;
}
export interface SvgProps extends React.HTMLAttributes<SVGElement> {
size: number;
secondaryColor?: string;
className?: string;
onClick?: () => void;
onMouseDown?: React.MouseEventHandler;
}
const getIconStyles = stylesFactory(() => {
const getIconStyles = stylesFactory((theme: GrafanaTheme, type: IconType) => {
const defaultIconColor = type === 'default' ? 'currentColor' : theme.colors.orange;
return {
container: css`
display: inline-block;
`,
icon: css`
display: inline-flex;
width: 16px;
align-items: center;
justify-content: center;
height: 16px;
text-align: center;
font-size: 14px;
&:before {
vertical-align: middle;
vertical-align: middle;
display: inline-block;
margin-bottom: ${theme.spacing.xxs};
cursor: pointer;
* {
fill: ${defaultIconColor};
}
`,
};
});
export const Icon: React.FC<IconProps> = ({ name, className, onClick, onMouseDown }) => {
const styles = getIconStyles();
return <i className={cx(styles.icon, 'fa', `fa-${name}`, className)} onClick={onClick} onMouseDown={onMouseDown} />;
};
export const Icon = React.forwardRef<HTMLDivElement, IconProps>(
({ size = 'md', type = 'default', title, name, className, style, ...divElementProps }, ref) => {
const theme = useTheme();
const styles = getIconStyles(theme, type);
const svgSize = getSvgSize(size, theme);
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 />;
}
return (
<div className={styles.container} {...divElementProps} ref={ref}>
{type === 'default' && <Component size={svgSize} className={cx(styles.icon, className)} style={style} />}
{type === 'mono' && <Component size={svgSize} className={cx(styles.icon, className)} style={style} />}
</div>
);
}
);
Icon.displayName = 'Icon';
/* Transform string with px to number and add 2 pxs as path in svg is 2px smaller */
const getSvgSize = (size: ComponentSize | 'xl' | 'xxl', theme: GrafanaTheme) => {
let svgSize;
if (size === 'xl') {
svgSize = Number(theme.typography.heading.h1.slice(0, -2));
} else if (size === 'xxl') {
svgSize = Number(theme.height.lg.slice(0, -2));
} else {
svgSize = Number(theme.typography.size[size].slice(0, -2)) + 2;
}
return svgSize;
};

View File

@ -0,0 +1,13 @@
import React, { FunctionComponent } from 'react';
import { SvgProps } from '../Icon';
export const Apps: FunctionComponent<SvgProps> = ({ size, ...rest }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width={size} height={size} {...rest}>
<rect width="9" height="9" x="2" y="2" rx="1" />
<rect width="9" height="9" x="2" y="13" rx="1" opacity="0.6" />
<rect width="9" height="9" x="13" y="2" rx="1" opacity="0.6" />
<rect width="9" height="9" x="13" y="13" rx="1" opacity="0.6" />
</svg>
);
};

View File

@ -0,0 +1,14 @@
import React, { FunctionComponent } from 'react';
import { SvgProps } from '../Icon';
export const Cog: FunctionComponent<SvgProps> = ({ size, ...rest }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width={size} height={size} {...rest}>
<path d="M21.31641,9.55176l-1.88672-.62891.88965-1.77832a.99983.99983,0,0,0-.1875-1.1543L18.01025,3.86816a.99981.99981,0,0,0-1.15429-.1875l-1.77832.88965-.62891-1.88672A1,1,0,0,0,13.5,2h-3a1,1,0,0,0-.94873.68359L8.92236,4.57031,7.144,3.68066a.99634.99634,0,0,0-1.15429.1875L3.86816,5.99023a.99983.99983,0,0,0-.1875,1.1543l.88965,1.77832-1.88672.62891A.9989.9989,0,0,0,2,10.5v3a.9989.9989,0,0,0,.68359.94824l1.88672.62891-.88965,1.77832a.99983.99983,0,0,0,.1875,1.1543l2.12159,2.12207a.99813.99813,0,0,0,1.15429.1875l1.77832-.88965.62891,1.88672A1,1,0,0,0,10.5,22h3a1,1,0,0,0,.94873-.68359l.62891-1.88672,1.77832.88965a.99994.99994,0,0,0,1.15429-.1875l2.12159-2.12207a.99983.99983,0,0,0,.1875-1.1543l-.88916-1.77832,1.88623-.62891A.9989.9989,0,0,0,22,13.5v-3A.9989.9989,0,0,0,21.31641,9.55176ZM12,15a3,3,0,1,1,3-3A3.00344,3.00344,0,0,1,12,15Z" />
<path
opacity="0.6"
d="M12,16a4,4,0,1,1,4-4A4.00427,4.00427,0,0,1,12,16Zm0-6a2,2,0,1,0,2,2A2.00229,2.00229,0,0,0,12,10Z"
/>
</svg>
);
};

View File

@ -0,0 +1,10 @@
import React, { FunctionComponent } from 'react';
import { SvgProps } from '../Icon';
export const Favorite: FunctionComponent<SvgProps> = ({ size, color, secondaryColor, ...rest }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width={size} height={size} {...rest}>
<path d="M17.56249,21.55957a.99941.99941,0,0,1-.46581-.11523L12,18.76465,6.90332,21.44434a.9999.9999,0,0,1-1.45117-1.05372l.97363-5.67578-4.124-4.01953a.99965.99965,0,0,1,.55469-1.70508l5.69824-.82812,2.54883-5.16406a1.04012,1.04012,0,0,1,1.793,0l2.54883,5.16406,5.69824.82812a.99965.99965,0,0,1,.55469,1.70508l-4.124,4.01953.97363,5.67578a1.00024,1.00024,0,0,1-.98536,1.169Z" />
</svg>
);
};

View File

@ -0,0 +1,14 @@
import React, { FunctionComponent } from 'react';
import { SvgProps } from '../Icon';
export const Shield: FunctionComponent<SvgProps> = ({ size, ...rest }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width={size} height={size} {...rest}>
<path d="M12,22a.9986.9986,0,0,1-.581-.18652l-3.6504-2.60743A9.01643,9.01643,0,0,1,4,11.88281v-7.457a1.00039,1.00039,0,0,1,1.20605-.97851,8.00088,8.00088,0,0,0,6.22168-1.26758.99888.99888,0,0,1,1.14454,0A7.9976,7.9976,0,0,0,18.794,3.44727,1.00039,1.00039,0,0,1,20,4.42578v7.457a9.01643,9.01643,0,0,1-3.76855,7.32324l-3.6504,2.60743A.9986.9986,0,0,1,12,22Z" />
<path
opacity="0.6"
d="M10.84961,14.7002h0a.99927.99927,0,0,1-.707-.293L8.543,12.80664A.99989.99989,0,0,1,9.957,11.39258l.89258.89355L13.543,9.59277A.99989.99989,0,1,1,14.957,11.00684l-3.40039,3.40039A.99928.99928,0,0,1,10.84961,14.7002Z"
/>
</svg>
);
};

View File

@ -0,0 +1,4 @@
export * from './Apps';
export * from './Cog';
export * from './Shield';
export * from './Favorite';

File diff suppressed because it is too large Load Diff

View File

@ -2,13 +2,13 @@ import React from 'react';
import { Portal } from '../Portal/Portal';
import { cx } from 'emotion';
import { withTheme } from '../../themes';
import { IconType } from '../Icon/types';
import { IconName } from '../../types';
import { Themeable } from '../../types';
import { getModalStyles } from './getModalStyles';
import { ModalHeader } from './ModalHeader';
interface Props extends Themeable {
icon?: IconType;
icon?: IconName;
title: string | JSX.Element;
className?: string;

View File

@ -1,12 +1,12 @@
import React, { useContext } from 'react';
import { getModalStyles } from './getModalStyles';
import { IconType } from '../Icon/types';
import { IconName } from '../../types';
import { ThemeContext } from '../../themes';
import { Icon } from '../Icon/Icon';
interface Props {
title: string;
icon?: IconType;
icon?: IconName;
}
export const ModalHeader: React.FC<Props> = ({ icon, title, children }) => {

View File

@ -1,9 +1,9 @@
import React from 'react';
import { IconType } from '../Icon/types';
import { IconName } from '../../types';
import { Icon } from '../Icon/Icon';
interface Props {
icon?: IconType;
icon?: IconName;
iconClass?: string;
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { IconType } from '../Icon/types';
import { IconName } from '../../types';
import { TabsBar } from '../Tabs/TabsBar';
import { Tab } from '../Tabs/Tab';
import { ModalHeader } from './ModalHeader';
@ -11,7 +11,7 @@ interface ModalTab {
}
interface Props {
icon: IconType;
icon: IconName;
title: string;
tabs: ModalTab[];
activeTab: string;

View File

@ -3,6 +3,7 @@ import classNames from 'classnames';
import { SelectableValue } from '@grafana/data';
import { css } from 'emotion';
import { Tooltip } from '../Tooltip/Tooltip';
import { Icon } from '../Icon/Icon';
import { ButtonSelect } from '../Forms/Legacy/Select/ButtonSelect';
import memoizeOne from 'memoize-one';
import { GrafanaTheme } from '@grafana/data';
@ -87,7 +88,7 @@ export class RefreshPickerBase extends PureComponent<Props> {
className="btn btn--radius-right-0 navbar-button navbar-button--border-right-0"
onClick={onRefresh!}
>
<i className="fa fa-refresh" />
<Icon name="sync" />
</button>
</Tooltip>
)}

View File

@ -8,16 +8,16 @@ import { SelectCommonProps, CustomControlProps } from './types';
import { SelectBase } from './SelectBase';
import { stylesFactory, useTheme } from '../../themes';
import { Icon } from '../Icon/Icon';
import { IconType } from '../Icon/types';
import { IconName } from '../../types';
interface ButtonSelectProps<T> extends Omit<SelectCommonProps<T>, 'renderControl' | 'size' | 'prefix'> {
icon?: IconType;
icon?: IconName;
variant?: ButtonVariant;
size?: ComponentSize;
}
interface SelectButtonProps extends Omit<ButtonProps, 'icon'> {
icon?: IconType;
icon?: IconName;
isOpen?: boolean;
}
@ -42,14 +42,12 @@ const SelectButton = React.forwardRef<HTMLButtonElement, SelectButtonProps>(
`,
}));
const styles = getStyles(useTheme());
const buttonIcon = `fa fa-${icon}`;
const caretIcon = isOpen ? 'caret-up' : 'caret-down';
return (
<Button {...buttonProps} ref={ref} icon={buttonIcon}>
<Button {...buttonProps} ref={ref} icon={icon}>
<span className={styles.wrapper}>
<span>{children}</span>
<span className={styles.caretWrap}>
<Icon name={caretIcon} />
<Icon name={isOpen ? 'angle-up' : 'angle-down'} />
</span>
</span>
</Button>

View File

@ -6,6 +6,6 @@ interface DropdownIndicatorProps {
}
export const DropdownIndicator: React.FC<DropdownIndicatorProps> = ({ isOpen }) => {
const icon = isOpen ? 'caret-up' : 'caret-down';
const icon = isOpen ? 'angle-up' : 'angle-down';
return <Icon name={icon} />;
};

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Select, AsyncSelect, MultiSelect, AsyncMultiSelect } from './Select';
import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
import { SelectableValue } from '@grafana/data';
import { getAvailableIcons, IconType } from '../Icon/types';
import { getAvailableIcons, IconName } from '../../types';
import { select, boolean } from '@storybook/addon-knobs';
import { Icon } from '../Icon/Icon';
import { Button } from '../Button';
@ -46,7 +46,7 @@ const getKnobs = () => {
let prefixEl: any = prefix;
if (prefix && prefix.match(/icon-/g)) {
prefixEl = <Icon name={prefix.replace(/icon-/g, '') as IconType} />;
prefixEl = <Icon name={prefix.replace(/icon-/g, '') as IconName} />;
}
return {

View File

@ -242,7 +242,7 @@ export function SelectBase<T>({
);
},
LoadingIndicator: (props: any) => {
return <Icon name="spinner" className="fa fa-spin" />;
return <i className="fa fa-spinner fa-spin" />;
},
LoadingMessage: (props: any) => {
return <div className={styles.loadingMessage}>{loadingMessage}</div>;

View File

@ -105,7 +105,7 @@ function renderHeaderCell(column: any, className: string, field?: Field) {
return (
<div className={className} {...headerProps}>
{column.render('Header')}
{column.isSorted && (column.isSortedDesc ? <Icon name="caret-down" /> : <Icon name="caret-up" />)}
{column.isSorted && (column.isSortedDesc ? <Icon name="angle-down" /> : <Icon name="angle-up" />)}
</div>
);
}

View File

@ -182,7 +182,9 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
{isPercent && <div className={styles.percentIcon}>%</div>}
</div>
}
suffix={<Icon className={styles.trashIcon} name="trash" onClick={() => this.onRemoveThreshold(threshold)} />}
suffix={
<Icon className={styles.trashIcon} name="trash-alt" onClick={() => this.onRemoveThreshold(threshold)} />
}
/>
);
}

View File

@ -111,7 +111,7 @@ interface CaretProps {
const Caret: FC<CaretProps> = ({ wrapperStyle = '' }) => {
return (
<div className={wrapperStyle}>
<Icon name="caret-down" />
<Icon name="angle-down" />
</div>
);
};

View File

@ -4,6 +4,7 @@ import { css, cx } from 'emotion';
// Components
import { Tooltip } from '../Tooltip/Tooltip';
import { Icon } from '../Icon/Icon';
import { TimePickerContent } from './TimePickerContent/TimePickerContent';
import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper';
@ -63,11 +64,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
display: flex;
`,
caretIcon: css`
margin-left: 3px;
i {
font-size: ${theme.typography.size.md};
}
margin-left: ${theme.spacing.xs};
`,
clockIcon: css`
margin-right: ${theme.spacing.xs};
`,
noRightBorderStyle: css`
label: noRightBorderStyle;
@ -144,7 +144,7 @@ export class UnthemedTimePicker extends PureComponent<Props, State> {
const styles = getStyles(theme);
const hasAbsolute = isDateTime(value.raw.from) || isDateTime(value.raw.to);
const syncedTimePicker = timeSyncButton && isSynced;
const timePickerIconClass = cx('fa fa-clock-o fa-fw', { ['icon-brand-gradient']: syncedTimePicker });
const timePickerIconClass = cx({ ['icon-brand-gradient']: syncedTimePicker });
const timePickerButtonClass = cx('btn navbar-button navbar-button--tight', {
[`btn--radius-right-0 ${styles.noRightBorderStyle}`]: !!timeSyncButton,
[`explore-active-button`]: syncedTimePicker,
@ -155,17 +155,15 @@ export class UnthemedTimePicker extends PureComponent<Props, State> {
<div className={styles.buttons}>
{hasAbsolute && (
<button className="btn navbar-button navbar-button--tight" onClick={onMoveBackward}>
<i className="fa fa-chevron-left" />
<Icon name="angle-left" />
</button>
)}
<div>
<Tooltip content={<TimePickerTooltip timeRange={value} />} placement="bottom">
<button aria-label="TimePicker Open Button" className={timePickerButtonClass} onClick={this.onOpen}>
<i className={timePickerIconClass} />
<Icon name="clock-nine" className={cx(styles.clockIcon, timePickerIconClass)} />
<TimePickerButtonLabel {...this.props} />
<span className={styles.caretIcon}>
{isOpen ? <i className="fa fa-caret-up fa-fw" /> : <i className="fa fa-caret-down fa-fw" />}
</span>
<span className={styles.caretIcon}>{<Icon name={isOpen ? 'angle-up' : 'angle-down'} />}</span>
</button>
</Tooltip>
{isOpen && (
@ -186,13 +184,13 @@ export class UnthemedTimePicker extends PureComponent<Props, State> {
{hasAbsolute && (
<button className="btn navbar-button navbar-button--tight" onClick={onMoveForward}>
<i className="fa fa-chevron-right" />
<Icon name="angle-right" />
</button>
)}
<Tooltip content={ZoomOutTooltip} placement="bottom">
<button className="btn navbar-button navbar-button--zoom" onClick={onZoom}>
<i className="fa fa-search-minus" />
<Icon name="search-minus" />
</button>
</Tooltip>
</div>

View File

@ -3,6 +3,7 @@ import { useMedia } from 'react-use';
import { css } from 'emotion';
import { useTheme, stylesFactory } from '../../../themes';
import { GrafanaTheme, TimeOption, TimeRange, TimeZone, isDateTime } from '@grafana/data';
import { Icon } from '../../Icon/Icon';
import { TimePickerTitle } from './TimePickerTitle';
import { TimeRangeForm } from './TimeRangeForm';
import { CustomScrollbar } from '../../CustomScrollbar/CustomScrollbar';
@ -193,7 +194,7 @@ const NarrowScreenForm: React.FC<FormProps> = props => {
<>
<div className={styles.header} onClick={() => setCollapsed(!collapsed)}>
<TimePickerTitle>Absolute time range</TimePickerTitle>
{collapsed ? <i className="fa fa-caret-up" /> : <i className="fa fa-caret-down" />}
{<Icon name={collapsed ? 'angle-up' : 'angle-down'} />}
</div>
{collapsed && (
<div className={styles.body}>

View File

@ -11,8 +11,8 @@ exports[`TimePicker renders buttons correctly 1`] = `
className="btn navbar-button navbar-button--tight"
onClick={[Function]}
>
<i
className="fa fa-chevron-left"
<Icon
name="angle-left"
/>
</button>
<div>
@ -38,8 +38,9 @@ exports[`TimePicker renders buttons correctly 1`] = `
className="btn navbar-button navbar-button--tight"
onClick={[Function]}
>
<i
className="fa fa-clock-o fa-fw"
<Icon
className="css-znfyx6"
name="clock-nine"
/>
<Memo()
onChange={[Function]}
@ -276,10 +277,10 @@ exports[`TimePicker renders buttons correctly 1`] = `
}
/>
<span
className="css-6x26ye"
className="css-132xth5"
>
<i
className="fa fa-caret-down fa-fw"
<Icon
name="angle-down"
/>
</span>
</button>
@ -289,8 +290,8 @@ exports[`TimePicker renders buttons correctly 1`] = `
className="btn navbar-button navbar-button--tight"
onClick={[Function]}
>
<i
className="fa fa-chevron-right"
<Icon
name="angle-right"
/>
</button>
<Component
@ -301,8 +302,8 @@ exports[`TimePicker renders buttons correctly 1`] = `
className="btn navbar-button navbar-button--zoom"
onClick={[Function]}
>
<i
className="fa fa-search-minus"
<Icon
name="search-minus"
/>
</button>
</Component>
@ -321,8 +322,8 @@ exports[`TimePicker renders content correctly after beeing open 1`] = `
className="btn navbar-button navbar-button--tight"
onClick={[Function]}
>
<i
className="fa fa-chevron-left"
<Icon
name="angle-left"
/>
</button>
<div>
@ -348,8 +349,9 @@ exports[`TimePicker renders content correctly after beeing open 1`] = `
className="btn navbar-button navbar-button--tight"
onClick={[Function]}
>
<i
className="fa fa-clock-o fa-fw"
<Icon
className="css-znfyx6"
name="clock-nine"
/>
<Memo()
onChange={[Function]}
@ -586,10 +588,10 @@ exports[`TimePicker renders content correctly after beeing open 1`] = `
}
/>
<span
className="css-6x26ye"
className="css-132xth5"
>
<i
className="fa fa-caret-up fa-fw"
<Icon
name="angle-up"
/>
</span>
</button>
@ -804,8 +806,8 @@ exports[`TimePicker renders content correctly after beeing open 1`] = `
className="btn navbar-button navbar-button--tight"
onClick={[Function]}
>
<i
className="fa fa-chevron-right"
<Icon
name="angle-right"
/>
</button>
<Component
@ -816,8 +818,8 @@ exports[`TimePicker renders content correctly after beeing open 1`] = `
className="btn navbar-button navbar-button--zoom"
onClick={[Function]}
>
<i
className="fa fa-search-minus"
<Icon
name="search-minus"
/>
</button>
</Component>

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { IconType } from '../Icon/types';
import { IconName } from '../../types';
import { SelectableValue } from '@grafana/data';
import { Button, ButtonVariant } from '../Button';
import { Select } from '../Select/Select';
@ -9,7 +9,7 @@ interface ValuePickerProps<T> {
/** Label to display on the picker button */
label: string;
/** Icon to display on the picker button */
icon?: IconType;
icon?: IconName;
/** ValuePicker options */
options: Array<SelectableValue<T>>;
onChange: (value: SelectableValue<T>) => void;

View File

@ -0,0 +1,128 @@
export type IconType = 'mono' | 'default';
export type IconName =
| 'question-circle'
| 'plus'
| 'angle-up'
| 'angle-down'
| 'angle-left'
| 'angle-right'
| 'pen'
| 'plane'
| 'power'
| 'trash-alt'
| 'exclamation-triangle'
| 'times'
| 'step-backward'
| 'square-shape'
| 'share-alt'
| 'forward'
| 'check'
| 'add-panel'
| 'copy'
| 'lock'
| 'panel-add'
| 'arrow-random'
| 'arrow-from-right'
| 'keyboard'
| 'search'
| 'chart-line'
| 'search-minus'
| 'clock-nine'
| 'sync'
| 'signin'
| 'cog'
| 'bars'
| 'save'
| 'apps'
| 'folder-plus'
| 'link'
| 'upload'
| 'home-alt'
| 'compass'
| 'sliders-v-alt'
| 'bell'
| 'database'
| 'user'
| 'plug'
| 'shield'
| 'key-skeleton-alt'
| 'users-alt'
| 'graph-bar'
| 'book'
| 'bolt'
| 'comments-alt'
| 'document-info'
| 'info-circle'
| 'bug'
| 'cube'
| 'star'
| 'edit'
| 'shield'
| 'eye'
| 'monitor'
| 'favorite';
export const getAvailableIcons = (): IconName[] => [
'question-circle',
'plane',
'plus',
'angle-up',
'shield',
'angle-down',
'angle-left',
'angle-right',
'pen',
'power',
'trash-alt',
'exclamation-triangle',
'times',
'step-backward',
'square-shape',
'share-alt',
'forward',
'check',
'add-panel',
'copy',
'lock',
'panel-add',
'arrow-random',
'arrow-from-right',
'keyboard',
'search',
'chart-line',
'search-minus',
'clock-nine',
'sync',
'signin',
'cog',
'bars',
'save',
'apps',
'folder-plus',
'link',
'upload',
'home-alt',
'compass',
'sliders-v-alt',
'bell',
'database',
'user',
'plug',
'shield',
'key-skeleton-alt',
'users-alt',
'graph-bar',
'book',
'bolt',
'comments-alt',
'document-info',
'info-circle',
'bug',
'cube',
'star',
'edit',
'eye',
'monitor',
'favorite',
];

View File

@ -3,3 +3,4 @@ export * from './input';
export * from './completion';
export * from './storybook';
export * from './forms';
export * from './icon';

View File

@ -1,5 +1,5 @@
import { select } from '@storybook/addon-knobs';
import { getAvailableIcons } from '../../components/Icon/types';
import { getAvailableIcons } from '../../types';
const VISUAL_GROUP = 'Visual options';

View File

@ -109,19 +109,19 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
if hasEditPermissionInFoldersQuery.Result {
children := []*dtos.NavLink{
{Text: "Dashboard", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/dashboard/new"},
{Text: "Dashboard", Icon: "apps", Url: setting.AppSubUrl + "/dashboard/new"},
}
if c.OrgRole == models.ROLE_ADMIN || c.OrgRole == models.ROLE_EDITOR {
children = append(children, &dtos.NavLink{Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "gicon gicon-folder-new", Url: setting.AppSubUrl + "/dashboards/folder/new"})
children = append(children, &dtos.NavLink{Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "folder-plus", Url: setting.AppSubUrl + "/dashboards/folder/new"})
}
children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"})
children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "upload", Url: setting.AppSubUrl + "/dashboard/import"})
data.NavTree = append(data.NavTree, &dtos.NavLink{
Text: "Create",
Id: "create",
Icon: "fa fa-fw fa-plus",
Icon: "plus",
Url: setting.AppSubUrl + "/dashboard/new",
Children: children,
SortWeight: dtos.WeightCreate,
@ -129,18 +129,18 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
}
dashboardChildNavs := []*dtos.NavLink{
{Text: "Home", Id: "home", Url: setting.AppSubUrl + "/", Icon: "gicon gicon-home", HideFromTabs: true},
{Text: "Home", Id: "home", Url: setting.AppSubUrl + "/", Icon: "home-alt", HideFromTabs: true},
{Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true},
{Text: "Manage", Id: "manage-dashboards", Url: setting.AppSubUrl + "/dashboards", Icon: "gicon gicon-manage"},
{Text: "Playlists", Id: "playlists", Url: setting.AppSubUrl + "/playlists", Icon: "gicon gicon-playlists"},
{Text: "Snapshots", Id: "snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots", Icon: "gicon gicon-snapshots"},
{Text: "Manage", Id: "manage-dashboards", Url: setting.AppSubUrl + "/dashboards", Icon: "sitemap"},
{Text: "Playlists", Id: "playlists", Url: setting.AppSubUrl + "/playlists", Icon: "presentation-play"},
{Text: "Snapshots", Id: "snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots", Icon: "camera"},
}
data.NavTree = append(data.NavTree, &dtos.NavLink{
Text: "Dashboards",
Id: "dashboards",
SubTitle: "Manage dashboards & folders",
Icon: "gicon gicon-dashboard",
Icon: "apps",
Url: setting.AppSubUrl + "/",
SortWeight: dtos.WeightDashboard,
Children: dashboardChildNavs,
@ -151,7 +151,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
Text: "Explore",
Id: "explore",
SubTitle: "Explore your data",
Icon: "gicon gicon-explore",
Icon: "compass",
SortWeight: dtos.WeightExplore,
Url: setting.AppSubUrl + "/explore",
})
@ -172,8 +172,8 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
HideFromMenu: true,
SortWeight: dtos.WeightProfile,
Children: []*dtos.NavLink{
{Text: "Preferences", Id: "profile-settings", Url: setting.AppSubUrl + "/profile", Icon: "gicon gicon-preferences"},
{Text: "Change Password", Id: "change-password", Url: setting.AppSubUrl + "/profile/password", Icon: "fa fa-fw fa-lock", HideFromMenu: true},
{Text: "Preferences", Id: "profile-settings", Url: setting.AppSubUrl + "/profile", Icon: "sliders-v-alt"},
{Text: "Change Password", Id: "change-password", Url: setting.AppSubUrl + "/profile/password", Icon: "lock", HideFromMenu: true},
},
}
@ -183,7 +183,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
Text: "Sign out",
Id: "sign-out",
Url: setting.AppSubUrl + "/logout",
Icon: "fa fa-fw fa-sign-out",
Icon: "arrow-from-right",
Target: "_self",
HideFromTabs: true,
})
@ -194,15 +194,15 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
if setting.AlertingEnabled && (c.OrgRole == models.ROLE_ADMIN || c.OrgRole == models.ROLE_EDITOR) {
alertChildNavs := []*dtos.NavLink{
{Text: "Alert Rules", Id: "alert-list", Url: setting.AppSubUrl + "/alerting/list", Icon: "gicon gicon-alert-rules"},
{Text: "Notification channels", Id: "channels", Url: setting.AppSubUrl + "/alerting/notifications", Icon: "gicon gicon-alert-notification-channel"},
{Text: "Alert Rules", Id: "alert-list", Url: setting.AppSubUrl + "/alerting/list", Icon: "list-ul"},
{Text: "Notification channels", Id: "channels", Url: setting.AppSubUrl + "/alerting/notifications", Icon: "comment-alt-share"},
}
data.NavTree = append(data.NavTree, &dtos.NavLink{
Text: "Alerting",
SubTitle: "Alert rules & notifications",
Id: "alerting",
Icon: "gicon gicon-alert",
Icon: "bell",
Url: setting.AppSubUrl + "/alerting/list",
Children: alertChildNavs,
SortWeight: dtos.WeightAlerting,
@ -259,7 +259,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
if len(appLink.Children) > 0 && c.OrgRole == models.ROLE_ADMIN {
appLink.Children = append(appLink.Children, &dtos.NavLink{Divider: true})
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "gicon gicon-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/"})
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/"})
}
if len(appLink.Children) > 0 {
@ -273,7 +273,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
if c.OrgRole == models.ROLE_ADMIN {
configNodes = append(configNodes, &dtos.NavLink{
Text: "Data Sources",
Icon: "gicon gicon-datasources",
Icon: "database",
Description: "Add and configure data sources",
Id: "datasources",
Url: setting.AppSubUrl + "/datasources",
@ -282,7 +282,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
Text: "Users",
Id: "users",
Description: "Manage org members",
Icon: "gicon gicon-user",
Icon: "user",
Url: setting.AppSubUrl + "/org/users",
})
}
@ -292,7 +292,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
Text: "Teams",
Id: "teams",
Description: "Manage org groups",
Icon: "gicon gicon-team",
Icon: "users-alt",
Url: setting.AppSubUrl + "/org/teams",
})
}
@ -302,7 +302,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
Text: "Plugins",
Id: "plugins",
Description: "View and configure plugins",
Icon: "gicon gicon-plugins",
Icon: "plug",
Url: setting.AppSubUrl + "/plugins",
})
@ -310,14 +310,14 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
Text: "Preferences",
Id: "org-settings",
Description: "Organization preferences",
Icon: "gicon gicon-preferences",
Icon: "sliders-v-alt",
Url: setting.AppSubUrl + "/org",
})
configNodes = append(configNodes, &dtos.NavLink{
Text: "API Keys",
Id: "apikeys",
Description: "Create & manage API keys",
Icon: "gicon gicon-apikeys",
Icon: "key-skeleton-alt",
Url: setting.AppSubUrl + "/org/apikeys",
})
}
@ -327,7 +327,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
Id: "cfg",
Text: "Configuration",
SubTitle: "Organization: " + c.OrgName,
Icon: "gicon gicon-cog",
Icon: "cog",
Url: configNodes[0].Url,
SortWeight: dtos.WeightConfig,
Children: configNodes,
@ -336,15 +336,15 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
if c.IsGrafanaAdmin {
adminNavLinks := []*dtos.NavLink{
{Text: "Users", Id: "global-users", Url: setting.AppSubUrl + "/admin/users", Icon: "gicon gicon-user"},
{Text: "Orgs", Id: "global-orgs", Url: setting.AppSubUrl + "/admin/orgs", Icon: "gicon gicon-org"},
{Text: "Settings", Id: "server-settings", Url: setting.AppSubUrl + "/admin/settings", Icon: "gicon gicon-preferences"},
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "fa fa-fw fa-bar-chart"},
{Text: "Users", Id: "global-users", Url: setting.AppSubUrl + "/admin/users", Icon: "user"},
{Text: "Orgs", Id: "global-orgs", Url: setting.AppSubUrl + "/admin/orgs", Icon: "building"},
{Text: "Settings", Id: "server-settings", Url: setting.AppSubUrl + "/admin/settings", Icon: "sliders-v-alt"},
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "graph-bar"},
}
if setting.LDAPEnabled {
adminNavLinks = append(adminNavLinks, &dtos.NavLink{
Text: "LDAP", Id: "ldap", Url: setting.AppSubUrl + "/admin/ldap", Icon: "fa fa-fw fa-address-book-o",
Text: "LDAP", Id: "ldap", Url: setting.AppSubUrl + "/admin/ldap", Icon: "book",
})
}
@ -353,7 +353,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
SubTitle: "Manage all users & orgs",
HideFromTabs: true,
Id: "admin",
Icon: "gicon gicon-shield",
Icon: "shield",
Url: setting.AppSubUrl + "/admin/users",
SortWeight: dtos.WeightAdmin,
Children: adminNavLinks,
@ -365,7 +365,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
SubTitle: fmt.Sprintf(`%s v%s (%s)`, setting.ApplicationName, setting.BuildVersion, setting.BuildCommit),
Id: "help",
Url: "#",
Icon: "gicon gicon-question",
Icon: "question-circle",
HideFromMenu: true,
SortWeight: dtos.WeightHelp,
Children: []*dtos.NavLink{},

View File

@ -44,7 +44,7 @@ func (l *OSSLicensingService) Init() error {
Text: "Upgrade",
Id: "upgrading",
Url: l.LicenseURL(req.SignedInUser),
Icon: "fa fa-fw fa-unlock-alt",
Icon: "unlock",
})
}
}

View File

@ -12,19 +12,19 @@ export let getFooterLinks = (): FooterLink[] => {
return [
{
text: 'Documentation',
icon: 'fa fa-file-code-o',
icon: 'document-info',
url: 'https://grafana.com/docs/grafana/latest/?utm_source=grafana_footer',
target: '_blank',
},
{
text: 'Support',
icon: 'fa fa-support',
icon: 'question-circle',
url: 'https://grafana.com/products/enterprise/?utm_source=grafana_footer',
target: '_blank',
},
{
text: 'Community',
icon: 'fa fa-comments-o',
icon: 'comments-alt',
url: 'https://community.grafana.com/?utm_source=grafana_footer',
target: '_blank',
},

View File

@ -47,7 +47,7 @@ export class OrgSwitcher extends React.PureComponent<Props, State> {
const currentOrgId = contextSrv.user.orgId;
return (
<Modal title="Switch Organization" icon="random" onDismiss={onDismiss} isOpen={true}>
<Modal title="Switch Organization" icon="arrow-random" onDismiss={onDismiss} isOpen={true}>
<table className="filter-table form-inline">
<thead>
<tr>

View File

@ -1,7 +1,9 @@
import React, { PureComponent } from 'react';
import { css } from 'emotion';
import appEvents from '../../app_events';
import { User } from '../../services/context_srv';
import { NavModelItem } from '@grafana/data';
import { Icon, IconName } from '@grafana/ui';
import { CoreEvents } from 'app/types';
import { OrgSwitcher } from '../OrgSwitcher';
import { getFooterLinks } from '../Footer/Footer';
@ -15,7 +17,7 @@ interface State {
showSwitcherModal: boolean;
}
class BottomNavLinks extends PureComponent<Props, State> {
export default class BottomNavLinks extends PureComponent<Props, State> {
state: State = {
showSwitcherModal: false,
};
@ -35,6 +37,9 @@ class BottomNavLinks extends PureComponent<Props, State> {
render() {
const { link, user } = this.props;
const { showSwitcherModal } = this.state;
const subMenuIconClassName = css`
margin-right: 8px;
`;
let children = link.children || [];
@ -46,7 +51,7 @@ class BottomNavLinks extends PureComponent<Props, State> {
<div className="sidemenu-item dropdown dropup">
<a href={link.url} className="sidemenu-link" target={link.target}>
<span className="icon-circle sidemenu-icon">
{link.icon && <i className={link.icon} />}
{link.icon && <Icon name={link.icon as IconName} size="xl" />}
{link.img && <img src={link.img} />}
</span>
</a>
@ -64,7 +69,7 @@ class BottomNavLinks extends PureComponent<Props, State> {
<div className="sidemenu-org-switcher__org-name">{user.orgName}</div>
</div>
<div className="sidemenu-org-switcher__switch">
<i className="fa fa-fw fa-random" />
<Icon name="arrow-random" className={subMenuIconClassName} />
Switch
</div>
</a>
@ -77,7 +82,7 @@ class BottomNavLinks extends PureComponent<Props, State> {
return (
<li key={`${child.text}-${index}`}>
<a href={child.url} target={child.target} rel="noopener">
{child.icon && <i className={child.icon} />}
{child.icon && <Icon name={child.icon as IconName} className={subMenuIconClassName} />}
{child.text}
</a>
</li>
@ -87,7 +92,7 @@ class BottomNavLinks extends PureComponent<Props, State> {
{link.id === 'help' && (
<li key="keyboard-shortcuts">
<a onClick={() => this.onOpenShortcuts()}>
<i className="fa fa-keyboard-o" /> Keyboard shortcuts
<Icon name="keyboard" className={subMenuIconClassName} /> Keyboard shortcuts
</a>
</li>
)}
@ -100,5 +105,3 @@ class BottomNavLinks extends PureComponent<Props, State> {
);
}
}
export default BottomNavLinks;

View File

@ -1,4 +1,6 @@
import React, { FC } from 'react';
import { css } from 'emotion';
import { Icon, IconName, useTheme } from '@grafana/ui';
export interface Props {
child: any;
@ -7,11 +9,15 @@ export interface Props {
const DropDownChild: FC<Props> = props => {
const { child } = props;
const listItemClassName = child.divider ? 'divider' : '';
const theme = useTheme();
const iconClassName = css`
margin-right: ${theme.spacing.sm};
`;
return (
<li className={listItemClassName}>
<a href={child.url}>
{child.icon && <i className={child.icon} />}
{child.icon && <Icon name={child.icon as IconName} className={iconClassName} />}
{child.text}
</a>
</li>

View File

@ -5,6 +5,7 @@ import BottomSection from './BottomSection';
import config from 'app/core/config';
import { CoreEvents } from 'app/types';
import { Branding } from 'app/core/components/Branding/Branding';
import { Icon } from '@grafana/ui';
const homeUrl = config.appSubUrl || '/';
@ -19,9 +20,9 @@ export class SideMenu extends PureComponent {
<Branding.MenuLogo />
</a>,
<div className="sidemenu__logo_small_breakpoint" onClick={this.toggleSideMenuSmallBreakpoint} key="hamburger">
<i className="fa fa-bars" />
<Icon name="bars" />
<span className="sidemenu__close">
<i className="fa fa-times" />
<Icon name="times" />
&nbsp;Close
</span>
</div>,

View File

@ -1,5 +1,6 @@
import React, { FC } from 'react';
import SideMenuDropDown from './SideMenuDropDown';
import { Icon } from '@grafana/ui';
export interface Props {
link: any;
@ -11,7 +12,7 @@ const TopSectionItem: FC<Props> = props => {
<div className="sidemenu-item dropdown">
<a className="sidemenu-link" href={link.url} target={link.target}>
<span className="icon-circle sidemenu-icon">
<i className={link.icon} />
<Icon name={link.icon} size="xl" />
{link.img && <img src={link.img} />}
</span>
</a>

View File

@ -118,8 +118,9 @@ exports[`Render should render organization switcher 1`] = `
<div
className="sidemenu-org-switcher__switch"
>
<i
className="fa fa-fw fa-random"
<Icon
className="css-f8is2k"
name="arrow-random"
/>
Switch
</div>

View File

@ -13,8 +13,9 @@ exports[`Render should render icon if exists 1`] = `
className=""
>
<a>
<i
className="icon-test"
<Icon
className="css-290ig"
name="icon-test"
/>
</a>
</li>

View File

@ -14,14 +14,14 @@ Array [
key="hamburger"
onClick={[Function]}
>
<i
className="fa fa-bars"
<Icon
name="bars"
/>
<span
className="sidemenu__close"
>
<i
className="fa fa-times"
<Icon
name="times"
/>
 Close
</span>

View File

@ -10,7 +10,9 @@ exports[`Render should render component 1`] = `
<span
className="icon-circle sidemenu-icon"
>
<i />
<Icon
size="xl"
/>
</span>
</a>
<SideMenuDropDown

View File

@ -164,7 +164,7 @@ exports[`ServerStats Should render table with stats 1`] = `
target="_blank"
>
<i
className="fa fa-file-code-o"
className="document-info"
/>
Documentation
@ -177,7 +177,7 @@ exports[`ServerStats Should render table with stats 1`] = `
target="_blank"
>
<i
className="fa fa-support"
className="question-circle"
/>
Support
@ -190,7 +190,7 @@ exports[`ServerStats Should render table with stats 1`] = `
target="_blank"
>
<i
className="fa fa-comments-o"
className="comments-alt"
/>
Community

View File

@ -2,6 +2,7 @@
import React from 'react';
import _ from 'lodash';
import { LocationUpdate } from '@grafana/runtime';
import { Icon, IconName } from '@grafana/ui';
import { e2e } from '@grafana/e2e';
import { connect, MapDispatchToProps } from 'react-redux';
// Utils
@ -139,7 +140,7 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
dashboard.removePanel(this.props.panel);
};
renderOptionLink = (icon: string, text: string, onClick: any) => {
renderOptionLink = (icon: IconName, text: string, onClick: any) => {
return (
<div>
<a
@ -149,7 +150,7 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
aria-label={e2e.pages.AddDashboard.selectors.ctaButtons(text)}
>
<div className="add-panel-widget__icon">
<i className={`gicon gicon-${icon}`} />
<Icon name={icon} size="xl" />
</div>
<span>{text}</span>
</a>
@ -164,16 +165,16 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
<div className="panel-container add-panel-widget-container">
<div className="add-panel-widget">
<div className="add-panel-widget__header grid-drag-handle">
<i className="gicon gicon-add-panel" />
<Icon name="apps" type="mono" size="xl" style={{ margin: '4px', marginRight: '8px' }} />
<span className="add-panel-widget__title">New Panel</span>
<button className="add-panel-widget__close" onClick={this.handleCloseAddPanel}>
<i className="fa fa-close" />
<Icon name="times" />
</button>
</div>
<div className="add-panel-widget__btn-container">
<div className="add-panel-widget__create">
{this.renderOptionLink('queries', 'Add Query', this.onCreateNewPanel)}
{this.renderOptionLink('visualization', 'Choose Visualization', () =>
{this.renderOptionLink('search', 'Add Query', this.onCreateNewPanel)}
{this.renderOptionLink('chart-line', 'Choose Visualization', () =>
this.onCreateNewPanel('visualization')
)}
</div>

View File

@ -10,8 +10,16 @@ exports[`Render should render component 1`] = `
<div
className="add-panel-widget__header grid-drag-handle"
>
<i
className="gicon gicon-add-panel"
<Icon
name="apps"
size="xl"
style={
Object {
"margin": "4px",
"marginRight": "8px",
}
}
type="mono"
/>
<span
className="add-panel-widget__title"
@ -22,8 +30,8 @@ exports[`Render should render component 1`] = `
className="add-panel-widget__close"
onClick={[Function]}
>
<i
className="fa fa-close"
<Icon
name="times"
/>
</button>
</div>
@ -43,8 +51,9 @@ exports[`Render should render component 1`] = `
<div
className="add-panel-widget__icon"
>
<i
className="gicon gicon-queries"
<Icon
name="search"
size="xl"
/>
</div>
<span>
@ -62,8 +71,9 @@ exports[`Render should render component 1`] = `
<div
className="add-panel-widget__icon"
>
<i
className="gicon gicon-visualization"
<Icon
name="chart-line"
size="xl"
/>
</div>
<span>

View File

@ -1,6 +1,7 @@
// Libaries
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { css } from 'emotion';
import { e2e } from '@grafana/e2e';
// Utils & Services
import { appEvents } from 'app/core/app_events';
@ -8,7 +9,7 @@ import { PlaylistSrv } from 'app/features/playlist/playlist_srv';
// Components
import { DashNavButton } from './DashNavButton';
import { DashNavTimeControls } from './DashNavTimeControls';
import { ModalsController } from '@grafana/ui';
import { ModalsController, Icon } from '@grafana/ui';
import { BackButton } from 'app/core/components/BackButton/BackButton';
// State
import { updateLocation } from 'app/core/actions';
@ -34,7 +35,7 @@ export interface StateProps {
type Props = StateProps & OwnProps;
export class DashNav extends PureComponent<Props> {
class DashNav extends PureComponent<Props> {
playlistSrv: PlaylistSrv;
constructor(props: Props) {
@ -95,6 +96,10 @@ export class DashNav extends PureComponent<Props> {
renderDashboardTitleSearchButton() {
const { dashboard, isFullscreen } = this.props;
/* Hard-coded value so we don't have to wrap whole component in withTheme because of 1 variable */
const iconClassName = css`
margin-right: 8px;
`;
const folderTitle = dashboard.meta.folderTitle;
const haveFolder = dashboard.meta.folderId > 0;
@ -103,17 +108,17 @@ export class DashNav extends PureComponent<Props> {
<>
<div>
<div className="navbar-page-btn">
{!isFullscreen && <i className="gicon gicon-dashboard" />}
{!isFullscreen && <Icon name="apps" size="xl" className={iconClassName} />}
{haveFolder && (
<>
<a className="navbar-page-btn__folder" onClick={this.onFolderNameClick}>
{folderTitle}
</a>
<i className="fa fa-chevron-right navbar-page-btn__folder-icon" />
<Icon name="angle-right" className={iconClassName} />
</>
)}
<a onClick={this.onDahboardNameClick}>
{dashboard.title} <i className="fa fa-caret-down navbar-page-btn__search" />
{dashboard.title} <Icon name="angle-down" className={iconClassName} />
</a>
</div>
</div>
@ -146,39 +151,33 @@ export class DashNav extends PureComponent<Props> {
<DashNavButton
tooltip="Go to previous dashboard"
classSuffix="tight"
icon="fa fa-step-backward"
icon="step-backward"
onClick={this.onPlaylistPrev}
/>
<DashNavButton
tooltip="Stop playlist"
classSuffix="tight"
icon="fa fa-stop"
icon="square-shape"
onClick={this.onPlaylistStop}
/>
<DashNavButton
tooltip="Go to next dashboard"
classSuffix="tight"
icon="fa fa-forward"
icon="forward"
onClick={this.onPlaylistNext}
/>
</div>
)}
<div className="navbar-buttons navbar-buttons--actions">
{canSave && (
<DashNavButton
tooltip="Add panel"
classSuffix="add-panel"
icon="gicon gicon-add-panel"
onClick={onAddPanel}
/>
)}
{canSave && <DashNavButton classSuffix="save" tooltip="Add panel" icon="panel-add" onClick={onAddPanel} />}
{canStar && (
<DashNavButton
tooltip="Mark as favorite"
classSuffix="star"
icon={`${isStarred ? 'fa fa-star' : 'fa fa-star-o'}`}
icon={isStarred ? 'favorite' : 'star'}
iconType={isStarred ? 'mono' : 'default'}
onClick={this.onStarDashboard}
/>
)}
@ -189,7 +188,7 @@ export class DashNav extends PureComponent<Props> {
<DashNavButton
tooltip="Share dashboard"
classSuffix="share"
icon="fa fa-share-square-o"
icon="share-alt"
onClick={() => {
showModal(ShareModal, {
dashboard,
@ -207,7 +206,7 @@ export class DashNav extends PureComponent<Props> {
<DashNavButton
tooltip="Save dashboard"
classSuffix="save"
icon="fa fa-save"
icon="save"
onClick={() => {
showModal(SaveDashboardModalProxy, {
dashboard,
@ -223,7 +222,7 @@ export class DashNav extends PureComponent<Props> {
<DashNavButton
tooltip="Open original dashboard"
classSuffix="snapshot-origin"
icon="gicon gicon-link"
icon="link"
href={sanitizeUrl(snapshotUrl)}
/>
)}
@ -232,19 +231,14 @@ export class DashNav extends PureComponent<Props> {
<DashNavButton
tooltip="Dashboard settings"
classSuffix="settings"
icon="gicon gicon-cog"
icon="cog"
onClick={this.onOpenSettings}
/>
)}
</div>
<div className="navbar-buttons navbar-buttons--tv">
<DashNavButton
tooltip="Cycle view mode"
classSuffix="tv"
icon="fa fa-desktop"
onClick={this.onToggleTVMode}
/>
<DashNavButton tooltip="Cycle view mode" classSuffix="tv" icon="monitor" onClick={this.onToggleTVMode} />
</div>
{!dashboard.timepicker.hidden && (

View File

@ -1,19 +1,28 @@
// Libraries
import React, { FunctionComponent } from 'react';
// Components
import { Tooltip } from '@grafana/ui';
import { Tooltip, Icon, IconName, IconType } from '@grafana/ui';
import { e2e } from '@grafana/e2e';
interface Props {
icon?: string;
icon?: IconName;
tooltip: string;
classSuffix?: string;
onClick?: () => void;
href?: string;
children?: React.ReactNode;
iconType?: IconType;
}
export const DashNavButton: FunctionComponent<Props> = ({ icon, tooltip, classSuffix, onClick, href, children }) => {
export const DashNavButton: FunctionComponent<Props> = ({
icon,
iconType,
tooltip,
classSuffix,
onClick,
href,
children,
}) => {
if (onClick) {
return (
<Tooltip content={tooltip} placement="bottom">
@ -22,7 +31,7 @@ export const DashNavButton: FunctionComponent<Props> = ({ icon, tooltip, classSu
onClick={onClick}
aria-label={e2e.pages.Dashboard.Toolbar.selectors.toolbarItems(tooltip)}
>
{icon && <i className={icon} />}
{icon && <Icon name={icon} type={iconType} />}
{children}
</button>
</Tooltip>
@ -32,7 +41,7 @@ export const DashNavButton: FunctionComponent<Props> = ({ icon, tooltip, classSu
return (
<Tooltip content={tooltip} placement="bottom">
<a className={`btn navbar-button navbar-button--${classSuffix}`} href={href}>
{icon && <i className={icon} />}
{icon && <Icon name={icon} type={iconType} />}
{children}
</a>
</Tooltip>

View File

@ -33,7 +33,7 @@ export const InspectHeader: FC<Props> = ({
<div className={styles.header}>
<div className={styles.actions}>
<div className={styles.iconWrapper} onClick={onToggleExpand}>
<Icon name={isExpanded ? 'chevron-right' : 'chevron-left'} className={styles.icon} />
<Icon name={isExpanded ? 'angle-right' : 'angle-left'} className={styles.icon} />
</div>
<div className={styles.iconWrapper} onClick={onClose}>
<Icon name="times" className={styles.icon} />

View File

@ -17,7 +17,7 @@ export const OptionsGroup: FC<Props> = ({ title, children }) => {
<div className={styles.header} onClick={() => toggleExpand(!isExpanded)}>
{title}
<div className={cx(styles.toggle, 'editor-options-group-toggle')}>
<Icon name={isExpanded ? 'chevron-down' : 'chevron-left'} />
<Icon name={isExpanded ? 'angle-down' : 'angle-left'} />
</div>
</div>
{isExpanded && <div className={styles.body}>{children}</div>}

View File

@ -101,7 +101,7 @@ export class ShareModal extends React.Component<Props, State> {
return (
<ModalTabsHeader
title={title}
icon="share-square-o"
icon="share-alt"
tabs={tabs}
activeTab={activeTab}
onChangeTab={this.onSelectTab}

View File

@ -3,7 +3,7 @@ import classNames from 'classnames';
import { isEqual } from 'lodash';
import { DataLink, ScopedVars, PanelMenuItem, PanelData, LoadingState, QueryResultMetaNotice } from '@grafana/data';
import { AngularComponent } from '@grafana/runtime';
import { ClickOutsideWrapper, Tooltip } from '@grafana/ui';
import { ClickOutsideWrapper, Tooltip, Icon } from '@grafana/ui';
import { e2e } from '@grafana/e2e';
import PanelHeaderCorner from './PanelHeaderCorner';
@ -109,11 +109,11 @@ export class PanelHeader extends Component<Props, State> {
<Tooltip content={notice.text} key={notice.severity}>
{notice.inspect ? (
<div className="panel-info-notice" onClick={e => this.openInspect(e, notice.inspect!)}>
<span className="fa fa-info-circle" style={{ marginRight: '8px', cursor: 'pointer' }} />
<Icon name="info-circle" style={{ marginRight: '8px' }} />
</div>
) : (
<a className="panel-info-notice" href={notice.url} target="_blank">
<span className="fa fa-info-circle" style={{ marginRight: '8px', cursor: 'pointer' }} />
<Icon name="info-circle" style={{ marginRight: '8px' }} />
</a>
)}
</Tooltip>
@ -163,7 +163,8 @@ export class PanelHeader extends Component<Props, State> {
{Object.values(notices).map(this.renderNotice)}
<span className="icon-gf panel-alert-icon" />
<span className="panel-title-text">
{title} <span className="fa fa-caret-down panel-menu-toggle" />
{title}
<Icon name="angle-down" className="panel-menu-toggle" />
</span>
{this.state.panelMenuOpen && (
<ClickOutsideWrapper onClick={this.closeMenu}>
@ -172,7 +173,7 @@ export class PanelHeader extends Component<Props, State> {
)}
{data.request && data.request.timeInfo && (
<span className="panel-time-info">
<i className="fa fa-clock-o" /> {data.request.timeInfo}
<Icon name="clock-nine" /> {data.request.timeInfo}
</span>
)}
</div>

View File

@ -1,5 +1,7 @@
import React, { FC } from 'react';
import { css } from 'emotion';
import { PanelMenuItem } from '@grafana/data';
import { Icon, IconName, useTheme } from '@grafana/ui';
import { e2e } from '@grafana/e2e';
interface Props {
@ -9,19 +11,37 @@ interface Props {
export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => {
const isSubMenu = props.type === 'submenu';
const isDivider = props.type === 'divider';
const theme = useTheme();
const menuIconClassName = css`
margin-right: ${theme.spacing.sm};
a::after {
display: none;
}
`;
const shortcutIconClassName = css`
position: absolute;
margin-top: 3px;
right: ${theme.spacing.xs};
color: ${theme.colors.textWeak};
`;
return isDivider ? (
<li className="divider" />
) : (
<li className={isSubMenu ? 'dropdown-submenu' : undefined}>
<a onClick={props.onClick} href={props.href}>
{props.iconClassName && <i className={props.iconClassName} />}
{props.iconClassName && <Icon name={props.iconClassName as IconName} className={menuIconClassName} />}
<span
className="dropdown-item-text"
aria-label={e2e.pages.Dashboard.Panels.Panel.selectors.headerItems(props.text)}
>
{props.text}
{isSubMenu && <Icon name="angle-right" className={shortcutIconClassName} />}
</span>
{props.shortcut && <span className="dropdown-menu-item-shortcut">{props.shortcut}</span>}
{props.shortcut && (
<span className="dropdown-menu-item-shortcut">
<Icon name="keyboard" className={menuIconClassName} /> {props.shortcut}
</span>
)}
</a>
{props.children}
</li>

View File

@ -10,31 +10,31 @@ describe('getPanelMenu', () => {
expect(menuItems).toMatchInlineSnapshot(`
Array [
Object {
"iconClassName": "gicon gicon-viewer",
"iconClassName": "eye",
"onClick": [Function],
"shortcut": "v",
"text": "View",
},
Object {
"iconClassName": "gicon gicon-editor",
"iconClassName": "edit",
"onClick": [Function],
"shortcut": "e",
"text": "Edit",
},
Object {
"iconClassName": "fa fa-fw fa-share",
"iconClassName": "share-alt",
"onClick": [Function],
"shortcut": "p s",
"text": "Share",
},
Object {
"iconClassName": "fa fa-fw fa-info-circle",
"iconClassName": "info-circle",
"onClick": [Function],
"shortcut": "p i",
"text": "Inspect",
},
Object {
"iconClassName": "fa fa-fw fa-cube",
"iconClassName": "cube",
"onClick": [Function],
"subMenu": Array [
Object {
@ -58,7 +58,7 @@ describe('getPanelMenu', () => {
"type": "divider",
},
Object {
"iconClassName": "fa fa-fw fa-trash",
"iconClassName": "trash-alt",
"onClick": [Function],
"shortcut": "p r",
"text": "Remove",

View File

@ -106,7 +106,7 @@ export function getPanelMenu(
menu.push({
text: 'View',
iconClassName: 'gicon gicon-viewer',
iconClassName: 'eye',
onClick: onViewPanel,
shortcut: 'v',
});
@ -114,7 +114,7 @@ export function getPanelMenu(
if (dashboard.canEditPanel(panel)) {
menu.push({
text: 'Edit',
iconClassName: 'gicon gicon-editor',
iconClassName: 'edit',
onClick: onEditPanel,
shortcut: 'e',
});
@ -122,7 +122,7 @@ export function getPanelMenu(
menu.push({
text: 'Share',
iconClassName: 'fa fa-fw fa-share',
iconClassName: 'share-alt',
onClick: onSharePanel,
shortcut: 'p s',
});
@ -130,7 +130,7 @@ export function getPanelMenu(
if (contextSrv.hasAccessToExplore() && !(panel.plugin && panel.plugin.meta.skipDataQuery)) {
menu.push({
text: 'Explore',
iconClassName: 'gicon gicon-explore',
iconClassName: 'compass',
shortcut: 'x',
onClick: onNavigateToExplore,
});
@ -138,7 +138,7 @@ export function getPanelMenu(
menu.push({
text: 'Inspect',
iconClassName: 'fa fa-fw fa-info-circle',
iconClassName: 'info-circle',
onClick: onInspectPanel,
shortcut: 'p i',
});
@ -146,7 +146,7 @@ export function getPanelMenu(
if (config.featureToggles.newEdit) {
menu.push({
text: 'New edit',
iconClassName: 'gicon gicon-editor',
iconClassName: 'edit',
onClick: onNewEditPanel,
shortcut: 'p i',
});
@ -198,7 +198,7 @@ export function getPanelMenu(
menu.push({
type: 'submenu',
text: 'More...',
iconClassName: 'fa fa-fw fa-cube',
iconClassName: 'cube',
subMenu: subMenu,
onClick: onMore,
});
@ -208,7 +208,7 @@ export function getPanelMenu(
menu.push({
text: 'Remove',
iconClassName: 'fa fa-fw fa-trash',
iconClassName: 'trash-alt',
onClick: onRemovePanel,
shortcut: 'p r',
});

View File

@ -54,7 +54,7 @@ export const SearchItem: FC<Props> = ({ item, editable, onToggleSelection, onTag
>
<SearchCheckbox editable={editable} checked={item.checked} onClick={toggleItem} />
<a href={item.url} className={styles.link}>
<Icon className={styles.icon} name="th-large" />
<Icon className={styles.icon} name="apps" />
<div className={styles.body} onClick={onItemClick}>
<span>{item.title}</span>
<span className={styles.folderTitle}>{item.folderTitle}</span>

View File

@ -1,8 +1,7 @@
import React, { FC } from 'react';
import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { Icon, stylesFactory, useTheme } from '@grafana/ui';
import { IconType } from '@grafana/ui/src/components/Icon/types';
import { Icon, IconName, stylesFactory, useTheme } from '@grafana/ui';
import { DashboardSection, ItemClickWithEvent } from '../types';
import { SearchItem } from './SearchItem';
import { SearchCheckbox } from './SearchCheckbox';
@ -99,7 +98,7 @@ const SectionHeader: FC<SectionHeaderProps> = ({ section, onSectionClick, onTogg
checked={section.checked}
onClick={(e: MouseEvent) => onToggleSelection(section, e)}
/>
<Icon className={styles.icon} name={section.icon as IconType} />
<Icon className={styles.icon} name={section.icon as IconName} />
<span className={styles.text}>{section.title}</span>
{section.url && (

View File

@ -17,7 +17,7 @@ export function PromExploreExtraField(props: PromExploreExtraFieldProps) {
const { label, onChangeFunc, onKeyDownFunc, value, hasTooltip, tooltipContent } = props;
return (
<div className="gf-form-inline explore-input--ml">
<div className="gf-form-inline explore-input--ml" aria-label="Prometheus extra field">
<div className="gf-form">
<FormLabel width={5} tooltip={hasTooltip ? tooltipContent : null}>
{label}

View File

@ -1,8 +1,7 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import { shallow } from 'enzyme';
import { act } from 'react-dom/test-utils';
import PromExploreQueryEditor from './PromExploreQueryEditor';
import { PromExploreExtraField } from './PromExploreExtraField';
import { PrometheusDatasource } from '../datasource';
import { PromQuery } from '../types';
import { LoadingState, PanelData, toUtc } from '@grafana/data';
@ -81,8 +80,8 @@ describe('PromExploreQueryEditor', () => {
it('should render PromQueryField with ExtraFieldElement', async () => {
// @ts-ignore strict null errpr TS2345: Argument of type '() => Promise<void>' is not assignable to parameter of type '() => void | undefined'.
await act(async () => {
const wrapper = setup(mount);
expect(wrapper.find(PromExploreExtraField).length).toBe(1);
const wrapper = setup(shallow);
expect(wrapper.html()).toContain('aria-label="Prometheus extra field"');
});
});
});

View File

@ -2,6 +2,7 @@
exports[`PrometheusExploreExtraField should render component 1`] = `
<div
aria-label="Prometheus extra field"
className="gf-form-inline explore-input--ml"
>
<div

View File

@ -1,10 +1,10 @@
// Libraries
import React, { PureComponent, FC } from 'react';
import { css, cx } from 'emotion';
import { css } from 'emotion';
// Utils & Services
import { PanelPlugin } from '@grafana/data';
import { stylesFactory, styleMixins } from '@grafana/ui';
import { stylesFactory, styleMixins, Icon, IconName } from '@grafana/ui';
import config from 'app/core/config';
// Types
@ -21,25 +21,25 @@ export class GrafanaLinksPanel extends PureComponent<Props> {
<div className={styles.list}>
<HomeLink
title="Documentation"
icon="fa fa-book"
icon="book"
url="https://grafana.com/docs/grafana/latest?utm_source=grafana_homelinks"
target="_blank"
/>
<HomeLink
title="Getting started"
icon="fa fa-bolt"
icon="bolt"
url="https://grafana.com/docs/grafana/latest/guides/getting_started/?utm_source=grafana_homelinks"
target="_blank"
/>
<HomeLink
title="Community forum"
icon="fa fa-comments"
icon="comments-alt"
url="https://community.grafana.com?utm_source=grafana_homelinks"
target="_blank"
/>
<HomeLink
title="Report a bug"
icon="fa fa-bug"
icon="bug"
url="https://github.com/grafana/grafana/issues/new?template=1-bug_report.md"
target="_blank"
/>
@ -54,7 +54,7 @@ interface HomeLinkProps {
title: string;
url: string;
target?: string;
icon: string;
icon: IconName;
}
export const HomeLink: FC<HomeLinkProps> = ({ title, url, target, icon }) => {
@ -62,7 +62,7 @@ export const HomeLink: FC<HomeLinkProps> = ({ title, url, target, icon }) => {
return (
<a className={styles.item} href={url} target={target}>
<i className={cx(icon, styles.icon)} />
<Icon name={icon} className={styles.icon} />
{title}
</a>
);
@ -89,7 +89,7 @@ export const getStyles = stylesFactory(() => {
padding: ${theme.spacing.md};
`,
icon: css`
padding-right: ${theme.spacing.sm};
margin-right: ${theme.spacing.sm};
`,
footer: css`
${styleMixins.listItem(theme)}

View File

@ -260,19 +260,17 @@
margin-bottom: -2px;
@include border-radius(5px 5px 5px 0);
}
// Caret to indicate there is a submenu
.dropdown-submenu > a::after {
position: absolute;
top: 35%;
right: $space-sm;
background-color: transparent;
color: $text-color-weak;
font: normal normal normal $font-size-sm/1 FontAwesome;
content: '\f0da';
pointer-events: none;
font-size: 11px;
}
// .dropdown-submenu > a::after {
// position: absolute;
// top: 35%;
// right: $space-sm;
// background-color: transparent;
// color: $text-color-weak;
// font: normal normal normal $font-size-sm/1 FontAwesome;
// content: '\f0da';
// pointer-events: none;
// font-size: 11px;
// }
.dropdown-submenu:hover > a::after {
border-left-color: $dropdownLinkColorHover;
}
@ -310,14 +308,6 @@
margin-left: $spacer;
color: $text-muted;
min-width: 47px;
&::before {
font-family: FontAwesome;
width: 28px;
display: inline-block;
text-align: center;
content: '\f11c';
}
}
.dropdown-menu.dropdown-menu--new {

View File

@ -61,13 +61,9 @@ $panel-header-no-title-zindex: 1;
.panel-menu-toggle {
color: $text-color-weak;
cursor: pointer;
padding: 3px 5px;
margin: 2px 0 0 2px;
visibility: hidden;
opacity: 0;
width: 16px;
height: 16px;
left: 1px;
top: 2px;
&:hover {
color: $link-hover-color;

View File

@ -116,26 +116,9 @@ $mobile-menu-breakpoint: md;
width: 35px;
height: 35px;
display: inline-block;
.fa,
.icon-gf,
.gicon {
color: $side-menu-link-color;
position: relative;
opacity: 0.7;
font-size: 130%;
height: 22px;
width: 22px;
}
.fa {
top: 2px;
position: relative;
}
.icon-gf {
top: 5px;
}
color: $side-menu-link-color;
position: relative;
opacity: 0.7;
img {
position: relative;

View File

@ -23,6 +23,15 @@ angular.module('grafana.routes', ['ngRoute']);
jest.mock('app/core/core', () => ({}));
jest.mock('app/features/plugins/plugin_loader', () => ({}));
/* Temporary solution as Jest can't parse Unicons imports.
* Therefore we are mocking in for all tests. Needs to be fixed before merging to master.
*/
jest.mock('@grafana/ui/src/components/Icon/Icon', () => {
return {
Icon: () => null as any,
};
});
configure({ adapter: new Adapter() });
const localStorageMock = (() => {

View File

@ -7,7 +7,15 @@ function shouldExclude(filename) {
return false;
}
const packagesToProcessbyBabel = ['debug', 'lru-cache', 'yallist', 'apache-arrow', 'react-hook-form', 'rc-trigger'];
const packagesToProcessbyBabel = [
'debug',
'lru-cache',
'yallist',
'apache-arrow',
'react-hook-form',
'rc-trigger',
'@iconscout/react-unicons',
];
for (const package of packagesToProcessbyBabel) {
if (filename.indexOf(`node_modules/${package}`) > 0) {
return false;