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:
@@ -1,37 +1,28 @@
|
||||
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 statusText = () => {
|
||||
if (!state.currentProject) return "no project";
|
||||
if (!state.openFile) return "open a file";
|
||||
if (lsp.status !== "ready") return `LSP ${lsp.status}`;
|
||||
if (!lsp.previewReady) return "starting…";
|
||||
return "live";
|
||||
};
|
||||
|
||||
const statusColor = () => {
|
||||
if (lsp.previewReady) return "text-emerald-600 bg-emerald-50";
|
||||
if (lsp.status === "ready") return "text-zinc-400 bg-stone-50";
|
||||
return "text-amber-600 bg-amber-50";
|
||||
};
|
||||
|
||||
const statusDot = () => {
|
||||
if (lsp.previewReady) return "bg-emerald-500";
|
||||
return "bg-zinc-300";
|
||||
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-white">
|
||||
<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-zinc-200 text-xs text-zinc-500 shrink-0">
|
||||
<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 px-2 py-0.5 rounded-full text-[11px] ${statusColor()}`}>
|
||||
<span className={`inline-block size-1.5 rounded-full ${statusDot()}`} />
|
||||
{statusText()}
|
||||
<span className="ml-auto flex items-center gap-1">
|
||||
{statusChip()}
|
||||
</span>
|
||||
{lsp.previewReady && (
|
||||
<button
|
||||
@@ -39,16 +30,17 @@ export function Preview() {
|
||||
onClick={() => {
|
||||
iframeRef.current?.contentWindow?.location.reload();
|
||||
}}
|
||||
className="px-2 py-0.5 rounded border border-zinc-200 hover:bg-stone-100 text-zinc-500"
|
||||
className="p-1 rounded-md text-ink-muted hover:bg-surface-hover transition-colors"
|
||||
aria-label="Reload preview"
|
||||
>
|
||||
Reload
|
||||
<Icon icon={RotateCw} className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
<span className="text-zinc-300 text-[11px] hidden sm:inline">Click preview to jump to source</span>
|
||||
<span className="text-ink-subtle text-[11px] hidden sm:inline">Click preview to jump to source</span>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 bg-zinc-50 relative min-h-0">
|
||||
<div className="flex-1 bg-surface relative min-h-0">
|
||||
{state.currentProject && state.openFile && lsp.status === "ready" && lsp.previewReady ? (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
@@ -58,8 +50,8 @@ export function Preview() {
|
||||
className="absolute inset-0 w-full h-full bg-white"
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-zinc-400 text-sm p-6 gap-2">
|
||||
<span>{statusText()}</span>
|
||||
<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}
|
||||
|
||||
Reference in New Issue
Block a user