@seamly/web-ui 20.0.0-beta.2 → 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.
- package/build/dist/lib/index.debug.js +29 -7
- package/build/dist/lib/index.debug.min.js +1 -1
- package/build/dist/lib/index.debug.min.js.LICENSE.txt +8 -0
- package/build/dist/lib/index.js +285 -17
- package/build/dist/lib/index.min.js +1 -1
- package/build/dist/lib/standalone.js +283 -4
- package/build/dist/lib/standalone.min.js +1 -1
- package/build/dist/lib/style-guide.js +219 -70
- package/build/dist/lib/style-guide.min.js +1 -1
- package/build/dist/lib/styles.css +1 -1
- package/package.json +1 -1
- package/src/.DS_Store +0 -0
- package/src/javascripts/style-guide/components/app.js +3 -3
- package/src/javascripts/style-guide/components/view.js +0 -1
- package/src/javascripts/style-guide/states.js +278 -63
- package/src/javascripts/ui/components/faq/faq.js +162 -0
- package/src/javascripts/ui/components/layout/deprecated-app-frame.js +86 -0
- package/src/javascripts/ui/components/view/deprecated-view.js +6 -4
- package/src/stylesheets/5-components/_error.scss +20 -10
- package/src/stylesheets/7-deprecated/5-components/_error.scss +19 -9
- package/src/stylesheets/7-deprecated/5-components/_input.scss +1 -1
- package/src/stylesheets/7-deprecated/5-components/_options.scss +0 -2
- package/webpack/config.common.js +7 -1
- package/webpack/config.test.js +1 -0
- package/CHANGELOG.md +0 -625
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useEffect, useRef, useMemo } from 'preact/hooks'
|
|
2
|
+
import { className } from '../../../lib/css'
|
|
3
|
+
import Icon from '../layout/icon'
|
|
4
|
+
import { actionTypes } from '../../utils/seamly-utils'
|
|
5
|
+
import { runIfElementContainsOrHasFocus } from '../../utils/general-utils'
|
|
6
|
+
import useSeamlyCommands from '../../hooks/use-seamly-commands'
|
|
7
|
+
import {
|
|
8
|
+
useSeamlyServiceData,
|
|
9
|
+
useSeamlyLayoutMode,
|
|
10
|
+
} from '../../hooks/seamly-state-hooks'
|
|
11
|
+
import { useGeneratedId } from '../../hooks/utility-hooks'
|
|
12
|
+
import { useSkiplinkTargetFocusing } from '../../hooks/focus-helper-hooks'
|
|
13
|
+
import { useLiveRegion } from '../../hooks/live-region-hooks'
|
|
14
|
+
import useSeamlyIdleDetachCountdown from '../../hooks/use-seamly-idle-detach-countdown'
|
|
15
|
+
import useSeamlyResumeConversationPrompt from '../../hooks/use-seamly-resume-conversation-prompt'
|
|
16
|
+
import { useI18n } from '../../../domains/i18n'
|
|
17
|
+
import InOutTransition, {
|
|
18
|
+
transitionStartStates,
|
|
19
|
+
} from '../widgets/in-out-transition'
|
|
20
|
+
import { useTranslatedEventData } from '../../../domains/translations'
|
|
21
|
+
import { useInterrupt } from '../../../domains/interrupt'
|
|
22
|
+
import { useUserHasResponded } from '../../../domains/app'
|
|
23
|
+
|
|
24
|
+
const Faq = () => {
|
|
25
|
+
const { t } = useI18n()
|
|
26
|
+
const { sendAction, addMessageBubble } = useSeamlyCommands()
|
|
27
|
+
const sectionId = useGeneratedId()
|
|
28
|
+
const focusSkiplinkTarget = useSkiplinkTargetFocusing()
|
|
29
|
+
const { sendPolite } = useLiveRegion()
|
|
30
|
+
const { hasInterrupt } = useInterrupt()
|
|
31
|
+
const { hasCountdown, endCountdown } = useSeamlyIdleDetachCountdown()
|
|
32
|
+
const { hasPrompt, continueChat } = useSeamlyResumeConversationPrompt()
|
|
33
|
+
|
|
34
|
+
const lastFaqEventPayload = useSeamlyServiceData('suggestion')
|
|
35
|
+
const [eventBody] = useTranslatedEventData({ payload: lastFaqEventPayload })
|
|
36
|
+
const faqs = useMemo(() => {
|
|
37
|
+
const newFaqs = lastFaqEventPayload && !hasInterrupt ? eventBody : []
|
|
38
|
+
const itemBaseClass = `faqs__item`
|
|
39
|
+
return newFaqs.map(({ categories = [], ...faqRest }) => ({
|
|
40
|
+
...faqRest,
|
|
41
|
+
categories,
|
|
42
|
+
classNames: [
|
|
43
|
+
itemBaseClass,
|
|
44
|
+
...categories.map(
|
|
45
|
+
(cat) =>
|
|
46
|
+
`faqs__item--${String(cat)
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/[^a-z0-9_\\-]/, '')}`,
|
|
49
|
+
),
|
|
50
|
+
],
|
|
51
|
+
}))
|
|
52
|
+
}, [lastFaqEventPayload, hasInterrupt, eventBody])
|
|
53
|
+
|
|
54
|
+
const prevFaqs = useRef(null)
|
|
55
|
+
const prevHasFaqs = useRef(false)
|
|
56
|
+
|
|
57
|
+
const { isInline } = useSeamlyLayoutMode()
|
|
58
|
+
const hasResponded = useUserHasResponded()
|
|
59
|
+
const hideForWindow = !isInline && hasResponded
|
|
60
|
+
const prevHideForWindow = useRef(hideForWindow)
|
|
61
|
+
|
|
62
|
+
const hasFaqs = !!faqs.length
|
|
63
|
+
const showFaqContainer = hasFaqs && !hideForWindow
|
|
64
|
+
const previousRenderedFaqList = useRef([])
|
|
65
|
+
const renderedFaqList = hasFaqs ? faqs : previousRenderedFaqList.current
|
|
66
|
+
previousRenderedFaqList.current = renderedFaqList
|
|
67
|
+
|
|
68
|
+
const containerRef = useRef(null)
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (prevFaqs.current !== faqs && !hideForWindow) {
|
|
72
|
+
if (hasFaqs) {
|
|
73
|
+
const politeText = prevHasFaqs.current
|
|
74
|
+
? t('faq.srUpdatedText')
|
|
75
|
+
: t('faq.srAvailableText')
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
sendPolite(politeText)
|
|
78
|
+
}, 30)
|
|
79
|
+
} else if (prevHasFaqs.current) {
|
|
80
|
+
sendPolite(t('faq.srUnavailableText'))
|
|
81
|
+
}
|
|
82
|
+
prevFaqs.current = faqs
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!prevHideForWindow.current && hideForWindow) {
|
|
86
|
+
runIfElementContainsOrHasFocus(containerRef.current, focusSkiplinkTarget)
|
|
87
|
+
sendPolite(t('faq.srUnavailableText'))
|
|
88
|
+
} else if (!hasFaqs && prevHasFaqs.current) {
|
|
89
|
+
runIfElementContainsOrHasFocus(containerRef.current, focusSkiplinkTarget)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
prevHasFaqs.current = hasFaqs
|
|
93
|
+
prevHideForWindow.current = hideForWindow
|
|
94
|
+
}, [hasFaqs, faqs, hideForWindow, focusSkiplinkTarget, sendPolite, t])
|
|
95
|
+
|
|
96
|
+
const onFaqClickHandler = ({ id, question }) => {
|
|
97
|
+
if (hasCountdown) {
|
|
98
|
+
endCountdown(true)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (hasPrompt) {
|
|
102
|
+
continueChat()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
sendAction({
|
|
106
|
+
type: actionTypes.custom,
|
|
107
|
+
originMessage: lastFaqEventPayload.id,
|
|
108
|
+
body: {
|
|
109
|
+
type: 'faqclick',
|
|
110
|
+
body: {
|
|
111
|
+
faqId: id,
|
|
112
|
+
faqQuestion: question,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
addMessageBubble(question)
|
|
118
|
+
focusSkiplinkTarget()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const headingText = t('faq.headingText')
|
|
122
|
+
const ContainerElement = headingText ? 'section' : 'div'
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<InOutTransition
|
|
126
|
+
isActive={showFaqContainer}
|
|
127
|
+
transitionStartState={transitionStartStates.notRendered}
|
|
128
|
+
>
|
|
129
|
+
<ContainerElement
|
|
130
|
+
className={className('faqs')}
|
|
131
|
+
aria-labelledby={headingText ? sectionId : null}
|
|
132
|
+
ref={containerRef}
|
|
133
|
+
>
|
|
134
|
+
{headingText && (
|
|
135
|
+
<h2 id={sectionId} className={className('faqs__heading')}>
|
|
136
|
+
{headingText}
|
|
137
|
+
</h2>
|
|
138
|
+
)}
|
|
139
|
+
{!!renderedFaqList.length && (
|
|
140
|
+
<ul className={className('faqs__list')}>
|
|
141
|
+
{renderedFaqList.map((faq) => (
|
|
142
|
+
<li key={faq.id.toString()} className={className(faq.classNames)}>
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
onClick={() => {
|
|
146
|
+
onFaqClickHandler(faq)
|
|
147
|
+
}}
|
|
148
|
+
className={className('button', 'button--secondary')}
|
|
149
|
+
>
|
|
150
|
+
<Icon name="chevronRight" size="8" />
|
|
151
|
+
{faq.question}
|
|
152
|
+
</button>
|
|
153
|
+
</li>
|
|
154
|
+
))}
|
|
155
|
+
</ul>
|
|
156
|
+
)}
|
|
157
|
+
</ContainerElement>
|
|
158
|
+
</InOutTransition>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default Faq
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'preact/hooks'
|
|
2
|
+
import { className } from '../../../lib/css'
|
|
3
|
+
import {
|
|
4
|
+
useSeamlyAppContainerClassNames,
|
|
5
|
+
useSeamlyLayoutMode,
|
|
6
|
+
useSeamlyContainerElement,
|
|
7
|
+
} from '../../hooks/seamly-hooks'
|
|
8
|
+
import Faq from '../faq/faq'
|
|
9
|
+
import { useConfig } from '../../../domains/config'
|
|
10
|
+
import { useUserHasResponded } from '../../../domains/app'
|
|
11
|
+
import { useI18n } from '../../../domains/i18n'
|
|
12
|
+
import { useVisibility, visibilityStates } from '../../../domains/visibility'
|
|
13
|
+
|
|
14
|
+
const DeprecatedAppFrame = ({ children }) => {
|
|
15
|
+
const [, setSeamlyContainerElement] = useSeamlyContainerElement()
|
|
16
|
+
const { isOpen, isVisible, setVisibility } = useVisibility()
|
|
17
|
+
const { zIndex, showFaq, layoutMode } = useConfig()
|
|
18
|
+
const { isModal, isInline } = useSeamlyLayoutMode()
|
|
19
|
+
const appContainerClassNames = useSeamlyAppContainerClassNames()
|
|
20
|
+
const userResponded = useUserHasResponded()
|
|
21
|
+
const { locale } = useI18n()
|
|
22
|
+
|
|
23
|
+
const containerElementRef = useCallback(
|
|
24
|
+
(container) => {
|
|
25
|
+
setSeamlyContainerElement(container)
|
|
26
|
+
},
|
|
27
|
+
[setSeamlyContainerElement],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const blockLang = useMemo(() => {
|
|
31
|
+
if (locale) {
|
|
32
|
+
const htmlElementLang = document
|
|
33
|
+
.querySelector('html')
|
|
34
|
+
.getAttribute('lang')
|
|
35
|
+
if (htmlElementLang !== locale) {
|
|
36
|
+
return locale
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined
|
|
40
|
+
}, [locale])
|
|
41
|
+
|
|
42
|
+
const classNames = ['app', 'app--deprecated', ...appContainerClassNames]
|
|
43
|
+
|
|
44
|
+
if (!isOpen && layoutMode === 'window') {
|
|
45
|
+
classNames.push('app--collapsed')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (userResponded) {
|
|
49
|
+
classNames.push('app--user-responded')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
classNames.push(`app--layout-${layoutMode}`)
|
|
53
|
+
|
|
54
|
+
const onKeyDownHandler = (e) => {
|
|
55
|
+
if ((e.code && e.code === 'Escape') || e.keyCode === 27)
|
|
56
|
+
if (!isInline && isOpen) {
|
|
57
|
+
setVisibility(visibilityStates.minimized)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const onClickHandler = (e) => {
|
|
62
|
+
if (isModal) {
|
|
63
|
+
e.stopPropagation()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
isVisible && (
|
|
69
|
+
<section
|
|
70
|
+
className={className(classNames)}
|
|
71
|
+
onKeyDown={onKeyDownHandler}
|
|
72
|
+
onClick={onClickHandler}
|
|
73
|
+
lang={blockLang}
|
|
74
|
+
tabIndex="-1"
|
|
75
|
+
ref={containerElementRef}
|
|
76
|
+
style={{ zIndex }}
|
|
77
|
+
data-nosnippet
|
|
78
|
+
>
|
|
79
|
+
<div className={className('app-wrapper')}>{children}</div>
|
|
80
|
+
{showFaq && <Faq />}
|
|
81
|
+
</section>
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default DeprecatedAppFrame
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import DeprecatedAppFrame from '../layout/deprecated-app-frame'
|
|
2
2
|
import ChatFrame from '../layout/chat-frame'
|
|
3
3
|
import AgentInfo from '../layout/agent-info'
|
|
4
4
|
import Header from '../layout/header'
|
|
@@ -7,14 +7,16 @@ import EntryContainer from '../entry/entry-container'
|
|
|
7
7
|
import Interrupt from '../layout/interrupt'
|
|
8
8
|
import { useSeamlyChat } from '../../hooks/seamly-hooks'
|
|
9
9
|
import { useVisibility } from '../../../domains/visibility'
|
|
10
|
+
import DeprecatedToggleButton from '../entry/toggle-button'
|
|
10
11
|
|
|
11
12
|
const DeprecatedView = () => {
|
|
12
13
|
const { isVisible } = useVisibility()
|
|
13
|
-
const { closeChat } = useSeamlyChat()
|
|
14
|
+
const { closeChat, openChat } = useSeamlyChat()
|
|
14
15
|
|
|
15
16
|
return (
|
|
16
17
|
isVisible && (
|
|
17
|
-
<
|
|
18
|
+
<DeprecatedAppFrame>
|
|
19
|
+
<DeprecatedToggleButton onOpenChat={openChat} />
|
|
18
20
|
<Header onCloseChat={closeChat}>
|
|
19
21
|
<AgentInfo />
|
|
20
22
|
</Header>
|
|
@@ -22,7 +24,7 @@ const DeprecatedView = () => {
|
|
|
22
24
|
<Conversation />
|
|
23
25
|
<EntryContainer />
|
|
24
26
|
</ChatFrame>
|
|
25
|
-
</
|
|
27
|
+
</DeprecatedAppFrame>
|
|
26
28
|
)
|
|
27
29
|
)
|
|
28
30
|
}
|
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
// BASE
|
|
5
5
|
// ----
|
|
6
|
-
.#{$n}-
|
|
7
|
-
display:
|
|
8
|
-
align-items: flex-start;
|
|
6
|
+
.#{$n}-error {
|
|
7
|
+
display: block;
|
|
9
8
|
width: 100%;
|
|
10
|
-
margin: 0 0 $spacer * 0.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
font-weight: $fontweight-bold;
|
|
9
|
+
margin: 0 0 $spacer * 0.25;
|
|
10
|
+
|
|
11
|
+
&:empty {
|
|
12
|
+
display: none;
|
|
13
|
+
margin: 0;
|
|
14
|
+
}
|
|
17
15
|
|
|
18
16
|
.#{$n}-icon {
|
|
19
17
|
flex: 0 0 16px;
|
|
@@ -21,4 +19,16 @@
|
|
|
21
19
|
height: 16px;
|
|
22
20
|
margin-right: $spacer * 0.25;
|
|
23
21
|
}
|
|
22
|
+
|
|
23
|
+
.#{$n}-error__message {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: flex-start;
|
|
26
|
+
width: 100%;
|
|
27
|
+
padding: $spacer * 0.25 $spacer * 0.5;
|
|
28
|
+
border-radius: $borderradius-small;
|
|
29
|
+
background-color: rgba($negative, 0.1);
|
|
30
|
+
color: $negative-dark;
|
|
31
|
+
font-size: $fontsize-small;
|
|
32
|
+
font-weight: $fontweight-bold;
|
|
33
|
+
}
|
|
24
34
|
}
|
|
@@ -4,16 +4,14 @@
|
|
|
4
4
|
// BASE
|
|
5
5
|
// ----
|
|
6
6
|
.#{$n}-error {
|
|
7
|
-
display:
|
|
8
|
-
align-items: flex-start;
|
|
7
|
+
display: block;
|
|
9
8
|
width: 100%;
|
|
10
|
-
margin: 0 0 $spacer * 0.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
font-weight: $fontweight-bold;
|
|
9
|
+
margin: 0 0 $spacer * 0.25;
|
|
10
|
+
|
|
11
|
+
&:empty {
|
|
12
|
+
display: none;
|
|
13
|
+
margin: 0;
|
|
14
|
+
}
|
|
17
15
|
|
|
18
16
|
.#{$n}-icon {
|
|
19
17
|
flex: 0 0 16px;
|
|
@@ -21,4 +19,16 @@
|
|
|
21
19
|
height: 16px;
|
|
22
20
|
margin-right: $spacer * 0.25;
|
|
23
21
|
}
|
|
22
|
+
|
|
23
|
+
.#{$n}-error__message {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: flex-start;
|
|
26
|
+
width: 100%;
|
|
27
|
+
padding: $spacer * 0.25 $spacer * 0.5;
|
|
28
|
+
border-radius: $borderradius-small;
|
|
29
|
+
background-color: rgba($negative, 0.1);
|
|
30
|
+
color: $negative-dark;
|
|
31
|
+
font-size: $fontsize-small;
|
|
32
|
+
font-weight: $fontweight-bold;
|
|
33
|
+
}
|
|
24
34
|
}
|
package/webpack/config.common.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
const path = require('path')
|
|
3
|
+
const webpack = require('webpack')
|
|
3
4
|
const webpackMerge = require('webpack-merge').merge
|
|
4
5
|
const site = require('@seamly/doc-site/lib/config/site')
|
|
5
6
|
const BundleAnalyzerPlugin =
|
|
@@ -28,7 +29,12 @@ module.exports = (env = {}, argv = {}, configOverrides = {}) => {
|
|
|
28
29
|
|
|
29
30
|
const PRODUCTION = [argv.mode, process.env.NODE_ENV].includes('production')
|
|
30
31
|
|
|
31
|
-
const plugins = [
|
|
32
|
+
const plugins = [
|
|
33
|
+
new webpack.DefinePlugin({
|
|
34
|
+
'process.env.API_DOMAIN': JSON.stringify(process.env.API_DOMAIN),
|
|
35
|
+
}),
|
|
36
|
+
]
|
|
37
|
+
|
|
32
38
|
if (env.analyze) {
|
|
33
39
|
plugins.push(new BundleAnalyzerPlugin())
|
|
34
40
|
}
|
package/webpack/config.test.js
CHANGED
|
@@ -26,6 +26,7 @@ module.exports = (env = {}, argv = {}, configOverrides = {}, options = {}) => {
|
|
|
26
26
|
entry: {
|
|
27
27
|
// Demo and test files
|
|
28
28
|
'tests/index': path.join(PUBLIC_ROOT, '/tests/index.js'),
|
|
29
|
+
'tests/deprecated': path.join(PUBLIC_ROOT, '/tests/deprecated.js'),
|
|
29
30
|
'tests/style-guide': path.join(PUBLIC_ROOT, '/tests/style-guide.js'),
|
|
30
31
|
'tests/init-with-callback/index': path.join(
|
|
31
32
|
PUBLIC_ROOT,
|