2
0

feat(theme): Add chat theme settings

This commit is contained in:
Baptiste Arnaud
2022-01-24 15:07:09 +01:00
parent 619d10ae4e
commit b0abe5b8fa
37 changed files with 771 additions and 375 deletions

View File

@ -17,7 +17,7 @@ export const SettingsContent = () => {
<Flex h="full" w="full" justifyContent="center" align="flex-start">
<Stack p="6" rounded="md" borderWidth={1} w="600px" minH="500px" mt={10}>
<TypingEmulation
typingEmulation={typebot?.settings.typingEmulation}
typingEmulation={typebot?.settings?.typingEmulation}
onUpdate={handleTypingEmulationUpdate}
/>
</Stack>

View File

@ -0,0 +1,38 @@
import { Stack, Flex, Text } from '@chakra-ui/react'
import { ContainerColors } from 'models'
import React from 'react'
import { ColorPicker } from '../GeneralSettings/ColorPicker'
type Props = {
buttons?: ContainerColors
onButtonsChange: (buttons: ContainerColors) => void
}
const defaultBackgroundColor = '#0042da'
const defaultTextColor = '#ffffff'
export const ButtonsTheme = ({ buttons, onButtonsChange }: Props) => {
const handleBackgroundChange = (backgroundColor: string) =>
onButtonsChange({ ...buttons, backgroundColor })
const handleTextChange = (color: string) =>
onButtonsChange({ ...buttons, color })
return (
<Stack>
<Flex justify="space-between" align="center">
<Text>Background:</Text>
<ColorPicker
initialColor={buttons?.backgroundColor ?? defaultBackgroundColor}
onColorChange={handleBackgroundChange}
/>
</Flex>
<Flex justify="space-between" align="center">
<Text>Text:</Text>
<ColorPicker
initialColor={buttons?.color ?? defaultTextColor}
onColorChange={handleTextChange}
/>
</Flex>
</Stack>
)
}

View File

@ -0,0 +1,56 @@
import { Heading, Stack } from '@chakra-ui/react'
import { ChatTheme, ContainerColors, InputColors } from 'models'
import React from 'react'
import { ButtonsTheme } from './ButtonsTheme'
import { GuestBubbles } from './GuestBubbles'
import { HostBubbles } from './HostBubbles'
import { InputsTheme } from './InputsTheme'
type Props = {
chatTheme?: ChatTheme
onChatThemeChange: (chatTheme: ChatTheme) => void
}
export const ChatThemeSettings = ({ chatTheme, onChatThemeChange }: Props) => {
const handleHostBubblesChange = (hostBubbles: ContainerColors) =>
onChatThemeChange({ ...chatTheme, hostBubbles })
const handleGuestBubblesChange = (guestBubbles: ContainerColors) =>
onChatThemeChange({ ...chatTheme, guestBubbles })
const handleButtonsChange = (buttons: ContainerColors) =>
onChatThemeChange({ ...chatTheme, buttons })
const handleInputsChange = (inputs: InputColors) =>
onChatThemeChange({ ...chatTheme, inputs })
return (
<Stack spacing={6}>
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
<Heading fontSize="lg">Bot bubbles</Heading>
<HostBubbles
hostBubbles={chatTheme?.hostBubbles}
onHostBubblesChange={handleHostBubblesChange}
/>
</Stack>
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
<Heading fontSize="lg">User bubbles</Heading>
<GuestBubbles
guestBubbles={chatTheme?.guestBubbles}
onGuestBubblesChange={handleGuestBubblesChange}
/>
</Stack>
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
<Heading fontSize="lg">Buttons</Heading>
<ButtonsTheme
buttons={chatTheme?.buttons}
onButtonsChange={handleButtonsChange}
/>
</Stack>
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
<Heading fontSize="lg">Inputs</Heading>
<InputsTheme
inputs={chatTheme?.inputs}
onInputsChange={handleInputsChange}
/>
</Stack>
</Stack>
)
}

View File

@ -0,0 +1,37 @@
import { Stack, Flex, Text } from '@chakra-ui/react'
import { ContainerColors } from 'models'
import React from 'react'
import { ColorPicker } from '../GeneralSettings/ColorPicker'
type Props = {
guestBubbles?: ContainerColors
onGuestBubblesChange: (hostBubbles: ContainerColors) => void
}
const defaultBackgroundColor = '#ff8e21'
const defaultTextColor = '#ffffff'
export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
const handleBackgroundChange = (backgroundColor: string) =>
onGuestBubblesChange({ ...guestBubbles, backgroundColor })
const handleTextChange = (color: string) =>
onGuestBubblesChange({ ...guestBubbles, color })
return (
<Stack>
<Flex justify="space-between" align="center">
<Text>Background:</Text>
<ColorPicker
initialColor={guestBubbles?.backgroundColor ?? defaultBackgroundColor}
onColorChange={handleBackgroundChange}
/>
</Flex>
<Flex justify="space-between" align="center">
<Text>Text:</Text>
<ColorPicker
initialColor={guestBubbles?.color ?? defaultTextColor}
onColorChange={handleTextChange}
/>
</Flex>
</Stack>
)
}

View File

@ -0,0 +1,38 @@
import { Stack, Flex, Text } from '@chakra-ui/react'
import { ContainerColors } from 'models'
import React from 'react'
import { ColorPicker } from '../GeneralSettings/ColorPicker'
type Props = {
hostBubbles?: ContainerColors
onHostBubblesChange: (hostBubbles: ContainerColors) => void
}
const defaultBackgroundColor = '#f7f8ff'
const defaultTextColor = '#303235'
export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
const handleBackgroundChange = (backgroundColor: string) =>
onHostBubblesChange({ ...hostBubbles, backgroundColor })
const handleTextChange = (color: string) =>
onHostBubblesChange({ ...hostBubbles, color })
return (
<Stack>
<Flex justify="space-between" align="center">
<Text>Background:</Text>
<ColorPicker
initialColor={hostBubbles?.backgroundColor ?? defaultBackgroundColor}
onColorChange={handleBackgroundChange}
/>
</Flex>
<Flex justify="space-between" align="center">
<Text>Text:</Text>
<ColorPicker
initialColor={hostBubbles?.color ?? defaultTextColor}
onColorChange={handleTextChange}
/>
</Flex>
</Stack>
)
}

View File

@ -0,0 +1,48 @@
import { Stack, Flex, Text } from '@chakra-ui/react'
import { InputColors } from 'models'
import React from 'react'
import { ColorPicker } from '../GeneralSettings/ColorPicker'
type Props = {
inputs?: InputColors
onInputsChange: (buttons: InputColors) => void
}
const defaultBackgroundColor = '#ffffff'
const defaultTextColor = '#303235'
const defaultPlaceholderColor = '#9095A0'
export const InputsTheme = ({ inputs, onInputsChange }: Props) => {
const handleBackgroundChange = (backgroundColor: string) =>
onInputsChange({ ...inputs, backgroundColor })
const handleTextChange = (color: string) =>
onInputsChange({ ...inputs, color })
const handlePlaceholderChange = (placeholderColor: string) =>
onInputsChange({ ...inputs, placeholderColor })
return (
<Stack>
<Flex justify="space-between" align="center">
<Text>Background:</Text>
<ColorPicker
initialColor={inputs?.backgroundColor ?? defaultBackgroundColor}
onColorChange={handleBackgroundChange}
/>
</Flex>
<Flex justify="space-between" align="center">
<Text>Text:</Text>
<ColorPicker
initialColor={inputs?.color ?? defaultTextColor}
onColorChange={handleTextChange}
/>
</Flex>
<Flex justify="space-between" align="center">
<Text>Placeholder text:</Text>
<ColorPicker
initialColor={inputs?.placeholderColor ?? defaultPlaceholderColor}
onColorChange={handlePlaceholderChange}
/>
</Flex>
</Stack>
)
}

View File

@ -0,0 +1 @@
export { ChatThemeSettings } from './ChatThemeSettings'

View File

@ -1,64 +0,0 @@
import {
AccordionItem,
AccordionButton,
HStack,
Heading,
AccordionIcon,
AccordionPanel,
Stack,
} from '@chakra-ui/react'
import { PencilIcon } from 'assets/icons'
import { Background } from 'models'
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import React from 'react'
import { BackgroundSelector } from './BackgroundSelector'
import { FontSelector } from './FontSelector'
export const GeneralContent = () => {
const { typebot, updateTypebot } = useTypebot()
const handleSelectFont = (font: string) => {
if (!typebot) return
updateTypebot({
theme: {
...typebot.theme,
general: { ...typebot.theme.general, font },
},
})
}
const handleBackgroundChange = (background: Background) => {
if (!typebot) return
updateTypebot({
theme: {
...typebot.theme,
general: { ...typebot.theme.general, background },
},
})
}
return (
<AccordionItem>
<AccordionButton py={6}>
<HStack flex="1" pl={2}>
<PencilIcon />
<Heading fontSize="lg" color="gray.700">
General
</Heading>
</HStack>
<AccordionIcon />
</AccordionButton>
{typebot && (
<AccordionPanel as={Stack} pb={4} spacing={6}>
<FontSelector
activeFont={typebot.theme.general.font}
onSelectFont={handleSelectFont}
/>
<BackgroundSelector
initialBackground={typebot.theme.general.background}
onBackgroundChange={handleBackgroundChange}
/>
</AccordionPanel>
)}
</AccordionItem>
)
}

View File

@ -1 +0,0 @@
export { GeneralContent } from './GeneralContent'

View File

@ -4,10 +4,12 @@ import React from 'react'
import { ColorPicker } from '../ColorPicker'
type BackgroundContentProps = {
background: Background
background?: Background
onBackgroundContentChange: (content: string) => void
}
const defaultBackgroundColor = '#ffffff'
export const BackgroundContent = ({
background,
onBackgroundContentChange,
@ -15,13 +17,13 @@ export const BackgroundContent = ({
const handleContentChange = (content: string) =>
onBackgroundContentChange(content)
switch (background.type) {
switch (background?.type) {
case BackgroundType.COLOR:
return (
<Flex justify="space-between" align="center">
<Text>Background color:</Text>
<ColorPicker
initialColor={background.content}
initialColor={background.content ?? defaultBackgroundColor}
onColorChange={handleContentChange}
/>
</Flex>

View File

@ -1,43 +1,35 @@
import { Stack, Text } from '@chakra-ui/react'
import { Background, BackgroundType } from 'models'
import { deepEqual } from 'fast-equals'
import React, { useEffect, useState } from 'react'
import React from 'react'
import { BackgroundContent } from './BackgroundContent'
import { BackgroundTypeRadioButtons } from './BackgroundTypeRadioButtons'
type Props = {
initialBackground?: Background
background?: Background
onBackgroundChange: (newBackground: Background) => void
}
const defaultBackgroundType = BackgroundType.NONE
export const BackgroundSelector = ({
initialBackground,
background,
onBackgroundChange,
}: Props) => {
const [currentBackground, setCurrentBackground] = useState<Background>(
initialBackground ?? { type: BackgroundType.NONE, content: '' }
)
useEffect(() => {
if (deepEqual(currentBackground, initialBackground)) return
onBackgroundChange(currentBackground)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentBackground])
const handleBackgroundTypeChange = (type: BackgroundType) =>
setCurrentBackground({ ...currentBackground, type })
background && onBackgroundChange({ ...background, type })
const handleBackgroundContentChange = (content: string) =>
setCurrentBackground({ ...currentBackground, content })
background && onBackgroundChange({ ...background, content })
return (
<Stack spacing={4}>
<Text>Background</Text>
<BackgroundTypeRadioButtons
backgroundType={currentBackground.type}
backgroundType={background?.type ?? defaultBackgroundType}
onBackgroundTypeChange={handleBackgroundTypeChange}
/>
<BackgroundContent
background={currentBackground}
background={background}
onBackgroundContentChange={handleBackgroundContentChange}
/>
</Stack>

View File

@ -0,0 +1,36 @@
import { Stack } from '@chakra-ui/react'
import { Background, BackgroundType, GeneralTheme } from 'models'
import React from 'react'
import { BackgroundSelector } from './BackgroundSelector'
import { FontSelector } from './FontSelector'
type Props = {
generalTheme?: GeneralTheme
onGeneralThemeChange: (general: GeneralTheme) => void
}
const defaultFont = 'Open Sans'
export const GeneralSettings = ({
generalTheme,
onGeneralThemeChange,
}: Props) => {
const handleSelectFont = (font: string) =>
onGeneralThemeChange({ ...generalTheme, font })
const handleBackgroundChange = (background: Background) =>
onGeneralThemeChange({ ...generalTheme, background })
return (
<Stack spacing={6}>
<FontSelector
activeFont={generalTheme?.font ?? defaultFont}
onSelectFont={handleSelectFont}
/>
<BackgroundSelector
background={generalTheme?.background ?? { type: BackgroundType.NONE }}
onBackgroundChange={handleBackgroundChange}
/>
</Stack>
)
}

View File

@ -0,0 +1 @@
export { GeneralSettings } from './GeneralSettings'

View File

@ -8,26 +8,58 @@ import {
HStack,
Stack,
} from '@chakra-ui/react'
import { ChatIcon, CodeIcon, LayoutIcon } from 'assets/icons'
import { ChatIcon, CodeIcon, LayoutIcon, PencilIcon } from 'assets/icons'
import { headerHeight } from 'components/shared/TypebotHeader'
import { useTypebot } from 'contexts/TypebotContext'
import { ChatTheme, GeneralTheme } from 'models'
import React from 'react'
import { GeneralContent } from './GeneralContent'
import { ChatThemeSettings } from './ChatSettings'
import { GeneralSettings } from './GeneralSettings'
export const SideMenu = () => {
const { typebot, updateTypebot } = useTypebot()
const handleChatThemeChange = (chat: ChatTheme) =>
updateTypebot({ theme: { ...typebot?.theme, chat } })
const handleGeneralThemeChange = (general: GeneralTheme) =>
updateTypebot({ theme: { ...typebot?.theme, general } })
return (
<Stack flex="1" maxW="400px" borderRightWidth={1} pt={10} spacing={10}>
<Stack
flex="1"
maxW="400px"
height={`calc(100vh - ${headerHeight}px)`}
borderRightWidth={1}
pt={10}
spacing={10}
overflowY="scroll"
pb="20"
>
<Heading fontSize="xl" textAlign="center">
Customize the theme
</Heading>
<Accordion allowMultiple allowToggle>
<GeneralContent />
<AccordionItem>
<AccordionButton py={6}>
<HStack flex="1" pl={2}>
<PencilIcon />
<Heading fontSize="lg">General</Heading>
</HStack>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={4}>
<GeneralSettings
generalTheme={typebot?.theme?.general}
onGeneralThemeChange={handleGeneralThemeChange}
/>
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<AccordionButton py={6}>
<HStack flex="1" pl={2}>
<LayoutIcon />
<Heading fontSize="lg" color="gray.700">
Layout
</Heading>
<Heading fontSize="lg">Layout</Heading>
</HStack>
<AccordionIcon />
</AccordionButton>
@ -42,26 +74,22 @@ export const SideMenu = () => {
<AccordionButton py={6}>
<HStack flex="1" pl={2}>
<ChatIcon />
<Heading fontSize="lg" color="gray.700">
Chat
</Heading>
<Heading fontSize="lg">Chat</Heading>
</HStack>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={4}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.
<ChatThemeSettings
chatTheme={typebot?.theme?.chat}
onChatThemeChange={handleChatThemeChange}
/>
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<AccordionButton py={6}>
<HStack flex="1" pl={2}>
<CodeIcon />
<Heading fontSize="lg" color="gray.700">
Custom CSS
</Heading>
<Heading fontSize="lg">Custom CSS</Heading>
</HStack>
<AccordionIcon />
</AccordionButton>