feat(tender-details): improve scrolling behavior and header offset management

- Introduced a new constant for the tender section scroll offset to enhance scrolling behavior when navigating to sections.
- Updated the scroll-to-section logic to account for the sticky header and added a small gap for better visual alignment.
- Refactored the useTenderSectionScrollSpy hook to utilize the new scroll offset, ensuring consistent behavior across sections.
- Improved code organization by separating scroll offset calculations into a dedicated function.
This commit is contained in:
AmirReza Jamali
2026-05-16 09:28:22 +03:30
parent 7aa13bb904
commit f79348312b
3 changed files with 32 additions and 4 deletions
+5 -1
View File
@@ -13,7 +13,11 @@ export const TENDER_SECTION_IDS = {
aiSummary: "tender-section-ai-summary", aiSummary: "tender-section-ai-summary",
} as const; } as const;
export const HEADER_SCROLL_MARGIN = "scroll-mt-[5.5rem]"; /** Sticky app header clearance — keep in sync with `getTenderSectionScrollOffset`. */
export const TENDER_SECTION_SCROLL_OFFSET_PX = 96;
/** For hash / native scroll-into-view (matches offset above). */
export const HEADER_SCROLL_MARGIN = "scroll-mt-24";
/** Body area only: center content in the card; ShowcaseSection `title` is unchanged */ /** Body area only: center content in the card; ShowcaseSection `title` is unchanged */
export const SHOWCASE_CENTERED_BODY = { export const SHOWCASE_CENTERED_BODY = {
@@ -1,4 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getTenderSectionScrollOffset } from "../scroll-to-section";
export function useTenderSectionScrollSpy(sectionIds: string[]) { export function useTenderSectionScrollSpy(sectionIds: string[]) {
const [activeId, setActiveId] = useState(sectionIds[0] ?? ""); const [activeId, setActiveId] = useState(sectionIds[0] ?? "");
@@ -17,7 +18,7 @@ export function useTenderSectionScrollSpy(sectionIds: string[]) {
if (sectionIds.length === 0) return; if (sectionIds.length === 0) return;
const pickActive = () => { const pickActive = () => {
const offset = 96; const offset = getTenderSectionScrollOffset();
const scrollY = window.scrollY; const scrollY = window.scrollY;
const viewportHeight = window.innerHeight; const viewportHeight = window.innerHeight;
const documentHeight = Math.max( const documentHeight = Math.max(
+25 -2
View File
@@ -1,11 +1,34 @@
import { TENDER_SECTION_SCROLL_OFFSET_PX } from "./constants";
const SECTION_SCROLL_GAP_PX = 8;
/** Pixels to leave above the section top (sticky header + small gap). */
export function getTenderSectionScrollOffset(): number {
if (typeof document === "undefined") {
return TENDER_SECTION_SCROLL_OFFSET_PX + SECTION_SCROLL_GAP_PX;
}
const header = document.querySelector("header");
const headerHeight = header?.getBoundingClientRect().height ?? 0;
return (
Math.max(TENDER_SECTION_SCROLL_OFFSET_PX, headerHeight) +
SECTION_SCROLL_GAP_PX
);
}
export function scrollToTenderSection(id: string) { export function scrollToTenderSection(id: string) {
const el = document.getElementById(id); const el = document.getElementById(id);
if (!el) return; if (!el) return;
const reduceMotion = const reduceMotion =
typeof window !== "undefined" && typeof window !== "undefined" &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches; window.matchMedia("(prefers-reduced-motion: reduce)").matches;
el.scrollIntoView({
const offset = getTenderSectionScrollOffset();
const top =
el.getBoundingClientRect().top + window.scrollY - offset;
window.scrollTo({
top: Math.max(0, top),
behavior: reduceMotion ? "auto" : "smooth", behavior: reduceMotion ? "auto" : "smooth",
block: "start",
}); });
} }