@instructure/ui-alerts 11.6.0 → 11.6.1-snapshot-129
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/CHANGELOG.md +36 -295
- package/es/Alert/{index.js → v1/index.js} +3 -3
- package/es/Alert/v2/index.js +263 -0
- package/es/Alert/v2/props.js +26 -0
- package/es/Alert/v2/styles.js +134 -0
- package/es/{index.js → exports/a.js} +1 -1
- package/{src/index.ts → es/exports/b.js} +1 -2
- package/lib/Alert/{index.js → v1/index.js} +9 -9
- package/lib/Alert/v2/index.js +270 -0
- package/lib/Alert/v2/props.js +31 -0
- package/lib/Alert/v2/styles.js +140 -0
- package/lib/{index.js → exports/a.js} +2 -2
- package/lib/exports/b.js +12 -0
- package/package.json +44 -22
- package/src/Alert/{index.tsx → v1/index.tsx} +4 -4
- package/src/Alert/v2/README.md +249 -0
- package/src/Alert/v2/index.tsx +349 -0
- package/src/Alert/v2/props.ts +148 -0
- package/src/Alert/v2/styles.ts +137 -0
- package/src/exports/a.ts +25 -0
- package/src/exports/b.ts +25 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Alert/{index.d.ts → v1/index.d.ts} +1 -1
- package/types/Alert/v1/index.d.ts.map +1 -0
- package/types/Alert/v1/props.d.ts.map +1 -0
- package/types/Alert/v1/styles.d.ts.map +1 -0
- package/types/Alert/v1/theme.d.ts.map +1 -0
- package/types/Alert/v2/index.d.ts +73 -0
- package/types/Alert/v2/index.d.ts.map +1 -0
- package/types/Alert/v2/props.d.ts +92 -0
- package/types/Alert/v2/props.d.ts.map +1 -0
- package/types/Alert/v2/styles.d.ts +15 -0
- package/types/Alert/v2/styles.d.ts.map +1 -0
- package/types/exports/a.d.ts +3 -0
- package/types/exports/a.d.ts.map +1 -0
- package/types/exports/b.d.ts +3 -0
- package/types/exports/b.d.ts.map +1 -0
- package/types/Alert/index.d.ts.map +0 -1
- package/types/Alert/props.d.ts.map +0 -1
- package/types/Alert/styles.d.ts.map +0 -1
- package/types/Alert/theme.d.ts.map +0 -1
- package/types/index.d.ts +0 -3
- package/types/index.d.ts.map +0 -1
- /package/es/Alert/{props.js → v1/props.js} +0 -0
- /package/es/Alert/{styles.js → v1/styles.js} +0 -0
- /package/es/Alert/{theme.js → v1/theme.js} +0 -0
- /package/lib/Alert/{props.js → v1/props.js} +0 -0
- /package/lib/Alert/{styles.js → v1/styles.js} +0 -0
- /package/lib/Alert/{theme.js → v1/theme.js} +0 -0
- /package/src/Alert/{README.md → v1/README.md} +0 -0
- /package/src/Alert/{props.ts → v1/props.ts} +0 -0
- /package/src/Alert/{styles.ts → v1/styles.ts} +0 -0
- /package/src/Alert/{theme.ts → v1/theme.ts} +0 -0
- /package/types/Alert/{props.d.ts → v1/props.d.ts} +0 -0
- /package/types/Alert/{styles.d.ts → v1/styles.d.ts} +0 -0
- /package/types/Alert/{theme.d.ts → v1/theme.d.ts} +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
---
|
|
2
|
+
describes: Alert
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The Alert component can be used to notify the user. It supports several
|
|
6
|
+
variants to provide context to the message.
|
|
7
|
+
|
|
8
|
+
Alert can optionally render as a dismissible 'dialog' with a close button.
|
|
9
|
+
|
|
10
|
+
The `margin` prop can be added to give
|
|
11
|
+
space above or below the alert.
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
---
|
|
15
|
+
type: example
|
|
16
|
+
---
|
|
17
|
+
<div>
|
|
18
|
+
<Alert
|
|
19
|
+
variant="success"
|
|
20
|
+
renderCloseButtonLabel="Close"
|
|
21
|
+
margin="small"
|
|
22
|
+
transition="none"
|
|
23
|
+
variantScreenReaderLabel="Success, "
|
|
24
|
+
>
|
|
25
|
+
Sample success alert text. I will close w/o a transition out if you close me
|
|
26
|
+
</Alert>
|
|
27
|
+
<Alert
|
|
28
|
+
variant="info"
|
|
29
|
+
renderCloseButtonLabel="Close"
|
|
30
|
+
margin="small"
|
|
31
|
+
variantScreenReaderLabel="Information, "
|
|
32
|
+
>
|
|
33
|
+
Sample info text. I will fade out if you close me.
|
|
34
|
+
</Alert>
|
|
35
|
+
<Alert
|
|
36
|
+
variant="error"
|
|
37
|
+
renderCloseButtonLabel="Close"
|
|
38
|
+
margin="small"
|
|
39
|
+
variantScreenReaderLabel="Error, "
|
|
40
|
+
>
|
|
41
|
+
Sample error text that continues for a while
|
|
42
|
+
to demonstrate what happens when the content stretches over
|
|
43
|
+
several lines. It really does take a lot of prose to get the
|
|
44
|
+
text to wrap when you are on a high resolution screen.
|
|
45
|
+
</Alert>
|
|
46
|
+
<Alert
|
|
47
|
+
variant="warning"
|
|
48
|
+
margin="small"
|
|
49
|
+
variantScreenReaderLabel="Warning, "
|
|
50
|
+
>
|
|
51
|
+
Sample warning text. This alert is not dismissible and cannot be closed.
|
|
52
|
+
</Alert>
|
|
53
|
+
</div>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The `timeout` prop can be used to automatically dismiss an alert after a time.
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
---
|
|
60
|
+
type: example
|
|
61
|
+
---
|
|
62
|
+
<Alert
|
|
63
|
+
variant="info"
|
|
64
|
+
margin="small"
|
|
65
|
+
timeout={5000}
|
|
66
|
+
variantScreenReaderLabel="Information, "
|
|
67
|
+
>
|
|
68
|
+
Sample info text. I will fade out after 5 seconds
|
|
69
|
+
</Alert>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Given a `liveRegion` property, Alerts will guarantee a screenreader will announce their text.
|
|
73
|
+
Use `liveRegionPoliteness` to choose an `aria-live` politeness setting of either `polite`
|
|
74
|
+
or `assertive` (default). Use `isLiveRegionAtomic` to choose an `aria-atomic` setting
|
|
75
|
+
of either `true` or `false` (default).
|
|
76
|
+
|
|
77
|
+
Due to a bug in some screen readers, the live region element should be static, either through
|
|
78
|
+
server rendering or included in the static HTML file for the app. The Alert component will
|
|
79
|
+
ensure that element has the correct ARIA attributes.
|
|
80
|
+
|
|
81
|
+
For more information about live regions, see
|
|
82
|
+
[this MDN article](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions).
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
---
|
|
86
|
+
type: example
|
|
87
|
+
---
|
|
88
|
+
const Example = () => {
|
|
89
|
+
const [alerts, setAlerts] = useState([])
|
|
90
|
+
const [count, setcount] = useState(0)
|
|
91
|
+
|
|
92
|
+
const variants = ['info', 'success', 'warning', 'error']
|
|
93
|
+
|
|
94
|
+
const addAlert = () => {
|
|
95
|
+
const variant = variants[count % variants.length]
|
|
96
|
+
const politeness = Math.random() < 0.5 ? 'polite' : 'assertive'
|
|
97
|
+
setAlerts([
|
|
98
|
+
...alerts,
|
|
99
|
+
{
|
|
100
|
+
key: count,
|
|
101
|
+
variant,
|
|
102
|
+
politeness
|
|
103
|
+
}
|
|
104
|
+
])
|
|
105
|
+
setcount(count + 1)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const closeAlert = (key) =>
|
|
109
|
+
setAlerts(alerts.filter((alert) => alert.key !== key))
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div>
|
|
113
|
+
<Button onClick={addAlert}>Add Alert</Button>
|
|
114
|
+
{alerts.map((alert) => {
|
|
115
|
+
return (
|
|
116
|
+
<View key={alert.key} display="block" margin="small 0">
|
|
117
|
+
<Alert
|
|
118
|
+
variant={alert.variant}
|
|
119
|
+
renderCloseButtonLabel="Close"
|
|
120
|
+
onDismiss={() => closeAlert(alert.key)}
|
|
121
|
+
liveRegion={() => document.getElementById('flash-messages')}
|
|
122
|
+
liveRegionPoliteness={alert.politeness}
|
|
123
|
+
margin="small 0"
|
|
124
|
+
>
|
|
125
|
+
This is {alert.politeness === 'polite' ? 'a' : 'an'}{' '}
|
|
126
|
+
{alert.politeness} {alert.variant} alert
|
|
127
|
+
</Alert>
|
|
128
|
+
</View>
|
|
129
|
+
)
|
|
130
|
+
})}
|
|
131
|
+
</div>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
render(<Example />)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Alerts can be used to emit screenreader only messages too
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
---
|
|
142
|
+
type: example
|
|
143
|
+
---
|
|
144
|
+
const Example = () => {
|
|
145
|
+
const [message, setMessage] = useState(null)
|
|
146
|
+
const [count, setCount] = useState(1)
|
|
147
|
+
|
|
148
|
+
const changeMessage = () => {
|
|
149
|
+
setMessage(`this is message ${count}`)
|
|
150
|
+
setCount(count + 1)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const clearMessage = () => {
|
|
154
|
+
setMessage(null)
|
|
155
|
+
setCount(count + 1)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div>
|
|
160
|
+
<Button onClick={changeMessage}>Change Message</Button>
|
|
161
|
+
<Button onClick={clearMessage} margin="0 0 0 small">
|
|
162
|
+
Clear Message
|
|
163
|
+
</Button>
|
|
164
|
+
<Alert
|
|
165
|
+
liveRegion={() => document.getElementById('flash-messages')}
|
|
166
|
+
isLiveRegionAtomic
|
|
167
|
+
screenReaderOnly
|
|
168
|
+
>
|
|
169
|
+
{message}
|
|
170
|
+
</Alert>
|
|
171
|
+
</div>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
render(<Example />)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
When Alerts are used inline, the shadow can be removed with the `hasShadow` property.
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
---
|
|
182
|
+
type: example
|
|
183
|
+
---
|
|
184
|
+
<View as="div" background="primary" padding="large">
|
|
185
|
+
<View
|
|
186
|
+
as="div"
|
|
187
|
+
background="primary"
|
|
188
|
+
padding="small medium"
|
|
189
|
+
borderWidth="small"
|
|
190
|
+
borderRadius="small"
|
|
191
|
+
margin="x-small 0"
|
|
192
|
+
>
|
|
193
|
+
{lorem.paragraph()}
|
|
194
|
+
</View>
|
|
195
|
+
<Alert
|
|
196
|
+
variant="info"
|
|
197
|
+
margin="x-small 0"
|
|
198
|
+
renderCloseButtonLabel="Close"
|
|
199
|
+
hasShadow={false}
|
|
200
|
+
>
|
|
201
|
+
This is an inline Alert, so it shouldn't have a shadow.
|
|
202
|
+
</Alert>
|
|
203
|
+
<View
|
|
204
|
+
as="div"
|
|
205
|
+
background="primary"
|
|
206
|
+
padding="small medium"
|
|
207
|
+
borderWidth="small"
|
|
208
|
+
borderRadius="small"
|
|
209
|
+
margin="x-small 0"
|
|
210
|
+
>
|
|
211
|
+
{lorem.paragraph()}
|
|
212
|
+
</View>
|
|
213
|
+
</View>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Guidelines
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
---
|
|
220
|
+
type: embed
|
|
221
|
+
---
|
|
222
|
+
<Guidelines>
|
|
223
|
+
<Figure recommendation="yes" title="Do">
|
|
224
|
+
<Figure.Item>Use the Info alert to notify the user of more information</Figure.Item>
|
|
225
|
+
<Figure.Item>Use the Error alert to notify user of an error</Figure.Item>
|
|
226
|
+
<Figure.Item>Use the Warning alert to notify user of a warning</Figure.Item>
|
|
227
|
+
<Figure.Item>Use the Success alert to notify user of a success event or action</Figure.Item>
|
|
228
|
+
<Figure.Item>Use the `variantScreenReaderLabel` prop to indicate the alert variant to screen reader users</Figure.Item>
|
|
229
|
+
</Figure>
|
|
230
|
+
<Figure recommendation="no" title="Don't">
|
|
231
|
+
<Figure.Item>Have alert messaging that is more than two lines long</Figure.Item>
|
|
232
|
+
<Figure.Item>Overuse alerts on the same page</Figure.Item>
|
|
233
|
+
</Figure>
|
|
234
|
+
</Guidelines>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
---
|
|
239
|
+
type: embed
|
|
240
|
+
---
|
|
241
|
+
<Guidelines>
|
|
242
|
+
<Figure recommendation="a11y" title="Accessibility">
|
|
243
|
+
<Figure.Item>If the alert requires user interaction to be dismissed, the alert should behave as a modal dialog. Focus should be set to the alert when it appears, remain in the alert until it is dismissed, and return to a logical place on the page when the alert is dismissed</Figure.Item>
|
|
244
|
+
<Figure.Item>aria-live="polite" alerts will only be announced if the user is not currently doing anything. Polite should be used in most situations involving live regions that present new info to users</Figure.Item>
|
|
245
|
+
<Figure.Item>aria-live="assertive" alerts will be announced to the user as soon as possible, but not necessarily immediately. Assertive should be used if there is information that a user must know about right away, for example, a warning message in a form that does validation on the fly</Figure.Item>
|
|
246
|
+
<Figure.Item>The aria-atomic=BOOLEAN is used to set whether or not the screen reader should always present the live region as a whole, even if only part of the region changes. The possible settings are: false or true. The default setting is false.</Figure.Item>
|
|
247
|
+
</Figure>
|
|
248
|
+
</Guidelines>
|
|
249
|
+
```
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { Fragment, Component } from 'react'
|
|
26
|
+
import ReactDOM from 'react-dom'
|
|
27
|
+
import keycode from 'keycode'
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
callRenderProp,
|
|
31
|
+
withDeterministicId,
|
|
32
|
+
passthroughProps
|
|
33
|
+
} from '@instructure/ui-react-utils'
|
|
34
|
+
import { CloseButton } from '@instructure/ui-buttons/latest'
|
|
35
|
+
import { View } from '@instructure/ui-view/latest'
|
|
36
|
+
import type { ViewOwnProps } from '@instructure/ui-view/latest'
|
|
37
|
+
import { ScreenReaderContent } from '@instructure/ui-a11y-content'
|
|
38
|
+
import {
|
|
39
|
+
InfoInstUIIcon,
|
|
40
|
+
XCircleInstUIIcon,
|
|
41
|
+
CircleCheckInstUIIcon,
|
|
42
|
+
TriangleAlertInstUIIcon
|
|
43
|
+
} from '@instructure/ui-icons'
|
|
44
|
+
import { Transition } from '@instructure/ui-motion'
|
|
45
|
+
import { logError as error } from '@instructure/console'
|
|
46
|
+
import { withStyle } from '@instructure/emotion'
|
|
47
|
+
|
|
48
|
+
import generateStyle from './styles'
|
|
49
|
+
|
|
50
|
+
import { allowedProps } from './props'
|
|
51
|
+
import type { AlertProps, AlertState } from './props'
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
---
|
|
55
|
+
category: components
|
|
56
|
+
---
|
|
57
|
+
**/
|
|
58
|
+
@withDeterministicId()
|
|
59
|
+
@withStyle(generateStyle)
|
|
60
|
+
class Alert extends Component<AlertProps, AlertState> {
|
|
61
|
+
static readonly componentId = 'Alert'
|
|
62
|
+
|
|
63
|
+
static allowedProps = allowedProps
|
|
64
|
+
static defaultProps = {
|
|
65
|
+
variant: 'info',
|
|
66
|
+
margin: 'x-small 0',
|
|
67
|
+
timeout: 0,
|
|
68
|
+
transition: 'fade',
|
|
69
|
+
open: true,
|
|
70
|
+
screenReaderOnly: false,
|
|
71
|
+
liveRegionPoliteness: 'assertive',
|
|
72
|
+
isLiveRegionAtomic: false,
|
|
73
|
+
children: null,
|
|
74
|
+
hasShadow: true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
constructor(props: AlertProps) {
|
|
78
|
+
super(props)
|
|
79
|
+
|
|
80
|
+
this.srid = this.props.deterministicId!()
|
|
81
|
+
this.state = {
|
|
82
|
+
open: true
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_timeouts: ReturnType<typeof setTimeout>[] = []
|
|
87
|
+
srid: string
|
|
88
|
+
|
|
89
|
+
variantUI = {
|
|
90
|
+
error: XCircleInstUIIcon,
|
|
91
|
+
info: InfoInstUIIcon,
|
|
92
|
+
success: CircleCheckInstUIIcon,
|
|
93
|
+
warning: TriangleAlertInstUIIcon
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ref: Element | null = null
|
|
97
|
+
|
|
98
|
+
handleRef = (el: Element | null) => {
|
|
99
|
+
this.ref = el
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
handleTimeout = () => {
|
|
103
|
+
if (this.props.timeout! > 0) {
|
|
104
|
+
this._timeouts.push(
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
this.close()
|
|
107
|
+
}, this.props.timeout)
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
clearTimeouts() {
|
|
113
|
+
this._timeouts.forEach((timeout) => clearTimeout(timeout))
|
|
114
|
+
this._timeouts = []
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
onExitTransition = () => {
|
|
118
|
+
if (this.props.onDismiss) {
|
|
119
|
+
this.props.onDismiss()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
close = () => {
|
|
124
|
+
this.clearTimeouts()
|
|
125
|
+
this.removeScreenreaderAlert()
|
|
126
|
+
this.setState({ open: false }, () => {
|
|
127
|
+
if (
|
|
128
|
+
this.props.onDismiss &&
|
|
129
|
+
(this.props.transition === 'none' || this.props.screenReaderOnly)
|
|
130
|
+
) {
|
|
131
|
+
this.props.onDismiss()
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// duck type for a dom node
|
|
137
|
+
isDOMNode(n: Element | null | undefined) {
|
|
138
|
+
return n && typeof n === 'object' && n.nodeType === 1
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getLiveRegion() {
|
|
142
|
+
const lr =
|
|
143
|
+
typeof this.props.liveRegion === 'function'
|
|
144
|
+
? this.props.liveRegion()
|
|
145
|
+
: this.props.liveRegion
|
|
146
|
+
|
|
147
|
+
return this.isDOMNode(lr) ? lr : null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
initLiveRegion(liveRegion: Element) {
|
|
151
|
+
error(
|
|
152
|
+
liveRegion.getAttribute('role') === 'alert',
|
|
153
|
+
`[Alert] live region must have role='alert' set on page load in order to announce content`
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if (liveRegion) {
|
|
157
|
+
liveRegion.setAttribute('aria-live', this.props.liveRegionPoliteness!)
|
|
158
|
+
// indicates what notifications the user agent will trigger when the
|
|
159
|
+
// accessibility tree within a live region is modified.
|
|
160
|
+
// additions: elements are added, text: Text content is added
|
|
161
|
+
liveRegion.setAttribute('aria-relevant', 'additions text')
|
|
162
|
+
liveRegion.setAttribute(
|
|
163
|
+
'aria-atomic',
|
|
164
|
+
`${this.props.isLiveRegionAtomic!}`
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
createScreenreaderContentNode() {
|
|
170
|
+
return (
|
|
171
|
+
<ScreenReaderContent>
|
|
172
|
+
{this.props.variantScreenReaderLabel || ''} {this.props.children}
|
|
173
|
+
</ScreenReaderContent>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
createScreenreaderAlert() {
|
|
178
|
+
const liveRegion = this.getLiveRegion()
|
|
179
|
+
if (liveRegion) {
|
|
180
|
+
const div = document.createElement('div')
|
|
181
|
+
div.setAttribute('id', this.srid)
|
|
182
|
+
|
|
183
|
+
liveRegion.appendChild(div)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
removeScreenreaderAlert() {
|
|
188
|
+
const liveRegion = this.getLiveRegion()
|
|
189
|
+
if (liveRegion) {
|
|
190
|
+
const div = document.getElementById(this.srid!)
|
|
191
|
+
if (div) {
|
|
192
|
+
// Accessibility attributes must be removed for the deletion of the node
|
|
193
|
+
// and then reapplied because JAWS/IE will not respect the
|
|
194
|
+
// "aria-relevant" attribute and read when the node is deleted if
|
|
195
|
+
// the attributes are in place
|
|
196
|
+
liveRegion.removeAttribute('aria-live')
|
|
197
|
+
liveRegion.removeAttribute('aria-relevant')
|
|
198
|
+
liveRegion.removeAttribute('aria-atomic')
|
|
199
|
+
|
|
200
|
+
this.initLiveRegion(liveRegion)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
handleKeyUp = (event: React.KeyboardEvent<ViewOwnProps>) => {
|
|
206
|
+
if (
|
|
207
|
+
this.props.renderCloseButtonLabel &&
|
|
208
|
+
event.keyCode === keycode.codes.esc
|
|
209
|
+
) {
|
|
210
|
+
this.close()
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
componentDidMount() {
|
|
215
|
+
this.props.makeStyles?.()
|
|
216
|
+
const liveRegion = this.getLiveRegion()
|
|
217
|
+
if (liveRegion) {
|
|
218
|
+
this.initLiveRegion(liveRegion)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this.handleTimeout()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
componentWillUnmount() {
|
|
225
|
+
this.clearTimeouts()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
componentDidUpdate(prevProps: AlertProps) {
|
|
229
|
+
this.props.makeStyles?.()
|
|
230
|
+
if (!!this.props.open === false && !!this.props.open !== !!prevProps.open) {
|
|
231
|
+
// this outside world is asking us to close the alert, which needs to
|
|
232
|
+
// take place internally so the transition runs
|
|
233
|
+
this.close()
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
renderIcon() {
|
|
238
|
+
const { renderCustomIcon, variant, styles } = this.props
|
|
239
|
+
const Icon = this.variantUI[variant!]
|
|
240
|
+
return (
|
|
241
|
+
<div css={styles?.icon}>
|
|
242
|
+
{renderCustomIcon ? callRenderProp(renderCustomIcon) : <Icon />}
|
|
243
|
+
</div>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
renderCloseButton() {
|
|
248
|
+
const closeButtonLabel =
|
|
249
|
+
this.props.renderCloseButtonLabel &&
|
|
250
|
+
callRenderProp(this.props.renderCloseButtonLabel)
|
|
251
|
+
|
|
252
|
+
return closeButtonLabel ? (
|
|
253
|
+
<div css={this.props.styles?.closeButton} key="closeButton">
|
|
254
|
+
<CloseButton
|
|
255
|
+
onClick={this.close}
|
|
256
|
+
size="small"
|
|
257
|
+
screenReaderLabel={closeButtonLabel}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
) : null
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
renderAlert() {
|
|
264
|
+
// prevent onDismiss from being passed to the View component
|
|
265
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
266
|
+
const {
|
|
267
|
+
margin,
|
|
268
|
+
styles,
|
|
269
|
+
children,
|
|
270
|
+
onDismiss,
|
|
271
|
+
variantScreenReaderLabel,
|
|
272
|
+
...props
|
|
273
|
+
} = this.props
|
|
274
|
+
return (
|
|
275
|
+
<View
|
|
276
|
+
{...passthroughProps({ ...props })}
|
|
277
|
+
as="div"
|
|
278
|
+
margin={margin}
|
|
279
|
+
css={styles?.alert}
|
|
280
|
+
onKeyUp={this.handleKeyUp}
|
|
281
|
+
elementRef={this.handleRef}
|
|
282
|
+
>
|
|
283
|
+
{this.renderIcon()}
|
|
284
|
+
<div css={styles?.content}>
|
|
285
|
+
{variantScreenReaderLabel && (
|
|
286
|
+
<span css={styles?.variantScreenReaderLabel}>
|
|
287
|
+
{variantScreenReaderLabel}
|
|
288
|
+
</span>
|
|
289
|
+
)}
|
|
290
|
+
{children}
|
|
291
|
+
</div>
|
|
292
|
+
{this.renderCloseButton()}
|
|
293
|
+
</View>
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
createScreenReaderPortal(liveRegion: Element) {
|
|
298
|
+
const { open } = this.state
|
|
299
|
+
|
|
300
|
+
return open
|
|
301
|
+
? ReactDOM.createPortal(
|
|
302
|
+
<div id={this.srid}>{this.createScreenreaderContentNode()}</div>,
|
|
303
|
+
liveRegion
|
|
304
|
+
)
|
|
305
|
+
: null
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
render() {
|
|
309
|
+
const liveRegion = this.getLiveRegion()
|
|
310
|
+
const screenReaderContent = liveRegion
|
|
311
|
+
? this.createScreenReaderPortal(liveRegion)
|
|
312
|
+
: null
|
|
313
|
+
// Don't render anything if screen reader only
|
|
314
|
+
if (this.props.screenReaderOnly) {
|
|
315
|
+
error(
|
|
316
|
+
!!this.getLiveRegion(),
|
|
317
|
+
`[Alert] The 'screenReaderOnly' prop must be used in conjunction with 'liveRegion'.`
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return screenReaderContent
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (this.props.transition === 'none') {
|
|
324
|
+
return this.state.open ? (
|
|
325
|
+
<Fragment>
|
|
326
|
+
{screenReaderContent}
|
|
327
|
+
{this.renderAlert()}
|
|
328
|
+
</Fragment>
|
|
329
|
+
) : null
|
|
330
|
+
}
|
|
331
|
+
return (
|
|
332
|
+
<Fragment>
|
|
333
|
+
{screenReaderContent}
|
|
334
|
+
<Transition
|
|
335
|
+
type={this.props.transition}
|
|
336
|
+
transitionOnMount
|
|
337
|
+
in={this.state.open}
|
|
338
|
+
unmountOnExit
|
|
339
|
+
onExited={this.onExitTransition}
|
|
340
|
+
>
|
|
341
|
+
{this.renderAlert()}
|
|
342
|
+
</Transition>
|
|
343
|
+
</Fragment>
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export default Alert
|
|
349
|
+
export { Alert }
|