@streamplace/components 0.7.34 → 0.7.35

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.
@@ -59,7 +59,7 @@ function StatusIndicator({ status, isLive }) {
59
59
  !isLive && text.gray[400],
60
60
  ], children: getStatusText() })] }));
61
61
  }
62
- function Header({ isLive, streamTitle = "Live Stream", viewers = 0, uptime = "00:00:00", bitrate = "0 mbps", timeBetweenSegments = 0, connectionStatus = "offline", }) {
62
+ function Header({ isLive, streamTitle = "Live Stream", viewers = 0, uptime = "00:00:00", bitrate = "0 mbps", timeBetweenSegments = 0, connectionStatus = "offline", problemsCount = 0, onProblemsPress, }) {
63
63
  const getConnectionQuality = () => {
64
64
  if (timeBetweenSegments <= 1500)
65
65
  return "good";
@@ -76,5 +76,19 @@ function Header({ isLive, streamTitle = "Live Stream", viewers = 0, uptime = "00
76
76
  bg.neutral[900],
77
77
  borders.width.thin,
78
78
  borders.color.neutral[700],
79
- ], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[4]], children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontSize: 18, fontWeight: "600" }], children: streamTitle }), (0, jsx_runtime_1.jsx)(StatusIndicator, { status: connectionStatus, isLive: isLive })] }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[6]], children: [isLive && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(MetricItem, { icon: lucide_react_native_1.Users, label: "Viewers", value: viewers.toLocaleString() }), (0, jsx_runtime_1.jsx)(MetricItem, { icon: lucide_react_native_1.Car, label: "Bitrate", value: bitrate })] })), !isLive && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[2]], children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Radio, { size: 16, color: "#6b7280" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.gray[400], { fontSize: 13 }], children: "Stream offline" })] }))] })] }));
79
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[4]], children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontSize: 18, fontWeight: "600" }], children: streamTitle }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[3]], children: [(0, jsx_runtime_1.jsx)(StatusIndicator, { status: connectionStatus, isLive: isLive }), problemsCount > 0 && ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: onProblemsPress, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
80
+ layout.flex.row,
81
+ layout.flex.alignCenter,
82
+ gap.all[1],
83
+ px[2],
84
+ py[1],
85
+ r.md,
86
+ bg.orange[900],
87
+ borders.width.thin,
88
+ borders.color.orange[700],
89
+ { marginVertical: -8 },
90
+ ], children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.AlertCircle, { size: 14, color: "#fb923c" }), (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [
91
+ text.orange[400],
92
+ { fontSize: 11, fontWeight: "600" },
93
+ ], children: [problemsCount, " ", problemsCount === 1 ? "Issue" : "Issues"] })] }) }))] })] }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[6]], children: [isLive && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(MetricItem, { icon: lucide_react_native_1.Users, label: "Viewers", value: viewers.toLocaleString() }), (0, jsx_runtime_1.jsx)(MetricItem, { icon: lucide_react_native_1.Car, label: "Bitrate", value: bitrate })] })), !isLive && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[2]], children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Radio, { size: 16, color: "#6b7280" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.gray[400], { fontSize: 13 }], children: "Stream offline" })] }))] })] }));
80
94
  }
@@ -8,47 +8,49 @@ const react_1 = require("react");
8
8
  const react_native_1 = require("react-native");
9
9
  const livestream_store_1 = require("../../livestream-store");
10
10
  const zero = tslib_1.__importStar(require("../../ui"));
11
+ const ui_1 = require("../ui");
11
12
  const { bg, r, borders, p, text, layout, gap } = zero;
13
+ const getIcon = (severity) => {
14
+ switch (severity) {
15
+ case "error":
16
+ return (0, jsx_runtime_1.jsx)(lucide_react_native_1.CircleX, { size: 24, color: "white" });
17
+ case "warning":
18
+ return (0, jsx_runtime_1.jsx)(lucide_react_native_1.CircleAlert, { size: 24, color: "white" });
19
+ case "info":
20
+ return (0, jsx_runtime_1.jsx)(lucide_react_native_1.Info, { size: 24, color: "white" });
21
+ default:
22
+ return (0, jsx_runtime_1.jsx)(lucide_react_native_1.Sparkle, { size: 24, color: "white" });
23
+ }
24
+ };
12
25
  const Problems = ({ probs, onIgnore, }) => {
13
- return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[3]], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontSize: 24, fontWeight: "bold" }], children: "Optimize Your Stream" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.gray[300]], children: "We've found a few things that could improve your stream's reliability." })] }), probs.map((p) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
26
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[4]], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[2]], children: [(0, jsx_runtime_1.jsx)(ui_1.Text, { size: "2xl", style: [text.white, { fontWeight: "600" }], children: "Optimize Your Stream" }), (0, jsx_runtime_1.jsx)(ui_1.Text, { style: [text.gray[300]], children: "We've found a few things that could improve your stream's reliability." })] }), probs.map((p) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
14
27
  gap.all[2],
15
28
  layout.flex.row,
16
29
  layout.flex.alignCenter,
17
30
  { gap: 8, alignItems: "flex-start" },
18
- ], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
19
- r.sm,
20
- p[2],
31
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
32
+ zero.r.full,
33
+ zero.p[1],
21
34
  {
22
- width: 82,
23
- textAlign: "center",
24
35
  backgroundColor: p.severity === "error"
25
36
  ? "#7f1d1d"
26
37
  : p.severity === "warning"
27
38
  ? "#7c2d12"
28
39
  : "#1e3a8a",
29
- color: "white",
30
- fontSize: 12,
31
40
  },
32
- ], children: p.severity }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [{ flex: 1 }, gap.all[1]], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontWeight: "600" }], children: p.code }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.gray[400], { fontSize: 14 }], children: p.message }), p.link && ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => p.link && react_native_1.Linking.openURL(p.link), children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
41
+ ], children: getIcon(p.severity) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [{ flex: 1 }, gap.all[1]], children: [(0, jsx_runtime_1.jsx)(ui_1.Text, { style: [text.white, { fontWeight: "600" }], children: p.code }), (0, jsx_runtime_1.jsx)(ui_1.Text, { style: [text.gray[400], { fontSize: 14 }], children: p.message }), p.link && ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => p.link && react_native_1.Linking.openURL(p.link), children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
33
42
  layout.flex.row,
34
43
  layout.flex.alignCenter,
35
44
  gap.all[2],
36
- ], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [{ color: "#3b82f6", fontSize: 14 }], children: "Learn More" }), (0, jsx_runtime_1.jsx)(lucide_react_native_1.ExternalLink, { size: 12, color: "#3b82f6" })] }) }))] })] }) }, p.message))), (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: onIgnore, style: [
37
- bg.blue[600],
38
- r.md,
39
- p[3],
40
- layout.flex.center,
41
- { marginTop: 16 },
42
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontWeight: "600" }], children: "Ignore" }) })] }));
45
+ ], children: [(0, jsx_runtime_1.jsx)(ui_1.Text, { style: [{ color: "#3b82f6", fontSize: 14 }], children: "Learn More" }), (0, jsx_runtime_1.jsx)(lucide_react_native_1.ExternalLink, { size: 12, color: "#3b82f6" })] }) }))] })] }) }, p.message))), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [layout.flex.row, layout.flex.justify.end], children: (0, jsx_runtime_1.jsx)(ui_1.Button, { onPress: onIgnore, variant: "secondary", children: (0, jsx_runtime_1.jsx)(ui_1.Text, { style: [text.white, { fontWeight: "600" }], children: "Acknowledge" }) }) })] }));
43
46
  };
44
- const ProblemsWrapper = ({ children, }) => {
47
+ exports.ProblemsWrapper = (0, react_1.forwardRef)(({ children }, ref) => {
45
48
  const problems = (0, livestream_store_1.useLivestreamStore)((x) => x.problems);
46
49
  const [dismiss, setDismiss] = (0, react_1.useState)(false);
47
- return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
48
- { position: "relative", flex: 1 },
49
- layout.flex.center,
50
- { flexBasis: 0 },
51
- ], children: [children, problems.length > 0 && !dismiss && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
50
+ (0, react_1.useImperativeHandle)(ref, () => ({
51
+ setDismiss,
52
+ }));
53
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [children, problems.length > 0 && !dismiss && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
52
54
  {
53
55
  position: "absolute",
54
56
  top: 0,
@@ -60,15 +62,14 @@ const ProblemsWrapper = ({ children, }) => {
60
62
  },
61
63
  layout.flex.center,
62
64
  { justifyContent: "flex-start" },
63
- p[8],
65
+ p[12],
64
66
  ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
65
- bg.gray[900],
66
- borders.color.gray[700],
67
+ bg.neutral[900],
68
+ borders.color.neutral[700],
67
69
  borders.width.thin,
68
70
  r.lg,
69
- p[4],
71
+ p[8],
70
72
  { maxWidth: 700, width: "100%" },
71
73
  ], children: (0, jsx_runtime_1.jsx)(Problems, { probs: problems, onIgnore: () => setDismiss(true) }) }) }))] }));
72
- };
73
- exports.ProblemsWrapper = ProblemsWrapper;
74
+ });
74
75
  exports.default = Problems;
@@ -6,9 +6,11 @@ const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const dropdown_menu_1 = require("@rn-primitives/dropdown-menu");
7
7
  const lucide_react_native_1 = require("lucide-react-native");
8
8
  const react_native_1 = require("react-native");
9
+ const __1 = require("../../..");
9
10
  const theme_1 = require("../../../lib/theme");
10
11
  const livestream_store_1 = require("../../../livestream-store");
11
12
  const player_store_1 = require("../../../player-store/");
13
+ const graph_1 = require("../../../streamplace-store/graph");
12
14
  const ui_1 = require("../../ui");
13
15
  function ContextMenu({ dropdownPortalContainer, }) {
14
16
  const quality = (0, player_store_1.usePlayerStore)((x) => x.selectedRendition);
@@ -21,6 +23,10 @@ function ContextMenu({ dropdownPortalContainer, }) {
21
23
  const livestream = (0, livestream_store_1.useLivestreamStore)((x) => x.livestream);
22
24
  const setReportModalOpen = (0, player_store_1.usePlayerStore)((x) => x.setReportModalOpen);
23
25
  const setReportSubject = (0, player_store_1.usePlayerStore)((x) => x.setReportSubject);
26
+ const { profile } = (0, __1.useLivestreamInfo)();
27
+ const avatars = (0, __1.useAvatars)(profile?.did ? [profile?.did] : []);
28
+ const ls = (0, livestream_store_1.useLivestreamStore)((x) => x.livestream);
29
+ let graphManager = (0, graph_1.useGraphManager)(profile?.did);
24
30
  const lowLatency = protocol === "webrtc";
25
31
  const setLowLatency = (value) => {
26
32
  setProtocol(value ? player_store_1.PlayerProtocol.WEBRTC : player_store_1.PlayerProtocol.HLS);
@@ -33,7 +39,44 @@ function ContextMenu({ dropdownPortalContainer, }) {
33
39
  const DropdownMenuContent = isMobile
34
40
  ? ui_1.ResponsiveDropdownMenuContent
35
41
  : ui_1.DropdownMenuContentWithoutPortal;
36
- return ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenu, { children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuTrigger, { children: (0, jsx_runtime_1.jsx)(lucide_react_native_1.Menu, { color: theme_1.colors.gray[200] }) }), (0, jsx_runtime_1.jsx)(Portal, { container: dropdownPortalContainer, children: (0, jsx_runtime_1.jsxs)(DropdownMenuContent, { side: "top", align: "end", children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Resolution", children: (0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuRadioGroup, { value: quality, onValueChange: setQuality, children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: "source", children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Source (Original Quality)" }) }), qualities.map((r) => ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: r.name, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: r.name }) })))] }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Advanced", children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: lowLatency, onCheckedChange: () => setLowLatency(!lowLatency), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Low Latency" }) }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuInfo, { description: "Reduces the delay between video and chat for a more real-time experience." }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: debugInfo, onCheckedChange: () => setShowDebugInfo(!debugInfo), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Show Debug Info" }) }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Report", children: (0, jsx_runtime_1.jsx)(ReportButton, { livestream: livestream, setReportModalOpen: setReportModalOpen, setReportSubject: setReportSubject }) })] }) })] }));
42
+ return ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenu, { children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuTrigger, { children: (0, jsx_runtime_1.jsx)(lucide_react_native_1.Menu, { color: theme_1.colors.gray[200] }) }), (0, jsx_runtime_1.jsx)(Portal, { container: dropdownPortalContainer, children: (0, jsx_runtime_1.jsxs)(DropdownMenuContent, { side: "top", align: "end", children: [react_native_1.Platform.OS !== "web" && ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuGroup, { title: "Streamer", children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
43
+ __1.zero.layout.flex.row,
44
+ __1.zero.layout.flex.center,
45
+ __1.zero.gap.all[3],
46
+ { flex: 1, minWidth: 0 },
47
+ ], children: [profile?.did && avatars[profile?.did]?.avatar && ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: {
48
+ uri: avatars[profile?.did]?.avatar,
49
+ }, style: { width: 42, height: 42, borderRadius: 999 }, resizeMode: "cover" }, "avatar")), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flex: 1, minWidth: 0 }, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
50
+ __1.zero.layout.flex.row,
51
+ __1.zero.layout.flex.alignCenter,
52
+ __1.zero.gap.all[2],
53
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => {
54
+ if (profile?.handle) {
55
+ const url = `https://bsky.app/profile/${profile.handle}`;
56
+ react_native_1.Linking.openURL(url);
57
+ }
58
+ }, children: (0, jsx_runtime_1.jsxs)(ui_1.Text, { children: ["@", profile?.handle || "user"] }) }) }), (0, jsx_runtime_1.jsx)(ui_1.Text, { color: "muted", size: "sm", numberOfLines: 2, ellipsizeMode: "tail", children: ls?.record.title || "Stream Title" })] })] }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuSeparator, {}), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { disabled: graphManager.isLoading || !profile?.did, onPress: async () => {
59
+ try {
60
+ if (graphManager.isFollowing) {
61
+ await graphManager.unfollow();
62
+ }
63
+ else {
64
+ await graphManager.follow();
65
+ }
66
+ }
67
+ catch (err) {
68
+ console.error("Follow/unfollow error:", err);
69
+ }
70
+ }, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { color: graphManager.isFollowing ? "destructive" : "default", children: graphManager.isLoading
71
+ ? "Loading..."
72
+ : graphManager.isFollowing
73
+ ? "Unfollow"
74
+ : "Follow" }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuSeparator, {}), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => {
75
+ if (profile?.handle) {
76
+ const url = `https://bsky.app/profile/${profile.handle}`;
77
+ react_native_1.Linking.openURL(url);
78
+ }
79
+ }, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "View Profile on Bluesky" }) })] })), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Resolution", children: (0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuRadioGroup, { value: quality, onValueChange: setQuality, children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: "source", children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Source (Original Quality)" }) }), qualities.map((r) => ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: r.name, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: r.name }) })))] }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Advanced", children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: lowLatency, onCheckedChange: () => setLowLatency(!lowLatency), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Low Latency" }) }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuInfo, { description: "Reduces the delay between video and chat for a more real-time experience." }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: debugInfo, onCheckedChange: () => setShowDebugInfo(!debugInfo), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Show Debug Info" }) }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Report", children: (0, jsx_runtime_1.jsx)(ReportButton, { livestream: livestream, setReportModalOpen: setReportModalOpen, setReportSubject: setReportSubject }) })] }) })] }));
37
80
  }
38
81
  function ReportButton({ livestream, setReportModalOpen, setReportSubject, }) {
39
82
  const { onOpenChange } = (0, dropdown_menu_1.useRootContext)();
@@ -85,7 +85,7 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
85
85
  { borderRadius: zero.borderRadius.md },
86
86
  ],
87
87
  inner: { gap: 4 },
88
- text: zt.text.sm,
88
+ text: zero.typography.universal.sm,
89
89
  };
90
90
  case "lg":
91
91
  return {
@@ -94,8 +94,8 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
94
94
  zero.py[3],
95
95
  { borderRadius: zero.borderRadius.md },
96
96
  ],
97
- inner: { gap: 8 },
98
- text: zt.text.lg,
97
+ inner: { gap: 12 },
98
+ text: zero.typography.universal.lg,
99
99
  };
100
100
  case "xl":
101
101
  return {
@@ -105,17 +105,17 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
105
105
  { borderRadius: zero.borderRadius.lg },
106
106
  ],
107
107
  inner: { gap: 12 },
108
- text: zt.text.xl,
108
+ text: zero.typography.universal.xl,
109
109
  };
110
110
  case "pill":
111
111
  return {
112
112
  button: [
113
- zero.px[4],
114
- zero.py[2],
113
+ zero.px[2],
114
+ zero.py[1],
115
115
  { borderRadius: zero.borderRadius.full },
116
116
  ],
117
117
  inner: { gap: 4 },
118
- text: zt.text.sm,
118
+ text: zero.typography.universal.xs,
119
119
  };
120
120
  case "md":
121
121
  default:
@@ -126,7 +126,7 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
126
126
  { borderRadius: zero.borderRadius.md },
127
127
  ],
128
128
  inner: { gap: 6 },
129
- text: zt.text.md,
129
+ text: zero.typography.universal.sm,
130
130
  };
131
131
  }
132
132
  }, [size, zt]);
@@ -170,6 +170,6 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
170
170
  return icons.color.default;
171
171
  }
172
172
  }, [variant, icons]);
173
- return ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Root, { ref: ref, disabled: disabled || loading, style: [buttonStyle, sizeStyles.button, style], ...props, children: (0, jsx_runtime_1.jsxs)(button_1.ButtonPrimitive.Content, { style: sizeStyles.inner, children: [loading && !leftIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "left", children: (0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: spinnerSize, color: spinnerColor }) })) : leftIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "left", style: { width: iconSize, height: iconSize }, children: leftIcon })) : null, (0, jsx_runtime_1.jsx)(text_1.TextPrimitive.Root, { style: [textStyle, sizeStyles.text], children: loading && loadingText ? loadingText : children }), loading && rightIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "right", children: (0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: spinnerSize, color: spinnerColor }) })) : rightIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "right", style: { width: iconSize, height: iconSize }, children: rightIcon })) : null] }) }));
173
+ return ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Root, { ref: ref, disabled: disabled || loading, style: [buttonStyle, sizeStyles.button, style], ...props, children: (0, jsx_runtime_1.jsxs)(button_1.ButtonPrimitive.Content, { style: sizeStyles.inner, children: [loading && !leftIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "left", children: (0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: spinnerSize, color: spinnerColor }) })) : leftIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "left", children: leftIcon })) : null, (0, jsx_runtime_1.jsx)(text_1.TextPrimitive.Root, { style: [textStyle, sizeStyles.text], children: loading && loadingText ? loadingText : children }), loading && rightIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "right", children: (0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: spinnerSize, color: spinnerColor }) })) : rightIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "right", style: { width: iconSize, height: iconSize }, children: rightIcon })) : null] }) }));
174
174
  });
175
175
  exports.Button.displayName = "Button";
@@ -12,6 +12,7 @@ const react_native_1 = require("react-native");
12
12
  const atoms_1 = require("../../lib/theme/atoms");
13
13
  const ui_1 = require("../../ui");
14
14
  const text_1 = require("./primitives/text");
15
+ const text_2 = require("./text");
15
16
  exports.DropdownMenu = DropdownMenuPrimitive.Root;
16
17
  exports.DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
17
18
  exports.DropdownMenuPortal = DropdownMenuPrimitive.Portal;
@@ -29,7 +30,7 @@ exports.DropdownMenuBottomSheet = (0, react_1.forwardRef)(function DropdownMenuB
29
30
  atoms_1.a.sizes.width[12],
30
31
  atoms_1.a.sizes.height[1],
31
32
  zt.bg.mutedForeground,
32
- ], children: (0, jsx_runtime_1.jsx)(bottom_sheet_1.BottomSheetView, { style: [atoms_1.px[2]], children: typeof children === "function"
33
+ ], children: (0, jsx_runtime_1.jsx)(bottom_sheet_1.BottomSheetView, { style: [atoms_1.px[4]], children: typeof children === "function"
33
34
  ? children({ pressed: true })
34
35
  : children }) }) }));
35
36
  });
@@ -123,9 +124,7 @@ exports.DropdownMenuItem = (0, react_1.forwardRef)(({ inset, disabled, style, ch
123
124
  atoms_1.py[1],
124
125
  atoms_1.pl[2],
125
126
  atoms_1.pr[2],
126
- ], children: typeof children === "function"
127
- ? children({ pressed: true })
128
- : children }) }) }));
127
+ ], children: typeof children === "function" ? (children({ pressed: true })) : typeof children === "string" ? ((0, jsx_runtime_1.jsx)(text_2.Text, { style: [inset && atoms_1.gap[2], disabled && { opacity: 0.5 }], children: children })) : (children) }) }) }));
129
128
  });
130
129
  exports.DropdownMenuCheckboxItem = (0, react_1.forwardRef)(({ children, checked, ...props }, ref) => {
131
130
  const { theme } = (0, ui_1.useTheme)();
@@ -152,21 +151,27 @@ exports.DropdownMenuRadioItem = (0, react_1.forwardRef)(({ children, ...props },
152
151
  });
153
152
  exports.DropdownMenuLabel = (0, react_1.forwardRef)(({ inset, ...props }, ref) => {
154
153
  const { theme } = (0, ui_1.useTheme)();
155
- return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { ref: ref, style: [
154
+ return ((0, jsx_runtime_1.jsx)(text_2.Text, { ref: ref, style: [
156
155
  atoms_1.px[2],
157
156
  atoms_1.py[2],
158
157
  { color: theme.colors.textMuted },
159
158
  atoms_1.a.fontSize.base,
160
- inset && atoms_1.gap[2],
159
+ (inset && atoms_1.gap[2]),
161
160
  ], ...props }));
162
161
  });
163
162
  exports.DropdownMenuSeparator = (0, react_1.forwardRef)((props, ref) => {
164
163
  const { theme } = (0, ui_1.useTheme)();
165
- return ((0, jsx_runtime_1.jsx)(react_native_1.View, { ref: ref, style: [{ height: 0.5 }, { backgroundColor: theme.colors.border }], ...props }));
164
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { ref: ref, style: [
165
+ {
166
+ borderBottomWidth: 1,
167
+ borderBottomColor: theme.colors.border,
168
+ marginVertical: -0.5,
169
+ },
170
+ ], ...props }));
166
171
  });
167
172
  function DropdownMenuShortcut(props) {
168
173
  const { theme } = (0, ui_1.useTheme)();
169
- return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
174
+ return ((0, jsx_runtime_1.jsx)(text_2.Text, { style: [
170
175
  atoms_1.ml.auto,
171
176
  { color: theme.colors.textMuted },
172
177
  atoms_1.a.fontSize.sm,
@@ -176,7 +181,7 @@ function DropdownMenuShortcut(props) {
176
181
  exports.DropdownMenuGroup = (0, react_1.forwardRef)((props, ref) => {
177
182
  const { theme } = (0, ui_1.useTheme)();
178
183
  const { inset, title, children, ...rest } = props;
179
- return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [atoms_1.pt[2], inset && atoms_1.gap[2]], ref: ref, ...rest, children: [title && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [{ color: theme.colors.textMuted }, atoms_1.pb[1], atoms_1.pl[2]], children: title })), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
184
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [atoms_1.pt[2], inset && atoms_1.gap[2]], ref: ref, ...rest, children: [title && ((0, jsx_runtime_1.jsx)(text_2.Text, { style: [{ color: theme.colors.textMuted }, atoms_1.pb[1], atoms_1.pl[2]], children: title })), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
180
185
  { backgroundColor: theme.colors.muted },
181
186
  react_native_1.Platform.OS === "web" ? [atoms_1.px[2], atoms_1.py[1]] : atoms_1.p[2],
182
187
  atoms_1.gap.all[1],
@@ -185,7 +190,7 @@ exports.DropdownMenuGroup = (0, react_1.forwardRef)((props, ref) => {
185
190
  });
186
191
  exports.DropdownMenuInfo = (0, react_1.forwardRef)(({ description, ...props }, ref) => {
187
192
  const { theme } = (0, ui_1.useTheme)();
188
- return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
193
+ return ((0, jsx_runtime_1.jsx)(text_2.Text, { style: [
189
194
  { color: theme.colors.textMuted },
190
195
  atoms_1.pt[1],
191
196
  atoms_1.pl[2],
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createThemedIcon = createThemedIcon;
4
+ exports.Icon = Icon;
4
5
  const jsx_runtime_1 = require("react/jsx-runtime");
5
6
  const theme_1 = require("../../lib/theme");
6
7
  // Size mapping
@@ -23,3 +24,8 @@ function createThemedIcon(IconComponent) {
23
24
  return ((0, jsx_runtime_1.jsx)(IconComponent, { size: iconSize, color: iconColor, ...restProps }));
24
25
  };
25
26
  }
27
+ // usage of createThemedIcon
28
+ function Icon({ icon, variant = "default", size = "md", color, ...restProps }) {
29
+ const ThemedIcon = createThemedIcon(icon);
30
+ return ((0, jsx_runtime_1.jsx)(ThemedIcon, { variant: variant, size: size, color: color, ...restProps }));
31
+ }
@@ -78,7 +78,6 @@ const primitiveStyles = react_native_1.StyleSheet.create({
78
78
  flexDirection: "row",
79
79
  alignItems: "center",
80
80
  justifyContent: "center",
81
- minHeight: 44, // iOS minimum touch target
82
81
  },
83
82
  disabled: {
84
83
  opacity: 0.5,
@@ -98,12 +97,6 @@ const primitiveStyles = react_native_1.StyleSheet.create({
98
97
  alignItems: "center",
99
98
  justifyContent: "center",
100
99
  },
101
- iconLeft: {
102
- marginRight: 8,
103
- },
104
- iconRight: {
105
- marginLeft: 8,
106
- },
107
100
  iconDisabled: {
108
101
  opacity: 0.5,
109
102
  },
@@ -580,52 +580,61 @@ exports.typography = {
580
580
  },
581
581
  },
582
582
  // Universal typography scale
583
+ // Atkinson's center is weird so the marginBottom is there to correct it?
583
584
  universal: {
584
585
  xs: {
585
586
  fontSize: 12,
586
587
  lineHeight: 16,
588
+ marginBottom: -0.7,
587
589
  fontWeight: "400",
588
590
  fontFamily: "AtkinsonHyperlegibleNext-Regular",
589
591
  },
590
592
  sm: {
591
593
  fontSize: 14,
592
594
  lineHeight: 20,
595
+ marginBottom: -1,
593
596
  fontWeight: "400",
594
597
  fontFamily: "AtkinsonHyperlegibleNext-Regular",
595
598
  },
596
599
  base: {
597
600
  fontSize: 16,
598
601
  lineHeight: 24,
602
+ marginBottom: -1.2,
599
603
  fontWeight: "400",
600
604
  fontFamily: "AtkinsonHyperlegibleNext-Regular",
601
605
  },
602
606
  lg: {
603
607
  fontSize: 18,
604
608
  lineHeight: 28,
609
+ marginBottom: -1.5,
605
610
  fontWeight: "400",
606
611
  fontFamily: "AtkinsonHyperlegibleNext-Regular",
607
612
  },
608
613
  xl: {
609
614
  fontSize: 20,
610
615
  lineHeight: 28,
616
+ marginBottom: -1.75,
611
617
  fontWeight: "500",
612
618
  fontFamily: "AtkinsonHyperlegibleNext-Medium",
613
619
  },
614
620
  "2xl": {
615
621
  fontSize: 24,
616
622
  lineHeight: 32,
623
+ marginBottom: -2,
617
624
  fontWeight: "600",
618
625
  fontFamily: "AtkinsonHyperlegibleNext-SemiBold",
619
626
  },
620
627
  "3xl": {
621
628
  fontSize: 30,
622
629
  lineHeight: 36,
630
+ marginBottom: -2.5,
623
631
  fontWeight: "700",
624
632
  fontFamily: "AtkinsonHyperlegibleNext-Bold",
625
633
  },
626
634
  "4xl": {
627
635
  fontSize: 36,
628
636
  lineHeight: 40,
637
+ marginBottom: -3,
629
638
  fontWeight: "700",
630
639
  fontFamily: "AtkinsonHyperlegibleNext-ExtraBold",
631
640
  },
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCreateFollowRecord = useCreateFollowRecord;
4
+ exports.useDeleteFollowRecord = useDeleteFollowRecord;
5
+ exports.useGraphManager = useGraphManager;
6
+ const react_1 = require("react");
7
+ const streamplace_store_1 = require("./streamplace-store");
8
+ const xrpc_1 = require("./xrpc");
9
+ function useCreateFollowRecord() {
10
+ let agent = (0, xrpc_1.usePDSAgent)();
11
+ const [isLoading, setIsLoading] = (0, react_1.useState)(false);
12
+ const createFollow = async (subjectDID) => {
13
+ if (!agent) {
14
+ throw new Error("No PDS agent found");
15
+ }
16
+ if (!agent.did) {
17
+ throw new Error("No user DID found, assuming not logged in");
18
+ }
19
+ setIsLoading(true);
20
+ try {
21
+ const record = {
22
+ $type: "app.bsky.graph.follow",
23
+ subject: subjectDID,
24
+ createdAt: new Date().toISOString(),
25
+ };
26
+ const result = await agent.com.atproto.repo.createRecord({
27
+ repo: agent.did,
28
+ collection: "app.bsky.graph.follow",
29
+ record,
30
+ });
31
+ return result;
32
+ }
33
+ finally {
34
+ setIsLoading(false);
35
+ }
36
+ };
37
+ return { createFollow, isLoading };
38
+ }
39
+ function useDeleteFollowRecord() {
40
+ let agent = (0, xrpc_1.usePDSAgent)();
41
+ const [isLoading, setIsLoading] = (0, react_1.useState)(false);
42
+ const deleteFollow = async (followRecordUri) => {
43
+ if (!agent) {
44
+ throw new Error("No PDS agent found");
45
+ }
46
+ if (!agent.did) {
47
+ throw new Error("No user DID found, assuming not logged in");
48
+ }
49
+ setIsLoading(true);
50
+ try {
51
+ const result = await agent.com.atproto.repo.deleteRecord({
52
+ repo: agent.did,
53
+ collection: "app.bsky.graph.follow",
54
+ rkey: followRecordUri.split("/").pop(),
55
+ });
56
+ return result;
57
+ }
58
+ finally {
59
+ setIsLoading(false);
60
+ }
61
+ };
62
+ return { deleteFollow, isLoading };
63
+ }
64
+ function useGraphManager(subjectDID) {
65
+ const agent = (0, xrpc_1.usePDSAgent)();
66
+ const [isFollowing, setIsFollowing] = (0, react_1.useState)(null);
67
+ const [followUri, setFollowUri] = (0, react_1.useState)(null);
68
+ const [isLoading, setIsLoading] = (0, react_1.useState)(false);
69
+ const [error, setError] = (0, react_1.useState)(null);
70
+ const userDID = agent?.did;
71
+ const streamplaceUrl = (0, streamplace_store_1.useStreamplaceStore)((state) => state.url);
72
+ const fetchFollowStatus = async () => {
73
+ if (!userDID || !subjectDID || !streamplaceUrl) {
74
+ setIsFollowing(null);
75
+ setFollowUri(null);
76
+ return;
77
+ }
78
+ setIsLoading(true);
79
+ setError(null);
80
+ try {
81
+ const res = await fetch(`${streamplaceUrl}/xrpc/place.stream.graph.getFollowingUser?subjectDID=${encodeURIComponent(subjectDID)}&userDID=${encodeURIComponent(userDID)}`, {
82
+ credentials: "include",
83
+ });
84
+ if (!res.ok) {
85
+ const errorText = await res.text();
86
+ throw new Error(`Failed to fetch follow status: ${errorText}`);
87
+ }
88
+ const data = await res.json();
89
+ if (data.follow) {
90
+ setIsFollowing(true);
91
+ setFollowUri(data.follow.uri);
92
+ }
93
+ else {
94
+ setIsFollowing(false);
95
+ setFollowUri(null);
96
+ }
97
+ }
98
+ catch (err) {
99
+ setError(`Could not determine follow state: ${err instanceof Error ? err.message : `Unknown error: ${err}`}`);
100
+ setIsFollowing(null);
101
+ }
102
+ finally {
103
+ setIsLoading(false);
104
+ }
105
+ };
106
+ (0, react_1.useEffect)(() => {
107
+ if (!userDID || !subjectDID) {
108
+ setIsFollowing(null);
109
+ setFollowUri(null);
110
+ setError(null);
111
+ return;
112
+ }
113
+ fetchFollowStatus();
114
+ }, [userDID, subjectDID, streamplaceUrl]);
115
+ const follow = async () => {
116
+ if (!agent || !subjectDID) {
117
+ throw new Error("Cannot follow: not logged in or no subject DID");
118
+ }
119
+ if (!agent.did) {
120
+ throw new Error("No user DID found, assuming not logged in");
121
+ }
122
+ setIsLoading(true);
123
+ setError(null);
124
+ const previousState = isFollowing;
125
+ setIsFollowing(true); // Optimistic
126
+ try {
127
+ const record = {
128
+ $type: "app.bsky.graph.follow",
129
+ subject: subjectDID,
130
+ createdAt: new Date().toISOString(),
131
+ };
132
+ const result = await agent.com.atproto.repo.createRecord({
133
+ repo: agent.did,
134
+ collection: "app.bsky.graph.follow",
135
+ record,
136
+ });
137
+ setFollowUri(result.data.uri);
138
+ setIsFollowing(true);
139
+ }
140
+ catch (err) {
141
+ setIsFollowing(previousState);
142
+ const errorMsg = `Failed to follow: ${err instanceof Error ? err.message : "Unknown error"}`;
143
+ setError(errorMsg);
144
+ throw new Error(errorMsg);
145
+ }
146
+ finally {
147
+ setIsLoading(false);
148
+ }
149
+ };
150
+ const unfollow = async () => {
151
+ if (!agent || !subjectDID) {
152
+ throw new Error("Cannot unfollow: not logged in or no subject DID");
153
+ }
154
+ if (!agent.did) {
155
+ throw new Error("No user DID found, assuming not logged in");
156
+ }
157
+ if (!followUri) {
158
+ throw new Error("Cannot unfollow: no follow URI found");
159
+ }
160
+ setIsLoading(true);
161
+ setError(null);
162
+ const previousState = isFollowing;
163
+ const previousUri = followUri;
164
+ setIsFollowing(false); // Optimistic
165
+ setFollowUri(null);
166
+ try {
167
+ await agent.com.atproto.repo.deleteRecord({
168
+ repo: agent.did,
169
+ collection: "app.bsky.graph.follow",
170
+ rkey: followUri.split("/").pop(),
171
+ });
172
+ setIsFollowing(false);
173
+ setFollowUri(null);
174
+ }
175
+ catch (err) {
176
+ setIsFollowing(previousState);
177
+ setFollowUri(previousUri);
178
+ const errorMsg = `Failed to unfollow: ${err instanceof Error ? err.message : "Unknown error"}`;
179
+ setError(errorMsg);
180
+ throw new Error(errorMsg);
181
+ }
182
+ finally {
183
+ setIsLoading(false);
184
+ }
185
+ };
186
+ return {
187
+ isFollowing,
188
+ followUri,
189
+ isLoading,
190
+ error,
191
+ follow,
192
+ unfollow,
193
+ refresh: fetchFollowStatus,
194
+ };
195
+ }