22import { PALETTE_STEPS } from '@/config/config'
33import { getStepIndex } from '@/config/helpers'
44import { contrast } from '@/utils/color'
5- import { Trash , Pencil } from 'lucide-react'
5+ import { Trash } from 'lucide-react'
66import Color from 'colorjs.io'
77import React , { useState , useEffect , useRef , useMemo } from 'react'
88
@@ -15,6 +15,8 @@ type ColorScaleProps = {
1515 onRename ?: ( newName : string ) => void
1616 onChangeHex ?: ( newHex : string ) => void
1717 onRemove ?: ( ) => void
18+ /** Stable identifier for testing - should not change when name/hex changes */
19+ testId ?: string
1820}
1921
2022// Type for color information in OKLCH format!
@@ -83,6 +85,7 @@ function ColorScaleBase({
8385 onRename,
8486 onChangeHex,
8587 onRemove,
88+ testId,
8689} : ColorScaleProps ) {
8790 // State to track client-side rendering for contrast calculations
8891 const [ isClient , setIsClient ] = useState ( false )
@@ -112,13 +115,15 @@ function ColorScaleBase({
112115 onRename,
113116 onChangeHex,
114117 onRemove,
118+ testId,
115119 } : {
116120 name ?: string
117121 headingColor : string
118122 baseHex ?: string
119123 onRename ?: ( n : string ) => void
120124 onChangeHex ?: ( h : string ) => void
121125 onRemove ?: ( ) => void
126+ testId ?: string
122127 } ) {
123128 const colorInputRef = useRef < HTMLInputElement | null > ( null )
124129 const [ localHex , setLocalHex ] = useState ( baseHex || '#000000' )
@@ -138,10 +143,11 @@ function ColorScaleBase({
138143 className = "min-w-0 max-w-40 flex-1 px-3 py-1.5 rounded-md border border-transparent hover:border-neutral-subtle focus:border-neutral-strong focus:bg-canvas bg-surface text-strong font-medium"
139144 style = { { color : headingColor } }
140145 aria-label = "Color name"
146+ data-testid = { testId ? `${ testId } -input-name` : undefined }
141147 />
142148 < input
143149 ref = { colorInputRef }
144- type = "color "
150+ type = "text "
145151 value = { localHex }
146152 onChange = { ( e ) => {
147153 const next = e . target . value
@@ -153,25 +159,20 @@ function ColorScaleBase({
153159 onChangeHex ?.( next )
154160 } , 250 )
155161 } }
156- className = "sr-only"
157- aria-label = { `Pick base color for ${ name ?? 'color' } ` }
158- tabIndex = { - 1 }
162+ placeholder = "#000000"
163+ className = "px-3 py-1.5 w-24 font-mono text-sm rounded-md border border-transparent hover:border-neutral-subtle focus:border-neutral-strong focus:bg-canvas bg-surface"
164+ aria-label = { `Base color hex value for ${ name ?? 'color' } ` }
165+ pattern = "^#[0-9A-Fa-f]{6}$"
166+ title = "Enter hex color (e.g., #FF0000)"
167+ data-testid = { testId ? `${ testId } -input-hex` : undefined }
159168 />
160- < button
161- type = "button"
162- onClick = { ( ) => colorInputRef . current ?. click ( ) }
163- className = "inline-flex items-center justify-center w-8 h-8 rounded-md hover:bg-neutral-fill-muted-hover print-hide"
164- title = "Edit base color"
165- aria-label = "Edit base color"
166- >
167- < Pencil className = "w-4 h-4" />
168- </ button >
169169 < button
170170 type = "button"
171171 onClick = { ( ) => onRemove ?.( ) }
172- className = "inline-flex items-center justify-center w-8 h-8 rounded-md border-neutral-subtle hover:bg-neutral-fill-muted-hover print-hide"
172+ className = "inline-flex items-center justify-center w-8 h-8 rounded-md border-neutral-subtle hover:bg-neutral-fill-muted-hover print-hide"
173173 title = "Remove color"
174174 aria-label = "Remove color"
175+ data-testid = { testId ? `${ testId } -remove-button` : undefined }
175176 >
176177 < Trash className = "w-4 h-4" />
177178 </ button >
@@ -181,7 +182,8 @@ function ColorScaleBase({
181182 ( prev , next ) =>
182183 prev . name === next . name &&
183184 prev . baseHex === next . baseHex &&
184- prev . headingColor === next . headingColor ,
185+ prev . headingColor === next . headingColor &&
186+ prev . testId === next . testId ,
185187 )
186188 } , [ ] )
187189
@@ -316,21 +318,20 @@ function ColorScaleBase({
316318 onRename = { onRename }
317319 onChangeHex = { onChangeHex }
318320 onRemove = { onRemove }
321+ testId = { testId }
319322 />
320- < div className = "grid grid-cols-15 gap-2 mb-4 print:mb-0 print:gap-0" >
323+ < div className = "grid gap-2 mb-4 grid-cols-15 print:mb-0 print:gap-0" >
321324 { colors . map ( ( color : string , i : number ) => {
322325 const textColor = getTextColorForStep ( colors , i + 1 )
323326 const step = PALETTE_STEPS [ i ]
324327 const pairsWithSteps = step ?. contrastWith || [ ]
325328 const oklchInfo = getOklchInfo ( color , i )
326329 const isDialogActive = activeDialog === i
327- const testId = colorName
328- ? `${ colorName . replace ( / \s + / g, '-' ) . toLowerCase ( ) } -${ i } `
329- : `color-step-${ i } `
330+ const stepTestId = testId ? `${ testId } -step-${ i } ` : `color-step-${ i } `
330331
331332 return (
332333 < div
333- data-testid = { testId }
334+ data-testid = { stepTestId }
334335 key = { 'color-step-' + i }
335336 ref = { ( el ) => {
336337 colorElementRefs . current [ i ] = el
@@ -414,7 +415,7 @@ function ColorScaleBase({
414415
415416 < div className = "flex flex-col justify-center" >
416417 < button
417- className = "mb-2 font-mono text-base text-left hover:bg-black/10 dark:hover:bg-white/10 rounded px-2 py-1 -mx-2 flex items-center gap-2 group"
418+ className = "flex items-center gap-2 px-2 py-1 mb-2 -mx-2 font-mono text-base text-left rounded hover:bg-black/10 dark:hover:bg-white/10 group"
418419 onClick = { ( e ) => {
419420 e . stopPropagation ( )
420421 copyToClipboard ( oklchInfo . hex , i )
@@ -441,7 +442,7 @@ function ColorScaleBase({
441442 ) : (
442443 // Copy icon (default state)
443444 < svg
444- className = "w-4 h-4 opacity-0 group-hover:opacity-100 transition-opacity "
445+ className = "w-4 h-4 transition- opacity opacity -0 group-hover:opacity-100"
445446 fill = "none"
446447 stroke = "currentColor"
447448 viewBox = "0 0 24 24"
@@ -522,7 +523,7 @@ function ColorScaleBase({
522523 >
523524 < td className = "flex items-center gap-2 py-1 pr-2" >
524525 < div
525- className = "w-3 h-3 border border-neutral-subtle rounded-full "
526+ className = "w-3 h-3 border rounded-full border-neutral-subtle"
526527 style = { {
527528 backgroundColor : colors [ targetStepIndex ] ,
528529 } }
@@ -620,7 +621,8 @@ function areEqual(prev: ColorScaleProps, next: ColorScaleProps) {
620621 prev . baseHex === next . baseHex &&
621622 prev . showContrast === next . showContrast &&
622623 prev . contrastMethod === next . contrastMethod &&
623- prev . colors === next . colors
624+ prev . colors === next . colors &&
625+ prev . testId === next . testId
624626 )
625627}
626628
0 commit comments