Add dark mode and theme toggle
This commit is contained in:
1 parent
f72f4ba71b
commit
d8edc8b252
13 files changed
+465
-10
No files matched your search
@@ -50,6 +50,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select";
|
||||
import { EditorToolbar } from "./EditorToolbar";
|
||||
import { ThemeToggle } from "./ThemeToggle";
|
||||
import { RevisionHistoryDialog } from "./dialogs/RevisionHistoryDialog";
|
||||
import { api } from "@/lib/api";
|
||||
import { consumeStandaloneMobileEditorReturn, openStandaloneMobileEditor } from "@/lib/mobile-editor";
|
||||
@@ -2330,13 +2331,14 @@ const RichEditorPane = ({
|
||||
<Type className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button className="hidden sm:inline-flex" size="icon" variant="ghost" title="搜索当前笔记" aria-label="搜索当前笔记" onClick={() => openNoteSearch()}>
|
||||
<Search className="h-4 w-4" />
|
||||
<Button className="hidden h-8 w-8 text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-950 focus-visible:ring-2 focus-visible:ring-slate-300 sm:inline-flex" size="icon" variant="ghost" title="搜索当前笔记" aria-label="搜索当前笔记" onClick={() => openNoteSearch()}>
|
||||
<Search className="h-5 w-5" strokeWidth={2.25} />
|
||||
</Button>
|
||||
<Button className="hidden sm:inline-flex" size="icon" variant="ghost" title="版本历史" aria-label="版本历史" onClick={() => setHistoryOpen(true)}>
|
||||
<History className="h-4 w-4" />
|
||||
<Button className="hidden h-8 w-8 text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-950 focus-visible:ring-2 focus-visible:ring-slate-300 sm:inline-flex" size="icon" variant="ghost" title="版本历史" aria-label="版本历史" onClick={() => setHistoryOpen(true)}>
|
||||
<History className="h-5 w-5" strokeWidth={2.25} />
|
||||
</Button>
|
||||
<GitHubRepositoryLink className="hidden h-8 w-8 justify-center rounded-md text-slate-500 transition hover:bg-slate-50 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500/70 lg:inline-flex" />
|
||||
<GitHubRepositoryLink className="hidden h-8 w-8 justify-center rounded-md text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500/70 lg:inline-flex" iconClassName="h-5 w-5" />
|
||||
<ThemeToggle />
|
||||
{!readOnly && (
|
||||
<Button
|
||||
className="hidden sm:inline-flex"
|
||||
|
||||
@@ -10,6 +10,7 @@ import { PwaInstallCard } from "./settings/PwaInstallCard";
|
||||
import { PasswordCard } from "./settings/PasswordCard";
|
||||
import { SessionCard } from "./settings/SessionCard";
|
||||
import { SystemInfoCard } from "./settings/SystemInfoCard";
|
||||
import { ThemeToggle } from "./ThemeToggle";
|
||||
|
||||
interface SettingsPaneProps {
|
||||
onClose: () => void;
|
||||
@@ -58,6 +59,7 @@ export const SettingsPane = ({
|
||||
<p className="mt-0.5 truncate text-xs font-medium text-slate-400">{t("settings.subtitle")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
</header>
|
||||
|
||||
<div className="min-w-0 flex-1 overflow-x-hidden overflow-y-auto px-4 py-4 lg:px-6 lg:py-6">
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
|
||||
export type ThemePreference = "system" | "light" | "dark";
|
||||
export type ResolvedTheme = "light" | "dark";
|
||||
|
||||
interface ThemeContextValue {
|
||||
preference: ThemePreference;
|
||||
resolvedTheme: ResolvedTheme;
|
||||
setPreference: (preference: ThemePreference) => void;
|
||||
}
|
||||
|
||||
interface ThemeProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const THEME_STORAGE_KEY = "edgeever.theme";
|
||||
const LIGHT_THEME_COLOR = "#f8fafc";
|
||||
const DARK_THEME_COLOR = "#0f172a";
|
||||
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
||||
|
||||
const getSystemTheme = () =>
|
||||
typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
|
||||
const resolveTheme = (preference: ThemePreference): ResolvedTheme =>
|
||||
preference === "system" ? getSystemTheme() : preference;
|
||||
|
||||
export const getStoredThemePreference = (): ThemePreference => {
|
||||
if (typeof window === "undefined") {
|
||||
return "system";
|
||||
}
|
||||
|
||||
const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
|
||||
return stored === "light" || stored === "dark" || stored === "system" ? stored : "system";
|
||||
};
|
||||
|
||||
const applyThemeToDocument = (preference: ThemePreference) => {
|
||||
const resolvedTheme = resolveTheme(preference);
|
||||
const root = document.documentElement;
|
||||
root.classList.toggle("dark", resolvedTheme === "dark");
|
||||
root.style.colorScheme = resolvedTheme;
|
||||
|
||||
const themeColor = document.querySelector<HTMLMetaElement>('meta[name="theme-color"]');
|
||||
themeColor?.setAttribute("content", resolvedTheme === "dark" ? DARK_THEME_COLOR : LIGHT_THEME_COLOR);
|
||||
return resolvedTheme;
|
||||
};
|
||||
|
||||
export const initializeTheme = () => {
|
||||
applyThemeToDocument(getStoredThemePreference());
|
||||
};
|
||||
|
||||
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
||||
const [preference, setPreferenceState] = useState<ThemePreference>(getStoredThemePreference);
|
||||
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() => resolveTheme(preference));
|
||||
|
||||
useEffect(() => {
|
||||
setResolvedTheme(applyThemeToDocument(preference));
|
||||
window.localStorage.setItem(THEME_STORAGE_KEY, preference);
|
||||
|
||||
if (preference !== "system") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const handleSystemThemeChange = () => setResolvedTheme(applyThemeToDocument("system"));
|
||||
mediaQuery.addEventListener("change", handleSystemThemeChange);
|
||||
return () => mediaQuery.removeEventListener("change", handleSystemThemeChange);
|
||||
}, [preference]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
preference,
|
||||
resolvedTheme,
|
||||
setPreference: (nextPreference: ThemePreference) => setPreferenceState(nextPreference),
|
||||
}),
|
||||
[preference, resolvedTheme]
|
||||
);
|
||||
|
||||
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
||||
};
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useTheme must be used within ThemeProvider");
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { MoonStar, SunMedium } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTheme } from "./ThemeProvider";
|
||||
|
||||
export const ThemeToggle = () => {
|
||||
const { t } = useTranslation();
|
||||
const { resolvedTheme, setPreference } = useTheme();
|
||||
const nextTheme = resolvedTheme === "dark" ? "light" : "dark";
|
||||
const label = nextTheme === "dark" ? t("settings.themeToggleToDark") : t("settings.themeToggleToLight");
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="hidden h-8 w-8 text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-950 focus-visible:ring-2 focus-visible:ring-slate-300 lg:inline-flex"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
title={label}
|
||||
aria-label={label}
|
||||
onClick={() => setPreference(nextTheme)}
|
||||
>
|
||||
{resolvedTheme === "dark" ? <SunMedium className="h-5 w-5" strokeWidth={2.25} /> : <MoonStar className="h-5 w-5" strokeWidth={2.25} />}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
type AppLocalePreference,
|
||||
} from "@/i18n";
|
||||
import { ShortcutSettingsItem } from "./ShortcutSettingsItem";
|
||||
import { useTheme, type ThemePreference } from "../ThemeProvider";
|
||||
|
||||
interface PreferenceCardProps {
|
||||
imageCompressionEnabled: boolean;
|
||||
@@ -28,6 +29,7 @@ export const PreferenceCard = ({
|
||||
onShortcutSettingsChange,
|
||||
}: PreferenceCardProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { preference: themePreference, setPreference: setThemePreference } = useTheme();
|
||||
const [activeLocalePreference, setActiveLocalePreference] = useState<AppLocalePreference>(() => getAppLocalePreference());
|
||||
|
||||
const handleLocalePreferenceChange = (preference: AppLocalePreference) => {
|
||||
@@ -82,6 +84,28 @@ export const PreferenceCard = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-14 flex-col items-start gap-3 rounded-lg border border-slate-100 bg-slate-50/70 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-semibold text-slate-900">{t("settings.themeTitle")}</div>
|
||||
<div className="mt-0.5 text-xs leading-4 text-slate-500">{t("settings.themeDescription")}</div>
|
||||
</div>
|
||||
<div className="w-full shrink-0 sm:w-44">
|
||||
<Select
|
||||
value={themePreference}
|
||||
onValueChange={(preference) => setThemePreference(preference as ThemePreference)}
|
||||
>
|
||||
<SelectTrigger aria-label={t("settings.themeTitle")} className="h-9 bg-white">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="system">{t("settings.themeSystem")}</SelectItem>
|
||||
<SelectItem value="light">{t("settings.themeLight")}</SelectItem>
|
||||
<SelectItem value="dark">{t("settings.themeDark")}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<ShortcutSettingsItem
|
||||
shortcutSettings={shortcutSettings}
|
||||
onShortcutSettingsChange={onShortcutSettingsChange}
|
||||
|
||||
@@ -139,6 +139,13 @@ export const enUS = {
|
||||
imageCompressionTitle: "Compress note images",
|
||||
imageCompressionDescription: "Compress large images locally before upload to reduce storage use.",
|
||||
imageCompressionAria: "Compress note images",
|
||||
themeTitle: "Interface theme",
|
||||
themeDescription: "Choose light, dark, or follow your system setting.",
|
||||
themeSystem: "Follow system",
|
||||
themeLight: "Light",
|
||||
themeDark: "Dark",
|
||||
themeToggleToDark: "Switch to dark mode",
|
||||
themeToggleToLight: "Switch to light mode",
|
||||
},
|
||||
systemInfo: {
|
||||
title: "System info",
|
||||
|
||||
@@ -139,6 +139,13 @@ export const zhCN = {
|
||||
imageCompressionTitle: "压缩笔记内图片",
|
||||
imageCompressionDescription: "上传大图时在本地压缩,节省资源占用。",
|
||||
imageCompressionAria: "是否压缩笔记内图片",
|
||||
themeTitle: "界面主题",
|
||||
themeDescription: "选择浅色、深色,或跟随系统设置。",
|
||||
themeSystem: "跟随系统",
|
||||
themeLight: "浅色",
|
||||
themeDark: "深色",
|
||||
themeToggleToDark: "切换到深色模式",
|
||||
themeToggleToLight: "切换到浅色模式",
|
||||
},
|
||||
systemInfo: {
|
||||
title: "系统信息",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { registerSW } from "virtual:pwa-register";
|
||||
import { App } from "./app/App";
|
||||
import "./i18n";
|
||||
import { emitPwaUpdateNotice, markPwaUpdateReloadPending } from "./lib/pwa-update-notice";
|
||||
import { initializeTheme, ThemeProvider } from "./components/ThemeProvider";
|
||||
import "./styles/globals.css";
|
||||
|
||||
const PWA_UPDATE_CHECK_INTERVAL_MS = 10 * 60 * 1_000;
|
||||
@@ -58,12 +59,16 @@ if (!root) {
|
||||
throw new Error("Root element not found");
|
||||
}
|
||||
|
||||
initializeTheme();
|
||||
|
||||
createRoot(root).render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<ThemeProvider>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { MobileStandaloneTiptapEditor } from "@/components/MobileStandaloneTiptapEditor";
|
||||
import { initializeTheme } from "@/components/ThemeProvider";
|
||||
import "./styles/mobile-markdown-editor.css";
|
||||
|
||||
const root = document.getElementById("mobile-editor-root");
|
||||
@@ -9,6 +10,8 @@ if (!root) {
|
||||
throw new Error("Mobile editor root not found");
|
||||
}
|
||||
|
||||
initializeTheme();
|
||||
|
||||
createRoot(root).render(
|
||||
<React.StrictMode>
|
||||
<MobileStandaloneTiptapEditor />
|
||||
|
||||
@@ -25,6 +25,17 @@
|
||||
--brand-green-800-rgb: 0 92 25;
|
||||
--brand-green-900-rgb: 0 67 18;
|
||||
--brand-green-950-rgb: 0 43 11;
|
||||
--slate-50-rgb: 248 250 252;
|
||||
--slate-100-rgb: 241 245 249;
|
||||
--slate-200-rgb: 226 232 240;
|
||||
--slate-300-rgb: 203 213 225;
|
||||
--slate-400-rgb: 148 163 184;
|
||||
--slate-500-rgb: 100 116 139;
|
||||
--slate-600-rgb: 71 85 105;
|
||||
--slate-700-rgb: 51 65 85;
|
||||
--slate-800-rgb: 30 41 59;
|
||||
--slate-900-rgb: 15 23 42;
|
||||
--dark-surface-rgb: 255 255 255;
|
||||
--editor-body-font-size: 0.9375rem;
|
||||
--editor-body-line-height: 1.6;
|
||||
--editor-paragraph-spacing: 0.45rem;
|
||||
@@ -66,6 +77,59 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--background: 222 47% 11%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 217 33% 17%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 217 33% 17%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 142 71% 45%;
|
||||
--primary-foreground: 222 47% 11%;
|
||||
--secondary: 217 33% 24%;
|
||||
--secondary-foreground: 142 71% 75%;
|
||||
--muted: 217 33% 24%;
|
||||
--muted-foreground: 215 20% 65%;
|
||||
--accent: 217 33% 24%;
|
||||
--accent-foreground: 142 71% 75%;
|
||||
--destructive: 0 72% 51%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 215 28% 28%;
|
||||
--input: 215 28% 28%;
|
||||
--ring: 142 71% 45%;
|
||||
--slate-50-rgb: 15 23 42;
|
||||
--slate-100-rgb: 30 41 59;
|
||||
--slate-200-rgb: 51 65 85;
|
||||
--slate-300-rgb: 71 85 105;
|
||||
--slate-400-rgb: 100 116 139;
|
||||
--slate-500-rgb: 148 163 184;
|
||||
--slate-600-rgb: 203 213 225;
|
||||
--slate-700-rgb: 226 232 240;
|
||||
--slate-800-rgb: 241 245 249;
|
||||
--slate-900-rgb: 248 250 252;
|
||||
--dark-surface-rgb: 30 41 59;
|
||||
--brand-green: #4ade80;
|
||||
--brand-green-hover: #22c55e;
|
||||
--brand-green-muted: #14532d;
|
||||
--brand-green-soft: #052e16;
|
||||
--brand-green-border: #166534;
|
||||
--brand-green-text: #86efac;
|
||||
--brand-green-rgb: 74 222 128;
|
||||
--brand-green-hover-rgb: 34 197 94;
|
||||
--brand-green-50-rgb: 20 83 45;
|
||||
--brand-green-100-rgb: 22 101 52;
|
||||
--brand-green-200-rgb: 34 133 67;
|
||||
--brand-green-300-rgb: 74 222 128;
|
||||
--brand-green-400-rgb: 134 239 172;
|
||||
--brand-green-500-rgb: 74 222 128;
|
||||
--brand-green-600-rgb: 34 197 94;
|
||||
--brand-green-700-rgb: 134 239 172;
|
||||
--brand-green-800-rgb: 187 247 208;
|
||||
--brand-green-900-rgb: 220 252 231;
|
||||
--brand-green-950-rgb: 240 253 244;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
@@ -90,6 +154,11 @@ body {
|
||||
overscroll-behavior-y: auto;
|
||||
}
|
||||
|
||||
:root.dark body {
|
||||
background: #0f172a;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
@@ -457,3 +526,153 @@ textarea {
|
||||
.markdown-content a:hover {
|
||||
color: var(--brand-green);
|
||||
}
|
||||
|
||||
/* Theme-aware surfaces for legacy white utilities and fixed semantic colors. */
|
||||
:root.dark [class~="bg-white"] {
|
||||
background-color: rgb(var(--dark-surface-rgb)) !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="bg-white/70"] {
|
||||
background-color: rgb(var(--dark-surface-rgb) / 0.7) !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="bg-white/75"] {
|
||||
background-color: rgb(var(--dark-surface-rgb) / 0.75) !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="bg-white/80"] {
|
||||
background-color: rgb(var(--dark-surface-rgb) / 0.8) !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="bg-white/85"] {
|
||||
background-color: rgb(var(--dark-surface-rgb) / 0.85) !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="bg-white/90"] {
|
||||
background-color: rgb(var(--dark-surface-rgb) / 0.9) !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="bg-white/95"] {
|
||||
background-color: rgb(var(--dark-surface-rgb) / 0.95) !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="border-white"] {
|
||||
border-color: rgb(var(--slate-200-rgb)) !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="text-slate-950"] {
|
||||
color: hsl(var(--foreground)) !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="bg-slate-50"],
|
||||
:root.dark [class*="bg-slate-50/"] { background-color: rgb(var(--slate-50-rgb)) !important; }
|
||||
:root.dark [class~="bg-slate-100"],
|
||||
:root.dark [class*="bg-slate-100/"] { background-color: rgb(var(--slate-100-rgb)) !important; }
|
||||
:root.dark [class~="bg-slate-200"],
|
||||
:root.dark [class*="bg-slate-200/"] { background-color: rgb(var(--slate-200-rgb)) !important; }
|
||||
:root.dark [class~="bg-slate-300"],
|
||||
:root.dark [class*="bg-slate-300/"] { background-color: rgb(var(--slate-300-rgb)) !important; }
|
||||
:root.dark [class~="bg-slate-400"],
|
||||
:root.dark [class*="bg-slate-400/"] { background-color: rgb(var(--slate-400-rgb)) !important; }
|
||||
:root.dark [class~="bg-slate-500"],
|
||||
:root.dark [class*="bg-slate-500/"] { background-color: rgb(var(--slate-500-rgb)) !important; }
|
||||
:root.dark [class~="bg-slate-600"],
|
||||
:root.dark [class*="bg-slate-600/"] { background-color: rgb(var(--slate-600-rgb)) !important; }
|
||||
:root.dark [class~="bg-slate-700"],
|
||||
:root.dark [class*="bg-slate-700/"] { background-color: rgb(var(--slate-700-rgb)) !important; }
|
||||
:root.dark [class~="bg-slate-800"],
|
||||
:root.dark [class*="bg-slate-800/"] { background-color: rgb(var(--slate-800-rgb)) !important; }
|
||||
:root.dark [class~="bg-slate-900"],
|
||||
:root.dark [class*="bg-slate-900/"] { background-color: rgb(var(--slate-900-rgb)) !important; }
|
||||
|
||||
:root.dark [class~="text-slate-50"] { color: rgb(var(--slate-50-rgb)) !important; }
|
||||
:root.dark [class~="text-slate-100"] { color: rgb(var(--slate-100-rgb)) !important; }
|
||||
:root.dark [class~="text-slate-200"] { color: rgb(var(--slate-200-rgb)) !important; }
|
||||
:root.dark [class~="text-slate-300"] { color: rgb(var(--slate-300-rgb)) !important; }
|
||||
:root.dark [class~="text-slate-400"] { color: rgb(var(--slate-400-rgb)) !important; }
|
||||
:root.dark [class~="text-slate-500"] { color: rgb(var(--slate-500-rgb)) !important; }
|
||||
:root.dark [class~="text-slate-600"] { color: rgb(var(--slate-600-rgb)) !important; }
|
||||
:root.dark [class~="text-slate-700"] { color: rgb(var(--slate-700-rgb)) !important; }
|
||||
:root.dark [class~="text-slate-800"] { color: rgb(var(--slate-800-rgb)) !important; }
|
||||
:root.dark [class~="text-slate-900"] { color: rgb(var(--slate-900-rgb)) !important; }
|
||||
|
||||
:root.dark [class~="border-slate-50"],
|
||||
:root.dark [class*="border-slate-50/"] { border-color: rgb(var(--slate-50-rgb)) !important; }
|
||||
:root.dark [class~="border-slate-100"],
|
||||
:root.dark [class*="border-slate-100/"] { border-color: rgb(var(--slate-100-rgb)) !important; }
|
||||
:root.dark [class~="border-slate-200"],
|
||||
:root.dark [class*="border-slate-200/"] { border-color: rgb(var(--slate-200-rgb)) !important; }
|
||||
:root.dark [class~="border-slate-300"],
|
||||
:root.dark [class*="border-slate-300/"] { border-color: rgb(var(--slate-300-rgb)) !important; }
|
||||
:root.dark [class~="border-slate-400"],
|
||||
:root.dark [class*="border-slate-400/"] { border-color: rgb(var(--slate-400-rgb)) !important; }
|
||||
|
||||
:root.dark [class*="hover:bg-slate-50"]:hover,
|
||||
:root.dark [class*="hover:bg-slate-100"]:hover { background-color: rgb(var(--slate-100-rgb)) !important; }
|
||||
|
||||
:root.dark [class*="hover:text-slate-900"]:hover,
|
||||
:root.dark [class*="focus:text-slate-900"]:focus { color: rgb(var(--slate-900-rgb)) !important; }
|
||||
|
||||
:root.dark [class~="bg-amber-50"],
|
||||
:root.dark [class~="bg-amber-50/95"] {
|
||||
background-color: #451a03 !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="text-amber-700"],
|
||||
:root.dark [class~="text-amber-800"] {
|
||||
color: #fcd34d !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="bg-rose-50"],
|
||||
:root.dark [class~="bg-rose-50/30"] {
|
||||
background-color: #4c0519 !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="text-rose-600"],
|
||||
:root.dark [class~="text-rose-700"] {
|
||||
color: #fda4af !important;
|
||||
}
|
||||
|
||||
:root.dark [class~="border-rose-100"],
|
||||
:root.dark [class~="border-rose-200"] {
|
||||
border-color: #881337 !important;
|
||||
}
|
||||
|
||||
:root.dark .ProseMirror blockquote {
|
||||
color: #cbd5e1;
|
||||
background: var(--brand-green-soft);
|
||||
}
|
||||
|
||||
:root.dark .ProseMirror code,
|
||||
:root.dark .ProseMirror pre {
|
||||
border-color: #166534;
|
||||
background: #13261a;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
:root.dark .markdown-content {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
:root.dark .markdown-content h1,
|
||||
:root.dark .markdown-content h2,
|
||||
:root.dark .markdown-content h3 {
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
:root.dark .markdown-content h1,
|
||||
:root.dark .markdown-content hr {
|
||||
border-color: #334155;
|
||||
}
|
||||
|
||||
:root.dark .markdown-content pre {
|
||||
background-color: #13261a;
|
||||
border-color: #166534;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
:root.dark .markdown-content code {
|
||||
background-color: #13261a;
|
||||
border-color: #166534;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
@@ -256,6 +256,64 @@ input {
|
||||
padding: 8px 0 max(10px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root.dark body,
|
||||
:root.dark .mobile-editor-shell {
|
||||
background: #0f172a;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
:root.dark .mobile-editor-header,
|
||||
:root.dark .mobile-editor-title,
|
||||
:root.dark .mobile-editor-tags,
|
||||
:root.dark .mobile-editor-notebook-sheet {
|
||||
background: #1e293b;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
:root.dark .mobile-editor-header,
|
||||
:root.dark .mobile-editor-tool-row {
|
||||
border-color: #334155;
|
||||
}
|
||||
|
||||
:root.dark .mobile-editor-back,
|
||||
:root.dark .mobile-editor-tool-button {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
:root.dark .mobile-editor-status {
|
||||
background: #334155;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
:root.dark .mobile-editor-status.active,
|
||||
:root.dark .mobile-editor-tool-button:active,
|
||||
:root.dark .mobile-editor-tool-button[aria-pressed="true"] {
|
||||
background: #14532d;
|
||||
border-color: #166534;
|
||||
color: #86efac;
|
||||
}
|
||||
|
||||
:root.dark .mobile-editor-status.error,
|
||||
:root.dark .mobile-editor-error {
|
||||
background: #4c0519;
|
||||
border-color: #881337;
|
||||
color: #fda4af;
|
||||
}
|
||||
|
||||
:root.dark .mobile-editor-notebook-button,
|
||||
:root.dark .mobile-editor-tags::placeholder,
|
||||
:root.dark .mobile-editor-title::placeholder {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
:root.dark .mobile-editor-sheet-backdrop {
|
||||
background: rgb(2 6 23 / 0.62);
|
||||
}
|
||||
|
||||
.mobile-editor-notebook-sheet-handle {
|
||||
width: 38px;
|
||||
height: 4px;
|
||||
|
||||
@@ -2,8 +2,10 @@ import type { Config } from "tailwindcss";
|
||||
import tailwindAnimate from "tailwindcss-animate";
|
||||
|
||||
const brandGreen = (shade: number) => `rgb(var(--brand-green-${shade}-rgb) / <alpha-value>)`;
|
||||
const slate = (shade: number) => `rgb(var(--slate-${shade}-rgb) / <alpha-value>)`;
|
||||
|
||||
export default {
|
||||
darkMode: "class",
|
||||
content: {
|
||||
relative: true,
|
||||
files: ["./index.html", "./src/**/*.{ts,tsx}"],
|
||||
@@ -30,6 +32,19 @@ export default {
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
colors: {
|
||||
slate: {
|
||||
50: slate(50),
|
||||
100: slate(100),
|
||||
200: slate(200),
|
||||
300: slate(300),
|
||||
400: slate(400),
|
||||
500: slate(500),
|
||||
600: slate(600),
|
||||
700: slate(700),
|
||||
800: slate(800),
|
||||
900: slate(900),
|
||||
950: "#020617",
|
||||
},
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
|
||||
@@ -47,8 +47,8 @@ export default defineConfig({
|
||||
start_url: "/",
|
||||
scope: "/",
|
||||
display: "standalone",
|
||||
background_color: "#f8fafc",
|
||||
theme_color: "#f8fafc",
|
||||
background_color: "#0f172a",
|
||||
theme_color: "#0f172a",
|
||||
lang: "zh-CN",
|
||||
categories: ["productivity", "utilities"],
|
||||
icons: [
|
||||
|
||||
Reference in new issue
Block a user