@seamly/web-ui 19.1.3 → 20.0.0-beta.3

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 (184) hide show
  1. package/build/dist/lib/components.js +2 -1
  2. package/build/dist/lib/components.min.js +1 -1
  3. package/build/dist/lib/index.debug.js +188 -111
  4. package/build/dist/lib/index.debug.min.js +1 -1
  5. package/build/dist/lib/index.debug.min.js.LICENSE.txt +48 -20
  6. package/build/dist/lib/index.js +11764 -11970
  7. package/build/dist/lib/index.min.js +1 -1
  8. package/build/dist/lib/index.min.js.LICENSE.txt +0 -5
  9. package/build/dist/lib/standalone.js +7131 -7333
  10. package/build/dist/lib/standalone.min.js +1 -1
  11. package/build/dist/lib/standalone.min.js.LICENSE.txt +0 -5
  12. package/build/dist/lib/style-guide.js +2117 -2007
  13. package/build/dist/lib/style-guide.min.js +1 -1
  14. package/build/dist/lib/styles.css +1 -1
  15. package/package.json +1 -2
  16. package/src/.DS_Store +0 -0
  17. package/src/icons/icon_file-32.svg +1 -1
  18. package/src/javascripts/api/index.js +48 -37
  19. package/src/javascripts/config.js +1 -5
  20. package/src/javascripts/domains/app/actions.js +22 -5
  21. package/src/javascripts/domains/config/actions.js +3 -0
  22. package/src/javascripts/domains/config/reducer.js +9 -0
  23. package/src/javascripts/domains/forms/hooks.js +3 -1
  24. package/src/javascripts/domains/forms/provider.js +12 -0
  25. package/src/javascripts/domains/forms/reducer.js +2 -0
  26. package/src/javascripts/domains/i18n/hooks.js +2 -1
  27. package/src/javascripts/domains/i18n/reducer.js +2 -0
  28. package/src/javascripts/domains/interrupt/reducer.js +2 -2
  29. package/src/javascripts/domains/options/middleware.js +15 -31
  30. package/src/javascripts/domains/store/index.js +2 -1
  31. package/src/javascripts/domains/store/state-reducer.js +3 -8
  32. package/src/javascripts/domains/translations/components/options-dialog/form.js +1 -1
  33. package/src/javascripts/domains/translations/components/options-dialog/index.js +15 -1
  34. package/src/javascripts/domains/translations/reducer.js +2 -0
  35. package/src/javascripts/domains/visibility/actions.js +1 -1
  36. package/src/javascripts/domains/visibility/hooks.js +10 -8
  37. package/src/javascripts/domains/visibility/utils.js +1 -2
  38. package/src/javascripts/index.js +7 -2
  39. package/src/javascripts/lib/css.js +7 -1
  40. package/src/javascripts/lib/engine/index.js +4 -3
  41. package/src/javascripts/lib/external-api/index.js +38 -29
  42. package/src/javascripts/package/components.js +2 -1
  43. package/src/javascripts/style-guide/components/app.js +4 -4
  44. package/src/javascripts/style-guide/components/static-core.js +9 -3
  45. package/src/javascripts/style-guide/components/view.js +0 -1
  46. package/src/javascripts/style-guide/states.js +468 -348
  47. package/src/javascripts/ui/components/chat-app.js +1 -1
  48. package/src/javascripts/ui/components/conversation/component-filter.js +6 -0
  49. package/src/javascripts/ui/components/conversation/event/carousel-component/index.js +8 -1
  50. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +2 -3
  51. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +70 -0
  52. package/src/javascripts/ui/components/conversation/event/participant.js +2 -5
  53. package/src/javascripts/ui/components/conversation/event/splash.js +26 -0
  54. package/src/javascripts/ui/components/conversation/event/text.js +1 -2
  55. package/src/javascripts/ui/components/core/seamly-core.js +12 -9
  56. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +4 -10
  57. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +1 -8
  58. package/src/javascripts/ui/components/entry/entry-container.js +5 -3
  59. package/src/javascripts/ui/components/entry/text-entry/index.js +7 -1
  60. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +5 -1
  61. package/src/javascripts/ui/components/entry/toggle-button.js +4 -2
  62. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +1 -1
  63. package/src/javascripts/ui/components/form-controls/error.js +6 -2
  64. package/src/javascripts/ui/components/form-controls/form.js +26 -3
  65. package/src/javascripts/ui/components/layout/app-frame.js +24 -15
  66. package/src/javascripts/ui/components/layout/chat-frame.js +0 -2
  67. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +86 -0
  68. package/src/javascripts/ui/components/layout/modal-wrapper.js +0 -80
  69. package/src/javascripts/ui/components/layout/pre-chat-messages.js +45 -0
  70. package/src/javascripts/ui/components/options/options-frame.js +9 -4
  71. package/src/javascripts/ui/components/options/options.js +1 -4
  72. package/src/javascripts/ui/components/options/transcript/index.js +15 -1
  73. package/src/javascripts/ui/components/options/transcript/transcript-form.js +1 -1
  74. package/src/javascripts/ui/components/suggestions/index.js +174 -0
  75. package/src/javascripts/ui/components/suggestions/suggestions-item.js +40 -0
  76. package/src/javascripts/ui/components/suggestions/suggestions-list.js +24 -0
  77. package/src/javascripts/ui/components/view/app-view.js +21 -0
  78. package/src/javascripts/ui/components/view/deprecated-view.js +32 -0
  79. package/src/javascripts/ui/components/view/index.js +27 -0
  80. package/src/javascripts/ui/components/view/inline-view.js +45 -0
  81. package/src/javascripts/ui/components/view/window-view/collapse-button.js +20 -0
  82. package/src/javascripts/ui/components/view/window-view/index.js +82 -0
  83. package/src/javascripts/ui/components/view/window-view/window-open-button.js +68 -0
  84. package/src/javascripts/ui/components/widgets/lightbox.js +7 -2
  85. package/src/javascripts/ui/hooks/component-helper-hooks.js +0 -9
  86. package/src/javascripts/ui/hooks/seamly-hooks.js +0 -1
  87. package/src/javascripts/ui/hooks/seamly-state-hooks.js +28 -4
  88. package/src/javascripts/ui/hooks/use-seamly-chat.js +12 -3
  89. package/src/javascripts/ui/hooks/use-seamly-commands.js +4 -31
  90. package/src/javascripts/ui/utils/seamly-utils.js +2 -14
  91. package/src/stylesheets/1-settings/_animations.scss +0 -6
  92. package/src/stylesheets/1-settings/_config.scss +34 -35
  93. package/src/stylesheets/2-tools/_functions.scss +0 -5
  94. package/src/stylesheets/2-tools/_mixins.scss +4 -16
  95. package/src/stylesheets/3-app/_app.scss +78 -135
  96. package/src/stylesheets/4-base/_a11y.scss +0 -3
  97. package/src/stylesheets/4-base/_elements.scss +0 -11
  98. package/src/stylesheets/4-base/_formelements.scss +4 -14
  99. package/src/stylesheets/5-components/_avatar.scss +2 -44
  100. package/src/stylesheets/5-components/_buttons.scss +6 -45
  101. package/src/stylesheets/5-components/_chat-status.scss +14 -38
  102. package/src/stylesheets/5-components/_choice-prompt.scss +33 -2
  103. package/src/stylesheets/5-components/_collapse-button.scss +16 -0
  104. package/src/stylesheets/5-components/_conversation.scss +26 -2
  105. package/src/stylesheets/5-components/_disclaimer.scss +10 -12
  106. package/src/stylesheets/5-components/_divider.scss +7 -4
  107. package/src/stylesheets/5-components/_error.scss +19 -9
  108. package/src/stylesheets/5-components/_form.scss +9 -0
  109. package/src/stylesheets/5-components/_icon.scss +10 -1
  110. package/src/stylesheets/5-components/_idle.scss +0 -8
  111. package/src/stylesheets/5-components/_input.scss +14 -20
  112. package/src/stylesheets/5-components/_interrupt.scss +0 -2
  113. package/src/stylesheets/5-components/_loader.scss +0 -32
  114. package/src/stylesheets/5-components/_message-author.scss +40 -0
  115. package/src/stylesheets/5-components/_message-body.scss +194 -0
  116. package/src/stylesheets/5-components/_message-card.scss +55 -0
  117. package/src/stylesheets/5-components/_message-carousel.scss +143 -0
  118. package/src/stylesheets/5-components/_message-count.scss +11 -28
  119. package/src/stylesheets/5-components/_message-cta.scss +23 -0
  120. package/src/stylesheets/5-components/_message-info.scss +11 -0
  121. package/src/stylesheets/5-components/_message-translation-info.scss +17 -0
  122. package/src/stylesheets/5-components/_message.scss +13 -364
  123. package/src/stylesheets/5-components/_modal.scss +28 -58
  124. package/src/stylesheets/5-components/_notification.scss +0 -5
  125. package/src/stylesheets/5-components/_options.scss +27 -42
  126. package/src/stylesheets/5-components/_pre-chat-messages.scss +30 -0
  127. package/src/stylesheets/5-components/_prompt.scss +0 -8
  128. package/src/stylesheets/5-components/_skip-link.scss +3 -3
  129. package/src/stylesheets/5-components/_suggestions.scss +96 -0
  130. package/src/stylesheets/5-components/_unstarted.scss +50 -0
  131. package/src/stylesheets/5-components/_upload.scss +26 -28
  132. package/src/stylesheets/5-components/_window-open-button.scss +39 -0
  133. package/src/stylesheets/6-webui-only/_hover.scss +151 -0
  134. package/src/stylesheets/6-webui-only/_scrollbar.scss +31 -0
  135. package/src/stylesheets/7-deprecated/1-settings/_animations.scss +43 -0
  136. package/src/stylesheets/7-deprecated/1-settings/_config.scss +105 -0
  137. package/src/stylesheets/7-deprecated/2-tools/_functions.scss +22 -0
  138. package/src/stylesheets/7-deprecated/2-tools/_mixins.scss +77 -0
  139. package/src/stylesheets/7-deprecated/3-app/_app.scss +214 -0
  140. package/src/stylesheets/7-deprecated/4-base/_a11y.scss +14 -0
  141. package/src/stylesheets/7-deprecated/4-base/_elements.scss +21 -0
  142. package/src/stylesheets/7-deprecated/4-base/_formelements.scss +57 -0
  143. package/src/stylesheets/{5-components → 7-deprecated/5-components}/_agent-info.scss +0 -0
  144. package/src/stylesheets/7-deprecated/5-components/_avatar.scss +64 -0
  145. package/src/stylesheets/7-deprecated/5-components/_buttons.scss +94 -0
  146. package/src/stylesheets/{5-components → 7-deprecated/5-components}/_card.scss +0 -0
  147. package/src/stylesheets/{5-components → 7-deprecated/5-components}/_carousel.scss +0 -0
  148. package/src/stylesheets/7-deprecated/5-components/_character-limit.scss +36 -0
  149. package/src/stylesheets/{5-components/_cobrowsing.scss → 7-deprecated/5-components/_chat-status.scss} +18 -16
  150. package/src/stylesheets/7-deprecated/5-components/_choice-prompt.scss +27 -0
  151. package/src/stylesheets/7-deprecated/5-components/_collapse-button.scss +17 -0
  152. package/src/stylesheets/7-deprecated/5-components/_conversation.scss +44 -0
  153. package/src/stylesheets/7-deprecated/5-components/_disclaimer.scss +36 -0
  154. package/src/stylesheets/7-deprecated/5-components/_divider.scss +91 -0
  155. package/src/stylesheets/7-deprecated/5-components/_error.scss +34 -0
  156. package/src/stylesheets/{5-components → 7-deprecated/5-components}/_faq.scss +8 -3
  157. package/src/stylesheets/{5-components → 7-deprecated/5-components}/_header-controls.scss +0 -0
  158. package/src/stylesheets/7-deprecated/5-components/_icon.scss +4 -0
  159. package/src/stylesheets/7-deprecated/5-components/_idle.scss +61 -0
  160. package/src/stylesheets/7-deprecated/5-components/_input.scss +78 -0
  161. package/src/stylesheets/7-deprecated/5-components/_interrupt.scss +35 -0
  162. package/src/stylesheets/7-deprecated/5-components/_loader.scss +78 -0
  163. package/src/stylesheets/7-deprecated/5-components/_message-count.scss +41 -0
  164. package/src/stylesheets/7-deprecated/5-components/_message.scss +385 -0
  165. package/src/stylesheets/7-deprecated/5-components/_modal.scss +138 -0
  166. package/src/stylesheets/7-deprecated/5-components/_notification.scss +20 -0
  167. package/src/stylesheets/7-deprecated/5-components/_options.scss +284 -0
  168. package/src/stylesheets/7-deprecated/5-components/_prompt.scss +44 -0
  169. package/src/stylesheets/7-deprecated/5-components/_skip-link.scss +21 -0
  170. package/src/stylesheets/{5-components → 7-deprecated/5-components}/_svg-graphic.scss +0 -0
  171. package/src/stylesheets/7-deprecated/5-components/_upload.scss +213 -0
  172. package/src/stylesheets/deprecated-view.scss +64 -0
  173. package/src/stylesheets/styles-webui-only.scss +3 -0
  174. package/src/stylesheets/styles.scss +15 -25
  175. package/webpack/config.common.js +7 -1
  176. package/webpack/config.site.js +4 -0
  177. package/webpack/config.test.js +1 -0
  178. package/webpack/defaults.js +5 -0
  179. package/src/javascripts/ui/components/conversation/event/hooks/use-text-rendering.js +0 -35
  180. package/src/javascripts/ui/components/layout/view.js +0 -36
  181. package/src/javascripts/ui/components/options/cobrowsing.js +0 -110
  182. package/src/javascripts/ui/components/warnings/cobrowsing-active-frame.js +0 -29
  183. package/src/javascripts/ui/components/warnings/cobrowsing-active.js +0 -33
  184. package/src/stylesheets/5-components/_modal_mode.scss +0 -108
@@ -1,80 +0,0 @@
1
- import { useEffect, useRef } from 'preact/hooks'
2
- import { createFocusTrap } from 'focus-trap'
3
- import InOutTransition, {
4
- transitionStartStates,
5
- } from '../widgets/in-out-transition'
6
- import { useSeamlyLayoutMode } from '../../hooks/seamly-hooks'
7
- import { useI18n } from '../../../domains/i18n'
8
- import { className } from '../../../lib/css'
9
- import { createAriaHider } from '../../utils/general-utils'
10
- import { useVisibility, visibilityStates } from '../../../domains/visibility'
11
-
12
- const ModalWrapper = ({ children }) => {
13
- const { t } = useI18n()
14
- const { isModal } = useSeamlyLayoutMode()
15
- const { isOpen, setVisibility } = useVisibility()
16
- const container = useRef(null)
17
- const focusTrap = useRef(null)
18
-
19
- const onInTransitionCompleteHandler = () => {
20
- focusTrap.current = createFocusTrap(container.current)
21
- focusTrap.current.activate()
22
- }
23
-
24
- const onOutTransitionCompleteHandler = () => {
25
- if (focusTrap.current) {
26
- focusTrap.current.deactivate()
27
- focusTrap.current = null
28
- }
29
- }
30
-
31
- useEffect(() => {
32
- if (!isModal) {
33
- return null
34
- }
35
-
36
- let disposeAriaHider
37
-
38
- if (isOpen) {
39
- disposeAriaHider = createAriaHider()
40
- // As the chat window is housed in a container we have to remove the
41
- // aria-hidden the aria hider places on the container.
42
- container.current.parentElement.removeAttribute('aria-hidden')
43
- }
44
-
45
- return () => {
46
- if (disposeAriaHider) {
47
- disposeAriaHider()
48
- disposeAriaHider = null
49
- }
50
- }
51
- }, [isOpen, isModal])
52
-
53
- const onClickHandler = () => {
54
- setVisibility(visibilityStates.minimized)
55
- }
56
-
57
- return isModal ? (
58
- <InOutTransition
59
- isActive={isOpen}
60
- transitionStartState={transitionStartStates.rendered}
61
- onInTransitionComplete={onInTransitionCompleteHandler}
62
- onOutTransitionComplete={onOutTransitionCompleteHandler}
63
- >
64
- <div
65
- className={className('modal-overlay')}
66
- role="dialog"
67
- aria-modal="true"
68
- aria-label={t('window.srModalLayoutLabel')}
69
- onClick={onClickHandler}
70
- ref={container}
71
- >
72
- {children}
73
- </div>
74
- </InOutTransition>
75
- ) : (
76
- children
77
- )
78
- }
79
-
80
- export default ModalWrapper
@@ -0,0 +1,45 @@
1
+ import { className } from '../../../lib/css'
2
+ import { useInterrupt } from '../../../domains/interrupt'
3
+ import { useConfig } from '../../../domains/config'
4
+ import useEventComponentMapping from '../../hooks/use-event-component-mapping'
5
+ import { useVisibility } from '../../../domains/visibility'
6
+ import InOutTransition, {
7
+ transitionStartStates,
8
+ } from '../widgets/in-out-transition'
9
+
10
+ export default function PreChatMessages() {
11
+ const { preChatEvents, layoutMode } = useConfig()
12
+ const { hasInterrupt } = useInterrupt()
13
+ const { isOpen } = useVisibility()
14
+
15
+ const isVisible = !(hasInterrupt || !preChatEvents.length || isOpen)
16
+
17
+ return (
18
+ <InOutTransition
19
+ isActive={isVisible}
20
+ transitionStartState={transitionStartStates.notRendered}
21
+ >
22
+ <ul
23
+ className={className(
24
+ 'pre-chat-messages',
25
+ `pre-chat-messages--${layoutMode}`,
26
+ )}
27
+ aria-hidden={!isVisible}
28
+ >
29
+ {preChatEvents.map((event) => (
30
+ <li
31
+ className={className('pre-chat-messages__message')}
32
+ key={event.payload.id}
33
+ >
34
+ <PreChatMessageEvent event={event} />
35
+ </li>
36
+ ))}
37
+ </ul>
38
+ </InOutTransition>
39
+ )
40
+ }
41
+
42
+ export function PreChatMessageEvent({ event }) {
43
+ const [Component] = useEventComponentMapping(event)
44
+ return <Component event={event} />
45
+ }
@@ -9,6 +9,7 @@ import { className } from '../../../lib/css'
9
9
  import { focusElement } from '../../utils/general-utils'
10
10
 
11
11
  const OptionsFrame = ({
12
+ className: givenClassName,
12
13
  children,
13
14
  onCancel,
14
15
  headingText,
@@ -44,10 +45,14 @@ const OptionsFrame = ({
44
45
 
45
46
  return (
46
47
  <section
47
- className={className('options', {
48
- 'options--right': position !== 'left',
49
- 'options--left': position === 'left',
50
- })}
48
+ className={className(
49
+ 'options',
50
+ {
51
+ 'options--right': position !== 'left',
52
+ 'options--left': position === 'left',
53
+ },
54
+ givenClassName,
55
+ )}
51
56
  aria-labelledby={mainHeadingId}
52
57
  tabIndex="-1"
53
58
  ref={container}
@@ -1,10 +1,8 @@
1
1
  import { useRef } from 'preact/hooks'
2
- import Cobrowsing from './cobrowsing'
3
2
  import Transcript from './transcript'
4
3
  import { useSeamlyOptions } from '../../hooks/seamly-hooks'
5
4
 
6
5
  const mapper = {
7
- cobrowsing: Cobrowsing,
8
6
  sendTranscript: Transcript,
9
7
  }
10
8
 
@@ -18,8 +16,7 @@ const Options = () => {
18
16
  if (optionActive) {
19
17
  prevRenderOption.current = optionActive
20
18
  }
21
-
22
- return <RenderOption />
19
+ return RenderOption ? <RenderOption /> : null
23
20
  }
24
21
 
25
22
  export default Options
@@ -1,4 +1,4 @@
1
- import { useCallback, useMemo } from 'preact/hooks'
1
+ import { useCallback, useMemo, useState } from 'preact/hooks'
2
2
  import { useI18n } from '../../../../domains/i18n'
3
3
  import OptionsFrame from '../options-frame'
4
4
  import { className } from '../../../../lib/css'
@@ -18,6 +18,7 @@ const formId = 'sendTranscript'
18
18
  const controlName = 'email'
19
19
 
20
20
  const Transcript = () => {
21
+ const [errorClass, setErrorClass] = useState(undefined)
21
22
  const { hideOption } = useSeamlyOptions()
22
23
  const { focusButton } = useOptionButton()
23
24
  const { t } = useI18n()
@@ -50,8 +51,20 @@ const Transcript = () => {
50
51
  [sendAction, hideOption, focusButton],
51
52
  )
52
53
 
54
+ const handleError = useCallback(
55
+ ({ isValid, isSubmitted }) => {
56
+ if (isSubmitted && !isValid) {
57
+ setErrorClass('options--error')
58
+ } else {
59
+ setErrorClass(undefined)
60
+ }
61
+ },
62
+ [setErrorClass],
63
+ )
64
+
53
65
  return (
54
66
  <OptionsFrame
67
+ className={errorClass}
55
68
  headingText={t('options.sendTranscript.title')}
56
69
  cancelButtonText={t('options.cancelButtonText')}
57
70
  >
@@ -65,6 +78,7 @@ const Transcript = () => {
65
78
  formId={formId}
66
79
  onSubmit={handleSubmit}
67
80
  validationSchema={validationSchema}
81
+ onError={handleError}
68
82
  >
69
83
  <TranscriptForm
70
84
  controlName={controlName}
@@ -6,7 +6,7 @@ import Input from '../../form-controls/input'
6
6
  export default function TranscriptForm({ controlName, describedById }) {
7
7
  const { t } = useI18n()
8
8
  return (
9
- <Form noValidate="true">
9
+ <Form noValidate="true" className={className('options__form')}>
10
10
  <Input
11
11
  name={controlName}
12
12
  type="email"
@@ -0,0 +1,174 @@
1
+ import { useEffect, useRef, useCallback, useMemo } from 'preact/hooks'
2
+ import { className } from '../../../lib/css'
3
+ import { useI18n } from '../../../domains/i18n'
4
+ import { useSeamlyServiceData } from '../../hooks/seamly-state-hooks'
5
+ import { useTranslatedEventData } from '../../../domains/translations'
6
+ import { useInterrupt } from '../../../domains/interrupt'
7
+ import { useGeneratedId } from '../../hooks/utility-hooks'
8
+ import useSeamlyIdleDetachCountdown from '../../hooks/use-seamly-idle-detach-countdown'
9
+ import useSeamlyResumeConversationPrompt from '../../hooks/use-seamly-resume-conversation-prompt'
10
+ import useSeamlyCommands from '../../hooks/use-seamly-commands'
11
+ import { useSkiplinkTargetFocusing } from '../../hooks/focus-helper-hooks'
12
+ import { actionTypes } from '../../utils/seamly-utils'
13
+ import { useUserHasResponded } from '../../../domains/app'
14
+ import { useVisibility, visibilityStates } from '../../../domains/visibility'
15
+ import InOutTransition, {
16
+ transitionStartStates,
17
+ } from '../widgets/in-out-transition'
18
+ import { useLiveRegion } from '../../hooks/live-region-hooks'
19
+ import { runIfElementContainsOrHasFocus } from '../../utils/general-utils'
20
+ import SuggestionsList from './suggestions-list'
21
+ import { useConfig } from '../../../domains/config'
22
+
23
+ const Suggestions = ({ isAside }) => {
24
+ // generic hooks
25
+ const { layoutMode } = useConfig()
26
+ const { t } = useI18n()
27
+ const { sendAction, addMessageBubble } = useSeamlyCommands()
28
+ const { isOpen, setVisibility } = useVisibility()
29
+ // a11y hooks
30
+ const sectionId = useGeneratedId()
31
+ const focusSkiplinkTarget = useSkiplinkTargetFocusing()
32
+ const containerRef = useRef(null)
33
+ const { sendPolite } = useLiveRegion()
34
+ // interrupt & countdown hooks
35
+ const { hasInterrupt } = useInterrupt()
36
+ const { hasCountdown, endCountdown } = useSeamlyIdleDetachCountdown()
37
+ const { hasPrompt, continueChat } = useSeamlyResumeConversationPrompt()
38
+ // data hooks
39
+ const hasResponded = useUserHasResponded()
40
+ const payload = useSeamlyServiceData('suggestion')
41
+ const [eventBody] = useTranslatedEventData({ payload })
42
+ const suggestions = useMemo(
43
+ () => (payload && !hasInterrupt ? eventBody : []),
44
+ [payload, hasInterrupt, eventBody],
45
+ )
46
+
47
+ const prevSuggestions = useRef(null)
48
+ const prevHasSuggestions = useRef(false)
49
+
50
+ const previousRenderedSuggestions = useRef([])
51
+ const hasSuggestions = !!suggestions.length
52
+ const hideSuggestions =
53
+ layoutMode === 'inline'
54
+ ? (hasResponded || isOpen) && !isAside
55
+ : hasResponded
56
+ const prevHideSuggestions = useRef(hideSuggestions)
57
+ const showSuggestionsContainer = hasSuggestions && !hideSuggestions
58
+ const renderedSuggestions = hasSuggestions
59
+ ? suggestions
60
+ : previousRenderedSuggestions.current
61
+ previousRenderedSuggestions.current = renderedSuggestions
62
+
63
+ // click handler
64
+ const handleClick = useCallback(
65
+ ({ id, question }) => {
66
+ if (hasCountdown) {
67
+ endCountdown(true)
68
+ }
69
+
70
+ if (hasPrompt) {
71
+ continueChat()
72
+ }
73
+
74
+ // @todo Refactor to 'suggestionclick'
75
+ sendAction({
76
+ type: actionTypes.custom,
77
+ originMessage: payload.id,
78
+ body: {
79
+ type: 'faqclick',
80
+ body: {
81
+ faqId: id,
82
+ faqQuestion: question,
83
+ },
84
+ },
85
+ })
86
+
87
+ addMessageBubble(question)
88
+ if (!isOpen) {
89
+ setVisibility(visibilityStates.open)
90
+ }
91
+ focusSkiplinkTarget()
92
+ },
93
+ [
94
+ addMessageBubble,
95
+ continueChat,
96
+ endCountdown,
97
+ focusSkiplinkTarget,
98
+ hasCountdown,
99
+ hasPrompt,
100
+ payload,
101
+ sendAction,
102
+ setVisibility,
103
+ isOpen,
104
+ ],
105
+ )
106
+
107
+ useEffect(() => {
108
+ if (prevSuggestions.current !== suggestions && !hideSuggestions) {
109
+ if (hasSuggestions) {
110
+ const politeText = prevHasSuggestions.current
111
+ ? t('suggestions.srUpdatedText')
112
+ : t('suggestions.srAvailableText')
113
+ setTimeout(() => {
114
+ sendPolite(politeText)
115
+ }, 30)
116
+ } else if (prevHasSuggestions.current) {
117
+ sendPolite(t('suggestions.srUnavailableText'))
118
+ }
119
+
120
+ prevSuggestions.current = suggestions
121
+ }
122
+
123
+ if (!prevHideSuggestions.current && hideSuggestions) {
124
+ runIfElementContainsOrHasFocus(containerRef.current, focusSkiplinkTarget)
125
+ sendPolite(t('suggestions.srUnavailableText'))
126
+ } else if (!hasSuggestions && prevHasSuggestions.current) {
127
+ runIfElementContainsOrHasFocus(containerRef.current, focusSkiplinkTarget)
128
+ }
129
+ prevHasSuggestions.current = hasSuggestions
130
+ prevHideSuggestions.current = hideSuggestions
131
+ }, [
132
+ suggestions,
133
+ hasSuggestions,
134
+ hideSuggestions,
135
+ focusSkiplinkTarget,
136
+ sendPolite,
137
+ t,
138
+ ])
139
+
140
+ const headingText = t('suggestions.headingText')
141
+ const footerText = t('suggestions.footerText')
142
+ const ContainerElement = headingText ? 'section' : 'div'
143
+
144
+ return (
145
+ <InOutTransition
146
+ isActive={showSuggestionsContainer}
147
+ transitionStartState={transitionStartStates.notRendered}
148
+ >
149
+ <ContainerElement
150
+ className={className('suggestions')}
151
+ aria-labelledby={headingText ? sectionId : null}
152
+ ref={containerRef}
153
+ >
154
+ {headingText && (
155
+ <h2 id={sectionId} className={className('suggestions__heading')}>
156
+ {headingText}
157
+ </h2>
158
+ )}
159
+ {!!renderedSuggestions.length && (
160
+ <SuggestionsList
161
+ hasIcon={true}
162
+ suggestions={renderedSuggestions}
163
+ onClickSuggestion={handleClick}
164
+ />
165
+ )}
166
+ {footerText && !isOpen && (
167
+ <p className={className('suggestions__footer')}>{footerText}</p>
168
+ )}
169
+ </ContainerElement>
170
+ </InOutTransition>
171
+ )
172
+ }
173
+
174
+ export default Suggestions
@@ -0,0 +1,40 @@
1
+ import { useMemo, useCallback } from 'preact/hooks'
2
+ import { className } from '../../../lib/css'
3
+ import Icon from '../layout/icon'
4
+
5
+ export const mapCategoryToClass = (category) =>
6
+ `suggestions__item--${String(category)
7
+ .toLowerCase()
8
+ .replace(/[^a-z0-9_\\-]/, '')}`
9
+
10
+ const SuggestionsItem = ({
11
+ id,
12
+ categories = [],
13
+ question,
14
+ onClick,
15
+ hasIcon,
16
+ }) => {
17
+ const mappedClassNames = useMemo(() => {
18
+ return ['suggestions-item', ...categories.map(mapCategoryToClass)]
19
+ }, [categories])
20
+
21
+ const handleClick = useCallback(() => {
22
+ if (onClick) {
23
+ onClick({ id, question })
24
+ }
25
+ }, [id, question, onClick])
26
+ return (
27
+ <li className={className(mappedClassNames)}>
28
+ <button
29
+ type="button"
30
+ onClick={handleClick}
31
+ className={className('button', 'button--primary')}
32
+ >
33
+ {hasIcon && <Icon name="chevronRight" size="8" />}
34
+ {question}
35
+ </button>
36
+ </li>
37
+ )
38
+ }
39
+
40
+ export default SuggestionsItem
@@ -0,0 +1,24 @@
1
+ import { className } from '../../../lib/css'
2
+ import SuggestionsItem from './suggestions-item'
3
+
4
+ const SuggestionsList = ({
5
+ className: givenClassName,
6
+ suggestions = [],
7
+ onClickSuggestion,
8
+ hasIcon,
9
+ }) => {
10
+ return (
11
+ <ul className={className('suggestions__list', givenClassName)}>
12
+ {suggestions.map((suggestion) => (
13
+ <SuggestionsItem
14
+ hasIcon={hasIcon}
15
+ key={suggestion.id}
16
+ onClick={onClickSuggestion}
17
+ {...suggestion}
18
+ />
19
+ ))}
20
+ </ul>
21
+ )
22
+ }
23
+
24
+ export default SuggestionsList
@@ -0,0 +1,21 @@
1
+ import AppFrame from '../layout/app-frame'
2
+ import Interrupt from '../layout/interrupt'
3
+ import Conversation from '../conversation/conversation'
4
+ import EntryContainer from '../entry/entry-container'
5
+ import ChatFrame from '../layout/chat-frame'
6
+ import useSeamlyChat from '../../hooks/use-seamly-chat'
7
+
8
+ const AppView = () => {
9
+ useSeamlyChat()
10
+
11
+ return (
12
+ <AppFrame>
13
+ <ChatFrame interruptComponent={Interrupt}>
14
+ <Conversation />
15
+ <EntryContainer />
16
+ </ChatFrame>
17
+ </AppFrame>
18
+ )
19
+ }
20
+
21
+ export default AppView
@@ -0,0 +1,32 @@
1
+ import DeprecatedAppFrame from '../layout/deprecated-app-frame'
2
+ import ChatFrame from '../layout/chat-frame'
3
+ import AgentInfo from '../layout/agent-info'
4
+ import Header from '../layout/header'
5
+ import Conversation from '../conversation/conversation'
6
+ import EntryContainer from '../entry/entry-container'
7
+ import Interrupt from '../layout/interrupt'
8
+ import { useSeamlyChat } from '../../hooks/seamly-hooks'
9
+ import { useVisibility } from '../../../domains/visibility'
10
+ import DeprecatedToggleButton from '../entry/toggle-button'
11
+
12
+ const DeprecatedView = () => {
13
+ const { isVisible } = useVisibility()
14
+ const { closeChat, openChat } = useSeamlyChat()
15
+
16
+ return (
17
+ isVisible && (
18
+ <DeprecatedAppFrame>
19
+ <DeprecatedToggleButton onOpenChat={openChat} />
20
+ <Header onCloseChat={closeChat}>
21
+ <AgentInfo />
22
+ </Header>
23
+ <ChatFrame interruptComponent={Interrupt}>
24
+ <Conversation />
25
+ <EntryContainer />
26
+ </ChatFrame>
27
+ </DeprecatedAppFrame>
28
+ )
29
+ )
30
+ }
31
+
32
+ export default DeprecatedView
@@ -0,0 +1,27 @@
1
+ import { useConfig } from '../../../domains/config'
2
+ import AppView from './app-view'
3
+ import InlineView from './inline-view'
4
+ import WindowView from './window-view'
5
+ import { useSeamlyStateContext } from '../../hooks/seamly-state-hooks'
6
+ import { visibilityStates } from '../../../domains/visibility'
7
+
8
+ const ViewComponentsMap = {
9
+ app: AppView,
10
+ inline: InlineView,
11
+ window: WindowView,
12
+ }
13
+
14
+ const View = () => {
15
+ const { layoutMode } = useConfig()
16
+ const { visible } = useSeamlyStateContext()
17
+ const ViewComponent = ViewComponentsMap[layoutMode]
18
+
19
+ if (!ViewComponent) {
20
+ console.warn('"layoutMode" should be one of "app", "inline" or "window"')
21
+ return null
22
+ }
23
+
24
+ return visible !== visibilityStates.hidden && <ViewComponent />
25
+ }
26
+
27
+ export default View
@@ -0,0 +1,45 @@
1
+ import { className } from '../../../lib/css'
2
+ import AppFrame from '../layout/app-frame'
3
+ import Interrupt from '../layout/interrupt'
4
+ import Conversation from '../conversation/conversation'
5
+ import EntryContainer from '../entry/entry-container'
6
+ import ChatFrame from '../layout/chat-frame'
7
+ import useSeamlyChat from '../../hooks/use-seamly-chat'
8
+ import { useVisibility } from '../../../domains/visibility'
9
+ import PreChatMessages from '../layout/pre-chat-messages'
10
+ import Suggestions from '../suggestions'
11
+ import InOutTransition, {
12
+ transitionStartStates,
13
+ } from '../widgets/in-out-transition'
14
+
15
+ const InlineView = () => {
16
+ useSeamlyChat()
17
+ const { isOpen } = useVisibility()
18
+
19
+ return (
20
+ <>
21
+ <InOutTransition
22
+ isActive={!isOpen}
23
+ transitionStartState={transitionStartStates.rendered}
24
+ >
25
+ <div className={className('unstarted-wrapper')}>
26
+ <PreChatMessages />
27
+ <Suggestions />
28
+ </div>
29
+ </InOutTransition>
30
+ <InOutTransition
31
+ isActive={isOpen}
32
+ transitionStartState={transitionStartStates.rendered}
33
+ >
34
+ <AppFrame>
35
+ <ChatFrame interruptComponent={Interrupt}>
36
+ {isOpen && <Conversation />}
37
+ <EntryContainer />
38
+ </ChatFrame>
39
+ </AppFrame>
40
+ </InOutTransition>
41
+ </>
42
+ )
43
+ }
44
+
45
+ export default InlineView
@@ -0,0 +1,20 @@
1
+ import { useCallback } from 'preact/hooks'
2
+ import { className } from '../../../../lib/css'
3
+ import Icon from '../../layout/icon'
4
+ import { useI18n } from '../../../../domains/i18n'
5
+
6
+ const CollapseButton = ({ onClick }) => {
7
+ const { t } = useI18n()
8
+ const handleClick = useCallback(() => onClick(), [onClick])
9
+ return (
10
+ <button
11
+ type="button"
12
+ className={className('button', 'collapse-button')}
13
+ onClick={handleClick}
14
+ >
15
+ <Icon name="chevronDown" size="32" alt={t('window.srCollapseButton')} />
16
+ </button>
17
+ )
18
+ }
19
+
20
+ export default CollapseButton