mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 02:59:35 +00:00
feat: integrate Pi backend and Pi Web
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useSyncExternalStore } from "react";
|
||||
|
||||
type Theme = "light" | "dark";
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
function subscribe(cb: () => void): () => void {
|
||||
listeners.add(cb);
|
||||
return () => {
|
||||
listeners.delete(cb);
|
||||
};
|
||||
}
|
||||
|
||||
function getSnapshot(): Theme {
|
||||
if (typeof document === "undefined") return "light";
|
||||
return document.documentElement.classList.contains("dark") ? "dark" : "light";
|
||||
}
|
||||
|
||||
function getServerSnapshot(): Theme {
|
||||
return "light";
|
||||
}
|
||||
|
||||
type ToggleOrigin = { x: number; y: number };
|
||||
|
||||
export function useTheme() {
|
||||
const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||
|
||||
const toggleTheme = useCallback((origin?: ToggleOrigin) => {
|
||||
const next: Theme = getSnapshot() === "dark" ? "light" : "dark";
|
||||
|
||||
const apply = () => {
|
||||
if (next === "dark") {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
try {
|
||||
localStorage.setItem("pi-theme", next);
|
||||
} catch {
|
||||
// ignore storage errors (private mode, quota, etc.)
|
||||
}
|
||||
listeners.forEach((cb) => cb());
|
||||
};
|
||||
|
||||
const reduceMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
||||
const supportsVT = typeof document.startViewTransition === "function";
|
||||
|
||||
if (!supportsVT || reduceMotion) {
|
||||
apply();
|
||||
return;
|
||||
}
|
||||
|
||||
const x = origin?.x ?? window.innerWidth / 2;
|
||||
const y = origin?.y ?? window.innerHeight / 2;
|
||||
const endRadius = Math.hypot(
|
||||
Math.max(x, window.innerWidth - x),
|
||||
Math.max(y, window.innerHeight - y),
|
||||
);
|
||||
|
||||
const transition = document.startViewTransition(apply);
|
||||
transition.ready
|
||||
.then(() => {
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${endRadius}px at ${x}px ${y}px)`,
|
||||
],
|
||||
},
|
||||
{
|
||||
duration: 450,
|
||||
easing: "cubic-bezier(0.22, 0.61, 0.36, 1)",
|
||||
pseudoElement: "::view-transition-new(root)",
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
// transition cancelled — ignore
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { theme, toggleTheme, isDark: theme === "dark" };
|
||||
}
|
||||
Reference in New Issue
Block a user