Dark theme improvements (#226)

This commit is contained in:
Brendan Kellam 2025-03-07 09:23:55 -08:00 committed by GitHub
parent e82c5e454e
commit 2286d94eba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 200 additions and 110 deletions

View file

@ -4,11 +4,11 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { useKeymapExtension } from "@/hooks/useKeymapExtension";
import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam";
import { useSyntaxHighlightingExtension } from "@/hooks/useSyntaxHighlightingExtension";
import { useThemeNormalized } from "@/hooks/useThemeNormalized";
import { search } from "@codemirror/search";
import CodeMirror, { Decoration, DecorationSet, EditorSelection, EditorView, ReactCodeMirrorRef, SelectionRange, StateField, ViewUpdate } from "@uiw/react-codemirror";
import { useEffect, useMemo, useRef, useState } from "react";
import { EditorContextMenu } from "../../components/editorContextMenu";
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
interface CodePreviewProps {
path: string;
@ -119,7 +119,7 @@ export const CodePreview = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [highlightRange, isEditorCreated]);
const { theme } = useThemeNormalized();
const theme = useCodeMirrorTheme();
return (
<ScrollArea className="h-full overflow-auto flex-1">
@ -132,7 +132,7 @@ export const CodePreview = ({
value={source}
extensions={extensions}
readOnly={true}
theme={theme === "dark" ? "dark" : "light"}
theme={theme}
>
{editorRef.current && editorRef.current.view && currentSelection && (
<EditorContextMenu

View file

@ -1,4 +1,4 @@
import { FileHeader } from "@/app/[domain]/components/fireHeader";
import { FileHeader } from "@/app/[domain]/components/fileHeader";
import { TopBar } from "@/app/[domain]/components/topBar";
import { Separator } from '@/components/ui/separator';
import { getFileSource, listRepositories } from '@/lib/server/searchService';

View file

@ -2,7 +2,6 @@
import { ScrollArea } from "@/components/ui/scroll-area";
import { useKeymapExtension } from "@/hooks/useKeymapExtension";
import { useThemeNormalized } from "@/hooks/useThemeNormalized";
import { json, jsonLanguage, jsonParseLinter } from "@codemirror/lang-json";
import { linter } from "@codemirror/lint";
import { EditorView, hoverTooltip } from "@codemirror/view";
@ -14,6 +13,7 @@ import { Schema } from "ajv";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { CodeHostType } from "@/lib/utils";
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
export type QuickActionFn<T> = (previous: T) => T;
export type QuickAction<T> = {
@ -119,7 +119,7 @@ const ConfigEditor = <T,>(props: ConfigEditorProps<T>, forwardedRef: Ref<ReactCo
);
const keymapExtension = useKeymapExtension(editorRef.current?.view);
const { theme } = useThemeNormalized();
const theme = useCodeMirrorTheme();
// ⚠️ DISGUSTING HACK AHEAD ⚠️
// Background: When navigating to the /connections/:id?tab=settings page, we were hitting a 500 error with the following
@ -251,7 +251,7 @@ const ConfigEditor = <T,>(props: ConfigEditorProps<T>, forwardedRef: Ref<ReactCo
customAutocompleteStyle,
...jsonSchemaExtensions,
]}
theme={theme === "dark" ? "dark" : "light"}
theme={theme}
/>
</ScrollArea>
<div

View file

@ -23,7 +23,6 @@ export const FileHeader = ({
branchDisplayName,
branchDisplayTitle,
}: FileHeaderProps) => {
const info = getRepoCodeHostInfo(repo);
return (
@ -47,7 +46,7 @@ export const FileHeader = ({
</Link>
{branchDisplayName && (
<p
className="text-xs font-semibold text-gray-500 dark:text-gray-400 mt-0.5 flex items-center gap-0.5"
className="text-xs font-semibold text-gray-500 dark:text-gray-400 mt-[3px] flex items-center gap-0.5"
title={branchDisplayTitle}
>
{/* hack since to make the @ symbol look more centered with the text */}
@ -64,7 +63,9 @@ export const FileHeader = ({
</p>
)}
<span>·</span>
<div className="flex-1 flex items-center overflow-hidden">
<div
className="flex-1 flex items-center overflow-hidden mt-0.5"
>
<span className="inline-block w-full truncate-start font-mono text-sm">
{!fileNameHighlightRange ?
fileName

View file

@ -3,9 +3,9 @@
import { EditorContextMenu } from "@/app/[domain]/components/editorContextMenu";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
import { useKeymapExtension } from "@/hooks/useKeymapExtension";
import { useSyntaxHighlightingExtension } from "@/hooks/useSyntaxHighlightingExtension";
import { useThemeNormalized } from "@/hooks/useThemeNormalized";
import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension";
import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension";
import { SearchResultFileMatch } from "@/lib/types";
@ -44,8 +44,8 @@ export const CodePreview = ({
}: CodePreviewProps) => {
const editorRef = useRef<ReactCodeMirrorRef>(null);
const { theme } = useThemeNormalized();
const [gutterWidth, setGutterWidth] = useState(0);
const theme = useCodeMirrorTheme();
const keymapExtension = useKeymapExtension(editorRef.current?.view);
const syntaxHighlighting = useSyntaxHighlightingExtension(file?.language ?? '', editorRef.current?.view);
@ -106,7 +106,7 @@ export const CodePreview = ({
return (
<div className="flex flex-col h-full">
<div className="flex flex-row bg-cyan-200 dark:bg-cyan-900 items-center justify-between pr-3 py-0.5">
<div className="flex flex-row bg-accent items-center justify-between pr-3 py-0.5 mt-7">
{/* Gutter icon */}
<div className="flex flex-row">
@ -178,8 +178,8 @@ export const CodePreview = ({
className="relative"
readOnly={true}
value={file?.content}
theme={theme === "dark" ? "dark" : "light"}
extensions={extensions}
theme={theme}
>
{
editorRef.current?.view &&

View file

@ -6,7 +6,7 @@ import { useQuery } from "@tanstack/react-query";
import { CodePreview, CodePreviewFile } from "./codePreview";
import { SearchResultFile } from "@/lib/types";
import { useDomain } from "@/hooks/useDomain";
import { SymbolIcon } from "@radix-ui/react-icons";
interface CodePreviewPanelProps {
fileMatch?: SearchResultFile;
onClose: () => void;
@ -24,7 +24,7 @@ export const CodePreviewPanel = ({
}: CodePreviewPanelProps) => {
const domain = useDomain();
const { data: file } = useQuery({
const { data: file, isLoading } = useQuery({
queryKey: ["source", fileMatch?.FileName, fileMatch?.Repository, fileMatch?.Branches],
queryFn: async (): Promise<CodePreviewFile | undefined> => {
if (!fileMatch) {
@ -88,6 +88,13 @@ export const CodePreviewPanel = ({
enabled: fileMatch !== undefined,
});
if (isLoading) {
return <div className="flex flex-col items-center justify-center h-full">
<SymbolIcon className="h-6 w-6 animate-spin" />
<p className="font-semibold text-center">Loading...</p>
</div>
}
return (
<CodePreview
file={file}

View file

@ -46,7 +46,7 @@ export const Entry = ({
)}
<p className="overflow-hidden text-ellipsis whitespace-nowrap">{displayName}</p>
</div>
<div className="px-2 py-0.5 bg-gray-100 dark:bg-gray-800 text-sm rounded-md">
<div className="px-2 py-0.5 bg-accent text-sm rounded-md">
{countText}
</div>
</div>

View file

@ -3,13 +3,11 @@
import { getCodemirrorLanguage } from "@/lib/codemirrorLanguage";
import { lineOffsetExtension } from "@/lib/extensions/lineOffsetExtension";
import { SearchResultRange } from "@/lib/types";
import { defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { EditorState, StateField, Transaction } from "@codemirror/state";
import { defaultLightThemeOption, oneDarkHighlightStyle, oneDarkTheme } from "@uiw/react-codemirror";
import { Decoration, DecorationSet, EditorView, lineNumbers } from "@codemirror/view";
import { useMemo, useRef } from "react";
import { LightweightCodeMirror, CodeMirrorRef } from "./lightweightCodeMirror";
import { useThemeNormalized } from "@/hooks/useThemeNormalized";
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
const markDecoration = Decoration.mark({
class: "cm-searchMatch-selected"
@ -29,19 +27,13 @@ export const CodePreview = ({
lineOffset,
}: CodePreviewProps) => {
const editorRef = useRef<CodeMirrorRef>(null);
const { theme } = useThemeNormalized();
const theme = useCodeMirrorTheme();
const extensions = useMemo(() => {
const codemirrorExtension = getCodemirrorLanguage(language);
return [
EditorView.editable.of(false),
...(theme === 'dark' ? [
syntaxHighlighting(oneDarkHighlightStyle),
oneDarkTheme,
] : [
syntaxHighlighting(defaultHighlightStyle),
defaultLightThemeOption,
]),
theme,
lineNumbers(),
lineOffsetExtension(lineOffset),
codemirrorExtension ? codemirrorExtension : [],

View file

@ -1,6 +1,6 @@
'use client';
import { FileHeader } from "@/app/[domain]/components/fireHeader";
import { FileHeader } from "@/app/[domain]/components/fileHeader";
import { Separator } from "@/components/ui/separator";
import { Repository, SearchResultFile } from "@/lib/types";
import { DoubleArrowDownIcon, DoubleArrowUpIcon } from "@radix-ui/react-icons";
@ -17,6 +17,7 @@ interface FileMatchContainerProps {
onShowAllMatchesButtonClicked: () => void;
isBranchFilteringEnabled: boolean;
repoMetadata: Record<string, Repository>;
yOffset: number;
}
export const FileMatchContainer = ({
@ -27,6 +28,7 @@ export const FileMatchContainer = ({
onShowAllMatchesButtonClicked,
isBranchFilteringEnabled,
repoMetadata,
yOffset,
}: FileMatchContainerProps) => {
const matchCount = useMemo(() => {
@ -92,7 +94,10 @@ export const FileMatchContainer = ({
<div>
{/* Title */}
<div
className="top-0 bg-cyan-200 dark:bg-cyan-900 primary-foreground px-2 py-0.5 flex flex-row items-center justify-between cursor-pointer"
className="bg-accent primary-foreground px-2 py-0.5 flex flex-row items-center justify-between cursor-pointer sticky top-0 z-10"
style={{
top: `-${yOffset}px`,
}}
onClick={() => {
onOpenFile();
}}
@ -119,7 +124,7 @@ export const FileMatchContainer = ({
}}
/>
{(index !== matches.length - 1 || isMoreContentButtonVisible) && (
<Separator className="dark:bg-gray-400" />
<Separator className="bg-accent" />
)}
</div>
))}

View file

@ -124,36 +124,40 @@ export const SearchResultsPanel = ({
position: "relative",
}}
>
{virtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.key}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
}}
>
<FileMatchContainer
file={fileMatches[virtualRow.index]}
onOpenFile={() => {
onOpenFileMatch(fileMatches[virtualRow.index]);
{virtualizer.getVirtualItems().map((virtualRow) => {
const file = fileMatches[virtualRow.index];
return (
<div
key={virtualRow.key}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
transform: `translateY(${virtualRow.start}px)`,
top: 0,
left: 0,
width: '100%',
}}
onMatchIndexChanged={(matchIndex) => {
onMatchIndexChanged(matchIndex);
}}
showAllMatches={showAllMatchesStates[virtualRow.index]}
onShowAllMatchesButtonClicked={() => {
onShowAllMatchesButtonClicked(virtualRow.index);
}}
isBranchFilteringEnabled={isBranchFilteringEnabled}
repoMetadata={repoMetadata}
/>
</div>
))}
>
<FileMatchContainer
file={file}
onOpenFile={() => {
onOpenFileMatch(file);
}}
onMatchIndexChanged={(matchIndex) => {
onMatchIndexChanged(matchIndex);
}}
showAllMatches={showAllMatchesStates[virtualRow.index]}
onShowAllMatchesButtonClicked={() => {
onShowAllMatchesButtonClicked(virtualRow.index);
}}
isBranchFilteringEnabled={isBranchFilteringEnabled}
repoMetadata={repoMetadata}
yOffset={virtualRow.start}
/>
</div>
)
})}
</div>
{isLoadMoreButtonVisible && (
<div className="p-3">

View file

@ -2,7 +2,7 @@
import { EditorState, Extension, StateEffect } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
interface CodeMirrorProps {
value?: string;
@ -29,14 +29,14 @@ const LightweightCodeMirror = forwardRef<CodeMirrorRef, CodeMirrorProps>(({
className,
}, ref) => {
const editor = useRef<HTMLDivElement | null>(null);
const [view, setView] = useState<EditorView>();
const [state, setState] = useState<EditorState>();
const viewRef = useRef<EditorView>();
const stateRef = useRef<EditorState>();
useImperativeHandle(ref, () => ({
editor: editor.current,
state,
view,
}), [editor, state, view]);
state: stateRef.current,
view: viewRef.current,
}), []);
useEffect(() => {
if (!editor.current) {
@ -47,31 +47,26 @@ const LightweightCodeMirror = forwardRef<CodeMirrorRef, CodeMirrorProps>(({
extensions: [], /* extensions are explicitly left out here */
doc: value,
});
setState(state);
stateRef.current = state;
const view = new EditorView({
state,
parent: editor.current,
});
setView(view);
// console.debug(`[CM] Editor created.`);
viewRef.current = view;
return () => {
view.destroy();
setView(undefined);
setState(undefined);
// console.debug(`[CM] Editor destroyed.`);
viewRef.current = undefined;
stateRef.current = undefined;
}
}, [value]);
useEffect(() => {
if (view) {
view.dispatch({ effects: StateEffect.reconfigure.of(extensions ?? []) });
// console.debug(`[CM] Editor reconfigured.`);
if (viewRef.current) {
viewRef.current.dispatch({ effects: StateEffect.reconfigure.of(extensions ?? []) });
}
}, [extensions, view]);
}, [extensions]);
return (
<div

View file

@ -10,8 +10,8 @@ import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam";
import { useSearchHistory } from "@/hooks/useSearchHistory";
import { Repository, SearchQueryParams, SearchResultFile } from "@/lib/types";
import { createPathWithQueryParams } from "@/lib/utils";
import { SymbolIcon } from "@radix-ui/react-icons";
import { createPathWithQueryParams, measure } from "@/lib/utils";
import { InfoCircledIcon, SymbolIcon } from "@radix-ui/react-icons";
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
@ -47,14 +47,19 @@ const SearchPageInternal = () => {
const { data: searchResponse, isLoading } = useQuery({
queryKey: ["search", searchQuery, maxMatchDisplayCount],
queryFn: () => search({
queryFn: () => measure(() => search({
query: searchQuery,
maxMatchDisplayCount,
}, domain),
}, domain), "client.search"),
select: ({ data, durationMs }) => ({
...data,
durationMs,
}),
enabled: searchQuery.length > 0,
refetchOnWindowFocus: false,
});
// Write the query to the search history
useEffect(() => {
if (searchQuery.length === 0) {
@ -136,7 +141,7 @@ const SearchPageInternal = () => {
return {
fileMatches: searchResponse.Result.Files ?? [],
searchDurationMs: Math.round(searchResponse.Result.Duration / 1000000),
searchDurationMs: Math.round(searchResponse.durationMs),
totalMatchCount: searchResponse.Result.MatchCount,
isBranchFilteringEnabled: searchResponse.isBranchFilteringEnabled,
repoUrlTemplates: searchResponse.Result.RepoURLs,
@ -160,12 +165,12 @@ const SearchPageInternal = () => {
}, [fileMatches]);
const onLoadMoreResults = useCallback(() => {
const url = createPathWithQueryParams('/search',
const url = createPathWithQueryParams(`/${domain}/search`,
[SearchQueryParams.query, searchQuery],
[SearchQueryParams.maxMatchDisplayCount, `${maxMatchDisplayCount * 2}`],
)
router.push(url);
}, [maxMatchDisplayCount, router, searchQuery]);
}, [maxMatchDisplayCount, router, searchQuery, domain]);
return (
<div className="flex flex-col h-screen overflow-clip">
@ -176,26 +181,6 @@ const SearchPageInternal = () => {
domain={domain}
/>
<Separator />
{!isLoading && (
<div className="bg-accent py-1 px-2 flex flex-row items-center gap-4">
{
fileMatches.length > 0 ? (
<p className="text-sm font-medium">{`[${searchDurationMs} ms] Found ${numMatches} matches in ${fileMatches.length} ${fileMatches.length > 1 ? 'files' : 'file'}`}</p>
) : (
<p className="text-sm font-medium">No results</p>
)
}
{isMoreResultsButtonVisible && (
<div
className="cursor-pointer text-blue-500 text-sm hover:underline"
onClick={onLoadMoreResults}
>
(load more)
</div>
)}
</div>
)}
<Separator />
</div>
{isLoading ? (
@ -211,6 +196,8 @@ const SearchPageInternal = () => {
isBranchFilteringEnabled={isBranchFilteringEnabled}
repoUrlTemplates={repoUrlTemplates}
repoMetadata={repoMetadata ?? {}}
searchDurationMs={searchDurationMs}
numMatches={numMatches}
/>
)}
</div>
@ -224,6 +211,8 @@ interface PanelGroupProps {
isBranchFilteringEnabled: boolean;
repoUrlTemplates: Record<string, string>;
repoMetadata: Record<string, Repository>;
searchDurationMs: number;
numMatches: number;
}
const PanelGroup = ({
@ -233,6 +222,8 @@ const PanelGroup = ({
isBranchFilteringEnabled,
repoUrlTemplates,
repoMetadata,
searchDurationMs,
numMatches,
}: PanelGroupProps) => {
const [selectedMatchIndex, setSelectedMatchIndex] = useState(0);
const [selectedFile, setSelectedFile] = useState<SearchResultFile | undefined>(undefined);
@ -272,7 +263,7 @@ const PanelGroup = ({
/>
</ResizablePanel>
<ResizableHandle
className="bg-accent w-1 transition-colors delay-50 data-[resize-handle-state=drag]:bg-accent-foreground data-[resize-handle-state=hover]:bg-accent-foreground"
className="w-[1px] bg-accent transition-colors delay-50 data-[resize-handle-state=drag]:bg-accent-foreground data-[resize-handle-state=hover]:bg-accent-foreground"
/>
{/* ~~ Search results ~~ */}
@ -281,6 +272,24 @@ const PanelGroup = ({
id={'search-results-panel'}
order={2}
>
<div className="py-1 px-2 flex flex-row items-center">
<InfoCircledIcon className="w-4 h-4 mr-2" />
{
fileMatches.length > 0 ? (
<p className="text-sm font-medium">{`[${searchDurationMs} ms] Found ${numMatches} matches in ${fileMatches.length} ${fileMatches.length > 1 ? 'files' : 'file'}`}</p>
) : (
<p className="text-sm font-medium">No results</p>
)
}
{isMoreResultsButtonVisible && (
<div
className="cursor-pointer text-blue-500 text-sm hover:underline ml-4"
onClick={onLoadMoreResults}
>
(load more)
</div>
)}
</div>
{filteredFileMatches.length > 0 ? (
<SearchResultsPanel
fileMatches={filteredFileMatches}
@ -302,7 +311,7 @@ const PanelGroup = ({
)}
</ResizablePanel>
<ResizableHandle
withHandle={selectedFile !== undefined}
className="mt-7 w-[1px] bg-accent transition-colors delay-50 data-[resize-handle-state=drag]:bg-accent-foreground data-[resize-handle-state=hover]:bg-accent-foreground"
/>
{/* ~~ Code preview ~~ */}

View file

@ -3,19 +3,18 @@
import { NEXT_PUBLIC_DOMAIN_SUB_PATH } from "@/lib/environment.client";
import { fileSourceResponseSchema, listRepositoriesResponseSchema, searchResponseSchema } from "@/lib/schemas";
import { FileSourceRequest, FileSourceResponse, ListRepositoriesResponse, SearchRequest, SearchResponse } from "@/lib/types";
import { measure } from "@/lib/utils";
import assert from "assert";
export const search = async (body: SearchRequest, domain: string): Promise<SearchResponse> => {
const path = resolveServerPath("/api/search");
const { data: result } = await measure(() => fetch(path, {
const result = await fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Org-Domain": domain,
},
body: JSON.stringify(body),
}).then(response => response.json()), "client.search");
}).then(response => response.json());
return searchResponseSchema.parse(result);
}

View file

@ -0,0 +1,78 @@
'use client';
import { useTailwind } from "./useTailwind";
import { useMemo } from "react";
import { useThemeNormalized } from "./useThemeNormalized";
import createTheme from "@uiw/codemirror-themes";
import { defaultLightThemeOption } from "@uiw/react-codemirror";
import { tags as t } from '@lezer/highlight';
import { syntaxHighlighting } from "@codemirror/language";
import { defaultHighlightStyle } from "@codemirror/language";
// From: https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts
const chalky = "#e5c07b",
coral = "#e06c75",
cyan = "#56b6c2",
invalid = "#ffffff",
ivory = "#abb2bf",
stone = "#7d8799",
malibu = "#61afef",
sage = "#98c379",
whiskey = "#d19a66",
violet = "#c678dd",
highlightBackground = "#2c313a",
background = "#282c34",
selection = "#3E4451",
cursor = "#528bff";
export const useCodeMirrorTheme = () => {
const tailwind = useTailwind();
const { theme } = useThemeNormalized();
const darkTheme = useMemo(() => {
return createTheme({
theme: 'dark',
settings: {
background: tailwind.theme.colors.background,
foreground: ivory,
caret: cursor,
selection: selection,
selectionMatch: "#aafe661a", // for matching selections
gutterBackground: background,
gutterForeground: stone,
gutterBorder: 'none',
gutterActiveForeground: ivory,
lineHighlight: highlightBackground,
},
styles: [
{ tag: t.comment, color: stone },
{ tag: t.keyword, color: violet },
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral },
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
{ tag: [t.definition(t.name), t.separator], color: ivory },
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky },
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan },
{ tag: [t.meta], color: stone },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.link, color: stone, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: coral },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
{ tag: t.invalid, color: invalid }
]
});
}, []);
const cmTheme = useMemo(() => {
return theme === 'dark' ? darkTheme : [
defaultLightThemeOption,
syntaxHighlighting(defaultHighlightStyle),
]
}, [theme]);
return cmTheme;
}