@streamplace/components 0.7.11 → 0.7.13

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 (48) hide show
  1. package/dist/assets/emoji-data.json +19371 -0
  2. package/dist/components/chat/chat-box.js +19 -2
  3. package/dist/components/chat/chat-message.js +12 -4
  4. package/dist/components/chat/chat.js +15 -4
  5. package/dist/components/chat/mod-view.js +10 -3
  6. package/dist/components/dashboard/chat-panel.js +38 -0
  7. package/dist/components/dashboard/header.js +80 -0
  8. package/dist/components/dashboard/index.js +14 -0
  9. package/dist/components/dashboard/information-widget.js +234 -0
  10. package/dist/components/dashboard/mod-actions.js +71 -0
  11. package/dist/components/dashboard/problems.js +74 -0
  12. package/dist/components/mobile-player/ui/viewer-context-menu.js +11 -2
  13. package/dist/components/ui/button.js +2 -2
  14. package/dist/components/ui/dropdown.js +18 -1
  15. package/dist/components/ui/index.js +2 -0
  16. package/dist/components/ui/info-box.js +31 -0
  17. package/dist/components/ui/info-row.js +23 -0
  18. package/dist/components/ui/toast.js +43 -0
  19. package/dist/index.js +3 -1
  20. package/dist/lib/theme/atoms.js +66 -45
  21. package/dist/lib/theme/tokens.js +285 -12
  22. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  23. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
  24. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
  25. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
  26. package/package.json +2 -2
  27. package/src/assets/emoji-data.json +19371 -0
  28. package/src/components/chat/chat-box.tsx +19 -1
  29. package/src/components/chat/chat-message.tsx +22 -14
  30. package/src/components/chat/chat.tsx +21 -6
  31. package/src/components/chat/mod-view.tsx +11 -2
  32. package/src/components/dashboard/chat-panel.tsx +80 -0
  33. package/src/components/dashboard/header.tsx +170 -0
  34. package/src/components/dashboard/index.tsx +5 -0
  35. package/src/components/dashboard/information-widget.tsx +526 -0
  36. package/src/components/dashboard/mod-actions.tsx +133 -0
  37. package/src/components/dashboard/problems.tsx +151 -0
  38. package/src/components/mobile-player/ui/viewer-context-menu.tsx +58 -38
  39. package/src/components/ui/button.tsx +2 -2
  40. package/src/components/ui/dropdown.tsx +36 -3
  41. package/src/components/ui/index.ts +2 -0
  42. package/src/components/ui/info-box.tsx +60 -0
  43. package/src/components/ui/info-row.tsx +48 -0
  44. package/src/components/ui/toast.tsx +110 -0
  45. package/src/index.tsx +3 -0
  46. package/src/lib/theme/atoms.ts +97 -43
  47. package/src/lib/theme/tokens.ts +285 -12
  48. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,151 @@
1
+ import { ExternalLink } from "lucide-react-native";
2
+ import { useState } from "react";
3
+ import { Linking, Pressable, Text, View } from "react-native";
4
+ import { useLivestreamStore } from "../../livestream-store";
5
+ import { LivestreamProblem } from "../../livestream-store/livestream-state";
6
+ import * as zero from "../../ui";
7
+
8
+ const { bg, r, borders, p, text, layout, gap } = zero;
9
+
10
+ const Problems = ({
11
+ probs,
12
+ onIgnore,
13
+ }: {
14
+ probs: LivestreamProblem[];
15
+ onIgnore: () => void;
16
+ }) => {
17
+ return (
18
+ <View style={[gap.all[3]]}>
19
+ <View>
20
+ <Text style={[text.white, { fontSize: 24, fontWeight: "bold" }]}>
21
+ Optimize Your Stream
22
+ </Text>
23
+ <Text style={[text.gray[300]]}>
24
+ We've found a few things that could improve your stream's reliability.
25
+ </Text>
26
+ </View>
27
+ {probs.map((p) => (
28
+ <View key={p.message}>
29
+ <View
30
+ style={[
31
+ gap.all[2],
32
+ layout.flex.row,
33
+ layout.flex.alignCenter,
34
+ { gap: 8, alignItems: "flex-start" },
35
+ ]}
36
+ >
37
+ <Text
38
+ style={[
39
+ r.sm,
40
+ p[2],
41
+ {
42
+ width: 82,
43
+ textAlign: "center",
44
+ backgroundColor:
45
+ p.severity === "error"
46
+ ? "#7f1d1d"
47
+ : p.severity === "warning"
48
+ ? "#7c2d12"
49
+ : "#1e3a8a",
50
+ color: "white",
51
+ fontSize: 12,
52
+ },
53
+ ]}
54
+ >
55
+ {p.severity}
56
+ </Text>
57
+ <View style={[{ flex: 1 }, gap.all[1]]}>
58
+ <Text style={[text.white, { fontWeight: "600" }]}>{p.code}</Text>
59
+ <Text style={[text.gray[400], { fontSize: 14 }]}>
60
+ {p.message}
61
+ </Text>
62
+ {p.link && (
63
+ <Pressable onPress={() => p.link && Linking.openURL(p.link)}>
64
+ <View
65
+ style={[
66
+ layout.flex.row,
67
+ layout.flex.alignCenter,
68
+ gap.all[2],
69
+ ]}
70
+ >
71
+ <Text style={[{ color: "#3b82f6", fontSize: 14 }]}>
72
+ Learn More
73
+ </Text>
74
+ <ExternalLink size={12} color="#3b82f6" />
75
+ </View>
76
+ </Pressable>
77
+ )}
78
+ </View>
79
+ </View>
80
+ </View>
81
+ ))}
82
+
83
+ <Pressable
84
+ onPress={onIgnore}
85
+ style={[
86
+ bg.blue[600],
87
+ r.md,
88
+ p[3],
89
+ layout.flex.center,
90
+ { marginTop: 16 },
91
+ ]}
92
+ >
93
+ <Text style={[text.white, { fontWeight: "600" }]}>Ignore</Text>
94
+ </Pressable>
95
+ </View>
96
+ );
97
+ };
98
+
99
+ export const ProblemsWrapper = ({
100
+ children,
101
+ }: {
102
+ children: React.ReactElement;
103
+ }) => {
104
+ const problems = useLivestreamStore((x) => x.problems);
105
+ const [dismiss, setDismiss] = useState(false);
106
+
107
+ return (
108
+ <View
109
+ style={[
110
+ { position: "relative", flex: 1 },
111
+ layout.flex.center,
112
+ { flexBasis: 0 },
113
+ ]}
114
+ >
115
+ {children}
116
+ {problems.length > 0 && !dismiss && (
117
+ <View
118
+ style={[
119
+ {
120
+ position: "absolute",
121
+ top: 0,
122
+ left: 0,
123
+ right: 0,
124
+ bottom: 0,
125
+ backgroundColor: "rgba(0, 0, 0, 0.8)",
126
+ zIndex: 100,
127
+ },
128
+ layout.flex.center,
129
+ { justifyContent: "flex-start" },
130
+ p[8],
131
+ ]}
132
+ >
133
+ <View
134
+ style={[
135
+ bg.gray[900],
136
+ borders.color.gray[700],
137
+ borders.width.thin,
138
+ r.lg,
139
+ p[4],
140
+ { maxWidth: 700, width: "100%" },
141
+ ]}
142
+ >
143
+ <Problems probs={problems} onIgnore={() => setDismiss(true)} />
144
+ </View>
145
+ </View>
146
+ )}
147
+ </View>
148
+ );
149
+ };
150
+
151
+ export default Problems;
@@ -1,14 +1,17 @@
1
1
  import { useRootContext } from "@rn-primitives/dropdown-menu";
2
2
  import { Settings } from "lucide-react-native";
3
+ import { Platform, View } from "react-native";
3
4
  import { colors } from "../../../lib/theme";
4
5
  import { useLivestreamStore } from "../../../livestream-store";
5
6
  import { PlayerProtocol, usePlayerStore } from "../../../player-store/";
6
7
  import {
7
8
  DropdownMenu,
8
9
  DropdownMenuCheckboxItem,
10
+ DropdownMenuContentWithoutPortal,
9
11
  DropdownMenuGroup,
10
12
  DropdownMenuInfo,
11
13
  DropdownMenuItem,
14
+ DropdownMenuPortal,
12
15
  DropdownMenuRadioGroup,
13
16
  DropdownMenuRadioItem,
14
17
  DropdownMenuTrigger,
@@ -16,7 +19,11 @@ import {
16
19
  Text,
17
20
  } from "../../ui";
18
21
 
19
- export function ContextMenu() {
22
+ export function ContextMenu({
23
+ dropdownPortalContainer,
24
+ }: {
25
+ dropdownPortalContainer?: any;
26
+ }) {
20
27
  const quality = usePlayerStore((x) => x.selectedRendition);
21
28
  const setQuality = usePlayerStore((x) => x.setSelectedRendition);
22
29
  const qualities = useLivestreamStore((x) => x.renditions);
@@ -36,49 +43,62 @@ export function ContextMenu() {
36
43
  setProtocol(value ? PlayerProtocol.WEBRTC : PlayerProtocol.HLS);
37
44
  };
38
45
 
46
+ // are we on mobile? then do dropdowns
47
+ const isMobile = Platform.OS === "ios" || Platform.OS === "android";
48
+
49
+ // dummy portal for mobile
50
+ const Portal = isMobile ? View : DropdownMenuPortal;
51
+
52
+ // render the responsive version on mobile as we can't fullscreen there
53
+ const DropdownMenuContent = isMobile
54
+ ? ResponsiveDropdownMenuContent
55
+ : DropdownMenuContentWithoutPortal;
56
+
39
57
  return (
40
58
  <DropdownMenu>
41
59
  <DropdownMenuTrigger>
42
60
  <Settings color={colors.gray[200]} />
43
61
  </DropdownMenuTrigger>
44
- <ResponsiveDropdownMenuContent side="top" align="end">
45
- <DropdownMenuGroup title="Resolution">
46
- <DropdownMenuRadioGroup value={quality} onValueChange={setQuality}>
47
- <DropdownMenuRadioItem value="source">
48
- <Text>Source (Original Quality)</Text>
49
- </DropdownMenuRadioItem>
50
- {qualities.map((r) => (
51
- <DropdownMenuRadioItem value={r.name}>
52
- <Text>{r.name}</Text>
62
+ <Portal container={dropdownPortalContainer}>
63
+ <DropdownMenuContent side="top" align="end">
64
+ <DropdownMenuGroup title="Resolution">
65
+ <DropdownMenuRadioGroup value={quality} onValueChange={setQuality}>
66
+ <DropdownMenuRadioItem value="source">
67
+ <Text>Source (Original Quality)</Text>
53
68
  </DropdownMenuRadioItem>
54
- ))}
55
- </DropdownMenuRadioGroup>
56
- </DropdownMenuGroup>
57
- <DropdownMenuGroup title="Advanced">
58
- <DropdownMenuCheckboxItem
59
- checked={lowLatency}
60
- onCheckedChange={() => setLowLatency(!lowLatency)}
61
- >
62
- <Text>Low Latency</Text>
63
- </DropdownMenuCheckboxItem>
64
- </DropdownMenuGroup>
65
- <DropdownMenuInfo description="Reduces the delay between video and chat for a more real-time experience." />
66
- <DropdownMenuGroup>
67
- <DropdownMenuCheckboxItem
68
- checked={debugInfo}
69
- onCheckedChange={() => setShowDebugInfo(!debugInfo)}
70
- >
71
- <Text>Show Debug Info</Text>
72
- </DropdownMenuCheckboxItem>
73
- </DropdownMenuGroup>
74
- <DropdownMenuGroup title="Report">
75
- <ReportButton
76
- livestream={livestream}
77
- setReportModalOpen={setReportModalOpen}
78
- setReportSubject={setReportSubject}
79
- />
80
- </DropdownMenuGroup>
81
- </ResponsiveDropdownMenuContent>
69
+ {qualities.map((r) => (
70
+ <DropdownMenuRadioItem value={r.name}>
71
+ <Text>{r.name}</Text>
72
+ </DropdownMenuRadioItem>
73
+ ))}
74
+ </DropdownMenuRadioGroup>
75
+ </DropdownMenuGroup>
76
+ <DropdownMenuGroup title="Advanced">
77
+ <DropdownMenuCheckboxItem
78
+ checked={lowLatency}
79
+ onCheckedChange={() => setLowLatency(!lowLatency)}
80
+ >
81
+ <Text>Low Latency</Text>
82
+ </DropdownMenuCheckboxItem>
83
+ </DropdownMenuGroup>
84
+ <DropdownMenuInfo description="Reduces the delay between video and chat for a more real-time experience." />
85
+ <DropdownMenuGroup>
86
+ <DropdownMenuCheckboxItem
87
+ checked={debugInfo}
88
+ onCheckedChange={() => setShowDebugInfo(!debugInfo)}
89
+ >
90
+ <Text>Show Debug Info</Text>
91
+ </DropdownMenuCheckboxItem>
92
+ </DropdownMenuGroup>
93
+ <DropdownMenuGroup title="Report">
94
+ <ReportButton
95
+ livestream={livestream}
96
+ setReportModalOpen={setReportModalOpen}
97
+ setReportSubject={setReportSubject}
98
+ />
99
+ </DropdownMenuGroup>
100
+ </DropdownMenuContent>
101
+ </Portal>
82
102
  </DropdownMenu>
83
103
  );
84
104
  }
@@ -231,8 +231,8 @@ function createStyles(theme: any) {
231
231
  },
232
232
 
233
233
  pillButton: {
234
- paddingHorizontal: theme.spacing[3],
235
- paddingVertical: theme.spacing[2],
234
+ paddingHorizontal: theme.spacing[2],
235
+ paddingVertical: theme.spacing[1],
236
236
  borderRadius: tokens.borderRadius.full,
237
237
  minHeight: tokens.touchTargets.minimum / 2,
238
238
  },
@@ -8,7 +8,7 @@ import {
8
8
  ChevronUp,
9
9
  Circle,
10
10
  } from "lucide-react-native";
11
- import { forwardRef, ReactNode, useMemo, useRef } from "react";
11
+ import React, { forwardRef, ReactNode, useMemo, useRef } from "react";
12
12
  import {
13
13
  Platform,
14
14
  Pressable,
@@ -200,6 +200,41 @@ export const DropdownMenuContent = forwardRef<
200
200
  );
201
201
  });
202
202
 
203
+ export const DropdownMenuContentWithoutPortal = forwardRef<
204
+ any,
205
+ DropdownMenuPrimitive.ContentProps & {
206
+ overlayStyle?: any;
207
+ }
208
+ >(({ overlayStyle, ...props }, ref) => {
209
+ return (
210
+ <DropdownMenuPrimitive.Overlay
211
+ style={[
212
+ Platform.OS !== "web" ? StyleSheet.absoluteFill : undefined,
213
+ overlayStyle,
214
+ ]}
215
+ >
216
+ <DropdownMenuPrimitive.Content
217
+ ref={ref}
218
+ style={
219
+ [
220
+ { zIndex: 999999 },
221
+ a.sizes.minWidth[32],
222
+ a.sizes.maxWidth[64],
223
+ a.overflow.hidden,
224
+ a.radius.all.md,
225
+ a.borders.width.thin,
226
+ a.borders.color.gray[800],
227
+ bg.gray[950],
228
+ p[2],
229
+ a.shadows.md,
230
+ ] as any
231
+ }
232
+ {...props}
233
+ />
234
+ </DropdownMenuPrimitive.Overlay>
235
+ );
236
+ });
237
+
203
238
  /// Responsive Dropdown Menu Content. On mobile this will render a *bottom sheet* that is **portaled to the root of the app**.
204
239
  /// Prefer passing scoped content in as **otherwise it may crash the app**.
205
240
  export const ResponsiveDropdownMenuContent = forwardRef<any, any>(
@@ -224,8 +259,6 @@ export const ResponsiveDropdownMenuContent = forwardRef<any, any>(
224
259
  },
225
260
  );
226
261
 
227
- import React from "react";
228
-
229
262
  export const DropdownMenuItem = forwardRef<
230
263
  any,
231
264
  DropdownMenuPrimitive.ItemProps & { inset?: boolean; disabled?: boolean }
@@ -9,6 +9,8 @@ export * from "./button";
9
9
  export * from "./dialog";
10
10
  export * from "./dropdown";
11
11
  export * from "./icons";
12
+ export * from "./info-box";
13
+ export * from "./info-row";
12
14
  export * from "./input";
13
15
  export * from "./loader";
14
16
  export * from "./resizeable";
@@ -0,0 +1,60 @@
1
+ import { Text, View } from "react-native";
2
+ import * as zero from "../../ui";
3
+
4
+ const { bg, r, p, text, layout, gap, flex } = zero;
5
+
6
+ interface InfoBoxProps {
7
+ icon: any;
8
+ label: string;
9
+ value: string;
10
+ status?: "good" | "warning" | "error" | "neutral";
11
+ }
12
+
13
+ export function InfoBox({
14
+ icon: Icon,
15
+ label,
16
+ value,
17
+ status = "neutral",
18
+ }: InfoBoxProps) {
19
+ const statusColors = {
20
+ good: text.green[400],
21
+ warning: text.yellow[400],
22
+ error: text.red[400],
23
+ neutral: text.white,
24
+ };
25
+
26
+ const statusColor = statusColors[status];
27
+
28
+ return (
29
+ <View
30
+ style={[
31
+ flex.values[1],
32
+ layout.flex.column,
33
+ layout.flex.spaceBetween,
34
+ layout.flex.alignCenter,
35
+ bg.neutral[700],
36
+ r.sm,
37
+ p[2],
38
+ ]}
39
+ >
40
+ <View
41
+ style={[
42
+ layout.flex.row,
43
+ layout.flex.spaceBetween,
44
+ gap.all[3],
45
+ zero.w.percent[100],
46
+ ]}
47
+ >
48
+ <Text style={[text.gray[100], { fontSize: 13, fontWeight: "500" }]}>
49
+ {label}
50
+ </Text>
51
+ <Icon size={16} color="#9ca3af" />
52
+ </View>
53
+ <View style={[layout.flex.align.end, zero.w.percent[100]]}>
54
+ <Text style={[statusColor, { fontSize: 26, fontWeight: "600" }]}>
55
+ {value}
56
+ </Text>
57
+ </View>
58
+ </View>
59
+ );
60
+ }
@@ -0,0 +1,48 @@
1
+ import { Text, View } from "react-native";
2
+ import * as zero from "../../ui";
3
+
4
+ const { text, layout, py, gap } = zero;
5
+
6
+ interface InfoRowProps {
7
+ icon: any;
8
+ label: string;
9
+ value: string;
10
+ status?: "good" | "warning" | "error" | "neutral";
11
+ }
12
+
13
+ export function InfoRow({
14
+ icon: Icon,
15
+ label,
16
+ value,
17
+ status = "neutral",
18
+ }: InfoRowProps) {
19
+ const statusColors = {
20
+ good: text.green[400],
21
+ warning: text.yellow[400],
22
+ error: text.red[400],
23
+ neutral: text.white,
24
+ };
25
+
26
+ const statusColor = statusColors[status];
27
+
28
+ return (
29
+ <View
30
+ style={[
31
+ layout.flex.row,
32
+ layout.flex.spaceBetween,
33
+ layout.flex.alignCenter,
34
+ py[2],
35
+ ]}
36
+ >
37
+ <View style={[layout.flex.row, layout.flex.alignCenter, gap.all[3]]}>
38
+ <Icon size={16} color="#9ca3af" />
39
+ <Text style={[text.gray[300], { fontSize: 13, fontWeight: "500" }]}>
40
+ {label}
41
+ </Text>
42
+ </View>
43
+ <Text style={[statusColor, { fontSize: 13, fontWeight: "600" }]}>
44
+ {value}
45
+ </Text>
46
+ </View>
47
+ );
48
+ }
@@ -13,6 +13,116 @@ import {
13
13
  import { useSafeAreaInsets } from "react-native-safe-area-context";
14
14
  import { useTheme } from "../../lib/theme/theme";
15
15
 
16
+ import { useCallback } from "react";
17
+
18
+ type ToastController = {
19
+ show: (
20
+ title: string,
21
+ description?: string,
22
+ options?: {
23
+ duration?: number;
24
+ actionLabel?: string;
25
+ onAction?: () => void;
26
+ },
27
+ ) => void;
28
+ hide: () => void;
29
+ };
30
+
31
+ type UseToastReturn = {
32
+ open: boolean;
33
+ title: string;
34
+ description?: string;
35
+ actionLabel?: string;
36
+ onAction?: () => void;
37
+ duration?: number;
38
+ setOpen: (open: boolean) => void;
39
+ setTitle: (title: string) => void;
40
+ setDescription: (description: string) => void;
41
+ setActionLabel: (label: string) => void;
42
+ setOnAction: (cb?: () => void) => void;
43
+ setDuration: (duration: number) => void;
44
+ toastController: ToastController;
45
+ };
46
+
47
+ /**
48
+ * useToast - a hook to manage Toast state and provide a toastController.
49
+ * Returns a ready-to-render ToastComponent.
50
+ */
51
+ export function useToast(
52
+ initial: {
53
+ title?: string;
54
+ description?: string;
55
+ duration?: number;
56
+ actionLabel?: string;
57
+ onAction?: () => void;
58
+ } = {},
59
+ ) {
60
+ const [open, setOpen] = useState(false);
61
+ const [title, setTitle] = useState(initial.title ?? "");
62
+ const [description, setDescription] = useState(initial.description ?? "");
63
+ const [duration, setDuration] = useState(initial.duration ?? 3);
64
+ const [actionLabel, setActionLabel] = useState(
65
+ initial.actionLabel ?? "Action",
66
+ );
67
+ const [onAction, setOnAction] = useState<(() => void) | undefined>(
68
+ initial.onAction,
69
+ );
70
+
71
+ const show = useCallback(
72
+ (
73
+ toastTitle: string,
74
+ toastDescription?: string,
75
+ options?: {
76
+ duration?: number;
77
+ actionLabel?: string;
78
+ onAction?: () => void;
79
+ },
80
+ ) => {
81
+ setTitle(toastTitle);
82
+ setDescription(toastDescription ?? "");
83
+ setDuration(options?.duration ?? 3);
84
+ setActionLabel(options?.actionLabel ?? "Action");
85
+ setOnAction(options?.onAction);
86
+ setOpen(true);
87
+ },
88
+ [],
89
+ );
90
+
91
+ const hide = useCallback(() => {
92
+ setOpen(false);
93
+ }, []);
94
+
95
+ // Ready-to-render Toast component
96
+ const ToastComponent = (
97
+ <Toast
98
+ open={open}
99
+ onOpenChange={setOpen}
100
+ title={title}
101
+ description={description}
102
+ actionLabel={actionLabel}
103
+ onAction={onAction}
104
+ duration={duration}
105
+ />
106
+ );
107
+
108
+ return {
109
+ open,
110
+ title,
111
+ description,
112
+ actionLabel,
113
+ onAction,
114
+ duration,
115
+ setOpen,
116
+ setTitle,
117
+ setDescription,
118
+ setActionLabel,
119
+ setOnAction,
120
+ setDuration,
121
+ toastController: { show, hide },
122
+ ToastComponent,
123
+ };
124
+ }
125
+
16
126
  type ToastProps = {
17
127
  open: boolean;
18
128
  onOpenChange: (open: boolean) => void;
package/src/index.tsx CHANGED
@@ -34,3 +34,6 @@ export * from "./lib/system-messages";
34
34
  export * from "./components/share/sharesheet";
35
35
 
36
36
  export * from "./components/keep-awake";
37
+
38
+ // Dashboard components
39
+ export * as Dashboard from "./components/dashboard";