@botpress/webchat 1.0.0 → 1.0.2

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 (105) hide show
  1. package/package.json +2 -1
  2. package/dist/vite.svg +0 -1
  3. package/index.html +0 -18
  4. package/public/vite.svg +0 -1
  5. package/src/App.tsx +0 -41
  6. package/src/Utils/colors.ts +0 -45
  7. package/src/Utils/eventEmitter.ts +0 -31
  8. package/src/Utils/index.ts +0 -2
  9. package/src/assets/check-circle-bold.svg +0 -5
  10. package/src/assets/chevron-up.svg +0 -3
  11. package/src/assets/file-05.svg +0 -6
  12. package/src/assets/globe-02.svg +0 -6
  13. package/src/assets/help-circle.svg +0 -3
  14. package/src/assets/info-circle.svg +0 -3
  15. package/src/assets/lock-01.svg +0 -4
  16. package/src/assets/mail-01.svg +0 -6
  17. package/src/assets/minus-circle.svg +0 -3
  18. package/src/assets/phone.svg +0 -6
  19. package/src/assets/send-03.svg +0 -4
  20. package/src/assets/share-04.svg +0 -5
  21. package/src/assets/slash-circle-01.svg +0 -3
  22. package/src/assets/x-circle-bold.svg +0 -5
  23. package/src/assets/x-close.svg +0 -3
  24. package/src/assets/x.svg +0 -3
  25. package/src/client/MessagingClient.ts +0 -87
  26. package/src/client/adapters/Audio.ts +0 -10
  27. package/src/client/adapters/Card.ts +0 -104
  28. package/src/client/adapters/Carousel.ts +0 -11
  29. package/src/client/adapters/Choice.ts +0 -48
  30. package/src/client/adapters/Dropdown.ts +0 -39
  31. package/src/client/adapters/File.ts +0 -10
  32. package/src/client/adapters/Image.ts +0 -10
  33. package/src/client/adapters/Location.ts +0 -18
  34. package/src/client/adapters/Message.ts +0 -26
  35. package/src/client/adapters/Text.ts +0 -11
  36. package/src/client/adapters/Utils.ts +0 -11
  37. package/src/client/adapters/Video.ts +0 -10
  38. package/src/client/adapters/Voice.ts +0 -9
  39. package/src/client/adapters/index.ts +0 -12
  40. package/src/client/index.ts +0 -2
  41. package/src/components/Avatar.tsx +0 -22
  42. package/src/components/Block.tsx +0 -17
  43. package/src/components/Composer.tsx +0 -115
  44. package/src/components/Container.tsx +0 -17
  45. package/src/components/Header.tsx +0 -141
  46. package/src/components/LoadingIndicator.tsx +0 -15
  47. package/src/components/Message.tsx +0 -52
  48. package/src/components/MessageList.tsx +0 -75
  49. package/src/components/Modal.tsx +0 -49
  50. package/src/components/RestartConversation.tsx +0 -52
  51. package/src/components/Webchat.tsx +0 -68
  52. package/src/components/dev-tools/DevTools.tsx +0 -496
  53. package/src/components/dev-tools/configuration.tsx +0 -27
  54. package/src/components/dev-tools/helpers.ts +0 -21
  55. package/src/components/index.ts +0 -12
  56. package/src/components/renderers/Audio.tsx +0 -11
  57. package/src/components/renderers/Bubble.tsx +0 -12
  58. package/src/components/renderers/Button.tsx +0 -59
  59. package/src/components/renderers/Carousel.tsx +0 -51
  60. package/src/components/renderers/Column.tsx +0 -22
  61. package/src/components/renderers/Dropdown.tsx +0 -170
  62. package/src/components/renderers/File.tsx +0 -13
  63. package/src/components/renderers/Image.tsx +0 -63
  64. package/src/components/renderers/Location.tsx +0 -16
  65. package/src/components/renderers/Row.tsx +0 -22
  66. package/src/components/renderers/Text.tsx +0 -32
  67. package/src/components/renderers/Video.tsx +0 -11
  68. package/src/components/renderers/index.ts +0 -28
  69. package/src/contexts/ComposerContext.ts +0 -16
  70. package/src/contexts/MessageContext.ts +0 -16
  71. package/src/contexts/ModalContext.ts +0 -19
  72. package/src/contexts/WebchatContext.ts +0 -61
  73. package/src/contexts/index.ts +0 -4
  74. package/src/hooks/index.ts +0 -3
  75. package/src/hooks/useImageSize.ts +0 -30
  76. package/src/hooks/useRefresh.ts +0 -33
  77. package/src/hooks/useWebchatStore.ts +0 -45
  78. package/src/index.css +0 -18
  79. package/src/index.ts +0 -3
  80. package/src/main.tsx +0 -33
  81. package/src/providers/ModalProvider.tsx +0 -35
  82. package/src/providers/WebchatProvider.tsx +0 -107
  83. package/src/providers/index.ts +0 -2
  84. package/src/schemas/index.ts +0 -1
  85. package/src/schemas/theme.ts +0 -188
  86. package/src/services/clipboard.ts +0 -8
  87. package/src/services/images.ts +0 -39
  88. package/src/services/index.ts +0 -3
  89. package/src/services/toast.tsx +0 -71
  90. package/src/themes/dawn.ts +0 -277
  91. package/src/themes/duskTheme.ts +0 -349
  92. package/src/themes/eggplant.ts +0 -353
  93. package/src/themes/galaxy.ts +0 -323
  94. package/src/themes/index.ts +0 -6
  95. package/src/themes/midnight.ts +0 -276
  96. package/src/themes/prism.ts +0 -349
  97. package/src/twind.config.ts +0 -31
  98. package/src/types/block-type.ts +0 -150
  99. package/src/types/image.ts +0 -10
  100. package/src/types/index.ts +0 -2
  101. package/src/vite-env.d.ts +0 -1
  102. package/tailwind.config.js +0 -0
  103. package/tsconfig.json +0 -30
  104. package/tsconfig.node.json +0 -10
  105. package/vite.config.ts +0 -31
@@ -1,11 +0,0 @@
1
- import { z } from 'zod'
2
- import { TextBlock } from '../../types'
3
- import { WithBubble, withBubble } from './Utils'
4
-
5
- export const TextSchema = z
6
- .object({
7
- type: z.literal('text'),
8
- text: z.string(),
9
- markdown: z.boolean().optional(),
10
- })
11
- .transform<WithBubble<TextBlock>>(({ type, text }) => withBubble({ type, text }))
@@ -1,11 +0,0 @@
1
- import { BlockObject, BubbleBlock } from '../../types'
2
-
3
- export type WithBubble<T extends BlockObject> = { block: T } & Omit<BubbleBlock, 'block'>
4
-
5
- //with bubble function with a generic type param
6
- export function withBubble<T extends BlockObject>(block: T): WithBubble<T> {
7
- return {
8
- block,
9
- type: 'bubble',
10
- }
11
- }
@@ -1,10 +0,0 @@
1
- import { z } from 'zod'
2
- import { VideoBlock } from '../../types'
3
-
4
- export const VideoSchema = z
5
- .object({
6
- type: z.literal('video'),
7
- video: z.string(),
8
- title: z.string().optional(),
9
- })
10
- .transform<VideoBlock>(({ type, video }) => ({ type, url: video }))
@@ -1,9 +0,0 @@
1
- import { z } from 'zod'
2
- import { AudioBlock } from '../../types'
3
-
4
- export const VoiceSchema = z
5
- .object({
6
- type: z.literal('voice'),
7
- audio: z.string(),
8
- })
9
- .transform<AudioBlock>(({ audio }) => ({ type: 'audio', url: audio }))
@@ -1,12 +0,0 @@
1
- export * from './Audio'
2
- export * from './Card'
3
- export * from './Carousel'
4
- export * from './Choice'
5
- export * from './File'
6
- export * from './Image'
7
- export * from './Location'
8
- export * from './Message'
9
- export * from './Text'
10
- export * from './Utils'
11
- export * from './Video'
12
- export * from './Voice'
@@ -1,2 +0,0 @@
1
- export * from './adapters'
2
- export * from './MessagingClient'
@@ -1,22 +0,0 @@
1
- import * as AvatarPrimitive from '@radix-ui/react-avatar'
2
- import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react'
3
-
4
- const Avatar = forwardRef<
5
- ElementRef<typeof AvatarPrimitive.Root>,
6
- ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
7
- >((props, ref) => <AvatarPrimitive.Root ref={ref} {...props} />)
8
- Avatar.displayName = AvatarPrimitive.Root.displayName
9
-
10
- const AvatarImage = forwardRef<
11
- ElementRef<typeof AvatarPrimitive.Image>,
12
- ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
13
- >((props, ref) => <AvatarPrimitive.Image ref={ref} {...props} />)
14
- AvatarImage.displayName = AvatarPrimitive.Image.displayName
15
-
16
- const AvatarFallback = forwardRef<
17
- ElementRef<typeof AvatarPrimitive.Fallback>,
18
- ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
19
- >((props, ref) => <AvatarPrimitive.Fallback ref={ref} {...props} />)
20
- AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
21
-
22
- export { Avatar, AvatarImage, AvatarFallback }
@@ -1,17 +0,0 @@
1
- import { ComponentType } from 'react'
2
- import { BlockMap, BlockTypes, CommonBlockProps } from '../types'
3
- import { renderers } from './renderers/index.ts'
4
- import { Renderers } from '../contexts/WebchatContext.ts'
5
-
6
- export const Block = <T extends BlockTypes>({
7
- block,
8
- styles,
9
- ...props
10
- }: {
11
- block: BlockMap[T]
12
- } & CommonBlockProps): JSX.Element => {
13
- const Block = renderers[block.type] as ComponentType<Renderers[T]>
14
-
15
- // This is a hack to get the types to work, this returns the correct component based on the block type.
16
- return <Block {...props} {...(block as any)} styles={styles} />
17
- }
@@ -1,115 +0,0 @@
1
- import { ComponentProps, FC, forwardRef, memo, useEffect, useRef, useState } from 'react'
2
- import { useWebchatContext, type StyleOptions, ComposerContext, useComposerContext } from '../contexts'
3
- import { ArrowUpCircleIcon } from '@heroicons/react/20/solid'
4
- import { Theme } from '../schemas'
5
- import { useWebchatStore } from '../hooks'
6
-
7
- const Composer = memo(
8
- forwardRef<HTMLDivElement, ComponentProps<'div'>>((props, ref) => {
9
- const {
10
- state,
11
- theme: { composer },
12
- } = useWebchatContext()
13
-
14
- const [value, setValue] = useState('')
15
-
16
- return (
17
- <ComposerContext.Provider value={{ value, setValue }}>
18
- <div data-disabled={state.disableComposer} {...props} {...composer?.container} ref={ref} />
19
- </ComposerContext.Provider>
20
- )
21
- })
22
- )
23
-
24
- const ComposerInput = memo(({ ...props }: ComponentProps<'textarea'>) => {
25
- const {
26
- state,
27
- theme: { composer },
28
- client,
29
- } = useWebchatContext()
30
- const { value, setValue } = useComposerContext()
31
-
32
- const ref = useRef<HTMLTextAreaElement>(null)
33
-
34
- const [historyIndex, setHistoryIndex] = useState(-1)
35
- const currentUser = useWebchatStore((state) => state.user)
36
- const messageHistory = useWebchatStore((state) => state.messageHistory)
37
-
38
- const sendComposerMessage = () => {
39
- if (!value) return
40
- void client.sendMessage(value)
41
- setValue('')
42
- }
43
-
44
- useEffect(() => {
45
- if (ref.current) {
46
- ref.current.selectionStart = ref.current.value.length
47
- ref.current.selectionEnd = ref.current.value.length
48
- }
49
- }, [value])
50
-
51
- return (
52
- <textarea
53
- {...composer?.input}
54
- disabled={state.disableComposer}
55
- ref={ref}
56
- value={value}
57
- data-has-value={!!value}
58
- onChange={(e) => setValue(e.target.value)}
59
- onKeyDown={(e) => {
60
- if (state.disableComposer) return
61
- if (e.key === 'Enter') {
62
- e.preventDefault()
63
- sendComposerMessage()
64
- setHistoryIndex(-1)
65
- }
66
- if (e.key === 'ArrowUp') {
67
- e.preventDefault()
68
- const userHistory = messageHistory[currentUser?.userId ?? ''] ?? []
69
- if (historyIndex < userHistory.length - 1) {
70
- setValue(userHistory[historyIndex + 1])
71
- }
72
- setHistoryIndex(() => Math.min(historyIndex + 1, userHistory.length - 1))
73
- }
74
- if (e.key === 'ArrowDown') {
75
- e.preventDefault()
76
- const userHistory = messageHistory[currentUser?.userId ?? ''] ?? []
77
- if (historyIndex === 0) {
78
- setValue('')
79
- } else {
80
- setValue(userHistory[historyIndex - 1])
81
- }
82
- setHistoryIndex(() => Math.max(historyIndex - 1, -1))
83
- }
84
- }}
85
- {...props}
86
- />
87
- )
88
- })
89
-
90
- type ComposerButtonStyle = NonNullable<Theme['composer']>['button']
91
- type ComposerButtonProps = {
92
- icon?: FC<StyleOptions>
93
- styles?: ComposerButtonStyle
94
- } & ComponentProps<'button'>
95
- const ComposerButton = memo(
96
- forwardRef<HTMLButtonElement, ComposerButtonProps>(({ icon: Icon = ArrowUpCircleIcon, ...props }, ref) => {
97
- //TODO: This should be in the context
98
- const { value, setValue } = useComposerContext()
99
- const sendComposerMessage = () => {
100
- if (!value) return
101
- setValue('')
102
- }
103
-
104
- const {
105
- theme: { composer },
106
- } = useWebchatContext()
107
-
108
- return (
109
- <button ref={ref} {...props} {...composer?.button?.container} disabled={!value} onClick={sendComposerMessage}>
110
- <Icon {...composer?.button?.icon} />
111
- </button>
112
- )
113
- })
114
- )
115
- export { Composer, ComposerInput, ComposerButton }
@@ -1,17 +0,0 @@
1
- import { ComponentProps, forwardRef } from 'react'
2
- import { useWebchatContext } from '../contexts'
3
- import { Toaster } from 'react-hot-toast'
4
- import { ModalProvider } from '../providers'
5
-
6
- export const Container = forwardRef<HTMLDivElement, ComponentProps<'div'>>(({ children, ...props }, ref) => {
7
- const {
8
- theme: { container },
9
- } = useWebchatContext()
10
-
11
- return (
12
- <div {...props} {...container} ref={ref}>
13
- <Toaster />
14
- <ModalProvider>{children}</ModalProvider>
15
- </div>
16
- )
17
- })
@@ -1,141 +0,0 @@
1
- import * as Collapsible from '@radix-ui/react-collapsible'
2
- import { ComponentProps, FC, PropsWithChildren, ReactNode, forwardRef, memo, useState } from 'react'
3
- import { Avatar, AvatarFallback, AvatarImage } from '.'
4
- import { StyleOptions, useWebchatContext } from '../contexts'
5
- import { ReactComponent as ShareIcon } from '../assets/share-04.svg'
6
- import { copyToClipboard } from '../services'
7
-
8
- type HeaderProps = {
9
- defaultOpen?: boolean
10
- open?: boolean
11
- disabled?: boolean
12
- onOpenChange?(open: boolean): void
13
- } & ComponentProps<'div'>
14
- const Header = memo(
15
- forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
16
- const {
17
- theme: { header },
18
- } = useWebchatContext()
19
- const [open, setOpen] = useState(false)
20
- return <Collapsible.Root {...props} {...header?.container} open={open} onOpenChange={setOpen} ref={ref} />
21
- })
22
- )
23
-
24
- type ContentProps = {
25
- asChild?: true | undefined
26
- } & ComponentProps<'button'>
27
- const Content = forwardRef<HTMLButtonElement, ContentProps>((props, ref) => {
28
- const {
29
- theme: { header },
30
- } = useWebchatContext()
31
- return <Collapsible.Trigger {...props} {...header?.content?.container} ref={ref} />
32
- })
33
-
34
- type ExpandedContentProps = {
35
- asChild?: true | undefined
36
- forceMount?: true | undefined
37
- } & ComponentProps<'div'>
38
- const ExpandedContent = forwardRef<HTMLDivElement, ExpandedContentProps>((props, ref) => {
39
- const {
40
- theme: { header },
41
- } = useWebchatContext()
42
- return <Collapsible.Content {...props} {...header?.expandedContent?.container} ref={ref} />
43
- })
44
-
45
- type HeaderAvatarProps = PropsWithChildren<{
46
- src?: string
47
- }>
48
- const HeaderAvatar = forwardRef<HTMLSpanElement, HeaderAvatarProps>(({ src, children }, ref) => {
49
- const {
50
- theme: { header },
51
- } = useWebchatContext()
52
- return (
53
- <Avatar ref={ref} {...header?.content?.avatar?.container}>
54
- <AvatarImage {...header?.content?.avatar?.image} src={src} />
55
- <AvatarFallback {...header?.content?.avatar?.fallback}>{children}</AvatarFallback>
56
- </Avatar>
57
- )
58
- })
59
-
60
- const Title = forwardRef<HTMLHeadingElement, ComponentProps<'h2'>>((props, ref) => {
61
- const {
62
- theme: { header },
63
- } = useWebchatContext()
64
- return <h2 {...props} {...header?.content?.title} ref={ref} />
65
- })
66
-
67
- const Description = forwardRef<HTMLParagraphElement, ComponentProps<'p'>>((props, ref) => {
68
- const {
69
- theme: { header },
70
- } = useWebchatContext()
71
- return <p {...props} {...header?.content?.description} ref={ref} />
72
- })
73
-
74
- const Actions = forwardRef<HTMLDivElement, ComponentProps<'div'>>((props, ref) => {
75
- const {
76
- theme: { header },
77
- } = useWebchatContext()
78
- return <div {...props} {...header?.content?.actions?.container} ref={ref} />
79
- })
80
-
81
- type DescriptionItemProps = {
82
- title?: ReactNode
83
- icon?: FC<StyleOptions>
84
- link?: string
85
- }
86
- const DescriptionItem = ({ icon, title, link, ...props }: DescriptionItemProps) => {
87
- const {
88
- theme: { header },
89
- } = useWebchatContext()
90
- const Icon = icon ?? ShareIcon
91
-
92
- const handleClick = (value: string | null) => {
93
- if (value) {
94
- copyToClipboard(value)
95
- }
96
- }
97
-
98
- if (!link && !title) {
99
- return null
100
- }
101
-
102
- //TODO: This can be refactored to be more generic
103
- if (link) {
104
- return (
105
- <a
106
- {...props}
107
- {...header?.expandedContent?.descriptionItems?.container}
108
- href={link}
109
- target="_blank"
110
- rel="noopener"
111
- >
112
- <Icon {...header?.expandedContent?.descriptionItems?.icon} />
113
- <p {...header?.expandedContent?.descriptionItems?.link}>{title}</p>
114
- </a>
115
- )
116
- }
117
-
118
- return (
119
- <div
120
- {...props}
121
- {...header?.expandedContent?.descriptionItems?.container}
122
- onClick={(e) => {
123
- handleClick(e.currentTarget.textContent)
124
- }}
125
- >
126
- <Icon {...header?.expandedContent?.descriptionItems?.icon} />
127
- <p {...header?.expandedContent?.descriptionItems?.text}>{title}</p>
128
- </div>
129
- )
130
- }
131
-
132
- const HeaderNamespace = Object.assign(Header, {
133
- Content,
134
- ExpandedContent,
135
- Avatar: HeaderAvatar,
136
- Description,
137
- Title,
138
- Actions,
139
- DescriptionItem,
140
- })
141
- export { HeaderNamespace as Header }
@@ -1,15 +0,0 @@
1
- import { useWebchatContext } from '../contexts'
2
- import { ComponentProps, memo } from 'react'
3
- import { clsx } from 'clsx'
4
-
5
- export const LoadingIndicator = memo(({ ...props }: ComponentProps<'div'>) => {
6
- const {
7
- theme: { loadingIndicator },
8
- } = useWebchatContext()
9
-
10
- return (
11
- <div {...props} {...loadingIndicator?.container}>
12
- <div {...loadingIndicator?.loader} />
13
- </div>
14
- )
15
- })
@@ -1,52 +0,0 @@
1
- import { ComponentProps, forwardRef, useEffect, useState } from 'react'
2
- import { useMount } from 'react-use'
3
- import { Avatar, AvatarFallback, AvatarImage } from '.'
4
- import { MessageContext } from '../contexts'
5
- import { useWebchatContext } from '../contexts'
6
- import type { MessageObject } from '../types'
7
- import { Block } from './Block'
8
-
9
- type MessageProps = {
10
- scroll: () => void
11
- } & Partial<MessageObject> &
12
- ComponentProps<'div'>
13
- export const Message = forwardRef<HTMLDivElement, MessageProps>(
14
- ({ direction, block, scroll, disableInput, children, sender, ...props }, ref) => {
15
- const [isLoading, setIsLoading] = useState<string[]>([])
16
-
17
- const {
18
- theme: { message: styles },
19
- configuration: { botAvatar, botName },
20
- } = useWebchatContext()
21
-
22
- useMount(() => {
23
- scroll()
24
- })
25
-
26
- useEffect(() => {
27
- if (isLoading.length === 0) {
28
- scroll()
29
- }
30
- }, [isLoading.length])
31
-
32
- return (
33
- <MessageContext.Provider value={{ isLoading, setIsLoading }}>
34
- <div
35
- {...props}
36
- {...styles?.container}
37
- data-loaded={isLoading.length === 0}
38
- data-disable-input={!!disableInput}
39
- data-direction={direction}
40
- ref={ref}
41
- >
42
- <Avatar {...styles?.avatar?.container}>
43
- <AvatarImage {...styles?.avatar?.image} src={sender?.avatar ?? botAvatar} />
44
- <AvatarFallback {...styles?.avatar?.fallback}>{sender?.name[0] ?? botName?.[0]}</AvatarFallback>
45
- </Avatar>
46
- {block && <Block block={block} styles={styles?.blocks} />}
47
- {children}
48
- </div>
49
- </MessageContext.Provider>
50
- )
51
- }
52
- )
@@ -1,75 +0,0 @@
1
- import { ComponentProps, memo, useEffect, useRef, useState } from 'react'
2
- import { Message } from '.'
3
- import { useWebchatContext } from '../contexts'
4
- import { useEffectOnce, useScroll } from 'react-use'
5
- import { LoadingIndicator } from './LoadingIndicator.tsx'
6
- import { useRefresh } from '../hooks'
7
-
8
- const TYPING_DELAY = 10000 // 10 seconds
9
-
10
- export const MessageList = memo(({ ...props }: ComponentProps<'ul'>) => {
11
- const {
12
- theme: { messageList },
13
- } = useWebchatContext()
14
- const { messages, setState, client } = useWebchatContext()
15
- const [isAtBottom, setIsAtBottom] = useState(true)
16
-
17
- const containerRef = useRef<HTMLUListElement>(null)
18
- const { y } = useScroll(containerRef)
19
-
20
- const [isTyping, setIsTyping] = useState(false)
21
-
22
- const [onRefresh, setRefreshDate] = useRefresh()
23
-
24
- const scroll = () => {
25
- if (!containerRef.current) return
26
- const { scrollHeight } = containerRef.current
27
- if (isAtBottom) {
28
- containerRef.current.scrollTo({ top: scrollHeight })
29
- }
30
- }
31
-
32
- useEffect(() => {
33
- const NineSecondsAgo = new Date(Date.now() - TYPING_DELAY + 2000)
34
- if (messages.length > 0 && messages[messages.length - 1].timestamp < NineSecondsAgo) {
35
- setIsTyping(false)
36
- }
37
- }, [onRefresh])
38
-
39
- useEffectOnce(() => {
40
- return client.on('messageSent', () => {
41
- setIsTyping(true)
42
- setRefreshDate(new Date(Date.now() + TYPING_DELAY))
43
- })
44
- })
45
-
46
- useEffectOnce(() => {
47
- return client.on('message', () => {
48
- setIsTyping(false)
49
- })
50
- })
51
-
52
- useEffect(() => {
53
- const lastMessage = messages[messages.length - 1]
54
- setState({ disableComposer: !!lastMessage?.disableInput })
55
- }, [messages.length])
56
-
57
- useEffect(() => {
58
- if (!containerRef.current) return
59
- const { offsetHeight, scrollHeight, scrollTop } = containerRef.current
60
- setIsAtBottom(scrollHeight <= scrollTop + offsetHeight + 100)
61
- }, [y])
62
-
63
- return (
64
- <ul {...props} {...messageList} ref={containerRef}>
65
- {messages.map((message, index) => (
66
- <Message scroll={scroll} key={index} {...message} />
67
- ))}
68
- {isTyping && (
69
- <Message scroll={scroll} direction={'incoming'}>
70
- <LoadingIndicator />
71
- </Message>
72
- )}
73
- </ul>
74
- )
75
- })
@@ -1,49 +0,0 @@
1
- import * as Dialog from '@radix-ui/react-dialog'
2
- import { ReactNode } from 'react'
3
- import { useWebchatContext } from '../contexts'
4
- import { XMarkIcon } from '@heroicons/react/24/outline'
5
-
6
- export function Modal({
7
- open,
8
- onOpenChange,
9
- children,
10
- }: {
11
- open?: boolean
12
- onOpenChange?: (open: boolean) => void
13
- children: ReactNode
14
- }) {
15
- return (
16
- <Dialog.Root open={open} onOpenChange={onOpenChange}>
17
- {children}
18
- </Dialog.Root>
19
- )
20
- }
21
-
22
- function ModalContent({ title, children }: { title: string; children: ReactNode }) {
23
- const {
24
- theme: { modal },
25
- } = useWebchatContext()
26
-
27
- return (
28
- // <Dialog.Portal>
29
- <>
30
- <Dialog.Overlay {...modal?.overlay} />
31
- <div {...modal?.container}>
32
- <Dialog.Content {...modal?.dialog?.container}>
33
- <div {...modal?.dialog?.title?.container}>
34
- <Dialog.Title {...modal?.dialog?.title?.text}>{title}</Dialog.Title>
35
- <Dialog.Close>
36
- <XMarkIcon role="button" tabIndex={0} {...modal?.dialog?.title?.closeIcon} />
37
- </Dialog.Close>
38
- </div>
39
- <div {...modal?.dialog?.content}>{children}</div>
40
- </Dialog.Content>
41
- </div>
42
- </>
43
- // </Dialog.Portal>
44
- )
45
- }
46
-
47
- Modal.Button = Dialog.Trigger
48
- Modal.Close = Dialog.Close
49
- Modal.Content = ModalContent
@@ -1,52 +0,0 @@
1
- import { ArrowPathIcon } from '@heroicons/react/20/solid'
2
- import { ComponentProps } from 'react'
3
- import { useModalContext, useWebchatContext } from '../contexts'
4
- import { Modal } from '.'
5
-
6
- type Props = {
7
- // onConfirm: () => void
8
- } & ComponentProps<typeof ArrowPathIcon>
9
-
10
- export const RestartConversation = ({ ...props }: Props) => {
11
- //TODO: Fix this, this was done quick to avoid the app component re-rendering
12
- const {
13
- theme: { header },
14
- client: { restartConversation: onConfirm },
15
- } = useWebchatContext()
16
-
17
- const { showModal } = useModalContext()
18
-
19
- const onSelect = (e: React.SyntheticEvent) => {
20
- e.stopPropagation()
21
- showModal({
22
- title: 'Restart Conversation',
23
- content: (
24
- <>
25
- <p>Are you sure you want to restart a new conversation?</p>
26
- <Modal.Close asChild>
27
- <button
28
- className="ml-auto mt-4 rounded-md border border-blue-700 bg-blue-600 px-2 py-1 text-white"
29
- onClick={onConfirm}
30
- >
31
- clear
32
- </button>
33
- </Modal.Close>
34
- </>
35
- ),
36
- })
37
- }
38
- return (
39
- <ArrowPathIcon
40
- {...props}
41
- {...header?.content?.actions?.icons}
42
- role="button"
43
- tabIndex={0}
44
- onClick={onSelect}
45
- onKeyDown={(e) => {
46
- if (e.key === 'Enter') {
47
- onSelect(e)
48
- }
49
- }}
50
- />
51
- )
52
- }