UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: отключение межстраничного лоудера
This commit is contained in:
parent
7ac9a3dbd3
commit
347d95709c
|
|
@ -4,12 +4,19 @@
|
||||||
* See the LICENSE file for details.
|
* 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() {
|
export function LogoSpinner() {
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
|
const logoSrc = resolvedTheme === "dark" ? LogoSpinnerDark : LogoSpinnerLight;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center">
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,13 @@ type TNodedcProcessingLoaderProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
tone?: "accent" | "white";
|
tone?: "accent" | "white";
|
||||||
variant?: "orbit" | "fluid";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function NodedcProcessingLoader({
|
export function NodedcProcessingLoader({ className, label, tone = "accent" }: TNodedcProcessingLoaderProps) {
|
||||||
className,
|
|
||||||
label,
|
|
||||||
tone = "accent",
|
|
||||||
variant = "orbit",
|
|
||||||
}: TNodedcProcessingLoaderProps) {
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-col items-center justify-center gap-5 text-center", className)}>
|
<div className={cn("flex flex-col items-center justify-center gap-5 text-center", className)}>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn("nodedc-processing-loader", tone === "white" && "nodedc-processing-loader-white")}
|
||||||
"nodedc-processing-loader",
|
|
||||||
variant === "fluid" && "nodedc-processing-loader-fluid",
|
|
||||||
tone === "white" && "nodedc-processing-loader-white"
|
|
||||||
)}
|
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
{label && <div className="text-18 font-semibold text-primary">{label}</div>}
|
{label && <div className="text-18 font-semibold text-primary">{label}</div>}
|
||||||
|
|
|
||||||
|
|
@ -4,150 +4,6 @@
|
||||||
* See the LICENSE file for details.
|
* See the LICENSE file for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
export default function AppProgressBar(): null {
|
||||||
import { BProgress } from "@bprogress/core";
|
return null;
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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