@defra/flood-webchat 0.0.1-beta.4 → 0.0.1-beta.40

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 (47) hide show
  1. package/README.md +83 -1
  2. package/dist/client.js +1939 -416
  3. package/dist/client.js.map +1 -1
  4. package/dist/server.js +181 -241
  5. package/dist/server.js.map +1 -1
  6. package/main.scss +10 -6
  7. package/package.json +5 -2
  8. package/src/client/components/availability/availability.jsx +64 -55
  9. package/src/client/components/availability/availability.scss +15 -33
  10. package/src/client/components/chat/chat.jsx +199 -0
  11. package/src/client/components/chat/chat.scss +233 -0
  12. package/src/client/components/errorSummary/error-summary.jsx +34 -0
  13. package/src/client/components/message/message.jsx +20 -0
  14. package/src/client/components/panel/panel-footer.jsx +9 -0
  15. package/src/client/components/panel/panel-header.jsx +54 -14
  16. package/src/client/components/panel/panel.jsx +113 -72
  17. package/src/client/components/panel/panel.scss +63 -22
  18. package/src/client/components/screens/end-chat.jsx +77 -0
  19. package/src/client/components/screens/feedback.jsx +65 -0
  20. package/src/client/components/screens/pre-chat.jsx +50 -30
  21. package/src/client/components/screens/request-chat.jsx +171 -4
  22. package/src/client/components/screens/settings.jsx +97 -0
  23. package/src/client/components/screens/unavailable.jsx +23 -0
  24. package/src/client/components/skip-link.jsx +42 -0
  25. package/src/client/hooks/useFocusedElements.js +80 -0
  26. package/src/client/hooks/useTextareaAutosize.js +13 -0
  27. package/src/client/index.jsx +15 -1
  28. package/src/client/lib/agent-status-headline.js +17 -0
  29. package/src/client/lib/check-availability.js +1 -0
  30. package/src/client/lib/history.js +13 -0
  31. package/src/client/lib/message-notification.js +36 -0
  32. package/src/client/lib/transform-messages.js +34 -0
  33. package/src/client/scss/_objects.scss +33 -0
  34. package/src/client/scss/_settings.scss +80 -0
  35. package/src/client/scss/_tools.scss +33 -0
  36. package/src/client/scss/_utilities.scss +25 -0
  37. package/src/client/store/AppProvider.jsx +179 -0
  38. package/src/client/store/actions-map.js +143 -0
  39. package/src/client/store/constants.js +3 -0
  40. package/src/client/store/reducer.js +31 -0
  41. package/src/client/store/useApp.js +5 -0
  42. package/src/client/store/useChatSdk.js +37 -0
  43. package/src/server/index.js +15 -6
  44. package/src/server/lib/client.js +31 -35
  45. package/src/server/lib/utils.js +16 -8
  46. package/src/client/lib/external-stores.js +0 -4
  47. package/src/client/lib/external-sync-store.js +0 -42
@@ -0,0 +1,233 @@
1
+ @keyframes blink {
2
+ 50% {
3
+ fill: transparent
4
+ }
5
+ }
6
+
7
+ .wc-status {
8
+ display: flex;
9
+ position: relative;
10
+ padding: 15px;
11
+ @include mq ($from: tablet) {
12
+ padding: 10px;
13
+ }
14
+
15
+ &::after {
16
+ position: absolute;
17
+ content: "";
18
+ bottom: 0;
19
+ left: 15px;
20
+ right: 15px;
21
+ height: 0;
22
+ border-bottom: 1px solid $govuk-border-colour;
23
+ @include mq ($from: tablet) {
24
+ left: 10px;
25
+ right: 10px;
26
+ }
27
+ }
28
+
29
+ &__availability {
30
+ font-size: 16px;
31
+ margin-bottom: 0;
32
+ }
33
+
34
+ &__link {
35
+ @extend %wc-link-button;
36
+ margin-left: auto;
37
+ }
38
+ }
39
+
40
+ .wc-body[tabindex="0"]:focus-visible {
41
+ outline: 3px solid transparent;
42
+ outline-offset: -2px;
43
+ box-shadow:
44
+ inset 0 0 0 2px $govuk-focus-colour,
45
+ inset 0 0 0 4px govuk-colour('black'),
46
+ 0 0 0 1px $govuk-focus-colour;
47
+ }
48
+
49
+ .wc-chat {
50
+ @extend %govuk-body-s;
51
+ line-height: 1.25;
52
+ font-size: 14px;
53
+ overflow: auto;
54
+ list-style: none;
55
+ padding: 0;
56
+
57
+ &__message {
58
+ margin: 5px 15px 0;
59
+ @include mq ($from: tablet) {
60
+ margin-left: 10px;
61
+ margin-right: 10px;
62
+ }
63
+
64
+ &:first-child {
65
+ padding-top: 15px;
66
+ }
67
+
68
+ &.inbound {
69
+ text-align: right;
70
+ }
71
+ }
72
+
73
+ &__from {
74
+ line-height: 1.25;
75
+ color: govuk-colour('dark-grey');
76
+ margin-top: 2px;
77
+ margin-bottom: 2px;
78
+ }
79
+
80
+ &__text {
81
+ font-size: 16px;
82
+ padding: 10px 12.5px;
83
+ margin-bottom: 0;
84
+ display: inline-block;
85
+ border: 1px solid transparent;
86
+
87
+ @media (forced-colors: active) {
88
+ border-color: currentColor;
89
+ }
90
+
91
+ &.inbound {
92
+ color: govuk-colour('white');
93
+ background-color: govuk-colour('blue');
94
+ border-radius: 10px 0 10px 10px;
95
+ }
96
+
97
+ &.outbound {
98
+ background-color: govuk-colour('light-grey');
99
+ border-radius: 0 10px 10px;
100
+ }
101
+
102
+ svg {
103
+ display: block;
104
+ color: govuk-colour('dark-grey');
105
+ }
106
+
107
+ @media (forced-colors: active) {
108
+ svg {
109
+ color: currentColor;
110
+ }
111
+ }
112
+
113
+
114
+ svg circle {
115
+ animation: 1s blink infinite;
116
+ fill: currentColor;
117
+
118
+ &:nth-child(2) {
119
+ animation-delay: 250ms
120
+ }
121
+
122
+ &:nth-child(3) {
123
+ animation-delay: 500ms
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ .wc-form {
130
+ position: relative;
131
+ display: flex;
132
+ flex-wrap: nowrap;
133
+ align-items: baseline;
134
+ border-top: 1px solid govuk-colour('black');
135
+ border-bottom: 1px solid govuk-colour('black');
136
+
137
+ &__label {
138
+ position: absolute;
139
+ pointer-events: none;
140
+ font-size: 16px;
141
+ line-height: 20px;
142
+ margin: 20px;
143
+ @include mq ($from: tablet) {
144
+ margin: 18px 18px 19px;
145
+ }
146
+ left: 0;
147
+ bottom: 0;
148
+ color: $govuk-secondary-text-colour;
149
+ z-index: 3;
150
+ }
151
+
152
+ &__textarea {
153
+ @extend %govuk-body-s;
154
+ box-sizing: border-box;
155
+ min-height: auto;
156
+ font-size: 16px;
157
+ line-height: 20px;
158
+ max-height: 120px;
159
+ flex: 1 1 auto;
160
+ align-self: flex-end;
161
+ margin: 20px;
162
+ @include mq ($from: tablet) {
163
+ margin: 18px 18px 19px;
164
+ }
165
+ padding: 0;
166
+ border: 0;
167
+ height: 20px;
168
+ resize: none;
169
+ overflow-x: hidden;
170
+ overscroll-behavior: contain;
171
+
172
+ &:focus {
173
+ outline: none;
174
+ box-shadow: none;
175
+ z-index: 2;
176
+ }
177
+ }
178
+
179
+ &__button {
180
+ margin: 10px 15px 12px 0;
181
+ @include mq ($from: tablet) {
182
+ margin-right: 10px;
183
+ }
184
+ font-size: 16px;
185
+ position: relative;
186
+ flex: 0 0 auto;
187
+ align-self: flex-end;
188
+ display: flex;
189
+ flex-direction: row;
190
+ flex-wrap: nowrap;
191
+ align-items: center;
192
+ width: auto;
193
+ cursor: pointer;
194
+ }
195
+
196
+ &.wc-focus-within::after {
197
+ @include focus($glow: 5px, $strong: 2px, $background: 0, $inset: 0);
198
+ left: 4px;
199
+ right: 4px;
200
+ top: 4px;
201
+ bottom: 4px;
202
+ }
203
+ }
204
+
205
+ .wc-body:focus-visible + .wc-footer .wc-form {
206
+ border-top: 1px solid transparent;
207
+ }
208
+
209
+ .wc-footer {
210
+ position: relative;
211
+ flex: 0 0 0;
212
+
213
+ &__settings {
214
+ @include govuk-responsive-padding(2);
215
+
216
+ display: flex;
217
+ flex-direction: row;
218
+
219
+ &-link {
220
+ @extend %wc-link-button;
221
+
222
+ &:first-child {
223
+ margin-right: govuk-spacing(3);
224
+ }
225
+ }
226
+ }
227
+
228
+ &__input {
229
+ @include govuk-responsive-padding(2);
230
+
231
+ border-top: 1px solid govuk-colour('black');
232
+ }
233
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+
3
+ export const ErrorSummary = ({ errors }) => {
4
+ const errs = Object.keys(errors)
5
+
6
+ const goToInput = e => {
7
+ e.preventDefault()
8
+ const key = e.target.getAttribute('data-key')
9
+ document.querySelector(`#wc-${key}`).focus()
10
+ }
11
+
12
+ if (errs.length === 0) {
13
+ return null
14
+ }
15
+
16
+ return (
17
+ <div className='govuk-error-summary govuk-!-static-margin-bottom-7' data-module='govuk-error-summary' tabIndex='-1'>
18
+ <div role='alert'>
19
+ <h2 id='wc-error' className='wc-error-summary__title govuk-error-summary__title'>
20
+ There is a problem
21
+ </h2>
22
+ <div className='govuk-error-summary__body'>
23
+ <ul className='govuk-list govuk-error-summary__list wc-error-summary__list'>
24
+ {errs.map(key => (
25
+ <li key={key}>
26
+ <a href='#' data-key={key} onClick={goToInput}>{errors[key]}</a>
27
+ </li>
28
+ ))}
29
+ </ul>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ )
34
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import { classnames } from '../../lib/classnames'
3
+
4
+ export function Message ({ message, previousMessage }) {
5
+ const outbound = message.direction === 'outbound'
6
+
7
+ const messageOwnerSameAsPrevious = message?.direction === previousMessage?.direction
8
+
9
+ return (
10
+ <li className={classnames('wc-chat__message', outbound ? 'outbound' : 'inbound')}>
11
+ <div className='wc-chat__from'>
12
+ {messageOwnerSameAsPrevious ? null : <span>{outbound ? message.assignee : 'You'}</span>}
13
+ <span className='govuk-visually-hidden'>:</span>
14
+ </div>
15
+ <div className={classnames('wc-chat__text', outbound ? 'outbound' : 'inbound')}>
16
+ {message.text}
17
+ </div>
18
+ </li>
19
+ )
20
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react'
2
+
3
+ export function PanelFooter ({ children }) {
4
+ return (
5
+ <div className='wc-footer'>
6
+ {children}
7
+ </div>
8
+ )
9
+ }
@@ -1,7 +1,30 @@
1
1
  import React from 'react'
2
+ import { useApp } from '../../store/useApp.js'
3
+ import { historyReplaceState } from '../../lib/history.js'
2
4
 
3
- export function PanelHeader ({ screen, isConnected, onBack, onClose }) {
4
- let ButtonComponent = (
5
+ export function PanelHeader () {
6
+ const { thread, threadId, setChatVisibility, setUnseenCount, isMobile } = useApp()
7
+
8
+ const onClose = e => {
9
+ e.preventDefault()
10
+ setChatVisibility(false)
11
+ historyReplaceState()
12
+
13
+ if (threadId) {
14
+ thread?.lastMessageSeen()
15
+ setUnseenCount(0)
16
+ }
17
+ }
18
+
19
+ const BackButtonComponent = (
20
+ <button className='wc-header__back' aria-label='Close the webchat' onClick={onClose}>
21
+ <svg aria-hidden='true' focusable='false' width='20' height='20' viewBox='0 0 20 20'>
22
+ <path d='M4.828,11L12.314,18.485L10.899,19.899L1,10L10.899,0.101L12.314,1.515L4.828,9L19,9L19,11L4.828,11Z' fill='currentColor' />
23
+ </svg>
24
+ </button>
25
+ )
26
+
27
+ const CloseButtonComponent = (
5
28
  <button className='wc-header__close' aria-label='Close the webchat' onClick={onClose}>
6
29
  <svg aria-hidden='true' focusable='false' width='20' height='20' viewBox='0 0 20 20'>
7
30
  <path d='M10,8.6L15.6,3L17,4.4L11.4,10L17,15.6L15.6,17L10,11.4L4.4,17L3,15.6L8.6,10L3,4.4L4.4,3L10,8.6Z' fill='currentColor' />
@@ -9,27 +32,44 @@ export function PanelHeader ({ screen, isConnected, onBack, onClose }) {
9
32
  </button>
10
33
  )
11
34
 
12
- if (isConnected) {
13
- ButtonComponent = (
14
- <button className='wc-hide-btn' aria-label='Minimise the webchat' data-wc-hide-btn>
15
- <svg aria-hidden='true' focusable='false' width='20' height='20' viewBox='0 0 20 20'>
16
- <path d='M10 14.4l-7-7L4.4 6l5.6 5.6L15.6 6 17 7.4l-7 7z' fill='currentColor' />
17
- </svg>
18
- </button>
19
- )
35
+ const MinimiseButtonComponent = (
36
+ <button className='wc-header__hide' aria-label='Minimise the webchat' onClick={onClose}>
37
+ <svg aria-hidden='true' focusable='false' width='20' height='20' viewBox='0 0 20 20'>
38
+ <path d='M10 14.4l-7-7L4.4 6l5.6 5.6L15.6 6 17 7.4l-7 7z' fill='currentColor' />
39
+ </svg>
40
+ </button>
41
+ )
42
+
43
+ const isMobileAndHasHistory = isMobile && window.history.state
44
+ const isMobileAndNoHistory = isMobile && !window.history.state
45
+
46
+ let RightButtonComponent = CloseButtonComponent
47
+
48
+ if (threadId) {
49
+ RightButtonComponent = MinimiseButtonComponent
50
+
51
+ if (isMobileAndNoHistory) {
52
+ RightButtonComponent = MinimiseButtonComponent
53
+ }
20
54
  }
21
55
 
22
- const BackButtonComponent = !isConnected && screen !== 0 ? <a href='#' className='wc-header__link govuk-back-link govuk-back-link--inverse' onClick={onBack}>Back</a> : null
56
+ if (!threadId && isMobileAndNoHistory) {
57
+ RightButtonComponent = CloseButtonComponent
58
+ }
59
+
60
+ if (isMobileAndHasHistory) {
61
+ RightButtonComponent = null
62
+ }
23
63
 
24
64
  return (
25
65
  <div className='wc-header'>
26
- {BackButtonComponent}
66
+ {isMobileAndHasHistory ? BackButtonComponent : null}
27
67
 
28
- <h2 id='wc-header-title' className='wc-header__title govuk-heading-s'>
68
+ <h2 id='wc-header-title' className='wc-header__title'>
29
69
  Floodline Webchat
30
70
  </h2>
31
71
 
32
- {ButtonComponent}
72
+ {RightButtonComponent}
33
73
  </div>
34
74
  )
35
75
  }
@@ -1,106 +1,147 @@
1
1
  import React, { useEffect, useState, useCallback } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
+ import { classnames } from '../../lib/classnames.js'
3
4
 
4
- import { PanelHeader } from './panel-header.jsx'
5
5
  import { PreChat } from '../screens/pre-chat.jsx'
6
6
  import { RequestChat } from '../screens/request-chat.jsx'
7
-
8
- export const setAriaHidden = (bool) => {
9
- for (const node of document.body.children) {
10
- if (node.id !== 'wc-panel') {
11
- (bool) ? node.setAttribute('aria-hidden', 'true') : node.removeAttribute('aria-hidden')
12
- }
13
- }
14
- }
15
-
16
- export const getFocusableElements = () => {
17
- const selectors = [
18
- '#wc-panel a:not([disabled])',
19
- '#wc-panel button:not([disabled])',
20
- '#wc-panel select:not([disabled])',
21
- '#wc-panel input:not([disabled])',
22
- '#wc-panel textarea:not([disabled])',
23
- '#wc-panel *[tabindex="0"]:not([disabled])'
24
- ]
25
-
26
- const elements = document.body.querySelectorAll(selectors.join(','))
27
- return Array.from(elements).filter(e => !e.closest('[hidden]') && !e.closest('[aria-hidden="true"]'))
28
- }
29
-
30
- export function Panel ({ screenNumber, onClose }) {
31
- const [screen, setScreen] = useState(screenNumber)
32
- const [panelElements, setPanelElements] = useState([])
33
-
34
- const onKeyDown = useCallback((e) => {
35
- if (e.key === 'Escape' || e.key === 'Esc') return onClose()
36
-
37
- if (e.key === 'Tab') {
38
- const webchatPanelElement = document.querySelector('#wc-panel')
39
-
40
- if (webchatPanelElement && !document.activeElement.closest('#wc-panel')) {
41
- webchatPanelElement.focus()
42
- }
43
-
44
- if (e.shiftKey) {
45
- if (document.activeElement === panelElements[0]) {
46
- panelElements[panelElements.length - 1].focus()
47
- e.preventDefault()
48
- } else if (document.activeElement === document.querySelector('#wc-panel')) {
49
- panelElements[panelElements.length - 1]?.focus()
50
- e.preventDefault()
51
- }
52
- } else if (document.activeElement === panelElements[panelElements.length - 1]) {
53
- panelElements[0].focus()
54
- e.preventDefault()
7
+ import { Chat } from '../chat/chat.jsx'
8
+ import { Unavailable } from '../screens/unavailable.jsx'
9
+ import { EndChat } from '../screens/end-chat.jsx'
10
+ import { Settings } from '../screens/settings.jsx'
11
+ import { Feedback } from '../screens/feedback.jsx'
12
+
13
+ import { useApp } from '../../store/useApp.js'
14
+ import { useChatSdk } from '../../store/useChatSdk.js'
15
+ import { useFocusedElements } from '../../hooks/useFocusedElements.js'
16
+ import { historyReplaceState } from '../../lib/history.js'
17
+
18
+ export function Panel () {
19
+ const { sdk, availability, instigatorId, thread, threadId, setThread, setThreadId, setChatVisibility, setMessages, setUnseenCount, isMobile, isKeyboard } = useApp()
20
+ const { fetchThread, fetchMessages } = useChatSdk(sdk)
21
+
22
+ const [screen, setScreen] = useState(threadId ? 2 : 0)
23
+
24
+ useFocusedElements(screen)
25
+
26
+ const onEscapeKey = useCallback(e => {
27
+ if (e.key === 'Escape' || e.key === 'Esc') {
28
+ if (threadId) {
29
+ thread?.lastMessageSeen()
30
+ setUnseenCount(0)
55
31
  }
32
+ setChatVisibility(false)
33
+ historyReplaceState()
34
+ document.getElementById(instigatorId)?.focus()
56
35
  }
57
- }, [panelElements])
36
+ }, [thread, threadId, setUnseenCount, setChatVisibility])
58
37
 
38
+ /**
39
+ * We need ammend classes on the body element to handle mobile behaviour
40
+ */
59
41
  useEffect(() => {
60
- setPanelElements(getFocusableElements())
61
- }, [screen])
42
+ document.body.classList.remove('wc-u-hidden')
43
+ document.getElementsByTagName('html')[0].classList.add('wc-u-html')
44
+ document.body.classList.add('wc-u-body')
45
+ return () => {
46
+ document.getElementsByTagName('html')[0].classList.remove('wc-u-html')
47
+ document.body.classList.remove('wc-u-body')
48
+ }
49
+ }, [isMobile])
62
50
 
51
+ /**
52
+ * Initializes the eventListener for pressing the escape key
53
+ */
63
54
  useEffect(() => {
64
- setAriaHidden(true)
65
- document.querySelector('#wc-panel').focus()
66
- document.addEventListener('keydown', onKeyDown)
55
+ document.addEventListener('keydown', onEscapeKey)
67
56
 
68
57
  return () => {
69
- setAriaHidden()
70
- document.body.querySelector('.wc-availability__link')?.focus()
71
- document.removeEventListener('keydown', onKeyDown)
58
+ document.removeEventListener('keydown', onEscapeKey)
59
+ }
60
+ }, [onEscapeKey, setChatVisibility])
61
+
62
+ /**
63
+ * Recovers the thread if there is a threadId but no thread loaded in to state
64
+ */
65
+ useEffect(() => {
66
+ const recover = async () => {
67
+ try {
68
+ const fetchedThread = await fetchThread(threadId)
69
+ setThread(fetchedThread)
70
+
71
+ const fetchedMessages = await fetchMessages(fetchedThread, threadId)
72
+ setMessages(fetchedMessages)
73
+ } catch (err) {
74
+ console.log('[Chat Error] fetchThread', err)
75
+
76
+ setThreadId()
77
+ setThread()
78
+ setScreen(0)
79
+ }
80
+ }
81
+
82
+ if (threadId) {
83
+ if (thread) {
84
+ thread.lastMessageSeen()
85
+ setUnseenCount(0)
86
+ setScreen(2)
87
+ } else {
88
+ recover()
89
+ }
72
90
  }
73
- }, [panelElements])
91
+ }, [thread, threadId])
74
92
 
75
- const onBack = (e) => {
93
+ const handleScreenChange = newScreen => e => {
76
94
  e.preventDefault()
77
- setScreen(screen - 1)
95
+ setScreen(newScreen)
78
96
  }
79
97
 
98
+ const goToPreChatScreen = handleScreenChange(0)
99
+ const goToRequestChatScreen = handleScreenChange(1)
100
+ const goToChatScreen = handleScreenChange(2)
101
+ const goToEndChatScreen = handleScreenChange(3)
102
+ const goToFeedbackScreen = handleScreenChange(4)
103
+ const goToSettingsScreen = handleScreenChange(5)
104
+
80
105
  let ScreenComponent
81
106
 
82
107
  switch (screen) {
83
108
  case 0:
84
- ScreenComponent = <PreChat screen={screen} setScreen={setScreen} />
109
+ ScreenComponent = <PreChat onContinue={goToRequestChatScreen} />
85
110
  break
86
111
  case 1:
87
- ScreenComponent = <RequestChat onBack={onBack} />
112
+ ScreenComponent = <RequestChat onPreChatScreen={goToPreChatScreen} />
113
+ break
114
+ case 2:
115
+ ScreenComponent = <Chat onSettingsScreen={goToSettingsScreen} onEndChatScreen={goToEndChatScreen} />
116
+ break
117
+ case 3:
118
+ ScreenComponent = <EndChat onChatScreen={goToChatScreen} onEndChatConfirm={goToFeedbackScreen} />
119
+ break
120
+ case 4:
121
+ ScreenComponent = <Feedback onCancel={() => setChatVisibility(false)} />
122
+ break
123
+ case 5:
124
+ ScreenComponent = <Settings onCancel={goToChatScreen} />
88
125
  break
89
126
  default:
90
- ScreenComponent = <PreChat screen={screen} setScreen={setScreen} />
127
+ ScreenComponent = <PreChat onContinue={goToRequestChatScreen} />
91
128
  }
92
129
 
93
- const Component = (
94
- <div id='wc-panel' className='wc-panel' role='dialog' tabIndex='-1' aria-modal='true' aria-labelledby='wc-header-title'>
95
- <PanelHeader screen={screen} isConnected={false} onBack={onBack} onClose={onClose} />
130
+ if (availability === 'UNAVAILABLE') {
131
+ ScreenComponent = <Unavailable />
132
+ }
96
133
 
97
- <div className='wc-body'>
98
- <div className='wc-content govuk-body-s'>
99
- {ScreenComponent}
100
- </div>
134
+ const Component = (
135
+ <div id='wc-panel' role='dialog' className={classnames('wc-panel', isKeyboard && 'wc-focus-visible')} tabIndex='-1' aria-modal='true' aria-labelledby='wc-header-title'>
136
+ <div className='wc-panel__inner'>
137
+ {ScreenComponent}
101
138
  </div>
102
139
  </div>
103
140
  )
104
141
 
105
- return createPortal(Component, document.body)
142
+ return (
143
+ <>
144
+ {createPortal(Component, document.body)}
145
+ </>
146
+ )
106
147
  }