style(DeviceCard): enhance layout and styling for device details
This commit is contained in:
parent
be9391caec
commit
f05067c029
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user