# BottomSheet

> A modal sheet component that slides up from the bottom with gesture support and snap points.

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

---

**Example:** A basic bottom sheet with gesture support and snap points

```tsx
// components/demo/bottom-sheet/bottom-sheet-demo.tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function BottomSheetDemo() {
  const { isVisible, open, close } = useBottomSheet();

  return (
    <View>
      <Button onPress={open}>Open Bottom Sheet</Button>

      <BottomSheet
        isVisible={isVisible}
        onClose={close}
        snapPoints={[0.3, 0.6, 0.9]}
      >
        <View style={{ gap: 16 }}>
          <Text variant='title'>Welcome to Bottom Sheet</Text>
          <Text>
            This is a basic bottom sheet that supports gesture interactions. You
            can drag it up and down to different snap points, or swipe down
            quickly to dismiss it.
          </Text>
          <Button onPress={close}>Close</Button>
        </View>
      </BottomSheet>
    </View>
  );
}
```

## Installation

### CLI

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

### Manual

**1.** Install the following dependencies:

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

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

```tsx
// components/ui/bottom-sheet.tsx
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight'; // Make sure this path is correct
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React, { useEffect } from 'react';
import {
  Modal,
  ScrollView,
  TouchableWithoutFeedback,
  useWindowDimensions,
  ViewStyle,
} from 'react-native';
import {
  Gesture,
  GestureDetector,
  GestureHandlerRootView,
} from 'react-native-gesture-handler';
import Animated, {
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  withTiming,
} from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

type BottomSheetContentProps = {
  children: React.ReactNode;
  title?: string;
  style?: ViewStyle;
  rBottomSheetStyle: any;
  cardColor: string;
  mutedColor: string;
  screenHeight: number;
  onHandlePress?: () => void;
};

// Component for the bottom sheet content
// It now includes a ScrollView by default for better form handling.
const BottomSheetContent = ({
  children,
  title,
  style,
  rBottomSheetStyle,
  cardColor,
  mutedColor,
  screenHeight,
  onHandlePress,
}: BottomSheetContentProps) => {
  const insets = useSafeAreaInsets();

  return (
    <Animated.View
      style={[
        {
          height: screenHeight,
          width: '100%',
          position: 'absolute',
          top: screenHeight,
          backgroundColor: cardColor,
          borderTopLeftRadius: BORDER_RADIUS,
          borderTopRightRadius: BORDER_RADIUS,
        },
        rBottomSheetStyle,
        style,
      ]}
    >
      {/* Handle */}
      <TouchableWithoutFeedback onPress={onHandlePress}>
        <View
          style={{
            width: '100%',
            paddingVertical: 12,
            alignItems: 'center',
          }}
        >
          <View
            style={{
              width: 64,
              height: 6,
              backgroundColor: mutedColor,
              borderRadius: 999,
            }}
          />
        </View>
      </TouchableWithoutFeedback>

      {/* Title */}
      {title && (
        <View
          style={{
            marginHorizontal: 16,
            marginTop: 16,
            paddingBottom: 8,
          }}
        >
          <Text variant='title' style={{ textAlign: 'center' }}>
            {title}
          </Text>
        </View>
      )}

      {/* Content now wrapped in a ScrollView */}
      <ScrollView
        style={{ flex: 1 }}
        contentContainerStyle={{
          padding: 16,
          paddingBottom: Math.max(insets.bottom, 16),
        }}
        keyboardShouldPersistTaps='handled'
        showsVerticalScrollIndicator={false}
      >
        {children}
      </ScrollView>
    </Animated.View>
  );
};

type BottomSheetProps = {
  isVisible: boolean;
  onClose: () => void;
  children: React.ReactNode;
  snapPoints?: number[];
  enableBackdropDismiss?: boolean;
  title?: string;
  style?: ViewStyle;
  disablePanGesture?: boolean;
};

export function BottomSheet({
  isVisible,
  onClose,
  children,
  snapPoints = [0.3, 0.6, 0.9],
  enableBackdropDismiss = true,
  title,
  style,
  disablePanGesture = false,
}: BottomSheetProps) {
  const cardColor = useColor('card');
  const mutedColor = useColor('muted');
  const { keyboardHeight, isKeyboardVisible } = useKeyboardHeight();
  const { height: screenHeight } = useWindowDimensions();
  const maxTranslateY = -screenHeight + 50;

  const translateY = useSharedValue(0);
  const context = useSharedValue({ y: 0 });
  const opacity = useSharedValue(0);
  const currentSnapIndex = useSharedValue(0);
  // Shared value to hold keyboard height for use in worklets
  const keyboardHeightSV = useSharedValue(0);

  const snapPointsHeights = snapPoints.map((point) => -screenHeight * point);
  const defaultHeight = snapPointsHeights[0];

  const [modalVisible, setModalVisible] = React.useState(false);

  // Effect to handle opening and closing the bottom sheet
  useEffect(() => {
    if (isVisible) {
      setModalVisible(true);
      translateY.value = withSpring(defaultHeight, {
        damping: 50,
        stiffness: 400,
      });
      opacity.value = withTiming(1, { duration: 300 });
      currentSnapIndex.value = 0;
    } else {
      translateY.value = withSpring(0, { damping: 50, stiffness: 400 });
      opacity.value = withTiming(0, { duration: 300 }, (finished) => {
        if (finished) {
          runOnJS(setModalVisible)(false);
        }
      });
    }
  }, [isVisible, defaultHeight]);

  // Function to animate the sheet to a specific destination
  const scrollTo = (destination: number) => {
    'worklet';
    translateY.value = withSpring(destination, { damping: 50, stiffness: 400 });
  };

  // --- START: NEW KEYBOARD HANDLING LOGIC ---
  useEffect(() => {
    // Update the shared value whenever keyboardHeight changes
    keyboardHeightSV.value = keyboardHeight;

    // Only adjust position if the sheet is currently visible
    if (isVisible) {
      const currentSnapHeight = snapPointsHeights[currentSnapIndex.value];
      let destination: number;

      if (isKeyboardVisible) {
        // Keyboard is open, move sheet up by keyboard height
        destination = currentSnapHeight - keyboardHeight;
      } else {
        // Keyboard is closed, return to original snap point
        destination = currentSnapHeight;
      }
      scrollTo(destination);
    }
  }, [keyboardHeight, isKeyboardVisible, isVisible]);
  // --- END: NEW KEYBOARD HANDLING LOGIC ---

  const findClosestSnapPoint = (currentY: number) => {
    'worklet';
    // Adjust the currentY by the keyboard height to find the original snap point
    const adjustedY = currentY + keyboardHeightSV.value;

    let closest = snapPointsHeights[0];
    let minDistance = Math.abs(adjustedY - closest);
    let closestIndex = 0;

    for (let i = 0; i < snapPointsHeights.length; i++) {
      const snapPoint = snapPointsHeights[i];
      const distance = Math.abs(adjustedY - snapPoint);
      if (distance < minDistance) {
        minDistance = distance;
        closest = snapPoint;
        closestIndex = i;
      }
    }
    currentSnapIndex.value = closestIndex;
    return closest;
  };

  const handlePress = () => {
    const nextIndex = (currentSnapIndex.value + 1) % snapPointsHeights.length;
    currentSnapIndex.value = nextIndex;
    const destination = snapPointsHeights[nextIndex] - keyboardHeightSV.value;
    scrollTo(destination);
  };

  const animateClose = () => {
    'worklet';
    translateY.value = withSpring(0, { damping: 50, stiffness: 400 });
    opacity.value = withTiming(0, { duration: 300 }, (finished) => {
      if (finished) {
        runOnJS(onClose)();
      }
    });
  };

  const gesture = Gesture.Pan()
    .onStart(() => {
      context.value = { y: translateY.value };
    })
    .onUpdate((event) => {
      const newY = context.value.y + event.translationY;
      if (newY <= 0 && newY >= maxTranslateY) {
        translateY.value = newY;
      }
    })
    .onEnd((event) => {
      const currentY = translateY.value;
      const velocity = event.velocityY;

      if (velocity > 500 && currentY > -screenHeight * 0.2) {
        animateClose();
        return;
      }

      // Find the closest original snap point
      const closestSnapPoint = findClosestSnapPoint(currentY);
      // Calculate the final destination, accounting for the keyboard height
      const finalDestination = closestSnapPoint - keyboardHeightSV.value;
      scrollTo(finalDestination);
    });

  const rBottomSheetStyle = useAnimatedStyle(() => {
    return {
      transform: [{ translateY: translateY.value }],
    };
  });

  const rBackdropStyle = useAnimatedStyle(() => {
    return {
      opacity: opacity.value,
    };
  });

  const handleBackdropPress = () => {
    if (enableBackdropDismiss) {
      animateClose();
    }
  };

  return (
    <Modal
      visible={modalVisible}
      transparent
      statusBarTranslucent
      animationType='none'
    >
      <GestureHandlerRootView style={{ flex: 1 }}>
        <Animated.View
          style={[
            { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.8)' },
            rBackdropStyle,
          ]}
          accessibilityViewIsModal
        >
          <TouchableWithoutFeedback onPress={handleBackdropPress}>
            <Animated.View style={{ flex: 1 }} />
          </TouchableWithoutFeedback>

          {disablePanGesture ? (
            <BottomSheetContent
              children={children}
              title={title}
              style={style}
              rBottomSheetStyle={rBottomSheetStyle}
              cardColor={cardColor}
              mutedColor={mutedColor}
              screenHeight={screenHeight}
              onHandlePress={() => runOnJS(handlePress)()}
            />
          ) : (
            <GestureDetector gesture={gesture}>
              <BottomSheetContent
                children={children}
                title={title}
                style={style}
                rBottomSheetStyle={rBottomSheetStyle}
                cardColor={cardColor}
                mutedColor={mutedColor}
                screenHeight={screenHeight}
                onHandlePress={() => runOnJS(handlePress)()}
              />
            </GestureDetector>
          )}
        </Animated.View>
      </GestureHandlerRootView>
    </Modal>
  );
}

// Hook for managing bottom sheet state
export function useBottomSheet() {
  const [isVisible, setIsVisible] = React.useState(false);

  const open = React.useCallback(() => {
    setIsVisible(true);
  }, []);

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

  const toggle = React.useCallback(() => {
    setIsVisible((prev) => !prev);
  }, []);

  return {
    isVisible,
    open,
    close,
    toggle,
  };
}
```

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

**4.** Make sure to wrap your app with GestureHandlerRootView in your root layout.

```tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function RootLayout() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      {/* Your app content */}
    </GestureHandlerRootView>
  );
}
```

## Usage

```tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
```

```tsx
function MyComponent() {
  const { isVisible, open, close } = useBottomSheet();

  return (
    <>
      <Button onPress={open}>Open Bottom Sheet</Button>

      <BottomSheet
        isVisible={isVisible}
        onClose={close}
        title='Settings'
        snapPoints={[0.3, 0.6, 0.9]}
      >
        <Text>Your content here</Text>
      </BottomSheet>
    </>
  );
}
```

## Examples

#### Default

**Example:** A basic bottom sheet with gesture support and snap points

```tsx
// components/demo/bottom-sheet/bottom-sheet-demo.tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function BottomSheetDemo() {
  const { isVisible, open, close } = useBottomSheet();

  return (
    <View>
      <Button onPress={open}>Open Bottom Sheet</Button>

      <BottomSheet
        isVisible={isVisible}
        onClose={close}
        snapPoints={[0.3, 0.6, 0.9]}
      >
        <View style={{ gap: 16 }}>
          <Text variant='title'>Welcome to Bottom Sheet</Text>
          <Text>
            This is a basic bottom sheet that supports gesture interactions. You
            can drag it up and down to different snap points, or swipe down
            quickly to dismiss it.
          </Text>
          <Button onPress={close}>Close</Button>
        </View>
      </BottomSheet>
    </View>
  );
}
```

#### With Title

**Example:** Bottom sheet with a title header

```tsx
// components/demo/bottom-sheet/bottom-sheet-title.tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function BottomSheetTitle() {
  const { isVisible, open, close } = useBottomSheet();

  return (
    <View>
      <Button onPress={open}>Open Sheet with Title</Button>

      <BottomSheet
        isVisible={isVisible}
        onClose={close}
        title='Settings'
        snapPoints={[0.4, 0.7]}
      >
        <View style={{ gap: 16 }}>
          <Text>
            This bottom sheet includes a title in the header area. The title is
            centered and uses the theme's title text style.
          </Text>
          <Button variant='secondary' onPress={close}>
            Done
          </Button>
        </View>
      </BottomSheet>
    </View>
  );
}
```

#### Custom Snap Points

**Example:** Bottom sheet with custom snap point configurations

```tsx
// components/demo/bottom-sheet/bottom-sheet-snap-points.tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function BottomSheetSnapPoints() {
  const { isVisible, open, close } = useBottomSheet();

  return (
    <View>
      <Button onPress={open}>Custom Snap Points</Button>

      <BottomSheet
        isVisible={isVisible}
        onClose={close}
        title='Custom Heights'
        snapPoints={[0.2, 0.5, 0.8, 0.95]}
      >
        <View style={{ gap: 16 }}>
          <Text variant='title'>Multiple Snap Points</Text>
          <Text>
            This sheet has four different snap points: 20%, 50%, 80%, and 95% of
            screen height. Try dragging to see how it snaps to each position.
          </Text>
          <View style={{ gap: 12 }}>
            <Text variant='body'>Available heights:</Text>
            <Text>• 20% - Peek view</Text>
            <Text>• 50% - Medium height</Text>
            <Text>• 80% - Large view</Text>
            <Text>• 95% - Nearly fullscreen</Text>
          </View>
        </View>
      </BottomSheet>
    </View>
  );
}
```

#### Form Content

**Example:** Bottom sheet containing form elements and inputs

```tsx
// components/demo/bottom-sheet/bottom-sheet-form.tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function BottomSheetForm() {
  const { isVisible, open, close } = useBottomSheet();
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = () => {
    // Handle form submission
    console.log('Form submitted:', { name, email });
    close();
  };

  return (
    <View>
      <Button onPress={open}>Edit Profile</Button>

      <BottomSheet
        isVisible={isVisible}
        onClose={close}
        title='Edit Profile'
        snapPoints={[0.6, 0.8]}
        enableBackdropDismiss={false}
      >
        <View style={{ gap: 20 }}>
          <View style={{ gap: 12 }}>
            <Text variant='body'>Name</Text>
            <Input
              value={name}
              onChangeText={setName}
              variant='outline'
              placeholder='Enter your name'
            />
          </View>

          <View style={{ gap: 12 }}>
            <Text variant='body'>Email</Text>
            <Input
              value={email}
              onChangeText={setEmail}
              variant='outline'
              placeholder='Enter your email'
              keyboardType='email-address'
            />
          </View>

          <View
            style={{
              flex: 1,
              width: '100%',
              flexDirection: 'row',
              gap: 12,
              marginTop: 12,
            }}
          >
            <Button variant='outline' onPress={close} style={{ flex: 1 }}>
              Cancel
            </Button>
            <Button onPress={handleSubmit} style={{ flex: 2 }}>
              Save
            </Button>
          </View>
        </View>
      </BottomSheet>
    </View>
  );
}
```

#### List Content

**Example:** Bottom sheet with scrollable list content

```tsx
// components/demo/bottom-sheet/bottom-sheet-list.tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';
import { FlatList, TouchableOpacity } from 'react-native';

const items = [
  { id: '1', title: 'Photos', subtitle: '1,234 items' },
  { id: '2', title: 'Videos', subtitle: '56 items' },
  { id: '3', title: 'Documents', subtitle: '89 items' },
  { id: '4', title: 'Audio', subtitle: '23 items' },
  { id: '5', title: 'Downloads', subtitle: '12 items' },
  { id: '6', title: 'Archives', subtitle: '4 items' },
];

export function BottomSheetList() {
  const { isVisible, open, close } = useBottomSheet();

  const renderItem = ({ item }: { item: (typeof items)[0] }) => (
    <TouchableOpacity
      style={{
        padding: 16,
        borderBottomWidth: 1,
        borderBottomColor: 'rgba(0,0,0,0.1)',
      }}
      onPress={() => console.log('Selected:', item.title)}
    >
      <Text variant='body' style={{ fontWeight: '600' }}>
        {item.title}
      </Text>
      <Text variant='caption' style={{ marginTop: 2 }}>
        {item.subtitle}
      </Text>
    </TouchableOpacity>
  );

  return (
    <View>
      <Button onPress={open}>Browse Files</Button>

      <BottomSheet
        isVisible={isVisible}
        onClose={close}
        title='File Browser'
        snapPoints={[0.5, 0.8]}
      >
        <FlatList
          data={items}
          renderItem={renderItem}
          keyExtractor={(item) => item.id}
          showsVerticalScrollIndicator={false}
        />
      </BottomSheet>
    </View>
  );
}
```

#### No Backdrop Dismiss

**Example:** Bottom sheet that cannot be dismissed by tapping backdrop

```tsx
// components/demo/bottom-sheet/bottom-sheet-no-dismiss.tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function BottomSheetNoDismiss() {
  const { isVisible, open, close } = useBottomSheet();

  return (
    <View>
      <Button onPress={open}>Important Action</Button>

      <BottomSheet
        isVisible={isVisible}
        onClose={close}
        title='Confirm Action'
        snapPoints={[0.4]}
        enableBackdropDismiss={false}
      >
        <View style={{ gap: 16 }}>
          <Text>
            This bottom sheet cannot be dismissed by tapping the backdrop. You
            must use one of the action buttons below.
          </Text>
          <Text variant='caption' style={{ fontStyle: 'italic' }}>
            This is useful for critical confirmations or required actions.
          </Text>
          <View style={{ flexDirection: 'row', gap: 12, marginTop: 12 }}>
            <Button variant='outline' onPress={close} style={{ flex: 1 }}>
              Cancel
            </Button>
            <Button variant='destructive' onPress={close} style={{ flex: 1 }}>
              Confirm
            </Button>
          </View>
        </View>
      </BottomSheet>
    </View>
  );
}
```

#### Custom Styling

**Example:** Bottom sheet with custom styling and colors

```tsx
// components/demo/bottom-sheet/bottom-sheet-styled.tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import React from 'react';

export function BottomSheetStyled() {
  const { isVisible, open, close } = useBottomSheet();
  const accentColor = useColor('blue');

  return (
    <View>
      <Button onPress={open}>Styled Sheet</Button>

      <BottomSheet
        isVisible={isVisible}
        onClose={close}
        title='Custom Styling'
        snapPoints={[0.5, 0.8]}
        style={{
          borderTopWidth: 3,
          borderTopColor: accentColor,
        }}
      >
        <View style={{ gap: 16 }}>
          <View
            style={{
              backgroundColor: accentColor + '20',
              padding: 16,
              borderRadius: 12,
            }}
          >
            <Text variant='title' style={{ color: accentColor }}>
              Premium Feature
            </Text>
            <Text style={{ marginTop: 8 }}>
              This bottom sheet has custom styling including a colored border
              and accent-colored content areas.
            </Text>
          </View>

          <Button variant='success' onPress={close}>
            Get Started
          </Button>
        </View>
      </BottomSheet>
    </View>
  );
}
```

#### Menu Options

**Example:** Bottom sheet used as a menu with action items

```tsx
// components/demo/bottom-sheet/bottom-sheet-menu.tsx
import { BottomSheet, useBottomSheet } from '@/components/ui/bottom-sheet';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { TouchableOpacity } from 'react-native';

const menuItems = [
  { id: 'edit', title: 'Edit', icon: '✏️' },
  { id: 'share', title: 'Share', icon: '📤' },
  { id: 'copy', title: 'Copy Link', icon: '🔗' },
  { id: 'bookmark', title: 'Bookmark', icon: '🔖' },
  { id: 'delete', title: 'Delete', icon: '🗑️', destructive: true },
];

export function BottomSheetMenu() {
  const { isVisible, open, close } = useBottomSheet();

  const textColor = useColor('text');
  const destructiveColor = useColor('destructive');

  const handleMenuAction = (action: string) => {
    console.log('Menu action:', action);
    close();
  };

  return (
    <View>
      <Button onPress={open}>Show Menu</Button>

      <BottomSheet isVisible={isVisible} onClose={close} snapPoints={[0.4]}>
        <View>
          {menuItems.map((item, index) => (
            <TouchableOpacity
              key={item.id}
              style={{
                flexDirection: 'row',
                alignItems: 'center',
                padding: 16,
                borderBottomWidth: index < menuItems.length - 1 ? 1 : 0,
                borderBottomColor: 'rgba(0,0,0,0.1)',
              }}
              onPress={() => handleMenuAction(item.id)}
            >
              <Text style={{ fontSize: 20, marginRight: 16 }}>{item.icon}</Text>
              <Text
                variant='body'
                style={{
                  flex: 1,
                  color: item.destructive ? destructiveColor : textColor,
                }}
              >
                {item.title}
              </Text>
            </TouchableOpacity>
          ))}
        </View>
      </BottomSheet>
    </View>
  );
}
```

## API Reference

### BottomSheet

The main bottom sheet component that provides a modal interface sliding from the bottom.

| Prop                    | Type        | Default           | Description                                               |
| ----------------------- | ----------- | ----------------- | --------------------------------------------------------- |
| `isVisible`             | `boolean`   | -                 | Controls the visibility of the bottom sheet.              |
| `onClose`               | `function`  | -                 | Callback function called when the sheet is closed.        |
| `children`              | `ReactNode` | -                 | The content to display inside the bottom sheet.           |
| `snapPoints`            | `number[]`  | `[0.3, 0.6, 0.9]` | Array of snap points as percentages of screen height.     |
| `enableBackdropDismiss` | `boolean`   | `true`            | Whether tapping the backdrop should dismiss the sheet.    |
| `title`                 | `string`    | -                 | Optional title to display at the top of the sheet.        |
| `style`                 | `ViewStyle` | -                 | Additional styles to apply to the bottom sheet container. |

### useBottomSheet Hook

A custom hook that provides state management for the bottom sheet.

```tsx
const { isVisible, open, close, toggle } = useBottomSheet();
```

#### Returns

| Property    | Type       | Description                              |
| ----------- | ---------- | ---------------------------------------- |
| `isVisible` | `boolean`  | Current visibility state of the sheet.   |
| `open`      | `function` | Function to open the bottom sheet.       |
| `close`     | `function` | Function to close the bottom sheet.      |
| `toggle`    | `function` | Function to toggle the sheet visibility. |

## Gesture Support

The BottomSheet component includes built-in gesture support:

- **Pan Gesture**: Drag the sheet up and down to resize
- **Snap Points**: The sheet will snap to predefined heights
- **Velocity Detection**: Fast downward swipes will close the sheet
- **Boundary Limits**: Prevents dragging beyond defined limits

## Snap Points

Snap points define the available heights for the bottom sheet as percentages of screen height:

- `0.3` = 30% of screen height
- `0.6` = 60% of screen height
- `0.9` = 90% of screen height

The sheet will automatically snap to the nearest point when gestures end.

## Animation

The component uses React Native Reanimated for smooth animations:

- **Spring Animation**: Natural feeling spring animations for opening/closing
- **Gesture Responsiveness**: Real-time tracking of pan gestures
- **Backdrop Fade**: Smooth opacity transitions for the backdrop

## Accessibility

The BottomSheet component includes accessibility features:

- **Modal Semantics**: Proper modal behavior for screen readers
- **Focus Management**: Traps focus within the sheet when open
- **Gesture Alternatives**: Provides non-gesture ways to interact
- **Backdrop Dismiss**: Can be disabled for better accessibility control

## Best Practices

1. **Content Height**: Ensure your content works well with different snap points
2. **Backdrop Dismiss**: Consider disabling for critical actions
3. **Loading States**: Show loading indicators for async content
4. **Error Handling**: Provide clear error states within the sheet
5. **Keyboard Handling**: Account for keyboard appearance with form content
