In this tutorial you'll learn how to create a custom theme editor and a simple Color Panel Editor.
In this tutorial you'll use:
Prerequisites
You need HyperTheme Editor installed and working on your Chakra UI Project.
If you still not have installed HyperTheme, follow the installation instructions here.
HyperThemeEditor component
In the Installation page we have added the HyperThemeEditor
component that gives us a plug&play editor on the page through a Button
.
The source code of HyperThemeEditor
itself it's very simple as you can see here:
import React, { FC } from 'react'
import {
ThemeEditor,
ThemeEditorButton,
ThemeEditorButtonProps,
ThemeEditorDrawer,
} from '@hypertheme-editor/chakra-ui-core'
import { ThemeEditorColors } from '@hypertheme-editor/chakra-ui-colors'
import { ThemeEditorFontSizes } from '@hypertheme-editor/chakra-ui-font-sizes'
import { CgColorPicker } from 'react-icons/cg'
import { ImFontSize } from 'react-icons/im'
export type DefaultThemeEditorProps = ThemeEditorButtonProps
export const HyperThemeEditor: FC<DefaultThemeEditorProps> = (props) => {
return (
<ThemeEditor>
<ThemeEditorButton {...props} />
<ThemeEditorDrawer>
<ThemeEditorColors icon={CgColorPicker} title="Colors" />
<ThemeEditorFontSizes icon={ImFontSize} title="Font Sizes" />
</ThemeEditorDrawer>
</ThemeEditor>
)
}
Read more about HyperThemeEditor
component here.
Creating a Custom ThemeEditor
Based on the source code of HyperThemeEditor
we can create a custom ThemeEditor.
To do so, create a new MyThemeEditor
component with this content:
import React, { FC } from 'react'
import {
ThemeEditor,
ThemeEditorButton,
ThemeEditorButtonProps,
ThemeEditorColors,
ThemeEditorFontSizes,
} from '@hypertheme-editor/chakra-ui'
import { CgColorPicker } from 'react-icons/cg'
import { BiText } from 'react-icons/bi'
export const MyThemeEditor = (props) => {
return (
<ThemeEditor>
<ThemeEditorButton {...props} />
<ThemeEditorDrawer>
<ThemeEditorColors icon={CgColorPicker} title="Colors" />
<ThemeEditorFontSizes icon={BiText} title="Typography" />
</ThemeEditorDrawer>
</ThemeEditor>
)
}
As you can see, you can change the order of the panels, the icons and the labels.
HyperTheme uses react-icons
for rendering the icons on the editor, here you can search for the icon you need.
Read more about: ThemeEditor
, ThemeEditorDrawer
and ThemeEditorButton
.
Creating a custom Panel
Now that we have a custom ThemeEditor component we can start creating our custom editor panel.
Create a new MyColorEditor
component:
import React from 'react'
import { Box } from '@chakra-ui/react'
export default function MyCustomPanel(props) {
return <Box>Testing Custom Panel</Box>
}
Add it as a new Panel to our MyThemeEditor
component:
import React, { FC } from 'react'
import {
ThemeEditor,
ThemeEditorButton,
ThemeEditorButtonProps,
ThemeEditorColors,
ThemeEditorFontSizes,
} from '@hypertheme-editor/chakra-ui'
import { CgColorPicker } from 'react-icons/cg'
import { BiText } from 'react-icons/bi'
import MyCustomPanel from './MyCustomPanel'
export default function MyThemeEditor(props) {
return (
<ThemeEditor>
<ThemeEditorButton {...props} />
<ThemeEditorDrawer>
{/* Add the MyCustomPanel to our theme editor */}
<MyCustomPanel icon={CgColorPicker} title="My Panel" />
<ThemeEditorColors icon={CgColorPicker} title="Colors" />
<ThemeEditorFontSizes icon={BiText} title="Typography" />
</ThemeEditorDrawer>
</ThemeEditor>
)
}
Now you have a custom panel inside the ThemeEditorDrawer
component, your theme editor should look like the example below:
function MyCustomPanel(props) {
return <Box>Testing Custom Panel</Box>
}
function MyThemeEditor(props) {
return (
<ThemeEditor>
<ThemeEditorButton {...props} />
<ThemeEditorDrawer>
{/* Add the MyCustomPanel to our theme editor */}
<MyCustomPanel icon={CgColorPicker} title="My Panel" />
<ThemeEditorColors icon={CgColorPicker} title="Colors" />
<ThemeEditorFontSizes icon={BiText} title="Typography" />
</ThemeEditorDrawer>
</ThemeEditor>
)
}
render(<MyThemeEditor />)
useThemeEditor hook
Now that we have a theme editor with a custom editor panel, it's time to start retrieving and live edit the theme.
HyperTheme Editor provides the useThemeEditor
hook to:
- maintaint the current theme state
- live edit the current theme
- manage changes history
Check out more on the documentation.
Retrieving and show the current theme
In this section we're creating a ThemeColorBox
that shows a color for the theme.
Create a new ThemeColorBox
component with this content:
import React from 'react'
import { Box } from '@chakra-ui/react'
import { useThemeEditor } from '@hypertheme-editor/chakra-ui'
export default function ThemeColorBox({ token, paletteIndex = 500, ...props }) {
const { theme } = useThemeEditor()
const color = useMemo(() => {
// in Chakra UI colors could objects
// with an index (100, 200, 300, etc) or a single color
if (theme && theme.colors[token]) {
// if the color is a string, return it
if (typeof theme.colors[token] === 'string') {
return theme.colors[token]
// if it's an object return the current paletteIndex for that color
} else if (theme.colors[token][paletteIndex]) {
return theme.colors[token][paletteIndex]
}
}
return 'gray'
}, [theme, token, paletteIndex])
return <Box w="40px" h="40px" borderRadius="md" bgColor={color} {...props} />
}
Let's check our new component with some different theme colors, try to change the colors from the editor to check that is working correctly:
<HStack>
<HyperThemeEditor />
<ThemeColorBox token="blue" paletteIndex={500} />
<ThemeColorBox token="red" paletteIndex={500} />
<ThemeColorBox token="green" paletteIndex={500} />
<ThemeColorBox token="orange" paletteIndex={500} />
<ThemeColorBox token="yellow" paletteIndex={500} />
<ThemeColorBox token="purple" paletteIndex={500} />
</HStack>
Live edit the current theme
It's time to live edit the current theme.
Let's create a SimpleColorEditor
component that shows the current color and set a new one through an Input
:
import React from 'react'
import { useThemeEditor } from '@hypertheme-editor/chakra-ui'
import { Input } from '@chakra-ui/react'
import { useDebouncyEffect } from 'use-debouncy'
import { colord } from 'colord'
export default function SimpleColorEditor({ token, paletteIndex, ...props }) {
const { theme, setTheme } = useThemeEditor()
const [inputValue, setInputValue] = useState<string>(theme.colors[token][paletteIndex] || '')
const handleOnChange = useCallback((event) => {
setInputValue(event.target.value)
}, [])
// use a debounced effect so the UI is not blocked
// while the value are changed through the Input
useDebouncyEffect(
() => {
// check that the input color is valid
if (colord(inputValue).isValid()) {
// the color token could be a simple color or a palette object,
// so we have to check it.
// We also check that the input value differ from
// the one from the theme, this is necessary
// for undo/redo functionality to work correctly
if (typeof theme.colors[token] === 'string' && theme.colors[token] !== inputValue) {
// live edit the current theme
setTheme({
...theme,
colors: {
...theme.colors,
[token]: inputValue,
},
})
} else if (
theme.colors[token][paletteIndex] &&
theme.colors[token][paletteIndex] !== inputValue
) {
// live edit the current theme
setTheme({
...theme,
colors: {
...theme.colors,
[token]: {
...theme.colors.token,
[paletteIndex]: inputValue,
},
},
})
}
}
},
500,
[inputValue]
)
// update internal state if another panel change this value
useEffect(() => {
if (
theme.colors[token] &&
theme.colors[token][paletteIndex] &&
theme.colors[token][paletteIndex] !== inputValue
) {
setInputValue(theme.colors[token][paletteIndex])
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [theme.colors, token, paletteIndex])
return (
<HStack>
{/* add the ThemeColorBox we've created before */}
<ThemeColorBox token={token} paletteIndex={paletteIndex} />
<Input onChange={handleOnChange} value={inputValue} />
</HStack>
)
}
Let's try our new SimpleColorEditor
component.
If you change a value (using HEX notation) you should see the new color in current theme.
Compare the edited colors with the value provided by HyperThemeEditor
.
<VStack>
<HyperThemeEditor />
<SimpleColorEditor token="blue" paletteIndex={500} />
<SimpleColorEditor token="red" paletteIndex={500} />
<SimpleColorEditor token="green" paletteIndex={500} />
<SimpleColorEditor token="orange" paletteIndex={500} />
<SimpleColorEditor token="yellow" paletteIndex={500} />
<SimpleColorEditor token="purple" paletteIndex={500} />
</VStack>
Final mounting
Finally, we can mount our SimpleColorEditor
into our custom theme editor.
Edit the MyPanel
component like this:
import React from 'react'
import { Box } from '@chakra-ui/react'
export default function MyCustomPanel(props) {
return (
<VStack>
<SimpleColorEditor token="blue" paletteIndex={500} />
<SimpleColorEditor token="red" paletteIndex={500} />
<SimpleColorEditor token="green" paletteIndex={500} />
<SimpleColorEditor token="orange" paletteIndex={500} />
<SimpleColorEditor token="yellow" paletteIndex={500} />
<SimpleColorEditor token="purple" paletteIndex={500} />
</VStack>
)
}
The final result should be like this:
function MyCustomPanel(props) {
return (
<VStack>
<SimpleColorEditor token="blue" paletteIndex={500} />
<SimpleColorEditor token="red" paletteIndex={500} />
<SimpleColorEditor token="green" paletteIndex={500} />
<SimpleColorEditor token="orange" paletteIndex={500} />
<SimpleColorEditor token="yellow" paletteIndex={500} />
<SimpleColorEditor token="purple" paletteIndex={500} />
</VStack>
)
}
function MyThemeEditor(props) {
return (
<ThemeEditor>
<ThemeEditorButton {...props} />
<ThemeEditorDrawer>
{/* Add the MyCustomPanel to our theme editor */}
<MyCustomPanel icon={CgColorPicker} title="My Panel" />
<ThemeEditorColors icon={CgColorPicker} title="Colors" />
<ThemeEditorFontSizes icon={BiText} title="Typography" />
</ThemeEditorDrawer>
</ThemeEditor>
)
}
render(<MyThemeEditor />)
Congratulations! You have created your first working custom panel.
Top comments (2)
Are the links alright?
Sorry for that! now the problem is fixed ๐