11import React , { useEffect , useMemo , useRef , useState } from 'react' ;
22
33const families = [
4- 'slate ' , 'gray ' , 'zinc ' , 'neutral ' , 'stone ' ,
5- 'red ' , 'orange ' , 'amber ' , 'yellow ' , 'lime ' , 'green ' , 'emerald ' , 'teal' , 'cyan' , 'sky' , 'blue' , 'indigo' , 'violet' , 'purple' , ' fuchsia' , 'pink' , 'rose ',
4+ 'charcoal ' , 'metal ' , 'haiti ' , 'purple ' , 'blueBerry' , 'blue' , 'sky' , 'turquoise ',
5+ 'persianGreen ' , 'pastelGreen ' , 'grass ' , 'carrot ' , 'orange ' , 'red ' , 'raspberry ' , 'fuchsia' ,
66] ;
7- const shades = [ 50 , 100 , 200 , 300 , 400 , 500 , 600 , 700 , 800 , 900 , 950 ] as const ;
7+ const shades = [ 50 , 100 , 200 , 300 , 400 , 500 , 600 , 700 , 800 , 900 ] as const ;
88
9- function className ( family : string , shade : number ) {
10- return `bg -${ family } -${ shade } ` ;
9+ function cssVariableName ( family : string , shade : number ) {
10+ return `--color -${ family } -${ shade } ` ;
1111}
1212
13- type Format = 'class ' | 'hex' | 'rgb' | 'hsl' | 'oklch' | 'var ';
13+ type Format = 'var ' | 'hex' | 'rgb' | 'hsl' | 'oklch' ;
1414
1515export default function ColorPalette ( ) {
1616 const [ query , setQuery ] = useState ( '' ) ;
17- const [ setCopied ] = useState < string > ( '' ) ;
17+ const [ , setCopied ] = useState < string > ( '' ) ;
1818 const [ copiedKey , setCopiedKey ] = useState < string > ( '' ) ;
19- const [ format , setFormat ] = useState < Format > ( 'class ' ) ;
19+ const [ format , setFormat ] = useState < Format > ( 'var ' ) ;
2020 const [ open , setOpen ] = useState ( false ) ;
2121 const cacheRef = useRef < Record < string , string > > ( { } ) ;
2222 const dropdownRef = useRef < HTMLDivElement | null > ( null ) ;
@@ -36,20 +36,20 @@ export default function ColorPalette() {
3636 } catch { }
3737 }
3838
39- function getComputedBg ( cls : string ) : string | null {
40- if ( cacheRef . current [ cls ] ) return cacheRef . current [ cls ] ;
39+ function getComputedBg ( variable : string ) : string | null {
40+ if ( cacheRef . current [ variable ] ) return cacheRef . current [ variable ] ;
4141 try {
4242 const el = document . createElement ( 'div' ) ;
43- el . className = `${ cls } ` ;
4443 el . style . position = 'absolute' ;
4544 el . style . left = '-9999px' ;
4645 el . style . top = '-9999px' ;
4746 el . style . width = '1px' ;
4847 el . style . height = '1px' ;
48+ el . style . backgroundColor = `var(${ variable } )` ;
4949 document . body . appendChild ( el ) ;
5050 const color = getComputedStyle ( el ) . backgroundColor || '' ;
5151 document . body . removeChild ( el ) ;
52- cacheRef . current [ cls ] = color ;
52+ cacheRef . current [ variable ] = color ;
5353 return color ;
5454 } catch {
5555 return null ;
@@ -154,34 +154,12 @@ export default function ColorPalette() {
154154 return [ + ( L ) . toFixed ( 2 ) , + C . toFixed ( 2 ) , + H . toFixed ( 0 ) ] ;
155155 }
156156
157- function formatValue ( family : string , shade : number ) : string {
158- const cls = className ( family , shade ) ;
159- const varName = `--color-${ family } -${ shade } ` ;
160- if ( format === 'class' ) return `bg-${ family } -${ shade } ` ;
161- if ( format === 'var' ) return varName ;
162- const bg = getComputedBg ( cls ) ;
163- const rgb = bg ? rgbFromCss ( bg ) : null ;
164- if ( ! rgb ) return `bg-${ family } -${ shade } ` ;
165- if ( format === 'hex' ) return toHex ( rgb ) . toLowerCase ( ) ;
166- if ( format === 'rgb' ) return `${ rgb [ 0 ] } ${ rgb [ 1 ] } ${ rgb [ 2 ] } ` ;
167- if ( format === 'hsl' ) {
168- const [ h , s , l ] = toHsl ( rgb ) ;
169- return `${ h } ${ s } % ${ l } %` ;
170- }
171- if ( format === 'oklch' ) {
172- const [ L , C , H ] = toOKLCH ( rgb ) ;
173- return `oklch(${ L } ${ C } ${ H } )` ;
174- }
175- return `bg-${ family } -${ shade } ` ;
176- }
177-
178157 function formatValueFromEl ( el : HTMLElement , family : string , shade : number ) : string {
179- const cls = className ( family , shade ) ;
180- if ( format === 'class' ) return `bg-${ family } -${ shade } ` ;
181- if ( format === 'var' ) return `--color-${ family } -${ shade } ` ;
182- const bg = getComputedStyle ( el ) . backgroundColor || getComputedBg ( cls ) ;
158+ const variable = cssVariableName ( family , shade ) ;
159+ if ( format === 'var' ) return variable ;
160+ const bg = getComputedStyle ( el ) . backgroundColor || getComputedBg ( variable ) ;
183161 const rgb = bg ? rgbFromCss ( bg ) : null ;
184- if ( ! rgb ) return `bg- ${ family } - ${ shade } ` ;
162+ if ( ! rgb ) return variable ;
185163 if ( format === 'hex' ) return toHex ( rgb ) . toLowerCase ( ) ;
186164 if ( format === 'rgb' ) return `${ rgb [ 0 ] } ${ rgb [ 1 ] } ${ rgb [ 2 ] } ` ;
187165 if ( format === 'hsl' ) {
@@ -192,7 +170,7 @@ export default function ColorPalette() {
192170 const [ L , C , H ] = toOKLCH ( rgb ) ;
193171 return `oklch(${ L } ${ C } ${ H } )` ;
194172 }
195- return `bg- ${ family } - ${ shade } ` ;
173+ return variable ;
196174 }
197175
198176 useEffect ( ( ) => {
@@ -241,7 +219,7 @@ export default function ColorPalette() {
241219 className = "absolute left-0 top-full z-50 mt-2 w-52 origin-top-left rounded-xl outline outline-zinc-100 shadow bg-white text-[13px] text-zinc-600 divide-y divide-zinc-100"
242220 >
243221 < div className = "py-2" >
244- { ( [ 'class ' , 'hex' , 'rgb' , 'hsl' , 'oklch' , 'var '] as Format [ ] ) . map ( ( opt ) => (
222+ { ( [ 'var ' , 'hex' , 'rgb' , 'hsl' , 'oklch' ] as Format [ ] ) . map ( ( opt ) => (
245223 < button
246224 key = { opt }
247225 role = "menuitem"
@@ -265,7 +243,7 @@ export default function ColorPalette() {
265243 </ div >
266244 < input
267245 type = "text"
268- placeholder = "Filter colors (e.g., blue, gray )"
246+ placeholder = "Filter colors (e.g., charcoal, sky )"
269247 value = { query }
270248 onChange = { ( e ) => setQuery ( e . target . value ) }
271249 className = "flex-1 px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 bg-white border rounded-md outline-none border-zinc-200 focus:border-zinc-300 h-[38px]"
@@ -275,9 +253,9 @@ export default function ColorPalette() {
275253 { list . map ( ( family ) => (
276254 < section key = { family } >
277255 < div className = "mb-2 text-sm font-medium capitalize text-zinc-700" > { family } </ div >
278- < div className = "color- grid" >
256+ < div className = "grid gap-2 grid-cols-2 sm:grid-cols-3 md:grid-cols-5 lg:grid-cols-10 " >
279257 { shades . map ( ( shade ) => {
280- const cls = className ( family , shade ) ;
258+ const variable = cssVariableName ( family , shade ) ;
281259 const label = `${ family } -${ shade } ` ;
282260 const key = label ;
283261 return (
@@ -287,8 +265,9 @@ export default function ColorPalette() {
287265 const value = formatValueFromEl ( e . currentTarget as HTMLElement , family , shade ) ;
288266 copy ( value , key ) ;
289267 } }
290- title = { `Click to copy` }
291- className = { `relative h-14 rounded-md ${ cls } ` }
268+ title = { `Click to copy ${ variable } ` }
269+ className = "relative h-14 rounded-md transition focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
270+ style = { { backgroundColor : `var(${ variable } )` } }
292271 >
293272 { copiedKey === key && (
294273 < span className = "absolute inset-0 grid text-[0.70rem] font-mono place-items-center rounded-md bg-black/40 text-white" > Copied</ span >
@@ -302,29 +281,6 @@ export default function ColorPalette() {
302281 </ section >
303282 ) ) }
304283 </ div >
305-
306- < style jsx > { `
307- .color-grid {
308- display: grid;
309- grid-template-columns: repeat(2, minmax(0, 1fr));
310- gap: 0.5rem;
311- }
312- @media (min-width: 640px) {
313- .color-grid {
314- grid-template-columns: repeat(3, minmax(0, 1fr));
315- }
316- }
317- @media (min-width: 768px) {
318- .color-grid {
319- grid-template-columns: repeat(6, minmax(0, 1fr));
320- }
321- }
322- @media (min-width: 1024px) {
323- .color-grid {
324- grid-template-columns: repeat(11, minmax(0, 1fr));
325- }
326- }
327- ` } </ style >
328284 </ div >
329285 ) ;
330286}
0 commit comments