# Action Sheet

> A native-feeling action sheet component that provides a menu of options triggered from the bottom of the screen.

**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/action-sheet
- Markdown: https://ui.ahmedbna.com/docs/components/action-sheet.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/action-sheet.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/action-sheet.json
- Install: `npx bna-ui add action-sheet`
- npm dependencies: `expo-haptics`, `react-native-reanimated`, `react-native-safe-area-context`, `react-native-worklets`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `useHaptics`, `globals`, `text`, `view`
- Preview recording: https://demo.ahmedbna.com/0007-action-sheet-demo.MP4

---

**Example:** A basic action sheet with multiple options

```tsx
// components/demo/action-sheet/action-sheet-demo.tsx
import { ActionSheet } from '@/components/ui/action-sheet';
import { Button } from '@/components/ui/button';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function ActionSheetDemo() {
  const [visible, setVisible] = useState(false);

  const options = [
    {
      title: 'Edit',
      onPress: () => console.log('Edit pressed'),
    },
    {
      title: 'Share',
      onPress: () => console.log('Share pressed'),
    },
    {
      title: 'Delete',
      onPress: () => console.log('Delete pressed'),
      destructive: true,
    },
  ];

  return (
    <View>
      <Button onPress={() => setVisible(true)}>Show Action Sheet</Button>
      <ActionSheet
        visible={visible}
        onClose={() => setVisible(false)}
        title='Choose an action'
        message='Select one of the options below'
        options={options}
      />
    </View>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add action-sheet
```

### Manual

**1.** Install the following dependencies:

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

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

```tsx
// components/ui/action-sheet.tsx
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { useHaptics } from '@/hooks/useHaptics';
import { BORDER_RADIUS, FONT_SIZE } from '@/theme/globals';
import React, { useEffect, useState } from 'react';
import {
  ActionSheetIOS,
  Dimensions,
  Modal,
  Platform,
  Pressable,
  ScrollView,
  StyleSheet,
  TouchableOpacity,
  ViewStyle,
} from 'react-native';
import Animated, {
  Easing,
  interpolate,
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export interface ActionSheetOption {
  title: string;
  onPress: () => void;
  destructive?: boolean;
  disabled?: boolean;
  icon?: React.ReactNode;
}

interface ActionSheetProps {
  visible: boolean;
  onClose: () => void;
  title?: string;
  message?: string;
  options: ActionSheetOption[];
  cancelButtonTitle?: string;
  style?: ViewStyle;
  haptic?: boolean;
}

export function ActionSheet({
  visible,
  onClose,
  title,
  message,
  options,
  cancelButtonTitle = 'Cancel',
  style,
  haptic = true,
}: ActionSheetProps) {
  // Called above the platform branch below so the hook order stays stable.
  const feedback = useHaptics(haptic);

  // Use iOS native ActionSheet on iOS
  if (Platform.OS === 'ios') {
    useEffect(() => {
      if (visible) {
        const optionTitles = options.map((option) => option.title);
        const destructiveButtonIndex = options.findIndex(
          (option) => option.destructive
        );
        const disabledButtonIndices = options
          .map((option, index) => (option.disabled ? index : -1))
          .filter((index) => index !== -1);

        ActionSheetIOS.showActionSheetWithOptions(
          {
            title,
            message,
            options: [...optionTitles, cancelButtonTitle],
            cancelButtonIndex: optionTitles.length,
            destructiveButtonIndex:
              destructiveButtonIndex !== -1
                ? destructiveButtonIndex
                : undefined,
            disabledButtonIndices:
              disabledButtonIndices.length > 0
                ? disabledButtonIndices
                : undefined,
          },
          (buttonIndex) => {
            if (buttonIndex < optionTitles.length) {
              // ActionSheetIOS emits no haptic of its own.
              feedback(
                options[buttonIndex].destructive ? 'warning' : 'selection'
              );
              options[buttonIndex].onPress();
            }
            onClose();
          }
        );
      }
    }, [
      visible,
      title,
      message,
      options,
      cancelButtonTitle,
      onClose,
      feedback,
    ]);

    // Return null for iOS as we use the native ActionSheet
    return null;
  }

  // Custom implementation for Android and other platforms
  return (
    <AndroidActionSheet
      {...{
        visible,
        onClose,
        title,
        message,
        options,
        cancelButtonTitle,
        style,
        haptic,
      }}
    />
  );
}

// Custom ActionSheet implementation for Android using react-native-reanimated
function AndroidActionSheet({
  visible,
  onClose,
  title,
  message,
  options,
  cancelButtonTitle,
  style,
  haptic = true,
}: ActionSheetProps) {
  const [isSheetVisible, setIsSheetVisible] = useState(visible);
  const feedback = useHaptics(haptic);
  const progress = useSharedValue(0);
  const screenHeight = Dimensions.get('window').height;
  const insets = useSafeAreaInsets();

  const cardColor = useColor('card');
  const textColor = useColor('text');
  const mutedColor = useColor('textMuted');
  const borderColor = useColor('border');
  const destructiveColor = useColor('red');

  useEffect(() => {
    if (visible) {
      setIsSheetVisible(true);
      progress.value = withTiming(1, {
        duration: 300,
        easing: Easing.out(Easing.quad),
      });
    } else {
      // Animate out, then set the modal to invisible after the animation is done
      progress.value = withTiming(
        0,
        { duration: 250, easing: Easing.in(Easing.quad) },
        (finished) => {
          if (finished) {
            runOnJS(setIsSheetVisible)(false);
          }
        }
      );
    }
  }, [visible, progress]);

  // Animated style for the backdrop
  const backdropAnimatedStyle = useAnimatedStyle(() => ({
    opacity: progress.value,
  }));

  // Animated style for the sheet itself (slide up/down)
  const sheetAnimatedStyle = useAnimatedStyle(() => {
    const translateY = interpolate(progress.value, [0, 1], [screenHeight, 0]);
    return {
      transform: [{ translateY }],
    };
  });

  const handleOptionPress = (option: ActionSheetOption) => {
    if (!option.disabled) {
      feedback(option.destructive ? 'warning' : 'selection');
      option.onPress();
      onClose();
    }
  };

  // Dismissing is not a selection, so it stays silent.
  const handleBackdropPress = () => {
    onClose();
  };

  // Render null if the sheet is not supposed to be visible
  if (!isSheetVisible) {
    return null;
  }

  return (
    <Modal
      transparent
      visible={isSheetVisible}
      animationType='none'
      statusBarTranslucent
      onRequestClose={onClose}
    >
      <View style={styles.container}>
        <Animated.View style={[styles.backdrop, backdropAnimatedStyle]}>
          <Pressable
            style={styles.backdropPressable}
            onPress={handleBackdropPress}
          />
        </Animated.View>

        <Animated.View
          style={[
            styles.sheet,
            {
              backgroundColor: cardColor,
              paddingBottom: Math.max(insets.bottom, 16),
            },
            sheetAnimatedStyle,
            style,
          ]}
        >
          {/* Header */}
          {(title || message) && (
            <View style={styles.header}>
              {title && (
                <Text
                  style={[styles.title, { color: textColor }]}
                  numberOfLines={2}
                >
                  {title}
                </Text>
              )}
              {message && (
                <Text
                  style={[styles.message, { color: mutedColor }]}
                  numberOfLines={3}
                >
                  {message}
                </Text>
              )}
            </View>
          )}

          {/* Options */}
          <ScrollView
            style={styles.optionsContainer}
            showsVerticalScrollIndicator={false}
          >
            {options.map((option, index) => (
              <TouchableOpacity
                key={index}
                style={[
                  styles.option,
                  { borderBottomColor: borderColor },
                  index === options.length - 1 && styles.lastOption,
                  option.disabled && styles.disabledOption,
                ]}
                onPress={() => handleOptionPress(option)}
                disabled={option.disabled}
                activeOpacity={0.6}
                accessibilityRole='menuitem'
                accessibilityState={{ disabled: option.disabled }}
                accessibilityLabel={option.title}
              >
                <View style={styles.optionContent}>
                  {option.icon && (
                    <View style={styles.optionIcon}>{option.icon}</View>
                  )}
                  <Text
                    style={[
                      styles.optionText,
                      {
                        color: option.destructive
                          ? destructiveColor
                          : option.disabled
                            ? mutedColor
                            : textColor,
                      },
                    ]}
                    numberOfLines={1}
                  >
                    {option.title}
                  </Text>
                </View>
              </TouchableOpacity>
            ))}
          </ScrollView>

          {/* Cancel Button */}
          <View
            style={[styles.cancelContainer, { borderTopColor: borderColor }]}
          >
            <TouchableOpacity
              style={styles.cancelButton}
              onPress={onClose}
              activeOpacity={0.6}
              accessibilityRole='button'
              accessibilityLabel={cancelButtonTitle}
            >
              <Text style={[styles.cancelText, { color: textColor }]}>
                {cancelButtonTitle}
              </Text>
            </TouchableOpacity>
          </View>
        </Animated.View>
      </View>
    </Modal>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'flex-end',
  },
  backdrop: {
    ...StyleSheet.absoluteFill,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
  },
  backdropPressable: {
    flex: 1,
  },
  sheet: {
    borderTopLeftRadius: BORDER_RADIUS,
    borderTopRightRadius: BORDER_RADIUS,
    maxHeight: '80%',
    elevation: 10,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: -2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 10,
  },
  header: {
    paddingHorizontal: 20,
    paddingTop: 20,
    paddingBottom: 16,
    alignItems: 'center',
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    textAlign: 'center',
    marginBottom: 4,
  },
  message: {
    fontSize: FONT_SIZE - 1,
    textAlign: 'center',
    lineHeight: 20,
  },
  optionsContainer: {
    maxHeight: 300,
  },
  option: {
    borderBottomWidth: StyleSheet.hairlineWidth,
    paddingHorizontal: 20,
    paddingVertical: 16,
  },
  lastOption: {
    borderBottomWidth: 0,
  },
  disabledOption: {
    opacity: 0.5,
  },
  optionContent: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  optionIcon: {
    marginRight: 12,
    width: 24,
    height: 24,
    alignItems: 'center',
    justifyContent: 'center',
  },
  optionText: {
    fontSize: FONT_SIZE,
    fontWeight: '500',
    flex: 1,
  },
  cancelContainer: {
    borderTopWidth: StyleSheet.hairlineWidth,
    marginTop: 8,
  },
  cancelButton: {
    paddingHorizontal: 20,
    paddingVertical: 16,
    alignItems: 'center',
  },
  cancelText: {
    fontSize: FONT_SIZE,
    fontWeight: '600',
  },
});

// Hook for easier ActionSheet usage (No changes needed here)
export function useActionSheet() {
  const [isVisible, setIsVisible] = React.useState(false);
  const [config, setConfig] = React.useState<
    Omit<ActionSheetProps, 'visible' | 'onClose'>
  >({
    options: [],
  });

  const show = React.useCallback(
    (actionSheetConfig: Omit<ActionSheetProps, 'visible' | 'onClose'>) => {
      setConfig(actionSheetConfig);
      setIsVisible(true);
    },
    []
  );

  const hide = React.useCallback(() => {
    setIsVisible(false);
  }, []);

  const ActionSheetComponent = React.useMemo(
    () => <ActionSheet visible={isVisible} onClose={hide} {...config} />,
    [isVisible, hide, config]
  );

  return {
    show,
    hide,
    ActionSheet: ActionSheetComponent,
    isVisible,
  };
}
```

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

## Usage

```tsx
import { ActionSheet, useActionSheet } from '@/components/ui/action-sheet';
```

### Basic Usage with Hook

```tsx
function MyComponent() {
  const { show, ActionSheet } = useActionSheet();

  const handleShowActionSheet = () => {
    show({
      title: 'Choose an option',
      options: [
        {
          title: 'Edit',
          onPress: () => console.log('Edit pressed'),
        },
        {
          title: 'Delete',
          onPress: () => console.log('Delete pressed'),
          destructive: true,
        },
      ],
    });
  };

  return (
    <>
      <Button onPress={handleShowActionSheet}>Show Action Sheet</Button>
      {ActionSheet}
    </>
  );
}
```

### Direct Component Usage

```tsx
<ActionSheet
  visible={isVisible}
  onClose={() => setIsVisible(false)}
  title='Choose an action'
  message='Select one of the options below'
  options={[
    {
      title: 'Share',
      onPress: handleShare,
      icon: <ShareIcon />,
    },
    {
      title: 'Delete',
      onPress: handleDelete,
      destructive: true,
    },
  ]}
/>
```

## Examples

#### Default

**Example:** A basic action sheet with multiple options and different styles

```tsx
// components/demo/action-sheet/action-sheet-demo.tsx
import { ActionSheet } from '@/components/ui/action-sheet';
import { Button } from '@/components/ui/button';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function ActionSheetDemo() {
  const [visible, setVisible] = useState(false);

  const options = [
    {
      title: 'Edit',
      onPress: () => console.log('Edit pressed'),
    },
    {
      title: 'Share',
      onPress: () => console.log('Share pressed'),
    },
    {
      title: 'Delete',
      onPress: () => console.log('Delete pressed'),
      destructive: true,
    },
  ];

  return (
    <View>
      <Button onPress={() => setVisible(true)}>Show Action Sheet</Button>
      <ActionSheet
        visible={visible}
        onClose={() => setVisible(false)}
        title='Choose an action'
        message='Select one of the options below'
        options={options}
      />
    </View>
  );
}
```

#### With Icons

**Example:** An action sheet with icons next to each option

```tsx
// components/demo/action-sheet/action-sheet-icons.tsx
import { ActionSheet } from '@/components/ui/action-sheet';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/ui/icon';
import { Download, Edit, Share, Trash2 } from 'lucide-react-native';
import React, { useState } from 'react';

export function ActionSheetIcons() {
  const [visible, setVisible] = useState(false);

  const options = [
    {
      title: 'Edit',
      onPress: () => console.log('Edit pressed'),
      icon: <Icon name={Edit} size={20} />,
    },
    {
      title: 'Share',
      onPress: () => console.log('Share pressed'),
      icon: <Icon name={Share} size={20} />,
    },
    {
      title: 'Download',
      onPress: () => console.log('Download pressed'),
      icon: <Icon name={Download} size={20} />,
    },
    {
      title: 'Delete',
      onPress: () => console.log('Delete pressed'),
      destructive: true,
      icon: <Icon name={Trash2} size={20} />,
    },
  ];

  return (
    <>
      <Button onPress={() => setVisible(true)}>
        Show Action Sheet with Icons
      </Button>
      <ActionSheet
        visible={visible}
        onClose={() => setVisible(false)}
        title='File Actions'
        options={options}
      />
    </>
  );
}
```

#### Destructive Actions

**Example:** An action sheet featuring destructive actions with appropriate styling

```tsx
// components/demo/action-sheet/action-sheet-destructive.tsx
import { ActionSheet } from '@/components/ui/action-sheet';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/ui/icon';
import { AlertTriangle, Trash2 } from 'lucide-react-native';
import React, { useState } from 'react';

export function ActionSheetDestructive() {
  const [visible, setVisible] = useState(false);

  const options = [
    {
      title: 'Remove from Library',
      onPress: () => console.log('Remove from library'),
      destructive: true,
    },
    {
      title: 'Delete Permanently',
      onPress: () => console.log('Delete permanently'),
      destructive: true,
      icon: <Icon name={Trash2} size={20} />,
    },
    {
      title: 'Report Content',
      onPress: () => console.log('Report content'),
      destructive: true,
      icon: <Icon name={AlertTriangle} size={20} />,
    },
  ];

  return (
    <>
      <Button variant='destructive' onPress={() => setVisible(true)}>
        Destructive Actions
      </Button>
      <ActionSheet
        visible={visible}
        onClose={() => setVisible(false)}
        title='Are you sure?'
        message='These actions cannot be undone'
        options={options}
      />
    </>
  );
}
```

#### Disabled Options

**Example:** An action sheet with some disabled options

```tsx
// components/demo/action-sheet/action-sheet-disabled.tsx
import { ActionSheet } from '@/components/ui/action-sheet';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/ui/icon';
import { Copy, Edit, Share, Trash2 } from 'lucide-react-native';
import React, { useState } from 'react';

export function ActionSheetDisabled() {
  const [visible, setVisible] = useState(false);

  const options = [
    {
      title: 'Edit',
      onPress: () => console.log('Edit pressed'),
      icon: <Icon name={Edit} size={20} />,
    },
    {
      title: 'Copy',
      onPress: () => console.log('Copy pressed'),
      icon: <Icon name={Copy} size={20} />,
      disabled: true,
    },
    {
      title: 'Share',
      onPress: () => console.log('Share pressed'),
      icon: <Icon name={Share} size={20} />,
      disabled: true,
    },
    {
      title: 'Delete',
      onPress: () => console.log('Delete pressed'),
      destructive: true,
      icon: <Icon name={Trash2} size={20} />,
    },
  ];

  return (
    <>
      <Button onPress={() => setVisible(true)}>
        Show with Disabled Options
      </Button>
      <ActionSheet
        visible={visible}
        onClose={() => setVisible(false)}
        title='Document Actions'
        message='Some actions are not available'
        options={options}
      />
    </>
  );
}
```

#### Custom Styling

**Example:** An action sheet with custom styling and branding

```tsx
// components/demo/action-sheet/action-sheet-styled.tsx
import { ActionSheet } from '@/components/ui/action-sheet';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/ui/icon';
import { Bookmark, Heart, Send, Star } from 'lucide-react-native';
import React, { useState } from 'react';

export function ActionSheetStyled() {
  const [visible, setVisible] = useState(false);

  const options = [
    {
      title: 'Add to Favorites',
      onPress: () => console.log('Add to favorites'),
      icon: <Icon name={Heart} size={20} color='#FF6B6B' />,
    },
    {
      title: 'Rate this Item',
      onPress: () => console.log('Rate item'),
      icon: <Icon name={Star} size={20} color='#FFD93D' />,
    },
    {
      title: 'Save for Later',
      onPress: () => console.log('Save for later'),
      icon: <Icon name={Bookmark} size={20} color='#4ECDC4' />,
    },
    {
      title: 'Share with Friends',
      onPress: () => console.log('Share with friends'),
      icon: <Icon name={Send} size={20} color='#45B7D1' />,
    },
  ];

  return (
    <>
      <Button variant='secondary' onPress={() => setVisible(true)}>
        Custom Styled Sheet
      </Button>
      <ActionSheet
        visible={visible}
        onClose={() => setVisible(false)}
        title='✨ Quick Actions'
        message='Choose how you want to interact with this item'
        options={options}
        cancelButtonTitle='Maybe Later'
        style={{
          borderTopLeftRadius: 24,
          borderTopRightRadius: 24,
        }}
      />
    </>
  );
}
```

#### Long Options List

**Example:** An action sheet with many options that scrolls

```tsx
// components/demo/action-sheet/action-sheet-long.tsx
import { ActionSheet } from '@/components/ui/action-sheet';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/ui/icon';
import {
  Archive,
  Bookmark,
  Copy,
  Download,
  Edit,
  EyeOff,
  Flag,
  Heart,
  Pin,
  Send,
  Share,
  Star,
  Trash2,
} from 'lucide-react-native';
import React, { useState } from 'react';

export function ActionSheetLong() {
  const [visible, setVisible] = useState(false);

  const options = [
    {
      title: 'Edit Document',
      onPress: () => console.log('Edit'),
      icon: <Icon name={Edit} size={20} />,
    },
    {
      title: 'Share',
      onPress: () => console.log('Share'),
      icon: <Icon name={Share} size={20} />,
    },
    {
      title: 'Download',
      onPress: () => console.log('Download'),
      icon: <Icon name={Download} size={20} />,
    },
    {
      title: 'Copy Link',
      onPress: () => console.log('Copy link'),
      icon: <Icon name={Copy} size={20} />,
    },
    {
      title: 'Archive',
      onPress: () => console.log('Archive'),
      icon: <Icon name={Archive} size={20} />,
    },
    {
      title: 'Pin to Top',
      onPress: () => console.log('Pin'),
      icon: <Icon name={Pin} size={20} />,
    },
    {
      title: 'Add to Favorites',
      onPress: () => console.log('Favorite'),
      icon: <Icon name={Heart} size={20} />,
    },
    {
      title: 'Rate & Review',
      onPress: () => console.log('Rate'),
      icon: <Icon name={Star} size={20} />,
    },
    {
      title: 'Bookmark',
      onPress: () => console.log('Bookmark'),
      icon: <Icon name={Bookmark} size={20} />,
    },
    {
      title: 'Send Message',
      onPress: () => console.log('Send message'),
      icon: <Icon name={Send} size={20} />,
    },
    {
      title: 'Hide from Feed',
      onPress: () => console.log('Hide'),
      icon: <Icon name={EyeOff} size={20} />,
    },
    {
      title: 'Report Issue',
      onPress: () => console.log('Report'),
      icon: <Icon name={Flag} size={20} />,
    },
    {
      title: 'Delete',
      onPress: () => console.log('Delete'),
      destructive: true,
      icon: <Icon name={Trash2} size={20} />,
    },
  ];

  return (
    <>
      <Button onPress={() => setVisible(true)}>Show Long List</Button>
      <ActionSheet
        visible={visible}
        onClose={() => setVisible(false)}
        title='All Actions'
        message='Scroll to see all available options'
        options={options}
      />
    </>
  );
}
```

#### With Hook

**Example:** Using the useActionSheet hook for easier management

```tsx
// components/demo/action-sheet/action-sheet-hook.tsx
import { useActionSheet } from '@/components/ui/action-sheet';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/ui/icon';
import { View } from '@/components/ui/view';
import { Camera, FileText, Image, Mic } from 'lucide-react-native';
import React from 'react';

export function ActionSheetHook() {
  const { show, ActionSheet } = useActionSheet();

  const showMediaOptions = () => {
    show({
      title: 'Add Media',
      message: 'Choose the type of media to add',
      options: [
        {
          title: 'Take Photo',
          onPress: () => console.log('Take photo'),
          icon: <Icon name={Camera} size={20} />,
        },
        {
          title: 'Choose from Gallery',
          onPress: () => console.log('Choose from gallery'),
          icon: <Icon name={Image} size={20} />,
        },
        {
          title: 'Record Audio',
          onPress: () => console.log('Record audio'),
          icon: <Icon name={Mic} size={20} />,
        },
        {
          title: 'Add Document',
          onPress: () => console.log('Add document'),
          icon: <Icon name={FileText} size={20} />,
        },
      ],
    });
  };

  const showConfirmation = () => {
    show({
      title: 'Confirm Action',
      message: 'This action cannot be undone',
      options: [
        {
          title: 'Yes, Continue',
          onPress: () => console.log('Confirmed'),
          destructive: true,
        },
      ],
    });
  };

  return (
    <View style={{ gap: 12 }}>
      <Button onPress={showMediaOptions}>Add Media</Button>
      <Button variant='outline' onPress={showConfirmation}>
        Show Confirmation
      </Button>
      {ActionSheet}
    </View>
  );
}
```

## API Reference

### ActionSheet

The main ActionSheet component.

| Prop                | Type                  | Default    | Description                                        |
| ------------------- | --------------------- | ---------- | -------------------------------------------------- |
| `visible`           | `boolean`             | -          | Controls the visibility of the action sheet.       |
| `onClose`           | `() => void`          | -          | Callback fired when the action sheet should close. |
| `title`             | `string`              | -          | Optional title displayed at the top.               |
| `message`           | `string`              | -          | Optional message displayed below the title.        |
| `options`           | `ActionSheetOption[]` | -          | Array of options to display in the action sheet.   |
| `cancelButtonTitle` | `string`              | `'Cancel'` | Text for the cancel button.                        |
| `style`             | `ViewStyle`           | -          | Additional styles for the action sheet container.  |

### ActionSheetOption

Configuration for individual action sheet options.

| Prop          | Type              | Default | Description                                         |
| ------------- | ----------------- | ------- | --------------------------------------------------- |
| `title`       | `string`          | -       | The text to display for this option.                |
| `onPress`     | `() => void`      | -       | Callback fired when this option is pressed.         |
| `destructive` | `boolean`         | `false` | Whether this option should use destructive styling. |
| `disabled`    | `boolean`         | `false` | Whether this option should be disabled.             |
| `icon`        | `React.ReactNode` | -       | Optional icon to display next to the title.         |

### useActionSheet Hook

A convenient hook for managing action sheet state.

#### Returns

| Property      | Type                                                               | Description                          |
| ------------- | ------------------------------------------------------------------ | ------------------------------------ |
| `show`        | `(config: Omit<ActionSheetProps, 'visible' \| 'onClose'>) => void` | Function to show the action sheet.   |
| `hide`        | `() => void`                                                       | Function to hide the action sheet.   |
| `ActionSheet` | `React.ReactElement`                                               | The ActionSheet component to render. |
| `isVisible`   | `boolean`                                                          | Current visibility state.            |

## Platform Behavior

### iOS

On iOS, the ActionSheet automatically uses the native `ActionSheetIOS` API, providing the familiar iOS action sheet experience with proper integration into the system UI.

### Android & Other Platforms

On Android and other platforms, a custom implementation is used that mimics the native behavior with smooth animations and proper theming support.

## Accessibility

The ActionSheet component follows accessibility best practices:

- Proper focus management when opened/closed
- Screen reader announcements for destructive actions
- Keyboard navigation support where applicable
- Respects system accessibility settings

## Animation

The ActionSheet includes smooth animations:

- Slide-up animation from bottom of screen
- Backdrop fade-in/out
- Spring-based animations for natural feel
- Respects reduced motion preferences

## Theming

The ActionSheet automatically adapts to your app's theme:

- Uses theme colors for background, text, and borders
- Supports both light and dark modes
- Destructive actions use theme's destructive color
- Disabled states respect theme opacity values
