Files
2025-11-30 08:25:37 +08:00

221 lines
5.4 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* TheSys C1 with Custom Theming and Dark Mode
*
* Demonstrates:
* - Custom theme configuration
* - Dark mode toggle
* - System theme detection
* - Theme presets
* - CSS variable overrides
*/
import "@crayonai/react-ui/styles/index.css";
import { C1Chat, ThemeProvider } from "@thesysai/genui-sdk";
import { themePresets } from "@crayonai/react-ui";
import { useState, useEffect } from "react";
import "./App.css";
type ThemeMode = "light" | "dark" | "system";
// Custom theme object
const customLightTheme = {
mode: "light" as const,
colors: {
primary: "#3b82f6",
secondary: "#8b5cf6",
background: "#ffffff",
foreground: "#1f2937",
border: "#e5e7eb",
muted: "#f3f4f6",
accent: "#10b981",
},
fonts: {
body: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
heading: "'Poppins', sans-serif",
mono: "'Fira Code', 'Courier New', monospace",
},
borderRadius: "8px",
spacing: {
base: "16px",
},
};
const customDarkTheme = {
...customLightTheme,
mode: "dark" as const,
colors: {
primary: "#60a5fa",
secondary: "#a78bfa",
background: "#111827",
foreground: "#f9fafb",
border: "#374151",
muted: "#1f2937",
accent: "#34d399",
},
};
function useSystemTheme(): "light" | "dark" {
const [systemTheme, setSystemTheme] = useState<"light" | "dark">(
() =>
window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light"
);
useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handler = (e: MediaQueryListEvent) => {
setSystemTheme(e.matches ? "dark" : "light");
};
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}, []);
return systemTheme;
}
export default function ThemedChat() {
const [themeMode, setThemeMode] = useState<ThemeMode>(
() => (localStorage.getItem("theme-mode") as ThemeMode) || "system"
);
const [usePreset, setUsePreset] = useState(false);
const systemTheme = useSystemTheme();
// Determine actual theme to use
const actualTheme =
themeMode === "system" ? systemTheme : themeMode;
// Choose theme object
const theme = usePreset
? themePresets.candy // Use built-in preset
: actualTheme === "dark"
? customDarkTheme
: customLightTheme;
// Persist theme preference
useEffect(() => {
localStorage.setItem("theme-mode", themeMode);
// Apply to document for app-wide styling
document.documentElement.setAttribute("data-theme", actualTheme);
}, [themeMode, actualTheme]);
return (
<div className="themed-app">
<div className="theme-controls">
<div className="theme-selector">
<h3>Theme Mode</h3>
<div className="button-group">
<button
className={themeMode === "light" ? "active" : ""}
onClick={() => setThemeMode("light")}
>
Light
</button>
<button
className={themeMode === "dark" ? "active" : ""}
onClick={() => setThemeMode("dark")}
>
🌙 Dark
</button>
<button
className={themeMode === "system" ? "active" : ""}
onClick={() => setThemeMode("system")}
>
💻 System
</button>
</div>
</div>
<div className="theme-type">
<h3>Theme Type</h3>
<div className="button-group">
<button
className={!usePreset ? "active" : ""}
onClick={() => setUsePreset(false)}
>
Custom
</button>
<button
className={usePreset ? "active" : ""}
onClick={() => setUsePreset(true)}
>
Preset (Candy)
</button>
</div>
</div>
</div>
<div className="chat-wrapper">
<ThemeProvider theme={{ ...theme, mode: actualTheme }}>
<C1Chat
apiUrl="/api/chat"
agentName="Themed AI Assistant"
logoUrl="https://placehold.co/100x100/3b82f6/ffffff?text=AI"
/>
</ThemeProvider>
</div>
<div className="theme-info">
<h3>Current Theme</h3>
<pre className="theme-preview">
{JSON.stringify(
{
mode: actualTheme,
usingPreset: usePreset,
preferredMode: themeMode,
systemPreference: systemTheme,
},
null,
2
)}
</pre>
</div>
</div>
);
}
/**
* CSS Example (App.css):
*
* [data-theme="light"] {
* --app-bg: #ffffff;
* --app-text: #1f2937;
* }
*
* [data-theme="dark"] {
* --app-bg: #111827;
* --app-text: #f9fafb;
* }
*
* .themed-app {
* background: var(--app-bg);
* color: var(--app-text);
* min-height: 100vh;
* transition: background-color 0.3s ease, color 0.3s ease;
* }
*
* .theme-controls {
* padding: 2rem;
* display: flex;
* gap: 2rem;
* border-bottom: 1px solid var(--app-text);
* }
*
* .button-group button {
* padding: 0.5rem 1rem;
* border: 1px solid var(--app-text);
* background: transparent;
* color: var(--app-text);
* cursor: pointer;
* transition: all 0.2s;
* }
*
* .button-group button.active {
* background: var(--app-text);
* color: var(--app-bg);
* }
*/