'use client';
import { OptionalLink } from '@components/DocPage/DocNavList';
import { SearchTrigger } from '@components/DocPage/Search/SearchTrigger';
import {
Accordion,
Button,
ClickOutsideDiv,
Container,
KeyboardShortcut,
MaxWidthSmoother,
PopoverStatic,
} from '@intlayer/design-system';
import { useDevice } from '@intlayer/design-system/hooks';
import { cn } from '@utils/cn';
import { ArrowLeftToLine } from 'lucide-react';
import { useIntlayer } from 'next-intlayer';
import { type FC, useState } from 'react';
import { useScrollPositionPersistence } from '@/hooks/useScrollPositionPersistence';
import { PagesRoutes } from '@/Routes';
import type { Section } from './types';
type BlogNavListProps = {
blogData: Section;
activeSlugs: string[];
};
export const BlogNavListContent: FC<BlogNavListProps> = ({
blogData,
activeSlugs,
}) => {
const { docButton } = useIntlayer('blog-nav-list');
const navRef = useScrollPositionPersistence<HTMLElement>(
'blog-nav-scroll-position'
);
return (
<nav
ref={navRef}
className="m-auto flex max-h-[calc(100vh-8.2rem)] min-w-40 max-w-xl flex-col gap-5 overflow-auto px-3 pt-8 pb-20"
>
{Object.keys(blogData).map((key1) => {
const section1Data = blogData[key1];
const sectionDefault = section1Data.default;
const subSections = section1Data.subSections;
const slugs = sectionDefault?.slugs ?? [];
// Check if this section's own slugs match
const isSelfActive =
slugs.length > 0 &&
slugs.every((segment, index) => segment === activeSlugs[index]);
// Check if any subsection at any level matches
const isSubSectionActive = Object.values(subSections ?? {}).some(
(subSection2) => {
const subSlugs2 = subSection2.default?.slugs ?? [];
const isLevel2Active =
subSlugs2.length > 0 &&
subSlugs2.every(
(segment, index) => segment === activeSlugs[index]
);
// Check level 3 subsections
const isLevel3Active = Object.values(
subSection2.subSections ?? {}
).some((subSection3) => {
const subSlugs3 = subSection3.default?.slugs ?? [];
return (
subSlugs3.length > 0 &&
subSlugs3.every(
(segment, index) => segment === activeSlugs[index]
)
);
});
return isLevel2Active || isLevel3Active;
}
);
const isActive = isSelfActive || isSubSectionActive;
return (
<div key={key1}>
<OptionalLink
href={sectionDefault?.relativeUrl ?? ''}
label={key1}
isActive={isSelfActive && !isSubSectionActive}
>
{section1Data.title}
</OptionalLink>
{subSections && Object.keys(subSections).length > 0 && (
<ul className="mt-4 flex flex-col gap-4 border-neutral border-l-[0.5px] p-1 text-base">
{Object.keys(subSections).map((key2) => {
const section2Data = subSections[key2];
const sectionDefault = section2Data.default;
const subSections2 = section2Data.subSections;
const hasSubsections =
subSections2 && Object.keys(subSections2).length > 0;
const slugs = sectionDefault?.slugs ?? [];
// Check if this section's own slugs match
const isSelfActive =
slugs.length > 0 &&
slugs.every(
(segment, index) => segment === activeSlugs[index]
);
// Check if any subsection's slugs match (level 3)
const isSubSectionActive = Object.values(
subSections2 ?? {}
).some((subSection) => {
const subSlugs = subSection.default?.slugs ?? [];
return (
subSlugs.length > 0 &&
subSlugs.every(
(segment, index) => segment === activeSlugs[index]
)
);
});
const isActive = isSelfActive || isSubSectionActive;
return (
<li key={key2}>
{hasSubsections ? (
<Accordion
header={
<OptionalLink
label={key2}
href={sectionDefault?.relativeUrl ?? ''}
isActive={isSelfActive && !isSubSectionActive}
className="block w-full flex-row items-center text-nowrap p-2 text-left text-sm transition-colors hover:text-text"
>
{section2Data?.title}
</OptionalLink>
}
label={key2}
isOpen={isActive ? true : undefined}
className="py-0! pl-0!"
isActive={isSubSectionActive}
>
<div className="pl-3 text-sm">
{subSections2 &&
Object.keys(subSections2).length > 0 && (
<div className="flex flex-col items-start gap-2 p-1 text-neutral transition-colors hover:text-text">
{Object.keys(subSections2).map((key3) => {
const section3Data = subSections2[key3];
const slugs =
section3Data.default?.slugs ?? [];
const isActive =
slugs.length > 0 &&
slugs.every(
(segment, index) =>
segment === activeSlugs[index]
);
return (
<OptionalLink
key={key3}
label={key3}
href={
section3Data.default?.relativeUrl ??
''
}
isActive={isActive}
className="block w-full flex-row items-center text-nowrap p-2 text-left text-xs transition-colors hover:text-text"
>
{section3Data.title}
</OptionalLink>
);
})}
</div>
)}
</div>
</Accordion>
) : (
<OptionalLink
href={sectionDefault?.relativeUrl ?? ''}
className="block w-full flex-row items-center text-nowrap p-2 text-left text-sm transition-colors hover:text-text"
label={key2}
isActive={isActive}
>
{section2Data?.title}
</OptionalLink>
)}
</li>
);
})}
</ul>
)}
</div>
);
})}
<div>
<OptionalLink href={PagesRoutes.Doc} label={docButton.label.value}>
{docButton?.text}
</OptionalLink>
</div>
</nav>
);
};
export const BlogNavList: FC<BlogNavListProps> = ({
blogData,
activeSlugs,
}) => {
const { isMobile } = useDevice();
const [isHidden, setIsHidden] = useState(isMobile);
const { collapseButton } = useIntlayer('blog-nav-list');
return (
<ClickOutsideDiv
className="top-0 left-0 z-10 flex h-full justify-end max-md:fixed"
onClickOutSide={() => {
if (isMobile) {
setIsHidden(true);
}
}}
>
<Container className="h-full" roundedSize="none" transparency="sm">
<div className="relative h-full max-w-80">
<Container
transparency="sm"
className="sticky top-[3.6rem] z-10 m-auto pt-4"
roundedSize="none"
>
<div
className={cn([
'relative m-auto flex w-full flex-row items-center justify-end gap-2 px-2',
isHidden && 'flex-col-reverse',
!isHidden && 'pl-6',
])}
>
<SearchTrigger isMini={isHidden} />
<PopoverStatic identifier="blog-nav-collapse">
<Button
Icon={ArrowLeftToLine}
size="icon-md"
variant="hoverable"
color="text"
label={collapseButton.label.value}
className={cn([
'transition-transform',
isHidden && 'rotate-180',
])}
onClick={() => setIsHidden((isHidden) => !isHidden)}
/>
<PopoverStatic.Detail identifier="blog-nav-collapse">
<KeyboardShortcut
shortcut="Alt + ArrowLeft"
onTriggered={() => setIsHidden((isHidden) => !isHidden)}
size="sm"
/>
</PopoverStatic.Detail>
</PopoverStatic>
<div className="absolute bottom-0 left-0 h-8 w-full translate-y-full bg-linear-to-b from-card/90 backdrop-blur" />
</div>
</Container>
<div className="sticky top-28 pt-0">
<MaxWidthSmoother isHidden={isHidden ?? false}>
<div className="relative overflow-hidden">
<BlogNavListContent
blogData={blogData}
activeSlugs={activeSlugs}
/>
</div>
</MaxWidthSmoother>
</div>
</div>
</Container>
</ClickOutsideDiv>
);
};
export default BlogNavList;