mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-11 07:56:50 +02:00
fix: improve repository tree visualization UX
- Collapse directories by default in file tree - Hide files section on tree loading error - Add code-like skeleton loader with file header - Fix syntax highlight size jump between loading/loaded states - Replace purple accent with grayscale theme - Preload Rust and Markdown languages for reliable highlighting
This commit is contained in:
@@ -29,42 +29,39 @@ export function SyntaxHighlight({
|
||||
}: SyntaxHighlightProps) {
|
||||
const { html, loading, error } = useHighlightedCode(code, language);
|
||||
|
||||
// Use consistent wrapper structure for all states to avoid size jumps
|
||||
const wrapperClasses = cn(
|
||||
"shiki-container overflow-x-auto max-w-full [&_pre]:!bg-transparent [&_pre]:!m-0 [&_code]:text-xs [&_code]:font-mono",
|
||||
showLineNumbers && "line-numbers",
|
||||
className,
|
||||
);
|
||||
|
||||
// Loading state - show code without highlighting
|
||||
if (loading) {
|
||||
return (
|
||||
<pre
|
||||
className={cn(
|
||||
"shiki-loading overflow-x-auto max-w-full font-mono text-xs",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<code className="text-foreground/70">{code}</code>
|
||||
</pre>
|
||||
<div className={cn(wrapperClasses, "shiki-loading")}>
|
||||
<pre className="!bg-transparent !m-0">
|
||||
<code className="text-foreground/70">{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Error state - fallback to plain code
|
||||
if (error || !html) {
|
||||
return (
|
||||
<pre
|
||||
className={cn(
|
||||
"overflow-x-auto max-w-full font-mono text-xs",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
<div className={wrapperClasses}>
|
||||
<pre className="!bg-transparent !m-0">
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Render highlighted HTML
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"shiki-container overflow-x-auto max-w-full [&_pre]:!bg-transparent [&_code]:text-xs [&_code]:font-mono",
|
||||
showLineNumbers && "line-numbers",
|
||||
className,
|
||||
)}
|
||||
className={wrapperClasses}
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -97,23 +97,9 @@ export function RepositoryFilesSection({
|
||||
);
|
||||
}
|
||||
|
||||
// Error state
|
||||
// Error state - silently hide section
|
||||
if (treeError) {
|
||||
return (
|
||||
<section className={cn("flex flex-col gap-4", className)}>
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<FolderGit2 className="size-5" />
|
||||
Files
|
||||
</h2>
|
||||
<div className="flex items-start gap-3 p-4 bg-destructive/10 border border-destructive/20 rounded text-sm">
|
||||
<AlertCircle className="size-5 text-destructive flex-shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="font-medium">Unable to load repository files</p>
|
||||
<p className="text-muted-foreground text-xs">{treeError.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// No tree available
|
||||
@@ -147,8 +133,22 @@ export function RepositoryFilesSection({
|
||||
<div className="border border-border rounded max-h-96 overflow-auto bg-muted/20">
|
||||
{selectedFile ? (
|
||||
contentLoading ? (
|
||||
<div className="p-4">
|
||||
<Skeleton className="h-48" />
|
||||
<div className="relative">
|
||||
<div className="sticky top-0 bg-muted/80 backdrop-blur-sm px-3 py-1.5 border-b border-border/50 flex items-center justify-between">
|
||||
<span className="text-xs font-mono text-muted-foreground truncate">
|
||||
{selectedFile.path}
|
||||
</span>
|
||||
</div>
|
||||
<div className="p-3 space-y-2">
|
||||
<Skeleton className="h-3 w-3/4" />
|
||||
<Skeleton className="h-3 w-1/2" />
|
||||
<Skeleton className="h-3 w-5/6" />
|
||||
<Skeleton className="h-3 w-2/3" />
|
||||
<Skeleton className="h-3 w-4/5" />
|
||||
<Skeleton className="h-3 w-1/3" />
|
||||
<Skeleton className="h-3 w-3/4" />
|
||||
<Skeleton className="h-3 w-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
) : contentError ? (
|
||||
<div className="flex items-start gap-3 p-4 text-sm">
|
||||
|
||||
@@ -153,7 +153,7 @@ function TreeNode({
|
||||
selectedPath,
|
||||
depth,
|
||||
}: TreeNodeProps) {
|
||||
const [isOpen, setIsOpen] = useState(depth === 0);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const isSelected = selectedPath === path;
|
||||
|
||||
const handleClick = () => {
|
||||
|
||||
@@ -11,8 +11,8 @@ let highlighterPromise: Promise<HighlighterCore> | null = null;
|
||||
const loadedLanguages = new Set<string>();
|
||||
|
||||
/**
|
||||
* Grimoire dark theme matching current minimalistic Prism styles
|
||||
* Uses muted colors with primary accent for keywords/functions
|
||||
* Grimoire dark theme - minimalistic grayscale with semantic colors
|
||||
* Uses muted grays for syntax with color only for diff semantics
|
||||
*/
|
||||
const grimoireTheme: ThemeRegistration = {
|
||||
name: "grimoire-dark",
|
||||
@@ -32,7 +32,7 @@ const grimoireTheme: ThemeRegistration = {
|
||||
scope: ["string", "string.quoted"],
|
||||
settings: { foreground: "#9ca3af" },
|
||||
},
|
||||
// Keywords, operators - primary color
|
||||
// Keywords, operators - emphasized gray
|
||||
{
|
||||
scope: [
|
||||
"keyword",
|
||||
@@ -42,14 +42,14 @@ const grimoireTheme: ThemeRegistration = {
|
||||
"keyword.operator",
|
||||
"keyword.control",
|
||||
],
|
||||
settings: { foreground: "#a855f7" },
|
||||
settings: { foreground: "#d4d4d4" },
|
||||
},
|
||||
// Functions, methods - primary bold
|
||||
// Functions, methods - foreground bold
|
||||
{
|
||||
scope: ["entity.name.function", "support.function", "meta.function-call"],
|
||||
settings: { foreground: "#a855f7", fontStyle: "bold" },
|
||||
settings: { foreground: "#e5e5e5", fontStyle: "bold" },
|
||||
},
|
||||
// Classes, types - primary bold
|
||||
// Classes, types - foreground bold
|
||||
{
|
||||
scope: [
|
||||
"entity.name.class",
|
||||
@@ -57,9 +57,9 @@ const grimoireTheme: ThemeRegistration = {
|
||||
"support.class",
|
||||
"support.type",
|
||||
],
|
||||
settings: { foreground: "#a855f7", fontStyle: "bold" },
|
||||
settings: { foreground: "#e5e5e5", fontStyle: "bold" },
|
||||
},
|
||||
// Numbers, constants - primary
|
||||
// Numbers, constants - emphasized gray
|
||||
{
|
||||
scope: [
|
||||
"constant",
|
||||
@@ -67,7 +67,7 @@ const grimoireTheme: ThemeRegistration = {
|
||||
"constant.language",
|
||||
"constant.character",
|
||||
],
|
||||
settings: { foreground: "#a855f7" },
|
||||
settings: { foreground: "#d4d4d4" },
|
||||
},
|
||||
// Variables, parameters - foreground
|
||||
{
|
||||
@@ -91,7 +91,7 @@ const grimoireTheme: ThemeRegistration = {
|
||||
// Tags (HTML/JSX)
|
||||
{
|
||||
scope: ["entity.name.tag", "support.class.component"],
|
||||
settings: { foreground: "#a855f7" },
|
||||
settings: { foreground: "#d4d4d4" },
|
||||
},
|
||||
// JSON keys
|
||||
{
|
||||
@@ -124,7 +124,7 @@ const grimoireTheme: ThemeRegistration = {
|
||||
// Markdown headings
|
||||
{
|
||||
scope: ["markup.heading", "entity.name.section"],
|
||||
settings: { foreground: "#a855f7", fontStyle: "bold" },
|
||||
settings: { foreground: "#e5e5e5", fontStyle: "bold" },
|
||||
},
|
||||
// Markdown bold/italic
|
||||
{
|
||||
@@ -138,7 +138,7 @@ const grimoireTheme: ThemeRegistration = {
|
||||
// Markdown links
|
||||
{
|
||||
scope: ["markup.underline.link"],
|
||||
settings: { foreground: "#a855f7" },
|
||||
settings: { foreground: "#93c5fd" },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -277,6 +277,8 @@ const CORE_LANGUAGES = [
|
||||
"json",
|
||||
"diff",
|
||||
"bash",
|
||||
"rust",
|
||||
"markdown",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
@@ -303,6 +305,8 @@ export async function getHighlighter(): Promise<HighlighterCore> {
|
||||
import("shiki/langs/json.mjs"),
|
||||
import("shiki/langs/diff.mjs"),
|
||||
import("shiki/langs/bash.mjs"),
|
||||
import("shiki/langs/rust.mjs"),
|
||||
import("shiki/langs/markdown.mjs"),
|
||||
],
|
||||
engine: createOnigurumaEngine(import("shiki/wasm")),
|
||||
}).then((hl) => {
|
||||
|
||||
Reference in New Issue
Block a user