import { Button, ButtonVariant } from '@/components/ui/button'; import { Text } from '@/components/ui/text'; import { useColor } from '@/hooks/useColor'; import { FONT_SIZE } from '@/theme/globals'; import { Share as ShareIcon } from 'lucide-react-native'; import React, { useCallback, useMemo } from 'react'; import { Alert, Platform, Share as RNShare, ShareOptions, TextStyle, View, } from 'react-native'; export interface ShareContent { message?: string; url?: string; title?: string; subject?: string; // For email sharing on iOS } export interface ShareButtonOptions { dialogTitle?: string; // Android only excludedActivityTypes?: string[]; // iOS only tintColor?: string; // iOS only anchor?: number; // iOS only - for iPad anchoring } interface ShareButtonProps { content: ShareContent; options?: ShareButtonOptions; children?: React.ReactNode; variant?: ButtonVariant; size?: 'default' | 'sm' | 'lg' | 'icon'; disabled?: boolean; loading?: boolean; onShareStart?: () => void; onShareSuccess?: (activityType?: string | null) => void; onShareError?: (error: Error) => void; onShareDismiss?: () => void; showIcon?: boolean; iconSize?: number; fallbackMessage?: string; validateContent?: boolean; testID?: string; } export function ShareButton({ content, options, children, variant = 'default', size = 'default', disabled = false, loading = false, onShareStart, onShareSuccess, onShareError, onShareDismiss, showIcon = true, iconSize = 18, validateContent = true, }: ShareButtonProps) { const primaryColor = useColor('primary'); const primaryForegroundColor = useColor('primaryForeground'); const secondaryForegroundColor = useColor('secondaryForeground'); const destructiveForegroundColor = useColor('destructiveForeground'); // Validate content requirements const isContentValid = useMemo(() => { if (!validateContent) return true; // At least one of url or message is required const hasRequiredContent = Boolean(content.message || content.url); // Additional validation if (content.url && !isValidUrl(content.url)) { return false; } return hasRequiredContent; }, [content, validateContent]); const handleShare = useCallback(async () => { if (!isContentValid) { const error = new Error( 'Invalid share content: At least one of message or url is required' ); onShareError?.(error); Alert.alert('Share Error', 'Cannot share: invalid content provided'); return; } try { onShareStart?.(); // Build share content object const shareContent: any = {}; if (content.message) shareContent.message = content.message; if (content.url) shareContent.url = content.url; // Platform-specific content if (Platform.OS === 'ios') { if (content.title) shareContent.title = content.title; if (content.subject) shareContent.subject = content.subject; } // Build share options object const shareOptions: ShareOptions = {}; if (options) { // Android-specific options if (Platform.OS === 'android' && options.dialogTitle) { shareOptions.dialogTitle = options.dialogTitle; } // iOS-specific options if (Platform.OS === 'ios') { if (options.excludedActivityTypes) { shareOptions.excludedActivityTypes = options.excludedActivityTypes; } if (options.tintColor) { shareOptions.tintColor = options.tintColor; } if (options.anchor) { shareOptions.anchor = options.anchor; } } } const result = await RNShare.share(shareContent, shareOptions); if (result.action === RNShare.sharedAction) { onShareSuccess?.(result.activityType); } else if (result.action === RNShare.dismissedAction) { onShareDismiss?.(); } } catch (error: any) { const shareError = error instanceof Error ? error : new Error(String(error)); onShareError?.(shareError); // More user-friendly error messages const errorMessage = getShareErrorMessage(shareError); Alert.alert('Share Error', errorMessage); } }, [ content, options, isContentValid, onShareStart, onShareSuccess, onShareError, onShareDismiss, ]); const isButtonDisabled = disabled || loading || !isContentValid; const getButtonTextStyle = (): TextStyle => { const baseTextStyle: TextStyle = { fontSize: FONT_SIZE, fontWeight: '500', }; switch (variant) { case 'destructive': return { ...baseTextStyle, color: destructiveForegroundColor }; case 'success': return { ...baseTextStyle, color: destructiveForegroundColor }; case 'outline': return { ...baseTextStyle, color: primaryColor }; case 'secondary': return { ...baseTextStyle, color: secondaryForegroundColor }; case 'ghost': return { ...baseTextStyle, color: primaryColor }; case 'link': return { ...baseTextStyle, color: primaryColor, textDecorationLine: 'underline', }; default: return { ...baseTextStyle, color: primaryForegroundColor }; } }; // Create button content with proper layout const buttonContent = () => { if (!showIcon || loading) { return children; } if (!children) { return ; } // Handle string children properly with correct styling const textContent = typeof children === 'string' ? ( {children} ) : ( children ); return ( {textContent} ); }; return ( ); } // Utility function to validate URLs function isValidUrl(url: string): boolean { try { new URL(url); return true; } catch { // Try with protocol if missing try { new URL(`https://${url}`); return true; } catch { return false; } } } // Utility function to provide user-friendly error messages function getShareErrorMessage(error: Error): string { const message = error.message.toLowerCase(); if (message.includes('cancel') || message.includes('dismiss')) { return 'Share was cancelled'; } if (message.includes('network') || message.includes('connection')) { return 'Network error occurred while sharing'; } if (message.includes('permission')) { return 'Permission denied for sharing'; } if (message.includes('not supported')) { return 'Sharing is not supported on this device'; } return 'An error occurred while sharing. Please try again.'; } // Hook for easier usage with common share scenarios export function useShare() { const shareText = useCallback( (text: string, options?: ShareButtonOptions) => { return RNShare.share({ message: text }, options); }, [] ); const shareUrl = useCallback( (url: string, message?: string, options?: ShareButtonOptions) => { return RNShare.share({ url, message }, options); }, [] ); const shareContent = useCallback( (content: ShareContent, options?: ShareButtonOptions) => { const shareData: any = {}; if (content.message) shareData.message = content.message; if (content.url) shareData.url = content.url; if (Platform.OS === 'ios') { if (content.title) shareData.title = content.title; if (content.subject) shareData.subject = content.subject; } return RNShare.share(shareData, options); }, [] ); return { shareText, shareUrl, shareContent, }; }