1
1
import { AuthorizationAccessCheck } from "@flanksource-ui/components/Permissions/AuthorizationAccessCheck" ;
2
+ import CollapsiblePanel from "@flanksource-ui/ui/CollapsiblePanel/CollapsiblePanel" ;
2
3
import { Icon } from "@flanksource-ui/ui/Icons/Icon" ;
3
- import {
4
- Menu ,
5
- MenuButton ,
6
- MenuItem ,
7
- MenuItems ,
8
- Transition
9
- } from "@headlessui/react" ;
4
+ import { Popover , PopoverButton , PopoverPanel } from "@headlessui/react" ;
10
5
import { ChevronDownIcon } from "@heroicons/react/solid" ;
11
- import { Fragment , useState } from "react" ;
6
+ import { useMemo , useState } from "react" ;
12
7
import { useGetPlaybooksToRun } from "../../../../api/query-hooks/playbooks" ;
13
8
import { RunnablePlaybook } from "../../../../api/types/playbooks" ;
14
9
import PlaybookSpecIcon from "../../Settings/PlaybookSpecIcon" ;
@@ -43,47 +38,79 @@ export default function PlaybooksDropdownMenu({
43
38
config_id
44
39
} ) ;
45
40
41
+ const playbooksGroupedByCategory = useMemo (
42
+ ( ) =>
43
+ playbooks ?. reduce (
44
+ ( acc , playbook ) => {
45
+ const category = playbook . spec ?. category || "Uncategorized" ;
46
+ if ( ! acc [ category ] ) {
47
+ acc [ category ] = [ ] ;
48
+ }
49
+ acc [ category ] . push ( playbook ) ;
50
+ return acc ;
51
+ } ,
52
+ { } as Record <
53
+ string ,
54
+ ( RunnablePlaybook & {
55
+ spec : any ;
56
+ } ) [ ]
57
+ >
58
+ ) ,
59
+ [ playbooks ]
60
+ ) ;
61
+
46
62
if ( error || playbooks ?. length === 0 || isLoading ) {
47
63
return null ;
48
64
}
49
65
50
66
return (
51
67
< AuthorizationAccessCheck resource = { "playbook_runs" } action = { "write" } >
52
- < div className = "my-2 text-right" >
53
- < Menu as = "div" className = "relative inline-block text-left " >
54
- < MenuButton className = "btn-white px-2 py-1" >
68
+ < >
69
+ < Popover className = "group " >
70
+ < PopoverButton className = "btn-white px-2 py-1" >
55
71
< Icon name = "playbook" className = "mr-2 mt-0.5 h-5 w-5" />
56
72
Playbooks
57
- < ChevronDownIcon
58
- className = "-mr-1 ml-2 h-5 w-5 text-gray-400"
59
- aria-hidden = "true"
60
- />
61
- </ MenuButton >
62
-
63
- { /* @ts -ignore */ }
64
- < Transition
65
- as = { Fragment as any }
66
- enter = "transition ease-out duration-100"
67
- enterFrom = "transform opacity-0 scale-95"
68
- enterTo = "transform opacity-100 scale-100"
69
- leave = "transition ease-in duration-75"
70
- leaveFrom = "transform opacity-100 scale-100"
71
- leaveTo = "transform opacity-0 scale-95"
73
+ < ChevronDownIcon className = "size-5 group-data-[open]:rotate-180" />
74
+ </ PopoverButton >
75
+ < PopoverPanel
76
+ portal
77
+ className = "menu-items absolute right-0 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
78
+ anchor = "bottom start"
72
79
>
73
- < MenuItems portal className = "menu-items" anchor = "bottom start" >
74
- { playbooks ?. map ( ( playbook ) => (
75
- < MenuItem
76
- as = "button"
77
- className = "menu-item w-full"
78
- onClick = { ( ) => setSelectedPlaybookSpec ( playbook ) }
79
- key = { playbook . id }
80
- >
81
- < PlaybookSpecIcon playbook = { playbook } showLabel showTag />
82
- </ MenuItem >
83
- ) ) }
84
- </ MenuItems >
85
- </ Transition >
86
- </ Menu >
80
+ { playbooksGroupedByCategory && (
81
+ < >
82
+ { Object . entries ( playbooksGroupedByCategory ) . map (
83
+ ( [ category , playbooks ] ) => (
84
+ < CollapsiblePanel
85
+ key = { category }
86
+ Header = {
87
+ < div className = "flex flex-row items-center gap-2 text-sm text-gray-600" >
88
+ { category }
89
+ </ div >
90
+ }
91
+ iconClassName = "h-4 w-4"
92
+ isCollapsed
93
+ >
94
+ < div className = { `flex flex-col` } >
95
+ { playbooks . map ( ( playbook ) => (
96
+ < div
97
+ key = { playbook . id }
98
+ onClick = { ( ) => {
99
+ setSelectedPlaybookSpec ( playbook ) ;
100
+ } }
101
+ className = { `flex cursor-pointer flex-col justify-between gap-1 px-4 py-2 text-sm` }
102
+ >
103
+ < PlaybookSpecIcon playbook = { playbook } showLabel />
104
+ </ div >
105
+ ) ) }
106
+ </ div >
107
+ </ CollapsiblePanel >
108
+ )
109
+ ) }
110
+ </ >
111
+ ) }
112
+ </ PopoverPanel >
113
+ </ Popover >
87
114
{ selectedPlaybookSpec && (
88
115
< SubmitPlaybookRunForm
89
116
componentId = { component_id }
@@ -96,7 +123,7 @@ export default function PlaybooksDropdownMenu({
96
123
playbook = { selectedPlaybookSpec }
97
124
/>
98
125
) }
99
- </ div >
126
+ </ >
100
127
</ AuthorizationAccessCheck >
101
128
) ;
102
129
}
0 commit comments