Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚡ (embed) Add size and icon picker in bubble settings #508

Merged
merged 1 commit into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions apps/builder/src/components/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ import tinyColor from 'tinycolor2'

const colorsSelection: `#${string}`[] = [
'#666460',
'#AFABA3',
'#FFFFFF',
'#A87964',
'#D09C46',
'#DE8031',
'#598E71',
'#4A8BB2',
'#9B74B7',
'#C75F96',
'#C75F96',
'#0042DA',
]

type Props = {
Expand Down Expand Up @@ -78,18 +78,19 @@ export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => {
</PopoverHeader>
<PopoverBody as={Stack}>
<SimpleGrid columns={5} spacing={2}>
{colorsSelection.map((c) => (
{colorsSelection.map((color) => (
<Button
key={c}
aria-label={c}
background={c}
key={color}
aria-label={color}
background={color}
height="22px"
width="22px"
padding={0}
minWidth="unset"
borderRadius={3}
_hover={{ background: c }}
onClick={handleClick(c)}
borderWidth={color === '#FFFFFF' ? 1 : undefined}
_hover={{ background: color }}
onClick={handleClick(color)}
/>
))}
</SimpleGrid>
Expand Down
5 changes: 2 additions & 3 deletions apps/builder/src/components/EditableEmojiOrImageIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,9 @@ export const EditableEmojiOrImageIcon = ({
filePath={uploadFilePath}
defaultUrl={icon ?? ''}
onSubmit={onChangeIcon}
isGiphyEnabled={false}
isUnsplashEnabled={false}
isEmojiEnabled={true}
excludedTabs={['giphy', 'unsplash']}
onClose={onClose}
initialTab="icon"
/>
</PopoverContent>
</>
Expand Down
17 changes: 12 additions & 5 deletions apps/builder/src/components/ImageUploadContent/IconPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ export const IconPicker = ({ onIconSelected }: Props) => {
)
const searchQuery = useRef<string>('')
const [selectedColor, setSelectedColor] = useState(initialIconColor)
const isWhite = useMemo(
() =>
selectedColor.toLowerCase() === '#ffffff' ||
selectedColor.toLowerCase() === '#fff' ||
selectedColor === 'white',
[selectedColor]
)

useEffect(() => {
if (!bottomElement.current) return
Expand Down Expand Up @@ -68,10 +75,9 @@ export const IconPicker = ({ onIconSelected }: Props) => {

const selectIcon = async (iconName: string) => {
const svg = await (await fetch(`/icons/${iconName}.svg`)).text()
const dataUri = `data:image/svg+xml;utf8,${svg.replace(
'<svg',
`<svg fill='${encodeURIComponent(selectedColor)}'`
)}`
const dataUri = `data:image/svg+xml;utf8,${svg
.replace('<svg', `<svg fill='${encodeURIComponent(selectedColor)}'`)
.replace(/"/g, "'")}`
onIconSelected(dataUri)
}

Expand All @@ -96,8 +102,9 @@ export const IconPicker = ({ onIconSelected }: Props) => {
>
{displayedIconNames.map((iconName) => (
<Button
variant="ghost"
size="sm"
variant={isWhite ? 'solid' : 'ghost'}
colorScheme={isWhite ? 'blackAlpha' : undefined}
fontSize="xl"
w="38px"
h="38px"
Expand Down
100 changes: 65 additions & 35 deletions apps/builder/src/components/ImageUploadContent/ImageUploadContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,52 @@ import { IconPicker } from './IconPicker'
type Tabs = 'link' | 'upload' | 'giphy' | 'emoji' | 'unsplash' | 'icon'

type Props = {
filePath: string
filePath: string | undefined
includeFileName?: boolean
defaultUrl?: string
isEmojiEnabled?: boolean
isGiphyEnabled?: boolean
isUnsplashEnabled?: boolean
imageSize?: 'small' | 'regular' | 'thumb'
initialTab?: Tabs
onSubmit: (url: string) => void
onClose?: () => void
}
} & (
| {
includedTabs?: Tabs[]
}
| {
excludedTabs?: Tabs[]
}
)

const defaultDisplayedTabs: Tabs[] = [
'link',
'upload',
'giphy',
'emoji',
'unsplash',
'icon',
]

export const ImageUploadContent = ({
filePath,
includeFileName,
defaultUrl,
onSubmit,
isEmojiEnabled = false,
isGiphyEnabled = true,
isUnsplashEnabled = true,
imageSize = 'regular',
onClose,
initialTab,
...props
}: Props) => {
const includedTabs =
'includedTabs' in props
? props.includedTabs ?? defaultDisplayedTabs
: defaultDisplayedTabs
const excludedTabs = 'excludedTabs' in props ? props.excludedTabs ?? [] : []
const displayedTabs = defaultDisplayedTabs.filter(
(tab) => !excludedTabs.includes(tab) && includedTabs.includes(tab)
)

const [currentTab, setCurrentTab] = useState<Tabs>(
isEmojiEnabled ? 'emoji' : 'link'
initialTab ?? displayedTabs[0]
)

const handleSubmit = (url: string) => {
Expand All @@ -44,7 +66,25 @@ export const ImageUploadContent = ({
return (
<Stack>
<HStack>
{isEmojiEnabled && (
{displayedTabs.includes('link') && (
<Button
variant={currentTab === 'link' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('link')}
size="sm"
>
Link
</Button>
)}
{displayedTabs.includes('upload') && (
<Button
variant={currentTab === 'upload' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('upload')}
size="sm"
>
Upload
</Button>
)}
{displayedTabs.includes('emoji') && (
<Button
variant={currentTab === 'emoji' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('emoji')}
Expand All @@ -53,21 +93,7 @@ export const ImageUploadContent = ({
Emoji
</Button>
)}
<Button
variant={currentTab === 'link' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('link')}
size="sm"
>
Link
</Button>
<Button
variant={currentTab === 'upload' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('upload')}
size="sm"
>
Upload
</Button>
{isGiphyEnabled && (
{displayedTabs.includes('giphy') && (
<Button
variant={currentTab === 'giphy' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('giphy')}
Expand All @@ -76,7 +102,7 @@ export const ImageUploadContent = ({
Giphy
</Button>
)}
{isUnsplashEnabled && (
{displayedTabs.includes('unsplash') && (
<Button
variant={currentTab === 'unsplash' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('unsplash')}
Expand All @@ -85,13 +111,15 @@ export const ImageUploadContent = ({
Unsplash
</Button>
)}
<Button
variant={currentTab === 'icon' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('icon')}
size="sm"
>
Icon
</Button>
{displayedTabs.includes('icon') && (
<Button
variant={currentTab === 'icon' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('icon')}
size="sm"
>
Icon
</Button>
)}
</HStack>

<BodyContent
Expand All @@ -115,21 +143,23 @@ const BodyContent = ({
onSubmit,
}: {
includeFileName?: boolean
filePath: string
filePath: string | undefined
tab: Tabs
defaultUrl?: string
imageSize: 'small' | 'regular' | 'thumb'
onSubmit: (url: string) => void
}) => {
switch (tab) {
case 'upload':
case 'upload': {
if (!filePath) return null
return (
<UploadFileContent
filePath={filePath}
includeFileName={includeFileName}
onNewUrl={onSubmit}
/>
)
}
case 'link':
return <EmbedLinkContent defaultUrl={defaultUrl} onNewUrl={onSubmit} />
case 'giphy':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ import { JavascriptBubbleSnippet } from '../JavascriptBubbleSnippet'
export const parseDefaultBubbleTheme = (typebot?: Typebot) => ({
button: {
backgroundColor: typebot?.theme.chat.buttons.backgroundColor,
iconColor: typebot?.theme.chat.buttons.color,
},
previewMessage: {
backgroundColor: typebot?.theme.general.background.content ?? 'white',
textColor: 'black',
},
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Stack, Heading, HStack, Flex, Text, Image } from '@chakra-ui/react'
import {
Stack,
Heading,
HStack,
Flex,
Text,
Image,
chakra,
} from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { isDefined } from '@typebot.io/lib'
import { isDefined, isSvgSrc } from '@typebot.io/lib'
import { PreviewMessageSettings } from './PreviewMessageSettings'
import { ThemeSettings } from './ThemeSettings'
import { isLight } from '@typebot.io/lib/hexToRgb'

type Props = {
defaultPreviewMessageAvatar: string
Expand Down Expand Up @@ -79,24 +88,72 @@ export const BubbleSettings = ({
<Flex
align="center"
justifyContent="center"
boxSize="3rem"
transition="all 0.2s ease-in-out"
boxSize={theme?.button?.size === 'large' ? '64px' : '48px'}
bgColor={theme?.button?.backgroundColor}
rounded="full"
boxShadow="0 0 #0000,0 0 #0000,0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1)"
>
<svg
viewBox="0 0 24 24"
style={{
stroke: theme?.button?.iconColor,
}}
width="30px"
strokeWidth="2px"
fill="transparent"
>
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
<BubbleIcon buttonTheme={theme?.button} />
</Flex>
</Stack>
</Stack>
</Stack>
)
}

const BubbleIcon = ({
buttonTheme,
}: {
buttonTheme: NonNullable<BubbleProps['theme']>['button']
}) => {
if (!buttonTheme?.customIconSrc)
return (
<svg
viewBox="0 0 24 24"
style={{
stroke: buttonTheme?.backgroundColor
? isLight(buttonTheme?.backgroundColor)
? '#000'
: '#fff'
: '#fff',
transition: 'all 0.2s ease-in-out',
}}
width={buttonTheme?.size === 'large' ? '36px' : '28px'}
strokeWidth="2px"
fill="transparent"
>
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
)

if (
buttonTheme.customIconSrc.startsWith('http') ||
buttonTheme.customIconSrc.startsWith('data:image/svg+xml')
)
return (
<Image
src={buttonTheme.customIconSrc}
transition="all 0.2s ease-in-out"
boxSize={
isSvgSrc(buttonTheme.customIconSrc)
? buttonTheme?.size === 'large'
? '36px'
: '28px'
: '90%'
}
rounded={isSvgSrc(buttonTheme.customIconSrc) ? undefined : 'full'}
alt="Bubble button icon"
objectFit={isSvgSrc(buttonTheme.customIconSrc) ? undefined : 'cover'}
/>
)
return (
<chakra.span
transition="all 0.2s ease-in-out"
fontSize={buttonTheme.size === 'large' ? '36px' : '24px'}
lineHeight={buttonTheme.size === 'large' ? '40px' : '32px'}
>
{buttonTheme.customIconSrc}
</chakra.span>
)
}
Loading