# Popover

> A contextual overlay that displays rich content triggered by user interaction.

**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/popover
- Markdown: https://ui.ahmedbna.com/docs/components/popover.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/popover.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/popover.json
- Install: `npx bna-ui add popover`
- 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/0219-popover-demo.MP4

---

**Example:** A basic popover with trigger button and content

```tsx
// components/demo/popover/popover-demo.tsx
import { Button } from '@/components/ui/button';
import {
  Popover,
  PopoverBody,
  PopoverClose,
  PopoverContent,
  PopoverFooter,
  PopoverHeader,
  PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import React from 'react';

export function PopoverDemo() {
  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button>Open Popover</Button>
      </PopoverTrigger>
      <PopoverContent>
        <PopoverHeader>
          <Text variant='title'>Popover Title</Text>
        </PopoverHeader>
        <PopoverBody>
          <Text>
            This is the popover content. You can put any content here.
          </Text>
        </PopoverBody>
        <PopoverFooter>
          <PopoverClose>
            <Button variant='outline' size='sm'>
              Close
            </Button>
          </PopoverClose>
        </PopoverFooter>
      </PopoverContent>
    </Popover>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add popover
```

### Manual

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

```tsx
// components/ui/popover.tsx
import { Button } from '@/components/ui/button';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React, {
  createContext,
  ReactNode,
  useContext,
  useEffect,
  useRef,
  useState,
} from 'react';
import {
  Dimensions,
  Modal,
  Pressable,
  StyleSheet,
  TouchableOpacity,
  View,
  ViewStyle,
} from 'react-native';

// Context for sharing state between popover components
interface PopoverContextType {
  isOpen: boolean;
  setIsOpen: (open: boolean) => void;
  triggerLayout: { x: number; y: number; width: number; height: number };
  setTriggerLayout: (layout: any) => void;
}

const PopoverContext = createContext<PopoverContextType | undefined>(undefined);

const usePopover = () => {
  const context = useContext(PopoverContext);
  if (!context) {
    throw new Error('Popover components must be used within a Popover');
  }
  return context;
};

// Main Popover wrapper
interface PopoverProps {
  children: ReactNode;
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
}

export function Popover({
  children,
  open = false,
  onOpenChange,
}: PopoverProps) {
  const [isOpen, setIsOpenState] = useState(open);
  const [triggerLayout, setTriggerLayout] = useState({
    x: 0,
    y: 0,
    width: 0,
    height: 0,
  });

  // Sync with external open state
  useEffect(() => {
    setIsOpenState(open);
  }, [open]);

  const setIsOpen = (newOpen: boolean) => {
    setIsOpenState(newOpen);
    onOpenChange?.(newOpen);
  };

  return (
    <PopoverContext.Provider
      value={{
        isOpen,
        setIsOpen,
        triggerLayout,
        setTriggerLayout,
      }}
    >
      {children}
    </PopoverContext.Provider>
  );
}

// Popover Trigger
interface PopoverTriggerProps {
  children: ReactNode;
  asChild?: boolean;
  style?: ViewStyle;
}

export function PopoverTrigger({
  children,
  asChild = false,
  style,
}: PopoverTriggerProps) {
  const { setIsOpen, setTriggerLayout, isOpen } = usePopover();
  const triggerRef = useRef<React.ComponentRef<typeof TouchableOpacity>>(null);

  const measureTrigger = () => {
    if (triggerRef.current) {
      triggerRef.current.measure(
        (
          x: number,
          y: number,
          width: number,
          height: number,
          pageX: number,
          pageY: number
        ) => {
          setTriggerLayout({ x: pageX, y: pageY, width, height });
        }
      );
    }
  };

  const handlePress = () => {
    measureTrigger();
    setIsOpen(!isOpen);
  };

  if (asChild && React.isValidElement(children)) {
    // Clone the child and add onPress handler
    return React.cloneElement(children, {
      ref: triggerRef,
      onPress: handlePress,
      accessibilityRole: 'button',
      accessibilityState: { expanded: isOpen },
      style: [(children.props as any).style, style],
    } as any);
  }

  return (
    <Button
      ref={triggerRef}
      style={style}
      onPress={handlePress}
      accessibilityRole='button'
      accessibilityState={{ expanded: isOpen }}
    >
      {children}
    </Button>
  );
}

// Popover Content
interface PopoverContentProps {
  children: ReactNode;
  align?: 'start' | 'center' | 'end';
  side?: 'top' | 'right' | 'bottom' | 'left';
  sideOffset?: number;
  alignOffset?: number;
  style?: ViewStyle;
  maxWidth?: number;
  maxHeight?: number;
}

export function PopoverContent({
  children,
  align = 'center',
  side = 'bottom',
  sideOffset = 8,
  alignOffset = 0,
  style,
  maxWidth = 300,
  maxHeight = 400,
}: PopoverContentProps) {
  const { isOpen, setIsOpen, triggerLayout } = usePopover();
  const [contentSize, setContentSize] = useState({ width: 0, height: 0 });
  const popoverColor = useColor('popover');
  const borderColor = useColor('border');

  const handleClose = () => {
    setIsOpen(false);
  };

  // Calculate position based on side and align props
  const getPosition = () => {
    const screenDimensions = Dimensions.get('window');
    const { x, y, width, height } = triggerLayout;

    // Use actual content size if available, otherwise use maxWidth/maxHeight
    const contentWidth = contentSize.width || maxWidth;
    const contentHeight = Math.min(
      contentSize.height || maxHeight,
      screenDimensions.height * 0.8
    );

    let top = 0;
    let left = 0;
    let actualSide = side;

    // Initial position calculation based on preferred side
    switch (side) {
      case 'top':
        top = y - contentHeight - sideOffset;
        break;
      case 'bottom':
        top = y + height + sideOffset;
        break;
      case 'left':
        left = x - contentWidth - sideOffset;
        break;
      case 'right':
        left = x + width + sideOffset;
        break;
    }

    // Calculate alignment for vertical sides (top/bottom)
    if (side === 'top' || side === 'bottom') {
      switch (align) {
        case 'start':
          left = x + alignOffset;
          break;
        case 'center':
          left = x + width / 2 - contentWidth / 2 + alignOffset;
          break;
        case 'end':
          left = x + width - contentWidth + alignOffset;
          break;
      }
    }
    // Calculate alignment for horizontal sides (left/right)
    else {
      switch (align) {
        case 'start':
          top = y + alignOffset;
          break;
        case 'center':
          top = y + height / 2 - contentHeight / 2 + alignOffset;
          break;
        case 'end':
          top = y + height - contentHeight + alignOffset;
          break;
      }
    }

    // Screen boundary adjustments with side flipping
    const padding = 16;

    // Check if we need to flip sides due to space constraints
    if (side === 'top' && top < padding) {
      // Not enough space on top, try bottom
      const bottomSpace = screenDimensions.height - (y + height + sideOffset);
      if (bottomSpace >= contentHeight) {
        actualSide = 'bottom';
        top = y + height + sideOffset;
      } else {
        // Keep top but adjust position
        top = padding;
      }
    } else if (
      side === 'bottom' &&
      top + contentHeight > screenDimensions.height - padding
    ) {
      // Not enough space on bottom, try top
      const topSpace = y - sideOffset;
      if (topSpace >= contentHeight) {
        actualSide = 'top';
        top = y - contentHeight - sideOffset;
      } else {
        // Keep bottom but adjust position
        top = screenDimensions.height - contentHeight - padding;
      }
    } else if (side === 'left' && left < padding) {
      // Not enough space on left, try right
      const rightSpace = screenDimensions.width - (x + width + sideOffset);
      if (rightSpace >= contentWidth) {
        actualSide = 'right';
        left = x + width + sideOffset;
      } else {
        // Keep left but adjust position
        left = padding;
      }
    } else if (
      side === 'right' &&
      left + contentWidth > screenDimensions.width - padding
    ) {
      // Not enough space on right, try left
      const leftSpace = x - sideOffset;
      if (leftSpace >= contentWidth) {
        actualSide = 'left';
        left = x - contentWidth - sideOffset;
      } else {
        // Keep right but adjust position
        left = screenDimensions.width - contentWidth - padding;
      }
    }

    // Final boundary adjustments (without side flipping)
    if (left < padding) {
      left = padding;
    } else if (left + contentWidth > screenDimensions.width - padding) {
      left = screenDimensions.width - contentWidth - padding;
    }

    if (top < padding) {
      top = padding;
    } else if (top + contentHeight > screenDimensions.height - padding) {
      top = screenDimensions.height - contentHeight - padding;
    }

    return {
      top: Math.max(padding, top),
      left: Math.max(padding, left),
      maxWidth,
      maxHeight: Math.min(maxHeight, screenDimensions.height - 2 * padding),
      actualSide,
    };
  };

  const position = getPosition();

  const handleContentLayout = (event: any) => {
    const { width, height } = event.nativeEvent.layout;
    setContentSize({ width, height });
  };

  return (
    <Modal
      visible={isOpen}
      transparent
      animationType='fade'
      onRequestClose={handleClose}
    >
      <Pressable style={styles.overlay} onPress={handleClose}>
        <View
          style={[
            styles.content,
            {
              backgroundColor: popoverColor,
              borderColor: borderColor,
              top: position.top,
              left: position.left,
              maxWidth: position.maxWidth,
              maxHeight: position.maxHeight,
            },
            style,
          ]}
          onLayout={handleContentLayout}
          onStartShouldSetResponder={() => true}
          accessibilityViewIsModal
          accessibilityRole='menu'
        >
          {children}
        </View>
      </Pressable>
    </Modal>
  );
}

// Popover Header
interface PopoverHeaderProps {
  children: ReactNode;
  style?: ViewStyle;
}

export function PopoverHeader({ children, style }: PopoverHeaderProps) {
  const borderColor = useColor('border');

  return (
    <View style={[styles.header, { borderBottomColor: borderColor }, style]}>
      {children}
    </View>
  );
}

// Popover Body
interface PopoverBodyProps {
  children: ReactNode;
  style?: ViewStyle;
}

export function PopoverBody({ children, style }: PopoverBodyProps) {
  return <View style={[styles.body, style]}>{children}</View>;
}

// Popover Footer
interface PopoverFooterProps {
  children: ReactNode;
  style?: ViewStyle;
}

export function PopoverFooter({ children, style }: PopoverFooterProps) {
  const borderColor = useColor('border');

  return (
    <View style={[styles.footer, { borderTopColor: borderColor }, style]}>
      {children}
    </View>
  );
}

// Popover Close (utility component)
interface PopoverCloseProps {
  children: ReactNode;
  asChild?: boolean;
  style?: ViewStyle;
}

export function PopoverClose({
  children,
  asChild = false,
  style,
}: PopoverCloseProps) {
  const { setIsOpen } = usePopover();

  const handlePress = () => {
    setIsOpen(false);
  };

  if (asChild && React.isValidElement(children)) {
    return React.cloneElement(children, {
      onPress: handlePress,
      accessibilityRole: 'button',
      style: [(children.props as any).style, style],
    } as any);
  }

  return (
    <TouchableOpacity
      style={style}
      onPress={handlePress}
      activeOpacity={0.7}
      accessibilityRole='button'
    >
      {children}
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  overlay: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
  },
  content: {
    position: 'absolute',
    borderRadius: BORDER_RADIUS,
    borderWidth: 1,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 4,
    },
    shadowOpacity: 0.25,
    shadowRadius: 6,
    elevation: 8,
    minWidth: 200, // Ensure minimum width
  },
  header: {
    paddingHorizontal: 16,
    paddingVertical: 12,
    borderBottomWidth: 1,
  },
  body: {
    padding: 16,
  },
  footer: {
    paddingHorizontal: 16,
    paddingVertical: 12,
    borderTopWidth: 1,
    flexDirection: 'row',
    justifyContent: 'flex-end',
    gap: 8,
  },
});
```

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

## Usage

```tsx
import {
  Popover,
  PopoverBody,
  PopoverClose,
  PopoverContent,
  PopoverFooter,
  PopoverHeader,
  PopoverTrigger,
} from '@/components/ui/popover';
```

```tsx
<Popover>
  <PopoverTrigger>
    <Button>Open Popover</Button>
  </PopoverTrigger>
  <PopoverContent>
    <PopoverHeader>
      <Text>Popover Title</Text>
    </PopoverHeader>
    <PopoverBody>
      <Text>This is the popover content.</Text>
    </PopoverBody>
    <PopoverFooter>
      <PopoverClose>
        <Button variant='outline'>Close</Button>
      </PopoverClose>
    </PopoverFooter>
  </PopoverContent>
</Popover>
```

## Examples

#### Default

**Example:** A basic popover with trigger button and content

```tsx
// components/demo/popover/popover-demo.tsx
import { Button } from '@/components/ui/button';
import {
  Popover,
  PopoverBody,
  PopoverClose,
  PopoverContent,
  PopoverFooter,
  PopoverHeader,
  PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import React from 'react';

export function PopoverDemo() {
  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button>Open Popover</Button>
      </PopoverTrigger>
      <PopoverContent>
        <PopoverHeader>
          <Text variant='title'>Popover Title</Text>
        </PopoverHeader>
        <PopoverBody>
          <Text>
            This is the popover content. You can put any content here.
          </Text>
        </PopoverBody>
        <PopoverFooter>
          <PopoverClose>
            <Button variant='outline' size='sm'>
              Close
            </Button>
          </PopoverClose>
        </PopoverFooter>
      </PopoverContent>
    </Popover>
  );
}
```

#### Positioning

**Example:** Popovers positioned on different sides of the trigger

```tsx
// components/demo/popover/popover-positioning.tsx
import { Button } from '@/components/ui/button';
import {
  Popover,
  PopoverBody,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function PopoverPositioning() {
  return (
    <View style={{ gap: 16, alignItems: 'center' }}>
      <Popover>
        <PopoverTrigger asChild>
          <Button style={{ width: 200 }}>Top</Button>
        </PopoverTrigger>
        <PopoverContent side='top'>
          <PopoverBody>
            <Text>Popover content positioned on top</Text>
          </PopoverBody>
        </PopoverContent>
      </Popover>

      <View style={{ flexDirection: 'row', gap: 16 }}>
        <Popover>
          <PopoverTrigger asChild>
            <Button style={{ flex: 1 }}>Left</Button>
          </PopoverTrigger>
          <PopoverContent side='left'>
            <PopoverBody>
              <Text>Left positioned</Text>
            </PopoverBody>
          </PopoverContent>
        </Popover>

        <Popover>
          <PopoverTrigger asChild>
            <Button style={{ flex: 1 }}>Right</Button>
          </PopoverTrigger>
          <PopoverContent side='right'>
            <PopoverBody>
              <Text>Right positioned</Text>
            </PopoverBody>
          </PopoverContent>
        </Popover>
      </View>

      <Popover>
        <PopoverTrigger asChild>
          <Button style={{ width: 200 }}>Bottom</Button>
        </PopoverTrigger>
        <PopoverContent side='bottom'>
          <PopoverBody>
            <Text>Popover content positioned on bottom</Text>
          </PopoverBody>
        </PopoverContent>
      </Popover>
    </View>
  );
}
```

#### Alignment

**Example:** Popovers with different alignment options

```tsx
// components/demo/popover/popover-alignment.tsx
import { Button } from '@/components/ui/button';
import {
  Popover,
  PopoverBody,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function PopoverAlignment() {
  return (
    <View style={{ gap: 24, alignItems: 'center' }}>
      <View style={{ gap: 16 }}>
        <Text variant='title'>Bottom Side Alignment</Text>
        <View style={{ flexDirection: 'row', gap: 16 }}>
          <Popover>
            <PopoverTrigger asChild>
              <Button>Start</Button>
            </PopoverTrigger>
            <PopoverContent side='bottom' align='start'>
              <PopoverBody>
                <Text>Aligned to start (left)</Text>
              </PopoverBody>
            </PopoverContent>
          </Popover>

          <Popover>
            <PopoverTrigger asChild>
              <Button>Center</Button>
            </PopoverTrigger>
            <PopoverContent side='bottom' align='center'>
              <PopoverBody>
                <Text>Aligned to center</Text>
              </PopoverBody>
            </PopoverContent>
          </Popover>

          <Popover>
            <PopoverTrigger asChild>
              <Button>End</Button>
            </PopoverTrigger>
            <PopoverContent side='bottom' align='end'>
              <PopoverBody>
                <Text>Aligned to end (right)</Text>
              </PopoverBody>
            </PopoverContent>
          </Popover>
        </View>
      </View>

      <View style={{ gap: 16 }}>
        <Text variant='title'>Right Side Alignment</Text>
        <View style={{ gap: 16 }}>
          <Popover>
            <PopoverTrigger asChild>
              <Button>Start</Button>
            </PopoverTrigger>
            <PopoverContent side='right' align='start'>
              <PopoverBody>
                <Text>Aligned to start (top)</Text>
              </PopoverBody>
            </PopoverContent>
          </Popover>

          <Popover>
            <PopoverTrigger asChild>
              <Button>Center</Button>
            </PopoverTrigger>
            <PopoverContent side='right' align='center'>
              <PopoverBody>
                <Text>Aligned to center</Text>
              </PopoverBody>
            </PopoverContent>
          </Popover>

          <Popover>
            <PopoverTrigger asChild>
              <Button>End</Button>
            </PopoverTrigger>
            <PopoverContent side='right' align='end'>
              <PopoverBody>
                <Text>Aligned to end (bottom)</Text>
              </PopoverBody>
            </PopoverContent>
          </Popover>
        </View>
      </View>
    </View>
  );
}
```

#### Controlled

**Example:** A controlled popover with external state management

```tsx
// components/demo/popover/popover-controlled.tsx
import { Button } from '@/components/ui/button';
import {
  Popover,
  PopoverBody,
  PopoverContent,
  PopoverHeader,
  PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function PopoverControlled() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <View style={{ gap: 16, alignItems: 'center' }}>
      <View style={{ flexDirection: 'row', gap: 12 }}>
        <Button
          variant={isOpen ? 'default' : 'outline'}
          onPress={() => setIsOpen(true)}
        >
          Open
        </Button>
        <Button
          variant={!isOpen ? 'default' : 'outline'}
          onPress={() => setIsOpen(false)}
        >
          Close
        </Button>
      </View>

      <Text>Status: {isOpen ? 'Open' : 'Closed'}</Text>

      <Popover open={isOpen} onOpenChange={setIsOpen}>
        <PopoverTrigger asChild>
          <Button>Controlled Popover</Button>
        </PopoverTrigger>
        <PopoverContent>
          <PopoverHeader>
            <Text variant='title'>Controlled Popover</Text>
          </PopoverHeader>
          <PopoverBody>
            <Text>
              This popover's state is controlled externally. You can open and
              close it using the buttons above or by clicking the trigger.
            </Text>
          </PopoverBody>
        </PopoverContent>
      </Popover>
    </View>
  );
}
```

#### Custom Content

**Example:** Popovers with custom content and styling

```tsx
// components/demo/popover/popover-custom.tsx
import { Button } from '@/components/ui/button';
import {
  Popover,
  PopoverBody,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import React from 'react';

export function PopoverCustom() {
  const primaryColor = useColor('primary');
  const mutedColor = useColor('muted');

  return (
    <View style={{ gap: 16, alignItems: 'center' }}>
      <Popover>
        <PopoverTrigger asChild>
          <Button>Custom Styled</Button>
        </PopoverTrigger>
        <PopoverContent
          style={{
            backgroundColor: primaryColor,
            borderRadius: 16,
            maxWidth: 250,
          }}
        >
          <PopoverBody style={{ padding: 20 }}>
            <Text style={{ color: 'black', textAlign: 'center' }}>
              This popover has custom styling with primary color background
            </Text>
          </PopoverBody>
        </PopoverContent>
      </Popover>

      <Popover>
        <PopoverTrigger asChild>
          <Button variant='outline'>Large Content</Button>
        </PopoverTrigger>
        <PopoverContent
          maxWidth={400}
          maxHeight={300}
          style={{
            backgroundColor: mutedColor,
          }}
        >
          <PopoverBody style={{ padding: 24 }}>
            <Text variant='title' style={{ marginBottom: 12 }}>
              Large Popover Content
            </Text>
            <Text style={{ lineHeight: 20 }}>
              This popover has custom dimensions and can hold more content. It
              demonstrates how you can customize the appearance and size of
              popover components to fit your design needs.
            </Text>
            <Text style={{ marginTop: 12, fontStyle: 'italic' }}>
              The content area is scrollable if it exceeds the maximum height.
            </Text>
          </PopoverBody>
        </PopoverContent>
      </Popover>

      <Popover>
        <PopoverTrigger asChild>
          <Button size='icon'>?</Button>
        </PopoverTrigger>
        <PopoverContent side='top' align='center'>
          <PopoverBody>
            <Text style={{ textAlign: 'center' }}>
              This is a help tooltip using a custom circular trigger button
            </Text>
          </PopoverBody>
        </PopoverContent>
      </Popover>
    </View>
  );
}
```

#### Form Content

**Example:** A popover containing form elements

```tsx
// components/demo/popover/popover-form.tsx
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
  Popover,
  PopoverBody,
  PopoverClose,
  PopoverContent,
  PopoverFooter,
  PopoverHeader,
  PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function PopoverForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

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

  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button>Add Contact</Button>
      </PopoverTrigger>
      <PopoverContent maxWidth={320}>
        <PopoverHeader>
          <Text variant='title'>Add New Contact</Text>
        </PopoverHeader>
        <PopoverBody>
          <View style={{ gap: 16 }}>
            <View>
              <Text style={{ marginBottom: 8, fontWeight: '500' }}>Name</Text>
              <Input
                placeholder='Enter full name'
                value={name}
                onChangeText={setName}
              />
            </View>
            <View>
              <Text style={{ marginBottom: 8, fontWeight: '500' }}>Email</Text>
              <Input
                placeholder='Enter email address'
                value={email}
                onChangeText={setEmail}
                keyboardType='email-address'
                autoCapitalize='none'
              />
            </View>
          </View>
        </PopoverBody>
        <PopoverFooter>
          <PopoverClose asChild>
            <Button variant='outline' size='sm'>
              Cancel
            </Button>
          </PopoverClose>
          <PopoverClose asChild>
            <Button
              size='sm'
              onPress={handleSubmit}
              disabled={!name.trim() || !email.trim()}
            >
              Add Contact
            </Button>
          </PopoverClose>
        </PopoverFooter>
      </PopoverContent>
    </Popover>
  );
}
```

#### Menu Style

**Example:** A popover styled as a dropdown menu

```tsx
// components/demo/popover/popover-menu.tsx
import { Button } from '@/components/ui/button';
import {
  Popover,
  PopoverClose,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import React from 'react';
import { TouchableOpacity } from 'react-native';

interface MenuItemProps {
  icon: string;
  label: string;
  onPress: () => void;
  destructive?: boolean;
}

function MenuItem({
  icon,
  label,
  onPress,
  destructive = false,
}: MenuItemProps) {
  const textColor = useColor(destructive ? 'destructive' : 'foreground');
  const mutedColor = useColor('muted');

  return (
    <PopoverClose asChild>
      <TouchableOpacity
        style={{
          flexDirection: 'row',
          alignItems: 'center',
          padding: 12,
          borderRadius: 6,
        }}
        onPress={onPress}
        activeOpacity={0.7}
      >
        <Text
          style={{
            fontSize: 16,
            marginRight: 12,
            width: 20,
            textAlign: 'center',
          }}
        >
          {icon}
        </Text>
        <Text style={{ color: textColor, fontSize: 14 }}>{label}</Text>
      </TouchableOpacity>
    </PopoverClose>
  );
}

export function PopoverMenu() {
  const borderColor = useColor('border');

  const handleMenuAction = (action: string) => {
    console.log(`Menu action: ${action}`);
  };

  return (
    <View style={{ gap: 16, alignItems: 'center' }}>
      <Popover>
        <PopoverTrigger asChild>
          <Button variant='outline'>⚙️ Options</Button>
        </PopoverTrigger>
        <PopoverContent
          align='end'
          style={{
            padding: 8,
            minWidth: 180,
          }}
        >
          <MenuItem
            icon='👤'
            label='View Profile'
            onPress={() => handleMenuAction('profile')}
          />
          <MenuItem
            icon='⚙️'
            label='Settings'
            onPress={() => handleMenuAction('settings')}
          />
          <MenuItem
            icon='📄'
            label='Export Data'
            onPress={() => handleMenuAction('export')}
          />
          <View
            style={{
              height: 1,
              backgroundColor: borderColor,
              marginVertical: 4,
            }}
          />
          <MenuItem
            icon='🚪'
            label='Sign Out'
            onPress={() => handleMenuAction('signout')}
          />
          <MenuItem
            icon='🗑️'
            label='Delete Account'
            onPress={() => handleMenuAction('delete')}
            destructive
          />
        </PopoverContent>
      </Popover>

      <Popover>
        <PopoverTrigger asChild>
          <Button>✏️ Actions</Button>
        </PopoverTrigger>
        <PopoverContent side='bottom' align='start' style={{ padding: 8 }}>
          <MenuItem
            icon='📝'
            label='Edit'
            onPress={() => handleMenuAction('edit')}
          />
          <MenuItem
            icon='📋'
            label='Copy'
            onPress={() => handleMenuAction('copy')}
          />
          <MenuItem
            icon='📤'
            label='Share'
            onPress={() => handleMenuAction('share')}
          />
          <MenuItem
            icon='⭐'
            label='Add to Favorites'
            onPress={() => handleMenuAction('favorite')}
          />
        </PopoverContent>
      </Popover>
    </View>
  );
}
```

## API Reference

### Popover

The root component that manages the popover state and provides context.

| Prop           | Type                      | Default | Description                                 |
| -------------- | ------------------------- | ------- | ------------------------------------------- |
| `children`     | `ReactNode`               | -       | The popover trigger and content components. |
| `open`         | `boolean`                 | `false` | Controls the open state of the popover.     |
| `onOpenChange` | `(open: boolean) => void` | -       | Callback fired when the open state changes. |

### PopoverTrigger

The element that triggers the popover when pressed.

| Prop       | Type        | Default | Description                                          |
| ---------- | ----------- | ------- | ---------------------------------------------------- |
| `children` | `ReactNode` | -       | The trigger content.                                 |
| `asChild`  | `boolean`   | `false` | When true, merges props with the child element.      |
| `style`    | `ViewStyle` | -       | Additional styles to apply to the trigger container. |

### PopoverContent

The container for the popover content with positioning logic.

| Prop          | Type                                     | Default  | Description                                            |
| ------------- | ---------------------------------------- | -------- | ------------------------------------------------------ |
| `children`    | `ReactNode`                              | -        | The popover content.                                   |
| `align`       | `'start' \| 'center' \| 'end'`           | `center` | How to align the popover relative to the trigger.      |
| `side`        | `'top' \| 'right' \| 'bottom' \| 'left'` | `bottom` | The preferred side of the trigger to position against. |
| `sideOffset`  | `number`                                 | `8`      | The distance between the trigger and the popover.      |
| `alignOffset` | `number`                                 | `0`      | An offset in pixels from the alignment position.       |
| `style`       | `ViewStyle`                              | -        | Additional styles to apply to the content container.   |
| `maxWidth`    | `number`                                 | `300`    | Maximum width of the popover in pixels.                |
| `maxHeight`   | `number`                                 | `400`    | Maximum height of the popover in pixels.               |

### PopoverHeader

A header section for the popover content.

| Prop       | Type        | Description                                         |
| ---------- | ----------- | --------------------------------------------------- |
| `children` | `ReactNode` | The header content.                                 |
| `style`    | `ViewStyle` | Additional styles to apply to the header container. |

### PopoverBody

The main content area of the popover.

| Prop       | Type        | Description                                       |
| ---------- | ----------- | ------------------------------------------------- |
| `children` | `ReactNode` | The body content.                                 |
| `style`    | `ViewStyle` | Additional styles to apply to the body container. |

### PopoverFooter

A footer section for the popover content.

| Prop       | Type        | Description                                         |
| ---------- | ----------- | --------------------------------------------------- |
| `children` | `ReactNode` | The footer content.                                 |
| `style`    | `ViewStyle` | Additional styles to apply to the footer container. |

### PopoverClose

A utility component that closes the popover when pressed.

| Prop       | Type        | Default | Description                                        |
| ---------- | ----------- | ------- | -------------------------------------------------- |
| `children` | `ReactNode` | -       | The close trigger content.                         |
| `asChild`  | `boolean`   | `false` | When true, merges props with the child element.    |
| `style`    | `ViewStyle` | -       | Additional styles to apply to the close container. |

## Positioning

The popover automatically positions itself relative to the trigger element and adjusts based on available screen space:

- **Side**: Controls which side of the trigger the popover appears on (`top`, `right`, `bottom`, `left`)
- **Alignment**: Controls how the popover aligns relative to the trigger (`start`, `center`, `end`)
- **Auto-flipping**: When there's insufficient space, the popover automatically flips to the opposite side
- **Boundary detection**: Ensures the popover stays within screen boundaries with appropriate padding

## Accessibility

The Popover component is built with accessibility in mind:

- Content is marked accessibilityViewIsModal with accessibilityRole="menu", scoping VoiceOver/TalkBack to it while open
- Trigger and close controls expose accessibilityRole="button" and accessibilityState=\{\{ expanded }}
- Tap-outside-to-dismiss via the backdrop for an easy close gesture
- Touch-friendly interaction areas via the underlying Button component's sizing
