@dhis2-ui/alert 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 +6 -5
- package/src/alert-bar/action.js +37 -0
- package/src/alert-bar/actions.js +47 -0
- package/src/alert-bar/alert-bar.e2e.stories.js +53 -0
- package/src/alert-bar/alert-bar.js +180 -0
- package/src/alert-bar/alert-bar.prod.stories.js +258 -0
- package/src/alert-bar/alert-bar.styles.js +92 -0
- package/src/alert-bar/dismiss.js +39 -0
- package/src/alert-bar/features/api/index.js +51 -0
- package/src/alert-bar/features/api.feature +29 -0
- package/src/alert-bar/features/common/index.js +26 -0
- package/src/alert-bar/features/hide/index.js +39 -0
- package/src/alert-bar/features/hide.feature +26 -0
- package/src/alert-bar/icon.js +80 -0
- package/src/alert-bar/index.js +1 -0
- package/src/alert-bar/message.js +19 -0
- package/src/alert-stack/alert-stack.e2e.stories.js +13 -0
- package/src/alert-stack/alert-stack.js +44 -0
- package/src/alert-stack/alert-stack.prod.stories.js +111 -0
- package/src/alert-stack/features/render_children/alertbars.js +11 -0
- package/src/alert-stack/features/render_children.feature +5 -0
- package/src/alert-stack/index.js +1 -0
- package/src/index.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2-ui/alert",
|
|
3
|
-
"version": "10.16.1",
|
|
3
|
+
"version": "10.16.3-alpha.1",
|
|
4
4
|
"description": "UI Alert",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,15 +33,16 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@dhis2/prop-types": "^3.1.2",
|
|
36
|
-
"@dhis2-ui/portal": "10.16.1",
|
|
37
|
-
"@dhis2/ui-constants": "10.16.1",
|
|
38
|
-
"@dhis2/ui-icons": "10.16.1",
|
|
36
|
+
"@dhis2-ui/portal": "10.16.3-alpha.1",
|
|
37
|
+
"@dhis2/ui-constants": "10.16.3-alpha.1",
|
|
38
|
+
"@dhis2/ui-icons": "10.16.3-alpha.1",
|
|
39
39
|
"classnames": "^2.3.1",
|
|
40
40
|
"prop-types": "^15.7.2"
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
43
43
|
"build",
|
|
44
|
-
"types"
|
|
44
|
+
"types",
|
|
45
|
+
"src"
|
|
45
46
|
],
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"react": "^18.3.1",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { spacers } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React, { Component } from 'react'
|
|
4
|
+
|
|
5
|
+
class Action extends Component {
|
|
6
|
+
onClick = (event) => {
|
|
7
|
+
this.props.onClick(event)
|
|
8
|
+
this.props.hide(event)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
return (
|
|
13
|
+
<span onClick={this.onClick} data-test={this.props.dataTest}>
|
|
14
|
+
{this.props.label}
|
|
15
|
+
<style jsx>{`
|
|
16
|
+
span {
|
|
17
|
+
margin-inline-end: ${spacers.dp12};
|
|
18
|
+
text-decoration: underline;
|
|
19
|
+
white-space: nowrap;
|
|
20
|
+
}
|
|
21
|
+
span:hover {
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
}
|
|
24
|
+
`}</style>
|
|
25
|
+
</span>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Action.propTypes = {
|
|
31
|
+
dataTest: PropTypes.string.isRequired,
|
|
32
|
+
hide: PropTypes.func.isRequired,
|
|
33
|
+
label: PropTypes.string.isRequired,
|
|
34
|
+
onClick: PropTypes.func.isRequired,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { Action }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { arrayWithLength } from '@dhis2/prop-types'
|
|
2
|
+
import { spacers } from '@dhis2/ui-constants'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { Action } from './action.js'
|
|
6
|
+
|
|
7
|
+
const Actions = ({ actions, hide, dataTest }) => {
|
|
8
|
+
if (!actions) {
|
|
9
|
+
return null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div>
|
|
14
|
+
{actions.map((action) => (
|
|
15
|
+
<Action
|
|
16
|
+
key={action.label}
|
|
17
|
+
{...action}
|
|
18
|
+
hide={hide}
|
|
19
|
+
dataTest={`${dataTest}-action`}
|
|
20
|
+
/>
|
|
21
|
+
))}
|
|
22
|
+
<style jsx>{`
|
|
23
|
+
div {
|
|
24
|
+
margin-inline-start: ${spacers.dp48};
|
|
25
|
+
margin-inline-end: -${spacers.dp12};
|
|
26
|
+
}
|
|
27
|
+
`}</style>
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const actionsPropType = arrayWithLength(
|
|
33
|
+
0,
|
|
34
|
+
2,
|
|
35
|
+
PropTypes.shape({
|
|
36
|
+
label: PropTypes.string.isRequired,
|
|
37
|
+
onClick: PropTypes.func.isRequired,
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
Actions.propTypes = {
|
|
42
|
+
dataTest: PropTypes.string.isRequired,
|
|
43
|
+
hide: PropTypes.func.isRequired,
|
|
44
|
+
actions: actionsPropType,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Actions, actionsPropType }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { AlertBar } from './index.js'
|
|
3
|
+
|
|
4
|
+
window.onHidden = window.Cypress && window.Cypress.cy.stub()
|
|
5
|
+
|
|
6
|
+
export default { title: 'AlertBar' }
|
|
7
|
+
|
|
8
|
+
export const Default = () => <AlertBar>Default</AlertBar>
|
|
9
|
+
export const CustomDuration = () => (
|
|
10
|
+
<AlertBar duration={2000}>Custom duration</AlertBar>
|
|
11
|
+
)
|
|
12
|
+
export const PermanentWithActions = () => (
|
|
13
|
+
<AlertBar
|
|
14
|
+
permanent
|
|
15
|
+
actions={[
|
|
16
|
+
{ label: 'Save', onClick: () => {} },
|
|
17
|
+
{ label: 'Cancel', onClick: () => {} },
|
|
18
|
+
]}
|
|
19
|
+
>
|
|
20
|
+
With Actions
|
|
21
|
+
</AlertBar>
|
|
22
|
+
)
|
|
23
|
+
export const DisabledIcon = () => <AlertBar icon={false}>Message</AlertBar>
|
|
24
|
+
export const CustomIcon = () => (
|
|
25
|
+
<AlertBar icon={<span>Custom icon</span>}>Message</AlertBar>
|
|
26
|
+
)
|
|
27
|
+
export const WithMessage = () => <AlertBar>With a message</AlertBar>
|
|
28
|
+
export const WithOnHidden = () => (
|
|
29
|
+
<AlertBar onHidden={window.onHidden}>Message</AlertBar>
|
|
30
|
+
)
|
|
31
|
+
export const Permanent = () => <AlertBar permanent>Message</AlertBar>
|
|
32
|
+
export const HiddenProp = () => {
|
|
33
|
+
const [hidden, setHidden] = useState(true)
|
|
34
|
+
const toggleVisibility = () => setHidden((prevHidden) => !prevHidden)
|
|
35
|
+
return (
|
|
36
|
+
<React.Fragment>
|
|
37
|
+
<button
|
|
38
|
+
style={{
|
|
39
|
+
display: 'block',
|
|
40
|
+
position: 'fixed',
|
|
41
|
+
bottom: 150,
|
|
42
|
+
left: 10,
|
|
43
|
+
}}
|
|
44
|
+
onClick={toggleVisibility}
|
|
45
|
+
>
|
|
46
|
+
{hidden ? 'Show' : 'Hide'}
|
|
47
|
+
</button>
|
|
48
|
+
<AlertBar permanent hidden={hidden} onHidden={window.onHidden}>
|
|
49
|
+
Short text
|
|
50
|
+
</AlertBar>
|
|
51
|
+
</React.Fragment>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { mutuallyExclusive } from '@dhis2/prop-types'
|
|
2
|
+
import cx from 'classnames'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
import React, { useRef, useState, useEffect } from 'react'
|
|
5
|
+
import { Actions, actionsPropType } from './actions.js'
|
|
6
|
+
import styles, { ANIMATION_TIME } from './alert-bar.styles.js'
|
|
7
|
+
import { Dismiss } from './dismiss.js'
|
|
8
|
+
import { Icon, iconPropType } from './icon.js'
|
|
9
|
+
import { Message } from './message.js'
|
|
10
|
+
|
|
11
|
+
const AlertBar = ({
|
|
12
|
+
actions,
|
|
13
|
+
children,
|
|
14
|
+
className,
|
|
15
|
+
critical,
|
|
16
|
+
dataTest = 'dhis2-uicore-alertbar',
|
|
17
|
+
duration = 8000,
|
|
18
|
+
hidden,
|
|
19
|
+
icon = true,
|
|
20
|
+
permanent,
|
|
21
|
+
success,
|
|
22
|
+
warning,
|
|
23
|
+
onHidden,
|
|
24
|
+
}) => {
|
|
25
|
+
const [inViewport, setInViewport] = useState(!hidden)
|
|
26
|
+
const [inDOM, setInDOM] = useState(!hidden)
|
|
27
|
+
const alertRef = useRef(null)
|
|
28
|
+
const showTimeout = useRef(null)
|
|
29
|
+
const displayTimeout = useRef(null)
|
|
30
|
+
const hideTimeout = useRef(null)
|
|
31
|
+
const displayStartTime = useRef(null)
|
|
32
|
+
const displayTimeRemaining = useRef(null)
|
|
33
|
+
const info = !critical && !success && !warning
|
|
34
|
+
const shouldAutoHide = !(permanent || warning || critical)
|
|
35
|
+
const show = () => {
|
|
36
|
+
setInDOM(true)
|
|
37
|
+
setInViewport(true)
|
|
38
|
+
}
|
|
39
|
+
const hide = () => {
|
|
40
|
+
setInDOM(true)
|
|
41
|
+
setInViewport(false)
|
|
42
|
+
}
|
|
43
|
+
const remove = () => {
|
|
44
|
+
setInDOM(false)
|
|
45
|
+
setInViewport(false)
|
|
46
|
+
onHidden && onHidden({}, null)
|
|
47
|
+
}
|
|
48
|
+
const clearAllTimeouts = () => {
|
|
49
|
+
clearTimeout(showTimeout.current)
|
|
50
|
+
clearTimeout(displayTimeout.current)
|
|
51
|
+
clearTimeout(hideTimeout.current)
|
|
52
|
+
}
|
|
53
|
+
const runHideAnimation = () => {
|
|
54
|
+
clearAllTimeouts()
|
|
55
|
+
if (alertRef.current) {
|
|
56
|
+
alertRef.current.style.setProperty(
|
|
57
|
+
'--alert-bar-collapse',
|
|
58
|
+
`-${alertRef.current.offsetHeight}px`
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
hide()
|
|
62
|
+
hideTimeout.current = setTimeout(remove, ANIMATION_TIME)
|
|
63
|
+
}
|
|
64
|
+
const startDisplayTimeout = () => {
|
|
65
|
+
if (shouldAutoHide) {
|
|
66
|
+
clearAllTimeouts()
|
|
67
|
+
displayStartTime.current = Date.now()
|
|
68
|
+
displayTimeRemaining.current = duration
|
|
69
|
+
displayTimeout.current = setTimeout(
|
|
70
|
+
runHideAnimation,
|
|
71
|
+
displayTimeRemaining.current
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const runShowAnimation = () => {
|
|
76
|
+
clearAllTimeouts()
|
|
77
|
+
show()
|
|
78
|
+
showTimeout.current = setTimeout(startDisplayTimeout, ANIMATION_TIME)
|
|
79
|
+
}
|
|
80
|
+
const pauseDisplayTimeout = () => {
|
|
81
|
+
if (shouldAutoHide) {
|
|
82
|
+
clearAllTimeouts()
|
|
83
|
+
const elapsedTime = Date.now() - displayStartTime.current
|
|
84
|
+
displayTimeRemaining.current -= elapsedTime
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const resumeDisplayTimeout = () => {
|
|
88
|
+
if (shouldAutoHide) {
|
|
89
|
+
clearAllTimeouts()
|
|
90
|
+
displayTimeout.current = setTimeout(
|
|
91
|
+
runHideAnimation,
|
|
92
|
+
displayTimeRemaining.current
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
// Additional check on inDOM prevents the AlertBar from briefly showing
|
|
99
|
+
// when it is mounted with a hidden prop set to true
|
|
100
|
+
if (hidden && inDOM) {
|
|
101
|
+
runHideAnimation()
|
|
102
|
+
}
|
|
103
|
+
if (!hidden) {
|
|
104
|
+
runShowAnimation()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return clearAllTimeouts
|
|
108
|
+
}, [hidden])
|
|
109
|
+
|
|
110
|
+
return !inDOM ? null : (
|
|
111
|
+
<div
|
|
112
|
+
ref={alertRef}
|
|
113
|
+
className={cx(className, {
|
|
114
|
+
info,
|
|
115
|
+
success,
|
|
116
|
+
warning,
|
|
117
|
+
critical,
|
|
118
|
+
inViewport,
|
|
119
|
+
})}
|
|
120
|
+
data-test={dataTest}
|
|
121
|
+
onMouseEnter={pauseDisplayTimeout}
|
|
122
|
+
onMouseLeave={resumeDisplayTimeout}
|
|
123
|
+
>
|
|
124
|
+
<Icon
|
|
125
|
+
dataTest={`${dataTest}-icon`}
|
|
126
|
+
icon={icon}
|
|
127
|
+
critical={critical}
|
|
128
|
+
success={success}
|
|
129
|
+
warning={warning}
|
|
130
|
+
info={info}
|
|
131
|
+
/>
|
|
132
|
+
<Message>{children}</Message>
|
|
133
|
+
<Actions
|
|
134
|
+
actions={actions}
|
|
135
|
+
hide={runHideAnimation}
|
|
136
|
+
dataTest={dataTest}
|
|
137
|
+
/>
|
|
138
|
+
<Dismiss
|
|
139
|
+
onClick={runHideAnimation}
|
|
140
|
+
dataTest={`${dataTest}-dismiss`}
|
|
141
|
+
/>
|
|
142
|
+
|
|
143
|
+
<style jsx>{styles}</style>
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const alertTypePropType = mutuallyExclusive(
|
|
149
|
+
['success', 'warning', 'critical'],
|
|
150
|
+
PropTypes.bool
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
AlertBar.propTypes = {
|
|
154
|
+
/** An array of 0-2 action objects
|
|
155
|
+
`[{label: "Save", onClick: clickHandler}]`*/
|
|
156
|
+
actions: actionsPropType,
|
|
157
|
+
/** The message string for the alert */
|
|
158
|
+
children: PropTypes.string,
|
|
159
|
+
className: PropTypes.string,
|
|
160
|
+
/** Alert bars with `critical` will not autohide */
|
|
161
|
+
critical: alertTypePropType,
|
|
162
|
+
dataTest: PropTypes.string,
|
|
163
|
+
/** How long you want the notification to display, in `ms`, when it's not permanent */
|
|
164
|
+
duration: PropTypes.number,
|
|
165
|
+
/** AlertBar will be hidden on creation when this is set to true */
|
|
166
|
+
hidden: PropTypes.bool,
|
|
167
|
+
/**
|
|
168
|
+
* A specific icon to override the default icon in the bar.
|
|
169
|
+
* If `false` is provided, no icon will be shown.
|
|
170
|
+
*/
|
|
171
|
+
icon: iconPropType,
|
|
172
|
+
/** When set, AlertBar will not autohide */
|
|
173
|
+
permanent: PropTypes.bool,
|
|
174
|
+
success: alertTypePropType,
|
|
175
|
+
/** Alert bars with `warning` will not autohide */
|
|
176
|
+
warning: alertTypePropType,
|
|
177
|
+
onHidden: PropTypes.func,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export { AlertBar }
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { IconFile16 } from '@dhis2/ui-icons'
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
import { AlertBar } from './index.js'
|
|
4
|
+
|
|
5
|
+
const subtitle = `
|
|
6
|
+
A floating alert that informs the user about temporary information
|
|
7
|
+
in the context of the current screen.
|
|
8
|
+
`
|
|
9
|
+
|
|
10
|
+
const description = `
|
|
11
|
+
Alert bars notify a user of some information. There are different types of
|
|
12
|
+
alert bar for displaying different types of content. Use the alert bar type
|
|
13
|
+
that matches your content type and importance. Note that alert bar can be
|
|
14
|
+
ignored by the user, so they shouldn't be used for content that needs to
|
|
15
|
+
block an app flow, use a modal instead.
|
|
16
|
+
|
|
17
|
+
Alert bars are always displayed at centered and fixed at the bottom of the
|
|
18
|
+
screen. Some types of alert bar dismiss after a set time, others must be
|
|
19
|
+
dismissed by the user.
|
|
20
|
+
|
|
21
|
+
See specification: [Design System](https://github.com/dhis2/design-system/blob/master/molecules/alertbar.md)
|
|
22
|
+
|
|
23
|
+
\`\`\`js
|
|
24
|
+
import { AlertBar } from '@dhis2/ui'
|
|
25
|
+
\`\`\`
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
const Wrapper = (fn) => (
|
|
29
|
+
<div style={{ height: '260px' }}>
|
|
30
|
+
<div
|
|
31
|
+
className="alert-bars"
|
|
32
|
+
style={{
|
|
33
|
+
width: '100%',
|
|
34
|
+
position: 'fixed',
|
|
35
|
+
bottom: 0,
|
|
36
|
+
left: 0,
|
|
37
|
+
paddingLeft: 16,
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{fn()}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const alertTypeArgType = {
|
|
46
|
+
table: {
|
|
47
|
+
type: {
|
|
48
|
+
summary: 'bool',
|
|
49
|
+
detail: "'success', 'warning', and 'critical' are mutually exclusive props",
|
|
50
|
+
},
|
|
51
|
+
defaultValue: {
|
|
52
|
+
summary: false,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
control: {
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
const iconArgType = {
|
|
60
|
+
table: {
|
|
61
|
+
type: {
|
|
62
|
+
summary: 'bool | element',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const permanentArgType = {
|
|
68
|
+
table: {
|
|
69
|
+
type: {
|
|
70
|
+
summary: 'bool',
|
|
71
|
+
},
|
|
72
|
+
defaultValue: {
|
|
73
|
+
summary: false,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
control: {
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const childrenArgType = {
|
|
82
|
+
table: {
|
|
83
|
+
type: {
|
|
84
|
+
summary: 'String to display in the alert bar',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const actionsArgType = {
|
|
90
|
+
table: {
|
|
91
|
+
type: {
|
|
92
|
+
summary: '[{ label: string, onClick: func }]',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default {
|
|
98
|
+
title: 'Alert Bar',
|
|
99
|
+
component: AlertBar,
|
|
100
|
+
decorators: [Wrapper],
|
|
101
|
+
args: {
|
|
102
|
+
children: 'I will autohide',
|
|
103
|
+
},
|
|
104
|
+
parameters: {
|
|
105
|
+
componentSubtitle: subtitle,
|
|
106
|
+
docs: {
|
|
107
|
+
description: {
|
|
108
|
+
component: description,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
argTypes: {
|
|
113
|
+
actions: { ...actionsArgType },
|
|
114
|
+
critical: { ...alertTypeArgType },
|
|
115
|
+
success: { ...alertTypeArgType },
|
|
116
|
+
warning: { ...alertTypeArgType },
|
|
117
|
+
permanent: { ...permanentArgType },
|
|
118
|
+
children: { ...childrenArgType },
|
|
119
|
+
icon: { ...iconArgType },
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const Default = (args) => <AlertBar {...args}></AlertBar>
|
|
124
|
+
|
|
125
|
+
export const States = () => (
|
|
126
|
+
<React.Fragment>
|
|
127
|
+
<AlertBar permanent>Default (info)</AlertBar>
|
|
128
|
+
<AlertBar permanent success>
|
|
129
|
+
Success
|
|
130
|
+
</AlertBar>
|
|
131
|
+
<AlertBar permanent warning>
|
|
132
|
+
Warning
|
|
133
|
+
</AlertBar>
|
|
134
|
+
<AlertBar permanent critical>
|
|
135
|
+
Critical
|
|
136
|
+
</AlertBar>
|
|
137
|
+
</React.Fragment>
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
export const RTL = () => (
|
|
141
|
+
<React.Fragment>
|
|
142
|
+
<div dir="rtl">
|
|
143
|
+
<AlertBar permanent>Default (info)</AlertBar>
|
|
144
|
+
<AlertBar permanent success>
|
|
145
|
+
Success
|
|
146
|
+
</AlertBar>
|
|
147
|
+
<AlertBar permanent warning>
|
|
148
|
+
Warning
|
|
149
|
+
</AlertBar>
|
|
150
|
+
<AlertBar permanent critical>
|
|
151
|
+
Critical
|
|
152
|
+
</AlertBar>
|
|
153
|
+
</div>
|
|
154
|
+
</React.Fragment>
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
export const AutoHiding = () => (
|
|
158
|
+
<React.Fragment>
|
|
159
|
+
<AlertBar permanent>Permanent never auto-hides</AlertBar>
|
|
160
|
+
<AlertBar warning>Warning never auto-hides</AlertBar>
|
|
161
|
+
<AlertBar critical>Critial never auto-hides</AlertBar>
|
|
162
|
+
<AlertBar
|
|
163
|
+
duration={2000}
|
|
164
|
+
onHidden={(payload, event) => {
|
|
165
|
+
console.log('onHidden payload', payload)
|
|
166
|
+
console.log('onHidden event', event)
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
Custom duration, hides after 2s
|
|
170
|
+
</AlertBar>
|
|
171
|
+
<AlertBar
|
|
172
|
+
onHidden={(payload, event) => {
|
|
173
|
+
console.log('onHidden payload', payload)
|
|
174
|
+
console.log('onHidden event', event)
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
Default auto-hides after 8s
|
|
178
|
+
</AlertBar>
|
|
179
|
+
</React.Fragment>
|
|
180
|
+
)
|
|
181
|
+
AutoHiding.storyName = 'Auto hiding'
|
|
182
|
+
|
|
183
|
+
export const WithActions = () => (
|
|
184
|
+
<AlertBar
|
|
185
|
+
permanent
|
|
186
|
+
actions={[
|
|
187
|
+
{ label: 'Save', onClick: () => {} },
|
|
188
|
+
{ label: 'Cancel', onClick: () => {} },
|
|
189
|
+
]}
|
|
190
|
+
>
|
|
191
|
+
With Actions
|
|
192
|
+
</AlertBar>
|
|
193
|
+
)
|
|
194
|
+
WithActions.storyName = 'With actions'
|
|
195
|
+
|
|
196
|
+
export const WithActionsAndInsufficientSpace = () => (
|
|
197
|
+
<AlertBar
|
|
198
|
+
permanent
|
|
199
|
+
actions={[
|
|
200
|
+
{ label: 'Long action 1', onClick: () => {} },
|
|
201
|
+
{ label: 'Long action 2', onClick: () => {} },
|
|
202
|
+
]}
|
|
203
|
+
>
|
|
204
|
+
Some text, a pretty normal amount, that conflicts with pretty long
|
|
205
|
+
actions
|
|
206
|
+
</AlertBar>
|
|
207
|
+
)
|
|
208
|
+
WithActionsAndInsufficientSpace.storyName =
|
|
209
|
+
'With actions and insufficient space'
|
|
210
|
+
|
|
211
|
+
export const Icons = () => (
|
|
212
|
+
<React.Fragment>
|
|
213
|
+
<AlertBar permanent>Default icon</AlertBar>
|
|
214
|
+
<AlertBar permanent icon={false}>
|
|
215
|
+
No icon
|
|
216
|
+
</AlertBar>
|
|
217
|
+
<AlertBar permanent icon={<IconFile16 />}>
|
|
218
|
+
Custom icon
|
|
219
|
+
</AlertBar>
|
|
220
|
+
</React.Fragment>
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
export const TextOverflow = () => (
|
|
224
|
+
<React.Fragment>
|
|
225
|
+
<AlertBar permanent>Short text</AlertBar>
|
|
226
|
+
<AlertBar permanent>
|
|
227
|
+
If the alert bar gets a ver long text, it will grow to a maximum of
|
|
228
|
+
600px and the text will overflow across several lines. If there are
|
|
229
|
+
multiple AlertBars in a stack, they will all grow to the size of the
|
|
230
|
+
widest sibling.
|
|
231
|
+
</AlertBar>
|
|
232
|
+
</React.Fragment>
|
|
233
|
+
)
|
|
234
|
+
TextOverflow.storyName = 'Text overflow'
|
|
235
|
+
|
|
236
|
+
export const HiddenProp = () => {
|
|
237
|
+
const [hidden, setHidden] = useState(true)
|
|
238
|
+
const toggleVisibility = () => setHidden((prevHidden) => !prevHidden)
|
|
239
|
+
return (
|
|
240
|
+
<React.Fragment>
|
|
241
|
+
<button
|
|
242
|
+
style={{
|
|
243
|
+
display: 'block',
|
|
244
|
+
position: 'fixed',
|
|
245
|
+
bottom: 150,
|
|
246
|
+
left: 10,
|
|
247
|
+
}}
|
|
248
|
+
onClick={toggleVisibility}
|
|
249
|
+
>
|
|
250
|
+
{hidden ? 'Show' : 'Hide'}
|
|
251
|
+
</button>
|
|
252
|
+
<AlertBar permanent hidden={hidden}>
|
|
253
|
+
Short text
|
|
254
|
+
</AlertBar>
|
|
255
|
+
</React.Fragment>
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
HiddenProp.storyName = 'Hidden prop'
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { colors, spacers, elevations } from '@dhis2/ui-constants'
|
|
2
|
+
import css from 'styled-jsx/css'
|
|
3
|
+
|
|
4
|
+
export const ANIMATION_TIME = 350
|
|
5
|
+
|
|
6
|
+
export default css`
|
|
7
|
+
div {
|
|
8
|
+
display: flex;
|
|
9
|
+
justify-content: space-between;
|
|
10
|
+
align-items: center;
|
|
11
|
+
border-radius: 4px;
|
|
12
|
+
box-shadow: ${elevations.e300};
|
|
13
|
+
padding-top: ${spacers.dp8};
|
|
14
|
+
padding-inline-end: ${spacers.dp8};
|
|
15
|
+
padding-bottom: ${spacers.dp8};
|
|
16
|
+
padding-inline-start: ${spacers.dp16};
|
|
17
|
+
margin-bottom: ${spacers.dp16};
|
|
18
|
+
max-width: 600px;
|
|
19
|
+
min-width: 300px;
|
|
20
|
+
font-size: 14px;
|
|
21
|
+
pointer-events: all;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* States */
|
|
25
|
+
|
|
26
|
+
div.info {
|
|
27
|
+
background-color: ${colors.grey900};
|
|
28
|
+
color: ${colors.white};
|
|
29
|
+
}
|
|
30
|
+
div.success {
|
|
31
|
+
background-color: ${colors.green800};
|
|
32
|
+
color: ${colors.white};
|
|
33
|
+
}
|
|
34
|
+
div.warning {
|
|
35
|
+
background-color: ${colors.yellow300};
|
|
36
|
+
color: ${colors.yellow900};
|
|
37
|
+
}
|
|
38
|
+
div.critical {
|
|
39
|
+
background-color: ${colors.red800};
|
|
40
|
+
color: ${colors.white};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Animation */
|
|
44
|
+
|
|
45
|
+
@keyframes slidein {
|
|
46
|
+
from {
|
|
47
|
+
transform: translateY(${spacers.dp24});
|
|
48
|
+
opacity: 0;
|
|
49
|
+
}
|
|
50
|
+
to {
|
|
51
|
+
transform: translateY(0);
|
|
52
|
+
opacity: 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@keyframes slideout {
|
|
57
|
+
0% {
|
|
58
|
+
transform: translateY(0);
|
|
59
|
+
opacity: 1;
|
|
60
|
+
margin-bottom: ${spacers.dp16};
|
|
61
|
+
}
|
|
62
|
+
45% {
|
|
63
|
+
opacity: 0;
|
|
64
|
+
}
|
|
65
|
+
100% {
|
|
66
|
+
transform: translateY(${spacers.dp8});
|
|
67
|
+
opacity: 0;
|
|
68
|
+
margin-bottom: var(--alert-bar-collapse, ${spacers.dp16});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
div.inViewport {
|
|
73
|
+
animation-duration: ${ANIMATION_TIME}ms;
|
|
74
|
+
animation-name: slidein;
|
|
75
|
+
animation-fill-mode: forwards;
|
|
76
|
+
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
div {
|
|
80
|
+
animation-duration: ${ANIMATION_TIME}ms;
|
|
81
|
+
animation-name: slideout;
|
|
82
|
+
animation-fill-mode: forwards;
|
|
83
|
+
animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@media (prefers-reduced-motion: reduce) {
|
|
87
|
+
div,
|
|
88
|
+
div.inViewport {
|
|
89
|
+
animation-duration: 0ms;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
`
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { spacers } from '@dhis2/ui-constants'
|
|
2
|
+
import { IconCross24 } from '@dhis2/ui-icons'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
const Dismiss = ({ onClick, dataTest }) => (
|
|
7
|
+
<div onClick={onClick} data-test={dataTest}>
|
|
8
|
+
<IconCross24 />
|
|
9
|
+
<style jsx>{`
|
|
10
|
+
div {
|
|
11
|
+
margin-inline-start: ${spacers.dp16};
|
|
12
|
+
min-height: 32px;
|
|
13
|
+
min-width: 32px;
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
border-radius: 5px;
|
|
18
|
+
}
|
|
19
|
+
div:hover {
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
background: rgba(0, 0, 0, 0.15);
|
|
22
|
+
}
|
|
23
|
+
div:active {
|
|
24
|
+
background: rgba(0, 0, 0, 0.25);
|
|
25
|
+
}
|
|
26
|
+
div :global(svg) {
|
|
27
|
+
width: 18px;
|
|
28
|
+
height: 18px;
|
|
29
|
+
}
|
|
30
|
+
`}</style>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
Dismiss.propTypes = {
|
|
35
|
+
dataTest: PropTypes.string.isRequired,
|
|
36
|
+
onClick: PropTypes.func.isRequired,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { Dismiss }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('an AlertBar with onHidden handler is rendered', () => {
|
|
4
|
+
cy.visitStory('AlertBar', 'With on hidden')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Given('an AlertBar with disabled icon is rendered', () => {
|
|
8
|
+
cy.visitStory('AlertBar', 'Disabled icon')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Given('an AlertBar with custom icon is rendered', () => {
|
|
12
|
+
cy.visitStory('AlertBar', 'Custom icon')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
Given('an AlertBar with a message is rendered', () => {
|
|
16
|
+
cy.visitStory('AlertBar', 'With message')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
Given('an AlertBar with permanent is rendered', () => {
|
|
20
|
+
cy.visitStory('AlertBar', 'Permanent')
|
|
21
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
When('the Alertbar is not rendered', () => {
|
|
25
|
+
cy.wait(8000)
|
|
26
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('not.exist')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
Then('the icon will be visible', () => {
|
|
30
|
+
cy.get('[data-test="dhis2-uicore-alertbar-icon"]').should('be.visible')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
Then('the icon will not be rendered', () => {
|
|
34
|
+
cy.get('[data-test="dhis2-uicore-alertbar-icon"]').should('not.exist')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
Then('the custom icon will be visible', () => {
|
|
38
|
+
cy.get('[data-test="dhis2-uicore-alertbar-icon"]')
|
|
39
|
+
.contains('Custom icon')
|
|
40
|
+
.should('be.visible')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
Then('the message will be visible', () => {
|
|
44
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]')
|
|
45
|
+
.contains('With a message')
|
|
46
|
+
.should('be.visible')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
Then('the AlertBar will be visible', () => {
|
|
50
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
|
|
51
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Feature: AlertBar API
|
|
2
|
+
|
|
3
|
+
Scenario: AlertBar icon shows by default
|
|
4
|
+
Given a default AlertBar is rendered
|
|
5
|
+
Then the icon will be visible
|
|
6
|
+
|
|
7
|
+
Scenario: AlertBar icon can be hidden
|
|
8
|
+
Given an AlertBar with disabled icon is rendered
|
|
9
|
+
Then the icon will not be rendered
|
|
10
|
+
|
|
11
|
+
Scenario: Custom AlertBar icon
|
|
12
|
+
Given an AlertBar with custom icon is rendered
|
|
13
|
+
Then the custom icon will be visible
|
|
14
|
+
|
|
15
|
+
Scenario: Standard AlertBar with a message
|
|
16
|
+
Given an AlertBar with a message is rendered
|
|
17
|
+
Then the message will be visible
|
|
18
|
+
|
|
19
|
+
Scenario: The AlertBar renders with a permanent flag
|
|
20
|
+
Given an AlertBar with permanent is rendered
|
|
21
|
+
When the default duration has passed
|
|
22
|
+
Then the AlertBar will be visible
|
|
23
|
+
|
|
24
|
+
Scenario: AlertBar will call the onHidden cb when hidden
|
|
25
|
+
Given an AlertBar with onHidden handler is rendered
|
|
26
|
+
When the default duration has passed
|
|
27
|
+
Then the AlertBar is not rendered
|
|
28
|
+
Then the onHidden handler is called
|
|
29
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { When, Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a default AlertBar is rendered', () => {
|
|
4
|
+
cy.visitStory('AlertBar', 'Default')
|
|
5
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
Given('the AlertBar is rendered', () => {
|
|
9
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('exist')
|
|
10
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
Then('the AlertBar is not rendered', () => {
|
|
14
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('not.exist')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
When('the default duration has passed', () => {
|
|
18
|
+
cy.wait(8000)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
Then('the onHidden handler is called', () => {
|
|
22
|
+
cy.window().should((win) => {
|
|
23
|
+
expect(win.onHidden).to.be.calledOnce
|
|
24
|
+
expect(win.onHidden).to.be.calledWith({}, null)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a permanent AlertBar with hidden and onHidden is rendered', () => {
|
|
4
|
+
cy.visitStory('AlertBar', 'Hidden prop')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Given('an AlertBar with a custom duration is rendered', () => {
|
|
8
|
+
cy.visitStory('AlertBar', 'Custom duration')
|
|
9
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
Given('a permanent AlertBar with actions is rendered', () => {
|
|
13
|
+
cy.visitStory('AlertBar', 'Permanent with actions')
|
|
14
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
When('the user clicks on the "Cancel" button', () => {
|
|
18
|
+
cy.get(
|
|
19
|
+
'[data-test="dhis2-uicore-alertbar-action"]:contains("Cancel")'
|
|
20
|
+
).click()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
When('the user click a button which toggles the hidden prop to false', () => {
|
|
24
|
+
cy.get('button:contains("Show")').click()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
When('the user click a button which toggles the hidden prop to true', () => {
|
|
28
|
+
cy.get('button:contains("Hide")').click()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
When('the custom duration has passed', () => {
|
|
32
|
+
cy.wait(2000)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
Then('the onHidden handler is not called', () => {
|
|
36
|
+
cy.window().should((win) => {
|
|
37
|
+
expect(win.onHidden).to.not.be.called
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Feature: Hiding the AlertBar
|
|
2
|
+
|
|
3
|
+
Scenario: AlertBar hides automatically after the default time
|
|
4
|
+
Given a default AlertBar is rendered
|
|
5
|
+
When the default duration has passed
|
|
6
|
+
Then the AlertBar is not rendered
|
|
7
|
+
|
|
8
|
+
Scenario: AlertBars hides automatically after a custom time
|
|
9
|
+
Given an AlertBar with a custom duration is rendered
|
|
10
|
+
When the custom duration has passed
|
|
11
|
+
Then the AlertBar is not rendered
|
|
12
|
+
|
|
13
|
+
Scenario: The user clicks the "Cancel" button
|
|
14
|
+
Given a permanent AlertBar with actions is rendered
|
|
15
|
+
When the user clicks on the "Cancel" button
|
|
16
|
+
Then the AlertBar is not rendered
|
|
17
|
+
|
|
18
|
+
Scenario: The hidden prop is toggled
|
|
19
|
+
Given a permanent AlertBar with hidden and onHidden is rendered
|
|
20
|
+
Then the AlertBar is not rendered
|
|
21
|
+
And the onHidden handler is not called
|
|
22
|
+
When the user click a button which toggles the hidden prop to false
|
|
23
|
+
Then the AlertBar is rendered
|
|
24
|
+
When the user click a button which toggles the hidden prop to true
|
|
25
|
+
Then the AlertBar is not rendered
|
|
26
|
+
And the onHidden handler is called
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { mutuallyExclusive } from '@dhis2/prop-types'
|
|
2
|
+
import { spacers, colors } from '@dhis2/ui-constants'
|
|
3
|
+
import {
|
|
4
|
+
IconErrorFilled24,
|
|
5
|
+
IconInfoFilled24,
|
|
6
|
+
IconWarningFilled24,
|
|
7
|
+
IconCheckmark24,
|
|
8
|
+
} from '@dhis2/ui-icons'
|
|
9
|
+
import PropTypes from 'prop-types'
|
|
10
|
+
import React from 'react'
|
|
11
|
+
|
|
12
|
+
const StatusIcon = ({ error, warning, valid, info, defaultTo = null }) => {
|
|
13
|
+
if (error) {
|
|
14
|
+
return <IconErrorFilled24 color={colors.white} />
|
|
15
|
+
}
|
|
16
|
+
if (warning) {
|
|
17
|
+
return <IconWarningFilled24 color={colors.yellow900} />
|
|
18
|
+
}
|
|
19
|
+
if (valid) {
|
|
20
|
+
return <IconCheckmark24 color={colors.white} />
|
|
21
|
+
}
|
|
22
|
+
if (info) {
|
|
23
|
+
return <IconInfoFilled24 color={colors.white} />
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return defaultTo
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
StatusIcon.propTypes = {
|
|
30
|
+
defaultTo: PropTypes.element,
|
|
31
|
+
error: PropTypes.bool,
|
|
32
|
+
info: PropTypes.bool,
|
|
33
|
+
valid: PropTypes.bool,
|
|
34
|
+
warning: PropTypes.bool,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const Icon = ({ icon, success, warning, critical, info, dataTest }) => {
|
|
38
|
+
if (icon === false) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div data-test={dataTest}>
|
|
44
|
+
{React.isValidElement(icon) ? (
|
|
45
|
+
icon
|
|
46
|
+
) : (
|
|
47
|
+
<StatusIcon
|
|
48
|
+
valid={success}
|
|
49
|
+
error={critical}
|
|
50
|
+
warning={warning}
|
|
51
|
+
info={info}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
<style jsx>{`
|
|
55
|
+
div {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
margin-inline-end: ${spacers.dp12};
|
|
59
|
+
}
|
|
60
|
+
`}</style>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const iconPropType = PropTypes.oneOfType([PropTypes.bool, PropTypes.element])
|
|
66
|
+
const alertStatePropType = mutuallyExclusive(
|
|
67
|
+
['success', 'warning', 'critical', 'info'],
|
|
68
|
+
PropTypes.bool
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
Icon.propTypes = {
|
|
72
|
+
dataTest: PropTypes.string.isRequired,
|
|
73
|
+
critical: alertStatePropType,
|
|
74
|
+
icon: iconPropType,
|
|
75
|
+
info: alertStatePropType,
|
|
76
|
+
success: alertStatePropType,
|
|
77
|
+
warning: alertStatePropType,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { Icon, iconPropType }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AlertBar } from './alert-bar.js'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
const Message = ({ children }) => (
|
|
5
|
+
<div>
|
|
6
|
+
{children}
|
|
7
|
+
<style jsx>{`
|
|
8
|
+
div {
|
|
9
|
+
flex-grow: 1;
|
|
10
|
+
}
|
|
11
|
+
`}</style>
|
|
12
|
+
</div>
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
Message.propTypes = {
|
|
16
|
+
children: PropTypes.string.isRequired,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Message }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { AlertBar } from '../alert-bar/index.js'
|
|
3
|
+
import { AlertStack } from './alert-stack.js'
|
|
4
|
+
|
|
5
|
+
export default { title: 'AlertStack' }
|
|
6
|
+
|
|
7
|
+
export const Default = () => (
|
|
8
|
+
<AlertStack>
|
|
9
|
+
<AlertBar permanent>Message</AlertBar>
|
|
10
|
+
<AlertBar permanent>Message</AlertBar>
|
|
11
|
+
<AlertBar permanent>Message</AlertBar>
|
|
12
|
+
</AlertStack>
|
|
13
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { layers } from '@dhis2/ui-constants'
|
|
2
|
+
import { Portal } from '@dhis2-ui/portal'
|
|
3
|
+
import cx from 'classnames'
|
|
4
|
+
import PropTypes from 'prop-types'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
|
|
7
|
+
export const AlertStack = ({
|
|
8
|
+
className,
|
|
9
|
+
children,
|
|
10
|
+
dataTest = 'dhis2-uicore-alertstack',
|
|
11
|
+
}) => (
|
|
12
|
+
<Portal>
|
|
13
|
+
<div className={cx(className)} data-test={dataTest}>
|
|
14
|
+
{children}
|
|
15
|
+
<style jsx>{`
|
|
16
|
+
div {
|
|
17
|
+
position: fixed;
|
|
18
|
+
top: auto;
|
|
19
|
+
inset-inline-end: auto;
|
|
20
|
+
bottom: 0;
|
|
21
|
+
inset-inline-start: 50%;
|
|
22
|
+
transform: translateX(-50%);
|
|
23
|
+
|
|
24
|
+
z-index: ${layers.alert};
|
|
25
|
+
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column-reverse;
|
|
28
|
+
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
div:dir(rtl) {
|
|
33
|
+
transform: translateX(50%);
|
|
34
|
+
}
|
|
35
|
+
`}</style>
|
|
36
|
+
</div>
|
|
37
|
+
</Portal>
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
AlertStack.propTypes = {
|
|
41
|
+
children: PropTypes.node,
|
|
42
|
+
className: PropTypes.string,
|
|
43
|
+
dataTest: PropTypes.string,
|
|
44
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React, { useEffect, useState, useRef } from 'react'
|
|
2
|
+
import { AlertBar } from '../alert-bar/index.js'
|
|
3
|
+
import { AlertStack } from './alert-stack.js'
|
|
4
|
+
|
|
5
|
+
const VARIANTS = ['info', 'success', 'warning', 'critical']
|
|
6
|
+
|
|
7
|
+
const description = `
|
|
8
|
+
A container for Alert Bars.
|
|
9
|
+
|
|
10
|
+
_**Note:** The demos on this page may be slow._
|
|
11
|
+
|
|
12
|
+
\`\`\`js
|
|
13
|
+
import { AlertStack } from '@dhis2/ui'
|
|
14
|
+
\`\`\`
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
title: 'Alert Stack',
|
|
19
|
+
component: AlertStack,
|
|
20
|
+
// Use an iframe in docs to contain 'portal'
|
|
21
|
+
parameters: {
|
|
22
|
+
docs: {
|
|
23
|
+
inlineStories: false,
|
|
24
|
+
iframeHeight: '300px',
|
|
25
|
+
description: { component: description },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Default = (args) => (
|
|
31
|
+
<AlertStack {...args}>
|
|
32
|
+
<AlertBar permanent>First notification - I am at the bottom</AlertBar>
|
|
33
|
+
<AlertBar permanent critical>
|
|
34
|
+
Second notification
|
|
35
|
+
</AlertBar>
|
|
36
|
+
<AlertBar permanent warning>
|
|
37
|
+
Third notification
|
|
38
|
+
</AlertBar>
|
|
39
|
+
<AlertBar permanent success>
|
|
40
|
+
Fourth notification - I am at the top
|
|
41
|
+
</AlertBar>
|
|
42
|
+
</AlertStack>
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
export const Interactive = (args) => {
|
|
46
|
+
const [alerts, setAlerts] = useState([])
|
|
47
|
+
const nextId = useRef(0)
|
|
48
|
+
const addAlert = () => {
|
|
49
|
+
const id = nextId.current++
|
|
50
|
+
const variant = VARIANTS[id % VARIANTS.length]
|
|
51
|
+
setAlerts((current) => [
|
|
52
|
+
...current,
|
|
53
|
+
{ id, variant, message: `Alert #${id} (${variant})` },
|
|
54
|
+
])
|
|
55
|
+
}
|
|
56
|
+
const removeAlert = (id) => {
|
|
57
|
+
setAlerts((current) => current.filter((a) => a.id !== id))
|
|
58
|
+
}
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<button
|
|
62
|
+
onClick={addAlert}
|
|
63
|
+
style={{
|
|
64
|
+
padding: '8px 16px',
|
|
65
|
+
fontSize: '14px',
|
|
66
|
+
cursor: 'pointer',
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
Add alert
|
|
70
|
+
</button>
|
|
71
|
+
<AlertStack {...args}>
|
|
72
|
+
{alerts.map(({ id, variant, message }) => (
|
|
73
|
+
<AlertBar
|
|
74
|
+
key={id}
|
|
75
|
+
{...{ [variant]: true }}
|
|
76
|
+
onHidden={() => removeAlert(id)}
|
|
77
|
+
>
|
|
78
|
+
{message}
|
|
79
|
+
</AlertBar>
|
|
80
|
+
))}
|
|
81
|
+
</AlertStack>
|
|
82
|
+
</>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const RTL = (args) => {
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
document.body.dir = 'rtl'
|
|
89
|
+
return () => {
|
|
90
|
+
document.body.dir = 'ltr'
|
|
91
|
+
}
|
|
92
|
+
}, [])
|
|
93
|
+
return (
|
|
94
|
+
<div dir="rtl">
|
|
95
|
+
<AlertStack {...args}>
|
|
96
|
+
<AlertBar permanent>
|
|
97
|
+
First notification - I am at the bottom
|
|
98
|
+
</AlertBar>
|
|
99
|
+
<AlertBar permanent critical>
|
|
100
|
+
Second notification
|
|
101
|
+
</AlertBar>
|
|
102
|
+
<AlertBar permanent warning>
|
|
103
|
+
Third notification
|
|
104
|
+
</AlertBar>
|
|
105
|
+
<AlertBar permanent success>
|
|
106
|
+
Fourth notification - I am at the top
|
|
107
|
+
</AlertBar>
|
|
108
|
+
</AlertStack>
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('an AlertStack with multiple AlertBars is rendered', () => {
|
|
4
|
+
cy.visitStory('Alertstack', 'Default')
|
|
5
|
+
cy.get('[data-test="dhis2-uicore-alertstack"]').should('be.visible')
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
Then('the AlertBars will be visible', () => {
|
|
9
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('have.length', 3)
|
|
10
|
+
cy.get('[data-test="dhis2-uicore-alertbar"]').should('be.visible')
|
|
11
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AlertStack } from './alert-stack.js'
|
package/src/index.js
ADDED