feat(theme): ✨ Add chat theme settings
This commit is contained in:
@ -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>
|
||||
|
38
apps/builder/components/theme/ChatSettings/ButtonsTheme.tsx
Normal file
38
apps/builder/components/theme/ChatSettings/ButtonsTheme.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
37
apps/builder/components/theme/ChatSettings/GuestBubbles.tsx
Normal file
37
apps/builder/components/theme/ChatSettings/GuestBubbles.tsx
Normal 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>
|
||||
)
|
||||
}
|
38
apps/builder/components/theme/ChatSettings/HostBubbles.tsx
Normal file
38
apps/builder/components/theme/ChatSettings/HostBubbles.tsx
Normal 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>
|
||||
)
|
||||
}
|
48
apps/builder/components/theme/ChatSettings/InputsTheme.tsx
Normal file
48
apps/builder/components/theme/ChatSettings/InputsTheme.tsx
Normal 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>
|
||||
)
|
||||
}
|
1
apps/builder/components/theme/ChatSettings/index.tsx
Normal file
1
apps/builder/components/theme/ChatSettings/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { ChatThemeSettings } from './ChatThemeSettings'
|
@ -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>
|
||||
)
|
||||
}
|
@ -1 +0,0 @@
|
||||
export { GeneralContent } from './GeneralContent'
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
)
|
||||
}
|
1
apps/builder/components/theme/GeneralSettings/index.ts
Normal file
1
apps/builder/components/theme/GeneralSettings/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { GeneralSettings } from './GeneralSettings'
|
@ -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>
|
||||
|
Reference in New Issue
Block a user