mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
Fix/sidebar and pro banner space (#2438)
* feat(utils): add featurebase utils * feat(config): add changelog, feedback and roadmap routes * feat(app): add featurebase script * feat(docs): add NEXT_PUBLIC_FB_FEEDBACK_URL * feat(featurebase): add featurebase components * feat(components): add featurebase components to navbar * feat(components): add featurebase components to sidebar * chore(config): remove changelog and feedback at this moment * fix(components): fb-roadmap-link styles * chore(components): hide feedback and changelog at this moment * feat(docs): add NEXT_PUBLIC_FB_FEEDBACK_ORG * feat(featurebase): add trackEvent & revise props * fix(sidebar): remove opacity classes from TreeItem component * fix(docs): pro banner margin --------- Co-authored-by: աɨռɢӄաօռɢ <wingkwong.code@gmail.com>
This commit is contained in:
parent
4957f56e86
commit
5528ccd042
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {FC} from "react";
|
||||
import {FC, useEffect, useState} from "react";
|
||||
import {ChevronIcon} from "@nextui-org/shared-icons";
|
||||
import {CollectionBase, Expandable, MultipleSelection, Node, ItemProps} from "@react-types/shared";
|
||||
import {BaseItem} from "@nextui-org/aria-utils";
|
||||
@ -32,6 +32,7 @@ import {FbFeedbackButton} from "@/components/featurebase/fb-feedback-button";
|
||||
import {FbChangelogButton} from "@/components/featurebase/fb-changelog-button";
|
||||
import {FbRoadmapLink} from "@/components/featurebase/fb-roadmap-link";
|
||||
import {openFeedbackWidget} from "@/utils/featurebase";
|
||||
import emitter from "@/libs/emitter";
|
||||
|
||||
export interface Props<T> extends Omit<ItemProps<T>, "title">, Route {
|
||||
slug?: string;
|
||||
@ -99,8 +100,6 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
"before:w-1",
|
||||
"before:h-1",
|
||||
"before:rounded-full",
|
||||
"opacity-80",
|
||||
"dark:opacity-60",
|
||||
);
|
||||
|
||||
const {pressProps} = usePress({
|
||||
@ -284,6 +283,8 @@ export interface DocsSidebarProps {
|
||||
}
|
||||
|
||||
export const DocsSidebar: FC<DocsSidebarProps> = ({routes, slug, tag, className}) => {
|
||||
const [isProBannerVisible, setIsProBannerVisible] = useState(true);
|
||||
|
||||
const expandedKeys = routes?.reduce((keys, route) => {
|
||||
if (route.defaultOpen) {
|
||||
keys.push(route.key as string);
|
||||
@ -292,8 +293,18 @@ export const DocsSidebar: FC<DocsSidebarProps> = ({routes, slug, tag, className}
|
||||
return keys;
|
||||
}, [] as string[]);
|
||||
|
||||
return (
|
||||
<div className={clsx("lg:fixed lg:top-20 mt-2 z-0 lg:h-[calc(100vh-121px)]", className)}>
|
||||
useEffect(() => {
|
||||
emitter.on("proBannerVisibilityChange", (value) => {
|
||||
setIsProBannerVisible(value === "visible");
|
||||
});
|
||||
|
||||
return () => {
|
||||
emitter.off("proBannerVisibilityChange");
|
||||
};
|
||||
}, []);
|
||||
|
||||
const treeContent = useMemo(() => {
|
||||
return (
|
||||
<Tree defaultExpandedKeys={expandedKeys} items={routes || []}>
|
||||
{(route) => (
|
||||
<Item
|
||||
@ -307,6 +318,18 @@ export const DocsSidebar: FC<DocsSidebarProps> = ({routes, slug, tag, className}
|
||||
</Item>
|
||||
)}
|
||||
</Tree>
|
||||
);
|
||||
}, [routes]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"lg:fixed mt-2 z-0 lg:h-[calc(100vh-121px)]",
|
||||
isProBannerVisible ? "lg:top-32" : "lg:top-20",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{treeContent}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {FC, useRef, useEffect} from "react";
|
||||
import {FC, useRef, useEffect, useState} from "react";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import {Divider, Spacer} from "@nextui-org/react";
|
||||
import {ChevronCircleTopLinearIcon} from "@nextui-org/shared-icons";
|
||||
@ -9,6 +9,7 @@ import scrollIntoView from "scroll-into-view-if-needed";
|
||||
import {Heading} from "@/libs/docs/utils";
|
||||
import {useScrollSpy} from "@/hooks/use-scroll-spy";
|
||||
import {useScrollPosition} from "@/hooks/use-scroll-position";
|
||||
import emitter from "@/libs/emitter";
|
||||
|
||||
export interface DocsTocProps {
|
||||
headings: Heading[];
|
||||
@ -22,6 +23,8 @@ const paddingLeftByLevel: Record<number, string> = {
|
||||
};
|
||||
|
||||
export const DocsToc: FC<DocsTocProps> = ({headings}) => {
|
||||
const [isProBannerVisible, setIsProBannerVisible] = useState(true);
|
||||
|
||||
const tocRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollPosition = useScrollPosition(tocRef);
|
||||
@ -51,10 +54,23 @@ export const DocsToc: FC<DocsTocProps> = ({headings}) => {
|
||||
}
|
||||
}, [activeId, activeIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
emitter.on("proBannerVisibilityChange", (value) => {
|
||||
setIsProBannerVisible(value === "visible");
|
||||
});
|
||||
|
||||
return () => {
|
||||
emitter.off("proBannerVisibilityChange");
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={tocRef}
|
||||
className="fixed w-full max-w-[12rem] flex flex-col gap-4 text-left top-20 pb-20 h-[calc(100vh-121px)] scrollbar-hide overflow-y-scroll"
|
||||
className={clsx(
|
||||
"fixed w-full max-w-[12rem] flex flex-col gap-4 text-left pb-28 h-[calc(100vh-121px)] scrollbar-hide overflow-y-scroll",
|
||||
isProBannerVisible ? "top-32" : "top-20",
|
||||
)}
|
||||
style={{
|
||||
WebkitMaskImage: `linear-gradient(to top, transparent 0%, #000 100px, #000 ${
|
||||
scrollPosition > 30 ? "90%" : "100%"
|
||||
|
||||
@ -365,7 +365,12 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
</NavbarContent>
|
||||
|
||||
<NavbarMenu>
|
||||
<DocsSidebar className="mt-4" routes={[...mobileRoutes, ...routes]} slug={slug} tag={tag} />
|
||||
<DocsSidebar
|
||||
className="mt-4 pt-8"
|
||||
routes={[...mobileRoutes, ...routes]}
|
||||
slug={slug}
|
||||
tag={tag}
|
||||
/>
|
||||
{children}
|
||||
</NavbarMenu>
|
||||
</NextUINavbar>
|
||||
|
||||
@ -3,8 +3,10 @@
|
||||
import {Icon} from "@iconify/react/dist/offline";
|
||||
import arrowRightIcon from "@iconify/icons-solar/arrow-right-linear";
|
||||
import {usePathname} from "next/navigation";
|
||||
import {useEffect} from "react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import emitter from "@/libs/emitter";
|
||||
|
||||
const hideOnPaths = ["examples"];
|
||||
|
||||
@ -19,6 +21,25 @@ export const ProBanner = () => {
|
||||
const pathname = usePathname();
|
||||
const shouldBeVisible = !hideOnPaths.some((path) => pathname.includes(path));
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldBeVisible) return;
|
||||
|
||||
// listen to scroll event, dispatch an event when scroll is at the top < 48 px
|
||||
const handleScroll = () => {
|
||||
if (window.scrollY < 48) {
|
||||
emitter.emit("proBannerVisibilityChange", "visible");
|
||||
} else {
|
||||
emitter.emit("proBannerVisibilityChange", "hidden");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, [shouldBeVisible]);
|
||||
|
||||
if (!shouldBeVisible) return null;
|
||||
|
||||
return (
|
||||
|
||||
9
apps/docs/libs/emitter.ts
Normal file
9
apps/docs/libs/emitter.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import mitt from "mitt";
|
||||
|
||||
type Events = {
|
||||
proBannerVisibilityChange: "hidden" | "visible";
|
||||
};
|
||||
|
||||
const emitter = mitt<Events>();
|
||||
|
||||
export default emitter;
|
||||
Loading…
x
Reference in New Issue
Block a user