This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
// Imported from investidor; return destinations are restricted to this platform.
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { router } from '@inertiajs/react';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { Trans } from './source-text'; // Import Trans component
|
||||
|
||||
interface BackButtonProps {
|
||||
to?: string;
|
||||
label?: React.ReactNode; // Change type to React.ReactNode to accept <Trans> component
|
||||
className?: string;
|
||||
theme?: 'light' | 'dark';
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const RETURN_URL_KEY = 'news:return-url';
|
||||
|
||||
function normalizeInternalUrl(value: string | null): string | null {
|
||||
if (!value || typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = value.startsWith('http')
|
||||
? new URL(value)
|
||||
: new URL(value, window.location.origin);
|
||||
|
||||
if (url.origin !== window.location.origin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = `${url.pathname}${url.search}${url.hash}`;
|
||||
const current = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
|
||||
return normalized !== current ? normalized : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getStoredReturnUrl(): string | null {
|
||||
try {
|
||||
return normalizeInternalUrl(sessionStorage.getItem(RETURN_URL_KEY));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getReturnUrlFromPage(): string | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const fromParam = normalizeInternalUrl(params.get('from'));
|
||||
|
||||
if (fromParam) {
|
||||
return fromParam;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getInternalReferrerUrl(): string | null {
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return normalizeInternalUrl(document.referrer);
|
||||
}
|
||||
|
||||
export const BackButton: React.FC<BackButtonProps> = ({
|
||||
to,
|
||||
label = <Trans>Voltar</Trans>, // Use Trans component for default label
|
||||
className = '',
|
||||
theme = 'light',
|
||||
onClick,
|
||||
}) => {
|
||||
const [showFloatingButton, setShowFloatingButton] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setShowFloatingButton(window.scrollY > 220);
|
||||
};
|
||||
|
||||
handleScroll();
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
const themeClasses =
|
||||
theme === 'dark'
|
||||
? 'border-white/20 bg-white/10 text-white shadow-lg backdrop-blur-md hover:bg-white/20'
|
||||
: 'border-red-100 bg-white text-[#ED1C24] shadow-sm hover:border-red-200 hover:bg-red-50';
|
||||
|
||||
const baseClasses =
|
||||
'inline-flex items-center gap-2 rounded-full border px-4 py-2 text-sm font-semibold transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-[#ED1C24]/25 active:scale-[0.98]';
|
||||
|
||||
const floatingClasses = [
|
||||
'fixed bottom-5 left-4 z-50 max-w-[calc(100vw-2rem)] sm:bottom-6 sm:left-6',
|
||||
'border-red-100 bg-white text-[#ED1C24] shadow-xl ring-1 ring-black/5 hover:border-red-200 hover:bg-red-50',
|
||||
showFloatingButton
|
||||
? 'pointer-events-auto translate-y-0 opacity-100'
|
||||
: 'pointer-events-none translate-y-3 opacity-0',
|
||||
].join(' ');
|
||||
|
||||
const renderContent = () => (
|
||||
<>
|
||||
<ArrowLeft size={16} aria-hidden="true" />
|
||||
<span className="truncate">{label}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
if (onClick) {
|
||||
onClick();
|
||||
return;
|
||||
}
|
||||
|
||||
const explicitReturnUrl = getReturnUrlFromPage();
|
||||
const storedReturnUrl = getStoredReturnUrl();
|
||||
|
||||
if (explicitReturnUrl) {
|
||||
router.visit(explicitReturnUrl);
|
||||
} else if (storedReturnUrl) {
|
||||
router.visit(storedReturnUrl);
|
||||
} else {
|
||||
router.visit(getInternalReferrerUrl() || to || '/');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className={`${baseClasses} ${themeClasses} ${className}`}
|
||||
aria-label={typeof label === 'string' ? label : undefined} // aria-label should be a string
|
||||
>
|
||||
{renderContent()}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className={`${baseClasses} ${floatingClasses}`}
|
||||
aria-label={typeof label === 'string' ? label : undefined} // aria-label should be a string
|
||||
aria-hidden={!showFloatingButton}
|
||||
tabIndex={showFloatingButton ? undefined : -1}
|
||||
>
|
||||
{renderContent()}
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackButton;
|
||||
Reference in New Issue
Block a user