/
ActionMenu.tsx
106 lines (93 loc) · 3.4 KB
/
ActionMenu.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import React, {memo, useCallback, useMemo} from 'react'
import {Button, ButtonProps, PopoverProps} from '@sanity/ui'
import {EllipsisVerticalIcon} from '@sanity/icons'
import {PortableTextEditor, usePortableTextEditor} from '@sanity/portable-text-editor'
import {CollapseMenu, CollapseMenuButton} from '../../../../components/collapseMenu'
import {PTEToolbarAction, PTEToolbarActionGroup} from './types'
import {useActiveActionKeys, useFocusBlock} from './hooks'
import {getActionIcon} from './helpers'
const CollapseMenuMemo = memo(CollapseMenu)
const MENU_POPOVER_PROPS: PopoverProps = {constrainSize: true, portal: true}
const COLLAPSE_BUTTON_PROPS: ButtonProps = {padding: 2, mode: 'bleed'}
interface ActionMenuProps {
disabled: boolean
groups: PTEToolbarActionGroup[]
isFullscreen?: boolean
collapsed?: boolean
}
export const ActionMenu = memo(function ActionMenu(props: ActionMenuProps) {
const {disabled: disabledProp, groups, isFullscreen, collapsed} = props
const focusBlock = useFocusBlock()
const editor = usePortableTextEditor()
const isVoidBlock = focusBlock?._type !== editor.schemaTypes.block.name
const isEmptyTextBlock =
!isVoidBlock &&
Array.isArray(focusBlock.children) &&
focusBlock.children.length === 1 &&
focusBlock?.children[0].text === ''
const disabled = disabledProp || isVoidBlock
const actions: Array<PTEToolbarAction & {firstInGroup?: true}> = useMemo(
() =>
groups.reduce<Array<PTEToolbarAction & {firstInGroup?: true}>>((acc, group) => {
return acc.concat(
group.actions.map(
// eslint-disable-next-line max-nested-callbacks
(action: PTEToolbarAction, actionIndex) => {
if (actionIndex === 0) return {...action, firstInGroup: true}
return action
}
)
)
}, []),
[groups]
)
const activeKeys = useActiveActionKeys({actions})
const handleMenuClose = useCallback(() => {
PortableTextEditor.focus(editor)
}, [editor])
const children = useMemo(
() =>
actions.map((action) => {
const annotationDisabled = action.type === 'annotation' && isEmptyTextBlock
const active = activeKeys.includes(action.key)
return (
<CollapseMenuButton
disabled={disabled || annotationDisabled}
{...COLLAPSE_BUTTON_PROPS}
dividerBefore={action.firstInGroup}
icon={getActionIcon(action, active)}
key={action.key}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => action.handle(active)}
selected={active}
text={action.title || action.key}
tooltipText={action.title || action.key}
tooltipProps={{
disabled: disabled || annotationDisabled,
placement: isFullscreen ? 'bottom' : 'top',
portal: 'default',
}}
/>
)
}),
[actions, activeKeys, disabled, isEmptyTextBlock, isFullscreen]
)
const menuButtonProps = useMemo(
() => ({
button: <Button icon={EllipsisVerticalIcon} mode="bleed" padding={2} disabled={disabled} />,
popover: MENU_POPOVER_PROPS,
}),
[disabled]
)
return (
<CollapseMenuMemo
collapsed={collapsed}
disableRestoreFocusOnClose
gap={1}
menuButtonProps={menuButtonProps}
onMenuClose={handleMenuClose}
>
{children}
</CollapseMenuMemo>
)
})