Nawigacja mobilna - dolny pasek i menu w szufladzie
Deploy Polska Lokalnie / deploy (push) Successful in 17s
Deploy Polska Lokalnie / deploy (push) Successful in 17s
Gorne menu na telefonie mialo poziomy pasek przewijania, a najwazniejsze skroty nie mialy sie gdzie zmiescic. - dolny pasek (<= 760 px): Start, Szukaj, Dodaj, Ulubione, Konto - pasek dzialow (<= 1100 px) przenosi sie do szuflady pod przyciskiem menu - w naglowku na telefonie zostaja wiadomosci, dzwonek i menu - kafelki narzedzi schodza do dwoch i jednej kolumny - karty hero wracaja do przeplywu, zamiast nachodzic na liste korzysci - spis tresci stron prawnych nie rozpycha juz strony przez tabele Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+193
-18
@@ -32,6 +32,17 @@ const plLogoDark = new URL('./assets/pl_logo_dark.png', import.meta.url).href;
|
|||||||
// Podswietlenie aktywnej pozycji menu wyznacza NavLink na podstawie adresu.
|
// Podswietlenie aktywnej pozycji menu wyznacza NavLink na podstawie adresu.
|
||||||
const navLinkClass = ({ isActive }: { isActive: boolean }) => (isActive ? 'active' : '');
|
const navLinkClass = ({ isActive }: { isActive: boolean }) => (isActive ? 'active' : '');
|
||||||
|
|
||||||
|
// Glowne menu serwisu. Jedno zrodlo dla paska na desktopie i dla szuflady na telefonie -
|
||||||
|
// dzieki temu nowy dzial nie zniknie z ktorejs wersji nawigacji.
|
||||||
|
const MAIN_NAV: { to: string; label: string; icon: string }[] = [
|
||||||
|
{ to: ROUTES.buy, label: 'Kupuję', icon: 'search' },
|
||||||
|
{ to: ROUTES.rent, label: 'Wynajmuję', icon: 'key' },
|
||||||
|
{ to: ROUTES.sell, label: 'Sprzedaję', icon: 'tag' },
|
||||||
|
{ to: ROUTES.valuation, label: 'Wycena mieszkania', icon: 'calculator' },
|
||||||
|
{ to: ROUTES.services, label: 'Firmy i usługi', icon: 'hammer' },
|
||||||
|
{ to: ROUTES.guides, label: 'Poradniki', icon: 'document' },
|
||||||
|
];
|
||||||
|
|
||||||
type MessageThreadListing = {
|
type MessageThreadListing = {
|
||||||
listingId: number;
|
listingId: number;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -3319,6 +3330,7 @@ function App() {
|
|||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
|
<MobileNav />
|
||||||
<CookieBanner />
|
<CookieBanner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -23607,6 +23619,9 @@ function Header({
|
|||||||
const notificationPopoverRef = useRef<HTMLDivElement | null>(null);
|
const notificationPopoverRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false);
|
const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false);
|
||||||
const accountMenuRef = useRef<HTMLDivElement | null>(null);
|
const accountMenuRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
// Szuflada z pelnym menu - na telefonach i tabletach poziomy pasek dzialow sie nie miesci.
|
||||||
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
// Dane powiadomień z centralnego providera. W dzwonku pokazujemy nieprzeczytane (znikają po kliknięciu).
|
// Dane powiadomień z centralnego providera. W dzwonku pokazujemy nieprzeczytane (znikają po kliknięciu).
|
||||||
const displayNotifications = toDisplayNotifications(serverNotifications);
|
const displayNotifications = toDisplayNotifications(serverNotifications);
|
||||||
@@ -23668,6 +23683,33 @@ function Header({
|
|||||||
};
|
};
|
||||||
}, [isAccountMenuOpen]);
|
}, [isAccountMenuOpen]);
|
||||||
|
|
||||||
|
// Szuflada zamyka sie po przejsciu na inna trase i klawiszem Escape; przy otwartej
|
||||||
|
// blokujemy przewijanie strony pod spodem.
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMobileMenuOpen(false);
|
||||||
|
}, [location.pathname, location.search]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMobileMenuOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousOverflow = document.body.style.overflow;
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
const onEscape = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
setIsMobileMenuOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', onEscape);
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = previousOverflow;
|
||||||
|
window.removeEventListener('keydown', onEscape);
|
||||||
|
};
|
||||||
|
}, [isMobileMenuOpen]);
|
||||||
|
|
||||||
// Pozycje menu sa linkami; zamkniecie popovera to efekt uboczny kliknięcia, nie nawigacja.
|
// Pozycje menu sa linkami; zamkniecie popovera to efekt uboczny kliknięcia, nie nawigacja.
|
||||||
const closeAccountMenu = () => setIsAccountMenuOpen(false);
|
const closeAccountMenu = () => setIsAccountMenuOpen(false);
|
||||||
|
|
||||||
@@ -23688,24 +23730,11 @@ function Header({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="nav" aria-label="Główna nawigacja">
|
<nav className="nav" aria-label="Główna nawigacja">
|
||||||
<NavLink to={ROUTES.buy} className={navLinkClass}>
|
{MAIN_NAV.map((item) => (
|
||||||
Kupuję
|
<NavLink key={item.to} to={item.to} className={navLinkClass}>
|
||||||
</NavLink>
|
{item.label}
|
||||||
<NavLink to={ROUTES.rent} className={navLinkClass}>
|
</NavLink>
|
||||||
Wynajmuję
|
))}
|
||||||
</NavLink>
|
|
||||||
<NavLink to={ROUTES.sell} className={navLinkClass}>
|
|
||||||
Sprzedaję
|
|
||||||
</NavLink>
|
|
||||||
<NavLink to={ROUTES.valuation} className={navLinkClass}>
|
|
||||||
Wycena mieszkania
|
|
||||||
</NavLink>
|
|
||||||
<NavLink to={ROUTES.services} className={navLinkClass}>
|
|
||||||
Firmy i usługi
|
|
||||||
</NavLink>
|
|
||||||
<NavLink to={ROUTES.guides} className={navLinkClass}>
|
|
||||||
Poradniki
|
|
||||||
</NavLink>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="header-actions">
|
<div className="header-actions">
|
||||||
@@ -23837,11 +23866,157 @@ function Header({
|
|||||||
<span>Dodaj ogłoszenie za darmo</span>
|
<span>Dodaj ogłoszenie za darmo</span>
|
||||||
<small>0 zł • Bez prowizji</small>
|
<small>0 zł • Bez prowizji</small>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="header-burger"
|
||||||
|
type="button"
|
||||||
|
aria-label="Menu serwisu"
|
||||||
|
aria-expanded={isMobileMenuOpen}
|
||||||
|
onClick={() => setIsMobileMenuOpen(true)}
|
||||||
|
>
|
||||||
|
<Icon name="rows" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isMobileMenuOpen && (
|
||||||
|
<div className="mobile-menu-overlay" onClick={() => setIsMobileMenuOpen(false)}>
|
||||||
|
<nav
|
||||||
|
className="mobile-menu"
|
||||||
|
aria-label="Menu serwisu"
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="mobile-menu-head">
|
||||||
|
<strong>Menu</strong>
|
||||||
|
<button type="button" aria-label="Zamknij" onClick={() => setIsMobileMenuOpen(false)}>×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mobile-menu-group">
|
||||||
|
{MAIN_NAV.map((item) => (
|
||||||
|
<NavLink key={item.to} to={item.to} className={navLinkClass}>
|
||||||
|
<Icon name={item.icon} />
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mobile-menu-group">
|
||||||
|
<h3>Narzędzia</h3>
|
||||||
|
<NavLink to={ROUTES.map} className={navLinkClass}>
|
||||||
|
<Icon name="pin" />
|
||||||
|
<span>Mapa ofert</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={ROUTES.priceHistory} className={navLinkClass}>
|
||||||
|
<Icon name="chart" />
|
||||||
|
<span>Historia cen</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={ROUTES.creditCalculator} className={navLinkClass}>
|
||||||
|
<Icon name="percent" />
|
||||||
|
<span>Kalkulator zdolności</span>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mobile-menu-group">
|
||||||
|
<h3>Moje konto</h3>
|
||||||
|
{isLoggedIn ? (
|
||||||
|
<>
|
||||||
|
<NavLink to={ROUTES.account} end className={navLinkClass}>
|
||||||
|
<Icon name="house" />
|
||||||
|
<span>Panel główny</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={ROUTES.accountListings} className={navLinkClass}>
|
||||||
|
<Icon name="document" />
|
||||||
|
<span>Moje oferty</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={ROUTES.messages} className={navLinkClass}>
|
||||||
|
<Icon name="message" />
|
||||||
|
<span>Wiadomości</span>
|
||||||
|
{messagesUnreadCount > 0 && <b>{messagesUnreadCount > 99 ? '99+' : messagesUnreadCount}</b>}
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={ROUTES.notifications} className={navLinkClass}>
|
||||||
|
<Icon name="bell" />
|
||||||
|
<span>Powiadomienia</span>
|
||||||
|
{unreadCount > 0 && <b>{unreadCount > 99 ? '99+' : unreadCount}</b>}
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={ROUTES.accountSettings} className={navLinkClass}>
|
||||||
|
<Icon name="gear" />
|
||||||
|
<span>Dane i ustawienia</span>
|
||||||
|
</NavLink>
|
||||||
|
{isAdmin && (
|
||||||
|
<NavLink to={ROUTES.admin} className={navLinkClass}>
|
||||||
|
<Icon name="shield" />
|
||||||
|
<span>Panel admina</span>
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="mobile-menu-logout"
|
||||||
|
onClick={() => {
|
||||||
|
setIsMobileMenuOpen(false);
|
||||||
|
onLogout();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name="lock" />
|
||||||
|
<span>Wyloguj</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<NavLink to={ROUTES.login} className={navLinkClass}>
|
||||||
|
<Icon name="user" />
|
||||||
|
<span>Zaloguj się</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={ROUTES.register} className={navLinkClass}>
|
||||||
|
<Icon name="plus" />
|
||||||
|
<span>Załóż konto</span>
|
||||||
|
</NavLink>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link className="mobile-menu-cta" to={ROUTES.add}>
|
||||||
|
<Icon name="plus" />
|
||||||
|
<span>Dodaj ogłoszenie za darmo</span>
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dolny pasek nawigacji na telefonach - pieć najczesciej uzywanych miejsc w serwisie.
|
||||||
|
// Wyrozniony srodkowy przycisk prowadzi do dodawania ogloszenia (gosc trafi na logowanie).
|
||||||
|
function MobileNav() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="mobile-nav" aria-label="Nawigacja mobilna">
|
||||||
|
<NavLink to={ROUTES.home} end className={navLinkClass}>
|
||||||
|
<Icon name="house" />
|
||||||
|
<span>Start</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={ROUTES.buy} className={navLinkClass}>
|
||||||
|
<Icon name="search" />
|
||||||
|
<span>Szukaj</span>
|
||||||
|
</NavLink>
|
||||||
|
<Link className="mobile-nav-add" to={ROUTES.add} aria-label="Dodaj ogłoszenie">
|
||||||
|
<span className="mobile-nav-add-circle">
|
||||||
|
<Icon name="plus" />
|
||||||
|
</span>
|
||||||
|
<span>Dodaj</span>
|
||||||
|
</Link>
|
||||||
|
<NavLink to={ROUTES.favorites} className={navLinkClass}>
|
||||||
|
<Icon name="heart" />
|
||||||
|
<span>Ulubione</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to={user ? ROUTES.account : ROUTES.login} end className={navLinkClass}>
|
||||||
|
<Icon name="user" />
|
||||||
|
<span>{user ? 'Konto' : 'Zaloguj'}</span>
|
||||||
|
</NavLink>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function NotificationsPage({ onOpenNotification }: { onOpenNotification: (notification: DisplayNotification) => void }) {
|
function NotificationsPage({ onOpenNotification }: { onOpenNotification: (notification: DisplayNotification) => void }) {
|
||||||
const [activeTab, setActiveTab] = useState<'all' | NotificationCategory>('all');
|
const [activeTab, setActiveTab] = useState<'all' | NotificationCategory>('all');
|
||||||
const { notifications: serverNotifications, unreadCount, markAllRead, clearAll } = useNotifications();
|
const { notifications: serverNotifications, unreadCount, markAllRead, clearAll } = useNotifications();
|
||||||
|
|||||||
+309
-31
@@ -144,6 +144,13 @@ svg {
|
|||||||
padding: 11px 14px;
|
padding: 11px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Przycisk menu na ciemnym pasku administratora. */
|
||||||
|
.header-admin .header-burger {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
color: #dbe4f0;
|
||||||
|
}
|
||||||
|
|
||||||
.brand-group {
|
.brand-group {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -366,6 +373,219 @@ svg {
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Nawigacja mobilna --------------------------------------------------------------
|
||||||
|
Na waskich ekranach poziomy pasek dzialow sie nie miesci. Zamiast przewijania w bok:
|
||||||
|
przycisk z trzema kreskami otwiera szuflade z pelnym menu (<= 1100 px), a pieć
|
||||||
|
najczesciej uzywanych miejsc siedzi w dolnym pasku (<= 760 px). Oba elementy sa
|
||||||
|
domyslnie ukryte i wlaczaja sie w regulach @media nizej w pliku. */
|
||||||
|
.header-burger {
|
||||||
|
align-items: center;
|
||||||
|
background: #f1f5fa;
|
||||||
|
border: 1px solid #dce5f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #16233b;
|
||||||
|
display: none;
|
||||||
|
height: 42px;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
width: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-burger svg {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-overlay {
|
||||||
|
background: rgba(9, 20, 38, 0.5);
|
||||||
|
inset: 0;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu {
|
||||||
|
animation: mobile-menu-in 0.18s ease-out;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: -18px 0 40px rgba(9, 20, 38, 0.25);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: auto;
|
||||||
|
max-width: 340px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px 16px 26px;
|
||||||
|
width: 86%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mobile-menu-in {
|
||||||
|
from { transform: translateX(16px); opacity: 0.4; }
|
||||||
|
to { transform: translateX(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-head {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-head strong {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-head button {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: #526171;
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-group h3 {
|
||||||
|
color: #7b8a9c;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
margin: 14px 0 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-group a,
|
||||||
|
.mobile-menu-logout {
|
||||||
|
align-items: center;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #111a2e;
|
||||||
|
display: flex;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 10px;
|
||||||
|
text-align: left;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-group a svg,
|
||||||
|
.mobile-menu-logout svg {
|
||||||
|
color: #526171;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-group a.active {
|
||||||
|
background: rgba(18, 167, 100, 0.1);
|
||||||
|
color: #0f8f55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-group a.active svg {
|
||||||
|
color: #0f8f55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-group a b {
|
||||||
|
background: #e4372e;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 900;
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 2px 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-logout,
|
||||||
|
.mobile-menu-logout svg {
|
||||||
|
color: #c53a2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-cta {
|
||||||
|
align-items: center;
|
||||||
|
background: #089858;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 900;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 22px;
|
||||||
|
padding: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-cta svg {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dolny pasek: nad trescia strony, ale pod oknami modalnymi (z-index 50 i wyzej). */
|
||||||
|
.mobile-nav {
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgba(255, 255, 255, 0.97);
|
||||||
|
border-top: 1px solid #e2e9f2;
|
||||||
|
bottom: 0;
|
||||||
|
display: none;
|
||||||
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
|
left: 0;
|
||||||
|
padding: 8px 0 max(8px, env(safe-area-inset-bottom));
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav a {
|
||||||
|
align-items: center;
|
||||||
|
color: #526171;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav a svg {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav a span {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav a.active {
|
||||||
|
color: #0f8f55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav-add {
|
||||||
|
color: #0f8f55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav-add-circle {
|
||||||
|
align-items: center;
|
||||||
|
background: #089858;
|
||||||
|
border: 4px solid #ffffff;
|
||||||
|
border-radius: 999px;
|
||||||
|
box-shadow: 0 8px 18px rgba(8, 152, 88, 0.35);
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
height: 52px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: -26px;
|
||||||
|
width: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav-add-circle svg {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav-add:active .mobile-nav-add-circle {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
.hero {
|
.hero {
|
||||||
min-height: 700px;
|
min-height: 700px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -806,6 +1026,21 @@ svg {
|
|||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cztery kolumny mieszcza sie tylko na duzym ekranie - nizej kafelki schodza do dwoch,
|
||||||
|
a na telefonie do jednej. Bez tego na 390 px kazdy kafelek mial ok. 70 px szerokosci. */
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.tool-grid {
|
||||||
|
gap: 14px;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.tool-grid {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tool-card,
|
.tool-card,
|
||||||
.free-card,
|
.free-card,
|
||||||
.listing-card {
|
.listing-card {
|
||||||
@@ -12399,32 +12634,39 @@ a.city-card:hover {
|
|||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
.header {
|
.header {
|
||||||
grid-template-columns: 1fr auto;
|
gap: 12px;
|
||||||
height: auto;
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
padding-bottom: 18px;
|
height: 72px;
|
||||||
padding-top: 18px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pasek dzialow nie miesci sie w jednej linii - caly przenosi sie do szuflady. */
|
||||||
.nav {
|
.nav {
|
||||||
grid-column: 1 / -1;
|
display: none;
|
||||||
justify-content: flex-start;
|
|
||||||
overflow-x: auto;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav button, .nav a {
|
.header-burger {
|
||||||
padding: 8px 0 12px;
|
display: inline-flex;
|
||||||
}
|
|
||||||
|
|
||||||
.nav button.active::after, .nav a.active::after {
|
|
||||||
bottom: 3px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-actions {
|
.header-actions {
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-text span {
|
/* Pasek administratora ma wlasne, luzniejsze reguly na szerokim ekranie - na telefonie
|
||||||
|
ikony musza zostac w jednej linii, inaczej przycisk menu spada pod naglowek. */
|
||||||
|
.header-admin {
|
||||||
|
min-height: 72px;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-admin .header-actions {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Podpisy skrotow ustepuja miejsca, ale awatar konta to tez <span> - musi zostac,
|
||||||
|
inaczej po zalogowaniu w naglowku wisi sama strzalka menu. */
|
||||||
|
.icon-text span:not(.avatar) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12716,9 +12958,27 @@ a.city-card:hover {
|
|||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
height: 66px;
|
||||||
padding: 16px 18px;
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dolny pasek przejmuje najwazniejsze skroty; tresc dostaje na niego miejsce,
|
||||||
|
a baner zgody podnosi sie ponad pasek, zeby go nie zaslanial. */
|
||||||
|
.mobile-nav {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-shell {
|
||||||
|
padding-bottom: 66px;
|
||||||
|
/* `clip` zamiast `hidden`: ucina przypadkowe wystawanie w bok (na telefonie potrafilo
|
||||||
|
dac 1 px przewijania), ale nie tworzy kontenera przewijania, wiec naglowek
|
||||||
|
nadal trzyma sie gory (position: sticky). */
|
||||||
|
overflow-x: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-banner {
|
||||||
|
bottom: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-field-num,
|
.map-field-num,
|
||||||
@@ -12737,8 +12997,23 @@ a.city-card:hover {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Na telefonie w naglowku zostaja tylko skroty, ktorych nie ma nigdzie indziej:
|
||||||
|
wiadomosci, dzwonek, konto i menu. Ulubione sa w dolnym pasku, odznaka ADMIN
|
||||||
|
powtarza sie w panelu, wiec tu ustepuja miejsca. */
|
||||||
.brand strong {
|
.brand strong {
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo-img {
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Awatar z rozwijanym menu konta jest zbedny - caly panel konta jest w szufladzie
|
||||||
|
pod przyciskiem menu, a skrot "Konto" w dolnym pasku. */
|
||||||
|
.brand-admin-badge,
|
||||||
|
.favorites-shortcut,
|
||||||
|
.account-menu-wrap {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-actions {
|
.header-actions {
|
||||||
@@ -12769,8 +13044,12 @@ a.city-card:hover {
|
|||||||
padding-right: 18px;
|
padding-right: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Karty "Zainteresowani kupujący" i "360°" stoja na sztywnych wspolrzednych, ktore na
|
||||||
|
telefonie nachodzily na liste korzysci i na siebie nawzajem. Zamiast dobierac kolejne
|
||||||
|
piksele wracaja do normalnego przeplywu pod trescia, a sekcja rosnie do ich wysokosci. */
|
||||||
.hero-inner {
|
.hero-inner {
|
||||||
height: 760px;
|
height: auto;
|
||||||
|
padding-bottom: 118px;
|
||||||
padding-top: 44px;
|
padding-top: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12789,17 +13068,14 @@ a.city-card:hover {
|
|||||||
margin-top: 34px;
|
margin-top: 34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-card {
|
.ai-card,
|
||||||
left: 18px;
|
|
||||||
right: auto;
|
|
||||||
top: 388px;
|
|
||||||
width: min(270px, calc(100% - 36px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tour-card {
|
.tour-card {
|
||||||
left: 18px;
|
left: auto;
|
||||||
|
margin-top: 18px;
|
||||||
|
position: static;
|
||||||
right: auto;
|
right: auto;
|
||||||
top: 590px;
|
top: auto;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-wrap {
|
.search-wrap {
|
||||||
@@ -13259,7 +13535,6 @@ a.city-card:hover {
|
|||||||
.tour-card {
|
.tour-card {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
top: 610px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.free-card button,
|
.free-card button,
|
||||||
@@ -33022,6 +33297,9 @@ a.listing-detail-back {
|
|||||||
.legal-main {
|
.legal-main {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
/* Bez minmax(0, ...) szeroka tabela (min-width 620 px) rozpychala kolumne i cala strone
|
||||||
|
na telefonie - tak zostaje w swoim kontenerze i to on przewija sie w bok. */
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legal-card {
|
.legal-card {
|
||||||
@@ -33148,7 +33426,7 @@ a.listing-detail-back {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.legal-layout {
|
.legal-layout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legal-toc {
|
.legal-toc {
|
||||||
|
|||||||
@@ -689,3 +689,62 @@ test.describe('Prywatnosc', () => {
|
|||||||
await expect(page.locator('.listing-detail-tour-embed iframe')).toHaveCount(1);
|
await expect(page.locator('.listing-detail-tour-embed iframe')).toHaveCount(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Telefon: poziome menu nie miesci sie w naglowku, wiec dzialy sa w szufladzie,
|
||||||
|
// a najczestsze miejsca w dolnym pasku. Kazda pozycja to link ze zmiana adresu.
|
||||||
|
test.describe('Nawigacja mobilna', () => {
|
||||||
|
test.use({ viewport: { width: 390, height: 844 } });
|
||||||
|
|
||||||
|
test('naglowek nie przewija sie w bok, menu glowne chowa sie w szufladzie', async ({ page }) => {
|
||||||
|
await goto(page, ROUTES.home);
|
||||||
|
await expect(page.locator('.nav')).toBeHidden();
|
||||||
|
|
||||||
|
const overflow = await page.evaluate(() => {
|
||||||
|
const de = document.documentElement;
|
||||||
|
return de.scrollWidth - de.clientWidth;
|
||||||
|
});
|
||||||
|
expect(overflow).toBeLessThanOrEqual(0);
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Menu serwisu' }).click();
|
||||||
|
await expect(page.locator('.mobile-menu')).toBeVisible();
|
||||||
|
await page.locator('.mobile-menu').getByRole('link', { name: 'Wynajmuję' }).click();
|
||||||
|
await expect(page).toHaveURL(new RegExp(`${ROUTES.rent}$`));
|
||||||
|
await expect(page.locator('.mobile-menu')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dolny pasek prowadzi pod wlasne adresy', async ({ page }) => {
|
||||||
|
await goto(page, ROUTES.home);
|
||||||
|
// Baner zgody stoi nad dolnym paskiem - na telefonie trzeba go najpierw zamknac.
|
||||||
|
await page.getByRole('button', { name: 'Tylko niezbędne' }).click();
|
||||||
|
const bar = page.locator('.mobile-nav');
|
||||||
|
await expect(bar).toBeVisible();
|
||||||
|
|
||||||
|
await bar.getByRole('link', { name: 'Szukaj' }).click();
|
||||||
|
await expect(page).toHaveURL(new RegExp(`${ROUTES.buy}$`));
|
||||||
|
|
||||||
|
await bar.getByRole('link', { name: 'Dodaj ogłoszenie' }).click();
|
||||||
|
// Dodawanie wymaga logowania - goscia przejmuje ProtectedRoute.
|
||||||
|
await expect(page).toHaveURL(new RegExp(`${ROUTES.login}$`));
|
||||||
|
|
||||||
|
await bar.getByRole('link', { name: 'Start' }).click();
|
||||||
|
await expect(page).toHaveURL(/\/$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dolny pasek nie zaslania stopki', async ({ page }) => {
|
||||||
|
await goto(page, ROUTES.buy);
|
||||||
|
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||||
|
await page.waitForTimeout(400);
|
||||||
|
|
||||||
|
const footerBottom = await page.locator('.site-footer').evaluate((el) => el.getBoundingClientRect().bottom);
|
||||||
|
const barTop = await page.locator('.mobile-nav').evaluate((el) => el.getBoundingClientRect().top);
|
||||||
|
expect(footerBottom).toBeLessThanOrEqual(barTop + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('na desktopie nie ma dolnego paska ani przycisku menu', async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 1440, height: 900 });
|
||||||
|
await goto(page, ROUTES.home);
|
||||||
|
await expect(page.locator('.mobile-nav')).toBeHidden();
|
||||||
|
await expect(page.locator('.header-burger')).toBeHidden();
|
||||||
|
await expect(page.locator('.nav')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user