# Share

> A button component for sharing content across platforms with native share functionality.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/share
- Markdown: https://ui.ahmedbna.com/docs/components/share.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/share.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/share.json
- Install: `npx bna-ui add share`
- npm dependencies: `expo-haptics`, `lucide-react-native`, `react-native-reanimated`, `react-native-svg`, `react-native-worklets`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `globals`, `useHaptics`, `text`, `view`, `icon`, `spinner`, `button`
- Preview recording: https://demo.ahmedbna.com/0260-share-demo.MP4

---

**Example:** A basic share button with text and URL sharing

```tsx
// components/demo/share/share-demo.tsx
import { ShareButton } from '@/components/ui/share';
import React from 'react';

export function ShareDemo() {
  return (
    <ShareButton
      content={{
        message: 'Check out this amazing app!',
        url: 'https://example.com',
        title: 'Amazing App',
      }}
      onShareSuccess={(activityType) => {
        console.log('Shared successfully:', activityType);
      }}
      onShareError={(error) => {
        console.error('Share failed:', error);
      }}
    >
      Share
    </ShareButton>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add share
```

### Manual

**1.** Install the following dependencies:

```bash
npx expo install lucide-react-native
```

**2.** Copy and paste the following code into your project.

```tsx
// components/ui/share.tsx
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,
  fallbackMessage,
  validateContent = true,
  testID,
}: 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 = {};

      const message = content.message || fallbackMessage;
      if (message) shareContent.message = message;
      if (content.url) shareContent.url = content.url;

      // `title` is Android-facing per RN's Share API (iOS ignores it)
      if (Platform.OS === 'android' && content.title) {
        shareContent.title = content.title;
      }

      // Build share options object
      const shareOptions: ShareOptions = {};

      // `subject` is iOS-facing and belongs on options, not content
      if (Platform.OS === 'ios' && content.subject) {
        shareOptions.subject = content.subject;
      }

      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,
    fallbackMessage,
    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 <ShareIcon size={iconSize} color={getButtonTextStyle().color} />;
    }

    // Handle string children properly with correct styling
    const textContent =
      typeof children === 'string' ? (
        <Text style={getButtonTextStyle()}>{children}</Text>
      ) : (
        children
      );

    return (
      <View style={{ flexDirection: 'row', alignItems: 'center' }}>
        <ShareIcon
          size={iconSize}
          color={getButtonTextStyle().color}
          style={{ marginRight: 8 }}
        />
        {textContent}
      </View>
    );
  };

  return (
    <Button
      onPress={handleShare}
      variant={variant}
      size={size}
      disabled={isButtonDisabled}
      loading={loading}
      testID={testID}
    >
      {buttonContent()}
    </Button>
  );
}

// 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;
      // `title` is Android-facing, `subject` is iOS-facing and belongs on
      // options (not content) — see RN's Share API docs.
      if (Platform.OS === 'android' && content.title) {
        shareData.title = content.title;
      }

      const shareOptions: ShareButtonOptions & Pick<ShareOptions, 'subject'> = {
        ...options,
      };
      if (Platform.OS === 'ios' && content.subject) {
        shareOptions.subject = content.subject;
      }

      return RNShare.share(shareData, shareOptions);
    },
    []
  );

  return {
    shareText,
    shareUrl,
    shareContent,
  };
}
```

**3.** Update the import paths to match your project setup.

## Usage

```tsx
import { ShareButton, useShare } from '@/components/ui/share';
```

```tsx
<ShareButton
  content={{
    message: 'Check out this amazing app!',
    url: 'https://example.com',
  }}
>
  Share
</ShareButton>
```

## Examples

#### Default

**Example:** A basic share button with text and URL sharing

```tsx
// components/demo/share/share-demo.tsx
import { ShareButton } from '@/components/ui/share';
import React from 'react';

export function ShareDemo() {
  return (
    <ShareButton
      content={{
        message: 'Check out this amazing app!',
        url: 'https://example.com',
        title: 'Amazing App',
      }}
      onShareSuccess={(activityType) => {
        console.log('Shared successfully:', activityType);
      }}
      onShareError={(error) => {
        console.error('Share failed:', error);
      }}
    >
      Share
    </ShareButton>
  );
}
```

#### Share Variants

**Example:** Share buttons with different visual variants

```tsx
// components/demo/share/share-variants.tsx
import { ShareButton } from '@/components/ui/share';
import { View } from '@/components/ui/view';
import React from 'react';

export function ShareVariants() {
  const shareContent = {
    message: 'Check out this amazing content!',
    url: 'https://example.com',
  };

  return (
    <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 12 }}>
      <ShareButton content={shareContent} variant='default'>
        Default
      </ShareButton>

      <ShareButton content={shareContent} variant='secondary'>
        Secondary
      </ShareButton>

      <ShareButton content={shareContent} variant='outline'>
        Outline
      </ShareButton>

      <ShareButton content={shareContent} variant='ghost'>
        Ghost
      </ShareButton>

      <ShareButton content={shareContent} variant='link'>
        Link
      </ShareButton>

      <ShareButton content={shareContent} variant='destructive'>
        Destructive
      </ShareButton>
    </View>
  );
}
```

#### Share Sizes

**Example:** Share buttons in different sizes

```tsx
// components/demo/share/share-sizes.tsx
import { ShareButton } from '@/components/ui/share';
import { View } from '@/components/ui/view';
import React from 'react';

export function ShareSizes() {
  const shareContent = {
    message: 'Check out this amazing content!',
    url: 'https://example.com',
  };

  return (
    <View style={{ gap: 12, alignItems: 'center' }}>
      <ShareButton content={shareContent} size='sm'>
        Small
      </ShareButton>

      <ShareButton content={shareContent} size='default'>
        Default
      </ShareButton>

      <ShareButton content={shareContent} size='lg'>
        Large
      </ShareButton>

      <ShareButton content={shareContent} size='icon' iconSize={20} />
    </View>
  );
}
```

#### URL Only

**Example:** Share button for sharing URLs without additional text

```tsx
// components/demo/share/share-url-only.tsx
import { ShareButton } from '@/components/ui/share';
import { View } from '@/components/ui/view';
import React from 'react';

export function ShareUrlOnly() {
  return (
    <View style={{ gap: 12 }}>
      <ShareButton content={{ url: 'https://github.com' }} variant='outline'>
        Share GitHub
      </ShareButton>

      <ShareButton
        content={{ url: 'https://reactnative.dev' }}
        variant='secondary'
      >
        Share React Native Docs
      </ShareButton>

      <ShareButton content={{ url: 'https://expo.dev' }} variant='ghost'>
        Share Expo
      </ShareButton>
    </View>
  );
}
```

#### Custom Content

**Example:** Share button with custom title, subject, and content

```tsx
// components/demo/share/share-custom-content.tsx
import { ShareButton } from '@/components/ui/share';
import { View } from '@/components/ui/view';
import React from 'react';

export function ShareCustomContent() {
  return (
    <View style={{ gap: 12 }}>
      {/* Rich content with title and subject */}
      <ShareButton
        content={{
          message:
            'I found this amazing article about React Native development. You should definitely check it out!',
          url: 'https://reactnative.dev/blog',
          title: 'React Native Blog',
          subject: 'Great React Native Article',
        }}
        options={{
          dialogTitle: 'Share this article',
        }}
      >
        Share Article
      </ShareButton>

      {/* App promotion */}
      <ShareButton
        content={{
          message:
            '🚀 Just discovered this incredible mobile app! The UI is amazing and it works perfectly on both iOS and Android. Download it now!',
          url: 'https://apps.apple.com/app/example',
          title: 'Amazing Mobile App',
          subject: 'You need to try this app!',
        }}
        variant='secondary'
      >
        Share App
      </ShareButton>

      {/* Event invitation */}
      <ShareButton
        content={{
          message:
            "🎉 You're invited to our tech meetup! Join us for an evening of networking, learning, and great discussions about mobile development.",
          url: 'https://meetup.com/event/123',
          title: 'Tech Meetup Invitation',
          subject: 'Join us at the Tech Meetup!',
        }}
        variant='outline'
      >
        Share Event
      </ShareButton>
    </View>
  );
}
```

#### Icon Only

**Example:** Compact share button with icon only

```tsx
// components/demo/share/share-icon-only.tsx
import { ShareButton } from '@/components/ui/share';
import { View } from '@/components/ui/view';
import React from 'react';

export function ShareIconOnly() {
  const shareContent = {
    message: 'Check out this amazing content!',
    url: 'https://example.com',
  };

  return (
    <View style={{ flexDirection: 'row', gap: 8 }}>
      <ShareButton
        content={shareContent}
        size='icon'
        variant='default'
        iconSize={18}
      />

      <ShareButton
        content={shareContent}
        size='icon'
        variant='secondary'
        iconSize={18}
      />

      <ShareButton
        content={shareContent}
        size='icon'
        variant='outline'
        iconSize={18}
      />

      <ShareButton
        content={shareContent}
        size='icon'
        variant='ghost'
        iconSize={20}
      />
    </View>
  );
}
```

#### With Callbacks

**Example:** Share button with success, error, and dismiss callbacks

```tsx
// components/demo/share/share-callbacks.tsx
import { ShareButton } from '@/components/ui/share';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function ShareCallbacks() {
  const [status, setStatus] = useState<string>('Ready to share');
  const [isLoading, setIsLoading] = useState(false);

  const handleShareStart = () => {
    setStatus('Starting share...');
    setIsLoading(true);
  };

  const handleShareSuccess = (activityType?: string | null) => {
    setStatus(
      `Shared successfully${activityType ? ` via ${activityType}` : ''}!`
    );
    setIsLoading(false);

    // Reset status after 3 seconds
    setTimeout(() => setStatus('Ready to share'), 3000);
  };

  const handleShareError = (error: Error) => {
    setStatus(`Share failed: ${error.message}`);
    setIsLoading(false);

    // Reset status after 3 seconds
    setTimeout(() => setStatus('Ready to share'), 3000);
  };

  const handleShareDismiss = () => {
    setStatus('Share cancelled');
    setIsLoading(false);

    // Reset status after 2 seconds
    setTimeout(() => setStatus('Ready to share'), 2000);
  };

  return (
    <View style={{ gap: 16 }}>
      <Text style={{ fontWeight: '500' }}>Status: {status}</Text>

      <ShareButton
        content={{
          message: 'Check out this awesome React Native component library!',
          url: 'https://github.com/ahmedbna/ui',
          title: 'UI Component Library',
        }}
        loading={isLoading}
        onShareStart={handleShareStart}
        onShareSuccess={handleShareSuccess}
        onShareError={handleShareError}
        onShareDismiss={handleShareDismiss}
      >
        Share with Callbacks
      </ShareButton>
    </View>
  );
}
```

#### Hook Usage

**Example:** Using the useShare hook for programmatic sharing

```tsx
// components/demo/share/share-hook.tsx
import { Button } from '@/components/ui/button';
import { useShare } from '@/components/ui/share';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function ShareHook() {
  const { shareText, shareUrl, shareContent } = useShare();
  const [status, setStatus] = useState<string>('Choose a sharing method');

  const handleShareText = async () => {
    try {
      setStatus('Sharing text...');
      await shareText(
        'Hello from the useShare hook! This is just a plain text message.'
      );
      setStatus('Text shared successfully!');
    } catch (error) {
      setStatus(`Failed to share text: ${(error as Error).message}`);
    }
  };

  const handleShareUrl = async () => {
    try {
      setStatus('Sharing URL...');
      await shareUrl(
        'https://reactnative.dev',
        'Check out the official React Native documentation!'
      );
      setStatus('URL shared successfully!');
    } catch (error) {
      setStatus(`Failed to share URL: ${(error as Error).message}`);
    }
  };

  const handleShareContent = async () => {
    try {
      setStatus('Sharing content...');
      await shareContent({
        message:
          '🚀 Just built an amazing React Native app with this component library!',
        url: 'https://github.com/ahmedbna/ui',
        title: 'Amazing UI Library',
        subject: 'Check out this UI library',
      });
      setStatus('Content shared successfully!');
    } catch (error) {
      setStatus(`Failed to share content: ${(error as Error).message}`);
    }
  };

  return (
    <View style={{ gap: 16 }}>
      <Text style={{ fontWeight: '500', textAlign: 'center' }}>{status}</Text>

      <View style={{ gap: 8 }}>
        <Button onPress={handleShareText} variant='outline'>
          Share Text Only
        </Button>

        <Button onPress={handleShareUrl} variant='secondary'>
          Share URL with Message
        </Button>

        <Button onPress={handleShareContent} variant='default'>
          Share Rich Content
        </Button>
      </View>
    </View>
  );
}
```

## API Reference

### ShareButton

The main share button component that handles native sharing functionality.

| Prop              | Type                                  | Default     | Description                                       |
| ----------------- | ------------------------------------- | ----------- | ------------------------------------------------- |
| `content`         | `ShareContent`                        | -           | **Required.** The content to be shared.           |
| `options`         | `ShareButtonOptions`                  | -           | Platform-specific sharing options.                |
| `children`        | `ReactNode`                           | -           | Button content. Shows share icon if not provided. |
| `variant`         | `ButtonVariant`                       | `'default'` | Button visual variant.                            |
| `size`            | `'default' \| 'sm' \| 'lg' \| 'icon'` | `'default'` | Button size.                                      |
| `disabled`        | `boolean`                             | `false`     | Whether the button is disabled.                   |
| `loading`         | `boolean`                             | `false`     | Whether the button is in loading state.           |
| `onShareStart`    | `() => void`                          | -           | Callback when sharing starts.                     |
| `onShareSuccess`  | `(activityType?: string) => void`     | -           | Callback when sharing succeeds.                   |
| `onShareError`    | `(error: Error) => void`              | -           | Callback when sharing fails.                      |
| `onShareDismiss`  | `() => void`                          | -           | Callback when share dialog is dismissed.          |
| `showIcon`        | `boolean`                             | `true`      | Whether to show the share icon.                   |
| `iconSize`        | `number`                              | `18`        | Size of the share icon.                           |
| `validateContent` | `boolean`                             | `true`      | Whether to validate content before sharing.       |
| `testID`          | `string`                              | -           | Test identifier for testing.                      |

### ShareContent

The content object that defines what will be shared.

| Property  | Type     | Description                                    |
| --------- | -------- | ---------------------------------------------- |
| `message` | `string` | The text message to share.                     |
| `url`     | `string` | The URL to share.                              |
| `title`   | `string` | The title for the shared content (iOS only).   |
| `subject` | `string` | The subject line for email sharing (iOS only). |

### ShareButtonOptions

Platform-specific options for customizing the share dialog.

| Property                | Type       | Description                                        |
| ----------------------- | ---------- | -------------------------------------------------- |
| `dialogTitle`           | `string`   | Title for the share dialog (Android only).         |
| `excludedActivityTypes` | `string[]` | Activity types to exclude from sharing (iOS only). |
| `tintColor`             | `string`   | Tint color for the share sheet (iOS only).         |
| `anchor`                | `number`   | Anchor point for iPad popover (iOS only).          |

### useShare Hook

A hook that provides programmatic sharing functions without UI components.

#### Returns

| Function       | Type                                                                            | Description                        |
| -------------- | ------------------------------------------------------------------------------- | ---------------------------------- |
| `shareText`    | `(text: string, options?: ShareButtonOptions) => Promise<any>`                  | Share plain text.                  |
| `shareUrl`     | `(url: string, message?: string, options?: ShareButtonOptions) => Promise<any>` | Share a URL with optional message. |
| `shareContent` | `(content: ShareContent, options?: ShareButtonOptions) => Promise<any>`         | Share complex content object.      |

## Platform Differences

### iOS

- Supports `title` and `subject` properties in share content
- Supports `excludedActivityTypes`, `tintColor`, and `anchor` options
- Share sheet appears as a modal from the bottom
- On iPad, can be anchored to a specific point

### Android

- Only supports `message` and `url` in share content
- Supports `dialogTitle` option for customizing dialog title
- Share dialog appears as a bottom sheet with available apps

## Error Handling

The ShareButton component includes comprehensive error handling:

- **Invalid Content**: Validates that either `message` or `url` is provided
- **Network Errors**: Handles network-related sharing failures
- **Permission Errors**: Handles cases where sharing permissions are denied
- **Unsupported Platform**: Handles devices that don't support sharing

Error messages are user-friendly and provide actionable feedback.

## Accessibility

The Share component is built with accessibility in mind:

- Uses semantic button structure for screen readers
- Provides appropriate ARIA labels and roles
- Supports dynamic text sizing
- Maintains proper focus management
- Includes loading states for better user feedback

## Best Practices

1. **Content Validation**: Always provide either a `message` or `url` in your share content
2. **Error Handling**: Implement `onShareError` callback to handle sharing failures gracefully
3. **Loading States**: Use the `loading` prop during async operations before sharing
4. **Platform Testing**: Test sharing functionality on both iOS and Android devices
5. **Fallback Options**: Consider providing alternative sharing methods if native sharing fails
