66 IconArrowLeft ,
77 IconChevronRight ,
88 Button ,
9+ IconMinus ,
910 IconRefresh ,
1011 IconArrowUp ,
1112 IconCheckmark ,
@@ -33,6 +34,7 @@ import {
3334 parseDuration ,
3435 severityOptions ,
3536 targetPanelOptions ,
37+ type NotificationDraftAction ,
3638 type NotificationDraft ,
3739 type QueuedNotification ,
3840} from './triggerNotificationUtils' ;
@@ -66,6 +68,12 @@ const formatDurationLabel = (duration: number) => {
6668 return `${ ( duration / 1000 ) . toFixed ( 1 ) } s` ;
6769} ;
6870
71+ const formatActionsLabel = ( count : number ) => `${ count } action${ count === 1 ? '' : 's' } ` ;
72+
73+ const isDraftActionReady = (
74+ action : Pick < NotificationDraftAction , 'feedback' | 'label' > ,
75+ ) => action . label . trim ( ) . length > 0 && action . feedback . trim ( ) . length > 0 ;
76+
6977const NotificationEntrySelect = ( {
7078 label,
7179 onChange,
@@ -154,6 +162,11 @@ const NotificationChipList = ({
154162 < span className = 'app__notification-dialog__chip-panel' >
155163 { notification . targetPanel }
156164 </ span >
165+ { notification . actions . length > 0 && (
166+ < span className = 'app__notification-dialog__chip-panel' >
167+ { formatActionsLabel ( notification . actions . length ) }
168+ </ span >
169+ ) }
157170 </ span >
158171 < Button
159172 appearance = 'ghost'
@@ -180,21 +193,32 @@ const NotificationChipList = ({
180193} ;
181194
182195const NotificationDraftForm = ( {
196+ addDraftAction,
183197 draft,
184198 queueCurrentDraft,
185199 queuedNotifications,
186200 registerQueuedNotifications,
201+ toggleDraftActionInPayload,
187202 removeQueuedNotification,
188203 setDraft,
204+ updateDraftAction,
189205} : {
206+ addDraftAction : ( ) => void ;
190207 draft : NotificationDraft ;
191208 queueCurrentDraft : ( ) => void ;
192209 queuedNotifications : QueuedNotification [ ] ;
193210 registerQueuedNotifications : ( ) => void ;
211+ toggleDraftActionInPayload : ( id : string ) => void ;
194212 removeQueuedNotification : ( id : string ) => void ;
195213 setDraft : Dispatch < SetStateAction < NotificationDraft > > ;
214+ updateDraftAction : < Key extends keyof NotificationDraftAction > (
215+ actionId : string ,
216+ key : Key ,
217+ value : NotificationDraftAction [ Key ] ,
218+ ) => void ;
196219} ) => {
197220 const canQueueCurrentDraft = isDraftReady ( draft ) ;
221+ const canAddDraftAction = draft . actions . every ( ( action ) => action . includedInPayload ) ;
198222
199223 return (
200224 < >
@@ -254,27 +278,68 @@ const NotificationDraftForm = ({
254278 options = { targetPanelOptions }
255279 value = { draft . targetPanel }
256280 />
257- < TextInput
258- className = 'app__notification-dialog__text-input'
259- label = 'Action Label (optional)'
260- onChange = { ( event ) =>
261- setDraft ( ( current ) => ( { ...current , actionLabel : event . target . value } ) )
262- }
263- placeholder = 'Optional button label'
264- value = { draft . actionLabel }
265- />
266- < TextInput
267- className = 'app__notification-dialog__text-input app__notification-dialog__text-input--wide'
268- label = 'Action Follow-up Message (optional)'
269- onChange = { ( event ) =>
270- setDraft ( ( current ) => ( {
271- ...current ,
272- actionFeedback : event . target . value ,
273- } ) )
274- }
275- placeholder = 'Optional notification triggered by the action button'
276- value = { draft . actionFeedback }
277- />
281+ < div className = 'app__notification-dialog__actions' >
282+ < div className = 'app__notification-dialog__actions-header' >
283+ < span className = 'app__notification-dialog__field-label' >
284+ Action(s) (optional)
285+ </ span >
286+ </ div >
287+ < div className = 'app__notification-dialog__actions-list' >
288+ { draft . actions . map ( ( action , index ) => {
289+ const canAddActionToPayload = isDraftActionReady ( action ) ;
290+
291+ return (
292+ < div className = 'app__notification-dialog__action-row' key = { action . id } >
293+ < TextInput
294+ className = 'app__notification-dialog__text-input'
295+ label = { `Action ${ index + 1 } Label` }
296+ onChange = { ( event ) =>
297+ updateDraftAction ( action . id , 'label' , event . target . value )
298+ }
299+ placeholder = 'Optional button label'
300+ value = { action . label }
301+ />
302+ < TextInput
303+ className = 'app__notification-dialog__text-input'
304+ label = { `Action ${ index + 1 } Follow-up Message` }
305+ onChange = { ( event ) =>
306+ updateDraftAction ( action . id , 'feedback' , event . target . value )
307+ }
308+ placeholder = 'Optional alert triggered by the action button'
309+ value = { action . feedback }
310+ />
311+ < Button
312+ aria-label = {
313+ action . includedInPayload
314+ ? 'Remove action from payload'
315+ : 'Add action to payload'
316+ }
317+ appearance = 'outline'
318+ circular
319+ className = 'app__notification-dialog__action-toggle'
320+ disabled = { ! action . includedInPayload && ! canAddActionToPayload }
321+ onClick = { ( ) => toggleDraftActionInPayload ( action . id ) }
322+ size = 'xs'
323+ variant = 'secondary'
324+ >
325+ { action . includedInPayload ? < IconMinus /> : < IconPlusSmall /> }
326+ </ Button >
327+ </ div >
328+ ) ;
329+ } ) }
330+ </ div >
331+ < Button
332+ appearance = 'ghost'
333+ className = 'app__notification-dialog__action-add'
334+ disabled = { ! canAddDraftAction }
335+ onClick = { addDraftAction }
336+ size = 'sm'
337+ variant = 'secondary'
338+ >
339+ < IconPlusSmall />
340+ Add action
341+ </ Button >
342+ </ div >
278343 </ div >
279344 </ div >
280345 </ Prompt . Body >
@@ -325,7 +390,25 @@ export const NotificationPromptDialog = ({
325390} : {
326391 referenceElement : HTMLElement | null ;
327392} ) => {
328- const [ draft , setDraft ] = useState ( initialDraft ) ;
393+ const actionIdRef = useRef ( 0 ) ;
394+ const createDraftAction = useCallback ( ( ) : NotificationDraftAction => {
395+ actionIdRef . current += 1 ;
396+
397+ return {
398+ feedback : '' ,
399+ id : `draft-notification-action-${ actionIdRef . current } ` ,
400+ includedInPayload : false ,
401+ label : '' ,
402+ } ;
403+ } , [ ] ) ;
404+ const createInitialDraftState = useCallback (
405+ ( ) : NotificationDraft => ( {
406+ ...initialDraft ,
407+ actions : [ createDraftAction ( ) ] ,
408+ } ) ,
409+ [ createDraftAction ] ,
410+ ) ;
411+ const [ draft , setDraft ] = useState < NotificationDraft > ( ( ) => createInitialDraftState ( ) ) ;
329412 const [ queuedNotifications , setQueuedNotifications ] = useState < QueuedNotification [ ] > (
330413 [ ] ,
331414 ) ;
@@ -337,9 +420,9 @@ export const NotificationPromptDialog = ({
337420 const dialogIsOpen = useDialogIsOpen ( notificationPromptDialogId , dialogManager ?. id ) ;
338421
339422 const resetState = useCallback ( ( ) => {
340- setDraft ( initialDraft ) ;
423+ setDraft ( createInitialDraftState ( ) ) ;
341424 setQueuedNotifications ( [ ] ) ;
342- } , [ ] ) ;
425+ } , [ createInitialDraftState ] ) ;
343426
344427 useEffect ( ( ) => {
345428 if ( dialogIsOpen ) return ;
@@ -376,8 +459,14 @@ export const NotificationPromptDialog = ({
376459 setQueuedNotifications ( ( current ) => [
377460 ...current ,
378461 {
379- actionFeedback : draft . actionFeedback ,
380- actionLabel : draft . actionLabel ,
462+ actions : draft . actions
463+ . map ( ( action ) => ( {
464+ includedInPayload : action . includedInPayload ,
465+ feedback : action . feedback . trim ( ) ,
466+ label : action . label . trim ( ) ,
467+ } ) )
468+ . filter ( ( action ) => action . includedInPayload && action . label && action . feedback )
469+ . map ( ( { feedback, label } ) => ( { feedback, label } ) ) ,
381470 duration,
382471 entryDirection : draft . entryDirection as NotificationListEnterFrom ,
383472 id : `queued-notification-${ chipIdRef . current } ` ,
@@ -386,8 +475,8 @@ export const NotificationPromptDialog = ({
386475 targetPanel : draft . targetPanel as NotificationTargetPanel ,
387476 } ,
388477 ] ) ;
389- setDraft ( initialDraft ) ;
390- } , [ draft ] ) ;
478+ setDraft ( createInitialDraftState ( ) ) ;
479+ } , [ createInitialDraftState , draft ] ) ;
391480
392481 const registerQueuedNotifications = useCallback ( ( ) => {
393482 queuedNotifications . forEach ( publishNotification ) ;
@@ -398,6 +487,56 @@ export const NotificationPromptDialog = ({
398487 setQueuedNotifications ( ( current ) => current . filter ( ( item ) => item . id !== id ) ) ;
399488 } , [ ] ) ;
400489
490+ const addDraftAction = useCallback ( ( ) => {
491+ setDraft ( ( current ) => ( {
492+ ...current ,
493+ actions : [ ...current . actions , createDraftAction ( ) ] ,
494+ } ) ) ;
495+ } , [ createDraftAction ] ) ;
496+
497+ const toggleDraftActionInPayload = useCallback ( ( id : string ) => {
498+ setDraft ( ( current ) => {
499+ return {
500+ ...current ,
501+ actions : current . actions . map ( ( action ) =>
502+ action . id === id
503+ ? { ...action , includedInPayload : ! action . includedInPayload }
504+ : action ,
505+ ) ,
506+ } ;
507+ } ) ;
508+ } , [ ] ) ;
509+
510+ const updateDraftAction = useCallback (
511+ < Key extends keyof NotificationDraftAction > (
512+ actionId : string ,
513+ key : Key ,
514+ value : NotificationDraftAction [ Key ] ,
515+ ) => {
516+ setDraft ( ( current ) => ( {
517+ ...current ,
518+ actions : current . actions . map ( ( action ) =>
519+ action . id === actionId
520+ ? {
521+ ...action ,
522+ [ key ] : value ,
523+ includedInPayload :
524+ key === 'feedback' || key === 'label'
525+ ? isDraftActionReady ( {
526+ ...action ,
527+ [ key ] : value ,
528+ } )
529+ ? action . includedInPayload
530+ : false
531+ : action . includedInPayload ,
532+ }
533+ : action ,
534+ ) ,
535+ } ) ) ;
536+ } ,
537+ [ ] ,
538+ ) ;
539+
401540 return (
402541 < DraggableDialog
403542 dialogClassName = 'app__notification-dialog'
@@ -412,12 +551,15 @@ export const NotificationPromptDialog = ({
412551 title = 'Trigger Notification'
413552 >
414553 < NotificationDraftForm
554+ addDraftAction = { addDraftAction }
415555 draft = { draft }
416556 queueCurrentDraft = { queueCurrentDraft }
417557 queuedNotifications = { queuedNotifications }
418558 registerQueuedNotifications = { registerQueuedNotifications }
419559 removeQueuedNotification = { removeQueuedNotification }
420560 setDraft = { setDraft }
561+ toggleDraftActionInPayload = { toggleDraftActionInPayload }
562+ updateDraftAction = { updateDraftAction }
421563 />
422564 </ DraggableDialog >
423565 ) ;
0 commit comments