@botonic/react 0.30.6 → 0.30.7-alpha.1

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 (63) hide show
  1. package/lib/cjs/contexts.js +3 -0
  2. package/lib/cjs/contexts.js.map +1 -1
  3. package/lib/cjs/index-types.d.ts +1 -0
  4. package/lib/cjs/webchat/actions.d.ts +2 -1
  5. package/lib/cjs/webchat/actions.js +1 -0
  6. package/lib/cjs/webchat/actions.js.map +1 -1
  7. package/lib/cjs/webchat/hooks/use-webchat.d.ts +1 -0
  8. package/lib/cjs/webchat/hooks/use-webchat.js +8 -0
  9. package/lib/cjs/webchat/hooks/use-webchat.js.map +1 -1
  10. package/lib/cjs/webchat/index-types.d.ts +1 -0
  11. package/lib/cjs/webchat/input-panel/textarea.js +8 -2
  12. package/lib/cjs/webchat/input-panel/textarea.js.map +1 -1
  13. package/lib/cjs/webchat/message-list/index.js +63 -35
  14. package/lib/cjs/webchat/message-list/index.js.map +1 -1
  15. package/lib/cjs/webchat/message-list/styles.js +3 -3
  16. package/lib/cjs/webchat/typing-indicator/index.d.ts +3 -1
  17. package/lib/cjs/webchat/typing-indicator/index.js +4 -3
  18. package/lib/cjs/webchat/typing-indicator/index.js.map +1 -1
  19. package/lib/cjs/webchat/typing-indicator/styles.d.ts +3 -2
  20. package/lib/cjs/webchat/typing-indicator/styles.js +6 -3
  21. package/lib/cjs/webchat/typing-indicator/styles.js.map +1 -1
  22. package/lib/cjs/webchat/webchat-reducer.js +2 -0
  23. package/lib/cjs/webchat/webchat-reducer.js.map +1 -1
  24. package/lib/cjs/webchat/webchat.js +2 -1
  25. package/lib/cjs/webchat/webchat.js.map +1 -1
  26. package/lib/esm/contexts.js +3 -0
  27. package/lib/esm/contexts.js.map +1 -1
  28. package/lib/esm/index-types.d.ts +1 -0
  29. package/lib/esm/webchat/actions.d.ts +2 -1
  30. package/lib/esm/webchat/actions.js +1 -0
  31. package/lib/esm/webchat/actions.js.map +1 -1
  32. package/lib/esm/webchat/hooks/use-webchat.d.ts +1 -0
  33. package/lib/esm/webchat/hooks/use-webchat.js +8 -0
  34. package/lib/esm/webchat/hooks/use-webchat.js.map +1 -1
  35. package/lib/esm/webchat/index-types.d.ts +1 -0
  36. package/lib/esm/webchat/input-panel/textarea.js +8 -2
  37. package/lib/esm/webchat/input-panel/textarea.js.map +1 -1
  38. package/lib/esm/webchat/message-list/index.js +62 -35
  39. package/lib/esm/webchat/message-list/index.js.map +1 -1
  40. package/lib/esm/webchat/message-list/styles.js +3 -3
  41. package/lib/esm/webchat/typing-indicator/index.d.ts +3 -1
  42. package/lib/esm/webchat/typing-indicator/index.js +5 -2
  43. package/lib/esm/webchat/typing-indicator/index.js.map +1 -1
  44. package/lib/esm/webchat/typing-indicator/styles.d.ts +3 -2
  45. package/lib/esm/webchat/typing-indicator/styles.js +5 -2
  46. package/lib/esm/webchat/typing-indicator/styles.js.map +1 -1
  47. package/lib/esm/webchat/webchat-reducer.js +2 -0
  48. package/lib/esm/webchat/webchat-reducer.js.map +1 -1
  49. package/lib/esm/webchat/webchat.js +2 -1
  50. package/lib/esm/webchat/webchat.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/contexts.tsx +3 -0
  53. package/src/index-types.ts +1 -0
  54. package/src/webchat/actions.ts +1 -0
  55. package/src/webchat/hooks/use-webchat.ts +9 -0
  56. package/src/webchat/index-types.ts +1 -0
  57. package/src/webchat/input-panel/textarea.tsx +12 -1
  58. package/src/webchat/message-list/index.tsx +79 -48
  59. package/src/webchat/message-list/styles.ts +3 -3
  60. package/src/webchat/typing-indicator/index.tsx +20 -12
  61. package/src/webchat/typing-indicator/styles.ts +7 -3
  62. package/src/webchat/webchat-reducer.ts +2 -0
  63. package/src/webchat/webchat.jsx +2 -0
@@ -3,13 +3,23 @@ import React, { useContext, useEffect, useRef, useState } from 'react'
3
3
  import { ROLES } from '../../constants'
4
4
  import { WebchatContext } from '../../contexts'
5
5
  import { BotonicContainerId } from '../constants'
6
- import { TypingIndicator } from '../typing-indicator'
6
+ import TypingIndicator from '../typing-indicator'
7
7
  import { IntroMessage } from './intro-message'
8
8
  import { ScrollButton } from './scroll-button'
9
9
  import { ContainerMessage, ScrollableMessageList } from './styles'
10
10
  import { UnreadMessagesBanner } from './unread-messages-banner'
11
11
  import { useNotifications } from './use-notifications'
12
12
 
13
+ const SCROLL_TIMEOUT = 200
14
+ const scrollOptionsEnd: ScrollIntoViewOptions = {
15
+ behavior: 'smooth',
16
+ block: 'end',
17
+ }
18
+ const scrollOptionsCenter: ScrollIntoViewOptions = {
19
+ behavior: 'smooth',
20
+ block: 'center',
21
+ }
22
+
13
23
  export const WebchatMessageList = () => {
14
24
  const {
15
25
  webchatState,
@@ -18,39 +28,50 @@ export const WebchatMessageList = () => {
18
28
  scrollableMessagesListRef,
19
29
  } = useContext(WebchatContext)
20
30
 
21
- const [firstUnreadMessageId, setFirstUnreadMessageId] = useState()
31
+ const { notificationsEnabled } = useNotifications()
22
32
 
23
- const lastMessageBottomRef = useRef<HTMLDivElement>(null)
33
+ const [firstUnreadMessageId, setFirstUnreadMessageId] = useState<string>()
24
34
 
25
- const scrollToBottom = () => {
35
+ const lastMessageRef = useRef<HTMLDivElement>(null)
36
+ const typingRef = useRef<HTMLDivElement>(null)
37
+ const unreadMessagesBannerRef = useRef<HTMLDivElement>(null)
38
+
39
+ const scrollToTyping = () => {
26
40
  setTimeout(() => {
27
- lastMessageBottomRef.current?.scrollIntoView({
28
- behavior: 'smooth',
29
- block: 'end',
30
- })
31
- }, 100)
41
+ typingRef.current?.scrollIntoView(scrollOptionsEnd)
42
+ }, SCROLL_TIMEOUT)
32
43
  }
33
44
 
34
- const handleScrollToBottom = () => {
35
- resetUnreadMessages()
36
- scrollToBottom()
45
+ const scrollToLastMessage = () => {
46
+ setTimeout(() => {
47
+ lastMessageRef.current?.scrollIntoView(scrollOptionsEnd)
48
+ }, SCROLL_TIMEOUT)
37
49
  }
38
50
 
39
- const unreadMessagesBannerRef = useRef<HTMLDivElement>(null)
40
-
41
51
  const scrollToBanner = () => {
42
52
  setTimeout(() => {
43
- unreadMessagesBannerRef.current?.scrollIntoView({
44
- behavior: 'smooth',
45
- block: 'center',
46
- })
47
- }, 100)
53
+ unreadMessagesBannerRef.current?.scrollIntoView(scrollOptionsCenter)
54
+ }, SCROLL_TIMEOUT)
48
55
  }
49
56
 
50
- const showUnreadMessagesBanner = (messageComponentId: string) =>
51
- firstUnreadMessageId &&
52
- messageComponentId === firstUnreadMessageId &&
53
- webchatState.numUnreadMessages > 0
57
+ const handleScrollToBottom = () => {
58
+ resetUnreadMessages()
59
+ if (webchatState.typing) {
60
+ scrollToTyping()
61
+ return
62
+ }
63
+
64
+ scrollToLastMessage()
65
+ }
66
+
67
+ const showUnreadMessagesBanner = (messageComponentId: string) => {
68
+ return (
69
+ !webchatState.isInputFocused &&
70
+ firstUnreadMessageId &&
71
+ messageComponentId === firstUnreadMessageId &&
72
+ webchatState.numUnreadMessages > 0
73
+ )
74
+ }
54
75
 
55
76
  useEffect(() => {
56
77
  const firstUnreadMessage = webchatState.messagesComponents.find(
@@ -60,39 +81,47 @@ export const WebchatMessageList = () => {
60
81
  }, [webchatState.messagesComponents])
61
82
 
62
83
  useEffect(() => {
63
- if (
64
- webchatState.messagesComponents.length > 0 &&
65
- lastMessageBottomRef.current
66
- ) {
84
+ if (webchatState.messagesComponents.length > 0 && lastMessageRef.current) {
67
85
  const observer = new IntersectionObserver(entries => {
68
86
  entries.forEach(entry => {
69
87
  setLastMessageVisible(entry.isIntersecting)
70
88
  })
71
89
  })
72
- observer.observe(lastMessageBottomRef.current)
90
+ observer.observe(lastMessageRef.current)
73
91
  }
74
92
  }, [webchatState.messagesComponents])
75
93
 
76
- const { notificationsEnabled } = useNotifications()
77
-
78
94
  useEffect(() => {
79
95
  if (!notificationsEnabled) {
80
- scrollToBottom()
81
- return
96
+ if (webchatState.typing) {
97
+ scrollToTyping()
98
+ return
99
+ }
100
+
101
+ scrollToLastMessage()
82
102
  }
83
- }, [webchatState.typing])
103
+ }, [webchatState.typing, webchatState.messagesComponents])
84
104
 
85
105
  useEffect(() => {
86
- if (notificationsEnabled) {
87
- if (webchatState.isWebchatOpen && unreadMessagesBannerRef.current) {
106
+ if (webchatState.isWebchatOpen && notificationsEnabled) {
107
+ if (unreadMessagesBannerRef.current) {
88
108
  scrollToBanner()
89
109
  return
90
110
  }
91
111
 
92
- scrollToBottom()
93
- return
112
+ if (webchatState.typing) {
113
+ scrollToTyping()
114
+ return
115
+ }
116
+
117
+ scrollToLastMessage()
94
118
  }
95
- }, [firstUnreadMessageId, webchatState.isWebchatOpen, webchatState.typing])
119
+ }, [
120
+ firstUnreadMessageId,
121
+ webchatState.isWebchatOpen,
122
+ webchatState.typing,
123
+ webchatState.messagesComponents,
124
+ ])
96
125
 
97
126
  const showScrollButton =
98
127
  webchatState.numUnreadMessages > 0 && !webchatState.isLastMessageVisible
@@ -107,25 +136,27 @@ export const WebchatMessageList = () => {
107
136
  <IntroMessage />
108
137
  {webchatState.messagesComponents.map((messageComponent, index) => {
109
138
  return (
110
- <ContainerMessage role={ROLES.MESSAGE} key={index}>
111
- {showUnreadMessagesBanner(messageComponent.props.id) && (
112
- <UnreadMessagesBanner
113
- unreadMessagesBannerRef={unreadMessagesBannerRef}
114
- />
115
- )}
116
- {messageComponent}
139
+ <>
140
+ <ContainerMessage role={ROLES.MESSAGE} key={index}>
141
+ {showUnreadMessagesBanner(messageComponent.props.id) && (
142
+ <UnreadMessagesBanner
143
+ unreadMessagesBannerRef={unreadMessagesBannerRef}
144
+ />
145
+ )}
146
+ {messageComponent}
147
+ </ContainerMessage>
117
148
  {index === webchatState.messagesComponents.length - 1 && (
118
149
  <div
119
- ref={lastMessageBottomRef}
150
+ ref={lastMessageRef}
120
151
  style={{
121
152
  content: '',
122
153
  }}
123
154
  ></div>
124
155
  )}
125
- </ContainerMessage>
156
+ </>
126
157
  )
127
158
  })}
128
- {webchatState.typing && <TypingIndicator />}
159
+ {webchatState.typing && <TypingIndicator ref={typingRef} />}
129
160
  </ScrollableMessageList>
130
161
  {showScrollButton && <ScrollButton handleClick={handleScrollToBottom} />}
131
162
  </>
@@ -15,9 +15,9 @@ export const DefaultIntroImage = styled.img`
15
15
  `
16
16
 
17
17
  export const ContainerScrollButton = styled.div`
18
- position: absolute;
19
- right: 10px;
20
- bottom: 65px;
18
+ position: sticky;
19
+ left: 85%;
20
+ bottom: 15px;
21
21
 
22
22
  background-color: #6d6a78;
23
23
  cursor: pointer;
@@ -1,16 +1,24 @@
1
- import React from 'react'
1
+ import React, { ForwardedRef, forwardRef } from 'react'
2
2
 
3
3
  import { COLORS, ROLES } from '../../constants'
4
- import { Dot, TypingIndicatorWrapper } from './styles'
4
+ import { Dot, TypingContainer, TypingMsgWrapper } from './styles'
5
5
 
6
- export const TypingIndicator = () => (
7
- <TypingIndicatorWrapper
8
- role={ROLES.TYPING_INDICATOR}
9
- className='typing-indicator'
10
- backgroundColor={COLORS.SEASHELL_WHITE}
11
- >
12
- <Dot />
13
- <Dot />
14
- <Dot />
15
- </TypingIndicatorWrapper>
6
+ const TypingIndicator = forwardRef<HTMLDivElement, any>(
7
+ (_props, ref: ForwardedRef<HTMLDivElement>) => (
8
+ <TypingContainer ref={ref}>
9
+ <TypingMsgWrapper
10
+ role={ROLES.TYPING_INDICATOR}
11
+ className='typing-indicator'
12
+ backgroundColor={COLORS.SEASHELL_WHITE}
13
+ >
14
+ <Dot />
15
+ <Dot />
16
+ <Dot />
17
+ </TypingMsgWrapper>
18
+ </TypingContainer>
19
+ )
16
20
  )
21
+
22
+ TypingIndicator.displayName = 'TypingIndicator'
23
+
24
+ export default TypingIndicator
@@ -12,16 +12,20 @@ const bulge = keyframes`
12
12
  }
13
13
  `
14
14
 
15
- interface TypingIndicatorWrapperProps {
15
+ export const TypingContainer = styled.div`
16
+ padding: 0px 8px 8px 8px;
17
+ `
18
+
19
+ interface TypingMsgWrapperProps {
16
20
  backgroundColor: string
17
21
  }
18
22
 
19
- export const TypingIndicatorWrapper = styled.div<TypingIndicatorWrapperProps>`
23
+ export const TypingMsgWrapper = styled.div<TypingMsgWrapperProps>`
20
24
  will-change: transform;
21
25
  width: 44px;
22
26
  line-height: 0px;
23
27
  border-radius: 20px;
24
- padding: 8px 2px 8px;
28
+ padding: 8px 2px;
25
29
  text-align: center;
26
30
  display: block;
27
31
  margin: 8px;
@@ -48,6 +48,8 @@ export function webchatReducer(
48
48
  return { ...state, lastRoutePath: action.payload }
49
49
  case WebchatAction.SET_CURRENT_ATTACHMENT:
50
50
  return { ...state, currentAttachment: action.payload }
51
+ case WebchatAction.SET_IS_INPUT_FOCUSED:
52
+ return { ...state, isInputFocused: action.payload }
51
53
  default:
52
54
  return messagesReducer(state, action)
53
55
  }
@@ -121,6 +121,7 @@ export const Webchat = forwardRef((props, ref) => {
121
121
  resetUnreadMessages,
122
122
  setCurrentAttachment,
123
123
  setError,
124
+ setIsInputFocused,
124
125
  setLastMessageVisible,
125
126
  setOnline,
126
127
  toggleCoverComponent,
@@ -682,6 +683,7 @@ export const Webchat = forwardRef((props, ref) => {
682
683
  openWebview,
683
684
  resolveCase,
684
685
  resetUnreadMessages,
686
+ setIsInputFocused,
685
687
  setLastMessageVisible,
686
688
  sendAttachment,
687
689
  sendInput,