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 (
<>
<div className="p-4 space-y-6">
<div className="p-4 space-y-5">
<div className="space-y-4">
{!device.physical && (
<div className="flex items-center justify-between">
@ -191,27 +191,27 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
)}
<div>
<label className="text-sm text-neutral-500 block mb-2">Type</label>
<div className="px-3 py-2 bg-neutral-50 border border-neutral-200 rounded-lg text-neutral-700">
<label className="text-xs font-medium text-neutral-400 uppercase tracking-wide block mb-1.5">Type</label>
<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)}
</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
type="text"
value={deviceData.name || device.name || `Device ${device.number}`}
onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'name'], v)}
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"
/>
</div>
{!device.physical && <>
<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">
<SettingsInput
type="text"
@ -219,19 +219,19 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'base_url'], v)}
onClear={() => updateDeviceSetting([...getDevicePath(), 'base_url'], '')}
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
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>
</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">
<SettingsInput
type="text"
@ -259,13 +259,13 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
setMediaSetFiles(null);
}}
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
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>
</div>
{mediaSetFiles && (
@ -279,7 +279,7 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
(deviceData.base_url ?? '').includes('://') ||
(deviceData.url ?? '').includes('://')) && (
<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">
<SettingsInput
type="text"
@ -287,13 +287,13 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
onCommit={(v) => updateDeviceSetting([...getDevicePath(), 'cache'], v)}
onClear={() => updateDeviceSetting([...getDevicePath(), 'cache'], '')}
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
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>
</div>
</div>
@ -305,12 +305,12 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
<label className="text-sm text-neutral-500">Mode</label>
{device.physical
? <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">
{([0, 1] as const).map((val, i) => (
: <div className="flex rounded-xl overflow-hidden text-sm bg-neutral-100">
{([0, 1] as const).map((val) => (
<button
key={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'}
</button>
@ -322,20 +322,20 @@ function DeviceCard({ device, config, setConfig, isActive, onBrowsingChange }: D
{deviceData.baud !== undefined && (
<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
type="number"
value={String(deviceData.baud ?? '')}
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 className="pt-4 border-t border-neutral-200">
<h3 className="text-sm text-neutral-500 mb-2">Device ID</h3>
<code className="text-xs text-neutral-600 bg-neutral-50 px-2 py-1 rounded">{device.id}</code>
<div className="pt-4 border-t border-neutral-100">
<h3 className="text-xs font-medium text-neutral-400 uppercase tracking-wide mb-1.5">Device ID</h3>
<code className="text-xs text-neutral-500 bg-neutral-100 px-2.5 py-1.5 rounded-lg">{device.id}</code>
</div>
</div>
@ -395,60 +395,65 @@ export default function DeviceDetailOverlay({
return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-50"
onClick={onClose}
>
<div className="fixed inset-0 z-50">
<motion.div
initial={{ y: '100%' }}
animate={{ y: 0 }}
exit={{ y: '100%' }}
transition={{ type: 'spring', damping: 30, stiffness: 300 }}
className="fixed inset-0 bg-white flex flex-col z-50"
onClick={(e) => e.stopPropagation()}
transition={{ type: 'spring', damping: 28, stiffness: 280 }}
className="fixed inset-0 bg-white/80 backdrop-blur-md flex flex-col overflow-hidden"
>
{/* ── Header ── */}
<div className="flex-shrink-0 border-b border-neutral-200 relative">
<div className="flex items-center justify-between p-4">
<button onClick={onClose} className="p-2 -m-2">
<X className="w-6 h-6" />
</button>
<div className={`flex flex-col items-center gap-0.5 ${
<div className="flex-shrink-0 border-b border-neutral-200/70 relative">
<div className="flex items-center gap-3 px-4 py-3">
<div className={`flex flex-col items-center gap-0.5 flex-shrink-0 ${
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} />
{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
</span>
)}
</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 && (
<span className="text-xs text-neutral-400 tabular-nums">
<span className="text-xs text-neutral-400 tabular-nums px-1">
{activeIndex + 1} / {devices.length}
</span>
)}
<button onClick={() => setShowCommandMenu(v => !v)} className="p-2 -m-2">
<MoreVertical className="w-6 h-6" />
<button
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>
</div>
</div>
{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
onClick={() => {
console.log(`Reset device ${activeDevice.number}`);
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" />
Reset Device
@ -483,7 +488,7 @@ export default function DeviceDetailOverlay({
</Swiper>
</div>
</motion.div>
</motion.div>
</div>
</AnimatePresence>
);
}