feat(theme): ✨ Add custom css settings
This commit is contained in:
@ -159,6 +159,7 @@ export const WebhookSettings = ({
|
|||||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={webhook?.body ?? ''}
|
value={webhook?.body ?? ''}
|
||||||
|
lang="json"
|
||||||
onChange={handleBodyChange}
|
onChange={handleBodyChange}
|
||||||
/>
|
/>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
@ -181,7 +182,9 @@ export const WebhookSettings = ({
|
|||||||
<Button onClick={handleTestRequestClick} colorScheme="blue">
|
<Button onClick={handleTestRequestClick} colorScheme="blue">
|
||||||
Test the request
|
Test the request
|
||||||
</Button>
|
</Button>
|
||||||
{testResponse && <CodeEditor isReadOnly value={testResponse} />}
|
{testResponse && (
|
||||||
|
<CodeEditor isReadOnly lang="json" value={testResponse} />
|
||||||
|
)}
|
||||||
{(testResponse || options?.responseVariableMapping) && (
|
{(testResponse || options?.responseVariableMapping) && (
|
||||||
<Accordion allowToggle allowMultiple>
|
<Accordion allowToggle allowMultiple>
|
||||||
<AccordionItem>
|
<AccordionItem>
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { Box, BoxProps } from '@chakra-ui/react'
|
import { Box, BoxProps } from '@chakra-ui/react'
|
||||||
import { EditorState, EditorView, basicSetup } from '@codemirror/basic-setup'
|
import { EditorState, EditorView, basicSetup } from '@codemirror/basic-setup'
|
||||||
import { json } from '@codemirror/lang-json'
|
import { json } from '@codemirror/lang-json'
|
||||||
|
import { css } from '@codemirror/lang-css'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string
|
value: string
|
||||||
|
lang: 'css' | 'json'
|
||||||
onChange?: (value: string) => void
|
onChange?: (value: string) => void
|
||||||
isReadOnly?: boolean
|
isReadOnly?: boolean
|
||||||
}
|
}
|
||||||
export const CodeEditor = ({
|
export const CodeEditor = ({
|
||||||
value,
|
value,
|
||||||
|
lang,
|
||||||
onChange,
|
onChange,
|
||||||
isReadOnly = false,
|
isReadOnly = false,
|
||||||
...props
|
...props
|
||||||
@ -31,14 +34,15 @@ export const CodeEditor = ({
|
|||||||
if (update.docChanged && onChange)
|
if (update.docChanged && onChange)
|
||||||
onChange(update.state.doc.toJSON().join(' '))
|
onChange(update.state.doc.toJSON().join(' '))
|
||||||
})
|
})
|
||||||
|
const extensions = [
|
||||||
|
updateListenerExtension,
|
||||||
|
basicSetup,
|
||||||
|
EditorState.readOnly.of(isReadOnly),
|
||||||
|
]
|
||||||
|
extensions.push(lang === 'json' ? json() : css())
|
||||||
const editor = new EditorView({
|
const editor = new EditorView({
|
||||||
state: EditorState.create({
|
state: EditorState.create({
|
||||||
extensions: [
|
extensions,
|
||||||
updateListenerExtension,
|
|
||||||
basicSetup,
|
|
||||||
json(),
|
|
||||||
EditorState.readOnly.of(isReadOnly),
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
parent: editorContainer.current,
|
parent: editorContainer.current,
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { CodeEditor } from 'components/shared/CodeEditor'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
customCss?: string
|
||||||
|
onCustomCssChange: (css: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CustomCssSettings = ({ customCss, onCustomCssChange }: Props) => {
|
||||||
|
return (
|
||||||
|
<CodeEditor
|
||||||
|
value={customCss ?? ''}
|
||||||
|
lang="css"
|
||||||
|
onChange={onCustomCssChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -8,12 +8,13 @@ import {
|
|||||||
HStack,
|
HStack,
|
||||||
Stack,
|
Stack,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { ChatIcon, CodeIcon, LayoutIcon, PencilIcon } from 'assets/icons'
|
import { ChatIcon, CodeIcon, PencilIcon } from 'assets/icons'
|
||||||
import { headerHeight } from 'components/shared/TypebotHeader'
|
import { headerHeight } from 'components/shared/TypebotHeader'
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
import { ChatTheme, GeneralTheme } from 'models'
|
import { ChatTheme, GeneralTheme } from 'models'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ChatThemeSettings } from './ChatSettings'
|
import { ChatThemeSettings } from './ChatSettings'
|
||||||
|
import { CustomCssSettings } from './CustomCssSettings/CustomCssSettings'
|
||||||
import { GeneralSettings } from './GeneralSettings'
|
import { GeneralSettings } from './GeneralSettings'
|
||||||
|
|
||||||
export const SideMenu = () => {
|
export const SideMenu = () => {
|
||||||
@ -25,6 +26,9 @@ export const SideMenu = () => {
|
|||||||
const handleGeneralThemeChange = (general: GeneralTheme) =>
|
const handleGeneralThemeChange = (general: GeneralTheme) =>
|
||||||
updateTypebot({ theme: { ...typebot?.theme, general } })
|
updateTypebot({ theme: { ...typebot?.theme, general } })
|
||||||
|
|
||||||
|
const handleCustomCssChange = (customCss: string) =>
|
||||||
|
updateTypebot({ theme: { ...typebot?.theme, customCss } })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
flex="1"
|
flex="1"
|
||||||
@ -55,21 +59,6 @@ export const SideMenu = () => {
|
|||||||
/>
|
/>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem>
|
|
||||||
<AccordionButton py={6}>
|
|
||||||
<HStack flex="1" pl={2}>
|
|
||||||
<LayoutIcon />
|
|
||||||
<Heading fontSize="lg">Layout</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.
|
|
||||||
</AccordionPanel>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem>
|
<AccordionItem>
|
||||||
<AccordionButton py={6}>
|
<AccordionButton py={6}>
|
||||||
<HStack flex="1" pl={2}>
|
<HStack flex="1" pl={2}>
|
||||||
@ -94,10 +83,10 @@ export const SideMenu = () => {
|
|||||||
<AccordionIcon />
|
<AccordionIcon />
|
||||||
</AccordionButton>
|
</AccordionButton>
|
||||||
<AccordionPanel pb={4}>
|
<AccordionPanel pb={4}>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
<CustomCssSettings
|
||||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
|
customCss={typebot?.theme?.customCss}
|
||||||
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
onCustomCssChange={handleCustomCssChange}
|
||||||
aliquip ex ea commodo consequat.
|
/>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
26
apps/builder/cypress/tests/theme/customCss.ts
Normal file
26
apps/builder/cypress/tests/theme/customCss.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { getIframeBody } from 'cypress/support'
|
||||||
|
|
||||||
|
describe('Custom CSS settings', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.task('seed')
|
||||||
|
cy.signOut()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reflect changes in real time', () => {
|
||||||
|
cy.loadTypebotFixtureInDatabase('typebots/theme/theme.json')
|
||||||
|
cy.signIn('test2@gmail.com')
|
||||||
|
cy.visit('/typebots/typebot4/theme')
|
||||||
|
cy.findByRole('button', { name: 'Custom CSS' }).click()
|
||||||
|
|
||||||
|
cy.findByTestId('code-editor').type(
|
||||||
|
'.typebot-button {background-color: green}',
|
||||||
|
{
|
||||||
|
parseSpecialCharSequences: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
getIframeBody()
|
||||||
|
.findByTestId('button')
|
||||||
|
.should('have.css', 'background-color')
|
||||||
|
.should('eq', 'rgb(0, 128, 0)')
|
||||||
|
})
|
||||||
|
})
|
@ -13,6 +13,7 @@
|
|||||||
"@chakra-ui/css-reset": "^1.1.1",
|
"@chakra-ui/css-reset": "^1.1.1",
|
||||||
"@chakra-ui/react": "^1.7.4",
|
"@chakra-ui/react": "^1.7.4",
|
||||||
"@codemirror/basic-setup": "^0.19.1",
|
"@codemirror/basic-setup": "^0.19.1",
|
||||||
|
"@codemirror/lang-css": "^0.19.3",
|
||||||
"@codemirror/lang-json": "^0.19.1",
|
"@codemirror/lang-json": "^0.19.1",
|
||||||
"@codemirror/text": "^0.19.6",
|
"@codemirror/text": "^0.19.6",
|
||||||
"@dnd-kit/core": "^4.0.3",
|
"@dnd-kit/core": "^4.0.3",
|
||||||
|
@ -48,6 +48,7 @@ export const TypebotViewer = ({
|
|||||||
{phoneNumberInputStyle}
|
{phoneNumberInputStyle}
|
||||||
{phoneSyle}
|
{phoneSyle}
|
||||||
{style}
|
{style}
|
||||||
|
{typebot.theme?.customCss}
|
||||||
</style>
|
</style>
|
||||||
}
|
}
|
||||||
style={{ width: '100%', height: '100%', border: 'none' }}
|
style={{ width: '100%', height: '100%', border: 'none' }}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export type Theme = {
|
export type Theme = {
|
||||||
general?: GeneralTheme
|
general?: GeneralTheme
|
||||||
chat?: ChatTheme
|
chat?: ChatTheme
|
||||||
|
customCss?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GeneralTheme = {
|
export type GeneralTheme = {
|
||||||
|
20
yarn.lock
20
yarn.lock
@ -811,7 +811,7 @@
|
|||||||
"@codemirror/state" "^0.19.0"
|
"@codemirror/state" "^0.19.0"
|
||||||
"@codemirror/view" "^0.19.23"
|
"@codemirror/view" "^0.19.23"
|
||||||
|
|
||||||
"@codemirror/highlight@^0.19.0":
|
"@codemirror/highlight@^0.19.0", "@codemirror/highlight@^0.19.6":
|
||||||
version "0.19.7"
|
version "0.19.7"
|
||||||
resolved "https://registry.yarnpkg.com/@codemirror/highlight/-/highlight-0.19.7.tgz#91a0c9994c759f5f153861e3aae74ff9e7c7c35b"
|
resolved "https://registry.yarnpkg.com/@codemirror/highlight/-/highlight-0.19.7.tgz#91a0c9994c759f5f153861e3aae74ff9e7c7c35b"
|
||||||
integrity sha512-3W32hBCY0pbbv/xidismw+RDMKuIag+fo4kZIbD7WoRj+Ttcaxjf+vP6RttRHXLaaqbWh031lTeON8kMlDhMYw==
|
integrity sha512-3W32hBCY0pbbv/xidismw+RDMKuIag+fo4kZIbD7WoRj+Ttcaxjf+vP6RttRHXLaaqbWh031lTeON8kMlDhMYw==
|
||||||
@ -831,6 +831,17 @@
|
|||||||
"@codemirror/state" "^0.19.2"
|
"@codemirror/state" "^0.19.2"
|
||||||
"@codemirror/view" "^0.19.0"
|
"@codemirror/view" "^0.19.0"
|
||||||
|
|
||||||
|
"@codemirror/lang-css@^0.19.3":
|
||||||
|
version "0.19.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@codemirror/lang-css/-/lang-css-0.19.3.tgz#7a17adf78c6fcdab4ad5ee4e360631c41e949e4a"
|
||||||
|
integrity sha512-tyCUJR42/UlfOPLb94/p7dN+IPsYSIzHbAHP2KQHANj0I+Orqp+IyIOS++M8TuCX4zkWh9dvi8s92yy/Tn8Ifg==
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/autocomplete" "^0.19.0"
|
||||||
|
"@codemirror/highlight" "^0.19.6"
|
||||||
|
"@codemirror/language" "^0.19.0"
|
||||||
|
"@codemirror/state" "^0.19.0"
|
||||||
|
"@lezer/css" "^0.15.2"
|
||||||
|
|
||||||
"@codemirror/lang-json@^0.19.1":
|
"@codemirror/lang-json@^0.19.1":
|
||||||
version "0.19.1"
|
version "0.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/@codemirror/lang-json/-/lang-json-0.19.1.tgz#616588d1422529965243c10af6c44ad0b9134fb0"
|
resolved "https://registry.yarnpkg.com/@codemirror/lang-json/-/lang-json-0.19.1.tgz#616588d1422529965243c10af6c44ad0b9134fb0"
|
||||||
@ -1338,6 +1349,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@lezer/common/-/common-0.15.11.tgz#965b5067036305f12e8a3efc344076850be1d3a8"
|
resolved "https://registry.yarnpkg.com/@lezer/common/-/common-0.15.11.tgz#965b5067036305f12e8a3efc344076850be1d3a8"
|
||||||
integrity sha512-vv0nSdIaVCRcJ8rPuDdsrNVfBOYe/4Szr/LhF929XyDmBndLDuWiCCHooGlGlJfzELyO608AyDhVsuX/ZG36NA==
|
integrity sha512-vv0nSdIaVCRcJ8rPuDdsrNVfBOYe/4Szr/LhF929XyDmBndLDuWiCCHooGlGlJfzELyO608AyDhVsuX/ZG36NA==
|
||||||
|
|
||||||
|
"@lezer/css@^0.15.2":
|
||||||
|
version "0.15.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@lezer/css/-/css-0.15.2.tgz#e96995da67df90bb4b191aaa8a486349cca5d8e7"
|
||||||
|
integrity sha512-tnMOMZY0Zs6JQeVjqfmREYMV0GnmZR1NitndLWioZMD6mA7VQF/PPKPmJX1f+ZgVZQc5Am0df9mX3aiJnNJlKQ==
|
||||||
|
dependencies:
|
||||||
|
"@lezer/lr" "^0.15.0"
|
||||||
|
|
||||||
"@lezer/json@^0.15.0":
|
"@lezer/json@^0.15.0":
|
||||||
version "0.15.0"
|
version "0.15.0"
|
||||||
resolved "https://registry.yarnpkg.com/@lezer/json/-/json-0.15.0.tgz#b96c1161eb8514e05f4eaaec95c68376e76e539f"
|
resolved "https://registry.yarnpkg.com/@lezer/json/-/json-0.15.0.tgz#b96c1161eb8514e05f4eaaec95c68376e76e539f"
|
||||||
|
Reference in New Issue
Block a user