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 (
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user