diff --git a/apps/web/modules/shell/user-dropdown/DownloadIcons.tsx b/apps/web/modules/shell/user-dropdown/DownloadIcons.tsx
new file mode 100644
index 00000000000000..0a96e9c565af27
--- /dev/null
+++ b/apps/web/modules/shell/user-dropdown/DownloadIcons.tsx
@@ -0,0 +1,625 @@
+export function AppleIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+export function PlayStoreIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+export function ChromeIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+export function SafariIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+export function FirefoxIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+export function EdgeIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+export function WindowsIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+export function LinuxIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/apps/web/modules/shell/user-dropdown/UserDropdown.test.tsx b/apps/web/modules/shell/user-dropdown/UserDropdown.test.tsx
index 91bef2c8bbbaeb..241a02c1bd4885 100644
--- a/apps/web/modules/shell/user-dropdown/UserDropdown.test.tsx
+++ b/apps/web/modules/shell/user-dropdown/UserDropdown.test.tsx
@@ -16,6 +16,14 @@ vi.mock("@calcom/lib/hooks/useLocale", () => ({
}),
}));
+vi.mock("@calcom/lib/hooks/useUserAgentData", () => ({
+ useUserAgentData: () => ({
+ os: "linux",
+ browser: "chrome",
+ isMobile: false,
+ }),
+}));
+
const mockUseMeQuery = vi.fn();
vi.mock("@calcom/trpc/react/hooks/useMeQuery", () => ({
default: () => mockUseMeQuery(),
@@ -39,14 +47,20 @@ vi.mock("@calcom/ui/components/icon", () => ({
Icon: ({ name }: { name: string }) => {name},
}));
-vi.mock("@calcom/ui/components/dropdown", () => ({
- Dropdown: ({ children }: { children: React.ReactNode }) =>
{children}
,
- DropdownItem: ({ children }: { children: React.ReactNode }) => {children}
,
- DropdownMenuContent: ({ children }: { children: React.ReactNode }) => {children}
,
- DropdownMenuItem: ({ children }: { children: React.ReactNode }) => {children}
,
- DropdownMenuPortal: ({ children }: { children: React.ReactNode }) => {children}
,
- DropdownMenuSeparator: () =>
,
- DropdownMenuTrigger: ({ children }: { children: React.ReactNode }) => {children}
,
+vi.mock("@coss/ui/components/menu", () => ({
+ Menu: ({ children }: { children: React.ReactNode }) => {children}
,
+ MenuTrigger: ({ children, render }: { children: React.ReactNode; render?: React.ReactElement }) => {
+ if (render) {
+ return React.cloneElement(render, {}, children);
+ }
+ return {children}
;
+ },
+ MenuPopup: ({ children }: { children: React.ReactNode }) => {children}
,
+ MenuItem: ({ children }: { children: React.ReactNode }) => {children}
,
+ MenuSeparator: () =>
,
+ MenuSub: ({ children }: { children: React.ReactNode }) => {children}
,
+ MenuSubTrigger: ({ children }: { children: React.ReactNode }) => {children}
,
+ MenuSubPopup: ({ children }: { children: React.ReactNode }) => {children}
,
}));
vi.mock("@calcom/ui/classNames", () => ({
@@ -232,7 +246,7 @@ describe("UserDropdown", () => {
const { UserDropdown } = await import("./UserDropdown");
const { getByTestId } = render();
- expect(getByTestId("dropdown")).toBeInTheDocument();
+ expect(getByTestId("menu")).toBeInTheDocument();
});
it("should render dropdown when isPending is true (loading state)", async () => {
@@ -244,7 +258,7 @@ describe("UserDropdown", () => {
const { UserDropdown } = await import("./UserDropdown");
const { getByTestId } = render();
- expect(getByTestId("dropdown")).toBeInTheDocument();
+ expect(getByTestId("menu")).toBeInTheDocument();
});
});
});
diff --git a/apps/web/modules/shell/user-dropdown/UserDropdown.tsx b/apps/web/modules/shell/user-dropdown/UserDropdown.tsx
index 65f509db6a61e9..30be66522bae61 100644
--- a/apps/web/modules/shell/user-dropdown/UserDropdown.tsx
+++ b/apps/web/modules/shell/user-dropdown/UserDropdown.tsx
@@ -1,26 +1,38 @@
-import { signOut } from "next-auth/react";
-import { usePathname } from "next/navigation";
-import type { MouseEvent } from "react";
-import { useEffect, useState } from "react";
-
-import { ROADMAP, DESKTOP_APP_LINK } from "@calcom/lib/constants";
+import { ROADMAP } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { useUserAgentData } from "@calcom/lib/hooks/useUserAgentData";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import classNames from "@calcom/ui/classNames";
import { Avatar } from "@calcom/ui/components/avatar";
-import {
- Dropdown,
- DropdownItem,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuPortal,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@calcom/ui/components/dropdown";
import { Icon } from "@calcom/ui/components/icon";
-// TODO (Platform): we shouldnt be importing from web here
import { useGetUserAttributes } from "@calcom/web/components/settings/platform/hooks/useGetUserAttributes";
import FreshChatProvider from "@calcom/web/modules/ee/support/lib/freshchat/FreshChatProvider";
+import {
+ Menu,
+ MenuItem,
+ MenuPopup,
+ MenuSeparator,
+ MenuSub,
+ MenuSubPopup,
+ MenuSubTrigger,
+ MenuTrigger,
+} from "@coss/ui/components/menu";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { signOut } from "next-auth/react";
+import type { MouseEvent } from "react";
+import { useEffect, useState } from "react";
+
+import {
+ AppleIcon,
+ ChromeIcon,
+ EdgeIcon,
+ FirefoxIcon,
+ LinuxIcon,
+ PlayStoreIcon,
+ SafariIcon,
+ WindowsIcon,
+} from "./DownloadIcons";
declare global {
interface Window {
@@ -32,7 +44,6 @@ declare global {
}
}
-// NOTE: This interface only includes types for commands we currently use.
type BeaconFunction = {
(command: "session-data", data: Record): void;
// Catch-all for other methods - add explicit types above if using new commands
@@ -43,12 +54,25 @@ interface UserDropdownProps {
small?: boolean;
}
+const DOWNLOAD_LINKS = {
+ ios: "https://go.cal.com/iOS",
+ android: "https://go.cal.com/android",
+ chrome: "https://go.cal.com/chrome",
+ safari: "https://go.cal.com/safari",
+ firefox: "https://go.cal.com/firefox",
+ edge: "https://go.cal.com/edge",
+ macos: "https://cal.com/download",
+ windows: "https://cal.com/download",
+ linux: "https://cal.com/download",
+} as const;
+
export function UserDropdown({ small }: UserDropdownProps) {
const { isPlatformUser } = useGetUserAttributes();
const { t } = useLocale();
const { data: user, isPending } = useMeQuery();
const pathname = usePathname();
const isPlatformPages = pathname?.startsWith("/settings/platform");
+ const { os, browser, isMobile } = useUserAgentData();
useEffect(() => {
if (typeof window === "undefined") return;
@@ -105,142 +129,173 @@ export function UserDropdown({ small }: UserDropdownProps) {
}
return (
-
- setMenuOpen((menuOpen) => !menuOpen)} disabled={isPending}>
-
-
-
-
-
- {
- setMenuOpen(false);
- }}
- className="group overflow-hidden rounded-md">
+
+
+ )}
+
+
+
+
+ {!isPlatformPages && (
<>
- {!isPlatformPages && (
- <>
-
-
- }
- href="/settings/my-account/profile">
- {t("my_profile")}
-
-
-
-
- }
- href="/settings/my-account/general">
- {t("my_settings")}
-
-
-
-
- }
- href="/settings/my-account/out-of-office">
- {t("out_of_office")}
-
-
-
- >
- )}
-
-
-
- {t("visit_roadmap")}
-
-
-
-
- {t("help")}
-
-
- {!isPlatformPages && (
-
-
- {t("download_desktop_app")}
-
-
- )}
-
- {!isPlatformPages && isPlatformUser && (
-
-
- Platform
-
-
- )}
-
-
-
- {
- signOut({ callbackUrl: "/auth/logout" });
- }}>
- {t("sign_out")}
-
-
+ }>
+
+ {t("my_profile")}
+
+ }>
+
+ {t("my_settings")}
+
+ }>
+
+ {t("out_of_office")}
+
+
>
-
-
-
-
+ )}
+
+ }>
+
+ {t("visit_roadmap")}
+
+
+ {!isPlatformPages && isMobile && os === "ios" && (
+ }>
+
+ {t("download_app")}
+
+ )}
+ {!isPlatformPages && isMobile && os === "android" && (
+ }>
+
+ {t("download_app")}
+
+ )}
+ {!isPlatformPages && !isMobile && (
+
+
+
+ {t("download_app")}
+
+
+ {os === "macos" && (
+ }>
+
+ {t("download_for_macos")}
+
+ )}
+ {os === "windows" && (
+ }>
+
+ {t("download_for_windows")}
+
+ )}
+ {os === "linux" && (
+ }>
+
+ {t("download_for_linux")}
+
+ )}
+ {browser === "chrome" && (
+ }>
+
+ {t("download_chrome_extension")}
+
+ )}
+ {browser === "safari" && (
+ }>
+
+ {t("download_safari_extension")}
+
+ )}
+ {browser === "firefox" && (
+ }>
+
+ {t("download_firefox_extension")}
+
+ )}
+ {browser === "edge" && (
+ }>
+
+ {t("download_edge_extension")}
+
+ )}
+ }>
+
+ {t("download_for_ios")}
+
+ }>
+
+ {t("download_for_android")}
+
+
+
+ )}
+
+ {!isPlatformPages && isPlatformUser && (
+ }
+ className="todesktop:hidden hidden lg:flex">
+
+ {t("platform")}
+
+ )}
+
+
+
+
+
+
);
}
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 4fc1e75ac8597e..57d44d3463d475 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -1785,6 +1785,16 @@
"using_booking_questions_as_variables": "How do I use booking questions as variables?",
"using_form_responses_as_variables": "How do I use form responses as variables?",
"download_desktop_app": "Download desktop app",
+ "download_app": "Download app",
+ "download_for_ios": "iOS",
+ "download_for_android": "Android",
+ "download_chrome_extension": "Chrome Extension",
+ "download_safari_extension": "Safari Extension",
+ "download_firefox_extension": "Firefox Extension",
+ "download_edge_extension": "Edge Extension",
+ "download_for_macos": "MacOS",
+ "download_for_windows": "Windows",
+ "download_for_linux": "Linux",
"set_ping_link": "Set Ping link",
"rate_limit_exceeded": "Rate limit exceeded",
"when_something_happens": "When something happens",
diff --git a/packages/lib/hooks/useUserAgentData.test.ts b/packages/lib/hooks/useUserAgentData.test.ts
new file mode 100644
index 00000000000000..a7d62f4876b8ab
--- /dev/null
+++ b/packages/lib/hooks/useUserAgentData.test.ts
@@ -0,0 +1,174 @@
+import { renderHook } from "@testing-library/react-hooks";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+import { useUserAgentData } from "./useUserAgentData";
+
+describe("useUserAgentData hook", () => {
+ const originalNavigator = global.navigator;
+
+ beforeEach(() => {
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ Object.defineProperty(global, "navigator", {
+ value: originalNavigator,
+ writable: true,
+ });
+ });
+
+ function mockUserAgent(userAgent: string) {
+ Object.defineProperty(global, "navigator", {
+ value: { userAgent },
+ writable: true,
+ });
+ }
+
+ describe("OS detection", () => {
+ it("should detect iOS on iPhone", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.os).toBe("ios");
+ expect(result.current.isMobile).toBe(true);
+ });
+
+ it("should detect iOS on iPad", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.os).toBe("ios");
+ expect(result.current.isMobile).toBe(true);
+ });
+
+ it("should detect Android", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.os).toBe("android");
+ expect(result.current.isMobile).toBe(true);
+ });
+
+ it("should detect macOS", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.os).toBe("macos");
+ expect(result.current.isMobile).toBe(false);
+ });
+
+ it("should detect Windows", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.os).toBe("windows");
+ expect(result.current.isMobile).toBe(false);
+ });
+
+ it("should detect Linux", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.os).toBe("linux");
+ expect(result.current.isMobile).toBe(false);
+ });
+
+ it("should return unknown for unrecognized OS", () => {
+ mockUserAgent("Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)");
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.os).toBe("unknown");
+ expect(result.current.isMobile).toBe(false);
+ });
+ });
+
+ describe("Browser detection", () => {
+ it("should detect Chrome on desktop", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.browser).toBe("chrome");
+ });
+
+ it("should detect Chrome on iOS (CriOS)", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/116.0.5845.103 Mobile/15E148 Safari/604.1"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.browser).toBe("chrome");
+ });
+
+ it("should detect Safari on macOS", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.browser).toBe("safari");
+ });
+
+ it("should detect Safari on iOS", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.browser).toBe("safari");
+ });
+
+ it("should detect Firefox on desktop", () => {
+ mockUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/116.0");
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.browser).toBe("firefox");
+ });
+
+ it("should detect Firefox on iOS (FxiOS)", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/116.0 Mobile/15E148 Safari/605.1.15"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.browser).toBe("firefox");
+ });
+
+ it("should detect Edge", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.browser).toBe("edge");
+ });
+
+ it("should not detect Chrome when Edge is present", () => {
+ mockUserAgent(
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69"
+ );
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.browser).toBe("edge");
+ expect(result.current.browser).not.toBe("chrome");
+ });
+
+ it("should return unknown for unrecognized browser", () => {
+ mockUserAgent("Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)");
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.browser).toBe("unknown");
+ });
+ });
+
+ describe("SSR handling", () => {
+ it("should return defaults when navigator is undefined", () => {
+ Object.defineProperty(global, "navigator", {
+ value: undefined,
+ writable: true,
+ });
+ const { result } = renderHook(() => useUserAgentData());
+ expect(result.current.os).toBe("unknown");
+ expect(result.current.browser).toBe("unknown");
+ expect(result.current.isMobile).toBe(false);
+ });
+ });
+});
diff --git a/packages/lib/hooks/useUserAgentData.ts b/packages/lib/hooks/useUserAgentData.ts
new file mode 100644
index 00000000000000..54abfb0969bbc7
--- /dev/null
+++ b/packages/lib/hooks/useUserAgentData.ts
@@ -0,0 +1,72 @@
+import { useEffect, useState } from "react";
+
+export type OperatingSystem = "macos" | "windows" | "linux" | "ios" | "android" | "unknown";
+export type Browser = "chrome" | "safari" | "firefox" | "edge" | "unknown";
+
+export interface UserAgentData {
+ os: OperatingSystem;
+ browser: Browser;
+ isMobile: boolean;
+}
+
+function detectOS(userAgent: string): OperatingSystem {
+ const ua = userAgent.toLowerCase();
+
+ if (/iphone|ipad|ipod/.test(ua)) {
+ return "ios";
+ }
+ if (/android/.test(ua)) {
+ return "android";
+ }
+ if (/macintosh|mac os x/.test(ua)) {
+ return "macos";
+ }
+ if (/windows/.test(ua)) {
+ return "windows";
+ }
+ if (/linux/.test(ua)) {
+ return "linux";
+ }
+ return "unknown";
+}
+
+function detectBrowser(userAgent: string): Browser {
+ const ua = userAgent.toLowerCase();
+
+ if (/edg\//.test(ua)) {
+ return "edge";
+ }
+ if (/chrome|chromium|crios/.test(ua) && !/edg\//.test(ua)) {
+ return "chrome";
+ }
+ if (/firefox|fxios/.test(ua)) {
+ return "firefox";
+ }
+ if (/safari/.test(ua) && !/chrome|chromium|crios/.test(ua)) {
+ return "safari";
+ }
+ return "unknown";
+}
+
+export function useUserAgentData(): UserAgentData {
+ const [userAgentData, setUserAgentData] = useState({
+ os: "unknown",
+ browser: "unknown",
+ isMobile: false,
+ });
+
+ useEffect(() => {
+ if (typeof window === "undefined" || typeof navigator === "undefined") {
+ return;
+ }
+
+ const userAgent = navigator.userAgent;
+ const os = detectOS(userAgent);
+ const browser = detectBrowser(userAgent);
+ const isMobile = os === "ios" || os === "android";
+
+ setUserAgentData({ os, browser, isMobile });
+ }, []);
+
+ return userAgentData;
+}
diff --git a/packages/ui/components/dropdown/Dropdown.tsx b/packages/ui/components/dropdown/Dropdown.tsx
index 599505e2ea1bab..28b242da95a95b 100644
--- a/packages/ui/components/dropdown/Dropdown.tsx
+++ b/packages/ui/components/dropdown/Dropdown.tsx
@@ -1,13 +1,11 @@
+import classNames from "@calcom/ui/classNames";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import Link from "next/link";
import type { ComponentProps } from "react";
import { forwardRef } from "react";
-
-import classNames from "@calcom/ui/classNames";
-
import type { ButtonColor } from "../button";
-import { Icon } from "../icon";
import type { IconName } from "../icon";
+import { Icon } from "../icon";
export const Dropdown = DropdownMenuPrimitive.Root;
@@ -112,6 +110,45 @@ DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem";
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
+export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
+
+type DropdownMenuSubTriggerProps = ComponentProps<(typeof DropdownMenuPrimitive)["SubTrigger"]>;
+export const DropdownMenuSubTrigger = forwardRef(
+ ({ className = "", children, ...props }, forwardedRef) => (
+
+ {children}
+
+
+ )
+);
+DropdownMenuSubTrigger.displayName = "DropdownMenuSubTrigger";
+
+type DropdownMenuSubContentProps = ComponentProps<(typeof DropdownMenuPrimitive)["SubContent"]>;
+export const DropdownMenuSubContent = forwardRef(
+ ({ children, sideOffset = 2, alignOffset = -5, ...props }, forwardedRef) => {
+ return (
+
+ {children}
+
+ );
+ }
+);
+DropdownMenuSubContent.displayName = "DropdownMenuSubContent";
+
type DropdownMenuRadioItemProps = ComponentProps<(typeof DropdownMenuPrimitive)["RadioItem"]>;
export const DropdownMenuRadioItem = forwardRef(
({ children, ...props }, forwardedRef) => {
diff --git a/packages/ui/components/dropdown/index.ts b/packages/ui/components/dropdown/index.ts
index f7dd2fec044c05..b9522277d37f4f 100644
--- a/packages/ui/components/dropdown/index.ts
+++ b/packages/ui/components/dropdown/index.ts
@@ -1,6 +1,6 @@
export {
- Dropdown,
ButtonOrLink,
+ Dropdown,
DropdownItem,
DropdownMenuCheckboxItem,
DropdownMenuContent,
@@ -8,5 +8,8 @@ export {
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "./Dropdown";