From 4dfce12a811ffdd99005d795fb00fcb3f8c11403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 16 Feb 2021 09:39:11 +0100 Subject: [PATCH] TagsInput: Design update and component refactor (#31163) * TagsInput: Design update and component refactor * Update packages/grafana-ui/src/components/TagsInput/TagsInput.tsx Co-authored-by: Alex Khomenko * Update packages/grafana-ui/src/components/TagsInput/TagsInput.tsx Co-authored-by: Alex Khomenko * Update packages/grafana-ui/src/components/TagsInput/TagsInput.tsx Co-authored-by: Alex Khomenko * Update packages/grafana-ui/src/components/TagsInput/TagsInput.tsx Co-authored-by: Alex Khomenko * Updated Co-authored-by: Alex Khomenko --- .../src/components/TagsInput/TagItem.tsx | 15 +- .../components/TagsInput/TagsInput.story.tsx | 26 +-- .../src/components/TagsInput/TagsInput.tsx | 167 +++++++----------- .../DashboardSettings/GeneralSettings.tsx | 2 +- 4 files changed, 91 insertions(+), 119 deletions(-) diff --git a/packages/grafana-ui/src/components/TagsInput/TagItem.tsx b/packages/grafana-ui/src/components/TagsInput/TagItem.tsx index 59b8ca36178..3d718c77331 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagItem.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagItem.tsx @@ -13,21 +13,24 @@ interface Props { 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: 3px 6px; - margin: 3px; + padding: 0 ${theme.spacing.xs}; + margin-right: 3px; white-space: nowrap; text-shadow: none; font-weight: 500; font-size: ${theme.typography.size.sm}; - display: flex; - align-items: center; `, nameStyle: css` @@ -36,6 +39,10 @@ const getStyles = stylesFactory(({ theme, name }: { theme: GrafanaTheme; name: s }; }); +/** + * @internal + * Only used internally by TagsInput + * */ export const TagItem: FC = ({ name, onRemove }) => { const theme = useTheme(); const styles = getStyles({ theme, name }); diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx index abbd8a97c97..b425f65fea2 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx @@ -1,11 +1,9 @@ -import React from 'react'; -import { action } from '@storybook/addon-actions'; +import React, { useState } from 'react'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { UseState } from '../../utils/storybook/UseState'; import { TagsInput } from '@grafana/ui'; import mdx from './TagsInput.mdx'; - -const mockTags = ['Some', 'Tags', 'With', 'This', 'New', 'Component']; +import { StoryExample } from '../../utils/storybook/StoryExample'; +import { VerticalGroup } from '../Layout/Layout'; export default { title: 'Forms/TagsInput', @@ -18,16 +16,18 @@ export default { }, }; -export const basic = () => { - return action('tags updated')(tags)} />; +export const Basic = () => { + const [tags, setTags] = useState([]); + return ; }; -export const withMockTags = () => { +export const WithManyTags = () => { + const [tags, setTags] = useState(['dashboard', 'prod', 'server', 'frontend', 'game', 'kubernetes']); return ( - - {(tags) => { - return action('tags updated')(tags)} />; - }} - + + + + + ); }; diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx index 1025cc18fb8..086f94549b6 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx @@ -1,122 +1,87 @@ -import React, { ChangeEvent, KeyboardEvent, PureComponent } from 'react'; -import { css, cx } from 'emotion'; -import { stylesFactory } from '../../themes/stylesFactory'; +import React, { ChangeEvent, KeyboardEvent, FC, useState } from 'react'; +import { css } from 'emotion'; import { Button } from '../Button'; -import { Input } from '../Forms/Legacy/Input/Input'; import { TagItem } from './TagItem'; +import { useStyles } from '../../themes/ThemeContext'; +import { GrafanaTheme } from '@grafana/data'; +import { Input } from '../Input/Input'; -interface Props { +export interface Props { placeholder?: string; tags?: string[]; - onChange: (tags: string[]) => void; } -interface State { - newTag: string; - tags: string[]; -} +export const TagsInput: FC = ({ placeholder = 'New tag (enter key to add)', tags = [], onChange }) => { + const [newTagName, setNewName] = useState(''); + const styles = useStyles(getStyles); -export class TagsInput extends PureComponent { - constructor(props: Props) { - super(props); - - this.state = { - newTag: '', - tags: this.props.tags || [], - }; - } - - onNameChange = (event: ChangeEvent) => { - this.setState({ - newTag: event.target.value, - }); + const onNameChange = (event: ChangeEvent) => { + setNewName(event.target.value); }; - onRemove = (tagToRemove: string) => { - this.setState( - (prevState: State) => ({ - ...prevState, - tags: prevState.tags.filter((tag) => tagToRemove !== tag), - }), - () => this.onChange() - ); + const onRemove = (tagToRemove: string) => { + onChange(tags?.filter((x) => x !== tagToRemove)); }; - // Using React.MouseEvent to avoid tslint error - onAdd = (event: React.MouseEvent) => { + const onAdd = (event: React.MouseEvent) => { event.preventDefault(); - if (this.state.newTag !== '') { - this.setNewTags(); - } + onChange(tags.concat(newTagName)); + setNewName(''); }; - onKeyboardAdd = (event: KeyboardEvent) => { + const onKeyboardAdd = (event: KeyboardEvent) => { event.preventDefault(); - if (event.key === 'Enter' && this.state.newTag !== '') { - this.setNewTags(); + if (event.key === 'Enter' && newTagName !== '') { + onChange(tags.concat(newTagName)); + setNewName(''); } }; - setNewTags = () => { - // We don't want to duplicate tags, clearing the input if - // the user is trying to add the same tag. - if (!this.state.tags.includes(this.state.newTag)) { - this.setState( - (prevState: State) => ({ - ...prevState, - tags: [...prevState.tags, prevState.newTag], - newTag: '', - }), - () => this.onChange() - ); - } else { - this.setState({ newTag: '' }); - } - }; - - onChange = () => { - this.props.onChange(this.state.tags); - }; - - render() { - const { placeholder = 'Add name' } = this.props; - const { tags, newTag } = this.state; - - const getStyles = stylesFactory(() => ({ - tagsCloudStyle: css` - display: flex; - justify-content: flex-start; - flex-wrap: wrap; - `, - - addButtonStyle: css` - margin-left: 8px; - `, - })); - - return ( -
-
- - -
-
- {tags && - tags.map((tag: string, index: number) => { - return ; - })} -
+ return ( +
+
+ {tags?.map((tag: string, index: number) => { + return ; + })}
- ); - } -} +
+ + Add + + } + /> +
+
+ ); +}; + +const getStyles = (theme: GrafanaTheme) => ({ + wrapper: css` + height: ${theme.spacing.formInputHeight}px; + align-items: center; + display: flex; + flex-wrap: wrap; + `, + tags: css` + display: flex; + justify-content: flex-start; + flex-wrap: wrap; + margin-right: ${theme.spacing.xs}; + `, + addButtonStyle: css` + margin: 0 -${theme.spacing.sm}; + `, +}); diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx index 1a408e50943..edfa4f71bd0 100644 --- a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx @@ -79,7 +79,7 @@ export const GeneralSettings: React.FC = ({ dashboard }) => { - +