fix: fullscreen layout and filter dropdown positioning

- Page now uses fixed inset-0 overlay to fill entire viewport
- Simplified top bar to single row (back + title + search + filter)
- Filter dropdown uses absolute positioning on desktop (no layout shift)
- Filter button always visible, toggles dropdown on click
This commit is contained in:
Fringg
2026-03-19 08:43:39 +03:00
parent ea143fdbc9
commit 4ebd85b65d
2 changed files with 92 additions and 105 deletions

View File

@@ -29,33 +29,19 @@ export function ReferralNetwork() {
const isPanelOpen = selectedNode !== null; const isPanelOpen = selectedNode !== null;
return ( return (
<div <div id="referral-network-container" className="fixed inset-0 z-40 flex flex-col bg-[#0a0a0f]">
id="referral-network-container"
className="relative flex h-[calc(100dvh-64px)] flex-col overflow-hidden bg-[#0a0a0f]"
>
{/* Top bar */} {/* Top bar */}
<div className="relative z-20 shrink-0 border-b border-dark-700/50 bg-dark-900/90 backdrop-blur-md"> <div className="relative z-20 shrink-0 border-b border-dark-700/50 bg-dark-900/90 backdrop-blur-md">
{/* Row 1: Back + Title + (desktop: search + filter) + (mobile: filter icon) */} {/* Row 1: Back + Title + Search + Filter */}
<div className="flex items-center gap-3 px-3 py-2 sm:px-4 sm:py-3"> <div className="flex items-center gap-2 px-3 py-2 sm:gap-3 sm:px-4 sm:py-3">
<AdminBackButton /> <AdminBackButton />
<h1 className="min-w-0 truncate text-sm font-bold text-dark-100 sm:text-lg"> <h1 className="shrink-0 text-sm font-bold text-dark-100 sm:text-base">
{t('admin.referralNetwork.title')} {t('admin.referralNetwork.title')}
</h1> </h1>
{/* Desktop: search + filter inline */} {/* Search — always visible */}
<div className="hidden flex-1 items-center gap-2 sm:flex"> <NetworkSearch className="min-w-0 flex-1 sm:max-w-md" />
<NetworkSearch className="max-w-md flex-1" /> {/* Filter button */}
{networkData && <NetworkFilters data={networkData} />} {networkData && <NetworkFilters data={networkData} />}
</div>
{/* Mobile: filter icon only */}
{networkData && (
<div className="ml-auto sm:hidden">
<NetworkFilters data={networkData} />
</div>
)}
</div>
{/* Row 2 (mobile only): full-width search */}
<div className="px-3 pb-2 sm:hidden">
<NetworkSearch className="w-full" />
</div> </div>
</div> </div>

View File

@@ -41,36 +41,6 @@ export function NetworkFilters({ data, className }: NetworkFiltersProps) {
updateFilters({ campaigns: next }); updateFilters({ campaigns: next });
} }
// Trigger button
if (!isOpen) {
return (
<button
onClick={() => setIsOpen(true)}
aria-label={t('admin.referralNetwork.filters.title')}
className={`relative flex shrink-0 items-center gap-2 rounded-lg border border-dark-700/50 bg-dark-800/80 px-2.5 py-2 text-sm text-dark-300 transition-colors hover:border-dark-600 hover:text-dark-100 ${className ?? ''}`}
>
<svg
className="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={1.5}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75"
/>
</svg>
<span className="hidden sm:inline">{t('admin.referralNetwork.filters.title')}</span>
{hasActiveFilters && (
<span className="absolute -right-1 -top-1 h-2 w-2 rounded-full bg-accent-500" />
)}
</button>
);
}
// Filter panel content (shared between mobile/desktop)
const panelContent = ( const panelContent = (
<div className="space-y-4"> <div className="space-y-4">
{/* Campaigns */} {/* Campaigns */}
@@ -136,63 +106,94 @@ export function NetworkFilters({ data, className }: NetworkFiltersProps) {
); );
return ( return (
<div ref={panelRef} className={className}> <div ref={panelRef} className={`relative shrink-0 ${className ?? ''}`}>
{/* Desktop: inline dropdown */} {/* Trigger button — always rendered */}
<div className="hidden sm:block"> <button
<div className="w-64 rounded-xl border border-dark-700/50 bg-dark-900/95 p-4 backdrop-blur-md"> onClick={() => setIsOpen(!isOpen)}
<div className="mb-3 flex items-center justify-between"> aria-label={t('admin.referralNetwork.filters.title')}
<h3 className="text-sm font-semibold text-dark-100"> className={`relative flex items-center gap-2 rounded-lg border px-2.5 py-2 text-sm transition-colors ${
{t('admin.referralNetwork.filters.title')} isOpen
</h3> ? 'border-accent-500/50 bg-dark-800 text-dark-100'
<button : 'border-dark-700/50 bg-dark-800/80 text-dark-300 hover:border-dark-600 hover:text-dark-100'
onClick={() => setIsOpen(false)} }`}
aria-label={t('common.close')} >
className="text-dark-500 transition-colors hover:text-dark-300" <svg
> className="h-4 w-4"
<svg fill="none"
className="h-4 w-4" viewBox="0 0 24 24"
fill="none" stroke="currentColor"
viewBox="0 0 24 24" strokeWidth={1.5}
stroke="currentColor" >
strokeWidth={2} <path
> strokeLinecap="round"
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /> strokeLinejoin="round"
</svg> d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75"
</button> />
</div> </svg>
{panelContent} <span className="hidden sm:inline">{t('admin.referralNetwork.filters.title')}</span>
</div> {hasActiveFilters && (
</div> <span className="absolute -right-1 -top-1 h-2 w-2 rounded-full bg-accent-500" />
)}
</button>
{/* Mobile: full-width overlay below top bar */} {/* Desktop: absolute dropdown below button */}
<div className="fixed inset-x-0 top-0 z-50 sm:hidden"> {isOpen && (
{/* Backdrop */} <div className="absolute right-0 top-full z-50 mt-2 hidden sm:block">
<div className="fixed inset-0 bg-black/60" onClick={() => setIsOpen(false)} /> <div className="w-64 rounded-xl border border-dark-700/50 bg-dark-900/95 p-4 shadow-xl backdrop-blur-md">
{/* Panel */} <div className="mb-3 flex items-center justify-between">
<div className="relative mx-3 mt-3 rounded-xl border border-dark-700/50 bg-dark-900/95 p-4 backdrop-blur-md"> <h3 className="text-sm font-semibold text-dark-100">
<div className="mb-3 flex items-center justify-between"> {t('admin.referralNetwork.filters.title')}
<h3 className="text-sm font-semibold text-dark-100"> </h3>
{t('admin.referralNetwork.filters.title')} <button
</h3> onClick={() => setIsOpen(false)}
<button aria-label={t('common.close')}
onClick={() => setIsOpen(false)} className="text-dark-500 transition-colors hover:text-dark-300"
aria-label={t('common.close')}
className="rounded-lg p-1 text-dark-500 transition-colors hover:bg-dark-800 hover:text-dark-300"
>
<svg
className="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
> >
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /> <svg
</svg> className="h-4 w-4"
</button> fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{panelContent}
</div> </div>
{panelContent}
</div> </div>
</div> )}
{/* Mobile: full-screen overlay */}
{isOpen && (
<div className="fixed inset-x-0 top-0 z-50 sm:hidden">
<div className="fixed inset-0 bg-black/60" onClick={() => setIsOpen(false)} />
<div className="relative mx-3 mt-3 rounded-xl border border-dark-700/50 bg-dark-900/95 p-4 backdrop-blur-md">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-sm font-semibold text-dark-100">
{t('admin.referralNetwork.filters.title')}
</h3>
<button
onClick={() => setIsOpen(false)}
aria-label={t('common.close')}
className="rounded-lg p-1 text-dark-500 transition-colors hover:bg-dark-800 hover:text-dark-300"
>
<svg
className="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{panelContent}
</div>
</div>
)}
</div> </div>
); );
} }