157 lines
4.7 KiB
TypeScript
157 lines
4.7 KiB
TypeScript
// 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;
|