2
0

feat(theme): Add custom css settings

This commit is contained in:
Baptiste Arnaud
2022-01-24 15:29:01 +01:00
parent b0abe5b8fa
commit 21448bcc8a
9 changed files with 88 additions and 28 deletions

View File

@ -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>

View File

@ -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,
}) })

View File

@ -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}
/>
)
}

View File

@ -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>

View 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)')
})
})

View File

@ -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",

View File

@ -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' }}

View File

@ -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 = {

View File

@ -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"