feat: complete UI redesign — editorial-paper palette, Monaco theme, lucide icons, primitives
Palette overhaul: - Warm cream canvas (#faf8f4) + near-white surfaces (#fdfdfb) + deep teal accent (#0f766e) - Unified on stone-warm neutrals via @theme block in index.css Typography: - Inter Variable + JetBrains Mono Variable via fontsource (self-hosted) - Monaco editor set to JetBrains Mono for consistency Icons: - lucide-react replaces all 8 hand-rolled SVGs - Leaf brand mark beside wordmark Shared primitives (src/components/ui/): - Icon, Button (4 variants, 2 sizes), IconButton, TextInput, SectionHeader, StatusDot, Chip Custom Monaco theme (src/theme/monaco-theme.ts): - typst-leaf-light: warm near-white background, restrained rainbow-free syntax - Keywords teal, strings warm green, comments stone italic, most tokens ink Component reworks: - TopBar: Leaf icon, h-12, chip-based status, Button/IconButton primitives - Sidebar: w-72, icon+label tabs with teal bottom border - StatusBar: lucide GitBranch/ArrowUp/ArrowDown, chip-based status - FileTree: lucide Plus/FolderPlus/Pencil/Trash2, teal active state - GitPanel: lucide status icons, restored staged/unstaged bg tint - Preview: RotateCw reload icon, chip status - EmptyState: muted Leaf icon - ToastHost: StatusDot + lucide X Fixes: - GitPanel staged/unstaged/untracked rows now have distinct bg tints - Button defaults to type="button" to prevent accidental form submission - Editor error fallback from red-400 to rose-600 - DESIGN.md: removed emoji from layout skeleton, fixed Modals/Monaco typo Build: pnpm -r typecheck + pnpm build pass.
This commit is contained in:
@@ -11,12 +11,20 @@ import { EmptyState } from "./components/EmptyState";
|
||||
import { PanelSection } from "./components/PanelSection";
|
||||
import { SettingsPanel } from "./components/SettingsPanel";
|
||||
import { ResizableSplit } from "./components/ResizableSplit";
|
||||
import { Files, GitGraph, Settings } from "lucide-react";
|
||||
import { Icon } from "./components/ui/Icon";
|
||||
|
||||
function Shell() {
|
||||
const { state, dispatch } = useApp();
|
||||
|
||||
const panelTabs = [
|
||||
{ id: "files" as const, label: "Files", icon: Files },
|
||||
{ id: "git" as const, label: "Git", icon: GitGraph },
|
||||
{ id: "settings" as const, label: "Settings", icon: Settings },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen bg-stone-50 text-zinc-800 flex flex-col overflow-hidden">
|
||||
<div className="h-screen w-screen bg-canvas text-ink flex flex-col overflow-hidden">
|
||||
<TopBar />
|
||||
|
||||
{state.error && (
|
||||
@@ -27,51 +35,33 @@ function Shell() {
|
||||
|
||||
<div className="flex flex-1 min-h-0">
|
||||
{/* Sidebar */}
|
||||
<aside className="w-64 border-r border-zinc-200 bg-stone-50 flex flex-col min-h-0 shrink-0">
|
||||
<aside className="w-72 border-r border-line bg-canvas flex flex-col min-h-0 shrink-0">
|
||||
<ProjectPicker />
|
||||
|
||||
{/* Sidebar tabs */}
|
||||
<div className="flex border-b border-zinc-200 text-xs">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => dispatch({ type: "setActivePanel", panel: "files" })}
|
||||
className={`flex-1 py-1.5 text-center uppercase tracking-wider ${
|
||||
state.activePanel === "files"
|
||||
? "text-emerald-700 border-b-2 border-emerald-500 bg-white"
|
||||
: "text-zinc-400 hover:text-zinc-600 hover:bg-stone-100"
|
||||
}`}
|
||||
>
|
||||
Files
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => dispatch({ type: "setActivePanel", panel: "git" })}
|
||||
className={`flex-1 py-1.5 text-center uppercase tracking-wider ${
|
||||
state.activePanel === "git"
|
||||
? "text-emerald-700 border-b-2 border-emerald-500 bg-white"
|
||||
: "text-zinc-400 hover:text-zinc-600 hover:bg-stone-100"
|
||||
}`}
|
||||
>
|
||||
Git
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => dispatch({ type: "setActivePanel", panel: "settings" })}
|
||||
className={`flex-1 py-1.5 text-center uppercase tracking-wider ${
|
||||
state.activePanel === "settings"
|
||||
? "text-emerald-700 border-b-2 border-emerald-500 bg-white"
|
||||
: "text-zinc-400 hover:text-zinc-600 hover:bg-stone-100"
|
||||
}`}
|
||||
>
|
||||
Settings
|
||||
</button>
|
||||
<div className="flex border-b border-line text-xs">
|
||||
{panelTabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
type="button"
|
||||
onClick={() => dispatch({ type: "setActivePanel", panel: tab.id })}
|
||||
className={`flex items-center justify-center gap-1.5 flex-1 py-1.5 transition-colors duration-150 ${
|
||||
state.activePanel === tab.id
|
||||
? "text-teal-600 border-b-2 border-teal-500 bg-surface"
|
||||
: "text-ink-muted hover:text-ink hover:bg-surface-hover"
|
||||
}`}
|
||||
>
|
||||
<Icon icon={tab.icon} className="size-3.5" />
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto">
|
||||
{state.activePanel === "files" && (
|
||||
<PanelSection title="Files">
|
||||
{state.currentProject ? <FileTree /> : (
|
||||
<div className="px-3 py-2 text-xs text-zinc-400">select a project</div>
|
||||
<div className="px-3 py-2 text-xs text-ink-subtle">select a project</div>
|
||||
)}
|
||||
</PanelSection>
|
||||
)}
|
||||
@@ -81,7 +71,7 @@ function Shell() {
|
||||
</aside>
|
||||
|
||||
{/* Main workspace */}
|
||||
<main className="flex-1 flex min-w-0 bg-white">
|
||||
<main className="flex-1 flex min-w-0 bg-surface">
|
||||
{state.currentProject && state.openFile ? (
|
||||
<ResizableSplit
|
||||
left={<EditorView />}
|
||||
@@ -103,7 +93,7 @@ function Shell() {
|
||||
<div className="flex-1">
|
||||
<EmptyState message="Open a file from the sidebar." hint=".typ files are listed under the Files section." />
|
||||
</div>
|
||||
<div className="w-96 border-l border-zinc-200">
|
||||
<div className="w-96 border-l border-line">
|
||||
<Preview />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user