mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat: featurebase integration (#2425)
* 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
This commit is contained in:
parent
fa27bccb37
commit
4957f56e86
@ -12,4 +12,8 @@ IS_PREVIEW=true/false
|
||||
ANALYZE_BUNDLE=true/false
|
||||
|
||||
# Vercel preview env (is used for taking the docs directly from the project files)
|
||||
NEXT_PUBLIC_PREVIEW=true/false
|
||||
NEXT_PUBLIC_PREVIEW=true/false
|
||||
|
||||
## Featurebase
|
||||
NEXT_PUBLIC_FB_FEEDBACK_ORG=
|
||||
NEXT_PUBLIC_FB_FEEDBACK_URL=
|
||||
@ -1,6 +1,7 @@
|
||||
import "@/styles/globals.css";
|
||||
import "@/styles/sandpack.css";
|
||||
import {Metadata} from "next";
|
||||
import Script from "next/script";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import {Analytics} from "@vercel/analytics/react";
|
||||
|
||||
@ -79,6 +80,7 @@ export default function RootLayout({children}: {children: React.ReactNode}) {
|
||||
<Cmdk />
|
||||
</Providers>
|
||||
{__PROD__ && <Analytics />}
|
||||
<Script id="featurebase-sdk" src="https://do.featurebase.app/js/sdk.js" />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@ -28,6 +28,10 @@ import {getRoutePaths} from "./utils";
|
||||
import {Route} from "@/libs/docs/page";
|
||||
import {TreeKeyboardDelegate} from "@/utils/tree-keyboard-delegate";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
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";
|
||||
|
||||
export interface Props<T> extends Omit<ItemProps<T>, "title">, Route {
|
||||
slug?: string;
|
||||
@ -85,6 +89,20 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
|
||||
const Component = hasChildNodes ? "ul" : "li";
|
||||
|
||||
const cn = clsx(
|
||||
"w-full",
|
||||
"font-normal",
|
||||
"before:mr-4",
|
||||
"before:content-['']",
|
||||
"before:block",
|
||||
"before:bg-default-300",
|
||||
"before:w-1",
|
||||
"before:h-1",
|
||||
"before:rounded-full",
|
||||
"opacity-80",
|
||||
"dark:opacity-60",
|
||||
);
|
||||
|
||||
const {pressProps} = usePress({
|
||||
onPress: () => {
|
||||
if (hasChildNodes) {
|
||||
@ -103,6 +121,79 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
|
||||
const {focusProps, isFocused, isFocusVisible} = useFocusRing();
|
||||
|
||||
const renderFeaturebaseComponent = (key: string) => {
|
||||
if (key === "roadmap")
|
||||
return <FbRoadmapLink className={cn} innerClassName="opacity-80 dark:opacity-60" />;
|
||||
if (key === "changelog")
|
||||
return (
|
||||
<NextUILink as={Link} className={cn} color="foreground" href="#">
|
||||
<FbChangelogButton />
|
||||
</NextUILink>
|
||||
);
|
||||
|
||||
return (
|
||||
<NextUILink as={Link} className={cn} color="foreground" href="#" onClick={openFeedbackWidget}>
|
||||
<FbFeedbackButton />
|
||||
</NextUILink>
|
||||
);
|
||||
};
|
||||
|
||||
const renderComponent = () => {
|
||||
if (hasChildNodes) {
|
||||
return (
|
||||
<span className="flex items-center gap-3">
|
||||
<span>{rendered}</span>
|
||||
<ChevronIcon
|
||||
className={clsx("transition-transform", {
|
||||
"-rotate-90": isExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof key === "string" && ["changelog", "feedback", "roadmap"].includes(key)) {
|
||||
return renderFeaturebaseComponent(key);
|
||||
}
|
||||
|
||||
return (
|
||||
<NextUILink as={Link} className={clsx(cn)} color="foreground" href={paths.pathname}>
|
||||
<span
|
||||
className={clsx(
|
||||
isSelected
|
||||
? "text-primary font-medium dark:text-foreground"
|
||||
: "opacity-80 dark:opacity-60",
|
||||
{
|
||||
"pointer-events-none": item.props?.comingSoon,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{rendered}
|
||||
</span>
|
||||
{isUpdated && (
|
||||
<Chip
|
||||
className="ml-1 py-1 text-tiny text-default-400 bg-default-100/50"
|
||||
color="default"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
Updated
|
||||
</Chip>
|
||||
)}
|
||||
{isNew && (
|
||||
<Chip className="ml-1 py-1 text-tiny" color="primary" size="sm" variant="flat">
|
||||
New
|
||||
</Chip>
|
||||
)}
|
||||
{item.props?.comingSoon && (
|
||||
<Chip className="ml-1 py-1 text-tiny" color="default" size="sm" variant="flat">
|
||||
Coming soon
|
||||
</Chip>
|
||||
)}
|
||||
</NextUILink>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...focusProps}
|
||||
@ -122,66 +213,7 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
>
|
||||
<div className="flex items-center gap-3 cursor-pointer" {...pressProps}>
|
||||
<Spacer x={spaceLeft} />
|
||||
{hasChildNodes ? (
|
||||
<span className="flex items-center gap-3">
|
||||
<span>{rendered}</span>
|
||||
<ChevronIcon
|
||||
className={clsx("transition-transform", {
|
||||
"-rotate-90": isExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<NextUILink
|
||||
as={Link}
|
||||
className={clsx(
|
||||
"w-full",
|
||||
"font-normal",
|
||||
"before:mr-4",
|
||||
"before:content-['']",
|
||||
"before:block",
|
||||
"before:bg-default-300",
|
||||
"before:w-1",
|
||||
"before:h-1",
|
||||
"before:rounded-full",
|
||||
)}
|
||||
color="foreground"
|
||||
href={paths.pathname}
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
isSelected
|
||||
? "text-primary font-medium dark:text-foreground"
|
||||
: "opacity-80 dark:opacity-60",
|
||||
{
|
||||
"pointer-events-none": item.props?.comingSoon,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{rendered}
|
||||
</span>
|
||||
{isUpdated && (
|
||||
<Chip
|
||||
className="ml-1 py-1 text-tiny text-default-400 bg-default-100/50"
|
||||
color="default"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
Updated
|
||||
</Chip>
|
||||
)}
|
||||
{isNew && (
|
||||
<Chip className="ml-1 py-1 text-tiny" color="primary" size="sm" variant="flat">
|
||||
New
|
||||
</Chip>
|
||||
)}
|
||||
{item.props?.comingSoon && (
|
||||
<Chip className="ml-1 py-1 text-tiny" color="default" size="sm" variant="flat">
|
||||
Coming soon
|
||||
</Chip>
|
||||
)}
|
||||
</NextUILink>
|
||||
)}
|
||||
{renderComponent()}
|
||||
{/* Workaround to avoid scrollbar overlapping */}
|
||||
<Spacer x={4} />
|
||||
</div>
|
||||
|
||||
46
apps/docs/components/featurebase/fb-changelog-button.tsx
Normal file
46
apps/docs/components/featurebase/fb-changelog-button.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import {useEffect} from "react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// ref: https://developers.featurebase.app/install/changelog-widget/install
|
||||
export const FbChangelogButton = ({className}: Props) => {
|
||||
useEffect(() => {
|
||||
const win = window as any;
|
||||
|
||||
if (typeof win.Featurebase !== "function") {
|
||||
win.Featurebase = function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
(win.Featurebase.q = win.Featurebase.q || []).push(arguments);
|
||||
};
|
||||
}
|
||||
win.Featurebase("initialize_changelog_widget", {
|
||||
organization: process.env.NEXT_PUBLIC_FB_FEEDBACK_ORG,
|
||||
theme: "dark",
|
||||
usersName: "",
|
||||
fullscreenPopup: true,
|
||||
alwaysShow: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const fbButtonOnClick = () => {
|
||||
(window as any).Featurebase("manually_open_changelog_popup");
|
||||
|
||||
trackEvent("Featurebase - Changelog", {
|
||||
name: "featurebase-changelog",
|
||||
action: "press",
|
||||
category: "featurebase",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<button className={className} onClick={fbButtonOnClick}>
|
||||
Changelog <span id="fb-update-badge" />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
42
apps/docs/components/featurebase/fb-feedback-button.tsx
Normal file
42
apps/docs/components/featurebase/fb-feedback-button.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import {useEffect} from "react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// ref: https://developers.featurebase.app/install/feedback-widget/setup
|
||||
export const FbFeedbackButton = ({className}: Props) => {
|
||||
useEffect(() => {
|
||||
const win = window as any;
|
||||
|
||||
if (typeof win.Featurebase !== "function") {
|
||||
win.Featurebase = function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
(win.Featurebase.q = win.Featurebase.q || []).push(arguments);
|
||||
};
|
||||
}
|
||||
win.Featurebase("initialize_feedback_widget", {
|
||||
organization: process.env.NEXT_PUBLIC_FB_FEEDBACK_ORG,
|
||||
theme: "dark",
|
||||
email: "",
|
||||
});
|
||||
}, []);
|
||||
|
||||
const fbButtonOnClick = () => {
|
||||
trackEvent("Featurebase - Feedback", {
|
||||
name: "featurebase-feedback",
|
||||
action: "press",
|
||||
category: "featurebase",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<button data-featurebase-feedback className={className} onClick={fbButtonOnClick}>
|
||||
Feedback
|
||||
</button>
|
||||
);
|
||||
};
|
||||
42
apps/docs/components/featurebase/fb-roadmap-link.tsx
Normal file
42
apps/docs/components/featurebase/fb-roadmap-link.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import NextLink from "next/link";
|
||||
import arrowRightUpIcon from "@iconify/icons-solar/arrow-right-up-linear";
|
||||
import {Icon} from "@iconify/react/dist/offline";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
innerClassName?: string;
|
||||
};
|
||||
|
||||
export const FbRoadmapLink = ({className, innerClassName}: Props) => {
|
||||
const fbLinkOnClick = () => {
|
||||
trackEvent("Featurebase - Roadmap", {
|
||||
name: "featurebase-roadmap",
|
||||
action: "press",
|
||||
category: "featurebase",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<NextLink
|
||||
className={clsx("inline-flex items-center", className)}
|
||||
color="foreground"
|
||||
href={`${process.env.NEXT_PUBLIC_FB_FEEDBACK_URL}/roadmap`}
|
||||
target="_blank"
|
||||
onClick={fbLinkOnClick}
|
||||
>
|
||||
<div className={clsx("relative", innerClassName)}>
|
||||
Roadmap
|
||||
<Icon
|
||||
className="absolute right-[-10px] top-0 outline-none transition-transform group-data-[hover=true]:translate-y-0.5 [&>path]:stroke-[2.5px]"
|
||||
icon={arrowRightUpIcon}
|
||||
width={10}
|
||||
/>
|
||||
</div>
|
||||
</NextLink>
|
||||
);
|
||||
};
|
||||
@ -43,6 +43,7 @@ import {
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {DocsSidebar} from "@/components/docs/sidebar";
|
||||
import {useCmdkStore} from "@/components/cmdk";
|
||||
import {FbRoadmapLink} from "@/components/featurebase/fb-roadmap-link";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface NavbarProps {
|
||||
@ -241,6 +242,20 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
Figma
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
{/* hide feedback and changelog at this moment */}
|
||||
{/* <NavbarItem>
|
||||
<NextLink className={navLinkClasses} color="foreground" href="#">
|
||||
<FbChangelogButton key="changelog" userName="" />
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NextLink className={navLinkClasses} color="foreground" href="#">
|
||||
<FbFeedbackButton key="feedback" userEmail="" />
|
||||
</NextLink>
|
||||
</NavbarItem> */}
|
||||
<NavbarItem>
|
||||
<FbRoadmapLink className={navLinkClasses} />
|
||||
</NavbarItem>
|
||||
{/* <NavbarItem>
|
||||
<Chip
|
||||
as={NextLink}
|
||||
|
||||
@ -394,6 +394,12 @@
|
||||
"title": "Figma",
|
||||
"keywords": "figma, nextui, design, ui kit",
|
||||
"path": "/figma"
|
||||
},
|
||||
{
|
||||
"key": "roadmap",
|
||||
"title": "Roadmap",
|
||||
"keywords": "roadmap, nextui",
|
||||
"path": "#"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
6
apps/docs/utils/featurebase.ts
Normal file
6
apps/docs/utils/featurebase.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const openFeedbackWidget = () => {
|
||||
window.postMessage({
|
||||
target: "FeaturebaseWidget",
|
||||
data: {action: "openFeedbackWidget"},
|
||||
});
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user