diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx new file mode 100644 index 000000000000..34c10011318d --- /dev/null +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx @@ -0,0 +1,51 @@ +import { DataFrame, DataLink, VariableSuggestion } from '@grafana/data'; +import React, { FC, useState } from 'react'; +import { DataLinkEditor } from '../DataLinkEditor'; +import { HorizontalGroup } from '../../Layout/Layout'; +import Forms from '../../Forms'; + +interface DataLinkEditorModalContentProps { + link: DataLink; + index: number; + data: DataFrame[]; + suggestions: VariableSuggestion[]; + onChange: (index: number, ink: DataLink) => void; + onClose: () => void; +} + +export const DataLinkEditorModalContent: FC = ({ + link, + index, + suggestions, + onChange, + onClose, +}) => { + const [dirtyLink, setDirtyLink] = useState(link); + return ( + <> + { + setDirtyLink(link); + }} + onRemove={() => {}} + /> + + { + onChange(index, dirtyLink); + onClose(); + }} + > + Save + + onClose()}> + Cancel + + + + ); +}; diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx new file mode 100644 index 000000000000..8353994897c3 --- /dev/null +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx @@ -0,0 +1,134 @@ +import { DataFrame, DataLink, GrafanaTheme, VariableSuggestion } from '@grafana/data'; +import React, { useState } from 'react'; +import { css } from 'emotion'; +import Forms from '../../Forms'; +import cloneDeep from 'lodash/cloneDeep'; +import { Modal } from '../../Modal/Modal'; +import { FullWidthButtonContainer } from '../../Button/FullWidthButtonContainer'; +import { selectThemeVariant, stylesFactory, useTheme } from '../../../themes'; +import { DataLinksListItem } from './DataLinksListItem'; +import { DataLinkEditorModalContent } from './DataLinkEditorModalContent'; + +interface DataLinksInlineEditorProps { + links: DataLink[]; + onChange: (links: DataLink[]) => void; + suggestions: VariableSuggestion[]; + data: DataFrame[]; +} + +export const DataLinksInlineEditor: React.FC = ({ links, onChange, suggestions, data }) => { + const theme = useTheme(); + const [editIndex, setEditIndex] = useState(); + const isEditing = editIndex !== null && editIndex !== undefined; + const styles = getDataLinksInlineEditorStyles(theme); + + const onDataLinkChange = (index: number, link: DataLink) => { + const update = cloneDeep(links); + update[index] = link; + onChange(update); + }; + + const onDataLinkAdd = () => { + let update = cloneDeep(links); + if (update) { + update.push({ + title: '', + url: '', + }); + } else { + update = [ + { + title: '', + url: '', + }, + ]; + } + setEditIndex(update.length - 1); + onChange(update); + }; + + const onDataLinkRemove = (index: number) => { + const update = cloneDeep(links); + update.splice(index, 1); + onChange(update); + }; + + return ( + <> + {links && ( +
+ {links.map((l, i) => { + return ( + setEditIndex(i)} + onRemove={() => onDataLinkRemove(i)} + data={data} + suggestions={suggestions} + /> + ); + })} +
+ )} + + {isEditing && ( + { + setEditIndex(null); + }} + > + setEditIndex(null)} + suggestions={suggestions} + /> + + )} + + + + Add data link + + + + ); +}; + +const getDataLinksInlineEditorStyles = stylesFactory((theme: GrafanaTheme) => { + const borderColor = selectThemeVariant( + { + light: theme.colors.gray85, + dark: theme.colors.dark9, + }, + theme.type + ); + + const shadow = selectThemeVariant( + { + light: theme.colors.gray85, + dark: theme.colors.black, + }, + theme.type + ); + + return { + wrapper: css` + border: 1px dashed ${borderColor}; + margin-bottom: ${theme.spacing.md}; + transition: box-shadow 0.5s cubic-bezier(0.19, 1, 0.22, 1); + box-shadow: none; + + &:hover { + box-shadow: 0 0 10px ${shadow}; + } + `, + }; +}); diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx new file mode 100644 index 000000000000..c7a8da97a653 --- /dev/null +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx @@ -0,0 +1,98 @@ +import React, { FC } from 'react'; +import { css, cx } from 'emotion'; +import { DataFrame, DataLink, GrafanaTheme, VariableSuggestion } from '@grafana/data'; +import { selectThemeVariant, stylesFactory, useTheme } from '../../../themes'; +import { HorizontalGroup } from '../../Layout/Layout'; +import { Icon } from '../../Icon/Icon'; + +interface DataLinksListItemProps { + index: number; + link: DataLink; + data: DataFrame[]; + onChange: (index: number, link: DataLink) => void; + onEdit: () => void; + onRemove: () => void; + suggestions: VariableSuggestion[]; + isEditing?: boolean; +} + +export const DataLinksListItem: FC = ({ + index, + link, + data, + onChange, + suggestions, + isEditing, + onEdit, + onRemove, +}) => { + const theme = useTheme(); + const styles = getDataLinkListItemStyles(theme); + + const hasTitle = link.title.trim() !== ''; + const hasUrl = link.url.trim() !== ''; + + return ( +
+ +
+
{hasTitle ? link.title : 'No data link provided'}
+
{hasUrl ? link.url : 'No url provided'}
+
+ + +
+ +
+
+ +
+
+
+
+ ); +}; + +const getDataLinkListItemStyles = stylesFactory((theme: GrafanaTheme) => { + const borderColor = selectThemeVariant( + { + light: theme.colors.gray85, + dark: theme.colors.dark9, + }, + theme.type + ); + const bg = selectThemeVariant( + { + light: theme.colors.white, + dark: theme.colors.dark1, + }, + theme.type + ); + + return { + wrapper: css` + border-bottom: 1px dashed ${borderColor}; + padding: ${theme.spacing.sm}; + transition: background 0.5s cubic-bezier(0.19, 1, 0.22, 1); + &:last-child { + border-bottom: 0; + } + &:hover { + background: ${bg}; + } + `, + action: css` + cursor: pointer; + `, + + notConfigured: css` + font-style: italic; + `, + url: css` + font-size: ${theme.typography.size.sm}; + `, + remove: css` + color: ${theme.colors.red88}; + `, + }; +}); diff --git a/packages/grafana-ui/src/components/FieldConfigs/links.tsx b/packages/grafana-ui/src/components/FieldConfigs/links.tsx index 1ac0ec15c439..1c923a82533c 100644 --- a/packages/grafana-ui/src/components/FieldConfigs/links.tsx +++ b/packages/grafana-ui/src/components/FieldConfigs/links.tsx @@ -1,18 +1,6 @@ -import { - FieldOverrideContext, - FieldConfigEditorProps, - DataLink, - FieldOverrideEditorProps, - DataFrame, -} from '@grafana/data'; -import React, { FC, useState } from 'react'; -import { css, cx } from 'emotion'; -import Forms from '../Forms'; -import { Modal } from '../Modal/Modal'; -import { DataLinkEditor } from '../DataLinks/DataLinkEditor'; -import cloneDeep from 'lodash/cloneDeep'; -import { VariableSuggestion } from '@grafana/data'; -import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer'; +import { FieldOverrideContext, FieldConfigEditorProps, DataLink, FieldOverrideEditorProps } from '@grafana/data'; +import React from 'react'; +import { DataLinksInlineEditor } from '../DataLinks/DataLinksInlineEditor/DataLinksInlineEditor'; export interface DataLinksFieldConfigSettings {} @@ -29,45 +17,13 @@ export const DataLinksValueEditor: React.FC { - const onDataLinkChange = (index: number, link: DataLink) => { - const links = cloneDeep(value); - links[index] = link; - onChange(links); - }; - - const onDataLinkAdd = () => { - const links = cloneDeep(value) || []; - - links.push({ - title: '', - url: '', - }); - - onChange(links); - }; - return ( - <> - {value && - value.map((l, i) => { - return ( - - ); - })} - - - - Add data link - - - + ); }; @@ -75,156 +31,13 @@ export const DataLinksOverrideEditor: React.FC { - const onDataLinkChange = (index: number, link: DataLink) => { - const links = cloneDeep(value); - links[index] = link; - onChange(links); - }; - - const onDataLinkAdd = () => { - let links = cloneDeep(value); - if (links) { - links.push({ - title: '', - url: '', - }); - } else { - links = [ - { - title: '', - url: '', - }, - ]; - } - onChange(links); - }; - return ( - <> - {value && - value.map((l, i) => { - return ( - - ); - })} - - - Create data link - - - ); -}; - -interface DataLinksListItemProps { - index: number; - link: DataLink; - data: DataFrame[]; - onChange: (index: number, link: DataLink) => void; - suggestions: VariableSuggestion[]; -} - -const DataLinksListItem: FC = ({ index, link, data, onChange, suggestions }) => { - const [isEditing, setIsEditing] = useState(false); - - const style = () => { - return { - wrapper: css` - display: flex; - justify-content: space-between; - `, - action: css` - flex-shrink: 0; - flex-grow: 0; - `, - noTitle: css` - font-style: italic; - `, - }; - }; - - const styles = style(); - const hasTitle = link.title.trim() !== ''; - - return ( - <> -
-
{hasTitle ? link.title : 'Edit data link'}
-
- setIsEditing(true)} /> -
-
- - {isEditing && ( - { - setIsEditing(false); - }} - > - setIsEditing(false)} - suggestions={suggestions} - /> - - )} - - ); -}; - -interface DataLinkEditorModalContentProps { - link: DataLink; - index: number; - data: DataFrame[]; - suggestions: VariableSuggestion[]; - onChange: (index: number, ink: DataLink) => void; - onClose: () => void; -} - -const DataLinkEditorModalContent: FC = ({ - link, - index, - data, - suggestions, - onChange, - onClose, -}) => { - const [dirtyLink, setDirtyLink] = useState(link); - - return ( - <> - { - setDirtyLink(link); - }} - onRemove={() => {}} - /> - { - onChange(index, dirtyLink); - onClose(); - }} - > - Save - - onClose()}>Cancel - + ); }; diff --git a/packages/grafana-ui/src/components/Layout/Layout.tsx b/packages/grafana-ui/src/components/Layout/Layout.tsx index c4d7b514e815..0a81cb311bcf 100644 --- a/packages/grafana-ui/src/components/Layout/Layout.tsx +++ b/packages/grafana-ui/src/components/Layout/Layout.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { css } from 'emotion'; -import { stylesFactory, useTheme } from '../../themes'; import { GrafanaTheme } from '@grafana/data'; +import { stylesFactory, useTheme } from '../../themes'; enum Orientation { Horizontal,