import { useState } from 'react';
import { action } from '@storybook/addon-actions';
import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { ToastContainer, toast } from './';
import { Box } from '../Box/Box';
import { Table } from '../Table/Table';
import { Button } from '../Button/Button';

<Meta
  title="Components/Toast/Overview"
  component={ToastContainer}
  parameters={{
    controls: { hideNoControlsWarning: true },
  }}
  decorators={[
    (Story, { args, viewMode }) => (
      <>
        {viewMode === 'story' && <ToastContainer />}
        <Story args={{ ...args }} />
      </>
    ),
  ]}
/>

<!-- Required for all toasts to show up on the page. -->

<ToastContainer />

# Toast

Use a toast to communicate non-critical information or feedback to a user about the result of an action.
Typically you'd reserve toasts for non-essential notifications, since they are meant to disappear automatically
under normal circumstances.

## Usage Guidelines

- Keep messages short. Typically if a toast results in a multiline message, than it is too long.
- Avoid adding actions inside of a toast. If you include an action (other than dismiss) in a toast,
  try and have that action available somewhere else on the page.
  E.G: a `retry` action can also be triggered by re-submitting a form.
- The main exception that makes for a useful toast action is an `undo` button, which is unlikely to make sense
  in most pages, but is a great escape hatch for a user that wants to take back an action.

### Toast or Alert or Modal?

When to use Toast:

- Confirmations, success messages, and simple notifications
- Non-critical errors that are relevant in the moment and do not need to interrupt the user experience, such as "No internet connection"

When not to use Toast:

- Do not use toasts for error messages. Instead, use [Alert](?path=/docs/components-alert-overview--default-story) to prominently inform users of persistent errors.
- Do not use toasts as a blocking alert or interstitial (i.e. restrict or contextualize user actions). In these
  cases, use a [Modal](?path=/docs/components-modal-overview--default-story).

## Content Guidelines

Keep the contents of a toast as short as possible by placing only the most important information in it. Toasts are meant to unobtrusively provide non-critical information or feedback. In general, toast messages should be:

- Short and affirmative
- Written in a pattern of noun + verb, such as "Proposal saved" or "hic_contract.pdf added".

<Canvas isExpanded>
  <Story
    name="Default"
    parameters={{
      design: {
        type: 'figma',
        url: 'https://www.figma.com/file/k1kOsplTGXvSSMQQCzhCND/Palmetto-Design-System?node-id=606%3A7733',
      },
    }}
  >
    {() => {
      const handleClick = () => toast('Proposal saved', { duration: 5000 });
      return (
        <>
          <Button onClick={handleClick} tone="neutral">
            Show toast
          </Button>
        </>
      );
    }}
  </Story>
</Canvas>

## Required Setup

1. Include the `<ToastContainer />` in your React app. (at the highest possible level of the DOM tree);
2. Create a toast anywhere in your app `toast('Hello')`.

**NOTE:** All examples of this page exclusively showcase step 2 above, as the ToastContainer is added globally on the page.

### Example

```jsx
// App.jsx
import { ToastContainer } from '@palmetto/palmetto-components';

export const App = () => {
  <MyAppContainer>
    <ToastContainer {/* custom props */} />
    {/* Routes, other providers, etc */}
  </MyAppContainer>
};

// SomeAppPage.jsx
import { toast } from '@palmetto/palmetto-components';

export const SomeAppPage = () => {
  <button onClick={() => toast('Hello')}>
    gimme a toast
  </button>
}
```

## Props (for `<ToastContainer />`)

<ArgsTable of={ToastContainer} />

## Toast Options

```jsx
// Example
toast('my toast', { duration: 3000, position: 'top-center' });
```

<Canvas>
  <Story name="Column">
    {() => {
      const codePreviewStyle = {
        padding: '3px 5px',
        borderRadius: '3px',
        border: '1px solid #EEEEEE',
        color: 'rgba(51,51,51,0.9)',
        backgroundColor: '#F8F8F8',
      };
      const toastInterfaceRows = [
        {
          name: 'id',
          type: 'string',
          description:
            'Unique id of toast. This is autogenerated if not included, but can be included to update an existing toast.',
        },
        {
          name: 'duration',
          type: 'number',
          description:
            'Duration (in milliseconds) that toast remains on screen before auto-dismissing.',
        },
        {
          name: 'ariaProps',
          type: "{ role: 'status' | 'alert', 'aria-live': 'assertive' | 'off' | 'polite' }",
          description: 'Accessibility props to be passed down to toast.',
        },
        {
          name: 'className',
          type: 'string',
          description: 'Custom class.',
        },
        {
          name: 'style',
          type: 'CSS.StyleObject',
          description: 'Custom styles',
        },
        {
          name: 'position',
          type: "  'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';",
          description:
            'Toast position (overrides global container toast position)',
        },
        {
          name: 'canDismiss',
          type: 'boolean',
          description:
            'Whether toast includes the close/dismiss icon allowing user to close it (defaults to true)',
        },
        {
          name: 'isCompact',
          type: 'boolean',
          description: 'Whether to render a compact toast (smaller padding)',
        },
      ];
      const columnConfig = [
        { heading: 'Name', dataKey: 'name' },
        {
          heading: 'Type',
          dataKey: 'type',
          render: cell => <code style={codePreviewStyle}>{cell}</code>,
        },
        { heading: 'Description', dataKey: 'description' },
      ];
      return (
        <Table rowKey="name" columns={columnConfig} rows={toastInterfaceRows} />
      );
    }}
  </Story>
</Canvas>

### Position

<Canvas isExpanded>
  <Story name="Position">
    {() => {
      const handleClick = position => toast('Hey there', { position });
      return (
        <>
          <Box gap="md" direction="row">
            <Button tone="neutral" onClick={() => handleClick('top-right')}>
              top right
            </Button>
            <Button tone="neutral" onClick={() => handleClick('top-center')}>
              top center
            </Button>
            <Button tone="neutral" onClick={() => handleClick('topleft')}>
              top left
            </Button>
            <Button tone="neutral" onClick={() => handleClick('bottom-right')}>
              bottom right
            </Button>
            <Button tone="neutral" onClick={() => handleClick('bottom-center')}>
              bottom center
            </Button>
            <Button tone="neutral" onClick={() => handleClick('bottom-left')}>
              bottom left
            </Button>
          </Box>
        </>
      );
    }}
  </Story>
</Canvas>

### Duration

We've included sensible defaults for the duration of a toast before it disappears, but if you require custom duration, follow these guidelines:

1. Error toasts should likely stay on the screen
   longer than success toasts, since they are arguably more important.
2. Toasts with actions, should stay on screen for at least 10 seconds.
3. async toasts with loading states should always included success and failure conditions,
   to avoid an infinite spinner.

<Canvas isExpanded>
  <Story name="Duration">
    {() => {
      const handleClick = () => {
        toast(`toast will close in 6 seconds`, { duration: 6000 });
      };
      return (
        <>
          <Box gap="md" direction="row">
            <Button onClick={handleClick} tone="neutral">
              Open toast with custom duration
            </Button>
          </Box>
        </>
      );
    }}
  </Story>
</Canvas>

### Dismissible (or lack thereof)

By default, toasts are dismissible since they
should not hinder the user experience, but we still wish to provide a quick and easy
escape hatch, should a user want to clear it out of their view.

In cases where you'd like the toast to remain for a set amount of time
without the user being able to clear it, you can use `canDismiss: false`.

<Canvas isExpanded>
  <Story name="Dismissible">
    {() => {
      const handleClick = () => {
        toast(`This toast can't be dismissed`, {
          duration: 5000,
          canDismiss: false,
        });
      };
      return (
        <>
          <Box gap="md" direction="row">
            <Button onClick={handleClick} tone="neutral">
              Open non-dismissible toast
            </Button>
          </Box>
        </>
      );
    }}
  </Story>
</Canvas>

### Programmatic Dismiss

In some cases you'll want to be able to dismiss a toast programmatically based on some other condition in the business logic.
For these cases, you can use `toast.dismiss`.

<Canvas isExpanded>
  <Story name="Programmatic Dismiss">
    {() => {
      const [toastId, setToastId] = useState(null);
      const handleClick = () => {
        if (!toastId) {
          const newToastId = toast(`Dismiss programmatically`, {
            duration: 30000,
            canDismiss: false,
          });
          setToastId(newToastId);
        }
      };
      const handleDismiss = () => {
        if (toastId) {
          toast.dismiss(toastId);
          setToastId(null);
        }
      };
      return (
        <>
          <Box gap="md" direction="row">
            <Button onClick={handleClick} tone="neutral" isDisabled={!!toastId}>
              Show Toast
            </Button>
            <Button
              variant="secondary"
              tone="neutral"
              isDisabled={!toastId}
              onClick={handleDismiss}
            >
              Dismiss toast
            </Button>
          </Box>
        </>
      );
    }}
  </Story>
</Canvas>

### Compact

Use the `isCompact` prop for a toast the uses less screen real estate.

<Canvas isExpanded>
  <Story name="Compact">
    {() => {
      const handleClick = () => {
        toast('This is compact', { isCompact: true });
      };
      return (
        <Button onClick={handleClick} tone="neutral">
          Open compact toast
        </Button>
      );
    }}
  </Story>
</Canvas>

### Toast Types

To give toasts an additional visual indication of feedback, `success`, `loading`, and `error` types add a contextual icon to the toast message.

<Canvas isExpanded>
  <Story name="Basic Types">
    {() => {
      const handleClick = type => {
        if (type === 'success') {
          const successToastId = toast.success('Proposal saved');
        } else if (type === 'error') {
          toast.error('Unable to save');
        } else if (type === 'loading') {
          const toastId = toast.loading('Loading...', { canDismiss: false });
        } else {
          toast('Toast without icon');
        }
      };
      return (
        <>
          <Box fontSize="lg" fontWeight="bold" margin="0 0 md 0">
            Basic Types
          </Box>
          <Box childGap="md" direction="row">
            <Button
              onClick={() => handleClick('blank')}
              variant="primary"
              tone="neutral"
            >
              Default
            </Button>
            <Button onClick={() => handleClick('error')} variant="danger">
              Error
            </Button>
            <Button onClick={() => handleClick('success')}>Success</Button>
            <Button onClick={() => handleClick('loading')} tone="neutral">
              Loading
            </Button>
          </Box>
        </>
      );
    }}
  </Story>
</Canvas>

<Canvas isExpanded>
  <Story name="Advanced Types">
    {() => {
      const handleClick = type => {
        if (type.includes('async')) {
          const promise = new Promise((resolve, reject) => {
            const randomNum = Math.floor(Math.random() * 2);
            setTimeout(() => {
              if (randomNum % 2 === 0) {
                resolve(', data loaded');
              } else {
                reject(', bad request');
              }
            }, 2000);
          });
          toast.async(promise, {
            loading: 'loading',
            success: data => `Success${type.includes('data') ? data : ''}`,
            error: err => `Error${type.includes('data') ? err : ''}`,
          });
        }
      };
      return (
        <>
          <Box fontSize="lg" fontWeight="bold" margin="0 0 md 0">
            Advanced Types
          </Box>
          <Box display="block" childGap="md">
            <Box display="block" childGap="sm">
              <p>
                With an async toast, you can pass the three states of an async
                op (loading, success, error) to a corresponding toast state. By
                passing the promise itself to the toast component, will will
                handle displaying different toast variations based on the state
                of the passed promise.
              </p>
              <Button
                onClick={() => handleClick('async')}
                variant="primary"
                tone="neutral"
              >
                async
              </Button>
            </Box>
            <Box display="block" childGap="sm">
              <p>
                If you need to include data or an error returned from the
                promise you can pass a function to the toasts in question, see
                code example.
              </p>
              <Button onClick={() => handleClick('async-data')} tone="neutral">
                async (including promise data)
              </Button>
            </Box>
          </Box>
        </>
      );
    }}
  </Story>
</Canvas>
