@streamplace/components 0.9.15 → 0.10.7

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.
Files changed (164) hide show
  1. package/dist/components/chat/badge.d.ts.map +1 -1
  2. package/dist/components/chat/badge.js +4 -2
  3. package/dist/components/chat/badge.js.map +1 -1
  4. package/dist/components/chat/chat-box.d.ts +2 -1
  5. package/dist/components/chat/chat-box.d.ts.map +1 -1
  6. package/dist/components/chat/chat-box.js +1 -1
  7. package/dist/components/chat/chat-box.js.map +1 -1
  8. package/dist/components/chat/chat-message.d.ts.map +1 -1
  9. package/dist/components/chat/chat-message.js +3 -3
  10. package/dist/components/chat/chat-message.js.map +1 -1
  11. package/dist/components/chat/chat.d.ts +4 -2
  12. package/dist/components/chat/chat.d.ts.map +1 -1
  13. package/dist/components/chat/chat.js +19 -5
  14. package/dist/components/chat/chat.js.map +1 -1
  15. package/dist/components/chat/mod-view.d.ts.map +1 -1
  16. package/dist/components/chat/mod-view.js +31 -6
  17. package/dist/components/chat/mod-view.js.map +1 -1
  18. package/dist/components/chat/teleport-modal.d.ts.map +1 -1
  19. package/dist/components/chat/teleport-modal.js +5 -4
  20. package/dist/components/chat/teleport-modal.js.map +1 -1
  21. package/dist/components/chat/user-profile-card.d.ts.map +1 -1
  22. package/dist/components/chat/user-profile-card.js +11 -8
  23. package/dist/components/chat/user-profile-card.js.map +1 -1
  24. package/dist/components/dashboard/moderator-panel.js +7 -1
  25. package/dist/components/dashboard/moderator-panel.js.map +1 -1
  26. package/dist/components/error-boundary.d.ts +15 -0
  27. package/dist/components/error-boundary.d.ts.map +1 -0
  28. package/dist/components/error-boundary.js +29 -0
  29. package/dist/components/error-boundary.js.map +1 -0
  30. package/dist/components/mobile-player/props.d.ts +1 -0
  31. package/dist/components/mobile-player/props.d.ts.map +1 -1
  32. package/dist/components/mobile-player/rotation-lock.js +2 -2
  33. package/dist/components/mobile-player/rotation-lock.js.map +1 -1
  34. package/dist/components/mobile-player/ui/viewer-context-menu.d.ts.map +1 -1
  35. package/dist/components/mobile-player/ui/viewer-context-menu.js +3 -2
  36. package/dist/components/mobile-player/ui/viewer-context-menu.js.map +1 -1
  37. package/dist/components/mobile-player/ui/viewer-loading-overlay.d.ts.map +1 -1
  38. package/dist/components/mobile-player/ui/viewer-loading-overlay.js +1 -0
  39. package/dist/components/mobile-player/ui/viewer-loading-overlay.js.map +1 -1
  40. package/dist/components/mobile-player/video-async.native.d.ts.map +1 -1
  41. package/dist/components/mobile-player/video-async.native.js +9 -2
  42. package/dist/components/mobile-player/video-async.native.js.map +1 -1
  43. package/dist/components/mobile-player/video.d.ts +3 -3
  44. package/dist/components/mobile-player/video.d.ts.map +1 -1
  45. package/dist/components/mobile-player/video.js.map +1 -1
  46. package/dist/components/stream-notification/pin-notification.d.ts +7 -0
  47. package/dist/components/stream-notification/pin-notification.d.ts.map +1 -0
  48. package/dist/components/stream-notification/pin-notification.js +63 -0
  49. package/dist/components/stream-notification/pin-notification.js.map +1 -0
  50. package/dist/components/ui/dropdown.d.ts.map +1 -1
  51. package/dist/components/ui/dropdown.js +3 -2
  52. package/dist/components/ui/dropdown.js.map +1 -1
  53. package/dist/components/ui/dropdown.native.d.ts.map +1 -1
  54. package/dist/components/ui/dropdown.native.js +3 -1
  55. package/dist/components/ui/dropdown.native.js.map +1 -1
  56. package/dist/components/ui/icons.js +1 -1
  57. package/dist/components/ui/icons.js.map +1 -1
  58. package/dist/components/ui/menu.d.ts +0 -8
  59. package/dist/components/ui/menu.d.ts.map +1 -1
  60. package/dist/components/ui/menu.js +1 -35
  61. package/dist/components/ui/menu.js.map +1 -1
  62. package/dist/components/ui/primitives/input.d.ts +4 -4
  63. package/dist/components/ui/primitives/input.d.ts.map +1 -1
  64. package/dist/components/ui/primitives/input.js +10 -3
  65. package/dist/components/ui/primitives/input.js.map +1 -1
  66. package/dist/components/ui/resizeable.d.ts +1 -1
  67. package/dist/components/ui/resizeable.d.ts.map +1 -1
  68. package/dist/components/ui/resizeable.js +8 -5
  69. package/dist/components/ui/resizeable.js.map +1 -1
  70. package/dist/components/ui/text.d.ts +1 -1
  71. package/dist/components/ui/toast.js +4 -4
  72. package/dist/components/ui/toast.js.map +1 -1
  73. package/dist/context/profile-cache.d.ts.map +1 -1
  74. package/dist/context/profile-cache.js +2 -1
  75. package/dist/context/profile-cache.js.map +1 -1
  76. package/dist/hooks/useSegmentTiming.js +1 -1
  77. package/dist/hooks/useSegmentTiming.js.map +1 -1
  78. package/dist/index.d.ts +1 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +3 -1
  81. package/dist/index.js.map +1 -1
  82. package/dist/lib/facet.js +1 -1
  83. package/dist/lib/facet.js.map +1 -1
  84. package/dist/lib/stream-notifications.d.ts +7 -0
  85. package/dist/lib/stream-notifications.d.ts.map +1 -1
  86. package/dist/lib/stream-notifications.js +21 -0
  87. package/dist/lib/stream-notifications.js.map +1 -1
  88. package/dist/lib/theme/atoms.d.ts +252 -252
  89. package/dist/lib/theme/theme.js +2 -2
  90. package/dist/lib/theme/theme.js.map +1 -1
  91. package/dist/livestream-provider/index.d.ts +1 -0
  92. package/dist/livestream-provider/index.d.ts.map +1 -1
  93. package/dist/livestream-provider/index.js +35 -3
  94. package/dist/livestream-provider/index.js.map +1 -1
  95. package/dist/livestream-store/chat.d.ts +2 -0
  96. package/dist/livestream-store/chat.d.ts.map +1 -1
  97. package/dist/livestream-store/chat.js +80 -1
  98. package/dist/livestream-store/chat.js.map +1 -1
  99. package/dist/livestream-store/livestream-state.d.ts +2 -1
  100. package/dist/livestream-store/livestream-state.d.ts.map +1 -1
  101. package/dist/livestream-store/livestream-store.d.ts +3 -2
  102. package/dist/livestream-store/livestream-store.d.ts.map +1 -1
  103. package/dist/livestream-store/livestream-store.js +4 -1
  104. package/dist/livestream-store/livestream-store.js.map +1 -1
  105. package/dist/livestream-store/websocket-consumer.d.ts.map +1 -1
  106. package/dist/livestream-store/websocket-consumer.js +14 -0
  107. package/dist/livestream-store/websocket-consumer.js.map +1 -1
  108. package/dist/streamplace-store/moderation.d.ts +1 -0
  109. package/dist/streamplace-store/moderation.d.ts.map +1 -1
  110. package/dist/streamplace-store/moderation.js +1 -0
  111. package/dist/streamplace-store/moderation.js.map +1 -1
  112. package/dist/streamplace-store/moderator-management.d.ts +1 -1
  113. package/dist/streamplace-store/moderator-management.d.ts.map +1 -1
  114. package/dist/streamplace-store/stream.d.ts +1 -1
  115. package/dist/streamplace-store/stream.d.ts.map +1 -1
  116. package/dist/streamplace-store/streamplace-store.d.ts +0 -1
  117. package/dist/streamplace-store/streamplace-store.d.ts.map +1 -1
  118. package/dist/streamplace-store/streamplace-store.js +1 -4
  119. package/dist/streamplace-store/streamplace-store.js.map +1 -1
  120. package/dist/streamplace-store/user.d.ts +1 -1
  121. package/dist/streamplace-store/user.d.ts.map +1 -1
  122. package/dist/streamplace-store/xrpc.js +4 -4
  123. package/dist/streamplace-store/xrpc.js.map +1 -1
  124. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  125. package/package.json +19 -18
  126. package/src/components/chat/badge.tsx +4 -2
  127. package/src/components/chat/chat-box.tsx +2 -0
  128. package/src/components/chat/chat-message.tsx +4 -5
  129. package/src/components/chat/chat.tsx +32 -5
  130. package/src/components/chat/mod-view.tsx +82 -3
  131. package/src/components/chat/teleport-modal.tsx +4 -3
  132. package/src/components/chat/user-profile-card.tsx +20 -8
  133. package/src/components/dashboard/moderator-panel.tsx +13 -2
  134. package/src/components/error-boundary.tsx +42 -0
  135. package/src/components/mobile-player/props.tsx +1 -0
  136. package/src/components/mobile-player/rotation-lock.tsx +2 -2
  137. package/src/components/mobile-player/ui/viewer-context-menu.tsx +3 -2
  138. package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +1 -0
  139. package/src/components/mobile-player/video-async.native.tsx +8 -1
  140. package/src/components/mobile-player/video.tsx +7 -5
  141. package/src/components/stream-notification/pin-notification.tsx +135 -0
  142. package/src/components/ui/dropdown.native.tsx +5 -1
  143. package/src/components/ui/dropdown.tsx +3 -2
  144. package/src/components/ui/icons.tsx +1 -1
  145. package/src/components/ui/menu.tsx +56 -62
  146. package/src/components/ui/primitives/input.tsx +18 -9
  147. package/src/components/ui/resizeable.tsx +11 -6
  148. package/src/components/ui/toast.tsx +1 -1
  149. package/src/context/profile-cache.tsx +7 -1
  150. package/src/hooks/useSegmentTiming.tsx +1 -1
  151. package/src/index.tsx +2 -0
  152. package/src/lib/facet.ts +1 -1
  153. package/src/lib/stream-notifications.ts +28 -0
  154. package/src/lib/theme/theme.tsx +1 -1
  155. package/src/livestream-provider/index.tsx +38 -2
  156. package/src/livestream-store/chat.tsx +92 -0
  157. package/src/livestream-store/livestream-state.tsx +2 -0
  158. package/src/livestream-store/livestream-store.tsx +4 -0
  159. package/src/livestream-store/websocket-consumer.tsx +15 -0
  160. package/src/streamplace-store/moderation.tsx +2 -0
  161. package/src/streamplace-store/moderator-management.tsx +1 -1
  162. package/src/streamplace-store/streamplace-store.tsx +0 -2
  163. package/src/streamplace-store/xrpc.tsx +1 -1
  164. package/node-compile-cache/v22.15.0-x64-92db9086-0/37be0eec +0 -0
@@ -0,0 +1,42 @@
1
+ import * as React from "react";
2
+ import { Text, View } from "react-native";
3
+
4
+ type ErrorBoundaryProps = {
5
+ children: React.ReactNode;
6
+ };
7
+
8
+ type ErrorBoundaryState = {
9
+ hasError: boolean;
10
+ };
11
+
12
+ export class ErrorBoundary extends React.Component<
13
+ ErrorBoundaryProps,
14
+ ErrorBoundaryState
15
+ > {
16
+ constructor(props: ErrorBoundaryProps) {
17
+ super(props);
18
+ this.state = { hasError: false };
19
+ }
20
+
21
+ static getDerivedStateFromError(error: any): ErrorBoundaryState {
22
+ // Update state so the next render will show the fallback UI.
23
+ return { hasError: true };
24
+ }
25
+
26
+ componentDidCatch(error: any, info: any) {
27
+ console.error("<ErrorBoundary> caught:", error);
28
+ }
29
+
30
+ render() {
31
+ if (this.state.hasError) {
32
+ // You can render any custom fallback UI
33
+ return (
34
+ <View>
35
+ <Text>Error</Text>
36
+ </View>
37
+ );
38
+ }
39
+
40
+ return this.props.children;
41
+ }
42
+ }
@@ -2,6 +2,7 @@ export type PlayerProps = {
2
2
  name: string;
3
3
  playerId?: string;
4
4
  src: string;
5
+ mode?: "live" | "vod";
5
6
  muted: boolean;
6
7
  telemetry: boolean;
7
8
  fullscreen: boolean;
@@ -64,12 +64,12 @@ export const RotationProvider: React.FC<RotationProviderProps> = ({
64
64
  try {
65
65
  await ScreenOrientation.unlockAsync();
66
66
  await ScreenOrientation.lockAsync(
67
- ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT,
67
+ ScreenOrientation.OrientationLock.LANDSCAPE_LEFT,
68
68
  );
69
69
  setIsLocked(true);
70
70
 
71
71
  // set current orientation to landscape right
72
- setCurrentOrientation(ScreenOrientation.Orientation.LANDSCAPE_RIGHT);
72
+ setCurrentOrientation(ScreenOrientation.Orientation.LANDSCAPE_LEFT);
73
73
 
74
74
  if (__DEV__) {
75
75
  console.log("📲 Manual landscape");
@@ -1,7 +1,8 @@
1
1
  import { useRootContext } from "@rn-primitives/dropdown-menu";
2
+ import { Image } from "expo-image";
2
3
  import { Cog } from "lucide-react-native";
3
4
  import { useState } from "react";
4
- import { Image, Linking, Platform, Pressable, View } from "react-native";
5
+ import { Linking, Platform, Pressable, View } from "react-native";
5
6
  import Animated, {
6
7
  Easing,
7
8
  useAnimatedStyle,
@@ -161,7 +162,7 @@ export function ContextMenu({
161
162
  uri: avatars[profile?.did]?.avatar,
162
163
  }}
163
164
  style={{ width: 42, height: 42, borderRadius: 999 }}
164
- resizeMode="cover"
165
+ contentFit="cover"
165
166
  />
166
167
  )}
167
168
  <View style={{ flex: 1, minWidth: 0 }}>
@@ -55,6 +55,7 @@ export function ViewerLoadingOverlay() {
55
55
  alignItems: "center",
56
56
  justifyContent: "center",
57
57
  backgroundColor: "rgba(0,0,0,0.3)",
58
+ pointerEvents: "none",
58
59
  },
59
60
  animatedStyle,
60
61
  ]}
@@ -91,6 +91,9 @@ export function NativeVideo(props?: {
91
91
 
92
92
  const handleLayout = useCallback((event: LayoutChangeEvent) => {
93
93
  const { width, height } = event.nativeEvent.layout;
94
+ if (dimensions.width === width && dimensions.height === height) {
95
+ return;
96
+ }
94
97
  setDimensions({ width, height });
95
98
  setPlayerWidth(width);
96
99
  setPlayerHeight(height);
@@ -165,7 +168,6 @@ export function NativeVideo(props?: {
165
168
  <VideoView
166
169
  ref={videoRef}
167
170
  player={player}
168
- allowsFullscreen
169
171
  nativeControls={fullscreen}
170
172
  onFullscreenEnter={() => {
171
173
  setFullscreen(true);
@@ -175,6 +177,11 @@ export function NativeVideo(props?: {
175
177
  }}
176
178
  allowsPictureInPicture={props?.pictureInPictureEnabled !== false}
177
179
  onLayout={handleLayout}
180
+ style={{
181
+ minWidth: "100%",
182
+ minHeight: "100%",
183
+ flex: 1,
184
+ }}
178
185
  />
179
186
  </>
180
187
  );
@@ -36,12 +36,14 @@ function assignVideoRef(
36
36
 
37
37
  type VideoProps = {
38
38
  url: string;
39
- videoRef?: React.RefObject<HTMLVideoElement>;
39
+ videoRef?: React.RefObject<HTMLVideoElement | null>;
40
40
  objectFit?: "contain" | "cover";
41
41
  pictureInPictureEnabled?: boolean;
42
42
  };
43
43
 
44
- function useVideoDimensions(videoRef: React.RefObject<HTMLVideoElement>) {
44
+ function useVideoDimensions(
45
+ videoRef: React.RefObject<HTMLVideoElement | null>,
46
+ ) {
45
47
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
46
48
 
47
49
  useEffect(() => {
@@ -133,7 +135,7 @@ const updateEvents = {
133
135
 
134
136
  const VideoElement = forwardRef<
135
137
  HTMLVideoElement,
136
- VideoProps & { videoRef?: React.RefObject<HTMLVideoElement> }
138
+ VideoProps & { videoRef?: React.RefObject<HTMLVideoElement | null> }
137
139
  >((props, ref) => {
138
140
  const x = usePlayerStore((x) => x);
139
141
  const url = useStreamplaceStore((x) => x.url);
@@ -359,7 +361,7 @@ export function HLSPlayer(props: VideoProps) {
359
361
  }
360
362
 
361
363
  export function WebRTCPlayer(
362
- props: VideoProps & { videoRef?: React.RefObject<HTMLVideoElement> },
364
+ props: VideoProps & { videoRef?: React.RefObject<HTMLVideoElement | null> },
363
365
  ) {
364
366
  const [webrtcError, setWebrtcError] = useState<string | null>(null);
365
367
  const setStatus = usePlayerStore((x) => x.setStatus);
@@ -432,7 +434,7 @@ export function WebRTCPlayerInner({
432
434
  width,
433
435
  height,
434
436
  }: {
435
- videoRef?: React.RefObject<HTMLVideoElement>;
437
+ videoRef?: React.RefObject<HTMLVideoElement | null>;
436
438
  url: string;
437
439
  width?: string | number;
438
440
  height?: string | number;
@@ -0,0 +1,135 @@
1
+ import { EyeOff, Pin, X } from "lucide-react-native";
2
+ import { useEffect, useState } from "react";
3
+ import { Linking, Pressable, View } from "react-native";
4
+ import { PinnedRecordViewHydrated, PlaceStreamChatProfile } from "streamplace";
5
+ import {
6
+ Text,
7
+ useCanModerate,
8
+ useLivestreamStore,
9
+ useTheme,
10
+ zero,
11
+ } from "../../";
12
+ import { RichtextSegment, segmentize } from "../../lib/facet";
13
+ import { formatHandleWithAt } from "../../utils/format-handle";
14
+
15
+ const getRgbColor = (color?: PlaceStreamChatProfile.Color) =>
16
+ color ? `rgb(${color.red}, ${color.green}, ${color.blue})` : undefined;
17
+
18
+ function renderSegment(segment: RichtextSegment, index: number) {
19
+ if (segment.features && segment.features.length > 0) {
20
+ const ftr = segment.features[0];
21
+ if (ftr.$type === "app.bsky.richtext.facet#link") {
22
+ return (
23
+ <Text
24
+ key={index}
25
+ style={[{ color: "#007AFF" }]}
26
+ onPress={() => Linking.openURL((ftr as any).uri || "")}
27
+ >
28
+ {segment.text}
29
+ </Text>
30
+ );
31
+ }
32
+ if (ftr.$type === "app.bsky.richtext.facet#mention") {
33
+ return (
34
+ <Text key={index} style={[{ color: "#007AFF" }]}>
35
+ {segment.text}
36
+ </Text>
37
+ );
38
+ }
39
+ }
40
+ return <Text key={index}>{segment.text}</Text>;
41
+ }
42
+
43
+ export function PinnedCommentNotification({
44
+ pinnedComment,
45
+ onDismiss,
46
+ onUnpin,
47
+ }: {
48
+ pinnedComment: PinnedRecordViewHydrated;
49
+ onDismiss: () => void;
50
+ onUnpin: () => void;
51
+ }) {
52
+ const z = useTheme();
53
+ const message = pinnedComment.message;
54
+ const pinnedByColor = (pinnedComment.pinnedBy as any)?.color || "#bebebe";
55
+ const record = pinnedComment.record;
56
+
57
+ const currentStreamer = useLivestreamStore((state) => state.profile?.did);
58
+
59
+ console.log("checking if we can mod", currentStreamer);
60
+
61
+ const canActuallyPin = useCanModerate(currentStreamer)?.canPin;
62
+
63
+ const messageRecord = message?.record as any;
64
+
65
+ const [expiresAt] = useState<Date | null>(
66
+ record.expiresAt ? new Date(record.expiresAt) : null,
67
+ );
68
+
69
+ useEffect(() => {
70
+ if (!expiresAt) return;
71
+ const remaining = expiresAt.getTime() - Date.now();
72
+ if (remaining <= 0) {
73
+ onDismiss();
74
+ return;
75
+ }
76
+ const timeout = setTimeout(onDismiss, remaining);
77
+ return () => clearTimeout(timeout);
78
+ }, [expiresAt, onDismiss]);
79
+
80
+ const authorName = message ? formatHandleWithAt(message.author) : "unknown";
81
+ const authorColor = getRgbColor((message as any)?.chatProfile?.color);
82
+
83
+ const segments = messageRecord
84
+ ? segmentize(messageRecord.text, messageRecord.facets)
85
+ : [];
86
+
87
+ return (
88
+ <View style={[zero.bg.neutral[900], zero.r.lg, { overflow: "hidden" }]}>
89
+ <View
90
+ style={[
91
+ zero.layout.flex.row,
92
+ zero.layout.flex.alignCenter,
93
+ zero.px[3],
94
+ zero.py[2],
95
+ { gap: 8 },
96
+ ]}
97
+ >
98
+ <View style={{ transform: [{ rotate: "-25deg" }] }}>
99
+ <Pin
100
+ size={24}
101
+ color={authorColor || z.theme.colors.primary}
102
+ fill={authorColor || z.theme.colors.primary}
103
+ />
104
+ </View>
105
+ <View
106
+ style={[zero.layout.flex.column, zero.flex.values[1], { gap: 4 }]}
107
+ >
108
+ <View style={[zero.layout.flex.row, { gap: 4, flexWrap: "wrap" }]}>
109
+ <Text
110
+ style={[
111
+ {
112
+ fontWeight: "600",
113
+ color: authorColor || zero.colors.gray[200],
114
+ },
115
+ ]}
116
+ >
117
+ {authorName}
118
+ </Text>
119
+ {segments.map((seg, i) => renderSegment(seg, i))}
120
+ </View>
121
+ </View>
122
+ <View style={[zero.layout.flex.row, { gap: 4 }]}>
123
+ {canActuallyPin && (
124
+ <Pressable onPress={onUnpin} style={{ padding: 4 }}>
125
+ <X size={16} color={zero.colors.gray[400]} />
126
+ </Pressable>
127
+ )}
128
+ <Pressable onPress={onDismiss} style={{ padding: 4 }}>
129
+ <EyeOff size={16} color={zero.colors.gray[400]} />
130
+ </Pressable>
131
+ </View>
132
+ </View>
133
+ </View>
134
+ );
135
+ }
@@ -358,7 +358,11 @@ export const DropdownMenuSubTrigger = forwardRef<
358
358
  const { icons } = useTheme();
359
359
 
360
360
  React.useEffect(() => {
361
- if (subMenuContext && subMenuTitle) {
361
+ if (
362
+ subMenuContext &&
363
+ subMenuTitle &&
364
+ subMenuContext.title !== subMenuTitle
365
+ ) {
362
366
  subMenuContext.setTitle(subMenuTitle);
363
367
  }
364
368
  }, [subMenuContext, subMenuTitle]);
@@ -74,7 +74,7 @@ export const DropdownMenuSubTrigger = forwardRef<
74
74
  inset?: boolean;
75
75
  children?: React.ReactNode;
76
76
  }
77
- >(({ inset, children, subMenuTitle, ...props }, ref) => {
77
+ >(({ inset, children, subMenuTitle, style, ...props }, ref) => {
78
78
  const { icons } = useTheme();
79
79
  const { open } = DropdownMenuPrimitive.useSubContext();
80
80
  const Icon =
@@ -96,6 +96,7 @@ export const DropdownMenuSubTrigger = forwardRef<
96
96
  layout.flex.alignCenter,
97
97
  p[2],
98
98
  pr[8],
99
+ style,
99
100
  ]}
100
101
  >
101
102
  {children}
@@ -513,7 +514,7 @@ export const DropdownMenuGroup = forwardRef<
513
514
  const { theme } = useTheme();
514
515
  const { inset, title, children, ...rest } = props;
515
516
  return (
516
- <View style={[pt[2], inset && gap[2]]} ref={ref} {...rest}>
517
+ <View style={[inset && gap[2]]} ref={ref} {...rest}>
517
518
  {title && (
518
519
  <Text style={[{ color: theme.colors.textMuted }, pb[1], pl[2]]}>
519
520
  {title}
@@ -1,6 +1,6 @@
1
1
  import { type LucideProps } from "lucide-react-native";
2
2
  import React from "react";
3
- import { useTheme } from "../../lib/theme";
3
+ import { useTheme } from "../../lib/theme/theme";
4
4
 
5
5
  // Simple icon wrapper that integrates with theme
6
6
  export interface IconProps {
@@ -1,10 +1,4 @@
1
- import {
2
- Children,
3
- cloneElement,
4
- forwardRef,
5
- isValidElement,
6
- ReactNode,
7
- } from "react";
1
+ import { forwardRef, ReactNode } from "react";
8
2
  import { Animated, Platform, View, ViewStyle } from "react-native";
9
3
  import { Gesture, GestureDetector } from "react-native-gesture-handler";
10
4
  import {
@@ -285,63 +279,63 @@ export const MenuInfo = forwardRef<View, MenuInfoProps>(
285
279
  },
286
280
  );
287
281
 
288
- export interface MenuDraggableGroupProps {
289
- children: ReactNode;
290
- onMove: (fromIndex: number, toIndex: number) => void;
291
- onDragEnd: (fromIndex: number, toIndex: number) => void;
292
- dragHandle?: ReactNode;
293
- style?: ViewStyle;
294
- }
282
+ // export interface MenuDraggableGroupProps {
283
+ // children: ReactNode;
284
+ // onMove: (fromIndex: number, toIndex: number) => void;
285
+ // onDragEnd: (fromIndex: number, toIndex: number) => void;
286
+ // dragHandle?: ReactNode;
287
+ // style?: ViewStyle;
288
+ // }
295
289
 
296
- export const MenuDraggableGroup = forwardRef<View, MenuDraggableGroupProps>(
297
- ({ children, onMove, onDragEnd, dragHandle, style }, ref) => {
298
- const { theme } = useTheme();
290
+ // export const MenuDraggableGroup = forwardRef<View, MenuDraggableGroupProps>(
291
+ // ({ children, onMove, onDragEnd, dragHandle, style }, ref) => {
292
+ // const { theme } = useTheme();
299
293
 
300
- const childrenArray = Children.toArray(children);
301
- const draggableItems = childrenArray.filter(
302
- (child) =>
303
- isValidElement(child) &&
304
- (child.type === MenuItem || child.type === MenuSeparator),
305
- );
294
+ // const childrenArray = Children.toArray(children);
295
+ // const draggableItems = childrenArray.filter(
296
+ // (child) =>
297
+ // isValidElement(child) &&
298
+ // (child.type === MenuItem || child.type === MenuSeparator),
299
+ // );
306
300
 
307
- let itemIndex = 0;
308
- const enhancedChildren = Children.map(children, (child) => {
309
- if (isValidElement(child)) {
310
- if (child.type === MenuItem) {
311
- const currentIndex = itemIndex;
312
- itemIndex++;
301
+ // let itemIndex = 0;
302
+ // const enhancedChildren = Children.map(children, (child) => {
303
+ // if (isValidElement(child)) {
304
+ // if (child.type === MenuItem) {
305
+ // const currentIndex = itemIndex;
306
+ // itemIndex++;
313
307
 
314
- return cloneElement(child, {
315
- draggable: true,
316
- dragHandle: dragHandle || child.props.dragHandle,
317
- _dragIndex: currentIndex,
318
- _dragTotalItems: draggableItems.filter(
319
- (c) => isValidElement(c) && c.type === MenuItem,
320
- ).length,
321
- _onDragMove: onMove,
322
- _onDragEnd: onDragEnd,
323
- } as any);
324
- }
325
- if (child.type === MenuSeparator) {
326
- return child;
327
- }
328
- }
329
- return child;
330
- });
308
+ // return cloneElement(child, {
309
+ // draggable: true,
310
+ // dragHandle: dragHandle || (child && child.props.dragHandle),
311
+ // _dragIndex: currentIndex,
312
+ // _dragTotalItems: draggableItems.filter(
313
+ // (c) => isValidElement(c) && c.type === MenuItem,
314
+ // ).length,
315
+ // _onDragMove: onMove,
316
+ // _onDragEnd: onDragEnd,
317
+ // } as any);
318
+ // }
319
+ // if (child.type === MenuSeparator) {
320
+ // return child;
321
+ // }
322
+ // }
323
+ // return child;
324
+ // });
331
325
 
332
- return (
333
- <View
334
- ref={ref}
335
- style={[
336
- { backgroundColor: theme.colors.muted + "c0" },
337
- Platform.OS === "web" ? [px[1], py[1]] : p[1],
338
- gap.all[1],
339
- { borderRadius: borderRadius.lg },
340
- style,
341
- ]}
342
- >
343
- {enhancedChildren}
344
- </View>
345
- );
346
- },
347
- );
326
+ // return (
327
+ // <View
328
+ // ref={ref}
329
+ // style={[
330
+ // { backgroundColor: theme.colors.muted + "c0" },
331
+ // Platform.OS === "web" ? [px[1], py[1]] : p[1],
332
+ // gap.all[1],
333
+ // { borderRadius: borderRadius.lg },
334
+ // style,
335
+ // ]}
336
+ // >
337
+ // {enhancedChildren}
338
+ // </View>
339
+ // );
340
+ // },
341
+ // );
@@ -4,28 +4,29 @@ import {
4
4
  } from "@gorhom/bottom-sheet";
5
5
  import React, { forwardRef } from "react";
6
6
  import {
7
- NativeSyntheticEvent,
7
+ BlurEvent,
8
+ FocusEvent,
8
9
  Platform,
9
10
  StyleSheet,
10
11
  Text,
11
12
  TextInput,
12
- TextInputFocusEventData,
13
13
  TextInputProps,
14
14
  TextProps,
15
15
  TouchableOpacity,
16
16
  View,
17
17
  ViewProps,
18
18
  } from "react-native";
19
- import { tokens } from "../../../ui";
19
+ import * as tokens from "../../../lib/theme/tokens";
20
20
 
21
21
  // Base input primitive interface
22
- export interface InputPrimitiveProps extends Omit<TextInputProps, "onChange"> {
22
+ export interface InputPrimitiveProps
23
+ extends Omit<TextInputProps, "onChange" | "onFocus" | "onBlur"> {
23
24
  error?: boolean;
24
25
  disabled?: boolean;
25
26
  loading?: boolean;
26
27
  onChange?: (text: string) => void;
27
- onFocus?: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
28
- onBlur?: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
28
+ onFocus?: (event: FocusEvent) => void;
29
+ onBlur?: (event: BlurEvent) => void;
29
30
  }
30
31
 
31
32
  // Input root primitive - the main TextInput component
@@ -75,7 +76,7 @@ export const InputRoot = forwardRef<any, InputPrimitiveProps>(
75
76
  );
76
77
 
77
78
  const handleFocus = React.useCallback(
78
- (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
79
+ (event: FocusEvent) => {
79
80
  setIsFocused(true);
80
81
  if (onFocus) {
81
82
  onFocus(event);
@@ -85,7 +86,7 @@ export const InputRoot = forwardRef<any, InputPrimitiveProps>(
85
86
  );
86
87
 
87
88
  const handleBlur = React.useCallback(
88
- (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
89
+ (event: BlurEvent) => {
89
90
  setIsFocused(false);
90
91
  if (onBlur) {
91
92
  onBlur(event);
@@ -272,13 +273,21 @@ export const InputAddon = forwardRef<
272
273
  style,
273
274
  ];
274
275
 
276
+ // Filter out null event handlers for TouchableOpacity compatibility
277
+ const { onBlur, onFocus, ...restProps } = props;
278
+ const touchableProps = {
279
+ ...restProps,
280
+ ...(onBlur !== null && onBlur !== undefined && { onBlur }),
281
+ ...(onFocus !== null && onFocus !== undefined && { onFocus }),
282
+ };
283
+
275
284
  if (touchable && onPress) {
276
285
  return (
277
286
  <TouchableOpacity
278
287
  ref={ref as React.Ref<React.ComponentRef<typeof TouchableOpacity>>}
279
288
  style={addonStyle as any}
280
289
  onPress={onPress}
281
- {...props}
290
+ {...touchableProps}
282
291
  >
283
292
  {children}
284
293
  </TouchableOpacity>
@@ -7,12 +7,14 @@ import {
7
7
  Pressable,
8
8
  } from "react-native-gesture-handler";
9
9
  import Animated, {
10
+ Easing,
10
11
  Extrapolation,
11
12
  interpolate,
12
13
  runOnJS,
13
14
  useAnimatedStyle,
14
15
  useSharedValue,
15
16
  withSpring,
17
+ withTiming,
16
18
  } from "react-native-reanimated";
17
19
  import { useSafeAreaInsets } from "react-native-safe-area-context";
18
20
  import { useKeyboardSlide } from "../../hooks";
@@ -23,6 +25,11 @@ const AnimatedView = Animated.createAnimatedComponent(View);
23
25
 
24
26
  const { height: SCREEN_HEIGHT } = Dimensions.get("window");
25
27
 
28
+ const TIMING_CONFIG = {
29
+ duration: 400,
30
+ easing: Easing.out(Easing.quad),
31
+ };
32
+
26
33
  type ResizableChatSheetProps = {
27
34
  startingPercentage?: number;
28
35
  isPlayerRatioGreater: boolean;
@@ -31,8 +38,6 @@ type ResizableChatSheetProps = {
31
38
  renderAbove?: (isCollapsed: boolean) => React.ReactNode;
32
39
  };
33
40
 
34
- const SPRING_CONFIG = { damping: 20, stiffness: 100 };
35
-
36
41
  export function Resizable({
37
42
  startingPercentage,
38
43
  isPlayerRatioGreater,
@@ -56,7 +61,7 @@ export function Resizable({
56
61
  const targetHeight = startingPercentage
57
62
  ? startingPercentage * SCREEN_HEIGHT
58
63
  : MIN_HEIGHT;
59
- sheetHeight.value = withSpring(targetHeight, SPRING_CONFIG);
64
+ sheetHeight.value = withTiming(targetHeight, TIMING_CONFIG);
60
65
  setIsCollapsed(targetHeight < COLLAPSE_HEIGHT);
61
66
  }, 1000);
62
67
  }, []);
@@ -73,7 +78,7 @@ export function Resizable({
73
78
 
74
79
  const nowCollapsed = newHeight < COLLAPSE_HEIGHT;
75
80
  if (nowCollapsed && !wasCollapsed.value) {
76
- sheetHeight.value = withSpring(MIN_HEIGHT, SPRING_CONFIG);
81
+ sheetHeight.value = withTiming(MIN_HEIGHT, TIMING_CONFIG);
77
82
  wasCollapsed.value = true;
78
83
  runOnJS(setIsCollapsed)(true);
79
84
  } else if (!nowCollapsed && wasCollapsed.value) {
@@ -139,8 +144,8 @@ export function Resizable({
139
144
  onPress={() => {
140
145
  const isCurrentlyCollapsed = sheetHeight.value === MIN_HEIGHT;
141
146
  sheetHeight.value = isCurrentlyCollapsed
142
- ? withSpring(MAX_HEIGHT, SPRING_CONFIG)
143
- : withSpring(MIN_HEIGHT, SPRING_CONFIG);
147
+ ? withTiming(MAX_HEIGHT, TIMING_CONFIG)
148
+ : withTiming(MIN_HEIGHT, TIMING_CONFIG);
144
149
  setIsCollapsed(!isCurrentlyCollapsed);
145
150
  }}
146
151
  >
@@ -21,7 +21,7 @@ import Animated, {
21
21
  } from "react-native-reanimated";
22
22
  import { useSafeAreaInsets } from "react-native-safe-area-context";
23
23
  import { Circle, Svg } from "react-native-svg";
24
- import { useTheme } from "../../ui";
24
+ import { useTheme } from "../../lib/theme/theme";
25
25
  import { Button } from "./button";
26
26
  import { Text } from "./text";
27
27
 
@@ -3,6 +3,7 @@ import {
3
3
  createContext,
4
4
  useCallback,
5
5
  useContext,
6
+ useMemo,
6
7
  useRef,
7
8
  useState,
8
9
  } from "react";
@@ -85,8 +86,13 @@ export function ProfileCacheProvider({
85
86
  [flush],
86
87
  );
87
88
 
89
+ const value = useMemo(
90
+ () => ({ profiles, requestProfiles }),
91
+ [profiles, requestProfiles],
92
+ );
93
+
88
94
  return (
89
- <ProfileCacheContext.Provider value={{ profiles, requestProfiles }}>
95
+ <ProfileCacheContext.Provider value={value}>
90
96
  {children}
91
97
  </ProfileCacheContext.Provider>
92
98
  );
@@ -22,7 +22,7 @@ function getLiveConnectionQuality(
22
22
  export function useSegmentTiming() {
23
23
  const latestSegment = useLivestreamStore((x) => x.segment);
24
24
  const [segmentDeltas, setSegmentDeltas] = useState<number[]>([]);
25
- const prevSegmentRef = useRef<any>();
25
+ const prevSegmentRef = useRef(latestSegment);
26
26
  const prevTimestampRef = useRef<number | null>(null);
27
27
  const ls = useLivestream();
28
28