Frontend color overhaul

This commit is contained in:
Jack Kingsman
2026-02-16 16:45:05 -08:00
parent 24685038f8
commit 877649ddc7
10 changed files with 141 additions and 88 deletions
+4 -1
View File
@@ -170,7 +170,10 @@ export const MessageInput = forwardRef<MessageInputHandle, MessageInputProps>(fu
const showCharCounter = !isRepeaterMode && limits !== null;
return (
<form className="px-4 py-3 border-t border-border flex flex-col gap-1" onSubmit={handleSubmit}>
<form
className="px-4 py-2.5 border-t border-border flex flex-col gap-1"
onSubmit={handleSubmit}
>
<div className="flex gap-2">
<Input
ref={inputRef}
+6 -6
View File
@@ -461,14 +461,14 @@ export function MessageList({
<div
className={cn(
'py-1.5 px-3 rounded-lg min-w-0',
msg.outgoing ? 'bg-[#1e3a29]' : 'bg-muted'
msg.outgoing ? 'bg-msg-outgoing' : 'bg-msg-incoming'
)}
>
{showAvatar && (
<div className="text-[13px] font-bold text-foreground mb-0.5">
<div className="text-[13px] font-semibold text-foreground mb-0.5">
{canClickSender ? (
<span
className="cursor-pointer hover:text-primary hover:underline"
className="cursor-pointer hover:text-primary transition-colors"
onClick={() => onSenderClick(displaySender)}
title={`Mention ${displaySender}`}
>
@@ -587,13 +587,13 @@ export function MessageList({
{showScrollToBottom && (
<button
onClick={scrollToBottom}
className="absolute bottom-4 right-4 w-10 h-10 rounded-full bg-muted hover:bg-accent border border-border flex items-center justify-center shadow-lg transition-opacity"
className="absolute bottom-4 right-4 w-9 h-9 rounded-full bg-card hover:bg-accent border border-border flex items-center justify-center shadow-lg transition-all hover:scale-105"
title="Scroll to bottom"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
+9 -6
View File
@@ -1,6 +1,7 @@
import { useEffect, useRef, useMemo } from 'react';
import { MeshCoreDecoder, PayloadType, Utils } from '@michaelhart/meshcore-decoder';
import type { RawPacket } from '../types';
import { cn } from '@/lib/utils';
interface RawPacketListProps {
packets: RawPacket[];
@@ -204,9 +205,9 @@ export function RawPacketList({ packets }: RawPacketListProps) {
const sortedPackets = [...decodedPackets].sort((a, b) => a.packet.timestamp - b.packet.timestamp);
return (
<div className="h-full overflow-y-auto p-4 flex flex-col gap-3" ref={listRef}>
<div className="h-full overflow-y-auto p-4 flex flex-col gap-2" ref={listRef}>
{sortedPackets.map(({ packet, decoded }) => (
<div key={packet.id} className="py-2 px-3 bg-muted rounded">
<div key={packet.id} className="py-2 px-3 bg-card rounded-md border border-border/50">
<div className="flex items-center gap-2">
{/* Route type badge */}
<span
@@ -220,25 +221,27 @@ export function RawPacketList({ packets }: RawPacketListProps) {
{!packet.decrypted && <span title="Encrypted">🔒</span>}
{/* Summary */}
<span className={packet.decrypted ? 'text-primary' : 'text-foreground'}>
<span
className={cn('text-[13px]', packet.decrypted ? 'text-primary' : 'text-foreground')}
>
{decoded.summary}
</span>
{/* Time */}
<span className="text-muted-foreground ml-auto text-sm">
<span className="text-muted-foreground ml-auto text-[12px] tabular-nums">
{formatTime(packet.timestamp)}
</span>
</div>
{/* Signal info */}
{(packet.snr !== null || packet.rssi !== null) && (
<div className="text-[11px] text-muted-foreground mt-0.5">
<div className="text-[11px] text-muted-foreground mt-0.5 tabular-nums">
{formatSignalInfo(packet)}
</div>
)}
{/* Raw hex data (always visible) */}
<div className="font-mono text-[10px] break-all text-muted-foreground mt-1 p-1 bg-background/50 rounded">
<div className="font-mono text-[10px] break-all text-muted-foreground mt-1.5 p-1.5 bg-background/60 rounded">
{packet.data.toUpperCase()}
</div>
</div>
+28 -26
View File
@@ -380,9 +380,9 @@ export function Sidebar({
<div
key={row.key}
className={cn(
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors',
isActive(row.type, row.id) && 'bg-accent border-l-primary',
row.unreadCount > 0 && '[&_.name]:font-bold [&_.name]:text-foreground'
row.unreadCount > 0 && '[&_.name]:font-semibold [&_.name]:text-foreground'
)}
onClick={() =>
handleSelectConversation({
@@ -400,14 +400,14 @@ export function Sidebar({
contactType={row.contact.type}
/>
)}
<span className="name flex-1 truncate">{row.name}</span>
<span className="name flex-1 truncate text-[13px]">{row.name}</span>
{row.unreadCount > 0 && (
<span
className={cn(
'text-[10px] font-semibold px-1.5 py-0.5 rounded-full min-w-[18px] text-center',
row.isMention
? 'bg-destructive text-destructive-foreground'
: 'bg-primary text-primary-foreground'
: 'bg-primary/90 text-primary-foreground'
)}
>
{row.unreadCount}
@@ -443,10 +443,10 @@ export function Sidebar({
const effectiveCollapsed = isSearching ? false : collapsed;
return (
<div className="flex justify-between items-center px-3 py-2 pt-3">
<div className="flex justify-between items-center px-3 py-2 pt-3.5">
<button
className={cn(
'flex items-center gap-1 text-[11px] uppercase text-muted-foreground hover:text-foreground',
'flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-muted-foreground hover:text-foreground transition-colors',
isSearching && 'cursor-default'
)}
onClick={() => {
@@ -454,14 +454,14 @@ export function Sidebar({
}}
title={effectiveCollapsed ? `Expand ${title}` : `Collapse ${title}`}
>
<span className="text-[10px]">{effectiveCollapsed ? '▸' : '▾'}</span>
<span className="text-[9px]">{effectiveCollapsed ? '▸' : '▾'}</span>
<span>{title}</span>
</button>
{(showSortToggle || unreadCount > 0) && (
<div className="ml-auto flex items-center gap-1.5">
{showSortToggle && (
<button
className="bg-transparent border border-border text-muted-foreground px-1.5 py-0.5 text-[10px] rounded hover:bg-accent hover:text-foreground mr-0.5"
className="bg-transparent text-muted-foreground/60 px-1 py-0.5 text-[10px] rounded hover:text-foreground transition-colors"
onClick={handleSortToggle}
title={sortOrder === 'alpha' ? 'Sort by recent' : 'Sort alphabetically'}
>
@@ -469,7 +469,7 @@ export function Sidebar({
</button>
)}
{unreadCount > 0 && (
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-muted text-muted-foreground">
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-secondary text-muted-foreground">
{unreadCount}
</span>
)}
@@ -482,31 +482,33 @@ export function Sidebar({
return (
<div className="sidebar w-60 h-full min-h-0 bg-card border-r border-border flex flex-col">
{/* Header */}
<div className="flex justify-between items-center px-3 py-3 border-b border-border">
<h2 className="text-xs uppercase text-muted-foreground font-medium">Conversations</h2>
<div className="flex justify-between items-center px-3 py-2.5 border-b border-border">
<h2 className="text-[10px] uppercase tracking-wider text-muted-foreground font-medium">
Conversations
</h2>
<Button
variant="ghost"
size="sm"
onClick={onNewMessage}
title="New Message"
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground transition-colors"
>
+
</Button>
</div>
{/* Search */}
<div className="relative px-3 py-2 border-b border-border">
<div className="relative px-3 py-2">
<Input
type="text"
placeholder="Search..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="h-8 text-sm pr-8"
className="h-7 text-[13px] pr-8 bg-background/50"
/>
{searchQuery && (
<button
className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground text-lg leading-none"
className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground text-lg leading-none transition-colors"
onClick={() => setSearchQuery('')}
title="Clear search"
>
@@ -521,7 +523,7 @@ export function Sidebar({
{!query && (
<div
className={cn(
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]',
isActive('raw', 'raw') && 'bg-accent border-l-primary'
)}
onClick={() =>
@@ -533,7 +535,7 @@ export function Sidebar({
}
>
<span className="text-muted-foreground text-xs">📡</span>
<span className="flex-1 truncate">Packet Feed</span>
<span className="flex-1 truncate text-muted-foreground">Packet Feed</span>
</div>
)}
@@ -541,7 +543,7 @@ export function Sidebar({
{!query && (
<div
className={cn(
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]',
isActive('map', 'map') && 'bg-accent border-l-primary'
)}
onClick={() =>
@@ -553,7 +555,7 @@ export function Sidebar({
}
>
<span className="text-muted-foreground text-xs">🗺</span>
<span className="flex-1 truncate">Node Map</span>
<span className="flex-1 truncate text-muted-foreground">Node Map</span>
</div>
)}
@@ -561,7 +563,7 @@ export function Sidebar({
{!query && (
<div
className={cn(
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]',
isActive('visualizer', 'visualizer') && 'bg-accent border-l-primary'
)}
onClick={() =>
@@ -573,7 +575,7 @@ export function Sidebar({
}
>
<span className="text-muted-foreground text-xs"></span>
<span className="flex-1 truncate">Mesh Visualizer</span>
<span className="flex-1 truncate text-muted-foreground">Mesh Visualizer</span>
</div>
)}
@@ -581,18 +583,18 @@ export function Sidebar({
{!query && (
<div
className={cn(
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]',
showCracker && 'bg-accent border-l-primary'
)}
onClick={onToggleCracker}
>
<span className="text-muted-foreground text-xs">🔓</span>
<span className="flex-1 truncate">
<span className="flex-1 truncate text-muted-foreground">
{showCracker ? 'Hide' : 'Show'} Room Finder
<span
className={cn(
'ml-1 text-xs',
crackerRunning ? 'text-green-500' : 'text-muted-foreground'
'ml-1 text-[11px]',
crackerRunning ? 'text-primary' : 'text-muted-foreground'
)}
>
({crackerRunning ? 'running' : 'idle'})
@@ -604,7 +606,7 @@ export function Sidebar({
{/* Mark All Read */}
{!query && Object.keys(unreadCounts).length > 0 && (
<div
className="px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent"
className="px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]"
onClick={onMarkAllRead}
>
<span className="text-muted-foreground text-xs"></span>
+17 -14
View File
@@ -3,6 +3,7 @@ import { Menu } from 'lucide-react';
import type { HealthStatus, RadioConfig } from '../types';
import { api } from '../api';
import { toast } from './ui/sonner';
import { cn } from '@/lib/utils';
interface StatusBarProps {
health: HealthStatus | null;
@@ -39,34 +40,39 @@ export function StatusBar({
};
return (
<div className="flex items-center gap-4 px-4 py-2 bg-card border-b border-border text-xs">
<div className="flex items-center gap-3 px-4 py-2.5 bg-card border-b border-border text-xs">
{/* Mobile menu button - only visible on small screens */}
{onMenuClick && (
<button
onClick={onMenuClick}
className="md:hidden p-1 bg-transparent border-none text-foreground cursor-pointer"
className="md:hidden p-1 bg-transparent border-none text-muted-foreground hover:text-foreground cursor-pointer transition-colors"
aria-label="Open menu"
>
<Menu className="h-5 w-5" />
</button>
)}
<h1 className="text-base font-semibold mr-auto">RemoteTerm</h1>
<h1 className="text-sm font-semibold tracking-tight mr-auto text-foreground">RemoteTerm</h1>
<div className="flex items-center gap-1 text-muted-foreground">
<div className="flex items-center gap-1.5">
<div
className={`w-2 h-2 rounded-full ${connected ? 'bg-primary' : 'bg-muted-foreground'}`}
className={cn(
'w-2 h-2 rounded-full transition-colors',
connected
? 'bg-primary shadow-[0_0_6px_hsl(var(--primary)/0.5)]'
: 'bg-muted-foreground'
)}
/>
<span className="hidden lg:inline text-foreground">
<span className="hidden lg:inline text-muted-foreground">
{connected ? 'Connected' : 'Disconnected'}
</span>
</div>
{config && (
<div className="hidden lg:flex items-center gap-2 text-muted-foreground">
<span className="text-foreground">{config.name || 'Unnamed'}</span>
<span className="text-foreground font-medium">{config.name || 'Unnamed'}</span>
<span
className="font-mono text-muted-foreground cursor-pointer hover:text-primary"
className="font-mono text-[11px] text-muted-foreground cursor-pointer hover:text-primary transition-colors"
onClick={() => {
navigator.clipboard.writeText(config.public_key);
toast.success('Public key copied!');
@@ -82,19 +88,16 @@ export function StatusBar({
<button
onClick={handleReconnect}
disabled={reconnecting}
className="px-3 py-1 bg-amber-950 border border-amber-800 text-amber-400 rounded text-xs cursor-pointer hover:bg-amber-900 disabled:opacity-50 disabled:cursor-not-allowed"
className="px-3 py-1 bg-amber-500/10 border border-amber-500/20 text-amber-400 rounded-md text-xs cursor-pointer hover:bg-amber-500/15 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{reconnecting ? 'Reconnecting...' : 'Reconnect'}
</button>
)}
<button
onClick={onSettingsClick}
className="px-3 py-1 bg-secondary border border-border text-foreground rounded text-xs cursor-pointer hover:bg-accent"
className="px-3 py-1.5 bg-secondary border border-border text-muted-foreground rounded-md text-xs cursor-pointer hover:bg-accent hover:text-foreground transition-colors"
>
<span role="img" aria-label="Settings">
&#128295;
</span>{' '}
{settingsMode ? 'Back to Chat' : 'Radio & Config'}
{settingsMode ? 'Back to Chat' : 'Settings'}
</button>
</div>
);