ae0d888ddf
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.
119 lines
4.2 KiB
TypeScript
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>
|
|
);
|
|
}
|