diff --git a/js/app/components/mobile/desktop-ui/index.ts b/js/app/components/mobile/desktop-ui/index.ts
index c156af42..91c372da 100644
--- a/js/app/components/mobile/desktop-ui/index.ts
+++ b/js/app/components/mobile/desktop-ui/index.ts
@@ -1,4 +1,5 @@
export { BottomControlBar } from "./bottom-controls";
+export { KebabMenu } from "./kebab";
export { LiveBubble } from "./live-bubble";
export { MuteOverlay } from "./mute-overlay";
export { TopControlBar } from "./top-controls";
diff --git a/js/app/components/mobile/desktop-ui/kebab.tsx b/js/app/components/mobile/desktop-ui/kebab.tsx
new file mode 100644
index 00000000..8cd66f99
--- /dev/null
+++ b/js/app/components/mobile/desktop-ui/kebab.tsx
@@ -0,0 +1,143 @@
+import {
+ AppBskyActorDefs,
+ ComAtprotoModerationCreateReport,
+} from "@atproto/api";
+import { useRootContext } from "@rn-primitives/dropdown-menu";
+import {
+ DropdownMenu,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+ ResponsiveDropdownMenuContent,
+ Text,
+ useLivestreamInfo,
+ useLivestreamStore,
+ usePlayerStore,
+ useTheme,
+} from "@streamplace/components";
+import { EllipsisVertical } from "lucide-react-native";
+import { useState } from "react";
+import Animated, {
+ Easing,
+ useAnimatedStyle,
+ withTiming,
+} from "react-native-reanimated";
+import { LivestreamViewHydrated } from "streamplace";
+
+type ReportSubject =
+ | ComAtprotoModerationCreateReport.InputSchema["subject"]
+ | null;
+
+interface KebabMenuProps {
+ dropdownPortalContainer?: string;
+}
+
+export function KebabMenu({ dropdownPortalContainer }: KebabMenuProps) {
+ const th = useTheme();
+ const [isOpen, setIsOpen] = useState(false);
+
+ const livestream = useLivestreamStore((x) => x.livestream);
+ const setReportModalOpen = usePlayerStore((x) => x.setReportModalOpen);
+ const setReportSubject = usePlayerStore((x) => x.setReportSubject);
+ const { profile } = useLivestreamInfo();
+
+ const iconRotate = useAnimatedStyle(() => {
+ return {
+ transform: [
+ {
+ rotateZ: withTiming(isOpen ? "5deg" : "0deg", {
+ duration: 200,
+ easing: Easing.out(Easing.ease),
+ }),
+ },
+ ],
+ };
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function ReportStreamItem({
+ livestream,
+ setReportModalOpen,
+ setReportSubject,
+}: {
+ livestream: LivestreamViewHydrated | null;
+ setReportModalOpen: (open: boolean) => void;
+ setReportSubject: (subject: ReportSubject) => void;
+}) {
+ const { onOpenChange } = useRootContext();
+
+ return (
+ {
+ if (!livestream) return;
+ onOpenChange?.(false);
+ setReportModalOpen(true);
+ setReportSubject({
+ $type: "com.atproto.repo.strongRef",
+ uri: livestream.uri,
+ cid: livestream.cid,
+ });
+ }}
+ disabled={!livestream}
+ >
+ Report Stream...
+
+ );
+}
+
+function ReportUserItem({
+ profile,
+ setReportModalOpen,
+ setReportSubject,
+}: {
+ profile: AppBskyActorDefs.ProfileViewBasic | null;
+ setReportModalOpen: (open: boolean) => void;
+ setReportSubject: (subject: ReportSubject) => void;
+}) {
+ const { onOpenChange } = useRootContext();
+
+ return (
+ {
+ if (!profile?.did) return;
+ onOpenChange?.(false);
+ setReportModalOpen(true);
+ setReportSubject({
+ $type: "com.atproto.admin.defs#repoRef",
+ did: profile.did,
+ });
+ }}
+ disabled={!profile?.did}
+ >
+ Report User...
+
+ );
+}