@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +36 -295
  2. package/es/Alert/{index.js → v1/index.js} +3 -3
  3. package/es/Alert/v2/index.js +263 -0
  4. package/es/Alert/v2/props.js +26 -0
  5. package/es/Alert/v2/styles.js +134 -0
  6. package/es/{index.js → exports/a.js} +1 -1
  7. package/{src/index.ts → es/exports/b.js} +1 -2
  8. package/lib/Alert/{index.js → v1/index.js} +9 -9
  9. package/lib/Alert/v2/index.js +270 -0
  10. package/lib/Alert/v2/props.js +31 -0
  11. package/lib/Alert/v2/styles.js +140 -0
  12. package/lib/{index.js → exports/a.js} +2 -2
  13. package/lib/exports/b.js +12 -0
  14. package/package.json +44 -22
  15. package/src/Alert/{index.tsx → v1/index.tsx} +4 -4
  16. package/src/Alert/v2/README.md +249 -0
  17. package/src/Alert/v2/index.tsx +349 -0
  18. package/src/Alert/v2/props.ts +148 -0
  19. package/src/Alert/v2/styles.ts +137 -0
  20. package/src/exports/a.ts +25 -0
  21. package/src/exports/b.ts +25 -0
  22. package/tsconfig.build.tsbuildinfo +1 -1
  23. package/types/Alert/{index.d.ts → v1/index.d.ts} +1 -1
  24. package/types/Alert/v1/index.d.ts.map +1 -0
  25. package/types/Alert/v1/props.d.ts.map +1 -0
  26. package/types/Alert/v1/styles.d.ts.map +1 -0
  27. package/types/Alert/v1/theme.d.ts.map +1 -0
  28. package/types/Alert/v2/index.d.ts +73 -0
  29. package/types/Alert/v2/index.d.ts.map +1 -0
  30. package/types/Alert/v2/props.d.ts +92 -0
  31. package/types/Alert/v2/props.d.ts.map +1 -0
  32. package/types/Alert/v2/styles.d.ts +15 -0
  33. package/types/Alert/v2/styles.d.ts.map +1 -0
  34. package/types/exports/a.d.ts +3 -0
  35. package/types/exports/a.d.ts.map +1 -0
  36. package/types/exports/b.d.ts +3 -0
  37. package/types/exports/b.d.ts.map +1 -0
  38. package/types/Alert/index.d.ts.map +0 -1
  39. package/types/Alert/props.d.ts.map +0 -1
  40. package/types/Alert/styles.d.ts.map +0 -1
  41. package/types/Alert/theme.d.ts.map +0 -1
  42. package/types/index.d.ts +0 -3
  43. package/types/index.d.ts.map +0 -1
  44. /package/es/Alert/{props.js → v1/props.js} +0 -0
  45. /package/es/Alert/{styles.js → v1/styles.js} +0 -0
  46. /package/es/Alert/{theme.js → v1/theme.js} +0 -0
  47. /package/lib/Alert/{props.js → v1/props.js} +0 -0
  48. /package/lib/Alert/{styles.js → v1/styles.js} +0 -0
  49. /package/lib/Alert/{theme.js → v1/theme.js} +0 -0
  50. /package/src/Alert/{README.md → v1/README.md} +0 -0
  51. /package/src/Alert/{props.ts → v1/props.ts} +0 -0
  52. /package/src/Alert/{styles.ts → v1/styles.ts} +0 -0
  53. /package/src/Alert/{theme.ts → v1/theme.ts} +0 -0
  54. /package/types/Alert/{props.d.ts → v1/props.d.ts} +0 -0
  55. /package/types/Alert/{styles.d.ts → v1/styles.d.ts} +0 -0
  56. /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 }