mirror of
https://github.com/aljazceru/claude-code-viewer.git
synced 2025-12-27 18:24:23 +01:00
restore theme feature
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
oneLight,
|
||||
} from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { useTheme } from "../../hooks/useTheme";
|
||||
|
||||
interface MarkdownContentProps {
|
||||
content: string;
|
||||
@@ -16,7 +17,7 @@ export const MarkdownContent: FC<MarkdownContentProps> = ({
|
||||
content,
|
||||
className = "",
|
||||
}) => {
|
||||
const resolvedTheme = "light" as "light" | "dark"; // TODO: 設定から取り出す
|
||||
const { resolvedTheme } = useTheme();
|
||||
const syntaxTheme = resolvedTheme === "dark" ? oneDark : oneLight;
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import type { ToolResultContent } from "@/lib/conversation-schema/content/ToolResultContentSchema";
|
||||
import type { AssistantMessageContent } from "@/lib/conversation-schema/message/AssistantMessageSchema";
|
||||
import { Button } from "../../../../../../../components/ui/button";
|
||||
import { useTheme } from "../../../../../../../hooks/useTheme";
|
||||
import type { SidechainConversation } from "../../../../../../../lib/conversation-schema";
|
||||
import { MarkdownContent } from "../../../../../../components/MarkdownContent";
|
||||
import { SidechainConversationModal } from "../conversationModal/SidechainConversationModal";
|
||||
@@ -38,7 +39,7 @@ export const AssistantConversationContent: FC<{
|
||||
getSidechainConversationByPrompt,
|
||||
getSidechainConversations,
|
||||
}) => {
|
||||
const resolvedTheme = "light" as "light" | "dark"; // TODO: 設定から取り出す
|
||||
const { resolvedTheme } = useTheme();
|
||||
const syntaxTheme = resolvedTheme === "dark" ? oneDark : oneLight;
|
||||
if (content.type === "text") {
|
||||
return (
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useTheme } from "@/hooks/useTheme";
|
||||
import { projectDetailQuery, projectListQuery } from "../lib/api/queries";
|
||||
|
||||
interface SettingsControlsProps {
|
||||
@@ -32,7 +33,7 @@ export const SettingsControls: FC<SettingsControlsProps> = ({
|
||||
const themeId = useId();
|
||||
const { config, updateConfig } = useConfig();
|
||||
const queryClient = useQueryClient();
|
||||
const theme = "system"; // TODO: 設定から取り出す
|
||||
const { theme } = useTheme();
|
||||
const { i18n } = useLingui();
|
||||
|
||||
const handleHideNoUserMessageChange = async () => {
|
||||
@@ -98,6 +99,14 @@ export const SettingsControls: FC<SettingsControlsProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const handleThemeChange = async (value: "light" | "dark" | "system") => {
|
||||
const newConfig = {
|
||||
...config,
|
||||
theme: value,
|
||||
};
|
||||
updateConfig(newConfig);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`space-y-4 ${className}`}>
|
||||
<div className="flex items-center space-x-2">
|
||||
@@ -298,12 +307,7 @@ export const SettingsControls: FC<SettingsControlsProps> = ({
|
||||
<Trans id="settings.theme" message="Theme" />
|
||||
</label>
|
||||
)}
|
||||
<Select
|
||||
value={theme || "system"}
|
||||
onValueChange={() => {
|
||||
// TODO: 設定を更新する
|
||||
}}
|
||||
>
|
||||
<Select value={theme ?? "system"} onValueChange={handleThemeChange}>
|
||||
<SelectTrigger id={themeId} className="w-full">
|
||||
<SelectValue placeholder={i18n._("Select theme")} />
|
||||
</SelectTrigger>
|
||||
|
||||
14
src/components/ThemeProvider.tsx
Normal file
14
src/components/ThemeProvider.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { type FC, type PropsWithChildren, useEffect } from "react";
|
||||
import { useTheme } from "../hooks/useTheme";
|
||||
|
||||
export const ThemeProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement;
|
||||
root.classList.remove("light", "dark");
|
||||
root.classList.add(resolvedTheme);
|
||||
}, [resolvedTheme]);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||
import { useTheme } from "../../hooks/useTheme";
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const theme = "system"; // TODO: 設定から取り出す
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
|
||||
22
src/hooks/useTheme.ts
Normal file
22
src/hooks/useTheme.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useMemo } from "react";
|
||||
import { useConfig } from "../app/hooks/useConfig";
|
||||
|
||||
type ResolvedTheme = "light" | "dark";
|
||||
|
||||
export const useTheme = () => {
|
||||
const { config } = useConfig();
|
||||
const resolvedTheme = useMemo<ResolvedTheme>(() => {
|
||||
if (config?.theme === "light" || config?.theme === "dark") {
|
||||
return config?.theme;
|
||||
}
|
||||
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
}, [config?.theme]);
|
||||
|
||||
return {
|
||||
theme: config?.theme ?? "system",
|
||||
resolvedTheme,
|
||||
};
|
||||
};
|
||||
@@ -1,36 +1,18 @@
|
||||
import {
|
||||
isServer,
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
} from "@tanstack/react-query";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import type { FC, PropsWithChildren } from "react";
|
||||
|
||||
let browserQueryClient: QueryClient | undefined;
|
||||
|
||||
export const getQueryClient = () => {
|
||||
if (isServer) {
|
||||
return makeQueryClient();
|
||||
} else {
|
||||
browserQueryClient ??= makeQueryClient();
|
||||
return browserQueryClient;
|
||||
}
|
||||
};
|
||||
|
||||
export const makeQueryClient = () =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: true,
|
||||
retry: false,
|
||||
},
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: true,
|
||||
retry: false,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const QueryClientProviderWrapper: FC<PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const queryClient = getQueryClient();
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import ReactDOM from "react-dom/client";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
|
||||
import "./styles.css";
|
||||
import { QueryClientProviderWrapper } from "./lib/api/QueryClientProviderWrapper";
|
||||
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
@@ -26,7 +27,9 @@ if (rootElement && !rootElement.innerHTML) {
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
<QueryClientProviderWrapper>
|
||||
<RouterProvider router={router} />
|
||||
</QueryClientProviderWrapper>
|
||||
</StrictMode>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ import { createRootRoute, Outlet } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
|
||||
import { RootErrorBoundary } from "../app/components/RootErrorBoundary";
|
||||
import { SSEEventListeners } from "../app/components/SSEEventListeners";
|
||||
import { ThemeProvider } from "../components/ThemeProvider";
|
||||
import { Toaster } from "../components/ui/sonner";
|
||||
import { QueryClientProviderWrapper } from "../lib/api/QueryClientProviderWrapper";
|
||||
import { LinguiClientProvider } from "../lib/i18n/LinguiProvider";
|
||||
import { SSEProvider } from "../lib/sse/components/SSEProvider";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: () => (
|
||||
<RootErrorBoundary>
|
||||
<QueryClientProviderWrapper>
|
||||
<ThemeProvider>
|
||||
<LinguiClientProvider>
|
||||
<SSEProvider>
|
||||
<SSEEventListeners>
|
||||
@@ -30,7 +30,7 @@ export const Route = createRootRoute({
|
||||
</SSEEventListeners>
|
||||
</SSEProvider>
|
||||
</LinguiClientProvider>
|
||||
</QueryClientProviderWrapper>
|
||||
</ThemeProvider>
|
||||
<Toaster position="top-right" />
|
||||
</RootErrorBoundary>
|
||||
),
|
||||
|
||||
@@ -9,6 +9,7 @@ const LayerImpl = Effect.gen(function* () {
|
||||
enterKeyBehavior: "shift-enter-send",
|
||||
permissionMode: "default",
|
||||
locale: "ja",
|
||||
theme: "system",
|
||||
});
|
||||
|
||||
const setUserConfig = (newConfig: UserConfig) =>
|
||||
|
||||
@@ -19,6 +19,7 @@ export const configMiddleware = createMiddleware<HonoContext>(
|
||||
enterKeyBehavior: "shift-enter-send",
|
||||
permissionMode: "default",
|
||||
locale: "ja",
|
||||
theme: "system",
|
||||
} satisfies UserConfig),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const userConfigSchema = z.object({
|
||||
.optional()
|
||||
.default("default"),
|
||||
locale: localeSchema.optional().default("en"),
|
||||
theme: z.enum(["light", "dark", "system"]).optional().default("system"),
|
||||
});
|
||||
|
||||
export type UserConfig = z.infer<typeof userConfigSchema>;
|
||||
|
||||
@@ -62,6 +62,7 @@ export const testPlatformLayer = (overrides?: {
|
||||
overrides?.userConfig?.enterKeyBehavior ?? "shift-enter-send",
|
||||
permissionMode: overrides?.userConfig?.permissionMode ?? "default",
|
||||
locale: overrides?.userConfig?.locale ?? "ja",
|
||||
theme: overrides?.userConfig?.theme ?? "system",
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user