UNPKG

@zapstra/core-router

Version:

Query-preserving router for Zapstra platform apps

216 lines (214 loc) 6.79 kB
import { useNavigate, useLocation, useNavigation } from '@remix-run/react'; import { createContext, useState, useRef, useEffect, useContext } from 'react'; import { jsxs, jsx } from 'react/jsx-runtime'; // src/navigation-utils.ts function navigateWithQuery(navigate, path, search) { navigate(path + (search || "")); } function useNavigateWithQuery() { const navigate = useNavigate(); const { search } = useLocation(); return (path) => navigateWithQuery(navigate, path, search); } function useQueryParams() { const { search } = useLocation(); const params = new URLSearchParams(search); const result = {}; for (const [key, value] of params.entries()) { result[key] = value; } return result; } function useIsPathActive() { const location = useLocation(); return (path, exact = false) => { if (exact) { return location.pathname === path; } if (path === "/app") { return location.pathname === "/app"; } return location.pathname.startsWith(path); }; } function GradientProgressSpinner({ isLoading, size = 80, strokeWidth = 4 }) { const [progress, setProgress] = useState(0); const intervalRef = useRef(null); const progressRef = useRef(0); const radius = (size - strokeWidth * 2) / 2; const circumference = radius * 2 * Math.PI; const center = size / 2; useEffect(() => { if (isLoading) { setProgress(0); progressRef.current = 0; let currentProgress = 0; intervalRef.current = window.setInterval(() => { currentProgress += (100 - currentProgress) * 0.1; if (currentProgress > 95) { currentProgress = 95; } progressRef.current = Math.floor(currentProgress); setProgress(progressRef.current); }, 100); } else if (!isLoading) { if (progressRef.current > 0 && progressRef.current < 100) { setProgress(100); progressRef.current = 100; setTimeout(() => { setProgress(0); progressRef.current = 0; }, 300); } if (intervalRef.current) { clearInterval(intervalRef.current); } } return () => { if (intervalRef.current) { clearInterval(intervalRef.current); } }; }, [isLoading]); if (!isLoading && progress === 0) return null; const strokeDashoffset = circumference - progress / 100 * circumference; return /* @__PURE__ */ jsxs( "div", { style: { position: "fixed", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "rgba(255, 255, 255, 0.9)", backdropFilter: "blur(4px)", zIndex: 999, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", transition: "opacity 0.3s ease", opacity: isLoading || progress > 0 ? 1 : 0, pointerEvents: isLoading ? "auto" : "none" }, children: [ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [ /* @__PURE__ */ jsxs("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: "progress-gradient", x1: "0%", y1: "0%", x2: "100%", y2: "100%", children: [ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#218dfd" }), /* @__PURE__ */ jsx("stop", { offset: "50%", stopColor: "#4a9fff" }), /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#20e3b2" }) ] }) }), /* @__PURE__ */ jsx( "circle", { cx: center, cy: center, r: radius, fill: "none", stroke: "#e5e7eb", strokeWidth } ), /* @__PURE__ */ jsx( "circle", { cx: center, cy: center, r: radius, fill: "none", stroke: "url(#progress-gradient)", strokeWidth, strokeLinecap: "round", strokeDasharray: circumference, strokeDashoffset, style: { transition: "stroke-dashoffset 0.2s ease", filter: "drop-shadow(0 0 3px rgba(33, 141, 253, 0.4))" } } ) ] }), /* @__PURE__ */ jsx( "div", { style: { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", textAlign: "center" }, children: /* @__PURE__ */ jsxs( "div", { style: { fontSize: "20px", fontWeight: 600, background: "linear-gradient(135deg, #218dfd 0%, #20e3b2 100%)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent", backgroundClip: "text" }, children: [ progress, "%" ] } ) } ) ] }), /* @__PURE__ */ jsx("div", { style: { marginTop: "20px", fontSize: "14px", color: "#6b7280", fontWeight: 500 }, children: "Loading..." }) ] } ); } var RouterCtx = createContext(null); function AppRouterProvider({ children, showLoadingSpinner = true, spinnerProps }) { const navigation = useNavigation(); const navWithQuery = useNavigateWithQuery(); const ctx = { route: navWithQuery, isNavigating: navigation.state !== "idle" }; return /* @__PURE__ */ jsxs(RouterCtx.Provider, { value: ctx, children: [ showLoadingSpinner && /* @__PURE__ */ jsx( GradientProgressSpinner, { isLoading: ctx.isNavigating, ...spinnerProps } ), children ] }); } function useAppRouter() { const ctx = useContext(RouterCtx); if (!ctx) { throw new Error("useAppRouter must be used inside <AppRouterProvider>"); } return ctx; } function withAppRouter(Component, options) { return function WrappedComponent(props) { return /* @__PURE__ */ jsx(AppRouterProvider, { ...options, children: /* @__PURE__ */ jsx(Component, { ...props }) }); }; } export { AppRouterProvider, GradientProgressSpinner, navigateWithQuery, useAppRouter, useIsPathActive, useNavigateWithQuery, useQueryParams, withAppRouter }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map