[web] interviews/sidebar: allow single and multiple types

This commit is contained in:
Yangshun 2024-11-16 19:51:20 +08:00
parent 597ce3711f
commit bf59fe68ee
4 changed files with 79 additions and 22 deletions

View File

@ -83,5 +83,5 @@ function useBlogSidebarNavigation() {
export default function BlogSidebar() {
const navigation = useBlogSidebarNavigation();
return <SidebarLinksSection items={navigation} size="md" />;
return <SidebarLinksSection items={navigation} size="md" type="single" />;
}

View File

@ -189,14 +189,14 @@ export function SidebarExpanded({
<div className="h-0 grow overflow-auto">
<ScrollArea>
{renderTopAddonElements?.(fadeInClass)}
<SidebarLinksSection items={startItems} size="md" />
<SidebarLinksSection items={startItems} size="md" type="single" />
</ScrollArea>
</div>
<div className={clsx('flex flex-col gap-y-4', fadeInClass)}>
{endItems.length > 0 && (
<>
<Divider />
<SidebarLinksSection items={endItems} size="md" />
<SidebarLinksSection items={endItems} size="md" type="single" />
</>
)}
{renderBottomAddonElements?.(fadeInClass)}

View File

@ -209,13 +209,25 @@ function SidebarLinks({
export default function SidebarLinksSection({
items,
size,
type,
}: Readonly<{
items: ReadonlyArray<SidebarLinkEntity>;
size: SidebarSize;
type: React.ComponentProps<typeof AccordionPrimitive.Root>['type'];
}>) {
const { pathname } = useI18nPathname();
// `single` type
const [openSection, setOpenSection] = useState<string | null>(null);
// `multiple` type
const [manuallyOpenSections, setManuallyOpenSections] = useState<
ReadonlyArray<string>
>([]);
const [automaticOpenSections, setAutomaticOpenSections] = useState<
ReadonlyArray<string>
>([]);
useEffect(() => {
const activeValue = (() => {
for (const item of items) {
@ -230,29 +242,70 @@ export default function SidebarLinksSection({
})();
if (activeValue) {
setOpenSection(activeValue);
if (type === 'single') {
setOpenSection(activeValue);
} else {
setAutomaticOpenSections([activeValue]);
}
}
}, [items, pathname]);
}, [items, pathname, type]);
const className = clsx('flex flex-col gap-y-2');
const contents = (
<ul>
{items.map((item) => (
<SidebarLinks
key={item.label}
item={item}
size={size}
onToggle={() => {
if (type === 'single') {
setOpenSection(openSection === item.label ? null : item.label);
} else {
const inAutomaticOpen = automaticOpenSections.includes(
item.label,
);
if (inAutomaticOpen) {
setAutomaticOpenSections(
automaticOpenSections.filter((label) => label !== item.label),
);
}
if (manuallyOpenSections.includes(item.label)) {
setManuallyOpenSections(
manuallyOpenSections.filter((label) => label !== item.label),
);
} else if (!inAutomaticOpen) {
setManuallyOpenSections([...manuallyOpenSections, item.label]);
}
}
}}
/>
))}
</ul>
);
if (type === 'single') {
return (
<AccordionPrimitive.Root
asChild={true}
className={className}
type="single"
// Fake null value to force everything to close.
value={openSection ?? '__FAKE_NULL_VALUE__'}>
{contents}
</AccordionPrimitive.Root>
);
}
return (
<AccordionPrimitive.Root
asChild={true}
className={clsx('flex flex-col gap-y-2')}
type="single"
// Fake null value to force everything to close.
value={openSection ?? '__FAKE_NULL_VALUE__'}>
<ul>
{items.map((item) => (
<SidebarLinks
key={item.label}
item={item}
size={size}
onToggle={() => {
setOpenSection(openSection === item.label ? null : item.label);
}}
/>
))}
</ul>
className={className}
type="multiple"
value={[...automaticOpenSections, ...manuallyOpenSections]}>
{contents}
</AccordionPrimitive.Root>
);
}

View File

@ -76,7 +76,11 @@ export function GuidesSidebar({
isSidebar && 'vignette-scroll',
)}>
<ScrollArea viewportClass={clsx('p-4')}>
<SidebarLinksSection items={navigation.items} size="sm" />
<SidebarLinksSection
items={navigation.items}
size="sm"
type="multiple"
/>
</ScrollArea>
</div>
</>