Skip to content

Commit c7303a0

Browse files
pdanpdansaadeghi
andauthored
docs: theme generator colorpicker improvement
* avoid bind:value in ThemeCSSModal because we already have a listener on it * use a single handleKeydown listener in ColorPalette * use colorName as key to mark colors as selected * fix and optimize inputValue processing in ColorPalette svelte reactivity is not very funny the effects are called right when the value changes (not batched), and we have to be very carefull with the order different state values are changed * track tailwind color name as well as the color value, to show the correct color name in case there's a identical value --------- Co-authored-by: Pouya Saadeghi <[email protected]>
1 parent b20e585 commit c7303a0

File tree

3 files changed

+74
-51
lines changed

3 files changed

+74
-51
lines changed

packages/docs/src/components/ColorPalette.svelte

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
let open = $state(false)
2323
let inputValue = $state(value)
2424
let isDragging = $state(false)
25+
let selectedTailwindColorName = $state(null)
2526
let colorState = $state({
2627
oklch: {},
2728
hsl: {},
@@ -39,53 +40,76 @@
3940
if (event.key === "Escape") {
4041
closeModal()
4142
} else if (event.key === "Enter") {
43+
event.preventDefault()
4244
if (validateColor(inputValue)) {
43-
value = inputValue
44-
inputValue = ""
45+
if (value !== inputValue) {
46+
const v = inputValue
47+
inputValue = ""
48+
value = v
49+
}
4550
closeModal()
4651
}
4752
}
4853
}
4954
50-
function handleDragStart(color) {
55+
function handleDragStart(color, name) {
5156
isDragging = true
5257
dragPreviewColor = color
5358
inputValue = color
59+
selectedTailwindColorName = name
5460
}
5561
56-
function handleDragOver(color) {
62+
function handleDragOver(color, name) {
5763
if (isDragging) {
5864
dragPreviewColor = color
5965
inputValue = color
66+
selectedTailwindColorName = name
6067
}
6168
}
6269
6370
function handleDragEnd(color) {
6471
if (isDragging) {
65-
value = color
66-
inputValue = ""
72+
if (value !== color) {
73+
inputValue = ""
74+
value = color
75+
} else {
76+
inputValue = color
77+
}
6778
isDragging = false
6879
dragPreviewColor = null
6980
}
7081
}
7182
7283
function handleGlobalMouseUp() {
7384
if (isDragging && dragPreviewColor) {
74-
value = dragPreviewColor // Ensure value is updated if drag ends elsewhere
75-
inputValue = ""
85+
if (value !== dragPreviewColor) {
86+
inputValue = ""
87+
value = dragPreviewColor // Ensure value is updated if drag ends elsewhere
88+
} else {
89+
inputValue = dragPreviewColor
90+
}
7691
}
7792
isDragging = false
7893
dragPreviewColor = null
7994
}
8095
96+
let inputDebounce = null
8197
function handleInput(event) {
82-
const newValue = event.target.value
83-
if (validateColor(newValue)) {
84-
value = newValue
85-
inputValue = ""
86-
} else {
87-
inputValue = newValue
98+
if (inputDebounce != null) {
99+
clearTimeout(inputDebounce)
88100
}
101+
inputDebounce = setTimeout(() => {
102+
inputDebounce = null
103+
104+
const newValue = event.target.value
105+
if (validateColor(newValue) && value !== newValue) {
106+
inputValue = ""
107+
value = newValue
108+
selectedTailwindColorName = null
109+
} else {
110+
inputValue = newValue
111+
}
112+
}, 300)
89113
}
90114
91115
function toggleModal() {
@@ -111,34 +135,41 @@
111135
onModalStateChange(false)
112136
}
113137
114-
$effect(() => {
115-
untrack(() => {
116-
if (value !== inputValue) {
117-
updateColorState(value)
118-
}
119-
})
120-
inputValue = value
138+
$effect.pre(() => {
139+
if (open) {
140+
untrack(() => {
141+
if (value !== inputValue) {
142+
updateColorState(value)
143+
}
144+
})
145+
inputValue = value
146+
}
121147
})
122148
123149
// Update inputValue when colorState changes
124-
$effect(() => {
150+
$effect.pre(() => {
125151
const newValue = generateColorValue()
126152
127153
untrack(() => {
128-
if (inputValue !== newValue) {
154+
if (newValue != null && inputValue !== newValue) {
129155
if (!colorState.changed) {
130156
colorState.value = newValue
131157
inputValue = newValue
132158
}
133159
value = newValue
160+
if (colorState.changed) {
161+
selectedTailwindColorName = null
162+
}
134163
}
135164
})
136165
})
137166
138167
// Get the color to display (preview during dragging, actual value otherwise)
139168
const displayColor = $derived(dragPreviewColor || value)
140169
141-
const colorName = $derived(colorDetails.find(([, color]) => color === inputValue)?.[0])
170+
const colorName = $derived(
171+
selectedTailwindColorName || colorDetails.find(([, color]) => color === inputValue)?.[0],
172+
)
142173
const colorPairsMap = $derived.by(() => {
143174
const map = {
144175
color: {},
@@ -247,7 +278,7 @@
247278
try {
248279
if (colorState.mode === "oklch") {
249280
const { l, c, h } = colorState.oklch
250-
return `oklch(${(l * 100).toFixed(1)}% ${c.toFixed(3)} ${h.toFixed(1)})`
281+
return `oklch(${(l * 100).toFixed(1)}% ${c.toFixed(3)} ${h.toFixed(3)})`
251282
}
252283
253284
if (colorState.mode === "hsl") {
@@ -268,7 +299,7 @@
268299
269300
return `rgb(${colorState.rgb.r} ${colorState.rgb.g} ${colorState.rgb.b})`
270301
} catch {
271-
return inputValue // Fallback to current value if conversion fails
302+
return null
272303
}
273304
}
274305
</script>
@@ -398,17 +429,17 @@
398429
<button
399430
class="appearance-none p-px [writing-mode:lr]"
400431
aria-label={name}
401-
onmousedown={() => handleDragStart(color)}
402-
onmouseover={() => handleDragOver(color)}
403-
onfocus={() => handleDragOver(color)}
432+
onmousedown={() => handleDragStart(color, name)}
433+
onmouseover={() => handleDragOver(color, name)}
434+
onfocus={() => handleDragOver(color, name)}
404435
onmouseup={() => handleDragEnd(color)}
405-
onkeypress={() => handleDragStart(color)}
436+
onkeypress={() => handleDragStart(color, name)}
406437
>
407438
<div
408439
class="border-base-content/10 relative grid aspect-square w-5 place-items-center rounded-full border bg-transparent select-none sm:m-px sm:w-7"
409-
class:[box-shadow:0_0_0_2px_white,0_0_0_4px_black]={displayColor === color}
410-
class:outline-white={displayColor === color}
411-
class:outline-offset-[-3px]={displayColor === color}
440+
class:[box-shadow:0_0_0_2px_white,0_0_0_4px_black]={colorName === name}
441+
class:outline-white={colorName === name}
442+
class:outline-offset-[-3px]={colorName === name}
412443
style:background-color={color}
413444
>
414445
{#if initials != null}
@@ -514,17 +545,8 @@
514545
<input
515546
type="text"
516547
class="grow xl:font-mono xl:normal-nums"
517-
bind:value={inputValue}
548+
value={inputValue}
518549
oninput={handleInput}
519-
onkeydown={(event) => {
520-
if (event.key === "Enter") {
521-
event.preventDefault()
522-
if (validateColor(inputValue)) {
523-
value = inputValue
524-
closeModal()
525-
}
526-
}
527-
}}
528550
aria-label={`${name} value`}
529551
/>
530552
{#if colorName}
@@ -554,7 +576,7 @@
554576
<div
555577
class="modal-backdrop"
556578
onclick={closeModal}
557-
onkeydown={(e) => e.key === "Enter" && closeModal()}
579+
onkeydown={handleKeydown}
558580
role="button"
559581
tabindex="0"
560582
aria-label="Close modal"

packages/docs/src/components/themegenerator/ThemeCSSModal.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
}
2525
2626
function handleThemeCSSInput(event) {
27-
const newThemeData = parseThemeCSS(event.target.value, currentTheme)
27+
themeCSS = event.target.value
28+
const newThemeData = parseThemeCSS(themeCSS, currentTheme)
2829
if (newThemeData) {
2930
currentTheme = newThemeData
3031
if (currentTheme.type === "custom") {
@@ -114,7 +115,7 @@
114115
spellcheck="false"
115116
data-theme="dark"
116117
class="textarea textarea-border textarea-xs block h-96 min-h-80 w-full max-w-none resize-none font-mono"
117-
bind:value={themeCSS}
118+
value={themeCSS}
118119
oninput={handleThemeCSSInput}
119120
></textarea>
120121
</div>

packages/docs/src/routes/(routes)/theme-generator/+page.svelte

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,18 @@
9898
})
9999
100100
const colorDetails = $derived(
101-
Object.entries(data.tailwindcolors).map(([key, color]) => {
101+
Object.entries(data.tailwindcolors).map(([keyTw, color]) => {
102102
const names = []
103103
const initials = []
104-
for (const [key, themeColor] of Object.entries(currentTheme)) {
104+
for (const [keyTheme, themeColor] of Object.entries(currentTheme)) {
105105
if (themeColor === color) {
106-
names.push(key.replace("--color-", ""))
107-
initials.push(data.colorInitials[key] || null)
106+
names.push(keyTheme.replace("--color-", ""))
107+
initials.push(data.colorInitials[keyTheme] || null)
108108
}
109109
}
110110
111111
return [
112-
key,
112+
keyTw,
113113
color,
114114
initials.length > 0 ? `${initials[0]}${initials.length > 1 ? "+" : ""}` : null,
115115
names,

0 commit comments

Comments
 (0)