UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: отключение межстраничного лоудера
This commit is contained in:
parent
7ac9a3dbd3
commit
347d95709c
|
|
@ -4,12 +4,19 @@
|
|||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { NodedcProcessingLoader } from "./nodedc-processing-loader";
|
||||
import { useTheme } from "next-themes";
|
||||
// assets
|
||||
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
|
||||
import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url";
|
||||
|
||||
export function LogoSpinner() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const logoSrc = resolvedTheme === "dark" ? LogoSpinnerDark : LogoSpinnerLight;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
<NodedcProcessingLoader tone="white" variant="fluid" />
|
||||
<img src={logoSrc} alt="logo" className="h-6 w-auto object-contain sm:h-11" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,23 +4,13 @@ type TNodedcProcessingLoaderProps = {
|
|||
className?: string;
|
||||
label?: string;
|
||||
tone?: "accent" | "white";
|
||||
variant?: "orbit" | "fluid";
|
||||
};
|
||||
|
||||
export function NodedcProcessingLoader({
|
||||
className,
|
||||
label,
|
||||
tone = "accent",
|
||||
variant = "orbit",
|
||||
}: TNodedcProcessingLoaderProps) {
|
||||
export function NodedcProcessingLoader({ className, label, tone = "accent" }: TNodedcProcessingLoaderProps) {
|
||||
return (
|
||||
<div className={cn("flex flex-col items-center justify-center gap-5 text-center", className)}>
|
||||
<span
|
||||
className={cn(
|
||||
"nodedc-processing-loader",
|
||||
variant === "fluid" && "nodedc-processing-loader-fluid",
|
||||
tone === "white" && "nodedc-processing-loader-white"
|
||||
)}
|
||||
className={cn("nodedc-processing-loader", tone === "white" && "nodedc-processing-loader-white")}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{label && <div className="text-18 font-semibold text-primary">{label}</div>}
|
||||
|
|
|
|||
|
|
@ -4,150 +4,6 @@
|
|||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { BProgress } from "@bprogress/core";
|
||||
import { useNavigation } from "react-router";
|
||||
import { NodedcProcessingLoader } from "@/components/common/nodedc-processing-loader";
|
||||
import "@bprogress/core/css";
|
||||
|
||||
/**
|
||||
* Progress bar configuration options
|
||||
*/
|
||||
interface ProgressConfig {
|
||||
/** Whether to show the loading spinner */
|
||||
showSpinner: boolean;
|
||||
/** Minimum progress percentage (0-1) */
|
||||
minimum: number;
|
||||
/** Animation speed in milliseconds */
|
||||
speed: number;
|
||||
/** Auto-increment speed in milliseconds */
|
||||
trickleSpeed: number;
|
||||
/** CSS easing function */
|
||||
easing: string;
|
||||
/** Enable auto-increment */
|
||||
trickle: boolean;
|
||||
/** Delay before showing progress bar in milliseconds */
|
||||
delay: number;
|
||||
/** Whether to disable the progress bar */
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for the progress bar
|
||||
*/
|
||||
const PROGRESS_CONFIG: Readonly<ProgressConfig> = {
|
||||
showSpinner: false,
|
||||
minimum: 0.1,
|
||||
speed: 400,
|
||||
trickleSpeed: 800,
|
||||
easing: "ease",
|
||||
trickle: true,
|
||||
delay: 0,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Navigation Progress Bar Component
|
||||
*
|
||||
* Automatically displays a progress bar at the top of the page during React Router navigation.
|
||||
* Integrates with React Router's useNavigation hook to monitor route changes.
|
||||
*
|
||||
* Note: Progress bar is disabled in production builds.
|
||||
*
|
||||
* @returns null - This component doesn't render any visible elements
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function App() {
|
||||
* return (
|
||||
* <>
|
||||
* <AppProgressBar />
|
||||
* <Outlet />
|
||||
* </>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export default function AppProgressBar() {
|
||||
const navigation = useNavigation();
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const startedRef = useRef<boolean>(false);
|
||||
const [isLoaderVisible, setIsLoaderVisible] = useState(false);
|
||||
|
||||
// Initialize BProgress once on mount
|
||||
useEffect(() => {
|
||||
// Skip initialization in production builds
|
||||
if (PROGRESS_CONFIG.isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure BProgress with our settings
|
||||
BProgress.configure({
|
||||
showSpinner: PROGRESS_CONFIG.showSpinner,
|
||||
minimum: PROGRESS_CONFIG.minimum,
|
||||
speed: PROGRESS_CONFIG.speed,
|
||||
trickleSpeed: PROGRESS_CONFIG.trickleSpeed,
|
||||
easing: PROGRESS_CONFIG.easing,
|
||||
trickle: PROGRESS_CONFIG.trickle,
|
||||
});
|
||||
|
||||
// Render the progress bar element in the DOM
|
||||
BProgress.render(true);
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
if (BProgress.isStarted()) {
|
||||
BProgress.done();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle navigation state changes
|
||||
useEffect(() => {
|
||||
// Skip navigation tracking in production builds
|
||||
if (PROGRESS_CONFIG.isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (navigation.state === "idle") {
|
||||
// Navigation complete - clear any pending timer
|
||||
if (timerRef.current !== null) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
|
||||
// Complete progress if it was started
|
||||
if (startedRef.current) {
|
||||
BProgress.done();
|
||||
startedRef.current = false;
|
||||
}
|
||||
setIsLoaderVisible(false);
|
||||
} else {
|
||||
// Navigation in progress (loading or submitting)
|
||||
// Only start if not already started and no timer pending
|
||||
if (timerRef.current === null && !startedRef.current) {
|
||||
timerRef.current = setTimeout((): void => {
|
||||
if (!BProgress.isStarted()) {
|
||||
BProgress.start();
|
||||
startedRef.current = true;
|
||||
}
|
||||
setIsLoaderVisible(true);
|
||||
timerRef.current = null;
|
||||
}, PROGRESS_CONFIG.delay);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timerRef.current !== null) {
|
||||
clearTimeout(timerRef.current);
|
||||
}
|
||||
};
|
||||
}, [navigation.state]);
|
||||
|
||||
if (!isLoaderVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none fixed inset-0 z-[9999] grid place-items-center bg-black/10 backdrop-blur-[1.5px]">
|
||||
<NodedcProcessingLoader tone="white" variant="fluid" />
|
||||
</div>
|
||||
);
|
||||
export default function AppProgressBar(): null {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4006,92 +4006,4 @@
|
|||
}
|
||||
}
|
||||
|
||||
.nodedc-processing-loader-fluid {
|
||||
width: 5rem;
|
||||
aspect-ratio: 1;
|
||||
padding: 0.625rem;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
background: transparent;
|
||||
filter: blur(5px) contrast(15);
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
.nodedc-processing-loader-fluid::before,
|
||||
.nodedc-processing-loader-fluid::after {
|
||||
content: "";
|
||||
position: static;
|
||||
grid-area: 1 / 1;
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0.3125rem;
|
||||
border: 0;
|
||||
border-radius: 9999px;
|
||||
background: currentColor;
|
||||
box-shadow: none;
|
||||
offset-path: none;
|
||||
-webkit-mask-size: 100% 20px, 100% 100%;
|
||||
mask-size: 100% 20px, 100% 100%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-composite: destination-out;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
|
||||
.nodedc-processing-loader-fluid::before {
|
||||
-webkit-mask-image:
|
||||
linear-gradient(#000 0 0),
|
||||
linear-gradient(#000 0 0);
|
||||
mask-image:
|
||||
linear-gradient(#000 0 0),
|
||||
linear-gradient(#000 0 0);
|
||||
animation: nodedc-processing-fluid-shape 2s infinite;
|
||||
}
|
||||
|
||||
.nodedc-processing-loader-fluid::after {
|
||||
-webkit-mask-image: linear-gradient(#000 0 0);
|
||||
mask-image: linear-gradient(#000 0 0);
|
||||
animation:
|
||||
nodedc-processing-fluid-shape 2s infinite,
|
||||
nodedc-processing-fluid-jitter 0.5s infinite cubic-bezier(0.5, 200, 0.5, -200);
|
||||
}
|
||||
|
||||
@keyframes nodedc-processing-fluid-shape {
|
||||
0% {
|
||||
-webkit-mask-position: 0 20%, 0 0;
|
||||
mask-position: 0 20%, 0 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
-webkit-mask-position: 0 80%, 0 0;
|
||||
mask-position: 0 80%, 0 0;
|
||||
}
|
||||
|
||||
40% {
|
||||
-webkit-mask-position: 0 100%, 0 0;
|
||||
mask-position: 0 100%, 0 0;
|
||||
}
|
||||
|
||||
60% {
|
||||
-webkit-mask-position: 0 0%, 0 0;
|
||||
mask-position: 0 0%, 0 0;
|
||||
}
|
||||
|
||||
80% {
|
||||
-webkit-mask-position: 0 35%, 0 0;
|
||||
mask-position: 0 35%, 0 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-mask-position: 0 0, 0 0;
|
||||
mask-position: 0 0, 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nodedc-processing-fluid-jitter {
|
||||
100% {
|
||||
transform: translate(0.1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue