import { Meta, Canvas } from '@storybook/addon-docs/blocks'
import { Warning } from '../../../docs/helpers'
import * as GridStories from './index.stories.tsx'

<Meta title="pv-grid/Components/Grid/Actions Menu" />

# Actions Menu

A common pattern is to expose a menu of actions that should be available per row of data. You learn more by reading [the design specification for this feature](https://design.planview.com/components/grid/grid-row#actions-menu).

In the grid, this is called the Actions Menu and is configured via the `actionsMenu` prop on the main `Grid` component.

### Synchronous Example

Note, you can right-click on any row below to see the context menu or click on the three vertical dots.

<Canvas of={GridStories.ActionsMenu} />

### Asynchronous Example

A variation you may need to use is an asynchronous loading menu. The example below shows that variation. To wire this up, you'll need to use the [Menu](#providing-the-full-menu) configuration instead of [MenuItems](#providing-menuitems-only)

<Canvas of={GridStories.ActionsMenuAsync} />

## Usage

The `actionsMenu` prop is either a component that receives `row` and `rowMeta` as props, or it is a configuration object that accepts either a `Menu` or `MenuItems` component and an optional `visible` predicate function (which also receives `row` and `rowMeta`).

<Warning>
    Note: Passing in a component that changes on each render (as shown in these
    usage examples) can have negative performance implications on grids with
    many rows or columns. If you notice any slowdown after adding an
    actionsMenu, be sure to either use [our preferred approach with a static
    component reference](#preferred-usage-external-component), or alternatively
    [memoize the `actionsMenu` property](#alternate-usage-memoized-callback) to
    avoid this issue.
</Warning>

### Providing MenuItems only

The simple component provided should return a fragment that contains only valid children of a `Menu`. These include components like `ListItem`, `ListGroup`, `SubMenu` and more.

The type for the MenuItems component is `GridActionsMenuComponent<TDataModel, TMetaModel>` and the props type is `GridActionsMenuProps<TDataModel, TMetaModel>`

```tsx
<Grid actionsMenu={({ row, rowMeta }) => (
    <>
        <ListGroup label="Some group">
            <ListItem label="An action" />
        </ListGroup>
        <ListItem label="Another action" />
    </>
)} />

// or

<Grid actionsMenu={{
    MenuItems: ({ row, rowMeta }) => (
        <>
            <ListGroup label="Some group">
                <ListItem label="An action" />
            </ListGroup>
            <ListItem label="Another action" />
        </>
    ),
    visible: ({ row, rowMeta }) => true // or false
}} />
```

### Providing the full Menu

If you need more control over the `Menu` component itself, including setting the `width` or `loading` state, then you should provide a `Menu` prop. The component must spread all the `menuProps` on the `@planview/pv-uikit` [Menu component](/docs/pv-uikit-menu--docs) as part of the rendering:

The type for the Menu component is `GridActionsMenuFullComponent<TDataModel, TMetaModel>` and the props type is `GridActionsMenuFullProps<TDataModel, TMetaModel>`

```tsx
import { Grid } from '@planview/pv-grid'
import { Menu } from '@planview/pv-uikit'

export default () => (
    <Grid
        actionsMenu={{
            Menu: ({ row, rowMeta, menuProps }) => (
                <Menu {...menuProps} width={400}>
                    <ListItem label="This will be a very long label" />
                </Menu>
            ),
            visible: ({ row, rowMeta }) => true, // or false
        }}
    />
)
```

### Conditional display of the button/menu

The `actionsMenu` component can use `row` and `rowMeta` properties to control which items in the list are displayed or enabled. But in some cases, for instance group headers, you may not want the menu or button to display at all. In those cases you should pass the `visible` callback:

In the following example, the assumption is some of the rows are group headers and as such, the `rowMeta` for that row would have a `type` of `group`.

```tsx
function UsersGrid() {
    // ...

    return (
        <Grid
            columns={columns}
            rows={rows}
            actionsMenu={{
                MenuItems: ActionsMenuItems,
                visible({ rowMeta }) {
                    // Only enable menus on leaf rows,
                    // not group headers
                    return rowMeta.type !== 'group'
                },
            }}
        />
    )
}
```

## Best Practices

### Preferred Usage: External Component

This example is the preferred way to define an `ActionsMenu` component and works well if you are already leveraging either React Context or something like Redux in your application. The example below assumes Redux. This approach uses the `Menu` configuration option but it would work with `MenuItems` with minor adjustments.

```tsx
// ActionsMenu.tsx
import { Menu } from '@planview/pv-uikit'
import { GridActionsMenuFullComponent } from '@planview/pv-grid'
import { useAppDispatch } from '../../store'
import { editAction, suppressAction, deleteAction } from '../../actions'

export const ActionsMenu: GridActionsMenuFullComponent<User> = ({
    row,
    menuProps,
}) => {
    // This could also just be something that uses React Context
    const dispatch = useAppDispatch()

    return (
        <Menu {...menuProps}>
            <ListItem
                label="Edit"
                onActivate={() => dispatch(editAction(row.id))}
            />
            <ListItem
                label="Suppress"
                onActivate={() => dispatch(suppressAction(row.id))}
            />
            <ListItemDivider />
            <ListItem
                label="Delete"
                onActivate={() => dispatch(deleteAction(row.id))}
            />
        </Menu>
    )
}
```

```tsx
// Grid.tsx
import { Grid } from '@planview/pv-grid'
import { ActionsMenu } from './ActionsMenu.tsx'

function UsersGrid() {
    // ...

    return (
        <Grid
            columns={columns}
            rows={rows}
            actionsMenu={{ Menu: ActionsMenu }}
        />
    )
}
```

## Alternate Usage: Memoized Callback

In cases where you need direct access to the parent components props and state, you may need to define the actions component as part of the render. In this situation, be sure to memoize the result so it remains consistent unless it needs to change.

```tsx
import { Grid, GridActionsMenuComponent } from '@planview/pv-grid'

type UserGridProps = {
    onActionClick: (actionName: string, rowId: string) => void
}

function UsersGrid({ onActionClick }: UserGridProps) {
    // ...

    const ActionsMenuItems = React.useCallback<GridActionsMenuComponent<User>>(
        ({ row }) => (
            <>
                <ListItem
                    label="Edit"
                    onActivate={() => onActionClick('edit', row.id)}
                />
                <ListItem
                    label="Suppress"
                    onActivate={() => onActionClick('suppress', row.id)}
                />
                <ListItemDivider />
                <ListItem
                    label="Delete"
                    onActivate={() => onActionClick('delete', row.id)}
                />
            </>
        ),
        [onActionClick]
    )

    return <Grid columns={columns} rows={rows} actionsMenu={ActionsMenuItems} />
}
```
