Skip to content

Commit

Permalink
✨ (theme) Add container theme options: border, shadow, filter (#1436)
Browse files Browse the repository at this point in the history
Closes #1332
  • Loading branch information
baptisteArno committed Apr 10, 2024
1 parent 75dd554 commit 5c3c7c2
Show file tree
Hide file tree
Showing 46 changed files with 2,125 additions and 548 deletions.
1 change: 0 additions & 1 deletion .env.dev.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Make sure to change this to your own random string of 32 characters (https://docs.typebot.io/self-hosting/docker#2-add-the-required-configuration)
ENCRYPTION_SECRET=H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S

DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot
Expand Down
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
"editor.tabSize": 2,
"typescript.updateImportsOnFileMove.enabled": "always",
"playwright.env": {
"DATABASE_URL": "postgresql://postgres:typebot@localhost:5432/typebot",
"DATABASE_URL": "postgresql://postgres:typebot@127.0.0.1:5432/typebot",
"NEXT_PUBLIC_VIEWER_URL": "http://localhost:3001",
"NEXTAUTH_URL": "http://localhost:3000"
"NEXTAUTH_URL": "http://localhost:3000",
"ENCRYPTION_SECRET": "H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S"
},
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
Expand Down
1 change: 1 addition & 0 deletions apps/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"format:check": "prettier --check ./src --ignore-path ../../.prettierignore"
},
"dependencies": {
"@typebot.io/theme": "workspace:*",
"@braintree/sanitize-url": "7.0.1",
"@chakra-ui/anatomy": "2.1.1",
"@chakra-ui/react": "2.7.1",
Expand Down
9 changes: 8 additions & 1 deletion apps/builder/src/components/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ const colorsSelection: `#${string}`[] = [
type Props = {
value?: string
defaultValue?: string
isDisabled?: boolean
onColorChange: (color: string) => void
}

export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => {
export const ColorPicker = ({
value,
defaultValue,
isDisabled,
onColorChange,
}: Props) => {
const { t } = useTranslate()
const [color, setColor] = useState(defaultValue ?? '')
const displayedValue = value ?? color
Expand All @@ -63,6 +69,7 @@ export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => {
padding={0}
borderRadius={3}
borderWidth={1}
isDisabled={isDisabled}
>
<Box rounded="full" boxSize="14px" bgColor={displayedValue} />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => {
fetchNewImages(query, 0)
}}
withVariableButton={false}
debounceTimeout={500}
forceDebounce
/>
<Link
isExternal
Expand Down
4 changes: 3 additions & 1 deletion apps/builder/src/components/inputs/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { env } from '@typebot.io/env'
import { MoreInfoTooltip } from '../MoreInfoTooltip'

export type TextInputProps = {
forceDebounce?: boolean
defaultValue?: string
onChange?: (value: string) => void
debounceTimeout?: number
Expand Down Expand Up @@ -62,6 +63,7 @@ export const TextInput = forwardRef(function TextInput(
autoComplete,
isDisabled,
autoFocus,
forceDebounce,
onChange: _onChange,
onFocus,
onKeyUp,
Expand All @@ -83,7 +85,7 @@ export const TextInput = forwardRef(function TextInput(
const onChange = useDebouncedCallback(
// eslint-disable-next-line @typescript-eslint/no-empty-function
_onChange ?? (() => {}),
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
env.NEXT_PUBLIC_E2E_TEST && !forceDebounce ? 0 : debounceTimeout
)

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ export const PreviewDrawer = () => {
right="0"
top={`0`}
h={`100%`}
w={`${width}px`}
bgColor={useColorModeValue('white', 'gray.900')}
borderLeftWidth={'1px'}
shadow="lg"
Expand All @@ -82,6 +81,7 @@ export const PreviewDrawer = () => {
onMouseLeave={() => setIsResizeHandleVisible(false)}
p="6"
zIndex={10}
style={{ width: `${width}px` }}
>
<Fade in={isResizeHandleVisible}>
<ResizeHandle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { Typebot } from '@typebot.io/schemas'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { JavascriptBubbleSnippet } from '../JavascriptBubbleSnippet'
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
import { defaultButtonsBackgroundColor } from '@typebot.io/schemas/features/typebot/theme/constants'

export const parseDefaultBubbleTheme = (typebot?: Typebot) => ({
button: {
backgroundColor:
typebot?.theme.chat?.buttons?.backgroundColor ??
defaultTheme.chat.buttons.backgroundColor,
defaultButtonsBackgroundColor,
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const DefaultAvatar = (props: IconProps) => {
fill="none"
xmlns="http://www.w3.org/2000/svg"
boxSize="40px"
borderRadius="full"
data-testid="default-avatar"
{...props}
>
Expand Down
14 changes: 9 additions & 5 deletions apps/builder/src/features/theme/components/ThemeSideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const ThemeSideMenu = () => {
typebot &&
updateTypebot({ updates: { theme: { ...typebot.theme, customCss } } })

const selectedTemplate = (
const selectTemplate = (
selectedTemplate: Partial<Pick<ThemeTemplate, 'id' | 'theme'>>
) => {
if (!typebot) return
Expand All @@ -56,6 +56,8 @@ export const ThemeSideMenu = () => {
},
})

const templateId = typebot?.selectedThemeTemplateId ?? undefined

return (
<Stack
flex="1"
Expand Down Expand Up @@ -84,12 +86,10 @@ export const ThemeSideMenu = () => {
<AccordionPanel pb={12}>
{typebot && (
<ThemeTemplates
selectedTemplateId={
typebot.selectedThemeTemplateId ?? undefined
}
selectedTemplateId={templateId}
currentTheme={typebot.theme}
workspaceId={typebot.workspaceId}
onTemplateSelect={selectedTemplate}
onTemplateSelect={selectTemplate}
/>
)}
</AccordionPanel>
Expand All @@ -106,6 +106,7 @@ export const ThemeSideMenu = () => {
<AccordionPanel pb={4}>
{typebot && (
<GeneralSettings
key={templateId}
isBrandingEnabled={
typebot.settings.general?.isBrandingEnabled ??
defaultSettings.general.isBrandingEnabled
Expand All @@ -128,9 +129,11 @@ export const ThemeSideMenu = () => {
<AccordionPanel pb={4}>
{typebot && (
<ChatThemeSettings
key={templateId}
workspaceId={typebot.workspaceId}
typebotId={typebot.id}
chatTheme={typebot.theme.chat}
generalBackground={typebot.theme.general?.background}
onChatThemeChange={updateChatTheme}
/>
)}
Expand All @@ -147,6 +150,7 @@ export const ThemeSideMenu = () => {
<AccordionPanel pb={4}>
{typebot && (
<CustomCssSettings
key={templateId}
customCss={typebot.theme.customCss}
onCustomCssChange={updateCustomCss}
/>
Expand Down
20 changes: 12 additions & 8 deletions apps/builder/src/features/theme/components/ThemeTemplateCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ import { Theme, ThemeTemplate } from '@typebot.io/schemas'
import { useState } from 'react'
import { DefaultAvatar } from './DefaultAvatar'
import {
defaultButtonsBackgroundColor,
BackgroundType,
defaultTheme,
defaultGuestAvatarIsEnabled,
defaultGuestBubblesBackgroundColor,
defaultHostAvatarIsEnabled,
defaultBackgroundColor,
defaultHostBubblesBackgroundColor,
} from '@typebot.io/schemas/features/typebot/theme/constants'
import { useTranslate } from '@tolgee/react'

Expand Down Expand Up @@ -71,28 +76,28 @@ export const ThemeTemplateCard = ({
const hostAvatar = {
isEnabled:
themeTemplate.theme.chat?.hostAvatar?.isEnabled ??
defaultTheme.chat.hostAvatar.isEnabled,
defaultHostAvatarIsEnabled,
url: themeTemplate.theme.chat?.hostAvatar?.url,
}

const hostBubbleBgColor =
themeTemplate.theme.chat?.hostBubbles?.backgroundColor ??
defaultTheme.chat.hostBubbles.backgroundColor
defaultHostBubblesBackgroundColor

const guestAvatar = {
isEnabled:
themeTemplate.theme.chat?.guestAvatar?.isEnabled ??
defaultTheme.chat.guestAvatar.isEnabled,
defaultGuestAvatarIsEnabled,
url: themeTemplate.theme.chat?.guestAvatar?.url,
}

const guestBubbleBgColor =
themeTemplate.theme.chat?.guestBubbles?.backgroundColor ??
defaultTheme.chat.guestBubbles.backgroundColor
defaultGuestBubblesBackgroundColor

const buttonBgColor =
themeTemplate.theme.chat?.buttons?.backgroundColor ??
defaultTheme.chat.buttons.backgroundColor
defaultButtonsBackgroundColor

return (
<Stack
Expand Down Expand Up @@ -197,8 +202,7 @@ const parseBackground = (
case undefined:
case BackgroundType.COLOR:
return {
backgroundColor:
background?.content ?? defaultTheme.general.background.content,
backgroundColor: background?.content ?? defaultBackgroundColor,
}
case BackgroundType.IMAGE:
return { backgroundImage: `url(${background.content})` }
Expand Down
134 changes: 134 additions & 0 deletions apps/builder/src/features/theme/components/chat/ChatContainerForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { FormLabel, HStack, Stack } from '@chakra-ui/react'
import { ChatTheme, GeneralTheme } from '@typebot.io/schemas'
import React from 'react'
import {
defaultBlur,
defaultContainerBackgroundColor,
defaultContainerMaxHeight,
defaultContainerMaxWidth,
defaultDarkTextColor,
defaultLightTextColor,
defaultOpacity,
defaultRoundness,
} from '@typebot.io/schemas/features/typebot/theme/constants'
import { ContainerThemeForm } from './ContainerThemeForm'
import { NumberInput } from '@/components/inputs'
import { DropdownList } from '@/components/DropdownList'
import { isChatContainerLight } from '@typebot.io/theme/isChatContainerLight'

type Props = {
generalBackground: GeneralTheme['background']
container: ChatTheme['container']
onContainerChange: (container: ChatTheme['container'] | undefined) => void
}

export const ChatContainerForm = ({
generalBackground,
container,
onContainerChange,
}: Props) => {
const updateMaxWidth = (maxWidth?: number) =>
updateDimension('maxWidth', maxWidth, maxWidthUnit)

const updateMaxWidthUnit = (unit: string) =>
updateDimension('maxWidth', maxWidth, unit)

const updateMaxHeight = (maxHeight?: number) =>
updateDimension('maxHeight', maxHeight, maxHeightUnit)

const updateMaxHeightUnit = (unit: string) =>
updateDimension('maxHeight', maxHeight, unit)

const updateDimension = (
dimension: 'maxWidth' | 'maxHeight',
value: number | undefined,
unit: string
) =>
onContainerChange({
...container,
[dimension]: `${value}${unit}`,
})

const { value: maxWidth, unit: maxWidthUnit } = parseValueAndUnit(
container?.maxWidth ?? defaultContainerMaxWidth
)

const { value: maxHeight, unit: maxHeightUnit } = parseValueAndUnit(
container?.maxHeight ?? defaultContainerMaxHeight
)

return (
<Stack>
<HStack justifyContent="space-between">
<FormLabel mb="0" mr="0">
Max width:
</FormLabel>
<HStack>
<NumberInput
size="sm"
width="100px"
defaultValue={maxWidth}
min={0}
step={10}
withVariableButton={false}
onValueChange={updateMaxWidth}
/>
<DropdownList
size="sm"
items={['px', '%', 'vh', 'vw']}
currentItem={maxWidthUnit}
onItemSelect={updateMaxWidthUnit}
/>
</HStack>
</HStack>

<HStack justifyContent="space-between">
<FormLabel mb="0" mr="0">
Max height:
</FormLabel>
<HStack>
<NumberInput
size="sm"
width="100px"
defaultValue={maxHeight}
min={0}
step={10}
onValueChange={updateMaxHeight}
withVariableButton={false}
/>
<DropdownList
size="sm"
items={['px', '%', 'vh', 'vw']}
currentItem={maxHeightUnit}
onItemSelect={updateMaxHeightUnit}
/>
</HStack>
</HStack>

<ContainerThemeForm
theme={container}
defaultTheme={{
backgroundColor: defaultContainerBackgroundColor,
border: {
roundeness: defaultRoundness,
},
blur: defaultBlur,
opacity: defaultOpacity,
color: isChatContainerLight({
chatContainer: container,
generalBackground,
})
? defaultLightTextColor
: defaultDarkTextColor,
}}
onThemeChange={onContainerChange}
/>
</Stack>
)
}

const parseValueAndUnit = (valueWithUnit: string) => {
const value = parseFloat(valueWithUnit)
const unit = valueWithUnit.replace(value.toString(), '')
return { value, unit }
}

0 comments on commit 5c3c7c2

Please sign in to comment.