feat: complete UI/UX overhaul with Impeccable design system
Replace Inter/Baskerville with self-hosted Manrope/Alegreya/JetBrains Mono variable fonts. Migrate all colors from hex to OKLCH tokens (30+ CSS custom properties) with full dark/light mode support. Replace Unicode emoji with lucide-react SVG icons throughout. Convert all page layouts to inline styles (Tailwind CSS 4 flex/grid classes unreliable in this project). Code-split routes via React.lazy (760KB → 15KB initial shell + 10 lazy chunks). Add global styles: scrollbar theming, selection color, input/button bases, :focus-visible ring, prefers-reduced-motion. Setup wizard gets 4-phase indicator with numbered circles, PathInput and StatusDot components. Toast container gets aria-live="polite". Tab close buttons changed to proper <button> elements with aria-labels. All 8 pages (Dashboard, Editor, Build, Server, Toolset, Repos, Settings, Setup) rewritten with consistent card/section/button patterns.
This commit is contained in:
@@ -3,14 +3,28 @@ import { Outlet, Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { Terminal } from "../components/terminal/Terminal";
|
||||
import { useWebSocket } from "../hooks/useWebSocket";
|
||||
import { useTheme } from "../hooks/useTheme";
|
||||
import {
|
||||
Code2,
|
||||
Wrench,
|
||||
Hammer,
|
||||
Play,
|
||||
GitBranch,
|
||||
Settings,
|
||||
Sun,
|
||||
Moon,
|
||||
Terminal as TerminalIcon,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
} from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ path: "/editor", label: "Editor", icon: "\u270E" },
|
||||
{ path: "/toolset", label: "Toolset", icon: "\u2699" },
|
||||
{ path: "/build", label: "Build", icon: "\u2692" },
|
||||
{ path: "/server", label: "Server", icon: "\u25B6" },
|
||||
{ path: "/repos", label: "Repos", icon: "\u2387" },
|
||||
{ path: "/settings", label: "Settings", icon: "\u2318" },
|
||||
const NAV_ITEMS: { path: string; label: string; Icon: LucideIcon }[] = [
|
||||
{ path: "/editor", label: "Editor", Icon: Code2 },
|
||||
{ path: "/toolset", label: "Toolset", Icon: Wrench },
|
||||
{ path: "/build", label: "Build", Icon: Hammer },
|
||||
{ path: "/server", label: "Server", Icon: Play },
|
||||
{ path: "/repos", label: "Repos", Icon: GitBranch },
|
||||
{ path: "/settings", label: "Settings", Icon: Settings },
|
||||
];
|
||||
|
||||
export function IDELayout({ sidebar }: { sidebar?: React.ReactNode }) {
|
||||
@@ -21,6 +35,7 @@ export function IDELayout({ sidebar }: { sidebar?: React.ReactNode }) {
|
||||
const navigate = useNavigate();
|
||||
const { subscribe } = useWebSocket();
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const showSidebar = location.pathname === "/editor" || location.pathname.startsWith("/editor/");
|
||||
|
||||
useEffect(() => {
|
||||
return subscribe("git:upstream-update", (event) => {
|
||||
@@ -85,25 +100,28 @@ export function IDELayout({ sidebar }: { sidebar?: React.ReactNode }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden" style={{ backgroundColor: "var(--forge-bg)" }}>
|
||||
<div style={{ display: "flex", height: "100vh", overflow: "hidden", backgroundColor: "var(--forge-bg)" }}>
|
||||
{/* Left sidebar nav */}
|
||||
<nav
|
||||
className="flex shrink-0 flex-col"
|
||||
aria-label="Main navigation"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "56px",
|
||||
flexShrink: 0,
|
||||
borderRight: "1px solid var(--forge-border)",
|
||||
backgroundColor: "var(--forge-surface)",
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center justify-center py-3"
|
||||
style={{ display: "flex", alignItems: "center", justifyContent: "center", padding: "0.75rem 0", textDecoration: "none" }}
|
||||
title="Dashboard"
|
||||
>
|
||||
<img src="/layonara.png" alt="Layonara" style={{ width: "40px" }} />
|
||||
<img src="/layonara.png" alt="Layonara" style={{ width: "36px" }} />
|
||||
</Link>
|
||||
|
||||
<div className="mt-2 flex flex-1 flex-col">
|
||||
<div style={{ marginTop: "0.25rem", display: "flex", flexDirection: "column", flex: 1 }}>
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const isActive =
|
||||
item.path === "/"
|
||||
@@ -115,20 +133,26 @@ export function IDELayout({ sidebar }: { sidebar?: React.ReactNode }) {
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className="relative flex flex-col items-center justify-center py-2.5 text-center transition-colors hover:bg-white/5"
|
||||
style={{
|
||||
borderLeft: isActive
|
||||
? "3px solid var(--forge-accent)"
|
||||
: "3px solid transparent",
|
||||
backgroundColor: isActive ? "rgba(148, 98, 0, 0.1)" : undefined,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "0.625rem 0",
|
||||
position: "relative",
|
||||
textDecoration: "none",
|
||||
transition: "background-color 150ms, color 150ms",
|
||||
backgroundColor: isActive ? "var(--forge-accent-subtle)" : undefined,
|
||||
color: isActive ? "var(--forge-accent)" : "var(--forge-text-secondary)",
|
||||
}}
|
||||
onMouseEnter={(e) => { if (!isActive) e.currentTarget.style.backgroundColor = "var(--forge-surface-raised)"; }}
|
||||
onMouseLeave={(e) => { if (!isActive) e.currentTarget.style.backgroundColor = isActive ? "var(--forge-accent-subtle)" : ""; }}
|
||||
title={item.label}
|
||||
>
|
||||
<span className="text-base">{item.icon}</span>
|
||||
<span className="mt-0.5 text-[9px] leading-tight">{item.label}</span>
|
||||
<item.Icon size={18} strokeWidth={isActive ? 2.5 : 2} />
|
||||
<span style={{ marginTop: "0.25rem", fontSize: "0.625rem", fontWeight: 500, lineHeight: 1 }}>{item.label}</span>
|
||||
{badge > 0 && (
|
||||
<span className="absolute right-1 top-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-amber-500 px-0.5 text-[8px] font-bold text-black">
|
||||
<span className="absolute right-1 top-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full px-0.5 text-[8px] font-bold" style={{ backgroundColor: "var(--forge-accent)", color: "var(--forge-accent-text)" }}>
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
@@ -139,69 +163,100 @@ export function IDELayout({ sidebar }: { sidebar?: React.ReactNode }) {
|
||||
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="flex items-center justify-center py-3 text-sm transition-colors hover:bg-white/5"
|
||||
style={{ color: "var(--forge-text-secondary)" }}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "0.625rem 0",
|
||||
color: "var(--forge-text-secondary)",
|
||||
background: "none",
|
||||
border: "none",
|
||||
width: "100%",
|
||||
transition: "background-color 150ms, color 150ms",
|
||||
}}
|
||||
title={`Switch to ${theme === "dark" ? "light" : "dark"} mode`}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = "var(--forge-surface-raised)"; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = ""; }}
|
||||
>
|
||||
{theme === "dark" ? "\u2600\uFE0F" : "\uD83C\uDF19"}
|
||||
{theme === "dark" ? <Sun size={18} /> : <Moon size={18} />}
|
||||
<span style={{ marginTop: "0.25rem", fontSize: "0.625rem", fontWeight: 500, lineHeight: 1 }}>{theme === "dark" ? "Light" : "Dark"}</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<div style={{ display: "flex", flexDirection: "column", flex: 1, overflow: "hidden" }}>
|
||||
<header
|
||||
className="flex shrink-0 items-center gap-4 px-4 py-1.5"
|
||||
style={{ borderBottom: "1px solid var(--forge-border)" }}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "1rem",
|
||||
padding: "0.375rem 1rem",
|
||||
borderBottom: "1px solid var(--forge-border)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="text-lg font-bold"
|
||||
style={{
|
||||
fontFamily: "'Baskerville', 'Georgia', 'Palatino', serif",
|
||||
color: "var(--forge-accent)",
|
||||
}}
|
||||
>
|
||||
Layonara Forge
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1" />
|
||||
<span
|
||||
style={{
|
||||
fontFamily: "var(--font-heading)",
|
||||
fontSize: "var(--text-lg)",
|
||||
fontWeight: 700,
|
||||
color: "var(--forge-accent)",
|
||||
}}
|
||||
>
|
||||
Layonara Forge
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{sidebar && (
|
||||
<div style={{ display: "flex", flex: 1, overflow: "hidden" }}>
|
||||
{sidebar && showSidebar && (
|
||||
<aside
|
||||
className="shrink-0 overflow-hidden"
|
||||
style={{
|
||||
width: "250px",
|
||||
flexShrink: 0,
|
||||
overflow: "hidden",
|
||||
borderRight: "1px solid var(--forge-border)",
|
||||
}}
|
||||
>
|
||||
{sidebar}
|
||||
</aside>
|
||||
)}
|
||||
<main className="flex-1 overflow-hidden">
|
||||
<main style={{ flex: 1, overflow: "hidden" }}>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setTerminalOpen((v) => !v)}
|
||||
className="flex shrink-0 items-center gap-1 px-3 py-0.5 text-xs transition-colors hover:bg-white/5"
|
||||
style={{
|
||||
borderTop: "1px solid var(--forge-border)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "0.5rem",
|
||||
padding: "0.375rem 0.75rem",
|
||||
color: "var(--forge-text-secondary)",
|
||||
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
||||
fontFamily: "var(--font-mono)",
|
||||
fontSize: "var(--text-xs)",
|
||||
background: "none",
|
||||
border: "none",
|
||||
borderTop: "1px solid var(--forge-border)",
|
||||
width: "100%",
|
||||
cursor: "pointer",
|
||||
transition: "background-color 150ms",
|
||||
}}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = "var(--forge-surface-raised)"; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = ""; }}
|
||||
>
|
||||
<span>{terminalOpen ? "\u25BC" : "\u25B2"}</span>
|
||||
<TerminalIcon size={12} />
|
||||
<span>Terminal</span>
|
||||
{terminalOpen ? <ChevronDown size={12} /> : <ChevronUp size={12} />}
|
||||
</button>
|
||||
|
||||
{terminalOpen && (
|
||||
<div
|
||||
className="shrink-0 overflow-hidden"
|
||||
style={{
|
||||
height: "300px",
|
||||
flexShrink: 0,
|
||||
overflow: "hidden",
|
||||
borderTop: "1px solid var(--forge-border)",
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user