@botpress/webchat 1.0.0 → 1.0.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.
- package/package.json +2 -1
- package/dist/vite.svg +0 -1
- package/index.html +0 -18
- package/public/vite.svg +0 -1
- package/src/App.tsx +0 -41
- package/src/Utils/colors.ts +0 -45
- package/src/Utils/eventEmitter.ts +0 -31
- package/src/Utils/index.ts +0 -2
- package/src/assets/check-circle-bold.svg +0 -5
- package/src/assets/chevron-up.svg +0 -3
- package/src/assets/file-05.svg +0 -6
- package/src/assets/globe-02.svg +0 -6
- package/src/assets/help-circle.svg +0 -3
- package/src/assets/info-circle.svg +0 -3
- package/src/assets/lock-01.svg +0 -4
- package/src/assets/mail-01.svg +0 -6
- package/src/assets/minus-circle.svg +0 -3
- package/src/assets/phone.svg +0 -6
- package/src/assets/send-03.svg +0 -4
- package/src/assets/share-04.svg +0 -5
- package/src/assets/slash-circle-01.svg +0 -3
- package/src/assets/x-circle-bold.svg +0 -5
- package/src/assets/x-close.svg +0 -3
- package/src/assets/x.svg +0 -3
- package/src/client/MessagingClient.ts +0 -87
- package/src/client/adapters/Audio.ts +0 -10
- package/src/client/adapters/Card.ts +0 -104
- package/src/client/adapters/Carousel.ts +0 -11
- package/src/client/adapters/Choice.ts +0 -48
- package/src/client/adapters/Dropdown.ts +0 -39
- package/src/client/adapters/File.ts +0 -10
- package/src/client/adapters/Image.ts +0 -10
- package/src/client/adapters/Location.ts +0 -18
- package/src/client/adapters/Message.ts +0 -26
- package/src/client/adapters/Text.ts +0 -11
- package/src/client/adapters/Utils.ts +0 -11
- package/src/client/adapters/Video.ts +0 -10
- package/src/client/adapters/Voice.ts +0 -9
- package/src/client/adapters/index.ts +0 -12
- package/src/client/index.ts +0 -2
- package/src/components/Avatar.tsx +0 -22
- package/src/components/Block.tsx +0 -17
- package/src/components/Composer.tsx +0 -115
- package/src/components/Container.tsx +0 -17
- package/src/components/Header.tsx +0 -141
- package/src/components/LoadingIndicator.tsx +0 -15
- package/src/components/Message.tsx +0 -52
- package/src/components/MessageList.tsx +0 -75
- package/src/components/Modal.tsx +0 -49
- package/src/components/RestartConversation.tsx +0 -52
- package/src/components/Webchat.tsx +0 -68
- package/src/components/dev-tools/DevTools.tsx +0 -496
- package/src/components/dev-tools/configuration.tsx +0 -27
- package/src/components/dev-tools/helpers.ts +0 -21
- package/src/components/index.ts +0 -12
- package/src/components/renderers/Audio.tsx +0 -11
- package/src/components/renderers/Bubble.tsx +0 -12
- package/src/components/renderers/Button.tsx +0 -59
- package/src/components/renderers/Carousel.tsx +0 -51
- package/src/components/renderers/Column.tsx +0 -22
- package/src/components/renderers/Dropdown.tsx +0 -170
- package/src/components/renderers/File.tsx +0 -13
- package/src/components/renderers/Image.tsx +0 -63
- package/src/components/renderers/Location.tsx +0 -16
- package/src/components/renderers/Row.tsx +0 -22
- package/src/components/renderers/Text.tsx +0 -32
- package/src/components/renderers/Video.tsx +0 -11
- package/src/components/renderers/index.ts +0 -28
- package/src/contexts/ComposerContext.ts +0 -16
- package/src/contexts/MessageContext.ts +0 -16
- package/src/contexts/ModalContext.ts +0 -19
- package/src/contexts/WebchatContext.ts +0 -61
- package/src/contexts/index.ts +0 -4
- package/src/hooks/index.ts +0 -3
- package/src/hooks/useImageSize.ts +0 -30
- package/src/hooks/useRefresh.ts +0 -33
- package/src/hooks/useWebchatStore.ts +0 -45
- package/src/index.css +0 -18
- package/src/index.ts +0 -3
- package/src/main.tsx +0 -33
- package/src/providers/ModalProvider.tsx +0 -35
- package/src/providers/WebchatProvider.tsx +0 -107
- package/src/providers/index.ts +0 -2
- package/src/schemas/index.ts +0 -1
- package/src/schemas/theme.ts +0 -188
- package/src/services/clipboard.ts +0 -8
- package/src/services/images.ts +0 -39
- package/src/services/index.ts +0 -3
- package/src/services/toast.tsx +0 -71
- package/src/themes/dawn.ts +0 -277
- package/src/themes/duskTheme.ts +0 -349
- package/src/themes/eggplant.ts +0 -353
- package/src/themes/galaxy.ts +0 -323
- package/src/themes/index.ts +0 -6
- package/src/themes/midnight.ts +0 -276
- package/src/themes/prism.ts +0 -349
- package/src/twind.config.ts +0 -31
- package/src/types/block-type.ts +0 -150
- package/src/types/image.ts +0 -10
- package/src/types/index.ts +0 -2
- package/src/vite-env.d.ts +0 -1
- package/tailwind.config.js +0 -0
- package/tsconfig.json +0 -30
- package/tsconfig.node.json +0 -10
- package/vite.config.ts +0 -31
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { ComponentProps, useEffect, useState } from 'react'
|
|
2
|
-
import { ButtonBlock, CommonBlockProps } from '../../types'
|
|
3
|
-
import { useWebchatContext } from '../../contexts'
|
|
4
|
-
|
|
5
|
-
type Props = CommonBlockProps & ButtonBlock
|
|
6
|
-
export const Button = ({ text, buttonValue, type, styles, variant, groupId, reusable, ...props }: Props) => {
|
|
7
|
-
const { eventEmitter, client } = useWebchatContext()
|
|
8
|
-
const [activated, setActivated] = useState(false)
|
|
9
|
-
const [groupActivated, setGroupActivated] = useState(false)
|
|
10
|
-
const [disabled, setDisabled] = useState(false)
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
if (!groupId) return
|
|
14
|
-
return eventEmitter.on(`button-group-${groupId}-click`, () => {
|
|
15
|
-
setGroupActivated(true)
|
|
16
|
-
if (!reusable) setDisabled(true)
|
|
17
|
-
})
|
|
18
|
-
}, [eventEmitter, groupId, reusable])
|
|
19
|
-
|
|
20
|
-
function onClick() {
|
|
21
|
-
if (groupId) {
|
|
22
|
-
eventEmitter.emit(`button-group-${groupId}-click`)
|
|
23
|
-
}
|
|
24
|
-
setActivated(true)
|
|
25
|
-
if (!reusable) setDisabled(true)
|
|
26
|
-
if (variant === 'link') return
|
|
27
|
-
client.sendMessage(buttonValue)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<ButtonOrAnchor
|
|
32
|
-
{...props}
|
|
33
|
-
variant={variant}
|
|
34
|
-
onClick={onClick}
|
|
35
|
-
disabled={disabled}
|
|
36
|
-
data-activated={activated ? '' : undefined}
|
|
37
|
-
data-group-activated={groupActivated ? '' : undefined}
|
|
38
|
-
data-type={variant}
|
|
39
|
-
value={buttonValue}
|
|
40
|
-
{...styles?.[type]}
|
|
41
|
-
>
|
|
42
|
-
{text}
|
|
43
|
-
</ButtonOrAnchor>
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
type ButtonOrAnchorProps =
|
|
47
|
-
| ({ variant: 'action' } & ComponentProps<'button'>)
|
|
48
|
-
| ({ variant: 'link'; value: string } & ComponentProps<'a'>)
|
|
49
|
-
const ButtonOrAnchor = (props: ButtonOrAnchorProps) => {
|
|
50
|
-
if (props.variant === 'link') {
|
|
51
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
52
|
-
const { value, variant, ...rest } = props
|
|
53
|
-
return <a {...rest} href={value} target="_blank" rel="noopener noreferrer" />
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
57
|
-
const { variant, ...rest } = props
|
|
58
|
-
return <button {...rest} />
|
|
59
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { forwardRef, useCallback, useEffect, useState } from 'react'
|
|
2
|
-
import { CarouselBlock, CommonBlockProps } from '../../types'
|
|
3
|
-
import useEmblaCarousel, { EmblaCarouselType } from 'embla-carousel-react'
|
|
4
|
-
import { Block } from '../Block'
|
|
5
|
-
import { ChevronRightIcon, ChevronLeftIcon } from '@heroicons/react/24/solid'
|
|
6
|
-
|
|
7
|
-
type Props = CommonBlockProps & CarouselBlock
|
|
8
|
-
export const Carousel = forwardRef<HTMLDivElement, Props>(({ type, styles, blocks }, ref) => {
|
|
9
|
-
const [emblaRef, emblaApi] = useEmblaCarousel({ skipSnaps: true })
|
|
10
|
-
|
|
11
|
-
const [prevBtnDisabled, setPrevBtnDisabled] = useState(true)
|
|
12
|
-
const [nextBtnDisabled, setNextBtnDisabled] = useState(true)
|
|
13
|
-
|
|
14
|
-
const scrollPrev = useCallback(() => emblaApi && emblaApi.scrollPrev(), [emblaApi])
|
|
15
|
-
const scrollNext = useCallback(() => emblaApi && emblaApi.scrollNext(), [emblaApi])
|
|
16
|
-
|
|
17
|
-
const carouselStyles = styles?.[type]
|
|
18
|
-
|
|
19
|
-
const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
|
|
20
|
-
setPrevBtnDisabled(!emblaApi.canScrollPrev())
|
|
21
|
-
setNextBtnDisabled(!emblaApi.canScrollNext())
|
|
22
|
-
}, [])
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (!emblaApi) return
|
|
26
|
-
onSelect(emblaApi)
|
|
27
|
-
emblaApi.on('reInit', onSelect)
|
|
28
|
-
emblaApi.on('select', onSelect)
|
|
29
|
-
}, [emblaApi, onSelect])
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div {...carouselStyles?.container} data-container="carousel" ref={emblaRef}>
|
|
33
|
-
<div ref={ref} {...carouselStyles?.slidesContainer}>
|
|
34
|
-
{blocks.map((block, index) => (
|
|
35
|
-
<Block key={index} styles={styles} block={block} />
|
|
36
|
-
))}
|
|
37
|
-
</div>
|
|
38
|
-
<ChevronLeftIcon
|
|
39
|
-
{...carouselStyles?.backButton}
|
|
40
|
-
data-disabled={prevBtnDisabled ? '' : undefined}
|
|
41
|
-
onClick={scrollPrev}
|
|
42
|
-
/>
|
|
43
|
-
|
|
44
|
-
<ChevronRightIcon
|
|
45
|
-
{...carouselStyles?.nextButton}
|
|
46
|
-
data-disabled={nextBtnDisabled ? '' : undefined}
|
|
47
|
-
onClick={scrollNext}
|
|
48
|
-
/>
|
|
49
|
-
</div>
|
|
50
|
-
)
|
|
51
|
-
})
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ComponentProps, forwardRef } from 'react'
|
|
2
|
-
import { ColumnBlock, CommonBlockProps } from '../../types'
|
|
3
|
-
import { Block } from '../Block'
|
|
4
|
-
|
|
5
|
-
type Props = CommonBlockProps & ColumnBlock & ComponentProps<'div'>
|
|
6
|
-
export const Column = forwardRef<HTMLDivElement, Props>(
|
|
7
|
-
({ blocks, styles, type, horizontalAlignment, verticalAlignment, ...props }, ref) => {
|
|
8
|
-
return (
|
|
9
|
-
<div
|
|
10
|
-
data-horizontal={horizontalAlignment}
|
|
11
|
-
data-vertical={verticalAlignment}
|
|
12
|
-
{...props}
|
|
13
|
-
{...styles?.[type]}
|
|
14
|
-
ref={ref}
|
|
15
|
-
>
|
|
16
|
-
{blocks.map((block, index) => (
|
|
17
|
-
<Block key={index} styles={styles} block={block} />
|
|
18
|
-
))}
|
|
19
|
-
</div>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
)
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
// export const Dropdown = ({ styles, type, options, label, ...props }: Props) => {
|
|
2
|
-
import * as React from 'react'
|
|
3
|
-
import {
|
|
4
|
-
useFloating,
|
|
5
|
-
useClick,
|
|
6
|
-
useDismiss,
|
|
7
|
-
useRole,
|
|
8
|
-
useListNavigation,
|
|
9
|
-
useInteractions,
|
|
10
|
-
FloatingFocusManager,
|
|
11
|
-
useTypeahead,
|
|
12
|
-
offset,
|
|
13
|
-
flip,
|
|
14
|
-
size,
|
|
15
|
-
autoUpdate,
|
|
16
|
-
FloatingPortal,
|
|
17
|
-
} from '@floating-ui/react'
|
|
18
|
-
import { CommonBlockProps, DropdownBlock } from '../../types'
|
|
19
|
-
import { ComponentProps } from 'react'
|
|
20
|
-
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
|
21
|
-
import { clsx } from 'clsx'
|
|
22
|
-
import { useWebchatContext } from '../../contexts'
|
|
23
|
-
|
|
24
|
-
// const options = ['Red', 'Orange', 'Yellow', 'Green', 'Cyan', 'Blue', 'Purple', 'Pink', 'Maroon', 'Black', 'White']
|
|
25
|
-
type Props = CommonBlockProps & DropdownBlock & ComponentProps<'div'>
|
|
26
|
-
|
|
27
|
-
export const Dropdown = ({ styles, type, options, label, reusable }: Props) => {
|
|
28
|
-
const [isOpen, setIsOpen] = React.useState(false)
|
|
29
|
-
const [activeIndex, setActiveIndex] = React.useState<number | null>(null)
|
|
30
|
-
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(null)
|
|
31
|
-
const { client } = useWebchatContext()
|
|
32
|
-
|
|
33
|
-
const optionsDict = options.reduce((acc, option) => {
|
|
34
|
-
acc[option.label] = option.value
|
|
35
|
-
return acc
|
|
36
|
-
}, {} as { [key: string]: string })
|
|
37
|
-
|
|
38
|
-
const labels = optionsDict ? Object.keys(optionsDict) : []
|
|
39
|
-
|
|
40
|
-
const onOpenChange = (open: boolean) => {
|
|
41
|
-
if (selectedIndex !== null && !reusable) {
|
|
42
|
-
setIsOpen(false)
|
|
43
|
-
} else {
|
|
44
|
-
setIsOpen(open)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const { refs, floatingStyles, context } = useFloating({
|
|
49
|
-
placement: 'bottom-start',
|
|
50
|
-
open: isOpen,
|
|
51
|
-
onOpenChange,
|
|
52
|
-
whileElementsMounted: autoUpdate,
|
|
53
|
-
middleware: [
|
|
54
|
-
offset(5),
|
|
55
|
-
flip({ padding: 10 }),
|
|
56
|
-
size({
|
|
57
|
-
apply({ rects, elements, availableHeight }) {
|
|
58
|
-
Object.assign(elements.floating.style, {
|
|
59
|
-
maxHeight: `${availableHeight}px`,
|
|
60
|
-
minWidth: `${rects.reference.width}px`,
|
|
61
|
-
})
|
|
62
|
-
},
|
|
63
|
-
padding: 10,
|
|
64
|
-
}),
|
|
65
|
-
],
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
const listRef = React.useRef<Array<HTMLElement | null>>([])
|
|
69
|
-
const listContentRef = React.useRef(labels)
|
|
70
|
-
const isTypingRef = React.useRef(false)
|
|
71
|
-
|
|
72
|
-
const click = useClick(context, { event: 'mousedown' })
|
|
73
|
-
const dismiss = useDismiss(context)
|
|
74
|
-
const role = useRole(context, { role: 'listbox' })
|
|
75
|
-
const listNav = useListNavigation(context, {
|
|
76
|
-
listRef,
|
|
77
|
-
activeIndex,
|
|
78
|
-
selectedIndex,
|
|
79
|
-
onNavigate: setActiveIndex,
|
|
80
|
-
// This is a large list, allow looping.
|
|
81
|
-
loop: true,
|
|
82
|
-
})
|
|
83
|
-
const typeahead = useTypeahead(context, {
|
|
84
|
-
listRef: listContentRef,
|
|
85
|
-
activeIndex,
|
|
86
|
-
selectedIndex,
|
|
87
|
-
onMatch: isOpen ? setActiveIndex : setSelectedIndex,
|
|
88
|
-
onTypingChange(isTyping) {
|
|
89
|
-
isTypingRef.current = isTyping
|
|
90
|
-
},
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([
|
|
94
|
-
dismiss,
|
|
95
|
-
role,
|
|
96
|
-
listNav,
|
|
97
|
-
typeahead,
|
|
98
|
-
click,
|
|
99
|
-
])
|
|
100
|
-
|
|
101
|
-
const handleSelect = (index: number) => {
|
|
102
|
-
setSelectedIndex(index)
|
|
103
|
-
client.sendMessage(optionsDict[labels[index]])
|
|
104
|
-
setIsOpen(false)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const selectedItemLabel = selectedIndex !== null ? labels[selectedIndex] : undefined
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<>
|
|
111
|
-
<div
|
|
112
|
-
tabIndex={0}
|
|
113
|
-
ref={refs.setReference}
|
|
114
|
-
aria-labelledby="select-label"
|
|
115
|
-
aria-autocomplete="none"
|
|
116
|
-
data-disabled={selectedIndex !== null && !reusable ? '' : undefined}
|
|
117
|
-
{...styles?.[type]?.button?.container}
|
|
118
|
-
{...getReferenceProps()}
|
|
119
|
-
>
|
|
120
|
-
<span {...styles?.[type]?.button?.text}>{selectedItemLabel || label || 'Select...'}</span>
|
|
121
|
-
<ChevronDownIcon {...styles?.[type]?.button?.icon} />
|
|
122
|
-
</div>
|
|
123
|
-
{isOpen && (
|
|
124
|
-
<FloatingPortal>
|
|
125
|
-
<FloatingFocusManager context={context} modal={false}>
|
|
126
|
-
<div
|
|
127
|
-
ref={refs.setFloating}
|
|
128
|
-
className={styles?.[type]?.content?.container?.className}
|
|
129
|
-
// className="min-w-[100px] overflow-y-auto rounded-md border border-gray-200 bg-white text-sm text-gray-700 outline-none"
|
|
130
|
-
style={{ ...floatingStyles, ...styles?.[type]?.content?.container?.style }}
|
|
131
|
-
{...getFloatingProps()}
|
|
132
|
-
>
|
|
133
|
-
{labels.map((value, i) => (
|
|
134
|
-
<div
|
|
135
|
-
key={value}
|
|
136
|
-
ref={(node) => {
|
|
137
|
-
listRef.current[i] = node
|
|
138
|
-
}}
|
|
139
|
-
role="option"
|
|
140
|
-
tabIndex={i === activeIndex ? 0 : -1}
|
|
141
|
-
aria-selected={i === selectedIndex && i === activeIndex}
|
|
142
|
-
data-active={i === activeIndex ? '' : undefined}
|
|
143
|
-
{...styles?.[type]?.content?.item}
|
|
144
|
-
{...getItemProps({
|
|
145
|
-
onClick() {
|
|
146
|
-
handleSelect(i)
|
|
147
|
-
},
|
|
148
|
-
onKeyDown(event) {
|
|
149
|
-
if (event.key === 'Enter') {
|
|
150
|
-
event.preventDefault()
|
|
151
|
-
handleSelect(i)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (event.key === ' ' && !isTypingRef.current) {
|
|
155
|
-
event.preventDefault()
|
|
156
|
-
handleSelect(i)
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
})}
|
|
160
|
-
>
|
|
161
|
-
{value}
|
|
162
|
-
</div>
|
|
163
|
-
))}
|
|
164
|
-
</div>
|
|
165
|
-
</FloatingFocusManager>
|
|
166
|
-
</FloatingPortal>
|
|
167
|
-
)}
|
|
168
|
-
</>
|
|
169
|
-
)
|
|
170
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from 'react'
|
|
2
|
-
import { CommonBlockProps, FileBlock } from '../../types'
|
|
3
|
-
import { DocumentArrowDownIcon } from '@heroicons/react/20/solid'
|
|
4
|
-
|
|
5
|
-
type Props = CommonBlockProps & FileBlock
|
|
6
|
-
export const File = forwardRef<HTMLAnchorElement, Props>(({ type, title, styles, url, ...props }, ref) => {
|
|
7
|
-
return (
|
|
8
|
-
<a {...props} {...styles?.[type]?.container} href={url} download ref={ref}>
|
|
9
|
-
<p {...styles?.[type]?.title}>{title || url}</p>
|
|
10
|
-
<DocumentArrowDownIcon {...styles?.[type]?.icon} />
|
|
11
|
-
</a>
|
|
12
|
-
)
|
|
13
|
-
})
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { forwardRef, useEffect, useId, useLayoutEffect, useState } from 'react'
|
|
2
|
-
import { CommonBlockProps, ImageBlock } from '../../types'
|
|
3
|
-
import { useMessageContext } from '../../contexts'
|
|
4
|
-
import { getImageSize } from '../../services'
|
|
5
|
-
|
|
6
|
-
type Props = CommonBlockProps & ImageBlock
|
|
7
|
-
export const Image = forwardRef<HTMLImageElement, Props>(
|
|
8
|
-
({ url, type, styles, orientation = 'auto', ...props }, ref) => {
|
|
9
|
-
const id = useId()
|
|
10
|
-
const { setIsLoading } = useMessageContext()
|
|
11
|
-
const [imageOrientation, setImageOrientation] = useState(orientation)
|
|
12
|
-
const [loaded, setLoaded] = useState(false)
|
|
13
|
-
|
|
14
|
-
useLayoutEffect(() => {
|
|
15
|
-
setIsLoading((prev) => [...prev, id])
|
|
16
|
-
}, [])
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!url) return
|
|
20
|
-
if (imageOrientation === 'auto') {
|
|
21
|
-
getImageSize(url).then(({ width, height }) => {
|
|
22
|
-
setImageOrientation(getClosestAspectRatio(width, height))
|
|
23
|
-
setIsLoading((prev) => {
|
|
24
|
-
return prev.filter((currId) => currId !== id)
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
}, [url])
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<>
|
|
32
|
-
{loaded ? null : <div data-orientation={imageOrientation} {...styles?.[type]?.placeholder} />}
|
|
33
|
-
<img
|
|
34
|
-
data-orientation={imageOrientation}
|
|
35
|
-
data-loaded={loaded}
|
|
36
|
-
{...props}
|
|
37
|
-
{...styles?.[type]?.image}
|
|
38
|
-
src={url}
|
|
39
|
-
alt=""
|
|
40
|
-
ref={ref}
|
|
41
|
-
loading="lazy"
|
|
42
|
-
onLoad={() => {
|
|
43
|
-
setLoaded(true)
|
|
44
|
-
}}
|
|
45
|
-
/>
|
|
46
|
-
</>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
function getClosestAspectRatio(width: number, height: number) {
|
|
52
|
-
type aspects = keyof typeof aspects
|
|
53
|
-
const aspects = {
|
|
54
|
-
square: 1,
|
|
55
|
-
portrait: 3 / 4,
|
|
56
|
-
landscape: 4 / 3,
|
|
57
|
-
} as const
|
|
58
|
-
const aspect = width / height
|
|
59
|
-
const closest = (Object.keys(aspects) as aspects[]).reduce((prev, curr) => {
|
|
60
|
-
return Math.abs(aspects[curr] - aspect) < Math.abs(aspects[prev] - aspect) ? curr : prev
|
|
61
|
-
})
|
|
62
|
-
return closest
|
|
63
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from 'react'
|
|
2
|
-
import { CommonBlockProps, LocationBlock } from '../../types'
|
|
3
|
-
import { MapPinIcon } from '@heroicons/react/20/solid'
|
|
4
|
-
|
|
5
|
-
type Props = CommonBlockProps & LocationBlock
|
|
6
|
-
export const Location = forwardRef<HTMLAnchorElement, Props>(
|
|
7
|
-
({ type, latitude, longitude, title, styles, ...props }, ref) => {
|
|
8
|
-
const link = `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`
|
|
9
|
-
return (
|
|
10
|
-
<a {...props} {...styles?.[type]?.container} href={link} type="_blank" rel="noopener" ref={ref}>
|
|
11
|
-
<p {...styles?.[type]?.title}>{title}</p>
|
|
12
|
-
<MapPinIcon {...styles?.[type]?.icon} />
|
|
13
|
-
</a>
|
|
14
|
-
)
|
|
15
|
-
}
|
|
16
|
-
)
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ComponentProps, forwardRef } from 'react'
|
|
2
|
-
import { RowBlock, CommonBlockProps } from '../../types'
|
|
3
|
-
import { Block } from '../Block'
|
|
4
|
-
|
|
5
|
-
type Props = CommonBlockProps & RowBlock & ComponentProps<'div'>
|
|
6
|
-
export const Row = forwardRef<HTMLDivElement, Props>(
|
|
7
|
-
({ blocks, styles, type, horizontalAlignment, verticalAlignment, ...props }, ref) => {
|
|
8
|
-
return (
|
|
9
|
-
<div
|
|
10
|
-
data-horizontal={horizontalAlignment}
|
|
11
|
-
data-vertical={verticalAlignment}
|
|
12
|
-
{...props}
|
|
13
|
-
{...styles?.[type]}
|
|
14
|
-
ref={ref}
|
|
15
|
-
>
|
|
16
|
-
{blocks.map((block, index) => (
|
|
17
|
-
<Block key={index} styles={styles} block={block} />
|
|
18
|
-
))}
|
|
19
|
-
</div>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
)
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
import { CommonBlockProps, TextBlock } from '../../types'
|
|
3
|
-
import ReactMarkdown from 'react-markdown'
|
|
4
|
-
|
|
5
|
-
import { ComponentProps } from 'react'
|
|
6
|
-
|
|
7
|
-
type Props = CommonBlockProps & TextBlock
|
|
8
|
-
export const Text = ({ text, type, styles }: Props) => {
|
|
9
|
-
const markdownStyles = styles?.[type]
|
|
10
|
-
|
|
11
|
-
const markdownComponents: ComponentProps<typeof ReactMarkdown>['components'] = {
|
|
12
|
-
h1: ({ node, ...props }) => <h1 {...props} {...markdownStyles?.heading1} />,
|
|
13
|
-
h2: ({ node, ...props }) => <h2 {...props} {...markdownStyles?.heading2} />,
|
|
14
|
-
h3: ({ node, ...props }) => <h3 {...props} {...markdownStyles?.heading3} />,
|
|
15
|
-
h4: 'h3',
|
|
16
|
-
h5: 'h3',
|
|
17
|
-
h6: 'h3',
|
|
18
|
-
em: ({ node, ...props }) => <em {...props} {...markdownStyles?.italic} />,
|
|
19
|
-
strong: ({ node, ...props }) => <strong {...props} {...markdownStyles?.bold} />,
|
|
20
|
-
p: ({ node, ...props }) => <p {...props} {...markdownStyles?.text} />,
|
|
21
|
-
hr: ({ node, ...props }) => <hr {...props} {...markdownStyles?.horizontalRule} />,
|
|
22
|
-
a: ({ node, ...props }) => <a {...props} {...markdownStyles?.link} />,
|
|
23
|
-
ol: ({ node, ordered, ...props }) => <ol {...props} {...markdownStyles?.orderedList} />,
|
|
24
|
-
ul: ({ node, ordered, ...props }) => <ul {...props} {...markdownStyles?.unorderedList} />,
|
|
25
|
-
li: ({ node, ordered, ...props }) => <li {...props} {...markdownStyles?.listItem} />,
|
|
26
|
-
br: ({ node, ...props }) => <br {...props} {...markdownStyles?.lineBreak} />,
|
|
27
|
-
img: () => null,
|
|
28
|
-
pre: () => null,
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return <ReactMarkdown components={markdownComponents}>{text}</ReactMarkdown>
|
|
32
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from 'react'
|
|
2
|
-
import { CommonBlockProps, VideoBlock } from '../../types'
|
|
3
|
-
|
|
4
|
-
type Props = CommonBlockProps & VideoBlock
|
|
5
|
-
export const Video = forwardRef<HTMLVideoElement, Props>(({ url, type, styles, ...props }, ref) => {
|
|
6
|
-
return (
|
|
7
|
-
<div>
|
|
8
|
-
<video {...props} controls src={url} {...styles?.[type]} ref={ref} />
|
|
9
|
-
</div>
|
|
10
|
-
)
|
|
11
|
-
})
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Text } from './Text'
|
|
2
|
-
import { Button } from './Button'
|
|
3
|
-
import { Bubble } from './Bubble'
|
|
4
|
-
import { Renderers } from '../../contexts'
|
|
5
|
-
import { Image } from './Image'
|
|
6
|
-
import { Video } from './Video'
|
|
7
|
-
import { Audio } from './Audio'
|
|
8
|
-
import { Location } from './Location'
|
|
9
|
-
import { Column } from './Column'
|
|
10
|
-
import { Row } from './Row'
|
|
11
|
-
import { Carousel } from './Carousel'
|
|
12
|
-
import { Dropdown } from './Dropdown'
|
|
13
|
-
import { File } from './File'
|
|
14
|
-
|
|
15
|
-
export const renderers: Renderers = {
|
|
16
|
-
text: Text,
|
|
17
|
-
button: Button,
|
|
18
|
-
bubble: Bubble,
|
|
19
|
-
audio: Audio,
|
|
20
|
-
column: Column,
|
|
21
|
-
file: File,
|
|
22
|
-
image: Image,
|
|
23
|
-
location: Location,
|
|
24
|
-
row: Row,
|
|
25
|
-
video: Video,
|
|
26
|
-
carousel: Carousel,
|
|
27
|
-
dropdown: Dropdown,
|
|
28
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react'
|
|
2
|
-
|
|
3
|
-
type ComposerContextValue = {
|
|
4
|
-
value: string
|
|
5
|
-
setValue: (value: string) => void
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const ComposerContext = createContext<ComposerContextValue | null>(null)
|
|
9
|
-
|
|
10
|
-
export function useComposerContext() {
|
|
11
|
-
const context = useContext(ComposerContext)
|
|
12
|
-
if (!context) {
|
|
13
|
-
throw new Error('useComposerContext must be used within a Message')
|
|
14
|
-
}
|
|
15
|
-
return context
|
|
16
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Dispatch, SetStateAction, createContext, useContext } from 'react'
|
|
2
|
-
|
|
3
|
-
type MessageContextVaue = {
|
|
4
|
-
isLoading: string[]
|
|
5
|
-
setIsLoading: Dispatch<SetStateAction<string[]>>
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const MessageContext = createContext<MessageContextVaue | null>(null)
|
|
9
|
-
|
|
10
|
-
export function useMessageContext() {
|
|
11
|
-
const context = useContext(MessageContext)
|
|
12
|
-
if (!context) {
|
|
13
|
-
throw new Error('useMessageContext must be used within a Message')
|
|
14
|
-
}
|
|
15
|
-
return context
|
|
16
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { ReactNode, createContext, useContext } from 'react'
|
|
2
|
-
|
|
3
|
-
type ModalContextVaue = {
|
|
4
|
-
open: boolean
|
|
5
|
-
showModal: (props: { title: string; content: ReactNode }) => void
|
|
6
|
-
hideModal: () => void
|
|
7
|
-
title: string
|
|
8
|
-
content: ReactNode | null
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const ModalContext = createContext<ModalContextVaue | null>(null)
|
|
12
|
-
|
|
13
|
-
export function useModalContext() {
|
|
14
|
-
const context = useContext(ModalContext)
|
|
15
|
-
if (!context) {
|
|
16
|
-
throw new Error('useModalContext must be used within a ModalProvider')
|
|
17
|
-
}
|
|
18
|
-
return context
|
|
19
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { CSSProperties, FC, ReactNode, createContext, useContext } from 'react'
|
|
2
|
-
import type { BlockObject, BlockStyles, MessageObject } from '../types'
|
|
3
|
-
import { EventEmitter, Events } from '../Utils'
|
|
4
|
-
import { Theme } from '../schemas'
|
|
5
|
-
import { type WebchatClient } from '../client'
|
|
6
|
-
|
|
7
|
-
export type StyleOptions = {
|
|
8
|
-
className?: string
|
|
9
|
-
style?: CSSProperties
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type DescriptionItem = {
|
|
13
|
-
title: string
|
|
14
|
-
link?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type Configuration = {
|
|
18
|
-
composerPlaceholder?: string
|
|
19
|
-
botName?: string
|
|
20
|
-
botAvatar?: string
|
|
21
|
-
botDescription?: string
|
|
22
|
-
website?: DescriptionItem
|
|
23
|
-
email?: DescriptionItem
|
|
24
|
-
phone?: DescriptionItem
|
|
25
|
-
privacyPolicy?: DescriptionItem
|
|
26
|
-
termsOfService?: DescriptionItem
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type Renderers<P extends BlockObject = BlockObject> = {
|
|
30
|
-
[T in P['type']]: FC<Extract<P, { type: T }> & { styles: BlockStyles }>
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type ModalProps = {
|
|
34
|
-
title: string | null
|
|
35
|
-
content: ReactNode | null
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
type ContextVaue = {
|
|
39
|
-
theme: Theme
|
|
40
|
-
renderers: Renderers
|
|
41
|
-
messages: MessageObject[]
|
|
42
|
-
eventEmitter: EventEmitter<Events>
|
|
43
|
-
configuration: Configuration
|
|
44
|
-
setConfiguration: React.Dispatch<React.SetStateAction<Configuration>>
|
|
45
|
-
state: {
|
|
46
|
-
disableComposer: boolean
|
|
47
|
-
}
|
|
48
|
-
client: { on: WebchatClient['on']; sendMessage: WebchatClient['sendMessage']; restartConversation: () => void }
|
|
49
|
-
setState: (state: Partial<ContextVaue['state']>) => void
|
|
50
|
-
setTheme: (styles: Partial<Theme>) => void // Temporary while I decide how to do the theme switching in dev mode
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export const WebchatContext = createContext<ContextVaue | null>(null)
|
|
54
|
-
|
|
55
|
-
export function useWebchatContext() {
|
|
56
|
-
const context = useContext(WebchatContext)
|
|
57
|
-
if (!context) {
|
|
58
|
-
throw new Error('useWebchatContext must be used within a WebchatProvider')
|
|
59
|
-
}
|
|
60
|
-
return context
|
|
61
|
-
}
|
package/src/contexts/index.ts
DELETED
package/src/hooks/index.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
2
|
-
import { getImageSize } from '../services'
|
|
3
|
-
import { Dimensions, Options, UseImageSizeResult } from '../types'
|
|
4
|
-
|
|
5
|
-
export const useImageSize = (url: string, options?: Options): UseImageSizeResult => {
|
|
6
|
-
const [dimensions, setDimensions] = useState<Dimensions | null>(null)
|
|
7
|
-
const [loading, setLoading] = useState(false)
|
|
8
|
-
const [error, setError] = useState<string | null>(null)
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
const fetch = async () => {
|
|
12
|
-
setLoading(true)
|
|
13
|
-
setDimensions(null)
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const { width, height } = await getImageSize(url, options)
|
|
17
|
-
|
|
18
|
-
setDimensions({ width, height })
|
|
19
|
-
} catch (error: unknown) {
|
|
20
|
-
setError((error as string).toString())
|
|
21
|
-
} finally {
|
|
22
|
-
setLoading(false)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
fetch()
|
|
27
|
-
}, [url, options])
|
|
28
|
-
|
|
29
|
-
return [dimensions, { loading, error }]
|
|
30
|
-
}
|