@streamplace/components 0.8.8 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,7 +25,6 @@ function ContextMenu({ dropdownPortalContainer, }) {
25
25
  const setReportModalOpen = (0, player_store_1.usePlayerStore)((x) => x.setReportModalOpen);
26
26
  const setReportSubject = (0, player_store_1.usePlayerStore)((x) => x.setReportSubject);
27
27
  const { profile } = (0, __1.useLivestreamInfo)();
28
- console.log("profile", profile);
29
28
  const avatars = (0, __1.useAvatars)(profile?.did ? [profile?.did] : []);
30
29
  const ls = (0, livestream_store_1.useLivestreamStore)((x) => x.livestream);
31
30
  const segment = (0, livestream_store_1.useLivestreamStore)((x) => x.segment);
@@ -41,10 +40,7 @@ function ContextMenu({ dropdownPortalContainer, }) {
41
40
  const isMobile = react_native_1.Platform.OS === "ios" || react_native_1.Platform.OS === "android";
42
41
  // dummy portal for mobile
43
42
  const Portal = isMobile ? react_native_1.View : ui_2.DropdownMenuPortal;
44
- // render the responsive version on mobile as we can't fullscreen there
45
- const DropdownMenuContent = isMobile
46
- ? ui_2.ResponsiveDropdownMenuContent
47
- : ui_2.DropdownMenuContentWithoutPortal;
43
+ const DropdownMenuContent = ui_2.ResponsiveDropdownMenuContent;
48
44
  return ((0, jsx_runtime_1.jsxs)(ui_2.DropdownMenu, { children: [(0, jsx_runtime_1.jsx)(ui_2.DropdownMenuTrigger, { children: (0, jsx_runtime_1.jsx)(lucide_react_native_1.Menu, { color: theme_1.colors.gray[200] }) }), (0, jsx_runtime_1.jsx)(Portal, { container: dropdownPortalContainer, children: (0, jsx_runtime_1.jsxs)(DropdownMenuContent, { side: "top", align: "end", children: [react_native_1.Platform.OS !== "web" && ((0, jsx_runtime_1.jsxs)(ui_2.DropdownMenuGroup, { title: "Streamer", children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
49
45
  __1.zero.layout.flex.row,
50
46
  __1.zero.layout.flex.center,
@@ -82,7 +78,12 @@ function ContextMenu({ dropdownPortalContainer, }) {
82
78
  const url = `https://bsky.app/profile/${profile.handle}`;
83
79
  react_native_1.Linking.openURL(url);
84
80
  }
85
- }, children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: "View Profile on Bluesky" }) })] })), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuGroup, { title: "Resolution", children: (0, jsx_runtime_1.jsxs)(ui_2.DropdownMenuRadioGroup, { value: quality, onValueChange: setQuality, children: [(0, jsx_runtime_1.jsx)(ui_2.DropdownMenuRadioItem, { value: "source", children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: "Source (Original Quality)" }) }), qualities.map((r) => ((0, jsx_runtime_1.jsx)(ui_2.DropdownMenuRadioItem, { value: r.name, children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: r.name }) })))] }) }), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuGroup, { title: "Advanced", children: (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuCheckboxItem, { checked: lowLatency, onCheckedChange: () => setLowLatency(!lowLatency), children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: "Low Latency" }) }) }), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuInfo, { description: "Reduces the delay between video and chat for a more real-time experience." }), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuCheckboxItem, { checked: debugInfo, onCheckedChange: () => setShowDebugInfo(!debugInfo), children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: "Show Debug Info" }) }) }), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuGroup, { title: "Report", children: (0, jsx_runtime_1.jsx)(ReportButton, { livestream: livestream, setReportModalOpen: setReportModalOpen, setReportSubject: setReportSubject }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [ui_1.pt[3], ui_1.px[2], ui_1.gap.all[2]], children: [contentWarnings && contentWarnings.length > 0 && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [ui_1.gap.all[1]], children: [(0, jsx_runtime_1.jsx)(ui_2.Text, { size: "base", color: "muted", children: "Stream may contain" }), (0, jsx_runtime_1.jsx)(__1.ContentWarnings, { warnings: contentWarnings, compact: true })] })), contentRights && Object.keys(contentRights).length > 0 && ((0, jsx_runtime_1.jsx)(__1.ContentRights, { contentRights: contentRights, size: "xs", color: "muted" }))] })] }) })] }));
81
+ }, children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: "View Profile on Bluesky" }) })] })), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsxs)(ui_2.DropdownMenuSub, { children: [(0, jsx_runtime_1.jsx)(ui_2.DropdownMenuSubTrigger, { subMenuTitle: "Quality", children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
82
+ __1.zero.flex.values[1],
83
+ __1.zero.layout.flex.row,
84
+ __1.zero.layout.flex.spaceBetween,
85
+ __1.zero.pr[4],
86
+ ], children: [(0, jsx_runtime_1.jsx)(ui_2.Text, { children: "Quality" }), (0, jsx_runtime_1.jsxs)(ui_2.Text, { muted: true, children: ["(", quality, ", ", lowLatency ? "low latency" : "regular latency", ")"] })] }) }), (0, jsx_runtime_1.jsxs)(ui_2.DropdownMenuSubContent, { children: [(0, jsx_runtime_1.jsx)(ui_2.DropdownMenuGroup, { title: "Resolution", children: (0, jsx_runtime_1.jsxs)(ui_2.DropdownMenuRadioGroup, { value: quality, onValueChange: setQuality, children: [(0, jsx_runtime_1.jsx)(ui_2.DropdownMenuRadioItem, { value: "source", children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: "Source (Original Quality)" }) }), qualities.map((r) => ((0, jsx_runtime_1.jsx)(ui_2.DropdownMenuRadioItem, { value: r.name, children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: r.name }) }, r.name)))] }) }), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuCheckboxItem, { checked: lowLatency, onCheckedChange: () => setLowLatency(!lowLatency), children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: "Low Latency" }) }) }), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuInfo, { description: "Reduces the delay between video and chat for a more real-time experience." })] })] }) }), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuGroup, { title: "Advanced", children: (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuCheckboxItem, { checked: debugInfo, onCheckedChange: () => setShowDebugInfo(!debugInfo), children: (0, jsx_runtime_1.jsx)(ui_2.Text, { children: "Show Debug Info" }) }) }), (0, jsx_runtime_1.jsx)(ui_2.DropdownMenuGroup, { title: "Report", children: (0, jsx_runtime_1.jsx)(ReportButton, { livestream: livestream, setReportModalOpen: setReportModalOpen, setReportSubject: setReportSubject }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [ui_1.pt[3], ui_1.px[2], ui_1.gap.all[2]], children: [contentWarnings && contentWarnings.length > 0 && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [ui_1.gap.all[1]], children: [(0, jsx_runtime_1.jsx)(ui_2.Text, { size: "base", color: "muted", children: "Stream may contain" }), (0, jsx_runtime_1.jsx)(__1.ContentWarnings, { warnings: contentWarnings, compact: true })] })), contentRights && Object.keys(contentRights).length > 0 && ((0, jsx_runtime_1.jsx)(__1.ContentRights, { contentRights: contentRights, size: "xs", color: "muted" }))] })] }) })] }));
86
87
  }
87
88
  function ReportButton({ livestream, setReportModalOpen, setReportSubject, }) {
88
89
  const { onOpenChange } = (0, dropdown_menu_1.useRootContext)();
@@ -1,40 +1,218 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DropdownMenuInfo = exports.DropdownMenuGroup = exports.DropdownMenuSeparator = exports.DropdownMenuLabel = exports.DropdownMenuRadioItem = exports.DropdownMenuCheckboxItem = exports.DropdownMenuItem = exports.ResponsiveDropdownMenuContent = exports.DropdownMenuContentWithoutPortal = exports.DropdownMenuContent = exports.DropdownMenuSubContent = exports.DropdownMenuSubTrigger = exports.DropdownMenuBottomSheet = exports.DropdownMenuRadioGroup = exports.DropdownMenuSub = exports.DropdownMenuPortal = exports.DropdownMenuTrigger = exports.DropdownMenu = void 0;
3
+ exports.DropdownMenuInfo = exports.DropdownMenuGroup = exports.DropdownMenuSeparator = exports.DropdownMenuLabel = exports.DropdownMenuRadioItem = exports.DropdownMenuCheckboxItem = exports.DropdownMenuItem = exports.ResponsiveDropdownMenuContent = exports.DropdownMenuContentWithoutPortal = exports.DropdownMenuContent = exports.DropdownMenuSubContent = exports.DropdownMenuSubTrigger = exports.DropdownMenuBottomSheet = exports.DropdownMenuSub = exports.DropdownMenuRadioGroup = exports.DropdownMenuPortal = exports.DropdownMenuTrigger = exports.DropdownMenu = void 0;
4
4
  exports.DropdownMenuShortcut = DropdownMenuShortcut;
5
5
  const tslib_1 = require("tslib");
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const bottom_sheet_1 = tslib_1.__importStar(require("@gorhom/bottom-sheet"));
8
8
  const DropdownMenuPrimitive = tslib_1.__importStar(require("@rn-primitives/dropdown-menu"));
9
9
  const lucide_react_native_1 = require("lucide-react-native");
10
- const react_1 = require("react");
10
+ const react_1 = tslib_1.__importStar(require("react"));
11
11
  const react_native_1 = require("react-native");
12
- const __1 = require("../..");
12
+ const react_native_reanimated_1 = tslib_1.__importStar(require("react-native-reanimated"));
13
+ const react_native_safe_area_context_1 = require("react-native-safe-area-context");
13
14
  const atoms_1 = require("../../lib/theme/atoms");
14
15
  const ui_1 = require("../../ui");
15
16
  const text_1 = require("./primitives/text");
16
17
  const text_2 = require("./text");
18
+ const NavigationStackContext = (0, react_1.createContext)(null);
19
+ const useNavigationStack = () => {
20
+ const context = (0, react_1.useContext)(NavigationStackContext);
21
+ return context;
22
+ };
23
+ const SubMenuContext = (0, react_1.createContext)(null);
17
24
  exports.DropdownMenu = DropdownMenuPrimitive.Root;
18
25
  exports.DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
19
26
  exports.DropdownMenuPortal = DropdownMenuPrimitive.Portal;
20
- exports.DropdownMenuSub = DropdownMenuPrimitive.Sub;
21
27
  exports.DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
28
+ // Custom DropdownMenuSub that works with mobile navigation
29
+ exports.DropdownMenuSub = (0, react_1.forwardRef)(({ children, ...props }, ref) => {
30
+ const navStack = useNavigationStack();
31
+ const [subMenuTitle, setSubMenuTitle] = (0, react_1.useState)();
32
+ const renderContentRef = (0, react_1.useRef)(null);
33
+ const [subMenuKey, setSubMenuKey] = (0, react_1.useState)(null);
34
+ // If we're in a mobile navigation stack, use custom context
35
+ if (navStack) {
36
+ const trigger = () => {
37
+ if (renderContentRef.current) {
38
+ const key = `submenu-${Date.now()}`;
39
+ setSubMenuKey(key);
40
+ navStack.push({
41
+ key,
42
+ title: subMenuTitle,
43
+ // Store a function that always reads the latest content from the ref
44
+ content: (props) => {
45
+ const renderFn = renderContentRef.current;
46
+ return renderFn ? renderFn() : null;
47
+ },
48
+ });
49
+ }
50
+ };
51
+ const setRenderContent = (renderer) => {
52
+ renderContentRef.current = renderer;
53
+ };
54
+ const contextValue = react_1.default.useMemo(() => ({
55
+ renderContent: () => renderContentRef.current?.(),
56
+ setRenderContent,
57
+ title: subMenuTitle,
58
+ setTitle: setSubMenuTitle,
59
+ trigger,
60
+ key: subMenuKey,
61
+ }), [subMenuTitle, subMenuKey]);
62
+ return ((0, jsx_runtime_1.jsx)(SubMenuContext.Provider, { value: contextValue, children: children }));
63
+ }
64
+ // Web - use primitive
65
+ return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Sub, { ref: ref, ...props, children: children }));
66
+ });
22
67
  exports.DropdownMenuBottomSheet = (0, react_1.forwardRef)(function DropdownMenuBottomSheet({ overlayStyle, portalHost, children, ...rest }, _ref) {
23
68
  // Use the primitives' context to know if open
24
- const { open, onOpenChange } = DropdownMenuPrimitive.useRootContext();
25
- const { zero: zt } = (0, ui_1.useTheme)();
69
+ const { onOpenChange } = DropdownMenuPrimitive.useRootContext();
70
+ const { zero: zt, theme } = (0, ui_1.useTheme)();
26
71
  const sheetRef = (0, react_1.useRef)(null);
27
- return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Portal, { hostName: portalHost, children: (0, jsx_runtime_1.jsx)(bottom_sheet_1.default, { ref: sheetRef, enablePanDownToClose: true, enableDynamicSizing: true, enableContentPanningGesture: false, backdropComponent: ({ style }) => ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [style, react_native_1.StyleSheet.absoluteFill], onPress: () => onOpenChange?.(false) })), onClose: () => onOpenChange?.(false), style: [overlayStyle, react_native_1.StyleSheet.flatten(rest.style)], backgroundStyle: [zt.bg.popover, atoms_1.a.radius.all.md, atoms_1.a.shadows.md, atoms_1.p[1]], handleIndicatorStyle: [
28
- atoms_1.a.sizes.width[12],
29
- atoms_1.a.sizes.height[1],
30
- zt.bg.mutedForeground,
31
- ], children: (0, jsx_runtime_1.jsx)(bottom_sheet_1.BottomSheetView, { style: [atoms_1.px[4]], children: typeof children === "function"
32
- ? children({ pressed: true })
33
- : children }) }) }));
72
+ const { width } = (0, react_native_1.useWindowDimensions)();
73
+ const isWide = react_native_1.Platform.OS !== "web" && width >= 800;
74
+ const sheetWidth = isWide ? 450 : width;
75
+ const horizontalMargin = isWide ? (width - sheetWidth) / 2 : 0;
76
+ const insets = (0, react_native_safe_area_context_1.useSafeAreaInsets)();
77
+ // Navigation stack state
78
+ const [stack, setStack] = (0, react_1.useState)([
79
+ { key: "root", content: children },
80
+ ]);
81
+ // Update root content when children changes
82
+ react_1.default.useEffect(() => {
83
+ setStack((prev) => {
84
+ if (!Array.isArray(prev) || prev.length === 0) {
85
+ return [{ key: "root", content: children }];
86
+ }
87
+ // Update the root item content
88
+ const newStack = [...prev];
89
+ newStack[0] = { ...newStack[0], content: children };
90
+ return newStack;
91
+ });
92
+ }, [children]);
93
+ const slideAnim = (0, react_native_reanimated_1.useSharedValue)(0);
94
+ const fadeAnim = (0, react_native_reanimated_1.useSharedValue)(1);
95
+ const push = (item) => {
96
+ // First, update the stack
97
+ setStack((prev) => {
98
+ if (!Array.isArray(prev))
99
+ return [{ key: "root", content: children }, item];
100
+ return [...prev, item];
101
+ });
102
+ // Then animate from right to center with fade
103
+ slideAnim.value = 40;
104
+ fadeAnim.value = 0;
105
+ slideAnim.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 350 });
106
+ fadeAnim.value = (0, react_native_reanimated_1.withTiming)(1, { duration: 350 });
107
+ };
108
+ const popStack = () => {
109
+ (0, react_1.startTransition)(() => {
110
+ setStack((prev) => {
111
+ if (!Array.isArray(prev) || prev.length <= 1) {
112
+ return [{ key: "root", content: children }];
113
+ }
114
+ return prev.slice(0, -1);
115
+ });
116
+ });
117
+ };
118
+ const resetAnimationValues = () => {
119
+ setTimeout(() => {
120
+ slideAnim.value = 0;
121
+ fadeAnim.value = 1;
122
+ }, 5);
123
+ };
124
+ const pop = () => {
125
+ if (stack.length <= 1)
126
+ return;
127
+ // Animate out to the right with fade
128
+ slideAnim.value = (0, react_native_reanimated_1.withTiming)(40, { duration: 150 });
129
+ fadeAnim.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 150 }, (finished) => {
130
+ if (finished) {
131
+ // Update stack first with startTransition for smoother render
132
+ (0, react_native_reanimated_1.runOnJS)(popStack)();
133
+ // Then reset animation position after a brief delay to ensure component has unmounted
134
+ (0, react_native_reanimated_1.runOnJS)(resetAnimationValues)();
135
+ }
136
+ });
137
+ };
138
+ const animatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
139
+ transform: [{ translateX: slideAnim.value }],
140
+ opacity: fadeAnim.value,
141
+ }));
142
+ const headerAnimatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
143
+ opacity: fadeAnim.value,
144
+ }));
145
+ const currentLevel = stack[stack.length - 1];
146
+ const isNested = stack.length > 1;
147
+ const onBackgroundTap = () => {
148
+ if (sheetRef.current)
149
+ sheetRef.current?.close();
150
+ setTimeout(() => {
151
+ onOpenChange?.(false);
152
+ }, 300);
153
+ };
154
+ // Safety check - if no current level, don't render
155
+ if (!currentLevel) {
156
+ return null;
157
+ }
158
+ return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Portal, { hostName: portalHost, children: (0, jsx_runtime_1.jsx)(NavigationStackContext.Provider, { value: { stack, push, pop, isNested }, children: (0, jsx_runtime_1.jsxs)(bottom_sheet_1.default, { ref: sheetRef, enablePanDownToClose: true, enableDynamicSizing: true, detached: isWide, bottomInset: isWide ? 0 : 0, backdropComponent: ({ style }) => ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [style, react_native_1.StyleSheet.absoluteFill], onPress: () => onBackgroundTap() })), onClose: () => onOpenChange?.(false), style: [
159
+ overlayStyle,
160
+ react_native_1.StyleSheet.flatten(rest.style),
161
+ isWide && { marginHorizontal: horizontalMargin },
162
+ ], backgroundStyle: [zt.bg.popover, atoms_1.a.radius.all.md, atoms_1.a.shadows.md, atoms_1.p[1]], handleIndicatorStyle: [
163
+ atoms_1.a.sizes.width[12],
164
+ atoms_1.a.sizes.height[1],
165
+ zt.bg.mutedForeground,
166
+ ], children: [isNested && ((0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.View, { style: [
167
+ headerAnimatedStyle,
168
+ atoms_1.a.layout.flex.row,
169
+ atoms_1.a.layout.flex.alignCenter,
170
+ atoms_1.px[4],
171
+ atoms_1.pb[2],
172
+ {
173
+ borderBottomWidth: 1,
174
+ borderBottomColor: theme.colors.border,
175
+ },
176
+ ], children: (0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { onPress: pop, style: [
177
+ atoms_1.a.layout.flex.row,
178
+ atoms_1.a.layout.flex.alignCenter,
179
+ atoms_1.gap.all[2],
180
+ ], hitSlop: 80, children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.ChevronLeft, { size: 20, color: theme.colors.foreground }), currentLevel?.title ? ((0, jsx_runtime_1.jsx)(text_2.Text, { size: "lg", children: currentLevel.title })) : null] }) })), (0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.View, { style: animatedStyle, children: (0, jsx_runtime_1.jsx)(bottom_sheet_1.BottomSheetScrollView, { style: [atoms_1.px[4]], contentContainerStyle: {
181
+ paddingBottom: insets.bottom + 50,
182
+ overflow: "hidden",
183
+ }, children: stack.map((level, index) => {
184
+ const isCurrent = index === stack.length - 1;
185
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [{ display: isCurrent ? "flex" : "none" }], children: typeof level.content === "function"
186
+ ? level.content({ pressed: true })
187
+ : level.content }, level.key));
188
+ }) }) })] }) }) }));
34
189
  });
35
- exports.DropdownMenuSubTrigger = (0, react_1.forwardRef)(({ inset, children, ...props }, ref) => {
190
+ exports.DropdownMenuSubTrigger = (0, react_1.forwardRef)(({ inset, children, subMenuTitle, ...props }, ref) => {
191
+ const navStack = useNavigationStack();
192
+ const subMenuContext = (0, react_1.useContext)(SubMenuContext);
193
+ const { icons, theme } = (0, ui_1.useTheme)();
194
+ // Set the title in the submenu context if provided
195
+ react_1.default.useEffect(() => {
196
+ if (subMenuContext && subMenuTitle) {
197
+ subMenuContext.setTitle(subMenuTitle);
198
+ }
199
+ }, [subMenuContext, subMenuTitle]);
200
+ // If we're in a navigation stack (mobile bottom sheet), handle differently
201
+ if (navStack && subMenuContext) {
202
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => {
203
+ subMenuContext.trigger();
204
+ }, ...props, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
205
+ inset && atoms_1.gap[2],
206
+ atoms_1.layout.flex.row,
207
+ atoms_1.layout.flex.alignCenter,
208
+ atoms_1.a.radius.all.sm,
209
+ atoms_1.py[1],
210
+ atoms_1.pl[2],
211
+ atoms_1.pr[2],
212
+ ], children: [typeof children === "function" ? (children({ pressed: true })) : typeof children === "string" ? ((0, jsx_runtime_1.jsx)(text_2.Text, { children: children })) : (children), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [atoms_1.a.layout.position.absolute, atoms_1.a.position.right[1]], children: (0, jsx_runtime_1.jsx)(lucide_react_native_1.ChevronRight, { size: 18, color: icons.color.muted }) })] }) }));
213
+ }
214
+ // Web behavior - use primitive
36
215
  const { open } = DropdownMenuPrimitive.useSubContext();
37
- const { icons } = (0, ui_1.useTheme)();
38
216
  const Icon = react_native_1.Platform.OS === "web" ? lucide_react_native_1.ChevronRight : open ? lucide_react_native_1.ChevronUp : lucide_react_native_1.ChevronDown;
39
217
  return ((0, jsx_runtime_1.jsx)(text_1.TextContext.Provider, { value: (0, text_1.objectFromObjects)([
40
218
  atoms_1.a.textColors.primary[500],
@@ -48,8 +226,35 @@ exports.DropdownMenuSubTrigger = (0, react_1.forwardRef)(({ inset, children, ...
48
226
  atoms_1.pr[8],
49
227
  ], children: [children, (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [atoms_1.a.layout.position.absolute, atoms_1.a.position.right[1]], children: (0, jsx_runtime_1.jsx)(Icon, { size: 18, color: icons.color.muted }) })] }) }) }));
50
228
  });
51
- exports.DropdownMenuSubContent = (0, react_1.forwardRef)((props, ref) => {
229
+ exports.DropdownMenuSubContent = (0, react_1.forwardRef)(({ children, ...props }, ref) => {
52
230
  const { zero: zt } = (0, ui_1.useTheme)();
231
+ const subMenuContext = (0, react_1.useContext)(SubMenuContext);
232
+ const navStack = useNavigationStack();
233
+ const prevChildrenRef = (0, react_1.useRef)(null);
234
+ // Register a render function that will be called fresh each time
235
+ react_1.default.useEffect(() => {
236
+ if (subMenuContext && navStack) {
237
+ // Only update if children reference actually changed
238
+ if (prevChildrenRef.current === children) {
239
+ return;
240
+ }
241
+ prevChildrenRef.current = children;
242
+ // Pass a function that returns the current children
243
+ subMenuContext.setRenderContent(() => children);
244
+ // Force a stack update to trigger rerender with the actual children
245
+ if (subMenuContext.key) {
246
+ // Store the children directly so React can handle updates
247
+ //navStack.updateContent(subMenuContext.key, children);
248
+ }
249
+ }
250
+ }, [children, subMenuContext, navStack]);
251
+ // On mobile, don't render the subcontent here - it'll be rendered in the nav stack
252
+ // But keep the component mounted so effects run when children change
253
+ if (navStack && subMenuContext) {
254
+ // Component stays mounted to track prop changes, but renders nothing
255
+ return null;
256
+ }
257
+ // Web - use primitive
53
258
  return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.SubContent, { ref: ref, style: [
54
259
  atoms_1.a.zIndex[50],
55
260
  atoms_1.a.sizes.minWidth[32],
@@ -61,10 +266,12 @@ exports.DropdownMenuSubContent = (0, react_1.forwardRef)((props, ref) => {
61
266
  zt.bg.popover,
62
267
  atoms_1.p[1],
63
268
  atoms_1.a.shadows.md,
64
- ], ...props }));
269
+ ], ...props, children: children }));
65
270
  });
66
271
  exports.DropdownMenuContent = (0, react_1.forwardRef)(({ overlayStyle, portalHost, style, children, ...props }, ref) => {
67
272
  const { zero: zt } = (0, ui_1.useTheme)();
273
+ const { height } = (0, react_native_1.useWindowDimensions)();
274
+ const maxHeight = height * 0.8;
68
275
  return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Portal, { hostName: portalHost, children: (0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Overlay, { style: [
69
276
  react_native_1.Platform.OS !== "web" ? react_native_1.StyleSheet.absoluteFill : undefined,
70
277
  overlayStyle,
@@ -80,7 +287,7 @@ exports.DropdownMenuContent = (0, react_1.forwardRef)(({ overlayStyle, portalHos
80
287
  atoms_1.p[2],
81
288
  atoms_1.a.shadows.md,
82
289
  style,
83
- ], ...props, children: (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { showsVerticalScrollIndicator: true, children: typeof children === "function"
290
+ ], ...props, children: (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { style: { maxHeight }, showsVerticalScrollIndicator: true, children: typeof children === "function"
84
291
  ? children({ pressed: false })
85
292
  : children }) }) }) }));
86
293
  });
@@ -111,9 +318,9 @@ exports.DropdownMenuContentWithoutPortal = (0, react_1.forwardRef)(({ overlaySty
111
318
  exports.ResponsiveDropdownMenuContent = (0, react_1.forwardRef)(({ children, ...props }, ref) => {
112
319
  const { width } = (0, react_native_1.useWindowDimensions)();
113
320
  // On web, you might want to always use the normal dropdown
114
- const isBottomSheet = react_native_1.Platform.OS !== "web" && width < 800;
321
+ const isBottomSheet = react_native_1.Platform.OS !== "web";
115
322
  if (isBottomSheet) {
116
- return ((0, jsx_runtime_1.jsx)(exports.DropdownMenuBottomSheet, { ref: ref, ...props, children: (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { style: [__1.zero.pb[12]], children: children }) }));
323
+ return ((0, jsx_runtime_1.jsx)(exports.DropdownMenuBottomSheet, { ref: ref, ...props, children: children }));
117
324
  }
118
325
  return ((0, jsx_runtime_1.jsx)(exports.DropdownMenuContent, { ref: ref, ...props, children: children }));
119
326
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamplace/components",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
4
4
  "description": "Streamplace React (Native) Components",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.tsx",
@@ -56,5 +56,5 @@
56
56
  "start": "tsc --watch --preserveWatchOutput",
57
57
  "prepare": "tsc"
58
58
  },
59
- "gitHead": "87db57ebb4908da9997777417493d55e64d08c6d"
59
+ "gitHead": "2ac59bf91315b2b1f848842c1048a23bd74658b7"
60
60
  }
@@ -16,7 +16,6 @@ import { gap, pt, px } from "../../../ui";
16
16
  import {
17
17
  DropdownMenu,
18
18
  DropdownMenuCheckboxItem,
19
- DropdownMenuContentWithoutPortal,
20
19
  DropdownMenuGroup,
21
20
  DropdownMenuInfo,
22
21
  DropdownMenuItem,
@@ -24,6 +23,9 @@ import {
24
23
  DropdownMenuRadioGroup,
25
24
  DropdownMenuRadioItem,
26
25
  DropdownMenuSeparator,
26
+ DropdownMenuSub,
27
+ DropdownMenuSubContent,
28
+ DropdownMenuSubTrigger,
27
29
  DropdownMenuTrigger,
28
30
  ResponsiveDropdownMenuContent,
29
31
  Text,
@@ -50,7 +52,6 @@ export function ContextMenu({
50
52
 
51
53
  const { profile } = useLivestreamInfo();
52
54
 
53
- console.log("profile", profile);
54
55
  const avatars = useAvatars(profile?.did ? [profile?.did] : []);
55
56
  const ls = useLivestreamStore((x) => x.livestream);
56
57
  const segment = useLivestreamStore((x) => x.segment);
@@ -72,10 +73,7 @@ export function ContextMenu({
72
73
  // dummy portal for mobile
73
74
  const Portal = isMobile ? View : DropdownMenuPortal;
74
75
 
75
- // render the responsive version on mobile as we can't fullscreen there
76
- const DropdownMenuContent = isMobile
77
- ? ResponsiveDropdownMenuContent
78
- : DropdownMenuContentWithoutPortal;
76
+ const DropdownMenuContent = ResponsiveDropdownMenuContent;
79
77
 
80
78
  return (
81
79
  <DropdownMenu>
@@ -175,28 +173,53 @@ export function ContextMenu({
175
173
  </DropdownMenuGroup>
176
174
  )}
177
175
 
178
- <DropdownMenuGroup title="Resolution">
179
- <DropdownMenuRadioGroup value={quality} onValueChange={setQuality}>
180
- <DropdownMenuRadioItem value="source">
181
- <Text>Source (Original Quality)</Text>
182
- </DropdownMenuRadioItem>
183
- {qualities.map((r) => (
184
- <DropdownMenuRadioItem value={r.name}>
185
- <Text>{r.name}</Text>
186
- </DropdownMenuRadioItem>
187
- ))}
188
- </DropdownMenuRadioGroup>
176
+ <DropdownMenuGroup>
177
+ <DropdownMenuSub>
178
+ <DropdownMenuSubTrigger subMenuTitle="Quality">
179
+ <View
180
+ style={[
181
+ zero.flex.values[1],
182
+ zero.layout.flex.row,
183
+ zero.layout.flex.spaceBetween,
184
+ zero.pr[4],
185
+ ]}
186
+ >
187
+ <Text>Quality</Text>
188
+ <Text muted>
189
+ ({quality}, {lowLatency ? "low latency" : "regular latency"}
190
+ )
191
+ </Text>
192
+ </View>
193
+ </DropdownMenuSubTrigger>
194
+ <DropdownMenuSubContent>
195
+ <DropdownMenuGroup title="Resolution">
196
+ <DropdownMenuRadioGroup
197
+ value={quality}
198
+ onValueChange={setQuality}
199
+ >
200
+ <DropdownMenuRadioItem value="source">
201
+ <Text>Source (Original Quality)</Text>
202
+ </DropdownMenuRadioItem>
203
+ {qualities.map((r) => (
204
+ <DropdownMenuRadioItem key={r.name} value={r.name}>
205
+ <Text>{r.name}</Text>
206
+ </DropdownMenuRadioItem>
207
+ ))}
208
+ </DropdownMenuRadioGroup>
209
+ </DropdownMenuGroup>
210
+ <DropdownMenuGroup>
211
+ <DropdownMenuCheckboxItem
212
+ checked={lowLatency}
213
+ onCheckedChange={() => setLowLatency(!lowLatency)}
214
+ >
215
+ <Text>Low Latency</Text>
216
+ </DropdownMenuCheckboxItem>
217
+ </DropdownMenuGroup>
218
+ <DropdownMenuInfo description="Reduces the delay between video and chat for a more real-time experience." />
219
+ </DropdownMenuSubContent>
220
+ </DropdownMenuSub>
189
221
  </DropdownMenuGroup>
190
222
  <DropdownMenuGroup title="Advanced">
191
- <DropdownMenuCheckboxItem
192
- checked={lowLatency}
193
- onCheckedChange={() => setLowLatency(!lowLatency)}
194
- >
195
- <Text>Low Latency</Text>
196
- </DropdownMenuCheckboxItem>
197
- </DropdownMenuGroup>
198
- <DropdownMenuInfo description="Reduces the delay between video and chat for a more real-time experience." />
199
- <DropdownMenuGroup>
200
223
  <DropdownMenuCheckboxItem
201
224
  checked={debugInfo}
202
225
  onCheckedChange={() => setShowDebugInfo(!debugInfo)}