From f3b97276c50bdce37178ea7e67ff4c6ab683685b Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Fri, 12 Jun 2026 00:12:38 -0400 Subject: [PATCH] feat(DeviceDetailOverlay, IECPage): replace input fields with SettingsInput component for improved handling --- src/app/components/DeviceDetailOverlay.tsx | 32 +++++++++++----------- src/app/components/IECPage.tsx | 15 +++++----- src/app/components/ui/settings-input.tsx | 27 ++++++++++++++++++ 3 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 src/app/components/ui/settings-input.tsx diff --git a/src/app/components/DeviceDetailOverlay.tsx b/src/app/components/DeviceDetailOverlay.tsx index 31263ef..6f269c3 100644 --- a/src/app/components/DeviceDetailOverlay.tsx +++ b/src/app/components/DeviceDetailOverlay.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from 'react'; +import { SettingsInput } from './ui/settings-input'; import { X, ChevronLeft, ChevronRight, Printer, HardDrive, Network, Box, FolderOpen, MoreVertical, Play, Pause, SkipForward, SkipBack, RotateCcw } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { toast } from 'sonner'; @@ -341,12 +342,12 @@ export default function DeviceDetailOverlay({
- { + onCommit={(v) => { const path = getDevicePath(); - updateDeviceSetting([...path, 'name'], e.target.value); + updateDeviceSetting([...path, 'name'], v); }} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" /> @@ -356,12 +357,12 @@ export default function DeviceDetailOverlay({
- { + onCommit={(v) => { const path = getDevicePath(); - updateDeviceSetting([...path, 'base_url'], e.target.value); + updateDeviceSetting([...path, 'base_url'], v); }} className="flex-1 px-3 py-2 border border-neutral-300 rounded-lg" /> @@ -388,11 +389,10 @@ export default function DeviceDetailOverlay({
- { - const newUrl = e.target.value; + onCommit={(newUrl) => { const devicePath = getDevicePath(); const newConfig = JSON.parse(JSON.stringify(config)); let dev = newConfig; @@ -440,12 +440,12 @@ export default function DeviceDetailOverlay({
- { + onCommit={(v) => { const path = getDevicePath(); - updateDeviceSetting([...path, 'cache'], e.target.value); + updateDeviceSetting([...path, 'cache'], v); }} className="flex-1 px-3 py-2 border border-neutral-300 rounded-lg" /> @@ -500,12 +500,12 @@ export default function DeviceDetailOverlay({ {deviceData.baud !== undefined && (
- { + value={String(deviceData.baud ?? '')} + onCommit={(v) => { const path = getDevicePath(); - updateDeviceSetting([...path, 'baud'], parseInt(e.target.value)); + updateDeviceSetting([...path, 'baud'], parseInt(v)); }} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" /> diff --git a/src/app/components/IECPage.tsx b/src/app/components/IECPage.tsx index d070a5d..5c45e6c 100644 --- a/src/app/components/IECPage.tsx +++ b/src/app/components/IECPage.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { Cable, Code2, Cpu, FolderOpen, Link, List, Zap } from 'lucide-react'; import MediaBrowser from './MediaBrowser'; +import { SettingsInput } from './ui/settings-input'; interface IECPageProps { config: any; @@ -92,10 +93,10 @@ export default function IECPage({ config, setConfig }: IECPageProps) {
- updateSetting(['settings', 'autoboot'], e.target.value)} + onCommit={(v) => updateSetting(['settings', 'autoboot'], v)} className="flex-1 px-3 py-2 border border-neutral-300 rounded-lg" /> ) : ( - updateSetting(['settings', 'directory', key], parseInt(e.target.value))} + onCommit={(v) => updateSetting(['settings', 'directory', key], parseInt(v))} className="w-24 px-3 py-1 border border-neutral-300 rounded-lg text-right disabled:cursor-not-allowed" /> )} diff --git a/src/app/components/ui/settings-input.tsx b/src/app/components/ui/settings-input.tsx new file mode 100644 index 0000000..b9e65e2 --- /dev/null +++ b/src/app/components/ui/settings-input.tsx @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react'; + +interface SettingsInputProps extends Omit, 'onChange' | 'onBlur' | 'value'> { + value: string | number; + onCommit: (value: string) => void; +} + +/** + * A controlled input that buffers keystrokes locally and only calls + * `onCommit` when the field loses focus, preventing a settings save + * on every keystroke. + */ +export function SettingsInput({ value, onCommit, ...props }: SettingsInputProps) { + const [local, setLocal] = useState(String(value ?? '')); + + // Sync if the committed value changes externally (e.g. device switch). + useEffect(() => { setLocal(String(value ?? '')); }, [value]); + + return ( + setLocal(e.target.value)} + onBlur={() => onCommit(local)} + /> + ); +}