Compare commits

...

20 Commits

Author SHA1 Message Date
Piyush Gupta
d63beb7ee2 Merge branch 'feature/docs-in-page-section-nav' of https://github.com/bitroy/formbricks into bitroy-docs 2024-06-14 10:17:25 +05:30
Pranoy Roy
90f6e0f1ed Merge branch 'formbricks:main' into feature/docs-in-page-section-nav 2024-06-13 18:37:02 +05:30
Johannes
d8e71a42c0 side nav styling consistency 2024-06-13 13:50:04 +02:00
Pranoy Roy
d124fdd742 Merge branch 'formbricks:main' into feature/docs-in-page-section-nav 2024-06-12 23:19:45 +05:30
bitroy
d1bacd6c0e feat: highlight side nav items on scroll 2024-06-12 23:18:41 +05:30
Johannes
e6f9a53c34 Merge branch 'main' into feature/docs-in-page-section-nav 2024-06-12 16:34:24 +02:00
bitroy
e8fd9106e5 feat: updated docs layout wrt side navigation 2024-06-11 22:49:53 +05:30
Pranoy Roy
b2b6d09a9e Merge branch 'formbricks:main' into feature/docs-in-page-section-nav 2024-06-11 22:15:39 +05:30
Pranoy Roy
8697c587a7 Merge branch 'formbricks:main' into feature/docs-in-page-section-nav 2024-06-07 13:19:53 +05:30
bitroy
8c7769a40a feat: updated in-page side nav active link style 2024-06-06 09:48:36 +05:30
bitroy
821cbbdb07 feat: enhanced in-page side navigation for docs with nested content and url navigation 2024-06-05 21:42:15 +05:30
Pranoy Roy
9618e7dae7 Merge branch 'formbricks:main' into feature/docs-in-page-section-nav 2024-06-05 21:34:55 +05:30
Pranoy Roy
da103f840b Merge branch 'formbricks:main' into feature/docs-in-page-section-nav 2024-06-05 15:11:31 +05:30
Piyush Gupta
24873734e9 Merge branch 'main' of https://github.com/formbricks/formbricks into bitroy-docs 2024-06-05 13:37:52 +05:30
Pranoy Roy
207342dc4a Merge branch 'formbricks:main' into feature/docs-in-page-section-nav 2024-06-03 21:09:50 +05:30
Pranoy Roy
f1fdc78739 Merge branch 'formbricks:main' into feature/docs-in-page-section-nav 2024-06-03 20:59:44 +05:30
bitroy
f601ad062e feat: updated docs section nav styles 2024-06-03 11:06:03 +05:30
bitroy
da202cd3d2 feat: updated docs side nav style 2024-06-03 09:52:02 +05:30
bitroy
244d04f78d feat: added in-page section navigation 2024-06-03 00:17:46 +05:30
bitroy
51e79165bd feat: added a simple in-page side navigation for docs 2024-06-02 21:13:33 +05:30
7 changed files with 163 additions and 22 deletions

View File

@@ -115,7 +115,7 @@ const SmallPrint = () => {
export const Footer = () => {
return (
<footer className="mx-auto w-full max-w-2xl space-y-10 pb-16 lg:max-w-5xl">
<footer className="mb-10 mt-10 flex-auto pb-16">
<PageNavigation />
<SmallPrint />
</footer>

View File

@@ -44,7 +44,7 @@ const Anchor = ({ id, inView, children }: { id: string; inView: boolean; childre
);
};
export const Heading = <Level extends 2 | 3>({
export const Heading = <Level extends 2 | 3 | 4>({
children,
tag,
label,
@@ -59,11 +59,11 @@ export const Heading = <Level extends 2 | 3>({
anchor?: boolean;
}) => {
level = level ?? (2 as Level);
let Component = `h${level}` as "h2" | "h3";
let ref = useRef<HTMLHeadingElement>(null);
let registerHeading = useSectionStore((s) => s.registerHeading);
const Component: "h2" | "h3" | "h4" = `h${level}`;
const ref = useRef<HTMLHeadingElement>(null);
const registerHeading = useSectionStore((s) => s.registerHeading);
let inView = useInView(ref, {
const inView = useInView(ref, {
margin: `${remToPx(-3.5)}px 0px 0px 0px`,
amount: "all",
});
@@ -71,8 +71,12 @@ export const Heading = <Level extends 2 | 3>({
useEffect(() => {
if (level === 2) {
registerHeading({ id: props.id, ref, offsetRem: tag || label ? 8 : 6 });
} else if (level === 3) {
registerHeading({ id: props.id, ref, offsetRem: tag || label ? 7 : 5 });
} else if (level === 4) {
registerHeading({ id: props.id, ref, offsetRem: tag || label ? 6 : 4 });
}
});
}, [label, level, props.id, registerHeading, tag]);
return (
<>

View File

@@ -2,6 +2,7 @@
import { Logo } from "@/components/Logo";
import { Navigation } from "@/components/Navigation";
import SideNavigation from "@/components/SideNavigation";
import { motion } from "framer-motion";
import Link from "next/link";
import { usePathname } from "next/navigation";
@@ -16,7 +17,7 @@ export const Layout = ({
children: React.ReactNode;
allSections: Record<string, Array<Section>>;
}) => {
let pathname = usePathname();
const pathname = usePathname();
return (
<SectionProvider sections={allSections[pathname || ""] ?? []}>
@@ -34,9 +35,12 @@ export const Layout = ({
<Navigation className="hidden lg:mt-10 lg:block" isMobile={false} />
</div>
</motion.header>
<div className="relative flex h-full flex-col px-4 pt-14 sm:px-6 lg:px-8">
<main className="flex-auto">{children}</main>
<Footer />
<div className="flex h-screen flex-col">
<div className="flex flex-col px-4 pt-14 sm:px-6 lg:w-[calc(100%-20rem)] lg:px-8">
<main className="overflow-y-auto overflow-x-hidden">{children}</main>
<Footer />
</div>
<SideNavigation pathname={pathname} />
</div>
</div>
</SectionProvider>

View File

@@ -0,0 +1,80 @@
import useTableContentObserver from "@/hooks/useTableContentObserver";
import Link from "next/link";
import { useEffect, useState } from "react";
type Heading = {
id: string;
text: string | null;
level: number;
};
const SideNavigation = ({ pathname }) => {
const [headings, setHeadings] = useState<Heading[]>([]);
const [selectedId, setSelectedId] = useState<string | null>(null);
useTableContentObserver(setSelectedId, pathname);
useEffect(() => {
const getHeadings = () => {
const headingElements = document.querySelectorAll("h2[id], h3[id], h4[id]");
const headings: Heading[] = Array.from(headingElements).map((heading) => ({
id: heading.id,
text: heading.textContent,
level: parseInt(heading.tagName.slice(1)),
}));
const hasH2 = headings.some((heading) => heading.level === 2);
setHeadings(hasH2 ? headings : []);
};
getHeadings();
}, [pathname]);
const renderHeading = (items: Heading[], currentLevel: number) => (
<ul className="ml-1 mt-4">
{items.map((heading, index) => {
if (heading.level === currentLevel) {
let nextIndex = index + 1;
while (nextIndex < items.length && items[nextIndex].level > currentLevel) {
nextIndex++;
}
return (
<li
key={heading.text}
className={`mb-4 ml-4 text-slate-900 dark:text-white ml-${heading.level === 2 ? 0 : heading.level === 3 ? 4 : 6}`}>
<Link
href={`#${heading.id}`}
onClick={() => setSelectedId(heading.id)}
className={`${
heading.id === selectedId
? "text-brand font-medium"
: "font-normal text-slate-600 hover:text-slate-950 dark:text-white dark:hover:text-slate-50"
}`}>
{heading.text}
</Link>
{nextIndex > index + 1 && renderHeading(items.slice(index + 1, nextIndex), currentLevel + 1)}
</li>
);
}
return null;
})}
</ul>
);
if (headings.length) {
return (
<aside className="fixed right-0 top-0 hidden h-[calc(100%-2.5rem)] w-80 overflow-hidden overflow-y-auto pr-8 pt-16 text-sm [scrollbar-width:none] lg:mt-10 lg:block">
<div className="border-l border-slate-200 dark:border-slate-700">
<h3 className="ml-5 mt-1 text-xs font-semibold uppercase text-slate-400">on this page</h3>
{renderHeading(headings, 2)}
</div>
</aside>
);
}
return null;
};
export default SideNavigation;

View File

@@ -19,10 +19,19 @@ export const wrapper = ({ children }: { children: React.ReactNode }) => {
);
};
export const h2 = (props: Omit<React.ComponentPropsWithoutRef<typeof Heading>, "level">) => {
return <Heading level={2} {...props} />;
const createHeadingComponent = (level: 2 | 3 | 4) => {
const Component = (props: Omit<React.ComponentPropsWithoutRef<typeof Heading>, "level">) => {
return <Heading level={level} {...props} />;
};
Component.displayName = `H${level}`;
return Component;
};
export const h2 = createHeadingComponent(2);
export const h3 = createHeadingComponent(3);
export const h4 = createHeadingComponent(4);
const InfoIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
return (
<svg viewBox="0 0 16 16" aria-hidden="true" {...props}>

View File

@@ -0,0 +1,44 @@
import { useEffect, useRef } from "react";
const useTableContentObserver = (setActiveId, pathname) => {
const headingElementsRef = useRef({});
useEffect(() => {
const callback = (headings) => {
headingElementsRef.current = headings.reduce((map, headingElement) => {
return { ...map, [headingElement.target.id]: headingElement };
}, {});
const visibleHeadings = [];
Object.keys(headingElementsRef.current).forEach((key) => {
const headingElement = headingElementsRef.current[key];
if (headingElement.isIntersecting) visibleHeadings.push(headingElement);
});
const getIndexFromId = (id) => headingElements.findIndex((heading) => heading.id === id);
if (visibleHeadings.length === 1) {
setActiveId(visibleHeadings[0].target.id);
} else if (visibleHeadings.length > 1) {
const sortedVisibleHeadings = visibleHeadings.sort(
(a, b) => getIndexFromId(a.target.id) > getIndexFromId(b.target.id)
);
setActiveId(sortedVisibleHeadings[0].target.id);
}
};
const observer = new IntersectionObserver(callback, {
rootMargin: "-40px 0px -40% 0px",
});
const headingElements = Array.from(document.querySelectorAll("h2[id], h3[id], h4[id]"));
headingElements.forEach((element) => observer.observe(element));
return () => {
observer.disconnect();
headingElementsRef.current = {};
};
}, [setActiveId, pathname]);
};
export default useTableContentObserver;

View File

@@ -46,9 +46,9 @@ const rehypeShiki = () => {
const rehypeSlugify = () => {
return (tree) => {
let slugify = slugifyWithCounter();
const slugify = slugifyWithCounter();
visit(tree, "element", (node) => {
if (node.tagName === "h2" && !node.properties.id) {
if (["h2", "h3", "h4"].includes(node.tagName) && !node.properties.id) {
node.properties.id = slugify(toString(node));
}
});
@@ -83,15 +83,15 @@ const rehypeAddMDXExports = (getExports) => {
};
const getSections = (node) => {
let sections = [];
const sections = [];
for (let child of node.children ?? []) {
if (child.type === "element" && child.tagName === "h2") {
for (const child of node.children ?? []) {
if (child.type === "element" && ["h2", "h3", "h4"].includes(child.tagName)) {
sections.push(`{
title: ${JSON.stringify(toString(child))},
id: ${JSON.stringify(child.properties.id)},
...${child.properties.annotation}
}`);
title: ${JSON.stringify(toString(child))},
id: ${JSON.stringify(child.properties.id)},
...${child.properties.annotation}
}`);
} else if (child.children) {
sections.push(...getSections(child));
}