mirror of https://github.com/grafana/grafana
Granfana ui/tag components (#22964)
* Add Tag component * Add Tag story * Add TagList * Group Tab and TabList * Fix typechecks * Remove Meta * Use forwardRef for the Tag * Add actions instead of console.log * Add previewspull/23027/head
parent
289a5fb862
commit
014e7d9261
@ -0,0 +1,23 @@ |
||||
import { Story, Preview, Props } from '@storybook/addon-docs/blocks'; |
||||
import { Tag } from './Tag'; |
||||
|
||||
# Tag |
||||
|
||||
Used for displaying metadata, for example to add more details to search results. Background and border colors are generated from the tag name. |
||||
|
||||
<Preview> |
||||
<div> |
||||
<Tag name='Tag' onClick={(name) => console.log(name)} /> |
||||
</div> |
||||
</Preview> |
||||
|
||||
### Usage |
||||
|
||||
```jsx |
||||
import { Tag } from '@grafana/ui'; |
||||
|
||||
<Tag name='Tag' onClick={(name) => console.log(name)} /> |
||||
``` |
||||
|
||||
### Props |
||||
<Props of={Tag} /> |
@ -0,0 +1,20 @@ |
||||
import React from 'react'; |
||||
import { action } from '@storybook/addon-actions'; |
||||
import { Tag } from './Tag'; |
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; |
||||
import mdx from './Tag.mdx'; |
||||
|
||||
export default { |
||||
title: 'General/Tags/Tag', |
||||
component: Tag, |
||||
decorators: [withCenteredStory], |
||||
parameters: { |
||||
docs: { |
||||
page: mdx, |
||||
}, |
||||
}, |
||||
}; |
||||
|
||||
export const single = () => { |
||||
return <Tag name="Tag" onClick={action('Tag clicked')} />; |
||||
}; |
@ -0,0 +1,52 @@ |
||||
import React, { forwardRef, HTMLAttributes } from 'react'; |
||||
import { cx, css } from 'emotion'; |
||||
import { GrafanaTheme } from '@grafana/data'; |
||||
import { useTheme } from '../../themes'; |
||||
import { getTagColorsFromName } from '../../utils'; |
||||
|
||||
export interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onClick'> { |
||||
/** Name of the tag to display */ |
||||
name: string; |
||||
onClick?: (name: string) => any; |
||||
} |
||||
|
||||
export const Tag = forwardRef<HTMLElement, Props>(({ name, onClick, className, ...rest }, ref) => { |
||||
const theme = useTheme(); |
||||
const styles = getTagStyles(theme, name); |
||||
|
||||
const onTagClick = () => { |
||||
if (onClick) { |
||||
onClick(name); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<span key={name} ref={ref} onClick={onTagClick} className={cx(styles.wrapper, className)} {...rest}> |
||||
{name} |
||||
</span> |
||||
); |
||||
}); |
||||
|
||||
const getTagStyles = (theme: GrafanaTheme, name: string) => { |
||||
const { borderColor, color } = getTagColorsFromName(name); |
||||
return { |
||||
wrapper: css` |
||||
font-weight: ${theme.typography.weight.semibold}; |
||||
font-size: ${theme.typography.size.sm}; |
||||
line-height: ${theme.typography.lineHeight.xs}; |
||||
vertical-align: baseline; |
||||
background-color: ${color}; |
||||
color: ${theme.colors.white}; |
||||
white-space: nowrap; |
||||
text-shadow: none; |
||||
padding: 3px 6px; |
||||
border: 1px solid ${borderColor}; |
||||
border-radius: ${theme.border.radius.md}; |
||||
|
||||
:hover { |
||||
opacity: 0.85; |
||||
cursor: pointer; |
||||
} |
||||
`,
|
||||
}; |
||||
}; |
@ -0,0 +1,22 @@ |
||||
import { Story, Preview, Props } from '@storybook/addon-docs/blocks'; |
||||
import { TagList } from './TagList'; |
||||
|
||||
# TagList |
||||
|
||||
List of tags with predefined margins and positioning. |
||||
|
||||
<Preview> |
||||
<TagList tags={['datasource-test', 'gdev', 'mysql', 'mssql']} /> |
||||
</Preview> |
||||
|
||||
### Usage |
||||
|
||||
```jsx |
||||
import { TagList } from '@grafana/ui'; |
||||
const tags = ['datasource-test', 'gdev', 'mysql', 'mssql']; |
||||
|
||||
<TagList tags={tags} onClick={tag => console.log(tag)} /> |
||||
``` |
||||
|
||||
### Props |
||||
<Props of={TagList} /> |
@ -0,0 +1,26 @@ |
||||
import React from 'react'; |
||||
import { action } from '@storybook/addon-actions'; |
||||
import { TagList } from './TagList'; |
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; |
||||
import mdx from './TagList.mdx'; |
||||
|
||||
export default { |
||||
title: 'General/Tags/TagList', |
||||
component: TagList, |
||||
decorators: [withCenteredStory], |
||||
parameters: { |
||||
docs: { |
||||
page: mdx, |
||||
}, |
||||
}, |
||||
}; |
||||
|
||||
const tags = ['datasource-test', 'gdev', 'mysql', 'mssql']; |
||||
|
||||
export const list = () => { |
||||
return ( |
||||
<div style={{ width: 300 }}> |
||||
<TagList tags={tags} onClick={action('Tag clicked')} /> |
||||
</div> |
||||
); |
||||
}; |
@ -0,0 +1,38 @@ |
||||
import React, { FC } from 'react'; |
||||
import { cx, css } from 'emotion'; |
||||
import { Tag } from './Tag'; |
||||
|
||||
export interface Props { |
||||
tags: string[]; |
||||
onClick?: (name: string) => any; |
||||
/** Custom styles for the wrapper component */ |
||||
className?: string; |
||||
} |
||||
|
||||
export const TagList: FC<Props> = ({ tags, onClick, className }) => { |
||||
const styles = getStyles(); |
||||
|
||||
return ( |
||||
<span className={cx(styles.wrapper, className)}> |
||||
{tags.map(tag => ( |
||||
<Tag key={tag} name={tag} onClick={onClick} className={styles.tag} /> |
||||
))} |
||||
</span> |
||||
); |
||||
}; |
||||
|
||||
const getStyles = () => { |
||||
return { |
||||
wrapper: css` |
||||
display: flex; |
||||
flex: 1 1 auto; |
||||
flex-wrap: wrap; |
||||
padding: 10px; |
||||
`,
|
||||
tag: css` |
||||
margin-left: 6px; |
||||
font-size: 11px; |
||||
padding: 2px 6px; |
||||
`,
|
||||
}; |
||||
}; |
Loading…
Reference in new issue