fix(ui): tighten device detail page for control surface feel

- Header: reduce padding, align items-center, dot+label status,
  truncating hostname, compact metadata, version prefixed with v
- Actions: ghost icon buttons for Edit/Delete, tighter gap, smaller icons
- InfoRow: py-2→py-1, text-sm→text-xs, label w-32→w-24, border-subtle
- Data panels: rounded-lg→rounded-sm, p-4→p-3, border-default
- StandardConfigSidebar: tighter rows py-1.5→py-[3px], w-48→w-44,
  accent-soft active bg, text-label section headers, 50ms transitions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-21 12:45:31 -05:00
parent 2e301c9ffa
commit f7b95adfd2
2 changed files with 43 additions and 36 deletions

View File

@@ -133,17 +133,17 @@ export function StandardConfigSidebar({
onSwitchToSimple, onSwitchToSimple,
}: StandardConfigSidebarProps) { }: StandardConfigSidebarProps) {
return ( return (
<div className="w-48 flex-shrink-0 flex flex-col min-h-[400px]"> <div className="w-44 flex-shrink-0 flex flex-col min-h-[400px]">
<nav className="space-y-3 overflow-y-auto flex-1"> <nav className="space-y-2 overflow-y-auto flex-1">
{STANDARD_GROUPS.map((group) => { {STANDARD_GROUPS.map((group) => {
const GroupIcon = group.icon const GroupIcon = group.icon
return ( return (
<div key={group.label}> <div key={group.label}>
<p className="flex items-center gap-1.5 text-xs font-medium text-text-muted uppercase tracking-wider mb-1 px-3"> <p className="flex items-center gap-1.5 text-[7px] text-text-label uppercase tracking-[2px] mb-1 pl-2">
<GroupIcon className="h-3 w-3" /> <GroupIcon className="h-3 w-3" />
{group.label} {group.label}
</p> </p>
<div className="space-y-0.5"> <div>
{group.items.map((item) => { {group.items.map((item) => {
const isActive = activeTab === item.id const isActive = activeTab === item.id
return ( return (
@@ -152,10 +152,10 @@ export function StandardConfigSidebar({
onClick={() => onTabChange(item.id)} onClick={() => onTabChange(item.id)}
data-testid={`tab-${item.id}`} data-testid={`tab-${item.id}`}
className={cn( className={cn(
'flex items-center w-full text-left pl-7 pr-3 py-1.5 rounded-r-lg text-sm transition-colors', 'flex items-center w-full text-left pl-6 pr-2 py-[3px] text-xs border-l-2 transition-[border-color,color] duration-[50ms]',
isActive isActive
? 'bg-accent/10 text-accent border-l-2 border-accent' ? 'bg-accent-soft text-text-primary font-medium border-accent rounded-r-sm'
: 'text-text-secondary hover:text-text-primary hover:bg-elevated/50 border-l-2 border-transparent', : 'text-text-secondary hover:border-accent border-transparent',
)} )}
> >
<span className="truncate">{item.label}</span> <span className="truncate">{item.label}</span>
@@ -169,13 +169,13 @@ export function StandardConfigSidebar({
</nav> </nav>
{onSwitchToSimple && ( {onSwitchToSimple && (
<div className="mt-auto pt-4 border-t border-border/50"> <div className="mt-auto pt-2 border-t border-border-subtle">
<button <button
onClick={onSwitchToSimple} onClick={onSwitchToSimple}
className="flex items-center gap-2 w-full text-left px-3 py-2 text-xs text-text-muted hover:text-text-secondary transition-colors" className="flex items-center gap-1.5 w-full text-left px-2 py-1.5 text-[10px] text-text-muted hover:text-text-secondary transition-[color] duration-[50ms]"
> >
<Sliders className="h-3.5 w-3.5" /> <Sliders className="h-3 w-3" />
Switch to Simple mode Simple mode
</button> </button>
</div> </div>
)} )}

View File

@@ -306,9 +306,9 @@ function TlsModeSelector({
function InfoRow({ label, value }: { label: string; value: React.ReactNode }) { function InfoRow({ label, value }: { label: string; value: React.ReactNode }) {
return ( return (
<div className="flex items-start gap-4 py-2 border-b border-border/50 last:border-0"> <div className="flex items-center gap-3 py-1 border-b border-border-subtle last:border-0">
<span className="text-xs text-text-muted w-32 flex-shrink-0 pt-0.5">{label}</span> <span className="text-[10px] text-text-muted w-24 flex-shrink-0">{label}</span>
<span className="text-sm text-text-primary flex-1">{value ?? '—'}</span> <span className="text-xs text-text-primary flex-1">{value ?? '—'}</span>
</div> </div>
) )
} }
@@ -449,11 +449,11 @@ function DeviceDetailPage() {
return ( return (
<div className={cn('space-y-4', mode === 'simple' ? 'max-w-5xl' : 'max-w-3xl')} data-testid="device-detail"> <div className={cn('space-y-4', mode === 'simple' ? 'max-w-5xl' : 'max-w-3xl')} data-testid="device-detail">
{/* Device workspace header */} {/* Device workspace header */}
<div className="bg-sidebar border border-border-default rounded-sm p-2 px-3"> <div className="bg-sidebar border border-border-default rounded-sm px-3 py-1.5">
<div className="flex justify-between items-start"> <div className="flex justify-between items-center">
<div> <div className="min-w-0">
{/* Breadcrumb */} {/* Breadcrumb */}
<div className="mb-0.5"> <div className="mb-px">
<Link <Link
to="/tenants/$tenantId/devices" to="/tenants/$tenantId/devices"
params={{ tenantId }} params={{ tenantId }}
@@ -464,33 +464,40 @@ function DeviceDetailPage() {
<span className="text-[8px] text-text-muted mx-1">&rsaquo;</span> <span className="text-[8px] text-text-muted mx-1">&rsaquo;</span>
<span className="text-[8px] text-text-secondary">{device.hostname}</span> <span className="text-[8px] text-text-secondary">{device.hostname}</span>
</div> </div>
{/* Device name + status */} {/* Device name + status dot + label */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-1.5">
<div className={cn( <div className={cn(
'w-1.5 h-1.5 rounded-full', 'w-1.5 h-1.5 rounded-full flex-shrink-0',
device.status === 'online' ? 'bg-online' : device.status === 'online' ? 'bg-online' :
device.status === 'degraded' ? 'bg-warning' : 'bg-offline' device.status === 'degraded' ? 'bg-warning' : 'bg-offline'
)} /> )} />
<h1 className="text-sm font-semibold text-text-primary" data-testid="device-hostname"> <h1 className="text-[13px] font-semibold text-text-primary truncate" data-testid="device-hostname">
{device.hostname} {device.hostname}
</h1> </h1>
<span className={cn(
'text-[9px] flex-shrink-0',
device.status === 'online' ? 'text-online' :
device.status === 'degraded' ? 'text-warning' : 'text-offline'
)}>
{device.status}
</span>
<TlsSecurityBadge tlsMode={device.tls_mode} /> <TlsSecurityBadge tlsMode={device.tls_mode} />
</div> </div>
{/* Metadata line */} {/* Metadata */}
<div className="text-[9px] text-text-secondary mt-0.5 pl-3.5"> <div className="text-[9px] text-text-secondary mt-px pl-[9px]">
{device.model ?? device.board_name ?? '\u2014'} {device.model ?? device.board_name ?? '\u2014'}
{' \u00b7 '} {' \u00b7 '}
<span className="font-mono text-[8px]">{device.ip_address}</span> <span className="font-mono text-[8px]">{device.ip_address}</span>
{device.routeros_version && ( {device.routeros_version && (
<> <>
{' \u00b7 RouterOS '} {' \u00b7 '}
<span className="font-mono text-[8px]">{device.routeros_version}</span> <span className="font-mono text-[8px]">v{device.routeros_version}</span>
</> </>
)} )}
</div> </div>
</div> </div>
{/* Actions: right side */} {/* Actions */}
<div className="flex items-center gap-1.5 mt-3"> <div className="flex items-center gap-1 flex-shrink-0 ml-3">
<SimpleModeToggle mode={mode} onModeChange={toggleMode} /> <SimpleModeToggle mode={mode} onModeChange={toggleMode} />
{user?.role !== 'viewer' && device.routeros_version !== null && ( {user?.role !== 'viewer' && device.routeros_version !== null && (
<> <>
@@ -502,13 +509,13 @@ function DeviceDetailPage() {
<SSHTerminal tenantId={tenantId} deviceId={deviceId} deviceName={device.hostname} /> <SSHTerminal tenantId={tenantId} deviceId={deviceId} deviceName={device.hostname} />
)} )}
{canWrite(user) && ( {canWrite(user) && (
<Button variant="outline" size="sm" onClick={() => setEditOpen(true)} data-testid="button-edit-device"> <Button variant="ghost" size="icon" className="h-6 w-6 text-text-muted" onClick={() => setEditOpen(true)} data-testid="button-edit-device">
<Pencil className="h-3.5 w-3.5" /> <Pencil className="h-3 w-3" />
</Button> </Button>
)} )}
{canDelete(user) && ( {canDelete(user) && (
<Button variant="destructive" size="sm" onClick={handleDelete} data-testid="button-delete-device"> <Button variant="ghost" size="icon" className="h-6 w-6 text-text-muted" onClick={handleDelete} data-testid="button-delete-device">
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3 w-3" />
</Button> </Button>
)} )}
</div> </div>
@@ -535,7 +542,7 @@ function DeviceDetailPage() {
overviewContent={ overviewContent={
<> <>
{/* Device info */} {/* Device info */}
<div className="rounded-lg border border-border bg-panel px-4 py-2"> <div className="rounded-sm border border-border-default bg-panel px-3 py-1.5">
<InfoRow label="Model" value={device.model} /> <InfoRow label="Model" value={device.model} />
<InfoRow label="RouterOS" value={device.routeros_version} /> <InfoRow label="RouterOS" value={device.routeros_version} />
<InfoRow label="Firmware" value={device.firmware_version || 'N/A'} /> <InfoRow label="Firmware" value={device.firmware_version || 'N/A'} />
@@ -588,7 +595,7 @@ function DeviceDetailPage() {
</div> </div>
{/* Credentials (masked) */} {/* Credentials (masked) */}
<div className="rounded-lg border border-border bg-panel px-4 py-3"> <div className="rounded-sm border border-border-default bg-panel px-3 py-2">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-medium text-text-secondary">Credentials</h3> <h3 className="text-sm font-medium text-text-secondary">Credentials</h3>
<Button <Button
@@ -625,7 +632,7 @@ function DeviceDetailPage() {
</div> </div>
{/* Groups */} {/* Groups */}
<div className="rounded-lg border border-border bg-panel px-4 py-3 space-y-3"> <div className="rounded-sm border border-border-default bg-panel px-3 py-2 space-y-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FolderOpen className="h-4 w-4 text-text-muted" /> <FolderOpen className="h-4 w-4 text-text-muted" />
<h3 className="text-sm font-medium text-text-secondary">Groups</h3> <h3 className="text-sm font-medium text-text-secondary">Groups</h3>
@@ -671,7 +678,7 @@ function DeviceDetailPage() {
</div> </div>
{/* Tags */} {/* Tags */}
<div className="rounded-lg border border-border bg-panel px-4 py-3 space-y-3"> <div className="rounded-sm border border-border-default bg-panel px-3 py-2 space-y-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Tag className="h-4 w-4 text-text-muted" /> <Tag className="h-4 w-4 text-text-muted" />
<h3 className="text-sm font-medium text-text-secondary">Tags</h3> <h3 className="text-sm font-medium text-text-secondary">Tags</h3>