Setting the file. One moment.
Color Pair · Shadcn · shadcn-ui/ui · Skills Docs
ContentsBack to the top of the page assets/showcase/ color-pair.tsx
TypeScript · 126 lines · 3 KB
)
9 canvas.width = canvas.height = 1
10 const context = canvas. getContext ( "2d" , { willReadFrequently: true })
11 if ( ! context) {
12 return null
13 }
14 context.fillStyle = backdrop
15 context. fillRect ( 0 , 0 , 1 , 1 )
16 context.fillStyle = color
17 context. fillRect ( 0 , 0 , 1 , 1 )
18 const [ r , g , b ] = context. getImageData ( 0 , 0 , 1 , 1 ).data
19 return [r, g, b]
20 }
21
22 function toHex ( rgb : number []) {
23 return `#${ rgb . map (( value ) => value . toString ( 16 ). padStart ( 2 , "0" )). join ( "" ) }`
24 }
25
26 function luminance ([ r , g , b ] : number []) {
27 const [ R , G , B ] = [r, g, b]. map (( value ) => {
28 const channel = value / 255
29 return channel <= 0.03928
30 ? channel / 12.92
31 : ((channel + 0.055 ) / 1.055 ) ** 2.4
32 })
33 return 0.2126 * R + 0.7152 * G + 0.0722 * B
34 }
35
36 function contrast ( a : number [], b : number []) {
37 const [ light , dark ] = [ luminance (a), luminance (b)]. sort (( x , y ) => y - x)
38 return (light + 0.05 ) / (dark + 0.05 )
39 }
40
41 export function ColorPair ({
42 name ,
43 background ,
44 foreground ,
45 className ,
46 } : {
47 name : string
48 background : string
49 foreground : string
50 className ?: string
51 }) {
52 const ref = React. useRef < HTMLDivElement >( null )
53 const [ info , setInfo ] = React. useState <{ ratio : number ; bg : string } | null >(
54 null
55 )
56
57 React. useEffect (() => {
58 const element = ref.current
59 if ( ! element) {
60 return
61 }
62 // Re-measure when the theme class on <html> changes.
63 const measure = () => {
64 const style = getComputedStyle (element)
65 const page = getComputedStyle (document.body).backgroundColor
66 const bg = toRgb (style.backgroundColor, page)
67 if ( ! bg) {
68 return
69 }
70 const fg = toRgb (style.color, toHex (bg))
71 if (fg) {
72 setInfo ({ ratio: contrast (bg, fg), bg: toHex (bg) })
73 }
74 }
75 measure ()
76 const observer = new MutationObserver (measure)
77 observer. observe (document.documentElement, { attributes: true })
78 return () => observer. disconnect ()
79 }, [])
80
81 const level = ! info
82 ? ""
83 : info.ratio >= 7
84 ? "AAA"
85 : info.ratio >= 4.5
86 ? "AA"
87 : info.ratio >= 3
88 ? "AA Large"
89 : "Fail"
90
91 return (
92 < div
93 className ={ cn (
94 "flex flex-col overflow-hidden rounded-xl border" ,
95 className
96 ) }
97 >
98 < div
99 ref ={ ref }
100 style ={ {
101 backgroundColor: `var(--${ background })` ,
102 color: `var(--${ foreground })` ,
103 } }
104 className = "flex h-24 items-end justify-between p-4"
105 >
106 < span className = "text-2xl font-medium" >Aa</ span >
107 { info && (
108 < span className = "rounded-full border border-current/20 px-2 py-0.5 font-mono text-xs" >
109 { info.ratio. toFixed ( 2 ) } { level }
110 </ span >
111 ) }
112 </ div >
113 < div className = "flex flex-col gap-0.5 bg-background p-3" >
114 < span className = "text-sm font-medium" > { name } </ span >
115 < span className = "font-mono text-xs text-muted-foreground" >
116 -- { background } / -- { foreground }
117 </ span >
118 { info && (
119 < span className = "font-mono text-xs text-muted-foreground" >
120 { info.bg }
121 </ span >
122 ) }
123 </ div >
124 </ div >
125 )
126 }