Files
typst-leaf/typst-leaf-frontend/src/App.tsx
T
nav.sikand ae0d888ddf 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.
2026-07-03 13:34:47 +05:30

119 lines
4.2 KiB
TypeScript

import { AppProvider, useApp } from "./app-state";
import { EditorView } from "./components/Editor";
import { FileTree } from "./components/FileTree";
import { GitPanel } from "./components/GitPanel";
import { Preview } from "./components/Preview";
import { ProjectPicker } from "./components/ProjectPicker";
import { TopBar } from "./components/TopBar";
import { StatusBar } from "./components/StatusBar";
import { ToastHost } from "./components/ToastHost";
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-canvas text-ink flex flex-col overflow-hidden">
<TopBar />
{state.error && (
<div className="bg-rose-50 text-rose-700 text-xs px-4 py-1.5 border-b border-rose-200 shrink-0">
{state.error}
</div>
)}
<div className="flex flex-1 min-h-0">
{/* Sidebar */}
<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-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-ink-subtle">select a project</div>
)}
</PanelSection>
)}
{state.activePanel === "git" && <GitPanel />}
{state.activePanel === "settings" && <SettingsPanel />}
</div>
</aside>
{/* Main workspace */}
<main className="flex-1 flex min-w-0 bg-surface">
{state.currentProject && state.openFile ? (
<ResizableSplit
left={<EditorView />}
right={<Preview />}
onResize={() => {
document.dispatchEvent(new CustomEvent("typst-leaf:layout"));
}}
/>
) : (
<div className="flex-1 flex min-h-0">
{!state.currentProject ? (
<div className="flex-1 flex">
<div className="flex-1">
<EmptyState message="Select a project to begin." hint="Use the sidebar to pick or create a project." />
</div>
</div>
) : !state.openFile ? (
<div className="flex-1 flex">
<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-line">
<Preview />
</div>
</div>
) : null}
</div>
)}
</main>
</div>
<StatusBar />
<ToastHost />
</div>
);
}
export default function App() {
return (
<AppProvider>
<Shell />
</AppProvider>
);
}