Files
typst-leaf/typst-leaf-frontend/src/components/Preview.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

66 lines
2.4 KiB
TypeScript

import { useRef } from "react";
import { RotateCw } from "lucide-react";
import { useApp } from "../app-state";
import { Chip } from "./ui/Chip";
import { Icon } from "./ui/Icon";
export function Preview() {
const { state, lsp } = useApp();
const iframeRef = useRef<HTMLIFrameElement | null>(null);
const statusChip = () => {
if (!state.currentProject) return <Chip tone="neutral">no project</Chip>;
if (!state.openFile) return <Chip tone="neutral">open a file</Chip>;
if (lsp.status !== "ready") return <Chip tone="amber" dot dotPulse>LSP {lsp.status}</Chip>;
if (!lsp.previewReady) return <Chip tone="neutral">starting...</Chip>;
return <Chip tone="teal" dot>live</Chip>;
};
return (
<div className="h-full flex flex-col bg-surface">
{/* Toolbar */}
<div className="flex items-center gap-2 px-3 py-1.5 border-b border-line text-xs text-ink-muted shrink-0">
<span className="font-medium">Preview</span>
<span className="ml-auto flex items-center gap-1">
{statusChip()}
</span>
{lsp.previewReady && (
<button
type="button"
onClick={() => {
iframeRef.current?.contentWindow?.location.reload();
}}
className="p-1 rounded-md text-ink-muted hover:bg-surface-hover transition-colors"
aria-label="Reload preview"
>
<Icon icon={RotateCw} className="size-3.5" />
</button>
)}
<span className="text-ink-subtle text-[11px] hidden sm:inline">Click preview to jump to source</span>
</div>
{/* Content */}
<div className="flex-1 bg-surface relative min-h-0">
{state.currentProject && state.openFile && lsp.status === "ready" && lsp.previewReady ? (
<iframe
ref={iframeRef}
key={state.currentProject}
src="/preview-app"
title="Typst preview"
className="absolute inset-0 w-full h-full bg-white"
/>
) : (
<div className="absolute inset-0 flex flex-col items-center justify-center text-ink-subtle text-sm p-6 gap-2">
<span>{statusChip()}</span>
{lsp.status === "error" && lsp.error && (
<pre className="mt-2 max-w-full overflow-auto text-xs text-rose-600 bg-rose-50 p-3 rounded whitespace-pre-wrap break-all">
{lsp.error}
</pre>
)}
</div>
)}
</div>
</div>
);
}