# Alert Dialog

> A modal dialog that interrupts the user with important content and expects a response.

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

---

**Example:** A basic alert dialog with confirmation buttons.

```tsx
// components/demo/alert-dialog/alert-dialog-demo.tsx
import React from 'react';
import { AlertDialog, useAlertDialog } from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import { View } from '@/components/ui/view';

export function AlertDialogDemo() {
  const dialog = useAlertDialog();

  return (
    <View>
      <Button onPress={dialog.open}>Show Dialog</Button>

      <AlertDialog
        isVisible={dialog.isVisible}
        onClose={dialog.close}
        title='Are you absolutely sure?'
        description='This action cannot be undone. This will permanently delete your account and remove your data from our servers.'
        confirmText='Yes, delete'
        cancelText='Cancel'
        onConfirm={() => {
          console.log('Account deleted');
          dialog.close();
        }}
        onCancel={dialog.close}
      />
    </View>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add alert-dialog
```

### Manual

**1.** Install the following dependencies:

This component uses `react-native-reanimated` for animations.

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

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

```tsx
// components/ui/alert-dialog.tsx
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { useColor } from '@/hooks/useColor';
import React, { useEffect } from 'react';
import {
  Modal,
  StyleSheet,
  TouchableWithoutFeedback,
  View,
  ViewStyle,
} from 'react-native';
import Animated, {
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

export type AlertDialogProps = {
  isVisible: boolean;
  onClose: () => void;
  title?: string;
  description?: string;
  children?: React.ReactNode;
  confirmText?: string;
  cancelText?: string;
  onConfirm?: () => void;
  onCancel?: () => void;
  dismissible?: boolean;
  showCancelButton?: boolean;
  style?: ViewStyle;
};

// A simple card-like dialog overlay with fade-in animation similar to BottomSheet's backdrop
export function AlertDialog({
  isVisible,
  onClose,
  title,
  description,
  children,
  confirmText = 'OK',
  cancelText = 'Cancel',
  onConfirm,
  onCancel,
  dismissible = true,
  showCancelButton = true,
  style,
}: AlertDialogProps) {
  const cardColor = useColor('card');

  const [modalVisible, setModalVisible] = React.useState(false);
  const backdropOpacity = useSharedValue(0);
  const cardOpacity = useSharedValue(0);

  useEffect(() => {
    if (isVisible) {
      setModalVisible(true);
      backdropOpacity.value = withTiming(1, { duration: 250 });
      cardOpacity.value = withTiming(1, { duration: 200 });
    } else {
      backdropOpacity.value = withTiming(0, { duration: 250 }, (finished) => {
        if (finished) {
          runOnJS(setModalVisible)(false);
        }
      });
      cardOpacity.value = withTiming(0, { duration: 200 });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isVisible]);

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

  const rCardFadeStyle = useAnimatedStyle(() => ({
    opacity: cardOpacity.value,
  }));

  const animateClose = () => {
    'worklet';
    backdropOpacity.value = withTiming(0, { duration: 300 }, (finished) => {
      if (finished) {
        runOnJS(onClose)();
      }
    });
    cardOpacity.value = withTiming(0, { duration: 200 });
  };

  const handleBackdropPress = () => {
    if (dismissible) {
      animateClose();
      if (onCancel) onCancel();
    }
  };

  const handleCancel = () => {
    if (onCancel) onCancel();
    animateClose();
  };

  const handleConfirm = () => {
    if (onConfirm) onConfirm();
    animateClose();
  };

  return (
    <Modal
      visible={modalVisible}
      transparent
      statusBarTranslucent
      animationType='none'
    >
      <Animated.View
        style={[styles.backdrop, rBackdropStyle]}
        accessibilityViewIsModal
      >
        <TouchableWithoutFeedback onPress={handleBackdropPress}>
          <Animated.View style={styles.backdropTouchableArea} />
        </TouchableWithoutFeedback>

        {/* Non-animated outer wrapper: handles rounded corners and clipping */}
        <View
          style={[styles.roundedWrapper, { backgroundColor: cardColor }, style]}
        >
          {/* Only fade the inner content */}
          <Animated.View style={[styles.innerContent, rCardFadeStyle]}>
            <Card
              // Card has no rounded corners, background or shadow (delegated to wrapper)
              style={{ backgroundColor: 'transparent', elevation: 0 }}
            >
              {(title || description) && (
                <CardHeader>
                  {title ? (
                    <CardTitle accessibilityRole='alert'>{title}</CardTitle>
                  ) : null}
                  {description ? (
                    <CardDescription accessibilityRole='alert'>
                      {description}
                    </CardDescription>
                  ) : null}
                </CardHeader>
              )}
              {children ? <CardContent>{children}</CardContent> : null}
              <CardFooter>
                {showCancelButton && (
                  <Button variant='outline' onPress={handleCancel}>
                    {cancelText}
                  </Button>
                )}
                <Button style={{ flex: 1 }} onPress={handleConfirm}>
                  {confirmText}
                </Button>
              </CardFooter>
            </Card>
          </Animated.View>
        </View>
      </Animated.View>
    </Modal>
  );
}

const styles = StyleSheet.create({
  backdrop: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.8)',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 24,
  },
  backdropTouchableArea: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
  // Rounded corners and clipping consolidated here (non-animated)
  roundedWrapper: {
    width: '100%',
    borderRadius: 16,
    overflow: 'hidden',
  },
  // Inner content can render freely (only opacity is animated)
  innerContent: {
    width: '100%',
  },
});

export function useAlertDialog() {
  const [isVisible, setIsVisible] = React.useState(false);
  const open = React.useCallback(() => setIsVisible(true), []);
  const close = React.useCallback(() => setIsVisible(false), []);
  const toggle = React.useCallback(() => setIsVisible((v) => !v), []);
  return { isVisible, open, close, toggle };
}
```

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

This component depends on the `Card` and `Button` components, so make sure you have those installed as well.

## Usage

The `useAlertDialog` hook is the recommended way to control the dialog's visibility state.

```tsx
import { View } from 'react-native';
import { Button } from '@/components/ui/button';
import { AlertDialog, useAlertDialog } from '@/components/ui/alert-dialog';

export default function MyComponent() {
  const { isVisible, open, close } = useAlertDialog();

  const handleConfirm = () => {
    // Handle the confirmation logic
    console.log('Action confirmed!');
  };

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

      <AlertDialog
        isVisible={isVisible}
        onClose={close}
        title='Are you absolutely sure?'
        description='This action cannot be undone. This will permanently delete your account and remove your data from our servers.'
        onConfirm={handleConfirm}
      />
    </View>
  );
}
```

## Examples

#### Default

**Example:** A basic alert dialog with confirmation buttons.

```tsx
// components/demo/alert-dialog/alert-dialog-demo.tsx
import React from 'react';
import { AlertDialog, useAlertDialog } from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import { View } from '@/components/ui/view';

export function AlertDialogDemo() {
  const dialog = useAlertDialog();

  return (
    <View>
      <Button onPress={dialog.open}>Show Dialog</Button>

      <AlertDialog
        isVisible={dialog.isVisible}
        onClose={dialog.close}
        title='Are you absolutely sure?'
        description='This action cannot be undone. This will permanently delete your account and remove your data from our servers.'
        confirmText='Yes, delete'
        cancelText='Cancel'
        onConfirm={() => {
          console.log('Account deleted');
          dialog.close();
        }}
        onCancel={dialog.close}
      />
    </View>
  );
}
```

#### Destructive Action

**Example:** An alert dialog for destructive actions like delete.

```tsx
// components/demo/alert-dialog/alert-dialog-destructive.tsx
import React from 'react';
import { AlertDialog, useAlertDialog } from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import { View } from '@/components/ui/view';

export function AlertDialogDestructiveDemo() {
  const dialog = useAlertDialog();

  return (
    <View>
      <Button variant='destructive' onPress={dialog.open}>
        Delete Item
      </Button>

      <AlertDialog
        isVisible={dialog.isVisible}
        onClose={dialog.close}
        title='Delete Item'
        description='Are you sure you want to delete this item? This action cannot be undone.'
        confirmText='Delete'
        cancelText='Cancel'
        onConfirm={() => {
          console.log('Item deleted');
          dialog.close();
        }}
        onCancel={dialog.close}
      />
    </View>
  );
}
```

#### Custom Style

**Example:** A custom styled alert dialog with a different appearance.

```tsx
// components/demo/alert-dialog/alert-dialog-custom.tsx
import React from 'react';
import { AlertDialog, useAlertDialog } from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import { View } from '@/components/ui/view';
import { Text } from '@/components/ui/text';

export function AlertDialogCustomDemo() {
  const dialog = useAlertDialog();

  return (
    <View>
      <Button variant='outline' onPress={dialog.open}>
        Custom Dialog
      </Button>

      <AlertDialog
        isVisible={dialog.isVisible}
        onClose={dialog.close}
        confirmText='Continue'
        cancelText='Go Back'
        onConfirm={() => {
          console.log('Continued');
          dialog.close();
        }}
        onCancel={dialog.close}
        style={{ borderRadius: 24 }}
      >
        <View style={{ alignItems: 'center', padding: 16 }}>
          <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 8 }}>
            Custom Content
          </Text>
          <Text style={{ textAlign: 'center', marginBottom: 16 }}>
            This dialog contains custom content instead of using the title and
            description props.
          </Text>
        </View>
      </AlertDialog>
    </View>
  );
}
```

## API Reference

### AlertDialog

The main component that renders the modal dialog.

| Prop               | Type              | Default    | Description                                                                |
| ------------------ | ----------------- | ---------- | -------------------------------------------------------------------------- |
| `isVisible`        | `boolean`         | -          | **Required.** Controls the visibility of the dialog.                       |
| `onClose`          | `() => void`      | -          | **Required.** Callback function when the dialog is closed.                 |
| `title`            | `string`          | -          | The title displayed at the top of the dialog.                              |
| `description`      | `string`          | -          | The main content or description of the dialog.                             |
| `children`         | `React.ReactNode` | -          | Custom React nodes to render inside the dialog's content area.             |
| `confirmText`      | `string`          | `'OK'`     | The text for the confirmation button.                                      |
| `cancelText`       | `string`          | `'Cancel'` | The text for the cancellation button.                                      |
| `onConfirm`        | `() => void`      | -          | Callback function when the confirmation button is pressed.                 |
| `onCancel`         | `() => void`      | -          | Callback function when the cancel button is pressed or backdrop is tapped. |
| `dismissible`      | `boolean`         | `true`     | If `true`, tapping the backdrop will close the dialog.                     |
| `showCancelButton` | `boolean`         | `true`     | If `false`, the cancel button will not be rendered.                        |
| `style`            | `ViewStyle`       | -          | Custom styles to apply to the dialog's main container.                     |

### useAlertDialog

A hook to manage the state of the `AlertDialog` component.

| Return      | Type         | Description                                           |
| ----------- | ------------ | ----------------------------------------------------- |
| `isVisible` | `boolean`    | The current visibility state of the dialog.           |
| `open`      | `() => void` | A function to set the dialog's visibility to `true`.  |
| `close`     | `() => void` | A function to set the dialog's visibility to `false`. |
| `toggle`    | `() => void` | A function to toggle the dialog's visibility.         |

## Accessibility

The Alert Dialog component is designed with accessibility in mind to ensure a clear and interruptive user experience.

- The modal nature of the component and the dark backdrop ensure that the user's attention is focused on the dialog content.
- It uses standard, accessible components like `Button` and `Text` from the library.
- The dialog can be dismissed by tapping the backdrop (if `dismissible` is true), providing an intuitive closing mechanism.
