feat: add dev tool client component with hono jsx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Corey Johnson 2026-01-06 11:39:43 -08:00
parent 23472bae66
commit 9456957e63

114
src/dev/app.tsx Normal file
View File

@ -0,0 +1,114 @@
import { useState } from "hono/jsx"
import { render } from "hono/jsx/dom"
const App = () => {
const [spriteUrl, setSpriteUrl] = useState("")
const [width, setWidth] = useState(32)
const [height, setHeight] = useState(32)
const [frames, setFrames] = useState(4)
const [columns, setColumns] = useState<number | undefined>()
const [frameDuration, setFrameDuration] = useState(100)
const handleFileChange = (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0]
if (!file) return
setSpriteUrl(URL.createObjectURL(file))
}
const isGrid = columns !== undefined
const cols = isGrid ? columns : frames
const rows = isGrid ? Math.ceil(frames / columns!) : 1
const sheetWidth = cols * width
const sheetHeight = rows * height
const totalDuration = frames * frameDuration
const keyframeId = "sprite-preview"
let keyframes: string
if (isGrid) {
const steps: string[] = []
for (let i = 0; i < frames; i++) {
const col = i % columns!
const row = Math.floor(i / columns!)
const x = -col * width
const y = -row * height
const percent = (i / frames) * 100
steps.push(`${percent}%{background-position:${x}px ${y}px}`)
}
keyframes = `@keyframes ${keyframeId}{${steps.join("")}}`
} else {
keyframes = `@keyframes ${keyframeId}{from{background-position:0 0}to{background-position:-${sheetWidth}px 0}}`
}
const previewStyle = {
width: `${width}px`,
height: `${height}px`,
backgroundImage: `url('${spriteUrl}')`,
backgroundSize: `${sheetWidth}px ${sheetHeight}px`,
animation: `${keyframeId} ${totalDuration}ms steps(${frames}) infinite`,
}
const columnsAttr = columns ? `\n columns={${columns}}` : ""
const code = `<Sprite
src="YOUR_SPRITE_URL"
width={${width}}
height={${height}}
frames={${frames}}
frameDuration={${frameDuration}}${columnsAttr}
/>`
const copyCode = () => navigator.clipboard.writeText(code)
return (
<div class="grid">
<div>
<label>
Spritesheet
<input type="file" accept="image/*" onChange={handleFileChange} />
</label>
<label>
Frame Width (px)
<input type="number" value={width} min={1} onInput={(e) => setWidth(+(e.target as HTMLInputElement).value)} />
</label>
<label>
Frame Height (px)
<input type="number" value={height} min={1} onInput={(e) => setHeight(+(e.target as HTMLInputElement).value)} />
</label>
<label>
Frame Count
<input type="number" value={frames} min={1} onInput={(e) => setFrames(+(e.target as HTMLInputElement).value)} />
</label>
<label>
Columns (empty = horizontal strip)
<input type="number" value={columns} min={1} onInput={(e) => {
const val = (e.target as HTMLInputElement).value
setColumns(val ? +val : undefined)
}} />
</label>
<label>
Frame Duration (ms)
<input type="number" value={frameDuration} min={1} onInput={(e) => setFrameDuration(+(e.target as HTMLInputElement).value)} />
</label>
</div>
<div>
<div class="preview-area">
{spriteUrl ? (
<>
<style>{keyframes}</style>
<div style={previewStyle} />
</>
) : (
<span>Upload a spritesheet to preview</span>
)}
</div>
<label>
Generated Code
<textarea readonly rows={8} value={code} />
</label>
<button onClick={copyCode}>Copy Code</button>
</div>
</div>
)
}
render(<App />, document.getElementById("app")!)