style(DeviceCard): enhance layout and styling for device details

This commit is contained in:
Jaime Idolpx 2026-06-14 03:36:03 -04:00
parent be9391caec
commit f05067c029

View File

@ -176,7 +176,7 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
return ( return (
<> <>
<div className="p-4 space-y-6"> <div className="p-4 space-y-5">
<div className="space-y-4"> <div className="space-y-4">
{!device.physical && ( {!device.physical && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -191,27 +191,27 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
)} )}
<div> <div>
<label className="text-sm text-neutral-500 block mb-2">Type</label> <label className="text-xs font-medium text-neutral-400 uppercase tracking-wide block mb-1.5">Type</label>
<div className="px-3 py-2 bg-neutral-50 border border-neutral-200 rounded-lg text-neutral-700"> <div className="px-3 py-2.5 bg-neutral-100 rounded-xl text-sm text-neutral-700">
{device.type.charAt(0).toUpperCase() + device.type.slice(1)} {device.type.charAt(0).toUpperCase() + device.type.slice(1)}
</div> </div>
</div> </div>
<div> <div>
<label className="text-sm text-neutral-500 block mb-2">Device Name</label> <label className="text-xs font-medium text-neutral-400 uppercase tracking-wide block mb-1.5">Device Name</label>
<SettingsInput <SettingsInput
type="text" type="text"
value={deviceData.name || device.name || `Device ${device.number}`} value={deviceData.name || device.name || `Device ${device.number}`}
onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'name'], v)} onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'name'], v)}
onClear={() => updateDeviceSetting([...getDevicePath(), 'name'], '')} onClear={() => updateDeviceSetting([...getDevicePath(), 'name'], '')}
className="px-3 py-2 border border-neutral-300 rounded-lg" className="px-3 py-2.5 bg-neutral-100 border-0 rounded-xl text-sm"
containerClassName="w-full" containerClassName="w-full"
/> />
</div> </div>
{!device.physical && <> {!device.physical && <>
<div> <div>
<label className="text-sm text-neutral-500 block mb-2">Base URL</label> <label className="text-xs font-medium text-neutral-400 uppercase tracking-wide block mb-1.5">Base URL</label>
<div className="flex gap-2"> <div className="flex gap-2">
<SettingsInput <SettingsInput
type="text" type="text"
@ -219,19 +219,19 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'base_url'], v)} onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'base_url'], v)}
onClear={() => updateDeviceSetting([...getDevicePath(), 'base_url'], '')} onClear={() => updateDeviceSetting([...getDevicePath(), 'base_url'], '')}
containerClassName="flex-1" containerClassName="flex-1"
className="px-3 py-2 border border-neutral-300 rounded-lg" className="px-3 py-2.5 bg-neutral-100 border-0 rounded-xl text-sm"
/> />
<button <button
onClick={() => setBrowsingField('base_url')} onClick={() => setBrowsingField('base_url')}
className="px-3 py-2 border border-neutral-300 rounded-lg bg-neutral-50 hover:bg-neutral-100" className="px-3 py-2.5 bg-neutral-100 hover:bg-neutral-200 rounded-xl transition-colors"
> >
<FolderOpen className="w-5 h-5" /> <FolderOpen className="w-5 h-5 text-neutral-500" />
</button> </button>
</div> </div>
</div> </div>
<div> <div>
<label className="text-sm text-neutral-500 block mb-2">URL</label> <label className="text-xs font-medium text-neutral-400 uppercase tracking-wide block mb-1.5">URL</label>
<div className="flex gap-2"> <div className="flex gap-2">
<SettingsInput <SettingsInput
type="text" type="text"
@ -259,13 +259,13 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
setMediaSetFiles(null); setMediaSetFiles(null);
}} }}
containerClassName="flex-1" containerClassName="flex-1"
className="px-3 py-2 border border-neutral-300 rounded-lg" className="px-3 py-2.5 bg-neutral-100 border-0 rounded-xl text-sm"
/> />
<button <button
onClick={() => setBrowsingField('url')} onClick={() => setBrowsingField('url')}
className="px-3 py-2 border border-neutral-300 rounded-lg bg-neutral-50 hover:bg-neutral-100" className="px-3 py-2.5 bg-neutral-100 hover:bg-neutral-200 rounded-xl transition-colors"
> >
<FolderOpen className="w-5 h-5" /> <FolderOpen className="w-5 h-5 text-neutral-500" />
</button> </button>
</div> </div>
{mediaSetFiles && ( {mediaSetFiles && (
@ -279,7 +279,7 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
(deviceData.base_url ?? '').includes('://') || (deviceData.base_url ?? '').includes('://') ||
(deviceData.url ?? '').includes('://')) && ( (deviceData.url ?? '').includes('://')) && (
<div> <div>
<label className="text-sm text-neutral-500 block mb-2">Cache</label> <label className="text-xs font-medium text-neutral-400 uppercase tracking-wide block mb-1.5">Cache</label>
<div className="flex gap-2"> <div className="flex gap-2">
<SettingsInput <SettingsInput
type="text" type="text"
@ -287,13 +287,13 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'cache'], v)} onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'cache'], v)}
onClear={() => updateDeviceSetting([...getDevicePath(), 'cache'], '')} onClear={() => updateDeviceSetting([...getDevicePath(), 'cache'], '')}
containerClassName="flex-1" containerClassName="flex-1"
className="px-3 py-2 border border-neutral-300 rounded-lg" className="px-3 py-2.5 bg-neutral-100 border-0 rounded-xl text-sm"
/> />
<button <button
onClick={() => setBrowsingField('cache')} onClick={() => setBrowsingField('cache')}
className="px-3 py-2 border border-neutral-300 rounded-lg bg-neutral-50 hover:bg-neutral-100" className="px-3 py-2.5 bg-neutral-100 hover:bg-neutral-200 rounded-xl transition-colors"
> >
<FolderOpen className="w-5 h-5" /> <FolderOpen className="w-5 h-5 text-neutral-500" />
</button> </button>
</div> </div>
</div> </div>
@ -305,12 +305,12 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
<label className="text-sm text-neutral-500">Mode</label> <label className="text-sm text-neutral-500">Mode</label>
{device.physical {device.physical
? <span className="text-sm text-neutral-700 px-3 py-2">{(deviceData.mode ?? 0) === 0 ? 'Read Only' : 'Write Enabled'}</span> ? <span className="text-sm text-neutral-700 px-3 py-2">{(deviceData.mode ?? 0) === 0 ? 'Read Only' : 'Write Enabled'}</span>
: <div className="flex rounded-lg border border-neutral-300 overflow-hidden text-sm"> : <div className="flex rounded-xl overflow-hidden text-sm bg-neutral-100">
{([0, 1] as const).map((val, i) => ( {([0, 1] as const).map((val) => (
<button <button
key={val} key={val}
onClick={() => updateDeviceSetting([...getDevicePath(), 'mode'], val)} onClick={() => updateDeviceSetting([...getDevicePath(), 'mode'], val)}
className={`px-4 py-2 ${i > 0 ? 'border-l border-neutral-300' : ''} ${(deviceData.mode ?? 0) === val ? 'bg-blue-600 text-white' : 'bg-white text-neutral-700 hover:bg-neutral-50'}`} className={`px-4 py-2 transition-colors ${(deviceData.mode ?? 0) === val ? 'bg-blue-600 text-white' : 'text-neutral-600 hover:bg-neutral-200'}`}
> >
{val === 0 ? 'Read Only' : 'Write Enabled'} {val === 0 ? 'Read Only' : 'Write Enabled'}
</button> </button>
@ -322,20 +322,20 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
{deviceData.baud !== undefined && ( {deviceData.baud !== undefined && (
<div> <div>
<label className="text-sm text-neutral-500 block mb-2">Baud Rate</label> <label className="text-xs font-medium text-neutral-400 uppercase tracking-wide block mb-1.5">Baud Rate</label>
<SettingsInput <SettingsInput
type="number" type="number"
value={String(deviceData.baud ?? '')} value={String(deviceData.baud ?? '')}
onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'baud'], parseInt(v))} onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'baud'], parseInt(v))}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg" className="w-full px-3 py-2.5 bg-neutral-100 border-0 rounded-xl text-sm"
/> />
</div> </div>
)} )}
</div> </div>
<div className="pt-4 border-t border-neutral-200"> <div className="pt-4 border-t border-neutral-100">
<h3 className="text-sm text-neutral-500 mb-2">Device ID</h3> <h3 className="text-xs font-medium text-neutral-400 uppercase tracking-wide mb-1.5">Device ID</h3>
<code className="text-xs text-neutral-600 bg-neutral-50 px-2 py-1 rounded">{device.id}</code> <code className="text-xs text-neutral-500 bg-neutral-100 px-2.5 py-1.5 rounded-lg">{device.id}</code>
</div> </div>
</div> </div>
@ -395,60 +395,65 @@ export default function DeviceDetailOverlay({
return ( return (
<AnimatePresence> <AnimatePresence>
<motion.div <div className="fixed inset-0 z-50">
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-50"
onClick={onClose}
>
<motion.div <motion.div
initial={{ y: '100%' }} initial={{ y: '100%' }}
animate={{ y: 0 }} animate={{ y: 0 }}
exit={{ y: '100%' }} exit={{ y: '100%' }}
transition={{ type: 'spring', damping: 30, stiffness: 300 }} transition={{ type: 'spring', damping: 28, stiffness: 280 }}
className="fixed inset-0 bg-white flex flex-col z-50" className="fixed inset-0 bg-white/80 backdrop-blur-md flex flex-col overflow-hidden"
onClick={(e) => e.stopPropagation()}
> >
{/* ── Header ── */} {/* ── Header ── */}
<div className="flex-shrink-0 border-b border-neutral-200 relative"> <div className="flex-shrink-0 border-b border-neutral-200/70 relative">
<div className="flex items-center justify-between p-4"> <div className="flex items-center gap-3 px-4 py-3">
<button onClick={onClose} className="p-2 -m-2"> <div className={`flex flex-col items-center gap-0.5 flex-shrink-0 ${
<X className="w-6 h-6" />
</button>
<div className={`flex flex-col items-center gap-0.5 ${
activeDevice.physical ? 'text-green-600' : activeDevice.enabled ? 'text-blue-600' : 'text-neutral-400' activeDevice.physical ? 'text-green-600' : activeDevice.enabled ? 'text-blue-600' : 'text-neutral-400'
}`}> }`}>
<span className="text-sm font-semibold leading-none">{activeDevice.number}</span> <span className="text-xs font-semibold leading-none tabular-nums">{activeDevice.number}</span>
<DeviceIcon device={activeDevice} /> <DeviceIcon device={activeDevice} />
{activeDevice.physical && ( {activeDevice.physical && (
<span className="text-xs text-green-700 px-1 py-0.5 bg-green-50 border border-green-200 rounded leading-none"> <span className="text-[10px] text-green-700 px-1 py-0.5 bg-green-50 border border-green-200 rounded leading-none">
Physical Physical
</span> </span>
)} )}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex-1 min-w-0">
<p className="text-base font-semibold text-neutral-800 truncate leading-tight">
{activeDevice.name ?? `Device ${activeDevice.number}`}
</p>
<p className="text-xs text-neutral-400 capitalize">{activeDevice.type}</p>
</div>
<div className="flex items-center gap-1 flex-shrink-0">
{devices.length > 1 && ( {devices.length > 1 && (
<span className="text-xs text-neutral-400 tabular-nums"> <span className="text-xs text-neutral-400 tabular-nums px-1">
{activeIndex + 1} / {devices.length} {activeIndex + 1} / {devices.length}
</span> </span>
)} )}
<button onClick={() => setShowCommandMenu(v => !v)} className="p-2 -m-2"> <button
<MoreVertical className="w-6 h-6" /> onClick={() => setShowCommandMenu(v => !v)}
className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-500 transition-colors"
>
<MoreVertical className="w-5 h-5" />
</button>
<button
onClick={onClose}
className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-500 transition-colors"
>
<X className="w-5 h-5" />
</button> </button>
</div> </div>
</div> </div>
{showCommandMenu && ( {showCommandMenu && (
<div className="absolute right-4 top-14 bg-white rounded-lg shadow-lg border border-neutral-200 py-2 min-w-[200px] z-20"> <div className="absolute right-4 top-full mt-1 bg-white/90 backdrop-blur-sm rounded-xl shadow-lg border border-neutral-200/70 py-1.5 min-w-[180px] z-20">
<button <button
onClick={() => { onClick={() => {
console.log(`Reset device ${activeDevice.number}`); console.log(`Reset device ${activeDevice.number}`);
setShowCommandMenu(false); setShowCommandMenu(false);
}} }}
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2" className="w-full px-4 py-2.5 text-left text-sm hover:bg-neutral-100 flex items-center gap-2 text-neutral-700 transition-colors"
> >
<RotateCcw className="w-4 h-4" /> <RotateCcw className="w-4 h-4" />
Reset Device Reset Device
@ -483,7 +488,7 @@ export default function DeviceDetailOverlay({
</Swiper> </Swiper>
</div> </div>
</motion.div> </motion.div>
</motion.div> </div>
</AnimatePresence> </AnimatePresence>
); );
} }