feat(playground): add theme selector component and enhance code editor with additional themes and languages

This commit is contained in:
ngc2207 2024-12-15 21:15:19 +08:00
parent 668145a9c7
commit f590d77578
3 changed files with 116 additions and 47 deletions

View File

@ -1,52 +1,87 @@
"use client"; "use client";
import { useRef } from "react";
import "@fontsource-variable/fira-code"; import "@fontsource-variable/fira-code";
import { createHighlighter } from "shiki"; import { createHighlighter } from "shiki";
import type { editor } from "monaco-editor"; import type { editor } from "monaco-editor";
import { shikiToMonaco } from "@shikijs/monaco"; import { shikiToMonaco } from "@shikijs/monaco";
import { useEffect, useRef, useMemo } from "react";
import { useCodeEditorStore } from "@/store/codeEditorStore"; import { useCodeEditorStore } from "@/store/codeEditorStore";
import MonacoEditor, { type Monaco } from "@monaco-editor/react"; import MonacoEditor, { type Monaco } from "@monaco-editor/react";
const ADDITIONAL_THEMES = [
"andromeeda",
"aurora-x",
"ayu-dark",
"catppuccin-frappe",
"catppuccin-latte",
"catppuccin-macchiato",
"catppuccin-mocha",
"dark-plus",
"dracula",
"dracula-soft",
"one-dark-pro",
"vitesse-dark",
"vitesse-light",
];
const ADDITIONAL_LANGUAGES = ["c", "java"] as const satisfies Parameters<
typeof createHighlighter
>[0]["langs"];
export function CodeEditor() { export function CodeEditor() {
const monacoRef = useRef<Monaco | null>(null); const monacoRef = useRef<Monaco | null>(null);
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null); const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const { lang, theme, value, isLigature } = useCodeEditorStore(); const { lang, theme, value, isLigature } = useCodeEditorStore();
useEffect(() => {
if (monacoRef.current && editorRef.current) {
monacoRef.current.editor.setTheme(theme);
}
}, [theme]);
const options = useMemo(
() => ({
minimap: { enabled: false },
fontSize: 14,
fontFamily: "Fira Code Variable, monospace",
tabSize: 4,
showFoldingControls: "always" as const,
fontLigatures: isLigature,
automaticLayout: true,
guides: {
bracketPairs: true,
indentation: true,
},
}),
[isLigature]
);
const handleEditorMount = async (
editor: editor.IStandaloneCodeEditor,
monaco: Monaco
) => {
editorRef.current = editor;
monacoRef.current = monaco;
for (const lang of ADDITIONAL_LANGUAGES) {
monacoRef.current?.languages.register({ id: lang });
}
const highlighter = await createHighlighter({
themes: ADDITIONAL_THEMES,
langs: ADDITIONAL_LANGUAGES,
});
shikiToMonaco(highlighter, monacoRef.current);
};
return ( return (
<MonacoEditor <MonacoEditor
language={lang} language={lang}
theme="vs-dark" theme={theme}
options={{ options={options}
minimap: { enabled: false },
fontSize: 14,
fontFamily: "Fira Code Variable, monospace",
tabSize: 4,
showFoldingControls: "always",
fontLigatures: isLigature,
automaticLayout: true,
}}
value={value} value={value}
onMount={(editor, monaco) => { onMount={handleEditorMount}
editorRef.current = editor;
monacoRef.current = monaco;
void (async () => {
const ADDITIONAL_LANGUAGES = [
"c",
"java",
] as const satisfies Parameters<typeof createHighlighter>[0]["langs"];
for (const lang of ADDITIONAL_LANGUAGES) {
monacoRef.current?.languages.register({ id: lang });
}
const highlighter = await createHighlighter({
themes: [theme],
langs: ADDITIONAL_LANGUAGES,
});
shikiToMonaco(highlighter, monacoRef.current);
})();
}}
/> />
); );
} }

View File

@ -0,0 +1,47 @@
"use client";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useCodeEditorStore } from "@/store/codeEditorStore";
const themeOptions = [
{ value: "andromeeda", label: "Andromeeda" },
{ value: "aurora-x", label: "Aurora X" },
{ value: "ayu-dark", label: "Ayu Dark" },
{ value: "catppuccin-frappe", label: "Catppuccin Frappé" },
{ value: "catppuccin-latte", label: "Catppuccin Latte" },
{ value: "catppuccin-macchiato", label: "Catppuccin Macchiato" },
{ value: "catppuccin-mocha", label: "Catppuccin Mocha" },
{ value: "dark-plus", label: "Dark Plus" },
{ value: "dracula", label: "Dracula Theme" },
{ value: "dracula-soft", label: "Dracula Theme Soft" },
{ value: "one-dark-pro", label: "One Dark Pro" },
{ value: "vitesse-dark", label: "Vitesse Dark" },
{ value: "vitesse-light", label: "Vitesse Light" },
];
export default function ThemeSelector() {
const { theme, setTheme } = useCodeEditorStore();
return (
<div className="space-y-2">
<Select defaultValue={theme} onValueChange={setTheme}>
<SelectTrigger>
<SelectValue placeholder="Select theme" />
</SelectTrigger>
<SelectContent>
{themeOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
);
}

View File

@ -3,14 +3,9 @@ import {
SidebarProvider, SidebarProvider,
SidebarTrigger, SidebarTrigger,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbPage,
} from "@/components/ui/breadcrumb";
import { SessionProvider } from "next-auth/react"; import { SessionProvider } from "next-auth/react";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import ThemeSelector from "./components/theme-selector";
import { ModeSwitcher } from "@/components/mode-switcher"; import { ModeSwitcher } from "@/components/mode-switcher";
import LanguageSwitcher from "@/components/language-switcher"; import LanguageSwitcher from "@/components/language-switcher";
import { PlaygroundSidebar } from "@/app/playground/components/playground-sidebar"; import { PlaygroundSidebar } from "@/app/playground/components/playground-sidebar";
@ -28,15 +23,7 @@ export default function PlaygroundLayout({
<SidebarTrigger /> <SidebarTrigger />
<ModeSwitcher /> <ModeSwitcher />
<Separator orientation="vertical" className="mr-2 h-4" /> <Separator orientation="vertical" className="mr-2 h-4" />
<Breadcrumb> <ThemeSelector />
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbPage className="line-clamp-1">
Project Management & Task Tracking
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div> </div>
<div className="ml-auto px-3"> <div className="ml-auto px-3">
<LanguageSwitcher /> <LanguageSwitcher />