diff --git a/packages/grafana-ui/src/components/TagsInput/TagItem.tsx b/packages/grafana-ui/src/components/TagsInput/TagItem.tsx index 6ef390c1b04..3df2d17e1ac 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagItem.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagItem.tsx @@ -1,9 +1,9 @@ import { css } from '@emotion/css'; -import React, { FC } from 'react'; +import React, { useMemo } from 'react'; -import { GrafanaTheme } from '@grafana/data'; +import { GrafanaTheme2 } from '@grafana/data'; -import { stylesFactory, useTheme } from '../../themes'; +import { useStyles2 } from '../../themes'; import { getTagColorsFromName } from '../../utils'; import { IconButton } from '../IconButton/IconButton'; @@ -13,61 +13,60 @@ interface Props { onRemove: (tag: string) => void; } -const getStyles = stylesFactory(({ theme, name }: { theme: GrafanaTheme; name: string }) => { - const { color, borderColor } = getTagColorsFromName(name); - const height = theme.spacing.formInputHeight - 8; - - return { - itemStyle: css` - display: flex; - align-items: center; - height: ${height}px; - line-height: ${height - 2}px; - background-color: ${color}; - color: ${theme.palette.white}; - border: 1px solid ${borderColor}; - border-radius: 3px; - padding: 0 ${theme.spacing.xs}; - margin-right: 3px; - white-space: nowrap; - text-shadow: none; - font-weight: 500; - font-size: ${theme.typography.size.sm}; - `, - - nameStyle: css` - margin-right: 3px; - `, - - buttonStyles: css` - margin: 0; - &:hover::before { - display: none; - } - `, - }; -}); - /** * @internal * Only used internally by TagsInput * */ -export const TagItem: FC = ({ name, disabled, onRemove }) => { - const theme = useTheme(); - const styles = getStyles({ theme, name }); +export const TagItem = ({ name, disabled, onRemove }: Props) => { + const { color, borderColor } = useMemo(() => getTagColorsFromName(name), [name]); + const styles = useStyles2(getStyles); return ( -
+
  • {name} onRemove(name)} type="button" className={styles.buttonStyles} /> -
  • + ); }; + +const getStyles = (theme: GrafanaTheme2) => { + const height = theme.spacing.gridSize * 3; + + return { + itemStyle: css({ + display: 'flex', + gap: '3px', + alignItems: 'center', + height: `${height}px`, + lineHeight: `${height - 2}px`, + color: '#fff', + borderWidth: '1px', + borderStyle: 'solid', + borderRadius: '3px', + padding: `0 ${theme.spacing(0.5)}`, + whiteSpace: 'nowrap', + textShadow: 'none', + fontWeight: 500, + fontSize: theme.typography.size.sm, + }), + nameStyle: css({ + maxWidth: '25ch', + textOverflow: 'ellipsis', + overflow: 'hidden', + }), + buttonStyles: css({ + margin: 0, + '&:hover::before': { + display: 'none', + }, + }), + }; +}; diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx index 4c0db8c1014..950f111a24f 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx @@ -8,7 +8,7 @@ describe('TagsInput', () => { const onChange = jest.fn(); render(); - fireEvent.click(await screen.findByRole('button', { name: /remove one/i })); + fireEvent.click(await screen.findByRole('button', { name: /Remove \"One\"/i })); expect(onChange).toHaveBeenCalledWith(['Two']); }); @@ -17,7 +17,7 @@ describe('TagsInput', () => { const onChange = jest.fn(); render(); - fireEvent.click(await screen.findByRole('button', { name: /remove one/i })); + fireEvent.click(await screen.findByRole('button', { name: /Remove \"One\"/i })); expect(onChange).not.toHaveBeenCalled(); }); diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx index 017b1bc8364..d8397366b06 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx @@ -1,9 +1,9 @@ import { css, cx } from '@emotion/css'; -import React, { ChangeEvent, KeyboardEvent, FC, useState } from 'react'; +import React, { useCallback, useState } from 'react'; -import { GrafanaTheme } from '@grafana/data'; +import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles, useTheme2 } from '../../themes/ThemeContext'; +import { useStyles2, useTheme2 } from '../../themes/ThemeContext'; import { Button } from '../Button'; import { Input } from '../Input/Input'; @@ -25,7 +25,7 @@ export interface Props { invalid?: boolean; } -export const TagsInput: FC = ({ +export const TagsInput = ({ placeholder = 'New tag (enter key to add)', tags = [], onChange, @@ -35,25 +35,25 @@ export const TagsInput: FC = ({ addOnBlur, invalid, id, -}) => { - const [newTagName, setNewName] = useState(''); - const styles = useStyles(getStyles); +}: Props) => { + const [newTagName, setNewTagName] = useState(''); + const styles = useStyles2(getStyles); const theme = useTheme2(); - const onNameChange = (event: ChangeEvent) => { - setNewName(event.target.value); - }; + const onNameChange = useCallback((event: React.ChangeEvent) => { + setNewTagName(event.target.value); + }, []); const onRemove = (tagToRemove: string) => { onChange(tags.filter((x) => x !== tagToRemove)); }; - const onAdd = (event?: React.MouseEvent) => { + const onAdd = (event?: React.MouseEvent | React.KeyboardEvent) => { event?.preventDefault(); if (!tags.includes(newTagName)) { onChange(tags.concat(newTagName)); } - setNewName(''); + setNewTagName(''); }; const onBlur = () => { @@ -62,65 +62,61 @@ export const TagsInput: FC = ({ } }; - const onKeyboardAdd = (event: KeyboardEvent) => { - event.preventDefault(); + const onKeyboardAdd = (event: React.KeyboardEvent) => { if (event.key === 'Enter' && newTagName !== '') { - onChange(tags.concat(newTagName)); - setNewName(''); + onAdd(event); } }; return (
    -
    - {tags?.map((tag: string, index: number) => { - return ; - })} -
    -
    - { - // onKeyDown is triggered before onKeyUp, triggering submit behaviour on Enter press if this component - // is used inside forms. Moving onKeyboardAdd callback here doesn't work since text input is not captured in onKeyDown - if (e.key === 'Enter') { - e.preventDefault(); - } - }} - onBlur={onBlur} - invalid={invalid} - suffix={ - newTagName.length > 0 && ( - - ) - } - /> -
    + + Add + + } + /> + {tags?.length > 0 && ( +
      + {tags.map((tag) => ( + + ))} +
    + )}
    ); }; -const getStyles = (theme: GrafanaTheme) => ({ +const getStyles = (theme: GrafanaTheme2) => ({ wrapper: css` - min-height: ${theme.spacing.formInputHeight}px; - align-items: center; + min-height: ${theme.spacing(4)}; display: flex; + flex-direction: column; + gap: ${theme.spacing(1)}; flex-wrap: wrap; `, tags: css` display: flex; justify-content: flex-start; flex-wrap: wrap; - margin-right: ${theme.spacing.xs}; + gap: ${theme.spacing(0.5)}; `, addButtonStyle: css` - margin: 0 -${theme.spacing.sm}; + margin: 0 -${theme.spacing(1)}; `, }); diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx index f67eac1933b..da28d446b72 100644 --- a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx @@ -101,7 +101,7 @@ export function GeneralSettingsUnconnected({ - + = ({ editLinkIdx, {linkSettings.type === 'dashboards' && ( <> - + )} diff --git a/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx b/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx index 28ba6d834ba..e2ab36f8cec 100644 --- a/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx @@ -49,7 +49,7 @@ export const AnnotationEditor = (props: QueryEditorProps Graphite events tags - + );