@streamplace/components 0.9.0 → 0.9.1
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/chat/chat-box.d.ts.map +1 -1
- package/dist/components/chat/chat-box.js +90 -34
- package/dist/components/chat/chat-box.js.map +1 -1
- package/dist/components/chat/chat-message.d.ts +4 -0
- package/dist/components/chat/chat-message.d.ts.map +1 -1
- package/dist/components/chat/chat-message.js +3 -2
- package/dist/components/chat/chat-message.js.map +1 -1
- package/dist/components/chat/chat.d.ts.map +1 -1
- package/dist/components/chat/chat.js +56 -3
- package/dist/components/chat/chat.js.map +1 -1
- package/dist/components/chat/emoji-suggestions.d.ts.map +1 -1
- package/dist/components/chat/emoji-suggestions.js +11 -11
- package/dist/components/chat/emoji-suggestions.js.map +1 -1
- package/dist/components/chat/mention-suggestions.d.ts.map +1 -1
- package/dist/components/chat/mention-suggestions.js +20 -19
- package/dist/components/chat/mention-suggestions.js.map +1 -1
- package/dist/components/chat/system-message.d.ts +5 -1
- package/dist/components/chat/system-message.d.ts.map +1 -1
- package/dist/components/chat/system-message.js +4 -4
- package/dist/components/chat/system-message.js.map +1 -1
- package/dist/components/mobile-player/shared.d.ts +1 -1
- package/dist/components/mobile-player/shared.d.ts.map +1 -1
- package/dist/components/mobile-player/shared.js +11 -10
- package/dist/components/mobile-player/shared.js.map +1 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.d.ts +1 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.js +60 -43
- package/dist/components/mobile-player/ui/viewer-context-menu.js.map +1 -1
- package/dist/components/stream-notification/index.d.ts +3 -0
- package/dist/components/stream-notification/index.d.ts.map +1 -0
- package/dist/components/stream-notification/index.js +9 -0
- package/dist/components/stream-notification/index.js.map +1 -0
- package/dist/components/stream-notification/stream-notification-manager.d.ts +36 -0
- package/dist/components/stream-notification/stream-notification-manager.d.ts.map +1 -0
- package/dist/components/stream-notification/stream-notification-manager.js +96 -0
- package/dist/components/stream-notification/stream-notification-manager.js.map +1 -0
- package/dist/components/stream-notification/stream-notification.d.ts +5 -0
- package/dist/components/stream-notification/stream-notification.d.ts.map +1 -0
- package/dist/components/stream-notification/stream-notification.js +146 -0
- package/dist/components/stream-notification/stream-notification.js.map +1 -0
- package/dist/components/stream-notification/teleport-notification.d.ts +8 -0
- package/dist/components/stream-notification/teleport-notification.d.ts.map +1 -0
- package/dist/components/stream-notification/teleport-notification.js +116 -0
- package/dist/components/stream-notification/teleport-notification.js.map +1 -0
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/button.js +7 -0
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/dialog.d.ts +2 -2
- package/dist/components/ui/dropdown.d.ts +4 -0
- package/dist/components/ui/dropdown.d.ts.map +1 -1
- package/dist/components/ui/dropdown.js +41 -15
- package/dist/components/ui/dropdown.js.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +1 -0
- package/dist/components/ui/index.js.map +1 -1
- package/dist/components/ui/portal.d.ts +2 -0
- package/dist/components/ui/portal.d.ts.map +1 -0
- package/dist/components/ui/portal.js +5 -0
- package/dist/components/ui/portal.js.map +1 -0
- package/dist/components/ui/portal.web.d.ts +11 -0
- package/dist/components/ui/portal.web.d.ts.map +1 -0
- package/dist/components/ui/portal.web.js +22 -0
- package/dist/components/ui/portal.web.js.map +1 -0
- package/dist/components/ui/resizeable.d.ts +2 -1
- package/dist/components/ui/resizeable.d.ts.map +1 -1
- package/dist/components/ui/resizeable.js +68 -26
- package/dist/components/ui/resizeable.js.map +1 -1
- package/dist/components/ui/text.d.ts +1 -1
- package/dist/components/ui/view.d.ts +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/slash-commands/teleport.d.ts +4 -0
- package/dist/lib/slash-commands/teleport.d.ts.map +1 -0
- package/dist/lib/slash-commands/teleport.js +110 -0
- package/dist/lib/slash-commands/teleport.js.map +1 -0
- package/dist/lib/slash-commands.d.ts +16 -0
- package/dist/lib/slash-commands.d.ts.map +1 -0
- package/dist/lib/slash-commands.js +46 -0
- package/dist/lib/slash-commands.js.map +1 -0
- package/dist/lib/stream-notifications.d.ts +13 -0
- package/dist/lib/stream-notifications.d.ts.map +1 -0
- package/dist/lib/stream-notifications.js +46 -0
- package/dist/lib/stream-notifications.js.map +1 -0
- package/dist/lib/system-messages.d.ts +4 -8
- package/dist/lib/system-messages.d.ts.map +1 -1
- package/dist/lib/system-messages.js +38 -2
- package/dist/lib/system-messages.js.map +1 -1
- package/dist/lib/theme/atoms.d.ts +193 -193
- package/dist/livestream-provider/index.d.ts +7 -2
- package/dist/livestream-provider/index.d.ts.map +1 -1
- package/dist/livestream-provider/index.js +72 -4
- package/dist/livestream-provider/index.js.map +1 -1
- package/dist/livestream-store/livestream-state.d.ts +4 -1
- package/dist/livestream-store/livestream-state.d.ts.map +1 -1
- package/dist/livestream-store/livestream-store.d.ts.map +1 -1
- package/dist/livestream-store/livestream-store.js +3 -0
- package/dist/livestream-store/livestream-store.js.map +1 -1
- package/dist/livestream-store/websocket-consumer.d.ts.map +1 -1
- package/dist/livestream-store/websocket-consumer.js +30 -43
- package/dist/livestream-store/websocket-consumer.js.map +1 -1
- package/dist/streamplace-store/index.d.ts +1 -0
- package/dist/streamplace-store/index.d.ts.map +1 -1
- package/dist/streamplace-store/index.js +1 -0
- package/dist/streamplace-store/index.js.map +1 -1
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/package.json +4 -2
- package/src/components/chat/chat-box.tsx +126 -53
- package/src/components/chat/chat-message.tsx +1 -1
- package/src/components/chat/chat.tsx +79 -5
- package/src/components/chat/emoji-suggestions.tsx +27 -25
- package/src/components/chat/mention-suggestions.tsx +36 -33
- package/src/components/chat/system-message.tsx +14 -5
- package/src/components/mobile-player/shared.tsx +2 -1
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +192 -166
- package/src/components/stream-notification/index.ts +5 -0
- package/src/components/stream-notification/stream-notification-manager.ts +140 -0
- package/src/components/stream-notification/stream-notification.tsx +227 -0
- package/src/components/stream-notification/teleport-notification.tsx +187 -0
- package/src/components/ui/button.tsx +7 -0
- package/src/components/ui/dropdown.tsx +96 -26
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/portal.tsx +1 -0
- package/src/components/ui/portal.web.tsx +37 -0
- package/src/components/ui/resizeable.tsx +89 -35
- package/src/index.tsx +3 -0
- package/src/lib/slash-commands/teleport.ts +136 -0
- package/src/lib/slash-commands.ts +65 -0
- package/src/lib/stream-notifications.ts +51 -0
- package/src/lib/system-messages.ts +52 -2
- package/src/livestream-provider/index.tsx +106 -3
- package/src/livestream-store/livestream-state.tsx +4 -0
- package/src/livestream-store/livestream-store.tsx +3 -0
- package/src/livestream-store/websocket-consumer.tsx +35 -54
- package/src/streamplace-store/index.tsx +1 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { X } from "lucide-react-native";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Pressable, StyleSheet, View } from "react-native";
|
|
4
|
+
import Animated, {
|
|
5
|
+
Easing,
|
|
6
|
+
useAnimatedStyle,
|
|
7
|
+
useSharedValue,
|
|
8
|
+
withTiming,
|
|
9
|
+
} from "react-native-reanimated";
|
|
10
|
+
import { Text, useTheme } from "../../";
|
|
11
|
+
import {
|
|
12
|
+
StreamNotification,
|
|
13
|
+
streamNotificationManager,
|
|
14
|
+
} from "./stream-notification-manager";
|
|
15
|
+
|
|
16
|
+
export function StreamNotificationProvider({
|
|
17
|
+
children = <></>,
|
|
18
|
+
position = "top",
|
|
19
|
+
}: {
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
position?: "top" | "bottom";
|
|
22
|
+
}) {
|
|
23
|
+
const [notifications, setNotifications] = useState(
|
|
24
|
+
streamNotificationManager.getAll(),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
return streamNotificationManager.subscribe(setNotifications);
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<View style={styles.container}>
|
|
33
|
+
{children}
|
|
34
|
+
{notifications.map((notification, index) => (
|
|
35
|
+
<NotificationItem
|
|
36
|
+
key={notification.id}
|
|
37
|
+
notification={notification}
|
|
38
|
+
index={index}
|
|
39
|
+
position={position}
|
|
40
|
+
/>
|
|
41
|
+
))}
|
|
42
|
+
</View>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function NotificationItem({
|
|
47
|
+
notification,
|
|
48
|
+
index,
|
|
49
|
+
position,
|
|
50
|
+
}: {
|
|
51
|
+
notification: StreamNotification;
|
|
52
|
+
index: number;
|
|
53
|
+
position: "top" | "bottom";
|
|
54
|
+
}) {
|
|
55
|
+
const { theme } = useTheme();
|
|
56
|
+
const translateY = useSharedValue(position === "top" ? -100 : 100);
|
|
57
|
+
const opacity = useSharedValue(0);
|
|
58
|
+
const [isExiting, setIsExiting] = useState(false);
|
|
59
|
+
|
|
60
|
+
const NOTIFICATION_HEIGHT = 60;
|
|
61
|
+
const NOTIFICATION_GAP = 8;
|
|
62
|
+
const offset = 16 + index * (NOTIFICATION_HEIGHT + NOTIFICATION_GAP);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
translateY.value = withTiming(position === "top" ? offset : -offset, {
|
|
66
|
+
duration: 300,
|
|
67
|
+
easing: Easing.out(Easing.cubic),
|
|
68
|
+
});
|
|
69
|
+
opacity.value = withTiming(1, {
|
|
70
|
+
duration: 200,
|
|
71
|
+
});
|
|
72
|
+
}, [offset, position]);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (notification.shouldDismiss && !isExiting) {
|
|
76
|
+
setIsExiting(true);
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
streamNotificationManager.hide(
|
|
79
|
+
notification.id,
|
|
80
|
+
notification.dismissReason || "auto",
|
|
81
|
+
);
|
|
82
|
+
}, 200);
|
|
83
|
+
}
|
|
84
|
+
}, [
|
|
85
|
+
notification.shouldDismiss,
|
|
86
|
+
isExiting,
|
|
87
|
+
notification.id,
|
|
88
|
+
notification.dismissReason,
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (isExiting) {
|
|
93
|
+
translateY.value = withTiming(position === "top" ? -100 : 100, {
|
|
94
|
+
duration: 200,
|
|
95
|
+
easing: Easing.in(Easing.cubic),
|
|
96
|
+
});
|
|
97
|
+
opacity.value = withTiming(0, {
|
|
98
|
+
duration: 200,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}, [isExiting, position]);
|
|
102
|
+
|
|
103
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
104
|
+
transform: [{ translateY: translateY.value }],
|
|
105
|
+
opacity: opacity.value,
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
const variantStyles = {
|
|
109
|
+
default: {
|
|
110
|
+
backgroundColor: theme.colors.card,
|
|
111
|
+
borderColor: theme.colors.border,
|
|
112
|
+
},
|
|
113
|
+
info: {
|
|
114
|
+
backgroundColor: theme.colors.info,
|
|
115
|
+
borderColor: theme.colors.info,
|
|
116
|
+
},
|
|
117
|
+
warning: {
|
|
118
|
+
backgroundColor: theme.colors.warning,
|
|
119
|
+
borderColor: theme.colors.warning,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const handleDismiss = (reason: "user" | "auto" = "user") => {
|
|
124
|
+
console.log("Dismissing notification:", notification.id);
|
|
125
|
+
setIsExiting(true);
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
console.log("Requesting dismiss for notification:", notification.id);
|
|
128
|
+
streamNotificationManager.hide(notification.id, reason);
|
|
129
|
+
}, 200);
|
|
130
|
+
console.log(streamNotificationManager.getAll());
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const handleAction = () => {
|
|
134
|
+
notification.onAction?.();
|
|
135
|
+
streamNotificationManager.hide(notification.id, "user");
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const positionStyle = position === "top" ? { top: 0 } : { bottom: 0 };
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<Animated.View
|
|
142
|
+
style={[
|
|
143
|
+
styles.notification,
|
|
144
|
+
positionStyle,
|
|
145
|
+
notification.render
|
|
146
|
+
? {}
|
|
147
|
+
: variantStyles[notification.variant || "default"],
|
|
148
|
+
{ margin: 0, padding: 0 },
|
|
149
|
+
animatedStyle,
|
|
150
|
+
]}
|
|
151
|
+
>
|
|
152
|
+
{notification.render ? (
|
|
153
|
+
notification.render(isExiting, handleDismiss, notification.startTime)
|
|
154
|
+
) : (
|
|
155
|
+
<View style={styles.content}>
|
|
156
|
+
<Text style={[styles.message, { color: theme.colors.foreground }]}>
|
|
157
|
+
{notification.message}
|
|
158
|
+
</Text>
|
|
159
|
+
|
|
160
|
+
<View style={styles.actions}>
|
|
161
|
+
{notification.actionLabel && (
|
|
162
|
+
<Pressable onPress={handleAction}>
|
|
163
|
+
<Text
|
|
164
|
+
style={[styles.actionButton, { color: theme.colors.primary }]}
|
|
165
|
+
>
|
|
166
|
+
{notification.actionLabel}
|
|
167
|
+
</Text>
|
|
168
|
+
</Pressable>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
<Pressable
|
|
172
|
+
onPress={() => handleDismiss("user")}
|
|
173
|
+
style={styles.closeButton}
|
|
174
|
+
>
|
|
175
|
+
<X size={16} color={theme.colors.mutedForeground} />
|
|
176
|
+
</Pressable>
|
|
177
|
+
</View>
|
|
178
|
+
</View>
|
|
179
|
+
)}
|
|
180
|
+
</Animated.View>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const styles = StyleSheet.create({
|
|
185
|
+
container: {
|
|
186
|
+
flex: 1,
|
|
187
|
+
pointerEvents: "box-none",
|
|
188
|
+
},
|
|
189
|
+
notification: {
|
|
190
|
+
position: "absolute",
|
|
191
|
+
top: 0,
|
|
192
|
+
left: 16,
|
|
193
|
+
right: 16,
|
|
194
|
+
zIndex: 9999,
|
|
195
|
+
borderRadius: 8,
|
|
196
|
+
borderWidth: 1,
|
|
197
|
+
padding: 12,
|
|
198
|
+
shadowColor: "#000",
|
|
199
|
+
shadowOffset: { width: 0, height: 2 },
|
|
200
|
+
shadowOpacity: 0.25,
|
|
201
|
+
shadowRadius: 8,
|
|
202
|
+
elevation: 5,
|
|
203
|
+
},
|
|
204
|
+
content: {
|
|
205
|
+
flexDirection: "row",
|
|
206
|
+
alignItems: "center",
|
|
207
|
+
justifyContent: "space-between",
|
|
208
|
+
gap: 12,
|
|
209
|
+
},
|
|
210
|
+
message: {
|
|
211
|
+
flex: 1,
|
|
212
|
+
fontSize: 14,
|
|
213
|
+
fontWeight: "500",
|
|
214
|
+
},
|
|
215
|
+
actions: {
|
|
216
|
+
flexDirection: "row",
|
|
217
|
+
alignItems: "center",
|
|
218
|
+
gap: 12,
|
|
219
|
+
},
|
|
220
|
+
actionButton: {
|
|
221
|
+
fontSize: 14,
|
|
222
|
+
fontWeight: "600",
|
|
223
|
+
},
|
|
224
|
+
closeButton: {
|
|
225
|
+
padding: 4,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useWindowDimensions, View } from "react-native";
|
|
3
|
+
import Animated, {
|
|
4
|
+
Easing,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
useSharedValue,
|
|
7
|
+
withRepeat,
|
|
8
|
+
withTiming,
|
|
9
|
+
} from "react-native-reanimated";
|
|
10
|
+
import { Button, Text, useTheme, zero } from "../../";
|
|
11
|
+
|
|
12
|
+
export function TeleportNotification({
|
|
13
|
+
targetHandle,
|
|
14
|
+
countdown,
|
|
15
|
+
canCancel,
|
|
16
|
+
startTime,
|
|
17
|
+
onDismiss,
|
|
18
|
+
}: {
|
|
19
|
+
targetHandle: string;
|
|
20
|
+
countdown: number;
|
|
21
|
+
canCancel: boolean;
|
|
22
|
+
startTime?: number;
|
|
23
|
+
onDismiss: (reason?: "user" | "auto") => void;
|
|
24
|
+
}) {
|
|
25
|
+
const { zero: z } = useTheme();
|
|
26
|
+
const w = useWindowDimensions().width;
|
|
27
|
+
|
|
28
|
+
const [start, setStart] = useState(Date.now());
|
|
29
|
+
const [now, setNow] = useState(Date.now());
|
|
30
|
+
const [dismissed, setDismissed] = useState(false);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const interval = setInterval(() => {
|
|
34
|
+
setNow(Date.now());
|
|
35
|
+
}, 100);
|
|
36
|
+
return () => clearInterval(interval);
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const timeLeft = Math.max(0, countdown - Math.floor((now - start) / 1000));
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (dismissed) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (timeLeft <= 0) {
|
|
46
|
+
setDismissed(true);
|
|
47
|
+
onDismiss("auto");
|
|
48
|
+
}
|
|
49
|
+
}, [dismissed, onDismiss, timeLeft]);
|
|
50
|
+
|
|
51
|
+
// if we're past 5 seconds from start, stripes should already be hidden
|
|
52
|
+
const elapsedTime = startTime ? (Date.now() - startTime) / 1000 : 0;
|
|
53
|
+
const [showStripes, setShowStripes] = useState(elapsedTime < 5);
|
|
54
|
+
|
|
55
|
+
const stripeX = useSharedValue(0);
|
|
56
|
+
const stripeOpacity = useSharedValue(1);
|
|
57
|
+
const progressWidth = useSharedValue(100);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
// if stripes are already hidden, fade out asap and return
|
|
61
|
+
if (!showStripes) {
|
|
62
|
+
stripeOpacity.value = withTiming(0, { duration: 0 });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// warning stripes animation
|
|
66
|
+
stripeX.value = withRepeat(
|
|
67
|
+
withTiming(30 * 2, {
|
|
68
|
+
duration: 1000,
|
|
69
|
+
easing: Easing.linear,
|
|
70
|
+
}),
|
|
71
|
+
3,
|
|
72
|
+
false,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// hide stripes after 500ms
|
|
76
|
+
const stripesTimer = setTimeout(() => {
|
|
77
|
+
// woosh the stripes off to the right before hiding
|
|
78
|
+
stripeX.value = withTiming(30 * 80, {
|
|
79
|
+
duration: 1500,
|
|
80
|
+
easing: Easing.cubic,
|
|
81
|
+
});
|
|
82
|
+
// after animation, set stripes as hidden
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
setShowStripes(false);
|
|
85
|
+
}, 350);
|
|
86
|
+
}, 1500);
|
|
87
|
+
|
|
88
|
+
return () => clearTimeout(stripesTimer);
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (showStripes) return;
|
|
93
|
+
|
|
94
|
+
// animate progress bar
|
|
95
|
+
const percentage = (timeLeft / countdown) * 100;
|
|
96
|
+
progressWidth.value = withTiming(percentage, {
|
|
97
|
+
duration: 1000,
|
|
98
|
+
easing: Easing.linear,
|
|
99
|
+
});
|
|
100
|
+
}, [timeLeft, countdown, showStripes]);
|
|
101
|
+
|
|
102
|
+
const stripesStyle = useAnimatedStyle(() => ({
|
|
103
|
+
opacity: stripeOpacity.value,
|
|
104
|
+
transform: [{ translateX: stripeX.value }],
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const progressStyle = useAnimatedStyle(() => ({
|
|
108
|
+
width: `${progressWidth.value}%`,
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<View style={[{ overflow: "hidden" }, zero.r.lg, zero.bg.neutral[900]]}>
|
|
113
|
+
<View
|
|
114
|
+
style={[
|
|
115
|
+
zero.layout.flex.row,
|
|
116
|
+
zero.layout.flex.alignCenter,
|
|
117
|
+
zero.layout.flex.spaceBetween,
|
|
118
|
+
zero.px[3],
|
|
119
|
+
w > 650 ? zero.py[4] : zero.py[2],
|
|
120
|
+
]}
|
|
121
|
+
>
|
|
122
|
+
<Text size={w > 650 ? "xl" : "base"}>
|
|
123
|
+
Teleporting to @{targetHandle}
|
|
124
|
+
</Text>
|
|
125
|
+
<View
|
|
126
|
+
style={[
|
|
127
|
+
zero.layout.flex.row,
|
|
128
|
+
zero.layout.flex.alignCenter,
|
|
129
|
+
zero.gap.all[3],
|
|
130
|
+
]}
|
|
131
|
+
>
|
|
132
|
+
<Text color="muted">{timeLeft}s</Text>
|
|
133
|
+
{canCancel && (
|
|
134
|
+
<Button
|
|
135
|
+
onPress={() => onDismiss("user")}
|
|
136
|
+
width="min"
|
|
137
|
+
variant="destructive"
|
|
138
|
+
>
|
|
139
|
+
Cancel
|
|
140
|
+
</Button>
|
|
141
|
+
)}
|
|
142
|
+
</View>
|
|
143
|
+
</View>
|
|
144
|
+
<View
|
|
145
|
+
style={{
|
|
146
|
+
height: 4,
|
|
147
|
+
width: "100%",
|
|
148
|
+
borderRadius: 2,
|
|
149
|
+
overflow: "hidden",
|
|
150
|
+
backgroundColor: "#0f0f1e",
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<Animated.View
|
|
154
|
+
style={[
|
|
155
|
+
{ height: "100%", borderRadius: 2, backgroundColor: "#16f4d0" },
|
|
156
|
+
progressStyle,
|
|
157
|
+
]}
|
|
158
|
+
/>
|
|
159
|
+
</View>
|
|
160
|
+
<Animated.View
|
|
161
|
+
style={[
|
|
162
|
+
{
|
|
163
|
+
position: "absolute",
|
|
164
|
+
flexDirection: "row",
|
|
165
|
+
height: 180,
|
|
166
|
+
width: "200%",
|
|
167
|
+
//clickthrough
|
|
168
|
+
pointerEvents: "none",
|
|
169
|
+
},
|
|
170
|
+
stripesStyle,
|
|
171
|
+
]}
|
|
172
|
+
>
|
|
173
|
+
{[...Array(80)].map((_, i) => (
|
|
174
|
+
<View
|
|
175
|
+
key={i}
|
|
176
|
+
style={{
|
|
177
|
+
width: 30,
|
|
178
|
+
height: "100%",
|
|
179
|
+
backgroundColor: i % 2 === 0 ? "#FFA500" : "#000000",
|
|
180
|
+
transform: [{ skewX: "-45deg" }, { translateX: -30 * 8 }],
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
))}
|
|
184
|
+
</Animated.View>
|
|
185
|
+
</View>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
@@ -23,6 +23,7 @@ const buttonVariants = cva("", {
|
|
|
23
23
|
lg: "lg",
|
|
24
24
|
xl: "xl",
|
|
25
25
|
pill: "pill",
|
|
26
|
+
icon: "icon",
|
|
26
27
|
},
|
|
27
28
|
},
|
|
28
29
|
defaultVariants: {
|
|
@@ -143,6 +144,12 @@ export const Button = forwardRef<any, ButtonProps>(
|
|
|
143
144
|
inner: { gap: 4 },
|
|
144
145
|
text: zero.typography.universal.xs,
|
|
145
146
|
};
|
|
147
|
+
case "icon":
|
|
148
|
+
return {
|
|
149
|
+
button: [zero.p[2], { borderRadius: zero.borderRadius.md }],
|
|
150
|
+
inner: { gap: 0 },
|
|
151
|
+
text: zero.typography.universal.sm,
|
|
152
|
+
};
|
|
146
153
|
case "md":
|
|
147
154
|
default:
|
|
148
155
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as RadixDropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
1
2
|
import * as DropdownMenuPrimitive from "@rn-primitives/dropdown-menu";
|
|
2
3
|
import {
|
|
3
4
|
Check,
|
|
@@ -109,32 +110,83 @@ export const DropdownMenuSubTrigger = forwardRef<
|
|
|
109
110
|
|
|
110
111
|
export const DropdownMenuSubContent = forwardRef<
|
|
111
112
|
any,
|
|
112
|
-
DropdownMenuPrimitive.SubContentProps & {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
DropdownMenuPrimitive.SubContentProps & {
|
|
114
|
+
children?: ReactNode;
|
|
115
|
+
portalHost?: string;
|
|
116
|
+
sideOffset?: number;
|
|
117
|
+
alignOffset?: number;
|
|
118
|
+
avoidCollisions?: boolean;
|
|
119
|
+
}
|
|
120
|
+
>(
|
|
121
|
+
(
|
|
122
|
+
{
|
|
123
|
+
children,
|
|
124
|
+
portalHost,
|
|
125
|
+
sideOffset,
|
|
126
|
+
alignOffset,
|
|
127
|
+
avoidCollisions = true,
|
|
128
|
+
...props
|
|
129
|
+
},
|
|
130
|
+
ref,
|
|
131
|
+
) => {
|
|
132
|
+
const { zero: zt } = useTheme();
|
|
115
133
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
const [portalContainer, setPortalContainer] =
|
|
135
|
+
React.useState<HTMLElement | null>(null);
|
|
136
|
+
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
if (Platform.OS === "web" && portalHost) {
|
|
139
|
+
const element = document.querySelector<HTMLElement>(
|
|
140
|
+
`[data-portal-host="${portalHost}"]`,
|
|
141
|
+
);
|
|
142
|
+
setPortalContainer(element);
|
|
143
|
+
}
|
|
144
|
+
}, [portalHost]);
|
|
145
|
+
|
|
146
|
+
const styles = [
|
|
147
|
+
a.sizes.minWidth[64],
|
|
148
|
+
a.sizes.maxWidth[64],
|
|
149
|
+
a.overflow.hidden,
|
|
150
|
+
a.radius.all.md,
|
|
151
|
+
a.borders.width.thin,
|
|
152
|
+
zt.border.default,
|
|
153
|
+
mt[1],
|
|
154
|
+
zt.bg.popover,
|
|
155
|
+
p[1],
|
|
156
|
+
a.shadows.md,
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
// On web, use Radix directly to support custom portal container
|
|
160
|
+
if (Platform.OS === "web") {
|
|
161
|
+
const { forceMount } = props;
|
|
162
|
+
// Flatten RN style array into a plain CSS object for DOM
|
|
163
|
+
const flattenedStyles = StyleSheet.flatten(styles);
|
|
164
|
+
return (
|
|
165
|
+
<RadixDropdownMenu.Portal
|
|
166
|
+
{...(portalContainer ? { container: portalContainer } : {})}
|
|
167
|
+
>
|
|
168
|
+
<RadixDropdownMenu.SubContent
|
|
169
|
+
ref={ref}
|
|
170
|
+
style={flattenedStyles as React.CSSProperties}
|
|
171
|
+
forceMount={forceMount}
|
|
172
|
+
sideOffset={sideOffset}
|
|
173
|
+
alignOffset={alignOffset}
|
|
174
|
+
avoidCollisions={avoidCollisions}
|
|
175
|
+
>
|
|
176
|
+
{children}
|
|
177
|
+
</RadixDropdownMenu.SubContent>
|
|
178
|
+
</RadixDropdownMenu.Portal>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// On native, use rn-primitives
|
|
183
|
+
return (
|
|
184
|
+
<DropdownMenuPrimitive.SubContent ref={ref} style={styles} {...props}>
|
|
185
|
+
{children}
|
|
186
|
+
</DropdownMenuPrimitive.SubContent>
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
);
|
|
138
190
|
|
|
139
191
|
export const DropdownMenuContent = forwardRef<
|
|
140
192
|
any,
|
|
@@ -147,8 +199,26 @@ export const DropdownMenuContent = forwardRef<
|
|
|
147
199
|
const { height } = useWindowDimensions();
|
|
148
200
|
const maxHeight = height * 0.9;
|
|
149
201
|
|
|
202
|
+
const [portalContainer, setPortalContainer] =
|
|
203
|
+
React.useState<HTMLElement | null>(null);
|
|
204
|
+
|
|
205
|
+
React.useEffect(() => {
|
|
206
|
+
if (Platform.OS === "web" && portalHost) {
|
|
207
|
+
const element = document.querySelector<HTMLElement>(
|
|
208
|
+
`[data-portal-host="${portalHost}"]`,
|
|
209
|
+
);
|
|
210
|
+
setPortalContainer(element);
|
|
211
|
+
console.log("set portal container to", element);
|
|
212
|
+
}
|
|
213
|
+
}, [portalHost]);
|
|
214
|
+
|
|
150
215
|
return (
|
|
151
|
-
<DropdownMenuPrimitive.Portal
|
|
216
|
+
<DropdownMenuPrimitive.Portal
|
|
217
|
+
hostName={portalHost}
|
|
218
|
+
{...(Platform.OS === "web" && portalContainer
|
|
219
|
+
? { container: portalContainer }
|
|
220
|
+
: {})}
|
|
221
|
+
>
|
|
152
222
|
<DropdownMenuPrimitive.Overlay
|
|
153
223
|
style={[
|
|
154
224
|
Platform.OS !== "web" ? StyleSheet.absoluteFill : undefined,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@rn-primitives/portal";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
|
|
4
|
+
function Portal({
|
|
5
|
+
children,
|
|
6
|
+
hostName = "INTERNAL_PRIMITIVE_DEFAULT_HOST_NAME",
|
|
7
|
+
}: {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
hostName?: string;
|
|
10
|
+
}) {
|
|
11
|
+
const [hostElement, setHostElement] = useState<HTMLElement | null>(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const element = document.querySelector<HTMLElement>(
|
|
15
|
+
`[data-portal-host="${hostName}"]`,
|
|
16
|
+
);
|
|
17
|
+
setHostElement(element);
|
|
18
|
+
}, [hostName]);
|
|
19
|
+
|
|
20
|
+
if (!hostElement) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return createPortal(children, hostElement);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PortalHostProps {
|
|
28
|
+
name?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function PortalHost({
|
|
32
|
+
name = "INTERNAL_PRIMITIVE_DEFAULT_HOST_NAME",
|
|
33
|
+
}: PortalHostProps) {
|
|
34
|
+
return <div data-portal-host={name} />;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { Portal, PortalHost };
|