@dhis2-ui/tag 10.16.1 → 10.16.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -3
- package/src/features/accepts_icon/index.js +21 -0
- package/src/features/accepts_icon.feature +9 -0
- package/src/features/accepts_max_width/index.js +28 -0
- package/src/features/accepts_max_width.feature +9 -0
- package/src/features/accepts_text/index.js +12 -0
- package/src/features/accepts_text.feature +5 -0
- package/src/index.js +1 -0
- package/src/tag-icon.js +20 -0
- package/src/tag-text.js +18 -0
- package/src/tag.e2e.stories.js +15 -0
- package/src/tag.js +112 -0
- package/src/tag.prod.stories.js +112 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2-ui/tag",
|
|
3
|
-
"version": "10.16.1",
|
|
3
|
+
"version": "10.16.3-alpha.1",
|
|
4
4
|
"description": "UI Tag",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,13 +33,14 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@dhis2/prop-types": "^3.1.2",
|
|
36
|
-
"@dhis2/ui-constants": "10.16.1",
|
|
36
|
+
"@dhis2/ui-constants": "10.16.3-alpha.1",
|
|
37
37
|
"classnames": "^2.3.1",
|
|
38
38
|
"prop-types": "^15.7.2"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
|
41
41
|
"build",
|
|
42
|
-
"types"
|
|
42
|
+
"types",
|
|
43
|
+
"src"
|
|
43
44
|
],
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"react": "^18.3.1",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a default Tag is rendered', () => {
|
|
4
|
+
cy.visitStory('Tag', 'Without icon')
|
|
5
|
+
cy.get('[data-test="dhis2-uicore-tag"]').should('be.visible')
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
Given('a Tag with an icon is rendered', () => {
|
|
9
|
+
cy.visitStory('Tag', 'With icon')
|
|
10
|
+
cy.get('[data-test="dhis2-uicore-tag"]').should('be.visible')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
Then('the icon will not be rendered', () => {
|
|
14
|
+
cy.get('[data-test="dhis2-uicore-tag-icon"]').should('not.exist')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
Then('the icon will be visible', () => {
|
|
18
|
+
cy.get('[data-test="dhis2-uicore-tag-icon"]')
|
|
19
|
+
.contains('Icon')
|
|
20
|
+
.should('be.visible')
|
|
21
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given(
|
|
4
|
+
'a tag with the default max width and enough content to fill it completely is rendered',
|
|
5
|
+
() => {
|
|
6
|
+
cy.wrap('240px').as('maxWidth')
|
|
7
|
+
cy.visitStory('Tag', 'With default max width')
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-tag"]').should('be.visible')
|
|
9
|
+
}
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
Given(
|
|
13
|
+
'a tag with a max width and enough content to fill it completely is rendered',
|
|
14
|
+
() => {
|
|
15
|
+
cy.wrap('30px').as('maxWidth')
|
|
16
|
+
cy.visitStory('Tag', 'With custom max width')
|
|
17
|
+
cy.get('[data-test="dhis2-uicore-tag"]').should('be.visible')
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
Then('the width of the tag should be exactly the max width', () => {
|
|
22
|
+
cy.all(
|
|
23
|
+
() => cy.get('@maxWidth'),
|
|
24
|
+
() => cy.get('[data-test="dhis2-uicore-tag"]')
|
|
25
|
+
).then(([maxWidth, tag]) => {
|
|
26
|
+
expect(tag).to.have.css('width', maxWidth)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Feature: The Tag's max-width can be overwritten
|
|
2
|
+
|
|
3
|
+
Scenario: A Tag has a default max width
|
|
4
|
+
Given a tag with the default max width and enough content to fill it completely is rendered
|
|
5
|
+
Then the width of the tag should be exactly the max width
|
|
6
|
+
|
|
7
|
+
Scenario: A Tag has a custom max width
|
|
8
|
+
Given a tag with a max width and enough content to fill it completely is rendered
|
|
9
|
+
Then the width of the tag should be exactly the max width
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Tag with text is rendered', () => {
|
|
4
|
+
cy.visitStory('Tag', 'With text')
|
|
5
|
+
cy.get('[data-test="dhis2-uicore-tag"]').should('be.visible')
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
Then('the text will be visible', () => {
|
|
9
|
+
cy.get('[data-test="dhis2-uicore-tag-text"]')
|
|
10
|
+
.contains('Text content')
|
|
11
|
+
.should('be.visible')
|
|
12
|
+
})
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Tag } from './tag.js'
|
package/src/tag-icon.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export const TagIcon = ({ children, dataTest }) => (
|
|
5
|
+
<div data-test={dataTest}>
|
|
6
|
+
{children}
|
|
7
|
+
<style jsx>{`
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
margin-inline-end: 4px;
|
|
11
|
+
max-height: 16px;
|
|
12
|
+
max-width: 16px;
|
|
13
|
+
`}</style>
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
TagIcon.propTypes = {
|
|
18
|
+
dataTest: PropTypes.string.isRequired,
|
|
19
|
+
children: PropTypes.node,
|
|
20
|
+
}
|
package/src/tag-text.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export const TagText = ({ children, dataTest }) => (
|
|
5
|
+
<span data-test={dataTest}>
|
|
6
|
+
{children}
|
|
7
|
+
<style jsx>{`
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
white-space: nowrap;
|
|
10
|
+
text-overflow: ellipsis;
|
|
11
|
+
`}</style>
|
|
12
|
+
</span>
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
TagText.propTypes = {
|
|
16
|
+
dataTest: PropTypes.string.isRequired,
|
|
17
|
+
children: PropTypes.node,
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Tag } from './tag.js'
|
|
3
|
+
|
|
4
|
+
export default { title: 'Tag' }
|
|
5
|
+
export const WithoutIcon = () => <Tag>Default</Tag>
|
|
6
|
+
export const WithIcon = () => <Tag icon={<span>Icon</span>}>Default</Tag>
|
|
7
|
+
export const WithText = () => <Tag>Text content</Tag>
|
|
8
|
+
export const WithDefaultMaxWidth = () => (
|
|
9
|
+
<Tag>
|
|
10
|
+
This is a lot of content that should exceed the default max width of the
|
|
11
|
+
tag component quite significantly so testing is still works when the
|
|
12
|
+
default value of the max width is changed
|
|
13
|
+
</Tag>
|
|
14
|
+
)
|
|
15
|
+
export const WithCustomMaxWidth = () => <Tag maxWidth="30px">Text content</Tag>
|
package/src/tag.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { mutuallyExclusive } from '@dhis2/prop-types'
|
|
2
|
+
import { colors } from '@dhis2/ui-constants'
|
|
3
|
+
import cx from 'classnames'
|
|
4
|
+
import PropTypes from 'prop-types'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
import { TagIcon } from './tag-icon.js'
|
|
7
|
+
import { TagText } from './tag-text.js'
|
|
8
|
+
|
|
9
|
+
export const Tag = ({
|
|
10
|
+
maxWidth = '240px',
|
|
11
|
+
neutral,
|
|
12
|
+
negative,
|
|
13
|
+
positive,
|
|
14
|
+
icon,
|
|
15
|
+
bold,
|
|
16
|
+
className,
|
|
17
|
+
dataTest = 'dhis2-uicore-tag',
|
|
18
|
+
children,
|
|
19
|
+
}) => (
|
|
20
|
+
<div
|
|
21
|
+
data-test={dataTest}
|
|
22
|
+
className={cx(className, {
|
|
23
|
+
neutral,
|
|
24
|
+
positive,
|
|
25
|
+
negative,
|
|
26
|
+
bold,
|
|
27
|
+
})}
|
|
28
|
+
>
|
|
29
|
+
{icon && <TagIcon dataTest={`${dataTest}-icon`}>{icon}</TagIcon>}
|
|
30
|
+
<TagText dataTest={`${dataTest}-text`}>{children}</TagText>
|
|
31
|
+
<style jsx>
|
|
32
|
+
{`
|
|
33
|
+
div {
|
|
34
|
+
padding: 5px 6px;
|
|
35
|
+
border-radius: 3px;
|
|
36
|
+
background-color: ${colors.grey300};
|
|
37
|
+
fill: ${colors.grey700};
|
|
38
|
+
color: ${colors.grey900};
|
|
39
|
+
max-width: ${maxWidth};
|
|
40
|
+
display: inline-flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
font-size: 13px;
|
|
43
|
+
height: 23px;
|
|
44
|
+
}
|
|
45
|
+
.negative {
|
|
46
|
+
background-color: ${colors.red100};
|
|
47
|
+
fill: ${colors.red800};
|
|
48
|
+
color: ${colors.red900};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.neutral {
|
|
52
|
+
background-color: ${colors.blue100};
|
|
53
|
+
fill: ${colors.blue800};
|
|
54
|
+
color: ${colors.blue900};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.positive {
|
|
58
|
+
background-color: ${colors.green100};
|
|
59
|
+
fill: ${colors.green800};
|
|
60
|
+
color: ${colors.green900};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.bold {
|
|
64
|
+
font-weight: 700;
|
|
65
|
+
background-color: ${colors.grey700};
|
|
66
|
+
color: ${colors.white};
|
|
67
|
+
fill: ${colors.white};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.bold.neutral {
|
|
71
|
+
background-color: ${colors.blue800};
|
|
72
|
+
color: ${colors.blue050};
|
|
73
|
+
fill: ${colors.white};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.bold.positive {
|
|
77
|
+
background-color: ${colors.green700};
|
|
78
|
+
color: ${colors.green050};
|
|
79
|
+
fill: ${colors.white};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.bold.negative {
|
|
83
|
+
background-color: ${colors.red700};
|
|
84
|
+
color: ${colors.red050};
|
|
85
|
+
fill: ${colors.white};
|
|
86
|
+
}
|
|
87
|
+
`}
|
|
88
|
+
</style>
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
const tagVariantPropType = mutuallyExclusive(
|
|
93
|
+
['neutral', 'positive', 'negative'],
|
|
94
|
+
PropTypes.bool
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
Tag.propTypes = {
|
|
98
|
+
/** Use bold tags where it is important that the tag is seen by the user in an information dense interface. Bold tags should be reserved for edge cases and not overused. */
|
|
99
|
+
bold: PropTypes.bool,
|
|
100
|
+
children: PropTypes.node,
|
|
101
|
+
className: PropTypes.string,
|
|
102
|
+
dataTest: PropTypes.string,
|
|
103
|
+
/** Tags can contain icons. Use icons where they will help users easily identify the content of the tag. Tags must have a text label and cannot display only an icon. */
|
|
104
|
+
icon: PropTypes.node,
|
|
105
|
+
maxWidth: PropTypes.string,
|
|
106
|
+
/** Red 'negative' tags imply an error or a problem. `neutral`, `positive`, and `negative` are mutually exclusive props */
|
|
107
|
+
negative: tagVariantPropType,
|
|
108
|
+
/** Blue 'neutral' tags are used when a tag _could_ have valid or error status but is currently neutral. `neutral`, `positive`, and `negative` are mutually exclusive props */
|
|
109
|
+
neutral: tagVariantPropType,
|
|
110
|
+
/** Green 'valid' tags should be used to indicate validity or success. `neutral`, `positive`, and `negative` are mutually exclusive props */
|
|
111
|
+
positive: tagVariantPropType,
|
|
112
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Tag } from './tag.js'
|
|
3
|
+
|
|
4
|
+
const subtitle =
|
|
5
|
+
'Used to display categorizing labels or information for other elements in a collection.'
|
|
6
|
+
|
|
7
|
+
const description = `
|
|
8
|
+
Tags are used whenever an element in a collection needs to display its category or status. Tags should not be used for one-off, unique information. Tags can be displayed in any kind of component.
|
|
9
|
+
|
|
10
|
+
Tags are useful when displaying multiple elements in a collection that have the same basic attributes but belong to different categories or have different statuses. Do not use tags for elements that will always be the same, instead use a heading or other grouping method.
|
|
11
|
+
|
|
12
|
+
Tags are never used for primary interaction and should not be used as buttons. Clicking a tag could sort a collection by that tag, or open a page to display all elements that have that tag type. Tags should not be used as navigation elements.
|
|
13
|
+
|
|
14
|
+
\`\`\`js
|
|
15
|
+
import { Tag } from '@dhis2/ui'
|
|
16
|
+
\`\`\`
|
|
17
|
+
|
|
18
|
+
`
|
|
19
|
+
|
|
20
|
+
const tagArgType = {
|
|
21
|
+
table: { type: { summary: 'bool' } },
|
|
22
|
+
control: { type: 'boolean' },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
title: 'Tag',
|
|
27
|
+
component: Tag,
|
|
28
|
+
parameters: {
|
|
29
|
+
componentSubtitle: subtitle,
|
|
30
|
+
docs: { description: { component: description } },
|
|
31
|
+
},
|
|
32
|
+
argTypes: {
|
|
33
|
+
negative: { ...tagArgType },
|
|
34
|
+
neutral: { ...tagArgType },
|
|
35
|
+
positive: { ...tagArgType },
|
|
36
|
+
},
|
|
37
|
+
args: {
|
|
38
|
+
children: 'Dog',
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ExampleIcon = () => (
|
|
43
|
+
<svg
|
|
44
|
+
height="12"
|
|
45
|
+
viewBox="0 0 12 12"
|
|
46
|
+
width="12"
|
|
47
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
48
|
+
>
|
|
49
|
+
<g fill="inherit">
|
|
50
|
+
<path d="m6.00003329 5c-.40449432-.00016901-.76925356.24336235-.92416402.61701786-.15491047.3736555-.06945918.80383558.21650172 1.08991608s.71610521.37171165 1.08982546.21695745c.37372025-.15475421.61740424-.51941158.61740424-.92390594.00011082-.26517416-.10515147-.51952543-.29261873-.70707109-.18746725-.18754566-.44177449-.29291436-.70694867-.29291436z" />
|
|
51
|
+
<path d="m9.10419761 8.52484537c.76535472-.94655629 1.06135379-2.18836523.80523589-3.37821761-.05634956-.27032079-.32125073-.44379594-.59167333-.38746761-.2704226.05632834-.4439631.32112977-.38761355.59145056.19496765.89486787-.02567692 1.82986482-.60017581 2.5432874-.72092529.88419157-1.88113225 1.28314793-2.99364655 1.02941488-1.11251431-.25373305-1.98479962-1.11624341-2.25073114-2.22550911-.26593151-1.10926569.12051398-2.27331263.99713349-3.00355471.69583114-.57340823 1.61098701-.80724221 2.4967314-.63794667.17734061.03661596.36062942-.02545013.47919689-.16226782.11856746-.1368177.15389197-.32701448.09235365-.49725733s-.21032645-.29393785-.38899706-.32339268c-1.92234735-.37212623-3.83170501.70111683-4.5123533 2.53637752s.06739468 3.8933194 1.76783755 4.86377602c1.70044287.97045659 3.85367697.56818039 5.08870246-.95069268z" />
|
|
52
|
+
<path d="m5.99960069 0c-1.97689694.00113-3.82650358.97550158-4.94544231 2.60525583-1.11893874 1.62975425-1.3637448 3.70593215-.65455769 5.55124417.05611465.17686038.20555481.3081025.38818046.34090985.18262565.03280736.36840126-.03821571.48256414-.18448699.11416287-.14627127.13793722-.34373427.0617554-.51292286-.85968765-2.23032693-.01933119-4.75713774 2.00488351-6.02834816s4.66524393-.93071599 6.30084518.81233583c1.63560122 1.74305182 1.80759312 4.40037955.41033032 6.33972937-1.39726277 1.93934986-3.97236477 2.61745126-6.14355901 1.61778296-.16228789-.0750258-.35227387-.0577729-.49839205.0452597-.14611818.1030325-.22616973.2761916-.21000001.45425.01616973.1780583.12610416.3339645.28839206.4089903.78810364.364813 1.64655922.5525468 2.515.5500256 3.31370847 0 5.99999991-2.68631714 5.99999991-6.0000256s-2.68629144-6-5.99999991-6z" />
|
|
53
|
+
<path d="m8.99960069 3.5c0-.20225265-.1218571-.38459013-.30873435-.46195903-.18687725-.07736891-.40196034-.03452776-.54492981.1085414-.14296946.14306916-.18566067.35818204-.10816151.5450053.07749917.18682326.25992156.30855316.46218139.30841233.27600227-.00019246.49964428-.22399048.49964428-.5z" />
|
|
54
|
+
<circle cx="1.499601" cy="9.5" r="1" />
|
|
55
|
+
</g>
|
|
56
|
+
</svg>
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const ExampleLargeIcon = () => (
|
|
60
|
+
<svg
|
|
61
|
+
height="24"
|
|
62
|
+
viewBox="0 0 24 24"
|
|
63
|
+
width="24"
|
|
64
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
+
>
|
|
66
|
+
<path
|
|
67
|
+
d="m12 2c-5.52 0-10 4.48-10 10s4.48 10 10 10 10-4.48 10-10-4.48-10-10-10zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5s2.01-4.5 4.5-4.5 4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z"
|
|
68
|
+
fill="inherit"
|
|
69
|
+
/>
|
|
70
|
+
</svg>
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const Template = (args) => <Tag {...args} />
|
|
74
|
+
|
|
75
|
+
export const Default = Template.bind({})
|
|
76
|
+
|
|
77
|
+
export const WithIcon = Template.bind({})
|
|
78
|
+
WithIcon.args = { icon: <ExampleIcon /> }
|
|
79
|
+
|
|
80
|
+
export const Neutral = Template.bind({})
|
|
81
|
+
Neutral.args = { neutral: true }
|
|
82
|
+
|
|
83
|
+
export const Positive = Template.bind({})
|
|
84
|
+
Positive.args = { positive: true }
|
|
85
|
+
|
|
86
|
+
export const Negative = Template.bind({})
|
|
87
|
+
Negative.args = { negative: true }
|
|
88
|
+
|
|
89
|
+
export const Bold = Template.bind({})
|
|
90
|
+
Bold.args = { bold: true }
|
|
91
|
+
|
|
92
|
+
export const WithLargeIcon = Template.bind({})
|
|
93
|
+
WithLargeIcon.args = { icon: <ExampleLargeIcon /> }
|
|
94
|
+
|
|
95
|
+
export const WithClippedLongText = Template.bind({})
|
|
96
|
+
WithClippedLongText.args = {
|
|
97
|
+
icon: <ExampleIcon />,
|
|
98
|
+
children: 'I am long text, therefore I get clipped before I finish',
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const WithClippedTextAndMaxWidth = Template.bind({})
|
|
102
|
+
WithClippedTextAndMaxWidth.args = {
|
|
103
|
+
children: 'I am long text',
|
|
104
|
+
maxWidth: '50px',
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const RTL = (args) => (
|
|
108
|
+
<div dir="rtl">
|
|
109
|
+
<Template {...args} />
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
RTL.args = { icon: <ExampleIcon /> }
|