Add article about theme colors in JS
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Kaan Barmore-Genç 2023-08-10 00:36:31 -05:00
parent 1a8786305b
commit 97c6cd7eff
Signed by: kaan
GPG Key ID: B2E280771CD62FCF
2 changed files with 135 additions and 0 deletions

BIN
content/img/2023-08-10.chartjs.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,132 @@
---
title: "Getting theme colors in JavaScript using React with DaisyUI and TailwindCSS"
date: 2023-08-10T00:18:27-05:00
toc: false
images:
tags:
- web
- dev
- react
---
I've been building a web app using React and TailwindCSS, with DaisyUI. But
while working on it I hit a minor snag: I'm trying to use Chart.js, and Chart.js
creates a canvas to render charts. But the canvas can't pick up the CSS
variables that are defined on the page itself. That means that my charts can't
use the colors from my theme, unless I manually copy and paste the theme colors!
This is pretty bad for maintainability because if the theme colors are ever
changed, or themes are added or removed, you'll have to come back and update the
colors for the charts as well. Boo!
Luckily, I found out that you can read CSS variables from JavaScript. So I added
this code:
```ts
function getPropertyValue(name: string) {
return getComputedStyle(document.body).getPropertyValue(name);
}
```
This returns the value of any CSS variable you pass it. For example
`getPropertyValue("--tab-border")` returns `1px` for my theme!
Next, I just looked through the CSS on the page to figure out what CSS variables
DaisyUI sets for themes. I quickly found the most important ones I needed: the
primary and secondary colors, and the colors for the text that goes on top of
them.
```ts
const primary = getPropertyValue("--p");
const secondary = getPropertyValue("--s");
const primaryText = getPropertyValue("--pc");
const secondaryText = getPropertyValue("--sc");
```
This is all great! But I had one more concern: I needed a way to change these
variables and re-render components whenever the user toggles between the light
and dark themes.
I decided to use SWR for this. SWR is mainly meant to be used to fetch data from
an API, but there's really nothing stopping you from using it for anything else.
In this case, SWR will cache all the colors in a primary place, and allow me to
re-render all the components when the colors change using its `mutate` API.
Here's how that code looks like:
```ts
export function useThemeColor() {
const themeFetcher = useCallback(() => {
const primary = getPropertyValue("--p");
const primaryText = getPropertyValue("--pc");
const secondary = getPropertyValue("--s");
const secondaryText = getPropertyValue("--sc");
return { primary, primaryText, secondary, secondaryText };
}, []);
// The key "data:theme" could be anything, as long as it's unique in the app
const { data: color, mutate } = useSWR("data:theme", themeFetcher);
return { ...color, mutate };
}
```
It's very easy to use it.
```ts
export default function Dashboard() {
const { primary, primaryContent } = useThemeColor();
// ... fetch the data and labels ...
return (
<Line
data={{
labels,
datasets: [
{
data,
// borderColor is the color of the line
borderColor: `hsl(${primary})`,
// backgroundColor is the color of the dots
backgroundColor: `hsl(${primaryContent})`,
},
],
}}
/>
);
}
```
Here's what that looks like:
![A screenshot of a web page. At the top there is a dark red colored button labelled "Dashboard". There is a line chart below, which uses the same dark red color as the button.](/img/2023-08-10.chartjs.png)
To keep the colors changing whenever the user toggles the theme, you then just
have to call the `mutate` function inside your toggle button.
```ts
export function ThemeToggle() {
const { mutate: mutateTheme } = useThemeColor();
const [theme, setTheme] = useState("light");
const toggleTheme = useCallback(() => {
if (theme === "dark") {
document.body.dataset.theme = "autumn";
setTheme("light");
} else {
document.body.dataset.theme = "forest";
setTheme("dark");
}
mutateTheme();
}, [theme, mutateTheme, setTheme]);
return (
<div className="btn btn-ghost text-xl" onClick={toggleTheme}>
<PiLightbulbBold />
</div>
);
}
```
Oh and that's a bonus trick for you. You can swap the DaisyUI theme by just
setting `document.body.dataset.theme`, as long as that theme is enabled in the
settings.