chore(docs): va events added

This commit is contained in:
Junior Garcia 2023-09-13 08:39:58 -03:00
parent 05c966e8a4
commit 7fd0327657
25 changed files with 412 additions and 38 deletions

View File

@ -1,6 +1,7 @@
import {Button, Image, Link} from "@nextui-org/react";
import {Image} from "@nextui-org/react";
import {Blockquote} from "@/components/docs/components/blockquote";
import {FigmaButton} from "@/components/figma-button";
export default function FigmaPage() {
return (
@ -23,16 +24,7 @@ export default function FigmaPage() {
width="800"
/>
<div className="text-center max-w-2xl m-auto">
<Button className="max-w-fit" color="default" variant="bordered">
<Link
isExternal
showAnchorIcon
className="text-current"
href="https://www.figma.com/community/file/1267584376234254760"
>
Open in Figma
</Link>
</Button>
<FigmaButton />
<Blockquote color="warning">
This file is still in development and will be continuously updated.
</Blockquote>

View File

@ -8,10 +8,20 @@ import NextLink from "next/link";
import {AnimatePresence, motion} from "framer-motion";
import {useIsMounted} from "@/hooks/use-is-mounted";
import {trackEvent} from "@/utils/va";
const BlogPostCard = (post: BlogPost) => {
const isMounted = useIsMounted();
const handlePress = () => {
trackEvent("BlogPostCard - Selection", {
name: post.title,
action: "click",
category: "blog",
data: post.url ?? "",
});
};
return (
<AnimatePresence>
{isMounted && (
@ -27,6 +37,7 @@ const BlogPostCard = (post: BlogPost) => {
className="p-2 h-full border-transparent text-start bg-white/5 dark:bg-default-400/10 backdrop-blur-lg backdrop-saturate-[1.8]"
href={post.url}
isPressable={!!post.url}
onPress={handlePress}
>
<CardHeader>
<Link
@ -35,6 +46,7 @@ const BlogPostCard = (post: BlogPost) => {
href={post.url}
size="lg"
underline="hover"
onPress={handlePress}
>
<Balancer>{post.title}</Balancer>
</Link>

View File

@ -25,6 +25,7 @@ import {
import searchData from "@/config/search-meta.json";
import {useUpdateEffect} from "@/hooks/use-update-effect";
import {trackEvent} from "@/utils/va";
const hideOnPaths = ["examples"];
@ -175,6 +176,13 @@ export const Cmdk: FC<{}> = () => {
const matches = intersectionBy(...matchesForEachWord, "objectID").slice(0, MAX_RESULTS);
trackEvent("Cmdk - Search", {
name: "cmdk - search",
action: "search",
category: "cmdk",
data: {query, words, matches: matches?.map((match) => match.url).join(", ")},
});
return matches;
},
[query],
@ -190,10 +198,21 @@ export const Cmdk: FC<{}> = () => {
if (e?.key?.toLowerCase() === "k" && e[hotkey]) {
e.preventDefault();
isOpen ? onClose() : onOpen();
trackEvent("Cmdk - Open/Close", {
name: "cmdk - open/close",
action: "keydown",
category: "cmdk",
data: isOpen ? "close" : "open",
});
}
};
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
};
}, [isOpen]);
const onItemSelect = useCallback(
@ -201,6 +220,13 @@ export const Cmdk: FC<{}> = () => {
onClose();
router.push(item.url);
addToRecentSearches(item);
trackEvent("Cmdk - ItemSelect", {
name: item.content,
action: "click",
category: "cmdk",
data: item.url,
});
},
[router, recentSearches],
);

View File

@ -3,6 +3,8 @@
import {useRef} from "react";
import {Button} from "@nextui-org/react";
import {trackEvent} from "@/utils/va";
export const CustomButton = () => {
const buttonRef = useRef<HTMLButtonElement | null>(null);
@ -26,6 +28,11 @@ export const CustomButton = () => {
x: targetCenterX / clientWidth,
},
});
trackEvent("LandingPage - Confetti Button", {
action: "press",
category: "landing-page",
});
};
return (

View File

@ -9,6 +9,7 @@ import {useCodeDemo, UseCodeDemoProps} from "./use-code-demo";
import WindowResizer, {WindowResizerProps} from "./window-resizer";
import {GradientBoxProps} from "@/components/gradient-box";
import {trackEvent} from "@/utils/va";
const DynamicReactLiveDemo = dynamic(
() => import("./react-live-demo").then((m) => m.ReactLiveDemo),
@ -180,6 +181,14 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
panel: "pt-0",
}}
variant="underlined"
onSelectionChange={(tabKey) => {
trackEvent("CodeDemo - Selection", {
name: tabKey as string,
action: "tabChange",
category: "docs",
data: tabKey ?? "",
});
}}
>
<Tab key="preview" title="Preview">
{previewContent}

View File

@ -1,8 +1,10 @@
import React, {forwardRef} from "react";
import React, {forwardRef, useEffect} from "react";
import {clsx, getUniqueID} from "@nextui-org/shared-utils";
import BaseHighlight, {Language, PrismTheme, defaultProps} from "prism-react-renderer";
import {debounce} from "lodash";
import defaultTheme from "@/libs/prism-theme";
import {trackEvent} from "@/utils/va";
interface CodeblockProps {
language: Language;
@ -60,6 +62,45 @@ const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
const shouldHighlightLine = calculateLinesToHighlight(metastring);
const isMultiLine = codeString.split("\n").length > 2;
const lastSelectionText = React.useRef<string | null>(null);
useEffect(() => {
const handleSelectionChange = () => {
if (!window.getSelection) return;
const el = window.getSelection()?.anchorNode?.parentNode;
if (!el) return;
const selectionText = window.getSelection()?.toString();
if (!selectionText) return;
if (
!selectionText ||
selectionText === lastSelectionText.current ||
!codeString.includes(selectionText)
)
return;
lastSelectionText.current = selectionText;
trackEvent("Codeblock - Selection", {
action: "selectText",
category: "docs",
data: selectionText,
});
};
const debouncedHandleSelectionChange = debounce(handleSelectionChange, 1000);
document.addEventListener("selectionchange", debouncedHandleSelectionChange);
return () => {
document.removeEventListener("selectionchange", debouncedHandleSelectionChange);
};
}, []);
return (
<BaseHighlight
{...defaultProps}

View File

@ -4,6 +4,7 @@ import Balancer from "react-wrap-balancer";
import {GithubIcon, NpmIcon, AdobeIcon, StorybookIcon, NextJsIcon} from "@/components/icons";
import {COMPONENT_PATH, COMPONENT_THEME_PATH} from "@/libs/github/constants";
import {trackEvent} from "@/utils/va";
export interface ComponentLinksProps {
component: string;
@ -23,6 +24,16 @@ const ButtonLink = ({
href: string;
tooltip?: string | ReactNode;
}) => {
const handlePress = () => {
if (!href) return;
trackEvent("ComponentLinks - Click", {
category: "docs",
action: "click",
data: href || "",
});
};
const button = (
<Button
isExternal
@ -31,6 +42,7 @@ const ButtonLink = ({
href={href}
size="sm"
startContent={startContent}
onPress={handlePress}
{...props}
>
{children}

View File

@ -2,6 +2,8 @@ import {Tabs, Tab, Snippet} from "@nextui-org/react";
import Codeblock from "./codeblock";
import {trackEvent} from "@/utils/va";
type PackageManager = {
key: string;
name: string;
@ -32,6 +34,14 @@ export const ImportTabs = ({commands}: ImportTabsProps) => {
tabList: "relative h-10",
}}
variant="underlined"
onSelectionChange={(tabKey) => {
trackEvent("ImportTabs - Selection", {
name: tabKey as string,
action: "tabChange",
category: "docs",
data: commands[tabKey] ?? "",
});
}}
>
{importTabs.map(({key, name}) => {
if (!commands[key]) return null;
@ -47,6 +57,14 @@ export const ImportTabs = ({commands}: ImportTabsProps) => {
pre: "font-light text-base",
copyButton: "text-lg text-default-400",
}}
onCopy={() => {
trackEvent("ImportTabs - Copy", {
name,
action: "copyInstallScript",
category: "docs",
data: commands[name] ?? "",
});
}}
>
<Codeblock
hideScrollBar

View File

@ -3,6 +3,7 @@ import {Tabs, Tab, Snippet} from "@nextui-org/react";
import Codeblock from "./codeblock";
import {YarnIcon, NpmSmallIcon, PnpmIcon} from "@/components/icons";
import {trackEvent} from "@/utils/va";
type PackageManagerName = "npm" | "yarn" | "pnpm";
@ -27,7 +28,7 @@ const packageManagers: PackageManager[] = [
];
export interface PackageManagersProps {
commands: Partial<Record<PackageManagerName, string>>;
commands: Partial<Record<PackageManagerName, React.Key>>;
}
export const PackageManagers = ({commands}: PackageManagersProps) => {
@ -39,6 +40,14 @@ export const PackageManagers = ({commands}: PackageManagersProps) => {
tabList: "h-10",
}}
variant="underlined"
onSelectionChange={(tabKey) => {
trackEvent("PackageManagers - Selection", {
name: tabKey as string,
action: "tabChange",
category: "docs",
data: commands[tabKey as unknown as PackageManagerName] ?? "",
});
}}
>
{packageManagers.map(({name, icon}) => {
if (!commands[name]) return null;
@ -62,6 +71,14 @@ export const PackageManagers = ({commands}: PackageManagersProps) => {
pre: "font-light text-base",
copyButton: "text-lg text-zinc-500 mr-2",
}}
onCopy={() => {
trackEvent("PackageManagers - Copy", {
name,
action: "copyScript",
category: "docs",
data: commands[name] ?? "",
});
}}
>
<Codeblock removeIndent codeString={commands[name] as string} language="bash" />
</Snippet>

View File

@ -1,31 +1,43 @@
"use client";
import * as React from "react";
import NextLink from "next/link";
import {Link} from "@nextui-org/react";
import {useRouter} from "next/navigation";
import {ChevronIcon} from "@nextui-org/shared-icons";
import manifest from "@/config/routes.json";
import {removeFromLast} from "@/utils";
import {Route} from "@/libs/docs/page";
import {useDocsRoute} from "@/hooks/use-docs-route";
import {trackEvent} from "@/utils/va";
export interface FooterNavProps {
currentRoute?: Route;
}
export const DocsPager: React.FC<FooterNavProps> = ({currentRoute}) => {
const router = useRouter();
const {prevRoute, nextRoute} = useDocsRoute(manifest.routes, currentRoute);
const handlePress = (path: string) => {
trackEvent("DocsPager - Click", {
category: "docs",
action: "click",
data: path || "",
});
router.push(removeFromLast(path || "", "."));
};
return (
<div className="flex w-full justify-between py-20">
{prevRoute ? (
<Link
isBlock
as={NextLink}
className="flex gap-2"
className="cursor-pointer flex gap-2"
color="foreground"
href={removeFromLast(prevRoute.path || "", ".")}
onPress={() => handlePress(prevRoute.path || "")}
>
<ChevronIcon className="text-primary" height={20} width={20} />
{prevRoute.title}
@ -36,10 +48,9 @@ export const DocsPager: React.FC<FooterNavProps> = ({currentRoute}) => {
{nextRoute && (
<Link
isBlock
as={NextLink}
className="flex gap-1 items-center"
className="cursor-pointer flex gap-1 items-center"
color="foreground"
href={removeFromLast(nextRoute.path || "", ".")}
onPress={() => handlePress(nextRoute.path || "")}
>
{nextRoute.title}
<ChevronIcon className="rotate-180 text-primary" height={20} width={20} />

View File

@ -26,6 +26,7 @@ import {getRoutePaths} from "./utils";
import {Route} from "@/libs/docs/page";
import {TreeKeyboardDelegate} from "@/utils/tree-keyboard-delegate";
import {useScrollPosition} from "@/hooks/use-scroll-position";
import {trackEvent} from "@/utils/va";
export interface Props<T> extends Omit<ItemProps<T>, "title">, Route {
slug?: string;
@ -88,6 +89,12 @@ function TreeItem<T>(props: TreeItemProps<T>) {
state.toggleKey(item.key);
} else {
router.push(paths.pathname);
trackEvent("SidebarDocs", {
category: "docs",
action: "click",
data: paths.pathname || "",
});
}
},
});

View File

@ -0,0 +1,25 @@
"use client";
import {Button, Link} from "@nextui-org/react";
import {trackEvent} from "@/utils/va";
export const FigmaButton = () => (
<Button
isExternal
showAnchorIcon
as={Link}
className="max-w-fit text-current"
color="default"
href="https://www.figma.com/community/file/1267584376234254760"
variant="bordered"
onPress={() => {
trackEvent("FigmaPage - Open Figma Link", {
action: "click",
category: "figma",
});
}}
>
Open in Figma
</Button>
);

View File

@ -14,6 +14,7 @@ import {PaletteIcon, MagicIcon, GamingConsoleIcon, StarIcon} from "@/components/
import {NextUILogo, CodeWindow} from "@/components";
import landingContent from "@/content/landing";
import {useIsMobile} from "@/hooks/use-media-query";
import {trackEvent} from "@/utils/va";
const themesTabs = (isMobile: boolean) => [
{
@ -87,6 +88,12 @@ const CustomThemesExample = ({
const onSelectionChange = (value: React.Key) => {
onChangeTheme(value as Theme);
trackEvent("CustomThemes - Selection", {
action: "change_theme",
category: "landing-page",
data: value,
});
};
return (

View File

@ -6,6 +6,8 @@ import {Card, CardHeader, CardBody, LinkProps, SlotsToClasses} from "@nextui-org
import {useRouter} from "next/navigation";
import {LinkIcon} from "@nextui-org/shared-icons";
import {trackEvent} from "@/utils/va";
const styles = tv({
slots: {
base: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4",
@ -38,6 +40,13 @@ export const FeaturesGrid: React.FC<FeaturesGridProps> = ({features, classNames,
const slots = styles();
const handleClick = (feat: Feature) => {
trackEvent("FeaturesGrid - Click", {
name: feat.title,
action: "click",
category: "docs",
data: feat.href ?? "",
});
if (!feat.href) {
return;
}

View File

@ -11,6 +11,7 @@ import {FeaturesGrid} from "./features-grid";
import {sectionWrapper, subtitle, title} from "@/components/primitives";
import {GithubIcon, NoteLinearIcon, NextJsIcon} from "@/components/icons";
import {useIsMounted} from "@/hooks/use-is-mounted";
import {trackEvent} from "@/utils/va";
const bannerSuggestions = [
{
@ -69,6 +70,13 @@ export const InstallBanner = () => {
href="/docs/guide/installation"
radius="full"
size="md"
onClick={() => {
trackEvent("InstallBanner - Get Started", {
action: "press",
category: "landing-page",
data: "/docs/guide/installation",
});
}}
>
Get Started
</Button>
@ -81,6 +89,13 @@ export const InstallBanner = () => {
size="md"
startContent={<GithubIcon />}
variant="bordered"
onClick={() => {
trackEvent("InstallBanner - Github", {
action: "press",
category: "landing-page",
data: "https://github.com/nextui-org/nextui",
});
}}
>
Github
</Button>

View File

@ -12,6 +12,7 @@ import {OpenCollectiveIcon, PatreonIcon, HeartBoldIcon, PlusLinearIcon} from "@/
import {Sponsor, SPONSOR_TIERS, SPONSOR_COLORS, getTier} from "@/libs/docs/sponsors";
import {SonarPulse} from "@/components/sonar-pulse";
import {useIsMobile} from "@/hooks/use-media-query";
import {trackEvent} from "@/utils/va";
export interface SupportProps {
sponsors: Sponsor[];
@ -100,6 +101,14 @@ export const Support: FC<SupportProps> = ({sponsors = []}) => {
window.open(href, "_blank");
};
const handleBecomeSponsor = () => {
trackEvent("Support - Become a sponsor", {
action: "click",
category: "landing-page",
});
handleExternalLinkClick(supportAccounts[0].href);
};
const renderSponsors = useMemo(() => {
if (!sponsors.length) return null;
@ -181,7 +190,7 @@ export const Support: FC<SupportProps> = ({sponsors = []}) => {
aria-label="Become a sponsor"
className="z-50 w-auto h-auto bg-gradient-to-b from-[#FF1CF7] to-[#7928CA]"
radius="full"
onPress={() => handleExternalLinkClick(supportAccounts[0].href)}
onPress={handleBecomeSponsor}
>
<PlusLinearIcon
className="flex items-center justify-center rounded-full text-white"

View File

@ -12,6 +12,7 @@ import * as DocsComponents from "@/components/docs/components";
import * as BlogComponents from "@/components/blog/components";
import {Codeblock} from "@/components/docs/components";
import {VirtualAnchor, virtualAnchorEncode} from "@/components";
import {trackEvent} from "@/utils/va";
const Table: React.FC<{children?: React.ReactNode}> = ({children}) => {
return (
@ -144,6 +145,13 @@ const Code = ({
copyButton: "text-lg text-zinc-500 mr-2",
}}
codeString={codeString}
onCopy={() => {
trackEvent("MDXComponents - Copy", {
category: "docs",
action: "copyCode",
data: codeString,
});
}}
>
<Codeblock codeString={codeString} language={language} metastring={meta} />
</Components.Snippet>
@ -153,8 +161,21 @@ const Code = ({
const Link = ({href, children}: {href?: string; children?: React.ReactNode}) => {
const isExternal = href?.startsWith("http");
const handlePress = () => {
trackEvent("MDXComponents - Click", {
category: "docs",
action: "click",
data: href || "",
});
};
return (
<Components.Link href={href} isExternal={isExternal} showAnchorIcon={isExternal}>
<Components.Link
href={href}
isExternal={isExternal}
showAnchorIcon={isExternal}
onPress={handlePress}
>
{children}
</Components.Link>
);

View File

@ -44,6 +44,7 @@ import {
import {useIsMounted} from "@/hooks/use-is-mounted";
import {DocsSidebar} from "@/components/docs/sidebar";
import {useCmdkStore} from "@/components/cmdk";
import {trackEvent} from "@/utils/va";
export interface NavbarProps {
routes: Route[];
@ -74,8 +75,17 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
setCommandKey(isAppleDevice() ? "command" : "ctrl");
}, []);
const handleOpenCmdk = () => {
cmdkStore.onOpen();
trackEvent("Navbar - Search", {
name: "navbar - search",
action: "press",
category: "cmdk",
});
};
const {pressProps} = usePress({
onPress: () => cmdkStore.onOpen(),
onPress: handleOpenCmdk,
});
const {focusProps, isFocusVisible} = useFocusRing();
@ -101,7 +111,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
strokeWidth={2}
/>
}
onPress={() => cmdkStore.onOpen()}
onPress={handleOpenCmdk}
>
Quick Search...
</Button>
@ -121,6 +131,15 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
}
};
const handlePressNavbarItem = (name: string, url: string) => {
trackEvent("NavbarItem", {
name,
action: "press",
category: "navbar",
data: url,
});
};
return (
<NextUINavbar
ref={ref}
@ -138,6 +157,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
aria-label="Home"
className="flex justify-start items-center gap-2 tap-highlight-transparent transition-opacity active:opacity-50"
href="/"
onClick={() => handlePressNavbarItem("Home", "/")}
>
<SmallLogo className="w-6 h-6 md:hidden" />
<LargeLogo className="h-5 md:h-6" />
@ -184,6 +204,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
color="foreground"
data-active={includes(docsPaths, pathname)}
href="/docs/guide/introduction"
onClick={() => handlePressNavbarItem("Docs", "/docs/guide/introduction")}
>
Docs
</NextLink>
@ -194,6 +215,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
color="foreground"
data-active={includes(pathname, "components")}
href="/docs/components/avatar"
onClick={() => handlePressNavbarItem("Components", "/docs/components/avatar")}
>
Components
</NextLink>
@ -204,6 +226,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
color="foreground"
data-active={includes(pathname, "blog")}
href="/blog"
onClick={() => handlePressNavbarItem("Blog", "/blog")}
>
Blog
</NextLink>
@ -214,6 +237,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
color="foreground"
data-active={includes(pathname, "figma")}
href="/figma"
onClick={() => handlePressNavbarItem("Figma", "/figma")}
>
Figma
</NextLink>
@ -225,6 +249,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
color="secondary"
href="/blog/v2.1.0"
variant="dot"
onClick={() => handlePressNavbarItem("New components v2.1.0", "/blog/v2.1.0")}
>
New components v2.1.0&nbsp;
<span aria-label="party emoji" role="img">
@ -242,6 +267,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
aria-label="Github"
className="p-1"
href="https://github.com/nextui-org/nextui"
onClick={() => handlePressNavbarItem("Github", "https://github.com/nextui-org/nextui")}
>
<GithubIcon className="text-default-600 dark:text-default-500" />
</Link>
@ -273,13 +299,31 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
<NavbarContent className="hidden sm:flex basis-1/5 sm:basis-full" justify="end">
<NavbarItem className="hidden sm:flex">
<Link isExternal aria-label="Twitter" className="p-1" href={siteConfig.links.twitter}>
<Link
isExternal
aria-label="Twitter"
className="p-1"
href={siteConfig.links.twitter}
onPress={() => handlePressNavbarItem("Twitter", siteConfig.links.twitter)}
>
<TwitterIcon className="text-default-600 dark:text-default-500" />
</Link>
<Link isExternal aria-label="Discord" className="p-1" href={siteConfig.links.discord}>
<Link
isExternal
aria-label="Discord"
className="p-1"
href={siteConfig.links.discord}
onPress={() => handlePressNavbarItem("Discord", siteConfig.links.discord)}
>
<DiscordIcon className="text-default-600 dark:text-default-500" />
</Link>
<Link isExternal aria-label="Github" className="p-1" href={siteConfig.links.github}>
<Link
isExternal
aria-label="Github"
className="p-1"
href={siteConfig.links.github}
onPress={() => handlePressNavbarItem("Github", siteConfig.links.github)}
>
<GithubIcon className="text-default-600 dark:text-default-500" />
</Link>
<ThemeSwitch />
@ -295,6 +339,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
<HeartFilledIcon className="text-danger group-data-[hover=true]:animate-heartbeat" />
}
variant="flat"
onPress={() => handlePressNavbarItem("Sponsor", siteConfig.links.sponsor)}
>
Sponsor
</Button>

View File

@ -1,16 +1,28 @@
import React from "react";
import {usePathname} from "next/navigation";
import {Tooltip, Link, Button} from "@nextui-org/react";
import {Tooltip, Button} from "@nextui-org/react";
import {capitalize, last} from "lodash";
import {BugIcon} from "@/components/icons";
import {ISSUE_REPORT_URL} from "@/libs/github/constants";
import {trackEvent} from "@/utils/va";
export const BugReportButton = () => {
const pathname = usePathname();
const componentTitle = capitalize(last(pathname?.split("/")));
const handlePress = () => {
trackEvent("BugReportButton - Sandpack", {
name: "sandpack - bug report",
action: "press",
category: "docs",
data: `${ISSUE_REPORT_URL}${componentTitle}`,
});
window.open(`${ISSUE_REPORT_URL}${componentTitle}`, "_blank");
};
return (
<Tooltip
className="text-xs px-2"
@ -19,15 +31,7 @@ export const BugReportButton = () => {
placement="top"
radius="md"
>
<Button
isExternal
isIconOnly
as={Link}
href={`${ISSUE_REPORT_URL}${componentTitle}`}
size="sm"
title="Report a bug"
variant="light"
>
<Button isIconOnly size="sm" title="Report a bug" variant="light" onPress={handlePress}>
<BugIcon className="text-white dark:text-zinc-500" height={16} width={16} />
</Button>
</Tooltip>

View File

@ -1,10 +1,14 @@
import React from "react";
import {UnstyledOpenInCodeSandboxButton} from "@codesandbox/sandpack-react";
import {Tooltip, Button} from "@nextui-org/react";
import {useSandpack} from "@codesandbox/sandpack-react";
import {CodeSandboxIcon} from "@/components/icons";
import {trackEvent} from "@/utils/va";
export const CodeSandboxButton = () => {
const {sandpack} = useSandpack();
return (
<Tooltip
className="text-xs px-2"
@ -13,7 +17,20 @@ export const CodeSandboxButton = () => {
placement="top"
radius="md"
>
<Button isIconOnly as="span" size="sm" title="Open in CodeSandbox" variant="light">
<Button
isIconOnly
as="span"
size="sm"
title="Open in CodeSandbox"
variant="light"
onPress={() => {
trackEvent("CodeSandboxButton - Sandpack", {
action: "press",
category: "docs",
data: sandpack.files[sandpack.activeFile],
});
}}
>
<UnstyledOpenInCodeSandboxButton
style={{
width: "100%",

View File

@ -3,6 +3,7 @@ import {useSandpack} from "@codesandbox/sandpack-react";
import {Tooltip, Button} from "@nextui-org/react";
import {useClipboard} from "@nextui-org/use-clipboard";
import {trackEvent} from "@/utils/va";
import {CopyLinearIcon} from "@/components/icons";
export const CopyButton = () => {
@ -14,6 +15,13 @@ export const CopyButton = () => {
const code = sandpack.files[sandpack.activeFile].code;
copy(code);
trackEvent("CopyButton - Sandpack", {
name: "sandpack - copy code",
action: "press",
category: "docs",
data: sandpack.activeFile,
});
};
return (

View File

@ -3,6 +3,7 @@ import {Tabs, Tab} from "@nextui-org/react";
import {SandpackPredefinedTemplate} from "@codesandbox/sandpack-react";
import {TypescriptIcon, JavascriptIcon} from "@/components/icons";
import {trackEvent} from "@/utils/va";
interface Props {
template: SandpackPredefinedTemplate;
@ -20,6 +21,12 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({template, onC
setSelectedTemplate(newTemplate);
setTimeout(() => {
trackEvent("LanguageSelector - Selection", {
name: "template",
action: "tabChange",
category: "docs",
data: newTemplate ?? "",
});
onChange?.(newTemplate);
}, 250);
}, [template, onChange]);

View File

@ -8,6 +8,7 @@ import {clsx} from "@nextui-org/shared-utils";
import {useIsSSR} from "@react-aria/ssr";
import {SunFilledIcon, MoonFilledIcon} from "@/components/icons";
import {trackEvent} from "@/utils/va";
export interface ThemeSwitchProps {
className?: string;
@ -20,6 +21,12 @@ export const ThemeSwitch: FC<ThemeSwitchProps> = ({className, classNames}) => {
const onChange = () => {
theme === "light" ? setTheme("dark") : setTheme("light");
trackEvent("ThemeChange", {
action: "click",
category: "theme",
data: theme === "light" ? "dark" : "light",
});
};
const {Component, slots, isSelected, getBaseProps, getInputProps, getWrapperProps} = useSwitch({

View File

@ -1,7 +1,10 @@
"use client";
import React from "react";
import {Link} from "@nextui-org/react";
import {VercelIcon} from "@/components/icons";
import {trackEvent} from "@/utils/va";
export const VercelCallout: React.FC<unknown> = () => {
return (
@ -9,6 +12,13 @@ export const VercelCallout: React.FC<unknown> = () => {
isExternal
className="flex justify-end items-center gap-2 text-foreground"
href="https://www.vercel.com?utm_source=nextui&utm_marketing=oss"
onClick={() => {
trackEvent("VercelCallout", {
name: "vercel callout",
action: "click",
category: "footer",
});
}}
>
<p className="font-normal">Deployed on</p>
<VercelIcon className="text-black dark:text-white" height={18} />

38
apps/docs/utils/va.ts Normal file
View File

@ -0,0 +1,38 @@
import va from "@vercel/analytics";
import {__PROD__} from "./env";
export function getUniqueID(prefix: string) {
return `${prefix}-${Math.floor(Math.random() * 1000000)}`;
}
export type TrackEvent = {
category: string;
action: string;
name?: string;
data?: any;
};
const getSessionId = () => {
let sessionId = getUniqueID("session");
// save session id in local storage if it doesn't exist
if (!localStorage.getItem("sessionId")) {
localStorage.setItem("sessionId", sessionId);
return sessionId;
} else {
return localStorage.getItem("sessionId") ?? sessionId;
}
};
export const trackEvent = (label: string, event: TrackEvent) => {
if (!__PROD__) return;
const sessionId = getSessionId();
va.track(label, {
...event,
sessionId,
});
};