Skip to content

Commit 65e648a

Browse files
feat(eds-color-palette-generator): order colors and use text input for hex (#4022)
1 parent 645e090 commit 65e648a

File tree

6 files changed

+152
-150
lines changed

6 files changed

+152
-150
lines changed

packages/eds-color-palette-generator/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
"@playwright/test": "^1.55.0",
3434
"@tailwindcss/postcss": "^4.1.12",
3535
"@types/node": "^20.19.11",
36-
"@types/react": "^18.3.24",
37-
"@types/react-dom": "^18.3.7",
36+
"@types/react": "^19.1.13",
37+
"@types/react-dom": "^19.1.9",
3838
"dotenv": "^17.2.1",
3939
"eslint": "^9.34.0",
4040
"eslint-config-next": "15.2.4",

packages/eds-color-palette-generator/src/app/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ export default function App() {
351351
contrastMethod={contrastMethod}
352352
colorName={colorData.name}
353353
baseHex={colors[index]?.hex}
354+
testId={`color-scale-${index}`}
354355
onRename={(name) => updateColorName(index, name)}
355356
onChangeHex={(hex) => updateColorHex(index, hex)}
356357
onRemove={() => removeColor(index)}

packages/eds-color-palette-generator/src/components/ColorManagement.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,18 @@ export const ColorManagement = ({
4646
/>
4747
</div>
4848

49-
{/* Color Picker */}
49+
{/* Color Hex Input */}
5050
<div className="flex items-center gap-2">
5151
<input
52-
type="color"
53-
className="w-8 h-8 border border-neutral-medium rounded cursor-pointer"
52+
type="text"
53+
className="w-full p-2 text-sm font-mono bg-input border border-neutral-subtle rounded"
5454
value={color.hex}
5555
onChange={(e) => onUpdateColorHex(index, e.target.value)}
5656
data-testid={`color-hex-input-${index}`}
57-
aria-label={`Color ${index + 1} value`}
57+
aria-label={`Color ${index + 1} hex value`}
58+
placeholder="#000000"
59+
pattern="^#[0-9A-Fa-f]{6}$"
60+
title="Enter hex color (e.g., #FF0000)"
5861
/>
5962
</div>
6063

packages/eds-color-palette-generator/src/components/ColorScale.tsx

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { PALETTE_STEPS } from '@/config/config'
33
import { getStepIndex } from '@/config/helpers'
44
import { contrast } from '@/utils/color'
5-
import { Trash, Pencil } from 'lucide-react'
5+
import { Trash } from 'lucide-react'
66
import Color from 'colorjs.io'
77
import 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

packages/eds-color-palette-generator/tests/e2e/change-color.spec.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import 'dotenv/config'
33

44
test('should change color name and hex', async ({ page }) => {
55
await page.goto(process.env.PLAYWRIGHT_URL || 'http://localhost:3000/')
6-
await page.getByTestId('config-button').click()
7-
await page.getByTestId('color-name-input-0').click()
8-
await page.getByTestId('color-name-input-0').fill('brand')
96

10-
await page.getByTestId('color-hex-input-0').click()
11-
await page.getByTestId('color-hex-input-0').fill('#ee7e17')
7+
// Use stable test IDs that don't change when name changes
8+
await page.getByTestId('color-scale-0-input-name').fill('Brand')
129

13-
await expect(page.getByTestId('brand-10')).toMatchAriaSnapshot(
10+
await page.getByTestId('color-scale-0-input-hex').click()
11+
await page.getByTestId('color-scale-0-input-hex').fill('#ee7e17')
12+
13+
// Test the 11th color step (index 10) using stable test ID
14+
await expect(page.getByTestId('color-scale-0-step-10')).toMatchAriaSnapshot(
1415
'- \'button "Color 11: oklch(0.420 0.113 54.7), Click for details"\'',
1516
)
1617
})

0 commit comments

Comments
 (0)