refactor and simplify search bar positioning

This commit is contained in:
Pas 2025-11-08 12:39:28 -07:00
parent 544fe97c5e
commit b6a8028eff
2 changed files with 22 additions and 57 deletions

View file

@ -1,7 +1,7 @@
import classNames from "classnames"; import classNames from "classnames";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAsyncFn, useWindowSize } from "react-use"; import { useAsyncFn } from "react-use";
import { import {
base64ToBuffer, base64ToBuffer,
@ -33,6 +33,7 @@ import { RegisterCalloutPart } from "@/pages/parts/settings/RegisterCalloutPart"
import { SidebarPart } from "@/pages/parts/settings/SidebarPart"; import { SidebarPart } from "@/pages/parts/settings/SidebarPart";
import { PageTitle } from "@/pages/parts/util/PageTitle"; import { PageTitle } from "@/pages/parts/util/PageTitle";
import { AccountWithToken, useAuthStore } from "@/stores/auth"; import { AccountWithToken, useAuthStore } from "@/stores/auth";
import { useBannerSize } from "@/stores/banner";
import { useLanguageStore } from "@/stores/language"; import { useLanguageStore } from "@/stores/language";
import { usePreferencesStore } from "@/stores/preferences"; import { usePreferencesStore } from "@/stores/preferences";
import { useSubtitleStore } from "@/stores/subtitles"; import { useSubtitleStore } from "@/stores/subtitles";
@ -56,30 +57,13 @@ function SettingsLayout(props: {
const { t } = useTranslation(); const { t } = useTranslation();
const { isMobile } = useIsMobile(); const { isMobile } = useIsMobile();
const searchRef = useRef<HTMLInputElement>(null); const searchRef = useRef<HTMLInputElement>(null);
const { width: windowWidth, height: windowHeight } = useWindowSize(); const bannerSize = useBannerSize();
// Dynamic offset calculation like HeroPart // Navbar height is 80px (h-20)
const topSpacing = 16; // Base spacing const navbarHeight = 80;
const [stickyOffset, setStickyOffset] = useState(topSpacing); // On desktop: inline with navbar (same top position + 14px adjustment)
// On mobile: below navbar (navbar height + banner)
// Detect if running as a PWA on iOS const topOffset = isMobile ? navbarHeight + bannerSize : bannerSize + 14;
const isIOSPWA =
/iPad|iPhone|iPod/i.test(navigator.userAgent) &&
window.matchMedia("(display-mode: standalone)").matches;
const adjustedTopSpacing = isIOSPWA ? 60 : topSpacing;
const isLandscape = windowHeight < windowWidth && isIOSPWA;
const adjustedOffset = isLandscape ? -40 : 0;
useEffect(() => {
if (windowWidth > 1280) {
// On large screens the bar goes inline with the nav elements
setStickyOffset(adjustedTopSpacing);
} else {
// On smaller screens the bar goes below the nav elements
setStickyOffset(adjustedTopSpacing + 60 + adjustedOffset);
}
}, [adjustedOffset, adjustedTopSpacing, windowWidth]);
return ( return (
<WideContainer ultraWide classNames="overflow-visible"> <WideContainer ultraWide classNames="overflow-visible">
@ -87,7 +71,7 @@ function SettingsLayout(props: {
<div <div
className="fixed left-0 right-0 z-50" className="fixed left-0 right-0 z-50"
style={{ style={{
top: `${stickyOffset}px`, top: `${topOffset}px`,
}} }}
> >
<ThinContainer> <ThinContainer>

View file

@ -1,12 +1,12 @@
import classNames from "classnames"; import classNames from "classnames";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useRef, useState } from "react";
import Sticky from "react-sticky-el"; import Sticky from "react-sticky-el";
import { useWindowSize } from "react-use";
import { SearchBarInput } from "@/components/form/SearchBar"; import { SearchBarInput } from "@/components/form/SearchBar";
import { ThinContainer } from "@/components/layout/ThinContainer"; import { ThinContainer } from "@/components/layout/ThinContainer";
import { useSlashFocus } from "@/components/player/hooks/useSlashFocus"; import { useSlashFocus } from "@/components/player/hooks/useSlashFocus";
import { HeroTitle } from "@/components/text/HeroTitle"; import { HeroTitle } from "@/components/text/HeroTitle";
import { useIsMobile } from "@/hooks/useIsMobile";
import { useIsTV } from "@/hooks/useIsTv"; import { useIsTV } from "@/hooks/useIsTv";
import { useRandomTranslation } from "@/hooks/useRandomTranslation"; import { useRandomTranslation } from "@/hooks/useRandomTranslation";
import { useSearchQuery } from "@/hooks/useSearchQuery"; import { useSearchQuery } from "@/hooks/useSearchQuery";
@ -44,41 +44,22 @@ export function HeroPart({
const [search, setSearch, setSearchUnFocus] = searchParams; const [search, setSearch, setSearchUnFocus] = searchParams;
const [showBg, setShowBg] = useState(false); const [showBg, setShowBg] = useState(false);
const bannerSize = useBannerSize(); const bannerSize = useBannerSize();
const { isMobile } = useIsMobile();
const { isTV } = useIsTV();
const stickStateChanged = useCallback( const stickStateChanged = useCallback(
(isFixed: boolean) => { (isFixed: boolean) => {
setShowBg(isFixed); setShowBg(isFixed);
setIsSticky(isFixed); setIsSticky(isFixed);
}, },
[setShowBg, setIsSticky], [setIsSticky],
); );
const { width: windowWidth, height: windowHeight } = useWindowSize(); // Navbar height is 80px (h-20)
const navbarHeight = 80;
const { isTV } = useIsTV(); // On desktop: inline with navbar (same top position)
// On mobile: below navbar (navbar height + banner)
// Detect if running as a PWA on iOS const topOffset = isMobile ? navbarHeight + bannerSize : bannerSize + 14;
const isIOSPWA =
/iPad|iPhone|iPod/i.test(navigator.userAgent) &&
window.matchMedia("(display-mode: standalone)").matches;
const topSpacing = isIOSPWA ? 60 : 16;
const [stickyOffset, setStickyOffset] = useState(topSpacing);
const isLandscape = windowHeight < windowWidth && isIOSPWA;
const adjustedOffset = isLandscape
? -40 // landscape
: 0; // portrait
useEffect(() => {
if (windowWidth > 1280) {
// On large screens the bar goes inline with the nav elements
setStickyOffset(topSpacing);
} else {
// On smaller screens the bar goes below the nav elements
setStickyOffset(topSpacing + 60 + adjustedOffset);
}
}, [adjustedOffset, topSpacing, windowWidth]);
const time = getTimeOfDay(new Date()); const time = getTimeOfDay(new Date());
const title = randomT(`home.titles.${time}`); const title = randomT(`home.titles.${time}`);
@ -91,7 +72,7 @@ export function HeroPart({
<div <div
className={classNames( className={classNames(
"space-y-16 text-center", "space-y-16 text-center",
showTitle ? "mt-44" : `mt-4`, showTitle ? "mt-44" : "mt-4",
)} )}
> >
{showTitle && (!isTV || search.length === 0) ? ( {showTitle && (!isTV || search.length === 0) ? (
@ -102,9 +83,9 @@ export function HeroPart({
<div className="relative h-20 z-30"> <div className="relative h-20 z-30">
<Sticky <Sticky
topOffset={stickyOffset * -1 + bannerSize} topOffset={-topOffset}
stickyStyle={{ stickyStyle={{
paddingTop: `${stickyOffset + bannerSize}px`, paddingTop: `${topOffset}px`,
}} }}
onFixedToggle={stickStateChanged} onFixedToggle={stickStateChanged}
> >