meshtastic/src/components/FaqAccordion.tsx
2024-02-24 09:36:17 -06:00

99 lines
2.7 KiB
TypeScript

import {
Accordion,
AccordionItem,
AccordionItemButton,
AccordionItemHeading,
AccordionItemPanel,
} from "react-accessible-accordion";
import ReactMarkdown from "react-markdown";
import "../css/faq.css";
export interface Faq {
title: string;
content: string;
}
/**
* Finds the nearest heading to an element
* @param {Element} null The element to find the nearest heading to
* @return {Element|null} The heading or null
*/
const findNearestHeading = (element: Element): Element | null => {
const isHeading = (element: Element): boolean =>
/^H[1-6]$/.test(element.tagName);
let currentElement: Element | null = element;
while (currentElement !== null) {
// Check previous siblings
let prevSibling: Element | null = currentElement.previousElementSibling;
while (prevSibling) {
if (isHeading(prevSibling)) {
return prevSibling;
}
prevSibling = prevSibling.previousElementSibling;
}
// If no heading is found among siblings, move to the parent node
currentElement = currentElement.parentElement;
}
return null;
};
/**
* Takes in uuids from react-accessible-accordion onchange event
* and updates the browser url with the nearest heading's id
* @param {[type]} void [description]
* @return {[type]} [description]
*/
const updateUrlWithNearestHeadingId = (targetElementUuid: string): void => {
const targetElement: HTMLElement | null = document.getElementById(
`accordion__heading-${targetElementUuid[0]}`,
);
const nearestHeading: Element | null = targetElement
? findNearestHeading(targetElement)
: null;
// Add the hash without scrolling the page
if (nearestHeading?.id) {
window.history.pushState({}, "", `#${nearestHeading.id}`);
}
// If they're all collapsed, remove the hash
if (!targetElement) {
history.pushState(
null,
null,
window.location.origin +
window.location.pathname +
window.location.search,
);
}
};
export const FaqAccordion = ({ rows }: { rows: Faq[] }): JSX.Element => {
return (
<Accordion
allowMultipleExpanded={true}
allowZeroExpanded={true}
onChange={(itemUuids) => {
updateUrlWithNearestHeadingId(itemUuids);
}}
>
{rows.map((row, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: React complains if there is no key
<AccordionItem key={index}>
<AccordionItemHeading aria-level="3">
<AccordionItemButton>{row.title}</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>
<ReactMarkdown>{row.content}</ReactMarkdown>
</AccordionItemPanel>
</AccordionItem>
))}
</Accordion>
);
};