Skip to content

Commit 3693591

Browse files
committed
Haha! Might help if I added the new files to the repo.
1 parent 82186aa commit 3693591

4 files changed

Lines changed: 438 additions & 0 deletions

File tree

src/components/ThemeApplicator.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useEffect } from "react";
2+
import { useStorageState } from "react-use-storage-state";
3+
4+
function ThemeApplicator() {
5+
const [customThemes] = useStorageState("site-theme-customizations", { light: {}, dark: {} });
6+
7+
useEffect(() => {
8+
const styleElId = 'custom-theme-styles';
9+
let styleEl = document.getElementById(styleElId);
10+
if (!styleEl) {
11+
styleEl = document.createElement('style');
12+
styleEl.id = styleElId;
13+
document.head.appendChild(styleEl);
14+
}
15+
16+
const lightCustomizations = Object.entries(customThemes.light).map(([key, val]) => `${key}: ${val};`).join('\n');
17+
const darkCustomizations = Object.entries(customThemes.dark).map(([key, val]) => `${key}: ${val};`).join('\n');
18+
19+
styleEl.innerHTML = `
20+
:root[color-mode="light"] {
21+
${lightCustomizations}
22+
}
23+
:root[color-mode="dark"] {
24+
${darkCustomizations}
25+
}
26+
`;
27+
// No cleanup function needed if we want styles to persist
28+
}, [customThemes]);
29+
30+
return null;
31+
}
32+
33+
export default ThemeApplicator;

src/components/ThemeCustomizer.js

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import React, { useState, useEffect, useMemo } from "react";
2+
import { useStorageState } from "react-use-storage-state";
3+
import { HexColorPicker, HexColorInput } from "react-colorful";
4+
import Modal from "./Modal";
5+
6+
const lightDefaults = {
7+
"--main-bg-color": "white",
8+
"--main-font-color": "black",
9+
"--secondary-font-color": "#333",
10+
"--main-heading-color": "#1a3e6f",
11+
"--main-fg-color": "#1a3e6f",
12+
"--bg-color2": "#999999",
13+
"--secondary-color-1": "#ff6633",
14+
"--secondary-color-1-lighter": "#ff8962",
15+
"--secondary-color-2": "#99cccc",
16+
"--secondary-color-3": "#008ca8",
17+
"--secondary-color-3-lighter": "#009fbf",
18+
"--secondary-color-3-bg": "#eefcff",
19+
"--tag-background-color": "#f5f5f5",
20+
"--svg-default-fill": "black",
21+
"--svg-volcano-caps": "black",
22+
};
23+
24+
const darkDefaults = {
25+
"--main-bg-color": "#222",
26+
"--main-font-color": "#e6f2f2",
27+
"--secondary-font-color": "#c3d6f1",
28+
"--main-heading-color": "#008ca8",
29+
"--main-fg-color": "#009fbf",
30+
"--bg-color2": "#999999",
31+
"--secondary-color-1": "#ff6633",
32+
"--secondary-color-1-lighter": "#ff8962",
33+
"--secondary-color-2": "#99cccc",
34+
"--secondary-color-3": "#008ca8",
35+
"--secondary-color-3-lighter": "#009fbf",
36+
"--secondary-color-3-bg": "#333",
37+
"--tag-background-color": "#444",
38+
"--svg-default-fill": "var(--main-font-color)",
39+
"--svg-volcano-caps": "#888",
40+
};
41+
42+
const customizableProperties = [
43+
"--main-bg-color",
44+
"--main-font-color",
45+
"--secondary-font-color",
46+
"--main-heading-color",
47+
"--main-fg-color",
48+
"--bg-color2",
49+
"--secondary-color-1",
50+
"--secondary-color-1-lighter",
51+
"--secondary-color-2",
52+
"--secondary-color-3",
53+
"--secondary-color-3-lighter",
54+
"--secondary-color-3-bg",
55+
"--tag-background-color",
56+
"--svg-default-fill",
57+
"--svg-volcano-caps",
58+
];
59+
60+
function ThemeCustomizer({ show, handleClose }) {
61+
const [colorMode, setColorMode] = useStorageState("color-mode", "light");
62+
const [customThemes, setCustomThemes] = useStorageState("site-theme-customizations", { light: {}, dark: {} });
63+
const [localCustomThemes, setLocalCustomThemes] = useState(customThemes);
64+
65+
const [selectedVar, setSelectedVar] = useState(customizableProperties[0]);
66+
67+
const currentThemeValues = useMemo(() => {
68+
const defaults = colorMode === 'light' ? lightDefaults : darkDefaults;
69+
const customs = localCustomThemes[colorMode] || {};
70+
return { ...defaults, ...customs };
71+
}, [colorMode, localCustomThemes]);
72+
73+
const [currentColor, setCurrentColor] = useState(currentThemeValues[selectedVar]);
74+
75+
useEffect(() => {
76+
if (show) {
77+
setLocalCustomThemes(customThemes);
78+
}
79+
}, [show, customThemes]);
80+
81+
useEffect(() => {
82+
setCurrentColor(currentThemeValues[selectedVar]);
83+
}, [selectedVar, currentThemeValues]);
84+
85+
const handleColorChange = (newColor) => {
86+
setCurrentColor(newColor);
87+
setLocalCustomThemes(prev => {
88+
const newCustomThemes = { ...prev };
89+
if (!newCustomThemes[colorMode]) {
90+
newCustomThemes[colorMode] = {};
91+
}
92+
newCustomThemes[colorMode][selectedVar] = newColor;
93+
return newCustomThemes;
94+
});
95+
};
96+
97+
const handleSave = () => {
98+
setCustomThemes(localCustomThemes);
99+
handleClose();
100+
};
101+
102+
const handleReset = () => {
103+
setLocalCustomThemes({ light: {}, dark: {} });
104+
};
105+
106+
const handleResetCurrent = () => {
107+
setLocalCustomThemes(prev => ({
108+
...prev,
109+
[colorMode]: {}
110+
}));
111+
}
112+
113+
const jsonOutput = useMemo(() => {
114+
return JSON.stringify(localCustomThemes, null, 2);
115+
}, [localCustomThemes]);
116+
117+
const previewWrapperStyle = useMemo(() => {
118+
const defaults = colorMode === 'light' ? lightDefaults : darkDefaults;
119+
const customs = localCustomThemes[colorMode] || {};
120+
return { ...defaults, ...customs };
121+
}, [colorMode, localCustomThemes]);
122+
123+
return (
124+
<Modal
125+
show={show}
126+
title="Customize Site Theme"
127+
buttons={[
128+
{ label: "Save", action: handleSave },
129+
{ label: "Close", action: handleClose }
130+
]}
131+
>
132+
<div className="columns">
133+
<div className="column">
134+
<div className="tabs is-toggle is-fullwidth">
135+
<ul>
136+
<li className={colorMode === 'light' ? 'is-active' : ''}>
137+
<a onClick={() => setColorMode('light')}>Light Mode</a>
138+
</li>
139+
<li className={colorMode === 'dark' ? 'is-active' : ''}>
140+
<a onClick={() => setColorMode('dark')}>Dark Mode</a>
141+
</li>
142+
</ul>
143+
</div>
144+
<div className="field">
145+
<label className="label">CSS Variable</label>
146+
<div className="control">
147+
<div className="select is-fullwidth">
148+
<select value={selectedVar} onChange={e => setSelectedVar(e.target.value)}>
149+
{customizableProperties.map(prop => (
150+
<option key={prop} value={prop}>{prop.replace('--', '').replace(/-/g, ' ')}</option>
151+
))}
152+
</select>
153+
</div>
154+
</div>
155+
</div>
156+
<HexColorPicker color={currentColor} onChange={handleColorChange} />
157+
<HexColorInput color={currentColor} onChange={handleColorChange} prefixed className="input mt-3" />
158+
159+
<div className="field mt-4">
160+
<label className="label">Customizations (JSON)</label>
161+
<div className="control">
162+
<textarea className="textarea" readOnly value={jsonOutput} rows={10}></textarea>
163+
</div>
164+
</div>
165+
<div className="buttons mt-4">
166+
<button className="button apButton" onClick={handleResetCurrent}>Reset Current Theme</button>
167+
<button className="button apButtonNeutral" onClick={handleReset}>Reset All Themes</button>
168+
</div>
169+
</div>
170+
<div className="column" style={previewWrapperStyle}>
171+
<h3 className="subtitle" style={{color: "var(--main-font-color)"}}>Preview</h3>
172+
<div style={{border: "1px solid var(--main-font-color)", padding: "1em", backgroundColor: "var(--main-bg-color)", color: "var(--main-font-color)"}}>
173+
<h1 className="title" style={{color: "var(--main-heading-color)"}}>Sample Heading</h1>
174+
<p>This is some sample text. <a href="#" style={{color: "var(--secondary-color-3)"}}>This is a link.</a></p>
175+
<p style={{color: "var(--secondary-font-color)"}}>This is secondary font color.</p>
176+
<div className="buttons">
177+
<button className="button apButton">Primary Button</button>
178+
<button className="button apButtonAlert">Alert Button</button>
179+
</div>
180+
<div className="tags mt-2">
181+
<span className="tag" style={{backgroundColor: "var(--tag-background-color)", color: "var(--main-font-color)"}}>Tag 1</span>
182+
<span className="tag" style={{backgroundColor: "var(--tag-background-color)", color: "var(--main-font-color)"}}>Tag 2</span>
183+
</div>
184+
</div>
185+
</div>
186+
</div>
187+
</Modal>
188+
);
189+
}
190+
191+
export default ThemeCustomizer;

src/pages/ThemeApplicator.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useEffect } from "react";
2+
import { useStorageState } from "react-use-storage-state";
3+
4+
function ThemeApplicator() {
5+
const [customThemes] = useStorageState("site-theme-customizations", { light: {}, dark: {} });
6+
7+
useEffect(() => {
8+
const styleElId = 'custom-theme-styles';
9+
let styleEl = document.getElementById(styleElId);
10+
if (!styleEl) {
11+
styleEl = document.createElement('style');
12+
styleEl.id = styleElId;
13+
document.head.appendChild(styleEl);
14+
}
15+
16+
const lightCustomizations = Object.entries(customThemes.light).map(([key, val]) => `${key}: ${val};`).join('\n');
17+
const darkCustomizations = Object.entries(customThemes.dark).map(([key, val]) => `${key}: ${val};`).join('\n');
18+
19+
styleEl.innerHTML = `
20+
:root[color-mode="light"] {
21+
${lightCustomizations}
22+
}
23+
:root[color-mode="dark"] {
24+
${darkCustomizations}
25+
}
26+
`;
27+
// No cleanup function needed if we want styles to persist
28+
}, [customThemes]);
29+
30+
return null;
31+
}
32+
33+
export default ThemeApplicator;

0 commit comments

Comments
 (0)