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 ;
0 commit comments