@streamplace/components 0.0.1 → 0.7.0

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 (169) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +35 -0
  3. package/dist/components/chat/chat-box.js +109 -0
  4. package/dist/components/chat/chat-message.js +76 -0
  5. package/dist/components/chat/chat.js +56 -0
  6. package/dist/components/chat/mention-suggestions.js +39 -0
  7. package/dist/components/chat/mod-view.js +33 -0
  8. package/dist/components/mobile-player/fullscreen.js +69 -0
  9. package/dist/components/mobile-player/fullscreen.native.js +151 -0
  10. package/dist/components/mobile-player/player.js +103 -0
  11. package/dist/components/mobile-player/props.js +1 -0
  12. package/dist/components/mobile-player/shared.js +51 -0
  13. package/dist/components/mobile-player/ui/countdown.js +79 -0
  14. package/dist/components/mobile-player/ui/index.js +5 -0
  15. package/dist/components/mobile-player/ui/input.js +38 -0
  16. package/dist/components/mobile-player/ui/metrics.js +40 -0
  17. package/dist/components/mobile-player/ui/streamer-context-menu.js +4 -0
  18. package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -0
  19. package/dist/components/mobile-player/use-webrtc.js +232 -0
  20. package/dist/components/mobile-player/video.js +375 -0
  21. package/dist/components/mobile-player/video.native.js +238 -0
  22. package/dist/components/mobile-player/webrtc-diagnostics.js +106 -0
  23. package/dist/components/mobile-player/webrtc-primitives.js +25 -0
  24. package/dist/components/mobile-player/webrtc-primitives.native.js +1 -0
  25. package/dist/components/ui/button.js +220 -0
  26. package/dist/components/ui/dialog.js +203 -0
  27. package/dist/components/ui/dropdown.js +148 -0
  28. package/dist/components/ui/icons.js +22 -0
  29. package/dist/components/ui/index.js +22 -0
  30. package/dist/components/ui/input.js +202 -0
  31. package/dist/components/ui/loader.js +7 -0
  32. package/dist/components/ui/primitives/button.js +121 -0
  33. package/dist/components/ui/primitives/input.js +202 -0
  34. package/dist/components/ui/primitives/modal.js +203 -0
  35. package/dist/components/ui/primitives/text.js +286 -0
  36. package/dist/components/ui/resizeable.js +101 -0
  37. package/dist/components/ui/text.js +175 -0
  38. package/dist/components/ui/textarea.js +17 -0
  39. package/dist/components/ui/toast.js +129 -0
  40. package/dist/components/ui/view.js +250 -0
  41. package/dist/hooks/index.js +9 -0
  42. package/dist/hooks/useAvatars.js +32 -0
  43. package/dist/hooks/useCameraToggle.js +9 -0
  44. package/dist/hooks/useKeyboard.js +33 -0
  45. package/dist/hooks/useKeyboardSlide.js +11 -0
  46. package/dist/hooks/useLivestreamInfo.js +62 -0
  47. package/dist/hooks/useOuterAndInnerDimensions.js +27 -0
  48. package/dist/hooks/usePlayerDimensions.js +19 -0
  49. package/dist/hooks/useSegmentTiming.js +62 -0
  50. package/dist/index.js +16 -0
  51. package/dist/lib/facet.js +88 -0
  52. package/dist/lib/theme/atoms.js +620 -0
  53. package/dist/lib/theme/atoms.types.js +5 -0
  54. package/dist/lib/theme/index.js +9 -0
  55. package/dist/lib/theme/theme.js +248 -0
  56. package/dist/lib/theme/tokens.js +383 -0
  57. package/dist/lib/utils.js +94 -0
  58. package/dist/livestream-provider/index.js +25 -0
  59. package/dist/livestream-provider/websocket.js +41 -0
  60. package/dist/livestream-store/chat.js +186 -0
  61. package/dist/livestream-store/context.js +2 -0
  62. package/dist/livestream-store/index.js +4 -0
  63. package/dist/livestream-store/livestream-state.js +1 -0
  64. package/dist/livestream-store/livestream-store.js +42 -0
  65. package/dist/livestream-store/stream-key.js +115 -0
  66. package/dist/livestream-store/websocket-consumer.js +55 -0
  67. package/dist/player-store/context.js +2 -0
  68. package/dist/player-store/index.js +6 -0
  69. package/dist/player-store/player-provider.js +52 -0
  70. package/dist/player-store/player-state.js +22 -0
  71. package/dist/player-store/player-store.js +159 -0
  72. package/dist/player-store/single-player-provider.js +109 -0
  73. package/dist/streamplace-provider/context.js +2 -0
  74. package/dist/streamplace-provider/index.js +16 -0
  75. package/dist/streamplace-provider/poller.js +46 -0
  76. package/dist/streamplace-provider/xrpc.js +0 -0
  77. package/dist/streamplace-store/block.js +23 -0
  78. package/dist/streamplace-store/index.js +3 -0
  79. package/dist/streamplace-store/stream.js +193 -0
  80. package/dist/streamplace-store/streamplace-store.js +37 -0
  81. package/dist/streamplace-store/user.js +47 -0
  82. package/dist/streamplace-store/xrpc.js +12 -0
  83. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  84. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
  85. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
  86. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
  87. package/package.json +50 -8
  88. package/src/components/chat/chat-box.tsx +195 -0
  89. package/src/components/chat/chat-message.tsx +192 -0
  90. package/src/components/chat/chat.tsx +128 -0
  91. package/src/components/chat/mention-suggestions.tsx +71 -0
  92. package/src/components/chat/mod-view.tsx +118 -0
  93. package/src/components/mobile-player/fullscreen.native.tsx +193 -0
  94. package/src/components/mobile-player/fullscreen.tsx +79 -0
  95. package/src/components/mobile-player/player.tsx +134 -0
  96. package/src/components/mobile-player/props.tsx +11 -0
  97. package/src/components/mobile-player/shared.tsx +56 -0
  98. package/src/components/mobile-player/ui/countdown.tsx +119 -0
  99. package/src/components/mobile-player/ui/index.ts +5 -0
  100. package/src/components/mobile-player/ui/input.tsx +85 -0
  101. package/src/components/mobile-player/ui/metrics.tsx +69 -0
  102. package/src/components/mobile-player/ui/streamer-context-menu.tsx +3 -0
  103. package/src/components/mobile-player/ui/viewer-context-menu.tsx +70 -0
  104. package/src/components/mobile-player/use-webrtc.tsx +282 -0
  105. package/src/components/mobile-player/video.native.tsx +360 -0
  106. package/src/components/mobile-player/video.tsx +557 -0
  107. package/src/components/mobile-player/webrtc-diagnostics.tsx +149 -0
  108. package/src/components/mobile-player/webrtc-primitives.native.tsx +6 -0
  109. package/src/components/mobile-player/webrtc-primitives.tsx +33 -0
  110. package/src/components/ui/button.tsx +309 -0
  111. package/src/components/ui/dialog.tsx +376 -0
  112. package/src/components/ui/dropdown.tsx +399 -0
  113. package/src/components/ui/icons.tsx +50 -0
  114. package/src/components/ui/index.ts +33 -0
  115. package/src/components/ui/input.tsx +350 -0
  116. package/src/components/ui/loader.tsx +9 -0
  117. package/src/components/ui/primitives/button.tsx +292 -0
  118. package/src/components/ui/primitives/input.tsx +422 -0
  119. package/src/components/ui/primitives/modal.tsx +421 -0
  120. package/src/components/ui/primitives/text.tsx +499 -0
  121. package/src/components/ui/resizeable.tsx +169 -0
  122. package/src/components/ui/text.tsx +330 -0
  123. package/src/components/ui/textarea.tsx +34 -0
  124. package/src/components/ui/toast.tsx +203 -0
  125. package/src/components/ui/view.tsx +344 -0
  126. package/src/hooks/index.ts +9 -0
  127. package/src/hooks/useAvatars.tsx +44 -0
  128. package/src/hooks/useCameraToggle.ts +12 -0
  129. package/src/hooks/useKeyboard.tsx +41 -0
  130. package/src/hooks/useKeyboardSlide.ts +12 -0
  131. package/src/hooks/useLivestreamInfo.ts +67 -0
  132. package/src/hooks/useOuterAndInnerDimensions.tsx +32 -0
  133. package/src/hooks/usePlayerDimensions.ts +23 -0
  134. package/src/hooks/useSegmentTiming.tsx +88 -0
  135. package/src/index.tsx +27 -0
  136. package/src/lib/facet.ts +131 -0
  137. package/src/lib/theme/atoms.ts +760 -0
  138. package/src/lib/theme/atoms.types.ts +258 -0
  139. package/src/lib/theme/index.ts +48 -0
  140. package/src/lib/theme/theme.tsx +436 -0
  141. package/src/lib/theme/tokens.ts +409 -0
  142. package/src/lib/utils.ts +132 -0
  143. package/src/livestream-provider/index.tsx +48 -0
  144. package/src/livestream-provider/websocket.tsx +47 -0
  145. package/src/livestream-store/chat.tsx +261 -0
  146. package/src/livestream-store/context.tsx +10 -0
  147. package/src/livestream-store/index.tsx +4 -0
  148. package/src/livestream-store/livestream-state.tsx +21 -0
  149. package/src/livestream-store/livestream-store.tsx +59 -0
  150. package/src/livestream-store/stream-key.tsx +124 -0
  151. package/src/livestream-store/websocket-consumer.tsx +62 -0
  152. package/src/player-store/context.tsx +11 -0
  153. package/src/player-store/index.tsx +6 -0
  154. package/src/player-store/player-provider.tsx +89 -0
  155. package/src/player-store/player-state.tsx +187 -0
  156. package/src/player-store/player-store.tsx +239 -0
  157. package/src/player-store/single-player-provider.tsx +181 -0
  158. package/src/streamplace-provider/context.tsx +10 -0
  159. package/src/streamplace-provider/index.tsx +32 -0
  160. package/src/streamplace-provider/poller.tsx +55 -0
  161. package/src/streamplace-provider/xrpc.tsx +0 -0
  162. package/src/streamplace-store/block.tsx +29 -0
  163. package/src/streamplace-store/index.tsx +3 -0
  164. package/src/streamplace-store/stream.tsx +262 -0
  165. package/src/streamplace-store/streamplace-store.tsx +89 -0
  166. package/src/streamplace-store/user.tsx +57 -0
  167. package/src/streamplace-store/xrpc.tsx +15 -0
  168. package/tsconfig.json +9 -0
  169. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,33 @@
1
+ // Browser compatibility checks for WebRTC
2
+ const checkWebRTCSupport = () => {
3
+ if (typeof window === "undefined") {
4
+ throw new Error("WebRTC is not available in non-browser environments");
5
+ }
6
+
7
+ if (!window.RTCPeerConnection) {
8
+ throw new Error(
9
+ "RTCPeerConnection is not supported in this browser. Please use a modern browser that supports WebRTC.",
10
+ );
11
+ }
12
+
13
+ if (!window.RTCSessionDescription) {
14
+ throw new Error(
15
+ "RTCSessionDescription is not supported in this browser. Please use a modern browser that supports WebRTC.",
16
+ );
17
+ }
18
+ };
19
+
20
+ // Check support immediately
21
+ try {
22
+ checkWebRTCSupport();
23
+ } catch (error) {
24
+ console.error("WebRTC Compatibility Error:", error.message);
25
+ }
26
+
27
+ export const RTCPeerConnection = window.RTCPeerConnection;
28
+ export const RTCSessionDescription = window.RTCSessionDescription;
29
+ export const WebRTCMediaStream = window.MediaStream;
30
+ export const mediaDevices = navigator.mediaDevices;
31
+
32
+ // Export the compatibility checker for use in other components
33
+ export { checkWebRTCSupport };
@@ -0,0 +1,309 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import React, { forwardRef, useMemo } from "react";
3
+ import { ActivityIndicator, StyleSheet } from "react-native";
4
+ import { useTheme } from "../../lib/theme/theme";
5
+ import * as tokens from "../../lib/theme/tokens";
6
+ import { ButtonPrimitive, ButtonPrimitiveProps } from "./primitives/button";
7
+ import { TextPrimitive } from "./primitives/text";
8
+
9
+ // Button variants using class-variance-authority pattern
10
+ const buttonVariants = cva("", {
11
+ variants: {
12
+ variant: {
13
+ primary: "primary",
14
+ secondary: "secondary",
15
+ outline: "outline",
16
+ ghost: "ghost",
17
+ destructive: "destructive",
18
+ success: "success",
19
+ },
20
+ size: {
21
+ sm: "sm",
22
+ md: "md",
23
+ lg: "lg",
24
+ xl: "xl",
25
+ pill: "pill",
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: "primary",
30
+ size: "md",
31
+ },
32
+ });
33
+
34
+ export interface ButtonProps
35
+ extends Omit<ButtonPrimitiveProps, "children">,
36
+ VariantProps<typeof buttonVariants> {
37
+ children?: React.ReactNode;
38
+ leftIcon?: React.ReactNode;
39
+ rightIcon?: React.ReactNode;
40
+ loading?: boolean;
41
+ loadingText?: string;
42
+ }
43
+
44
+ export const Button = forwardRef<any, ButtonProps>(
45
+ (
46
+ {
47
+ variant = "primary",
48
+ size = "md",
49
+ children,
50
+ leftIcon,
51
+ rightIcon,
52
+ loading = false,
53
+ loadingText,
54
+ disabled,
55
+ style,
56
+ ...props
57
+ },
58
+ ref,
59
+ ) => {
60
+ const { theme } = useTheme();
61
+
62
+ // Create dynamic styles based on theme
63
+ const styles = useMemo(() => createStyles(theme), [theme]);
64
+
65
+ // Get variant styles
66
+ const buttonStyle = useMemo(() => {
67
+ const variantStyle = styles[`${variant}Button` as keyof typeof styles];
68
+ const sizeStyle = styles[`${size}Button` as keyof typeof styles];
69
+ return [variantStyle, sizeStyle];
70
+ }, [variant, size, styles]);
71
+
72
+ // Get inner styles for button content
73
+ const buttonInnerStyle = useMemo(() => {
74
+ const sizeInnerStyle =
75
+ styles[`${size}ButtonInner` as keyof typeof styles];
76
+ return sizeInnerStyle;
77
+ }, [size, styles]);
78
+
79
+ const textStyle = React.useMemo(() => {
80
+ const variantTextStyle = styles[`${variant}Text` as keyof typeof styles];
81
+ const sizeTextStyle = styles[`${size}Text` as keyof typeof styles];
82
+ return [variantTextStyle, sizeTextStyle];
83
+ }, [variant, size, styles]);
84
+
85
+ const iconSize = React.useMemo(() => {
86
+ switch (size) {
87
+ case "sm":
88
+ return 16;
89
+ case "lg":
90
+ return 20;
91
+ case "xl":
92
+ return 24;
93
+ case "md":
94
+ default:
95
+ return 18;
96
+ }
97
+ }, [size]);
98
+
99
+ const spinnerSize = useMemo(() => {
100
+ switch (size) {
101
+ case "sm":
102
+ return "small" as const;
103
+ case "lg":
104
+ case "xl":
105
+ return "large" as const;
106
+ case "md":
107
+ default:
108
+ return "small" as const;
109
+ }
110
+ }, [size]);
111
+
112
+ const spinnerColor = useMemo(() => {
113
+ switch (variant) {
114
+ case "outline":
115
+ case "ghost":
116
+ return theme.colors.primary;
117
+ case "secondary":
118
+ return theme.colors.secondaryForeground;
119
+ case "destructive":
120
+ return theme.colors.destructiveForeground;
121
+ default:
122
+ return theme.colors.primaryForeground;
123
+ }
124
+ }, [variant, theme.colors]);
125
+
126
+ return (
127
+ <ButtonPrimitive.Root
128
+ ref={ref}
129
+ disabled={disabled || loading}
130
+ style={[buttonStyle, style]}
131
+ {...props}
132
+ >
133
+ <ButtonPrimitive.Content style={buttonInnerStyle}>
134
+ {loading && !leftIcon ? (
135
+ <ButtonPrimitive.Icon position="left">
136
+ <ActivityIndicator size={spinnerSize} color={spinnerColor} />
137
+ </ButtonPrimitive.Icon>
138
+ ) : leftIcon ? (
139
+ <ButtonPrimitive.Icon
140
+ position="left"
141
+ style={{ width: iconSize, height: iconSize }}
142
+ >
143
+ {leftIcon}
144
+ </ButtonPrimitive.Icon>
145
+ ) : null}
146
+
147
+ <TextPrimitive.Root style={textStyle}>
148
+ {loading && loadingText ? loadingText : children}
149
+ </TextPrimitive.Root>
150
+
151
+ {loading && rightIcon ? (
152
+ <ButtonPrimitive.Icon position="right">
153
+ <ActivityIndicator size={spinnerSize} color={spinnerColor} />
154
+ </ButtonPrimitive.Icon>
155
+ ) : rightIcon ? (
156
+ <ButtonPrimitive.Icon
157
+ position="right"
158
+ style={{ width: iconSize, height: iconSize }}
159
+ >
160
+ {rightIcon}
161
+ </ButtonPrimitive.Icon>
162
+ ) : null}
163
+ </ButtonPrimitive.Content>
164
+ </ButtonPrimitive.Root>
165
+ );
166
+ },
167
+ );
168
+
169
+ Button.displayName = "Button";
170
+
171
+ // Create theme-based styles
172
+ function createStyles(theme: any) {
173
+ return StyleSheet.create({
174
+ // Variant styles
175
+ primaryButton: {
176
+ backgroundColor: theme.colors.primary,
177
+ borderWidth: 0,
178
+ ...theme.shadows.sm,
179
+ },
180
+ primaryText: {
181
+ color: theme.colors.primaryForeground,
182
+ fontWeight: "600",
183
+ },
184
+
185
+ secondaryButton: {
186
+ backgroundColor: theme.colors.secondary,
187
+ borderWidth: 0,
188
+ },
189
+ secondaryText: {
190
+ color: theme.colors.secondaryForeground,
191
+ fontWeight: "500",
192
+ },
193
+
194
+ outlineButton: {
195
+ backgroundColor: "transparent",
196
+ borderWidth: 1,
197
+ borderColor: theme.colors.border,
198
+ },
199
+ outlineText: {
200
+ color: theme.colors.foreground,
201
+ fontWeight: "500",
202
+ },
203
+
204
+ ghostButton: {
205
+ backgroundColor: "transparent",
206
+ borderWidth: 0,
207
+ },
208
+ ghostText: {
209
+ color: theme.colors.foreground,
210
+ fontWeight: "500",
211
+ },
212
+
213
+ destructiveButton: {
214
+ backgroundColor: theme.colors.destructive,
215
+ borderWidth: 0,
216
+ ...theme.shadows.sm,
217
+ },
218
+ destructiveText: {
219
+ color: theme.colors.destructiveForeground,
220
+ fontWeight: "600",
221
+ },
222
+
223
+ successButton: {
224
+ backgroundColor: theme.colors.success,
225
+ borderWidth: 0,
226
+ ...theme.shadows.sm,
227
+ },
228
+ successText: {
229
+ color: theme.colors.successForeground,
230
+ fontWeight: "600",
231
+ },
232
+
233
+ pillButton: {
234
+ paddingHorizontal: theme.spacing[3],
235
+ paddingVertical: theme.spacing[2],
236
+ borderRadius: tokens.borderRadius.full,
237
+ minHeight: tokens.touchTargets.minimum / 2,
238
+ },
239
+
240
+ pillText: {
241
+ color: theme.colors.primaryForeground,
242
+ fontWeight: "400",
243
+ },
244
+
245
+ // Size styles
246
+ smButton: {
247
+ paddingHorizontal: theme.spacing[3],
248
+ paddingVertical: theme.spacing[2],
249
+ borderRadius: tokens.borderRadius.md,
250
+ minHeight: tokens.touchTargets.minimum,
251
+ gap: theme.spacing[1],
252
+ },
253
+ smButtonInner: {
254
+ gap: theme.spacing[1],
255
+ },
256
+ smText: {
257
+ fontSize: 14,
258
+ lineHeight: 16,
259
+ },
260
+
261
+ mdButton: {
262
+ paddingHorizontal: theme.spacing[4],
263
+ paddingVertical: theme.spacing[3],
264
+ borderRadius: tokens.borderRadius.md,
265
+ minHeight: tokens.touchTargets.minimum,
266
+ gap: theme.spacing[2],
267
+ },
268
+ mdButtonInner: {
269
+ gap: theme.spacing[2],
270
+ },
271
+ mdText: {
272
+ fontSize: 16,
273
+ lineHeight: 18,
274
+ },
275
+
276
+ lgButton: {
277
+ paddingHorizontal: theme.spacing[6],
278
+ paddingVertical: theme.spacing[4],
279
+ borderRadius: tokens.borderRadius.md,
280
+ minHeight: tokens.touchTargets.comfortable,
281
+ gap: theme.spacing[3],
282
+ },
283
+ lgButtonInner: {
284
+ gap: theme.spacing[3],
285
+ },
286
+ lgText: {
287
+ fontSize: 18,
288
+ lineHeight: 20,
289
+ },
290
+
291
+ xlButton: {
292
+ paddingHorizontal: theme.spacing[8],
293
+ paddingVertical: theme.spacing[5],
294
+ borderRadius: tokens.borderRadius.lg,
295
+ minHeight: tokens.touchTargets.large,
296
+ gap: theme.spacing[4],
297
+ },
298
+ xlButtonInner: {
299
+ gap: theme.spacing[4],
300
+ },
301
+ xlText: {
302
+ fontSize: 20,
303
+ lineHeight: 24,
304
+ },
305
+ });
306
+ }
307
+
308
+ // Export button variants for external use
309
+ export { buttonVariants };
@@ -0,0 +1,376 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { X } from "lucide-react-native";
3
+ import React, { forwardRef } from "react";
4
+ import { Platform, StyleSheet, Text } from "react-native";
5
+ import { useTheme } from "../../lib/theme/theme";
6
+ import { createThemedIcon } from "./icons";
7
+ import { ModalPrimitive, ModalPrimitiveProps } from "./primitives/modal";
8
+
9
+ const ThemedX = createThemedIcon(X);
10
+
11
+ // Dialog variants using class-variance-authority pattern
12
+ const dialogVariants = cva("", {
13
+ variants: {
14
+ variant: {
15
+ default: "default",
16
+ sheet: "sheet",
17
+ fullscreen: "fullscreen",
18
+ },
19
+ size: {
20
+ sm: "sm",
21
+ md: "md",
22
+ lg: "lg",
23
+ xl: "xl",
24
+ full: "full",
25
+ },
26
+ position: {
27
+ center: "center",
28
+ top: "top",
29
+ bottom: "bottom",
30
+ left: "left",
31
+ right: "right",
32
+ },
33
+ },
34
+ defaultVariants: {
35
+ variant: "default",
36
+ size: "md",
37
+ position: "center",
38
+ },
39
+ });
40
+
41
+ export interface DialogProps
42
+ extends Omit<ModalPrimitiveProps, "children">,
43
+ VariantProps<typeof dialogVariants> {
44
+ children?: React.ReactNode;
45
+ title?: string;
46
+ description?: string;
47
+ dismissible?: boolean;
48
+ showCloseButton?: boolean;
49
+ onClose?: () => void;
50
+ }
51
+
52
+ export const Dialog = forwardRef<any, DialogProps>(
53
+ (
54
+ {
55
+ variant = "left",
56
+ size = "md",
57
+ position = "center",
58
+ children,
59
+ title,
60
+ description,
61
+ dismissible = true,
62
+ showCloseButton = true,
63
+ onClose,
64
+ open = false,
65
+ onOpenChange,
66
+ ...props
67
+ },
68
+ ref,
69
+ ) => {
70
+ const { theme } = useTheme();
71
+
72
+ // Create dynamic styles based on theme
73
+ const styles = React.useMemo(() => createStyles(theme), [theme]);
74
+
75
+ const handleClose = React.useCallback(() => {
76
+ if (onClose) {
77
+ onClose();
78
+ }
79
+ if (onOpenChange) {
80
+ onOpenChange(false);
81
+ }
82
+ }, [onClose, onOpenChange]);
83
+
84
+ const presentationStyle = React.useMemo(() => {
85
+ if (variant === "sheet" && Platform.OS === "ios") {
86
+ return "pageSheet" as const;
87
+ }
88
+ if (variant === "fullscreen") {
89
+ return "fullScreen" as const;
90
+ }
91
+ return Platform.OS === "ios"
92
+ ? ("pageSheet" as const)
93
+ : ("fullScreen" as const);
94
+ }, [variant]);
95
+
96
+ const animationType = React.useMemo(() => {
97
+ if (variant === "sheet") {
98
+ return "slide" as const;
99
+ }
100
+ return "fade" as const;
101
+ }, [variant]);
102
+
103
+ return (
104
+ <ModalPrimitive.Root
105
+ ref={ref}
106
+ open={open}
107
+ onOpenChange={onOpenChange}
108
+ presentationStyle={presentationStyle}
109
+ animationType={animationType}
110
+ {...props}
111
+ >
112
+ <ModalPrimitive.Overlay
113
+ dismissible={dismissible}
114
+ onDismiss={handleClose}
115
+ style={styles.overlay}
116
+ >
117
+ <ModalPrimitive.Content
118
+ position={position || "left"}
119
+ size={size || "md"}
120
+ style={[
121
+ styles.content,
122
+ styles[`${variant}Content` as keyof typeof styles],
123
+ styles[`${size}Content` as keyof typeof styles],
124
+ ]}
125
+ >
126
+ {(title || showCloseButton) && (
127
+ <ModalPrimitive.Header
128
+ withBorder={variant !== "sheet"}
129
+ style={styles.header}
130
+ >
131
+ <DialogTitle>{title}</DialogTitle>
132
+ {showCloseButton && (
133
+ <ModalPrimitive.Close
134
+ onClose={handleClose}
135
+ style={styles.closeButton}
136
+ >
137
+ <DialogCloseIcon />
138
+ </ModalPrimitive.Close>
139
+ )}
140
+ </ModalPrimitive.Header>
141
+ )}
142
+
143
+ <ModalPrimitive.Body
144
+ scrollable={variant !== "fullscreen"}
145
+ style={styles.body}
146
+ >
147
+ {description && (
148
+ <DialogDescription>{description}</DialogDescription>
149
+ )}
150
+ {children}
151
+ </ModalPrimitive.Body>
152
+ </ModalPrimitive.Content>
153
+ </ModalPrimitive.Overlay>
154
+ </ModalPrimitive.Root>
155
+ );
156
+ },
157
+ );
158
+
159
+ Dialog.displayName = "Dialog";
160
+
161
+ // Dialog Title component
162
+ export interface DialogTitleProps {
163
+ children?: React.ReactNode;
164
+ style?: any;
165
+ }
166
+
167
+ export const DialogTitle = forwardRef<any, DialogTitleProps>(
168
+ ({ children, style, ...props }, ref) => {
169
+ const { theme } = useTheme();
170
+ const styles = React.useMemo(() => createStyles(theme), [theme]);
171
+
172
+ if (!children) return null;
173
+
174
+ return (
175
+ <Text ref={ref} style={[styles.title, style]} {...props}>
176
+ {children}
177
+ </Text>
178
+ );
179
+ },
180
+ );
181
+
182
+ DialogTitle.displayName = "DialogTitle";
183
+
184
+ // Dialog Description component
185
+ export interface DialogDescriptionProps {
186
+ children?: React.ReactNode;
187
+ style?: any;
188
+ }
189
+
190
+ export const DialogDescription = forwardRef<any, DialogDescriptionProps>(
191
+ ({ children, style, ...props }, ref) => {
192
+ const { theme } = useTheme();
193
+ const styles = React.useMemo(() => createStyles(theme), [theme]);
194
+
195
+ if (!children) return null;
196
+
197
+ return (
198
+ <Text ref={ref} style={[styles.description, style]} {...props}>
199
+ {children}
200
+ </Text>
201
+ );
202
+ },
203
+ );
204
+
205
+ DialogDescription.displayName = "DialogDescription";
206
+
207
+ // Dialog Footer component
208
+ export interface DialogFooterProps {
209
+ children?: React.ReactNode;
210
+ direction?: "row" | "column";
211
+ justify?:
212
+ | "flex-start"
213
+ | "center"
214
+ | "flex-end"
215
+ | "space-between"
216
+ | "space-around";
217
+ withBorder?: boolean;
218
+ style?: any;
219
+ }
220
+
221
+ export const DialogFooter = forwardRef<any, DialogFooterProps>(
222
+ (
223
+ {
224
+ children,
225
+ direction = "row",
226
+ justify = "flex-end",
227
+ withBorder = true,
228
+ style,
229
+ ...props
230
+ },
231
+ ref,
232
+ ) => {
233
+ const { theme } = useTheme();
234
+ const styles = React.useMemo(() => createStyles(theme), [theme]);
235
+
236
+ if (!children) return null;
237
+
238
+ return (
239
+ <ModalPrimitive.Footer
240
+ ref={ref}
241
+ withBorder={withBorder}
242
+ direction={direction}
243
+ justify={justify}
244
+ style={[styles.footer, style]}
245
+ {...props}
246
+ >
247
+ {children}
248
+ </ModalPrimitive.Footer>
249
+ );
250
+ },
251
+ );
252
+
253
+ DialogFooter.displayName = "DialogFooter";
254
+
255
+ // Dialog Close Icon component (Lucide X)
256
+ const DialogCloseIcon = () => {
257
+ return <ThemedX size="md" variant="muted" />;
258
+ };
259
+
260
+ // Create theme-aware styles
261
+ function createStyles(theme: any) {
262
+ return StyleSheet.create({
263
+ overlay: {
264
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
265
+ },
266
+
267
+ content: {
268
+ backgroundColor: theme.colors.card,
269
+ borderRadius: theme.borderRadius.lg,
270
+ ...theme.shadows.lg,
271
+ maxHeight: "90%",
272
+ maxWidth: "90%",
273
+ },
274
+
275
+ // Variant styles
276
+ defaultContent: {
277
+ // Default styles already applied in content
278
+ },
279
+
280
+ sheetContent: {
281
+ borderTopLeftRadius: theme.borderRadius.xl,
282
+ borderTopRightRadius: theme.borderRadius.xl,
283
+ borderBottomLeftRadius: 0,
284
+ borderBottomRightRadius: 0,
285
+ marginTop: "auto",
286
+ marginBottom: 0,
287
+ maxHeight: "80%",
288
+ width: "100%",
289
+ maxWidth: "100%",
290
+ },
291
+
292
+ fullscreenContent: {
293
+ width: "100%",
294
+ height: "100%",
295
+ maxWidth: "100%",
296
+ maxHeight: "100%",
297
+ borderRadius: 0,
298
+ margin: 0,
299
+ },
300
+
301
+ // Size styles
302
+ smContent: {
303
+ minWidth: 300,
304
+ minHeight: 200,
305
+ },
306
+
307
+ mdContent: {
308
+ minWidth: 400,
309
+ minHeight: 300,
310
+ },
311
+
312
+ lgContent: {
313
+ minWidth: 500,
314
+ minHeight: 400,
315
+ },
316
+
317
+ xlContent: {
318
+ minWidth: 600,
319
+ minHeight: 500,
320
+ },
321
+
322
+ fullContent: {
323
+ width: "95%",
324
+ height: "95%",
325
+ maxWidth: "95%",
326
+ maxHeight: "95%",
327
+ },
328
+
329
+ header: {
330
+ paddingHorizontal: theme.spacing[6],
331
+ paddingVertical: theme.spacing[4],
332
+ flexDirection: "row",
333
+ alignItems: "center",
334
+ justifyContent: "space-between",
335
+ },
336
+
337
+ body: {
338
+ paddingHorizontal: theme.spacing[6],
339
+ paddingBottom: theme.spacing[6],
340
+ flex: 1,
341
+ },
342
+
343
+ footer: {
344
+ paddingHorizontal: theme.spacing[6],
345
+ paddingVertical: theme.spacing[4],
346
+ gap: theme.spacing[2],
347
+ },
348
+
349
+ title: {
350
+ fontSize: 20,
351
+ fontWeight: "600",
352
+ color: theme.colors.text,
353
+ flex: 1,
354
+ lineHeight: 24,
355
+ },
356
+
357
+ description: {
358
+ fontSize: 16,
359
+ color: theme.colors.textMuted,
360
+ lineHeight: 22,
361
+ marginBottom: theme.spacing[4],
362
+ },
363
+
364
+ closeButton: {
365
+ width: theme.touchTargets.minimum,
366
+ height: theme.touchTargets.minimum,
367
+ alignItems: "center",
368
+ justifyContent: "center",
369
+ borderRadius: theme.borderRadius.sm,
370
+ marginLeft: theme.spacing[2],
371
+ },
372
+ });
373
+ }
374
+
375
+ // Export dialog variants for external use
376
+ export { dialogVariants };