1- import type { Ref } from 'react' ;
1+ import { type Ref , useState } from 'react' ;
22
3+ import { useSpinDelay } from '../../hooks/use-spin-delay' ;
34import type { IconComponent } from '../../icons/glyph' ;
45import { Icon } from '../icon' ;
56import { Menu } from '../menu' ;
7+ import { Spinner } from '../spinner' ;
68
79export interface ActionMenuAction {
810 label : string ;
911 color ?: 'neutral' | 'negative' ;
1012 icon ?: IconComponent ;
1113 onClick : ( ) => void ;
14+ progress ?: 'idle' | 'pending' | 'handoff' ;
1215}
1316
1417export interface ActionMenuProps {
@@ -20,12 +23,31 @@ export interface ActionMenuProps {
2023}
2124
2225export function ActionMenu ( { label, actions, triggerRef, disabled, focusableWhenDisabled } : ActionMenuProps ) {
26+ const [ open , setOpen ] = useState ( false ) ;
27+ const [ activated , setActivated ] = useState ( false ) ;
28+ const pending = actions . some ( action => action . progress === 'pending' ) ;
29+ const handoff = actions . some ( action => action . progress === 'handoff' ) ;
30+ const showSpinner = useSpinDelay ( pending ? true : null ) ;
31+
32+ if ( activated && ! pending ) {
33+ setActivated ( false ) ;
34+ setOpen ( false ) ;
35+ }
36+
2337 if ( actions . length === 0 ) {
2438 return null ;
2539 }
2640
2741 return (
28- < Menu . Root placement = 'bottom-end' >
42+ < Menu . Root
43+ placement = 'bottom-end'
44+ open = { open && ! handoff }
45+ onOpenChange = { nextOpen => {
46+ if ( ! pending && ! handoff ) {
47+ setOpen ( nextOpen ) ;
48+ }
49+ } }
50+ >
2951 < Menu . Trigger
3052 ref = { triggerRef }
3153 aria-label = { label }
@@ -38,15 +60,30 @@ export function ActionMenu({ label, actions, triggerRef, disabled, focusableWhen
3860 key = { action . label }
3961 color = { action . color }
4062 label = { action . label }
41- onClick = { action . onClick }
42- disabled = { disabled }
63+ onClick = { ( ) => {
64+ if ( action . progress !== undefined ) {
65+ setActivated ( true ) ;
66+ }
67+ action . onClick ( ) ;
68+ } }
69+ closeOnClick = { action . progress === undefined }
70+ aria-busy = { action . progress === 'pending' || undefined }
71+ disabled = { disabled || pending || handoff }
4372 >
4473 { action . icon ? (
4574 < Menu . Media >
4675 < Icon glyph = { action . icon } />
4776 </ Menu . Media >
4877 ) : null }
4978 < Menu . Label > { action . label } </ Menu . Label >
79+ { action . progress === 'pending' && showSpinner ? (
80+ < Spinner
81+ size = 'sm'
82+ role = 'progressbar'
83+ aria-hidden = { undefined }
84+ aria-label = { action . label }
85+ />
86+ ) : null }
5087 </ Menu . Item >
5188 ) ) }
5289 </ Menu . Popup >
0 commit comments