refactor(ui): theme harmonization and semantic tokens
Some checks are pending
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run

This commit is contained in:
fullsizemalt 2026-01-01 19:05:26 -08:00
parent 6bdabb0e60
commit 6d957f1c92
5 changed files with 90 additions and 82 deletions

View file

@ -206,44 +206,50 @@ export function LayoutEditor({ floorId, className }: LayoutEditorProps) {
{/* Selected Slot Panel */}
{selectedSlot && (
// Panel container - kept existing glassmorphism but ensured semantic borders
<div className="w-80 border-l border-border bg-card/80 backdrop-blur-xl p-6 absolute right-0 top-0 bottom-0 shadow-2xl z-10 transition-transform animate-in slide-in-from-right">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-display font-semibold text-foreground">Slot Details</h3>
<button
onClick={() => setSelectedSlot(null)}
className="text-slate-400 hover:text-white"
className="text-muted-foreground hover:text-foreground transition-colors p-1 hover:bg-secondary rounded"
>
</button>
</div>
<div className="space-y-3 text-sm">
<div>
<span className="text-slate-400">Position:</span>
<span className="ml-2 text-white">R{selectedSlot.row} C{selectedSlot.column}</span>
<div className="space-y-4 text-sm">
<div className="grid grid-cols-[80px_1fr] items-center">
<span className="text-muted-foreground">Position:</span>
<span className="text-foreground font-mono bg-secondary/50 px-2 py-0.5 rounded w-fit">R{selectedSlot.row} C{selectedSlot.column}</span>
</div>
<div>
<span className="text-slate-400">Status:</span>
<div className="grid grid-cols-[80px_1fr] items-center">
<span className="text-muted-foreground">Status:</span>
<span className={cn(
'ml-2',
selectedSlot.status === 'OCCUPIED' && 'text-emerald-400',
selectedSlot.status === 'EMPTY' && 'text-slate-400',
selectedSlot.status === 'DAMAGED' && 'text-red-400',
selectedSlot.status === 'RESERVED' && 'text-yellow-400',
'px-2 py-0.5 rounded font-medium text-xs w-fit',
selectedSlot.status === 'OCCUPIED' && 'bg-primary/10 text-primary border border-primary/20',
selectedSlot.status === 'EMPTY' && 'bg-secondary text-muted-foreground border border-border',
selectedSlot.status === 'DAMAGED' && 'bg-destructive/10 text-destructive border border-destructive/20',
selectedSlot.status === 'RESERVED' && 'bg-warning/10 text-warning border border-warning/20',
)}>
{selectedSlot.status}
</span>
</div>
{selectedSlot.plantType && (
<>
<div className="grid grid-cols-[80px_1fr] items-start pt-2 border-t border-border mt-2">
<span className="text-muted-foreground mt-0.5">Plant:</span>
<div>
<span className="text-slate-400">Plant:</span>
<span className="ml-2 text-white">{selectedSlot.plantType.name}</span>
<span className="text-foreground font-medium block">{selectedSlot.plantType.name}</span>
{selectedSlot.plantType.strain && (
<span className="text-xs text-muted-foreground">{selectedSlot.plantType.strain}</span>
)}
</div>
</div>
{selectedSlot.tagNumber && (
<div>
<span className="text-slate-400">Tag:</span>
<span className="ml-2 text-white font-mono">{selectedSlot.tagNumber}</span>
<div className="grid grid-cols-[80px_1fr] items-center">
<span className="text-muted-foreground">Tag:</span>
<span className="text-foreground font-mono text-xs border border-border px-1.5 py-0.5 rounded bg-background">{selectedSlot.tagNumber}</span>
</div>
)}
</>
@ -251,18 +257,18 @@ export function LayoutEditor({ floorId, className }: LayoutEditorProps) {
</div>
{/* Actions */}
<div className="mt-6 space-y-2">
<div className="mt-8 space-y-3">
{selectedSlot.status === 'EMPTY' && (
<button className="w-full px-3 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-sm rounded transition-colors">
<button className="w-full px-3 py-2.5 bg-primary hover:bg-primary-hover text-primary-foreground font-medium text-sm rounded-lg shadow-sm transition-all hover:shadow-md active:scale-95">
Place Plant
</button>
)}
{selectedSlot.status === 'OCCUPIED' && (
<>
<button className="w-full px-3 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded transition-colors">
<button className="w-full px-3 py-2 bg-secondary hover:bg-secondary/80 text-foreground border border-border hover:border-border-strong text-sm rounded-lg transition-colors">
Move Plant
</button>
<button className="w-full px-3 py-2 bg-red-600 hover:bg-red-500 text-white text-sm rounded transition-colors">
<button className="w-full px-3 py-2 bg-destructive/10 hover:bg-destructive/20 text-destructive hover:text-destructive-foreground border border-destructive/20 hover:border-destructive/30 text-sm rounded-lg transition-all">
Remove Plant
</button>
</>

View file

@ -70,13 +70,13 @@ export function RackConfigModal({ rack, isOpen, onClose, onSave }: RackConfigMod
<div className="absolute inset-0 bg-black/60" onClick={onClose} />
{/* Modal */}
<div className="relative bg-slate-800 rounded-xl shadow-2xl w-full max-w-lg mx-4 border border-slate-600">
<div className="relative bg-card rounded-xl shadow-2xl w-full max-w-lg mx-4 border border-border animate-in scale-in">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-700">
<h2 className="text-lg font-semibold text-white">Configure Section</h2>
<div className="flex items-center justify-between px-6 py-4 border-b border-border">
<h2 className="text-lg font-semibold text-foreground">Configure Section</h2>
<button
onClick={onClose}
className="text-slate-400 hover:text-white transition-colors"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<X className="w-5 h-5" />
</button>
@ -87,22 +87,22 @@ export function RackConfigModal({ rack, isOpen, onClose, onSave }: RackConfigMod
{/* Name & Code */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-slate-300 mb-1">Name</label>
<label className="block text-sm font-medium text-foreground mb-1">Name</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded text-white focus:outline-none focus:border-emerald-500"
className="w-full px-3 py-2 bg-secondary/50 border border-input rounded text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20 transition-all font-sans"
placeholder="e.g., Table 1"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-1">Code</label>
<label className="block text-sm font-medium text-foreground mb-1">Code</label>
<input
type="text"
value={code}
onChange={(e) => setCode(e.target.value)}
className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded text-white focus:outline-none focus:border-emerald-500"
className="w-full px-3 py-2 bg-secondary/50 border border-input rounded text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20 transition-all font-mono"
placeholder="e.g., T1"
/>
</div>
@ -110,7 +110,7 @@ export function RackConfigModal({ rack, isOpen, onClose, onSave }: RackConfigMod
{/* Section Type */}
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">Section Type</label>
<label className="block text-sm font-medium text-foreground mb-2">Section Type</label>
<div className="grid grid-cols-5 gap-2">
{SUBTYPE_OPTIONS.map((opt) => (
<button
@ -119,8 +119,8 @@ export function RackConfigModal({ rack, isOpen, onClose, onSave }: RackConfigMod
className={cn(
'flex flex-col items-center p-2 rounded border transition-colors',
subtype === opt.value
? 'border-emerald-500 bg-emerald-900/30 text-emerald-400'
: 'border-slate-600 bg-slate-700 text-slate-300 hover:border-slate-500'
? 'border-primary bg-primary/10 text-primary'
: 'border-border bg-secondary/30 text-muted-foreground hover:border-border-strong hover:text-foreground'
)}
title={opt.desc}
>
@ -133,50 +133,50 @@ export function RackConfigModal({ rack, isOpen, onClose, onSave }: RackConfigMod
{/* Dimensions */}
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">Dimensions</label>
<label className="block text-sm font-medium text-foreground mb-2">Dimensions</label>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="block text-xs text-slate-400 mb-1">Rows</label>
<label className="block text-xs text-muted-foreground mb-1">Rows</label>
<input
type="number"
value={rows}
onChange={(e) => setRows(Math.max(1, Math.min(50, parseInt(e.target.value) || 1)))}
min={1}
max={50}
className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded text-white text-center focus:outline-none focus:border-emerald-500"
className="w-full px-3 py-2 bg-secondary/50 border border-input rounded text-foreground text-center focus:outline-none focus:border-primary font-mono"
/>
</div>
<div>
<label className="block text-xs text-slate-400 mb-1">Columns</label>
<label className="block text-xs text-muted-foreground mb-1">Columns</label>
<input
type="number"
value={columns}
onChange={(e) => setColumns(Math.max(1, Math.min(50, parseInt(e.target.value) || 1)))}
min={1}
max={50}
className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded text-white text-center focus:outline-none focus:border-emerald-500"
className="w-full px-3 py-2 bg-secondary/50 border border-input rounded text-foreground text-center focus:outline-none focus:border-primary font-mono"
/>
</div>
<div>
<label className="block text-xs text-slate-400 mb-1">Tiers</label>
<label className="block text-xs text-muted-foreground mb-1">Tiers</label>
<input
type="number"
value={tiers}
onChange={(e) => setTiers(Math.max(1, Math.min(10, parseInt(e.target.value) || 1)))}
min={1}
max={10}
className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded text-white text-center focus:outline-none focus:border-emerald-500"
className="w-full px-3 py-2 bg-secondary/50 border border-input rounded text-foreground text-center focus:outline-none focus:border-primary font-mono"
/>
</div>
</div>
<p className="text-xs text-slate-500 mt-2">
Total positions: <span className="text-emerald-400 font-medium">{totalSlots}</span>
<p className="text-xs text-muted-foreground mt-2">
Total positions: <span className="text-primary font-mono">{totalSlots}</span>
</p>
</div>
{/* Visual Preview */}
<div className="bg-slate-900 rounded p-3">
<p className="text-xs text-slate-400 mb-2">Preview</p>
<div className="bg-secondary/20 rounded p-4 border border-border/50">
<p className="text-xs text-muted-foreground mb-2">Preview</p>
<div
className="grid gap-1"
style={{
@ -187,29 +187,29 @@ export function RackConfigModal({ rack, isOpen, onClose, onSave }: RackConfigMod
{Array.from({ length: Math.min(rows * columns, 50) }, (_, i) => (
<div
key={i}
className="aspect-square bg-slate-700 border border-dashed border-slate-600 rounded"
className="aspect-square bg-secondary border border-dashed border-border rounded-sm"
style={{ minWidth: '12px', minHeight: '12px' }}
/>
))}
</div>
{rows * columns > 50 && (
<p className="text-xs text-slate-500 mt-1">Showing first 50 slots</p>
<p className="text-xs text-muted-foreground mt-1 italic">Showing first 50 slots</p>
)}
</div>
</div>
{/* Footer */}
<div className="flex justify-end gap-3 px-6 py-4 border-t border-slate-700">
<div className="flex justify-end gap-3 px-6 py-4 border-t border-border bg-secondary/10">
<button
onClick={onClose}
className="px-4 py-2 text-sm text-slate-300 hover:text-white transition-colors"
className="px-4 py-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Cancel
</button>
<button
onClick={handleSave}
disabled={saving || !name.trim()}
className="px-4 py-2 text-sm bg-emerald-600 hover:bg-emerald-500 disabled:bg-slate-600 disabled:cursor-not-allowed text-white rounded transition-colors"
className="px-4 py-2 text-sm bg-primary hover:bg-primary-hover disabled:bg-primary/50 disabled:cursor-not-allowed text-primary-foreground rounded transition-colors font-medium shadow-sm hover:shadow-md"
>
{saving ? 'Saving...' : 'Save Changes'}
</button>

View file

@ -45,11 +45,11 @@ const TIER_TAB_HEIGHT = 24;
// Subtype visual styles
const SUBTYPE_STYLES = {
TABLE: { bg: 'bg-amber-900/20', border: 'border-amber-700', Icon: Table },
RACK: { bg: 'bg-slate-800/50', border: 'border-slate-600', Icon: Server },
TRAY: { bg: 'bg-emerald-900/20', border: 'border-emerald-700', Icon: Box },
HANGER: { bg: 'bg-purple-900/20', border: 'border-purple-700', Icon: GripVertical },
FLOOR: { bg: 'bg-stone-900/20', border: 'border-stone-600', Icon: Square },
TABLE: { bg: 'bg-yellow-500/10', border: 'border-yellow-500/20', Icon: Table },
RACK: { bg: 'bg-secondary/30', border: 'border-border', Icon: Server },
TRAY: { bg: 'bg-primary/10', border: 'border-primary/20', Icon: Box },
HANGER: { bg: 'bg-purple-500/10', border: 'border-purple-500/20', Icon: GripVertical },
FLOOR: { bg: 'bg-muted/50', border: 'border-border', Icon: Square },
};
export function RackVisualizer({
@ -96,21 +96,21 @@ export function RackVisualizer({
return (
<div className={cn('inline-block group', className)}>
{/* Rack header */}
<div className={cn('flex items-center gap-2 px-3 py-1.5 rounded-t-lg border-b-0', style.bg, style.border, 'border')}>
<style.Icon className="w-5 h-5 text-slate-300" />
<span className="text-sm font-medium text-white">{rack.name}</span>
<span className="text-xs text-slate-400">({rack.code})</span>
<div className={cn('flex items-center gap-2 px-3 py-2 rounded-t-lg border-b-0 backdrop-blur-sm', style.bg, style.border, 'border')}>
<style.Icon className="w-4 h-4 text-muted-foreground" />
<span className="text-sm font-medium text-foreground">{rack.name}</span>
<span className="text-xs text-muted-foreground font-mono">({rack.code})</span>
<div className="ml-auto flex items-center gap-2">
{rack.tiers > 1 && (
<span className="text-xs text-slate-400">Tier {selectedTier}/{rack.tiers}</span>
<span className="text-xs text-muted-foreground font-mono">T{selectedTier}/{rack.tiers}</span>
)}
{onConfigClick && (
<button
onClick={() => onConfigClick(rack)}
className="opacity-0 group-hover:opacity-100 p-1 text-slate-400 hover:text-white hover:bg-slate-600 rounded transition-all"
className="opacity-0 group-hover:opacity-100 p-1 text-muted-foreground hover:text-foreground hover:bg-secondary rounded transition-all"
title="Configure section"
>
<Settings className="w-4 h-4" />
<Settings className="w-3.5 h-3.5" />
</button>
)}
</div>
@ -118,15 +118,15 @@ export function RackVisualizer({
{/* Tier tabs (if multi-tier) */}
{rack.tiers > 1 && (
<div className="flex border-l border-r" style={{ borderColor: style.border.replace('border-', '') }}>
<div className="flex border-l border-r border-border" style={{ borderColor: style.border.replace('border-', '') }}>
{Array.from({ length: rack.tiers }, (_, i) => i + 1).map(tier => (
<button
key={tier}
className={cn(
'flex-1 text-xs py-1 transition-colors',
'flex-1 text-[10px] py-1 transition-colors font-mono',
tier === selectedTier
? 'bg-emerald-600 text-white'
: 'bg-slate-800 text-slate-400 hover:bg-slate-700'
? 'bg-primary/20 text-primary font-bold'
: 'bg-secondary/50 text-muted-foreground hover:bg-secondary'
)}
>
T{tier}
@ -137,7 +137,7 @@ export function RackVisualizer({
{/* Grid */}
<div
className={cn('p-1 rounded-b-lg border', style.bg, style.border)}
className={cn('p-1 rounded-b-lg border shadow-sm', style.bg, style.border)}
style={{ width: gridWidth + 8, minHeight: gridHeight + 8 }}
>
<div
@ -217,37 +217,36 @@ function PlantSlot({ slot, isHighlighted, onClick, onDrop }: PlantSlotProps) {
onDrop={handleDrop}
className={cn(
'rounded flex items-center justify-center cursor-pointer transition-all duration-150',
'border-2',
'border',
// Empty slot styles
slot.status === 'EMPTY' && !isDragOver && 'border-dashed border-slate-600 bg-slate-800/30 hover:border-slate-500',
slot.status === 'EMPTY' && !isDragOver && 'border-dashed border-border/50 bg-card/20 hover:border-border hover:bg-card/40',
// Drag over state - highlight
isDragOver && 'border-solid border-emerald-400 bg-emerald-500/30 scale-105 shadow-lg shadow-emerald-500/20',
isDragOver && 'border-solid border-primary bg-primary/20 scale-105 shadow-[0_0_10px_rgba(37,99,235,0.3)]',
// Occupied
isOccupied && 'border-solid',
isOccupied && 'border-solid border-transparent shadow-sm',
// Damaged
isDamaged && 'border-red-500 bg-red-900/30',
isDamaged && 'border-destructive bg-destructive/10',
// Reserved
isReserved && 'border-yellow-500 bg-yellow-900/20 border-solid',
isReserved && 'border-warning bg-warning/10 border-solid',
// Highlighted (searched)
isHighlighted && 'ring-2 ring-emerald-400 ring-offset-1 ring-offset-slate-900'
isHighlighted && 'ring-2 ring-primary ring-offset-1 ring-offset-background'
)}
style={{
width: SLOT_SIZE,
height: SLOT_SIZE,
backgroundColor: isOccupied ? slot.plantType?.colour : undefined,
borderColor: isOccupied ? slot.plantType?.colour : undefined,
}}
title={isOccupied ? `${slot.plantType?.name} (${slot.tagNumber || 'No tag'})` : `Empty - R${slot.row}C${slot.column}`}
>
{isOccupied && (
<span className="text-white text-xs font-bold drop-shadow-md">
<span className="text-white text-xs font-bold font-mono drop-shadow-md tracking-tighter">
{slot.plantType?.strain?.substring(0, 3) || '•'}
</span>
)}
{isDamaged && <span className="text-red-400"></span>}
{isReserved && <span className="text-yellow-400 text-xs"></span>}
{isDamaged && <span className="text-destructive"></span>}
{isReserved && <span className="text-warning text-xs"></span>}
{isDragOver && !isOccupied && (
<span className="text-emerald-400 text-lg">+</span>
<span className="text-primary text-lg animate-pulse">+</span>
)}
</div>
);

View file

@ -110,9 +110,12 @@
/* ===== DARK MODE (Fediversion Theme) ===== */
.dark {
/* Backgrounds - Zinc scale */
--color-bg-primary: #09090b; /* zinc-950 */
--color-bg-secondary: #18181b; /* zinc-900 */
--color-bg-tertiary: #27272a; /* zinc-800 */
--color-bg-primary: #09090b;
/* zinc-950 */
--color-bg-secondary: #18181b;
/* zinc-900 */
--color-bg-tertiary: #27272a;
/* zinc-800 */
--color-bg-elevated: #18181b;
/* Text - improved contrast */

View file

@ -110,7 +110,7 @@ export default {
// Linear-inspired typography
fontFamily: {
sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
display: ['Space Grotesk', 'Inter', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
display: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'], // Switched back to Inter per user request
mono: ['JetBrains Mono', 'Fira Code', 'Consolas', 'monospace'],
},
fontSize: {