Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/perf upgrade #85

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 12 additions & 20 deletions app/components/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Popover, PopoverButton, PopoverPanel} from '@headlessui/react'
import {SwatchIcon, XMarkIcon} from '@heroicons/react/24/solid'
import React, {useCallback, useEffect} from 'react'
import React, {useCallback, useState} from 'react'
import {HexColorPicker} from 'react-colorful'
import {useDebounceValue} from 'usehooks-ts'
import {useDebounceCallback} from 'usehooks-ts'

import Button from '~/components/Button'
import {inputClasses, labelClasses} from '~/components/Palette'
Expand All @@ -14,25 +14,13 @@ export default function ColorPicker({
ringStyle,
}: {
color: string
onChange: Function
onChange: (value: string) => void
ringStyle: React.CSSProperties
}) {
const [value, setValue] = useDebounceValue(color, 500)

// Update local `value` on form change
useEffect(() => {
setValue(color)
}, [color, setValue])

// Update global `value` on picker change
useEffect(() => {
if (value) {
onChange(value.toUpperCase())
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])

const [value, setValue] = useState(color)
const debounceOnChange = useDebounceCallback(onChange, 500)
const {h, s, l: lightness} = hexToHSL(value)

const handleLightnessChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const newLightness = Number(e.target.value)
Expand All @@ -43,8 +31,9 @@ export default function ColorPicker({

const newValue = HSLToHex(h, s, newLightness)
setValue(newValue)
debounceOnChange(newValue)
},
[h, s, setValue],
[h, s, setValue, debounceOnChange],
)

return (
Expand All @@ -62,7 +51,10 @@ export default function ColorPicker({
<div className="flex flex-col items-justify-center gap-4">
<HexColorPicker
color={value.startsWith(`#`) ? value : `#${value}`}
onChange={setValue}
onChange={(value) => {
setValue(value)
debounceOnChange(value)
}}
/>

<div className="flex flex-col gap-2 px-2">
Expand Down
26 changes: 10 additions & 16 deletions app/components/Generator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import clsx from 'clsx'
import React, {useEffect, useMemo, useState} from 'react'
import React, {useEffect, useMemo, useRef, useState} from 'react'
import isEqual from 'react-fast-compare'

import Demo from '~/components/Demo'
Expand Down Expand Up @@ -29,6 +29,7 @@ export default function Generator({palettes, about, stars}: GeneratorProps) {
const [showDemo, setShowDemo] = useState(false)
const [currentMode, setCurrentMode] = useState<Mode>(MODES[0])
const previousPalettes: undefined | PaletteConfig[] = usePrevious(palettesState)
const paletteRefs = useRef<HTMLDivElement[]>([])

// Maybe update document meta on each state change
// Initially it seemed like a good idea to handle this globally as a side-effect
Expand All @@ -47,21 +48,13 @@ export default function Generator({palettes, about, stars}: GeneratorProps) {
}, [palettesState, previousPalettes])

const handleNew = () => {
const currentValues = palettesState.map((p) => p.value)
const randomPalette = createRandomPalette(currentValues)
const newPalettes = [...palettesState, randomPalette]
setPalettesState(newPalettes)

// Scroll new ID into view
if (typeof document !== 'undefined') {
setTimeout(() => {
const newElement = document.getElementById(`s-${randomPalette.value.toUpperCase()}`)

if (newElement) {
newElement.scrollIntoView({behavior: 'smooth'})
}
}, 50)
}
setPalettesState((prev) => {
const currentValues = prev.map((p) => p.value)
const randomPalette = createRandomPalette(currentValues)
return [...prev, randomPalette]
})

paletteRefs.current.at(-1)?.scrollIntoView({behavior: 'smooth'})
}

const handleDemo = () => setShowDemo(!showDemo)
Expand Down Expand Up @@ -139,6 +132,7 @@ export default function Generator({palettes, about, stars}: GeneratorProps) {
{palettesState.map((palette: PaletteConfig, index: number) => (
<React.Fragment key={palette.id}>
<Palette
paletteRef={(el) => (paletteRefs.current[index] = el)}
palette={palette}
updateGlobal={(updatedPalette: PaletteConfig) => handleUpdate(updatedPalette, index)}
deleteGlobal={
Expand Down
22 changes: 12 additions & 10 deletions app/components/Palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
LinkIcon,
TrashIcon,
} from '@heroicons/react/24/solid'
import React, {useEffect, useState} from 'react'
import React, {useCallback, useEffect, useState} from 'react'
import {useCopyToClipboard} from 'usehooks-ts'

import Graphs from '~/components/Graphs'
Expand Down Expand Up @@ -42,7 +42,7 @@ const tweakInputs = [
title: (useLightness: boolean) => (useLightness ? `Lightness Minimum` : `Luminance Minimum`),
value: DEFAULT_PALETTE_CONFIG.lMin,
},
]
] as const

const paletteInputs = [
{
Expand Down Expand Up @@ -73,10 +73,11 @@ type PaletteProps = {
updateGlobal: (updatedPalette: PaletteConfig) => void
deleteGlobal?: () => void
currentMode: Mode
paletteRef: (el: HTMLDivElement) => void
}

export default function Palette(props: PaletteProps) {
const {palette, updateGlobal, deleteGlobal, currentMode} = props
const {palette, updateGlobal, deleteGlobal, currentMode, paletteRef} = props

const [paletteState, setPaletteState] = useState({
...DEFAULT_PALETTE_CONFIG,
Expand Down Expand Up @@ -196,11 +197,10 @@ export default function Palette(props: PaletteProps) {
})
}

const handleCopyURL = () => {
const handleCopyURL = useCallback(() => {
const shareUrl = createCanonicalUrl([paletteState])

copy(shareUrl)
}
}, [paletteState, copy])

const handleOpenAPI = () => {
if (typeof document !== 'undefined') {
Expand All @@ -223,7 +223,11 @@ export default function Palette(props: PaletteProps) {
} as React.CSSProperties

return (
<article id={`s-${palette.value}`} className="grid grid-cols-1 gap-4 text-gray-500">
<article
ref={paletteRef}
id={`s-${palette.value}`}
className="grid grid-cols-1 gap-4 text-gray-500"
>
<div className="grid grid-cols-4 sm:grid-cols-5 gap-2">
{paletteInputs.map((input) => (
<div
Expand Down Expand Up @@ -360,16 +364,14 @@ export default function Palette(props: PaletteProps) {
</span>
</div>
</div>

<div className="grid gap-1 grid-cols-11 sm:grid-cols-4 lg:grid-cols-11 sm:gap-2 text-2xs sm:text-xs">
{paletteState.swatches
.filter((swatch) => ![0, 1000].includes(swatch.stop))
.map((swatch) => (
<Swatch key={swatch.stop} swatch={swatch} mode={currentMode} />
))}
</div>

{showGraphs && <Graphs palettes={[paletteState]} />}
{showGraphs && <Graphs palettes={[paletteState]} mode={currentMode} />}
</article>
)
}
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,30 @@
"@tailwindcss/typography": "^0.5.13",
"@vercel/analytics": "^1.3.1",
"@vercel/remix": "2.9.2-patch.2",
"babel-plugin-react-compiler": "19.0.0-beta-714736e-20250131",
"chroma-js": "^2.4.2",
"clsx": "^2.1.1",
"cypress": "^13.11.0",
"husky": "^9.0.11",
"isbot": "^5.1.9",
"nanoid": "^5.0.7",
"react": "^18.3.1",
"react": "^19.0.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.3.1",
"react-dom": "^19.0.0",
"react-fast-compare": "^3.2.2",
"react-popper-tooltip": "^4.4.2",
"satori": "^0.10.13",
"usehooks-ts": "^3.1.0",
"vite-plugin-babel": "^1.3.0",
"vite-tsconfig-paths": "^4.3.2",
"web-vitals": "^4.1.1"
},
"devDependencies": {
"@babel/eslint-parser": "^7.24.7",
"@remix-run/dev": "^2.9.2",
"@types/chroma-js": "^2.4.4",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@typescript-eslint/eslint-plugin": "^7.13.0",
"@typescript-eslint/parser": "^7.13.0",
"autoprefixer": "^10.4.19",
Expand All @@ -79,5 +81,6 @@
"engines": {
"node": ">=18"
},
"sideEffects": false
"sideEffects": false,
"packageManager": "[email protected]+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab"
}
Loading