@streamplace/components 0.8.6 → 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.
- package/dist/components/content-metadata/content-metadata-form.js +6 -22
- package/dist/components/content-metadata/content-rights.js +18 -47
- package/dist/components/content-metadata/content-warning-badge.js +42 -0
- package/dist/components/content-metadata/content-warnings.js +15 -48
- package/dist/components/content-metadata/index.js +3 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -13
- package/dist/components/ui/checkbox.js +6 -18
- package/dist/components/ui/dropdown.js +238 -26
- package/dist/components/ui/primitives/text.js +19 -4
- package/dist/components/ui/select.js +44 -75
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/package.json +2 -2
- package/src/components/content-metadata/content-metadata-form.tsx +9 -49
- package/src/components/content-metadata/content-rights.tsx +31 -56
- package/src/components/content-metadata/content-warning-badge.tsx +94 -0
- package/src/components/content-metadata/content-warnings.tsx +46 -58
- package/src/components/content-metadata/index.tsx +2 -0
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +81 -26
- package/src/components/ui/checkbox.tsx +23 -21
- package/src/components/ui/dropdown.tsx +445 -75
- package/src/components/ui/primitives/text.tsx +24 -4
- package/src/components/ui/select.tsx +97 -125
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,21 +1,38 @@
|
|
|
1
|
-
import BottomSheet, {
|
|
1
|
+
import BottomSheet, { BottomSheetScrollView } from "@gorhom/bottom-sheet";
|
|
2
2
|
import * as DropdownMenuPrimitive from "@rn-primitives/dropdown-menu";
|
|
3
3
|
import {
|
|
4
4
|
Check,
|
|
5
5
|
CheckCircle,
|
|
6
6
|
ChevronDown,
|
|
7
|
+
ChevronLeft,
|
|
7
8
|
ChevronRight,
|
|
8
9
|
ChevronUp,
|
|
9
10
|
Circle,
|
|
10
11
|
} from "lucide-react-native";
|
|
11
|
-
import React, {
|
|
12
|
+
import React, {
|
|
13
|
+
createContext,
|
|
14
|
+
forwardRef,
|
|
15
|
+
ReactNode,
|
|
16
|
+
startTransition,
|
|
17
|
+
useContext,
|
|
18
|
+
useRef,
|
|
19
|
+
useState,
|
|
20
|
+
} from "react";
|
|
12
21
|
import {
|
|
13
22
|
Platform,
|
|
14
23
|
Pressable,
|
|
24
|
+
ScrollView,
|
|
15
25
|
StyleSheet,
|
|
16
26
|
useWindowDimensions,
|
|
17
27
|
View,
|
|
18
28
|
} from "react-native";
|
|
29
|
+
import Animated, {
|
|
30
|
+
runOnJS,
|
|
31
|
+
useAnimatedStyle,
|
|
32
|
+
useSharedValue,
|
|
33
|
+
withTiming,
|
|
34
|
+
} from "react-native-reanimated";
|
|
35
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
19
36
|
import {
|
|
20
37
|
a,
|
|
21
38
|
borderRadius,
|
|
@@ -40,12 +57,103 @@ import {
|
|
|
40
57
|
} from "./primitives/text";
|
|
41
58
|
import { Text } from "./text";
|
|
42
59
|
|
|
60
|
+
// Navigation stack context for bottom sheet menus
|
|
61
|
+
interface NavigationStackItem {
|
|
62
|
+
key: string;
|
|
63
|
+
title?: string;
|
|
64
|
+
content: ReactNode | ((state: { pressed: boolean }) => ReactNode);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface NavigationStackContextValue {
|
|
68
|
+
stack: NavigationStackItem[];
|
|
69
|
+
push: (item: NavigationStackItem) => void;
|
|
70
|
+
pop: () => void;
|
|
71
|
+
isNested: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const NavigationStackContext =
|
|
75
|
+
createContext<NavigationStackContextValue | null>(null);
|
|
76
|
+
|
|
77
|
+
const useNavigationStack = () => {
|
|
78
|
+
const context = useContext(NavigationStackContext);
|
|
79
|
+
return context;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Context to capture submenu content for mobile navigation
|
|
83
|
+
interface SubMenuContextValue {
|
|
84
|
+
title?: string;
|
|
85
|
+
renderContent: () => ReactNode;
|
|
86
|
+
setRenderContent: (renderer: () => ReactNode) => void;
|
|
87
|
+
setTitle: (title: string) => void;
|
|
88
|
+
trigger: () => void;
|
|
89
|
+
key: string | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const SubMenuContext = createContext<SubMenuContextValue | null>(null);
|
|
93
|
+
|
|
43
94
|
export const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
44
95
|
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
45
96
|
export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
46
|
-
export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
47
97
|
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
48
98
|
|
|
99
|
+
// Custom DropdownMenuSub that works with mobile navigation
|
|
100
|
+
export const DropdownMenuSub = forwardRef<any, any>(
|
|
101
|
+
({ children, ...props }, ref) => {
|
|
102
|
+
const navStack = useNavigationStack();
|
|
103
|
+
const [subMenuTitle, setSubMenuTitle] = useState<string | undefined>();
|
|
104
|
+
const renderContentRef = useRef<(() => ReactNode) | null>(null);
|
|
105
|
+
const [subMenuKey, setSubMenuKey] = useState<string | null>(null);
|
|
106
|
+
|
|
107
|
+
// If we're in a mobile navigation stack, use custom context
|
|
108
|
+
if (navStack) {
|
|
109
|
+
const trigger = () => {
|
|
110
|
+
if (renderContentRef.current) {
|
|
111
|
+
const key = `submenu-${Date.now()}`;
|
|
112
|
+
setSubMenuKey(key);
|
|
113
|
+
navStack.push({
|
|
114
|
+
key,
|
|
115
|
+
title: subMenuTitle,
|
|
116
|
+
// Store a function that always reads the latest content from the ref
|
|
117
|
+
content: (props: any) => {
|
|
118
|
+
const renderFn = renderContentRef.current;
|
|
119
|
+
return renderFn ? renderFn() : null;
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const setRenderContent = (renderer: () => ReactNode) => {
|
|
126
|
+
renderContentRef.current = renderer;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const contextValue = React.useMemo(
|
|
130
|
+
() => ({
|
|
131
|
+
renderContent: () => renderContentRef.current?.(),
|
|
132
|
+
setRenderContent,
|
|
133
|
+
title: subMenuTitle,
|
|
134
|
+
setTitle: setSubMenuTitle,
|
|
135
|
+
trigger,
|
|
136
|
+
key: subMenuKey,
|
|
137
|
+
}),
|
|
138
|
+
[subMenuTitle, subMenuKey],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<SubMenuContext.Provider value={contextValue}>
|
|
143
|
+
{children}
|
|
144
|
+
</SubMenuContext.Provider>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Web - use primitive
|
|
149
|
+
return (
|
|
150
|
+
<DropdownMenuPrimitive.Sub ref={ref} {...props}>
|
|
151
|
+
{children}
|
|
152
|
+
</DropdownMenuPrimitive.Sub>
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
|
|
49
157
|
export const DropdownMenuBottomSheet = forwardRef<
|
|
50
158
|
any,
|
|
51
159
|
DropdownMenuPrimitive.ContentProps & {
|
|
@@ -53,61 +161,263 @@ export const DropdownMenuBottomSheet = forwardRef<
|
|
|
53
161
|
portalHost?: string;
|
|
54
162
|
}
|
|
55
163
|
>(function DropdownMenuBottomSheet(
|
|
56
|
-
{ overlayStyle, portalHost, children },
|
|
164
|
+
{ overlayStyle, portalHost, children, ...rest },
|
|
57
165
|
_ref,
|
|
58
166
|
) {
|
|
59
167
|
// Use the primitives' context to know if open
|
|
60
|
-
const {
|
|
61
|
-
const { zero: zt } = useTheme();
|
|
62
|
-
const snapPoints = useMemo(() => ["25%", "50%", "80%"], []);
|
|
168
|
+
const { onOpenChange } = DropdownMenuPrimitive.useRootContext();
|
|
169
|
+
const { zero: zt, theme } = useTheme();
|
|
63
170
|
const sheetRef = useRef<BottomSheet>(null);
|
|
171
|
+
const { width } = useWindowDimensions();
|
|
172
|
+
const isWide = Platform.OS !== "web" && width >= 800;
|
|
173
|
+
const sheetWidth = isWide ? 450 : width;
|
|
174
|
+
const horizontalMargin = isWide ? (width - sheetWidth) / 2 : 0;
|
|
175
|
+
|
|
176
|
+
const insets = useSafeAreaInsets();
|
|
177
|
+
|
|
178
|
+
// Navigation stack state
|
|
179
|
+
const [stack, setStack] = useState<NavigationStackItem[]>([
|
|
180
|
+
{ key: "root", content: children },
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
// Update root content when children changes
|
|
184
|
+
React.useEffect(() => {
|
|
185
|
+
setStack((prev) => {
|
|
186
|
+
if (!Array.isArray(prev) || prev.length === 0) {
|
|
187
|
+
return [{ key: "root", content: children }];
|
|
188
|
+
}
|
|
189
|
+
// Update the root item content
|
|
190
|
+
const newStack = [...prev];
|
|
191
|
+
newStack[0] = { ...newStack[0], content: children };
|
|
192
|
+
return newStack;
|
|
193
|
+
});
|
|
194
|
+
}, [children]);
|
|
195
|
+
|
|
196
|
+
const slideAnim = useSharedValue(0);
|
|
197
|
+
const fadeAnim = useSharedValue(1);
|
|
198
|
+
|
|
199
|
+
const push = (item: NavigationStackItem) => {
|
|
200
|
+
// First, update the stack
|
|
201
|
+
setStack((prev) => {
|
|
202
|
+
if (!Array.isArray(prev))
|
|
203
|
+
return [{ key: "root", content: children }, item];
|
|
204
|
+
return [...prev, item];
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Then animate from right to center with fade
|
|
208
|
+
slideAnim.value = 40;
|
|
209
|
+
fadeAnim.value = 0;
|
|
210
|
+
slideAnim.value = withTiming(0, { duration: 350 });
|
|
211
|
+
fadeAnim.value = withTiming(1, { duration: 350 });
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const popStack = () => {
|
|
215
|
+
startTransition(() => {
|
|
216
|
+
setStack((prev) => {
|
|
217
|
+
if (!Array.isArray(prev) || prev.length <= 1) {
|
|
218
|
+
return [{ key: "root", content: children }];
|
|
219
|
+
}
|
|
220
|
+
return prev.slice(0, -1);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const resetAnimationValues = () => {
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
slideAnim.value = 0;
|
|
228
|
+
fadeAnim.value = 1;
|
|
229
|
+
}, 5);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const pop = () => {
|
|
233
|
+
if (stack.length <= 1) return;
|
|
234
|
+
|
|
235
|
+
// Animate out to the right with fade
|
|
236
|
+
slideAnim.value = withTiming(40, { duration: 150 });
|
|
237
|
+
fadeAnim.value = withTiming(0, { duration: 150 }, (finished) => {
|
|
238
|
+
if (finished) {
|
|
239
|
+
// Update stack first with startTransition for smoother render
|
|
240
|
+
runOnJS(popStack)();
|
|
241
|
+
|
|
242
|
+
// Then reset animation position after a brief delay to ensure component has unmounted
|
|
243
|
+
runOnJS(resetAnimationValues)();
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
249
|
+
transform: [{ translateX: slideAnim.value }],
|
|
250
|
+
opacity: fadeAnim.value,
|
|
251
|
+
}));
|
|
252
|
+
|
|
253
|
+
const headerAnimatedStyle = useAnimatedStyle(() => ({
|
|
254
|
+
opacity: fadeAnim.value,
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
const currentLevel = stack[stack.length - 1];
|
|
258
|
+
const isNested = stack.length > 1;
|
|
259
|
+
|
|
260
|
+
const onBackgroundTap = () => {
|
|
261
|
+
if (sheetRef.current) sheetRef.current?.close();
|
|
262
|
+
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
onOpenChange?.(false);
|
|
265
|
+
}, 300);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Safety check - if no current level, don't render
|
|
269
|
+
if (!currentLevel) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
64
272
|
|
|
65
273
|
return (
|
|
66
274
|
<DropdownMenuPrimitive.Portal hostName={portalHost}>
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
a.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
275
|
+
<NavigationStackContext.Provider value={{ stack, push, pop, isNested }}>
|
|
276
|
+
<BottomSheet
|
|
277
|
+
ref={sheetRef}
|
|
278
|
+
enablePanDownToClose
|
|
279
|
+
enableDynamicSizing
|
|
280
|
+
detached={isWide}
|
|
281
|
+
bottomInset={isWide ? 0 : 0}
|
|
282
|
+
backdropComponent={({ style }) => (
|
|
283
|
+
<Pressable
|
|
284
|
+
style={[style, StyleSheet.absoluteFill]}
|
|
285
|
+
onPress={() => onBackgroundTap()}
|
|
286
|
+
/>
|
|
287
|
+
)}
|
|
288
|
+
onClose={() => onOpenChange?.(false)}
|
|
289
|
+
style={[
|
|
290
|
+
overlayStyle,
|
|
291
|
+
StyleSheet.flatten(rest.style),
|
|
292
|
+
isWide && { marginHorizontal: horizontalMargin },
|
|
293
|
+
]}
|
|
294
|
+
backgroundStyle={[zt.bg.popover, a.radius.all.md, a.shadows.md, p[1]]}
|
|
295
|
+
handleIndicatorStyle={[
|
|
296
|
+
a.sizes.width[12],
|
|
297
|
+
a.sizes.height[1],
|
|
298
|
+
zt.bg.mutedForeground,
|
|
299
|
+
]}
|
|
300
|
+
>
|
|
301
|
+
{isNested && (
|
|
302
|
+
<Animated.View
|
|
303
|
+
style={[
|
|
304
|
+
headerAnimatedStyle,
|
|
305
|
+
a.layout.flex.row,
|
|
306
|
+
a.layout.flex.alignCenter,
|
|
307
|
+
px[4],
|
|
308
|
+
pb[2],
|
|
309
|
+
{
|
|
310
|
+
borderBottomWidth: 1,
|
|
311
|
+
borderBottomColor: theme.colors.border,
|
|
312
|
+
},
|
|
313
|
+
]}
|
|
314
|
+
>
|
|
315
|
+
<Pressable
|
|
316
|
+
onPress={pop}
|
|
317
|
+
style={[
|
|
318
|
+
a.layout.flex.row,
|
|
319
|
+
a.layout.flex.alignCenter,
|
|
320
|
+
gap.all[2],
|
|
321
|
+
]}
|
|
322
|
+
hitSlop={80}
|
|
323
|
+
>
|
|
324
|
+
<ChevronLeft size={20} color={theme.colors.foreground} />
|
|
325
|
+
{currentLevel?.title ? (
|
|
326
|
+
<Text size="lg">{currentLevel.title}</Text>
|
|
327
|
+
) : null}
|
|
328
|
+
</Pressable>
|
|
329
|
+
</Animated.View>
|
|
330
|
+
)}
|
|
331
|
+
<Animated.View style={animatedStyle}>
|
|
332
|
+
<BottomSheetScrollView
|
|
333
|
+
style={[px[4]]}
|
|
334
|
+
contentContainerStyle={{
|
|
335
|
+
paddingBottom: insets.bottom + 50,
|
|
336
|
+
overflow: "hidden",
|
|
337
|
+
}}
|
|
338
|
+
>
|
|
339
|
+
{/* Render all stack levels to keep components mounted, but hide non-current ones */}
|
|
340
|
+
{stack.map((level, index) => {
|
|
341
|
+
const isCurrent = index === stack.length - 1;
|
|
342
|
+
return (
|
|
343
|
+
<View
|
|
344
|
+
key={level.key}
|
|
345
|
+
style={[{ display: isCurrent ? "flex" : "none" }]}
|
|
346
|
+
>
|
|
347
|
+
{typeof level.content === "function"
|
|
348
|
+
? level.content({ pressed: true })
|
|
349
|
+
: level.content}
|
|
350
|
+
</View>
|
|
351
|
+
);
|
|
352
|
+
})}
|
|
353
|
+
</BottomSheetScrollView>
|
|
354
|
+
</Animated.View>
|
|
355
|
+
</BottomSheet>
|
|
356
|
+
</NavigationStackContext.Provider>
|
|
96
357
|
</DropdownMenuPrimitive.Portal>
|
|
97
358
|
);
|
|
98
359
|
});
|
|
99
360
|
|
|
100
361
|
export const DropdownMenuSubTrigger = forwardRef<
|
|
101
362
|
any,
|
|
102
|
-
DropdownMenuPrimitive.SubTriggerProps & {
|
|
363
|
+
DropdownMenuPrimitive.SubTriggerProps & {
|
|
364
|
+
inset?: boolean;
|
|
365
|
+
subMenuTitle?: string;
|
|
366
|
+
} & {
|
|
103
367
|
ref?: React.RefObject<DropdownMenuPrimitive.SubTriggerRef>;
|
|
104
368
|
className?: string;
|
|
105
369
|
inset?: boolean;
|
|
106
370
|
children?: React.ReactNode;
|
|
107
371
|
}
|
|
108
|
-
>(({ inset, children, ...props }, ref) => {
|
|
372
|
+
>(({ inset, children, subMenuTitle, ...props }, ref) => {
|
|
373
|
+
const navStack = useNavigationStack();
|
|
374
|
+
const subMenuContext = useContext(SubMenuContext);
|
|
375
|
+
const { icons, theme } = useTheme();
|
|
376
|
+
|
|
377
|
+
// Set the title in the submenu context if provided
|
|
378
|
+
React.useEffect(() => {
|
|
379
|
+
if (subMenuContext && subMenuTitle) {
|
|
380
|
+
subMenuContext.setTitle(subMenuTitle);
|
|
381
|
+
}
|
|
382
|
+
}, [subMenuContext, subMenuTitle]);
|
|
383
|
+
|
|
384
|
+
// If we're in a navigation stack (mobile bottom sheet), handle differently
|
|
385
|
+
if (navStack && subMenuContext) {
|
|
386
|
+
return (
|
|
387
|
+
<Pressable
|
|
388
|
+
onPress={() => {
|
|
389
|
+
subMenuContext.trigger();
|
|
390
|
+
}}
|
|
391
|
+
{...props}
|
|
392
|
+
>
|
|
393
|
+
<View
|
|
394
|
+
style={[
|
|
395
|
+
inset && gap[2],
|
|
396
|
+
layout.flex.row,
|
|
397
|
+
layout.flex.alignCenter,
|
|
398
|
+
a.radius.all.sm,
|
|
399
|
+
py[1],
|
|
400
|
+
pl[2],
|
|
401
|
+
pr[2],
|
|
402
|
+
]}
|
|
403
|
+
>
|
|
404
|
+
{typeof children === "function" ? (
|
|
405
|
+
children({ pressed: true })
|
|
406
|
+
) : typeof children === "string" ? (
|
|
407
|
+
<Text>{children}</Text>
|
|
408
|
+
) : (
|
|
409
|
+
children
|
|
410
|
+
)}
|
|
411
|
+
<View style={[a.layout.position.absolute, a.position.right[1]]}>
|
|
412
|
+
<ChevronRight size={18} color={icons.color.muted} />
|
|
413
|
+
</View>
|
|
414
|
+
</View>
|
|
415
|
+
</Pressable>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Web behavior - use primitive
|
|
109
420
|
const { open } = DropdownMenuPrimitive.useSubContext();
|
|
110
|
-
const { icons } = useTheme();
|
|
111
421
|
const Icon =
|
|
112
422
|
Platform.OS === "web" ? ChevronRight : open ? ChevronUp : ChevronDown;
|
|
113
423
|
return (
|
|
@@ -140,9 +450,42 @@ export const DropdownMenuSubTrigger = forwardRef<
|
|
|
140
450
|
|
|
141
451
|
export const DropdownMenuSubContent = forwardRef<
|
|
142
452
|
any,
|
|
143
|
-
DropdownMenuPrimitive.SubContentProps
|
|
144
|
-
>((props, ref) => {
|
|
453
|
+
DropdownMenuPrimitive.SubContentProps & { children?: ReactNode }
|
|
454
|
+
>(({ children, ...props }, ref) => {
|
|
145
455
|
const { zero: zt } = useTheme();
|
|
456
|
+
const subMenuContext = useContext(SubMenuContext);
|
|
457
|
+
const navStack = useNavigationStack();
|
|
458
|
+
const prevChildrenRef = useRef<ReactNode>(null);
|
|
459
|
+
|
|
460
|
+
// Register a render function that will be called fresh each time
|
|
461
|
+
React.useEffect(() => {
|
|
462
|
+
if (subMenuContext && navStack) {
|
|
463
|
+
// Only update if children reference actually changed
|
|
464
|
+
if (prevChildrenRef.current === children) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
prevChildrenRef.current = children;
|
|
469
|
+
|
|
470
|
+
// Pass a function that returns the current children
|
|
471
|
+
subMenuContext.setRenderContent(() => children);
|
|
472
|
+
|
|
473
|
+
// Force a stack update to trigger rerender with the actual children
|
|
474
|
+
if (subMenuContext.key) {
|
|
475
|
+
// Store the children directly so React can handle updates
|
|
476
|
+
//navStack.updateContent(subMenuContext.key, children);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}, [children, subMenuContext, navStack]);
|
|
480
|
+
|
|
481
|
+
// On mobile, don't render the subcontent here - it'll be rendered in the nav stack
|
|
482
|
+
// But keep the component mounted so effects run when children change
|
|
483
|
+
if (navStack && subMenuContext) {
|
|
484
|
+
// Component stays mounted to track prop changes, but renders nothing
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Web - use primitive
|
|
146
489
|
return (
|
|
147
490
|
<DropdownMenuPrimitive.SubContent
|
|
148
491
|
ref={ref}
|
|
@@ -159,7 +502,9 @@ export const DropdownMenuSubContent = forwardRef<
|
|
|
159
502
|
a.shadows.md,
|
|
160
503
|
]}
|
|
161
504
|
{...props}
|
|
162
|
-
|
|
505
|
+
>
|
|
506
|
+
{children}
|
|
507
|
+
</DropdownMenuPrimitive.SubContent>
|
|
163
508
|
);
|
|
164
509
|
});
|
|
165
510
|
|
|
@@ -169,8 +514,11 @@ export const DropdownMenuContent = forwardRef<
|
|
|
169
514
|
overlayStyle?: any;
|
|
170
515
|
portalHost?: string;
|
|
171
516
|
}
|
|
172
|
-
>(({ overlayStyle, portalHost, ...props }, ref) => {
|
|
517
|
+
>(({ overlayStyle, portalHost, style, children, ...props }, ref) => {
|
|
173
518
|
const { zero: zt } = useTheme();
|
|
519
|
+
const { height } = useWindowDimensions();
|
|
520
|
+
const maxHeight = height * 0.8;
|
|
521
|
+
|
|
174
522
|
return (
|
|
175
523
|
<DropdownMenuPrimitive.Portal hostName={portalHost}>
|
|
176
524
|
<DropdownMenuPrimitive.Overlay
|
|
@@ -193,10 +541,17 @@ export const DropdownMenuContent = forwardRef<
|
|
|
193
541
|
zt.bg.popover,
|
|
194
542
|
p[2],
|
|
195
543
|
a.shadows.md,
|
|
544
|
+
style,
|
|
196
545
|
] as any
|
|
197
546
|
}
|
|
198
547
|
{...props}
|
|
199
|
-
|
|
548
|
+
>
|
|
549
|
+
<ScrollView style={{ maxHeight }} showsVerticalScrollIndicator={true}>
|
|
550
|
+
{typeof children === "function"
|
|
551
|
+
? children({ pressed: false })
|
|
552
|
+
: children}
|
|
553
|
+
</ScrollView>
|
|
554
|
+
</DropdownMenuPrimitive.Content>
|
|
200
555
|
</DropdownMenuPrimitive.Overlay>
|
|
201
556
|
</DropdownMenuPrimitive.Portal>
|
|
202
557
|
);
|
|
@@ -206,37 +561,52 @@ export const DropdownMenuContentWithoutPortal = forwardRef<
|
|
|
206
561
|
any,
|
|
207
562
|
DropdownMenuPrimitive.ContentProps & {
|
|
208
563
|
overlayStyle?: any;
|
|
564
|
+
maxHeightPercentage?: number;
|
|
209
565
|
}
|
|
210
|
-
>(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
style={
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
566
|
+
>(
|
|
567
|
+
(
|
|
568
|
+
{ overlayStyle, maxHeightPercentage = 0.8, children, style, ...props },
|
|
569
|
+
ref,
|
|
570
|
+
) => {
|
|
571
|
+
const { theme } = useTheme();
|
|
572
|
+
const { height } = useWindowDimensions();
|
|
573
|
+
const maxHeight = height * maxHeightPercentage;
|
|
574
|
+
|
|
575
|
+
return (
|
|
576
|
+
<DropdownMenuPrimitive.Overlay
|
|
577
|
+
style={[
|
|
578
|
+
Platform.OS !== "web" ? StyleSheet.absoluteFill : undefined,
|
|
579
|
+
overlayStyle,
|
|
580
|
+
]}
|
|
581
|
+
>
|
|
582
|
+
<DropdownMenuPrimitive.Content
|
|
583
|
+
ref={ref}
|
|
584
|
+
style={
|
|
585
|
+
[
|
|
586
|
+
{ zIndex: 999999 },
|
|
587
|
+
a.sizes.minWidth[32],
|
|
588
|
+
a.sizes.maxWidth[64],
|
|
589
|
+
a.radius.all.md,
|
|
590
|
+
a.borders.width.thin,
|
|
591
|
+
{ borderColor: theme.colors.border },
|
|
592
|
+
{ backgroundColor: theme.colors.popover },
|
|
593
|
+
p[2],
|
|
594
|
+
a.shadows.md,
|
|
595
|
+
style,
|
|
596
|
+
] as any
|
|
597
|
+
}
|
|
598
|
+
{...props}
|
|
599
|
+
>
|
|
600
|
+
<ScrollView style={{ maxHeight }} showsVerticalScrollIndicator={true}>
|
|
601
|
+
{typeof children === "function"
|
|
602
|
+
? children({ pressed: false })
|
|
603
|
+
: children}
|
|
604
|
+
</ScrollView>
|
|
605
|
+
</DropdownMenuPrimitive.Content>
|
|
606
|
+
</DropdownMenuPrimitive.Overlay>
|
|
607
|
+
);
|
|
608
|
+
},
|
|
609
|
+
);
|
|
240
610
|
|
|
241
611
|
/// Responsive Dropdown Menu Content. On mobile this will render a *bottom sheet* that is **portaled to the root of the app**.
|
|
242
612
|
/// Prefer passing scoped content in as **otherwise it may crash the app**.
|
|
@@ -245,7 +615,7 @@ export const ResponsiveDropdownMenuContent = forwardRef<any, any>(
|
|
|
245
615
|
const { width } = useWindowDimensions();
|
|
246
616
|
|
|
247
617
|
// On web, you might want to always use the normal dropdown
|
|
248
|
-
const isBottomSheet = Platform.OS !== "web"
|
|
618
|
+
const isBottomSheet = Platform.OS !== "web";
|
|
249
619
|
|
|
250
620
|
if (isBottomSheet) {
|
|
251
621
|
return (
|
|
@@ -277,6 +277,22 @@ export const TextRoot = forwardRef<RNText, TextPrimitiveProps>(
|
|
|
277
277
|
const inheritedContext =
|
|
278
278
|
inherit && !reset && parentContext ? parentContext : {};
|
|
279
279
|
|
|
280
|
+
// Calculate fontSize first for line height calculation
|
|
281
|
+
let calculatedFontSize = inheritedContext.fontSize;
|
|
282
|
+
|
|
283
|
+
// Apply variant font size
|
|
284
|
+
if (variant && variantStyles[variant]?.fontSize) {
|
|
285
|
+
calculatedFontSize = variantStyles[variant].fontSize as number;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Apply size-based font size
|
|
289
|
+
if (size) {
|
|
290
|
+
calculatedFontSize = typeof size === "number" ? size : sizeMap[size];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Use default if still undefined
|
|
294
|
+
calculatedFontSize = calculatedFontSize || 16;
|
|
295
|
+
|
|
280
296
|
// Calculate final styles
|
|
281
297
|
const finalStyles: TextStyle = {
|
|
282
298
|
// Start with inherited values
|
|
@@ -344,7 +360,10 @@ export const TextRoot = forwardRef<RNText, TextPrimitiveProps>(
|
|
|
344
360
|
|
|
345
361
|
// Apply line height
|
|
346
362
|
...(leading && {
|
|
347
|
-
lineHeight:
|
|
363
|
+
lineHeight:
|
|
364
|
+
typeof leading === "number"
|
|
365
|
+
? leading
|
|
366
|
+
: leadingMap[leading] * calculatedFontSize,
|
|
348
367
|
}),
|
|
349
368
|
|
|
350
369
|
// Apply letter spacing
|
|
@@ -389,7 +408,7 @@ export const TextRoot = forwardRef<RNText, TextPrimitiveProps>(
|
|
|
389
408
|
if (typeof fontSize === "number" && !styleObj.lineHeight && !leading) {
|
|
390
409
|
return {
|
|
391
410
|
...styleObj,
|
|
392
|
-
lineHeight: fontSize
|
|
411
|
+
lineHeight: fontSize,
|
|
393
412
|
};
|
|
394
413
|
}
|
|
395
414
|
}
|
|
@@ -478,7 +497,7 @@ export function createTextStyle(
|
|
|
478
497
|
if (props.leading === undefined) {
|
|
479
498
|
style.lineHeight =
|
|
480
499
|
typeof props.size === "number"
|
|
481
|
-
? props.size
|
|
500
|
+
? props.size
|
|
482
501
|
: sizeLineHeightMap[props.size];
|
|
483
502
|
}
|
|
484
503
|
}
|
|
@@ -492,10 +511,11 @@ export function createTextStyle(
|
|
|
492
511
|
}
|
|
493
512
|
|
|
494
513
|
if (props.leading) {
|
|
514
|
+
const fontSize = style.fontSize || 16; // default font size
|
|
495
515
|
style.lineHeight =
|
|
496
516
|
typeof props.leading === "number"
|
|
497
517
|
? props.leading
|
|
498
|
-
: leadingMap[props.leading];
|
|
518
|
+
: leadingMap[props.leading] * fontSize;
|
|
499
519
|
}
|
|
500
520
|
|
|
501
521
|
if (props.tracking) {
|