11import type { FC } from 'react'
22import { useEffect , useState } from 'react'
33import { useTranslation } from 'react-i18next'
4- import { Box , HStack , Icon } from '@chakra-ui/react'
4+ import {
5+ Box ,
6+ VStack ,
7+ Icon ,
8+ Divider ,
9+ Tooltip ,
10+ IconButton as ChakraIconButton ,
11+ useDisclosure ,
12+ useBreakpointValue ,
13+ } from '@chakra-ui/react'
514import { ChevronLeftIcon , ChevronRightIcon , SearchIcon } from '@chakra-ui/icons'
15+ import { LuNetwork , LuSettings } from 'react-icons/lu'
616
717import IconButton from '@/components/Chakra/IconButton.tsx'
818import Panel from '@/components/react-flow/Panel.tsx'
919import SearchEntities from '@/modules/Workspace/components/filters/SearchEntities.tsx'
1020import DrawerFilterToolbox from '@/modules/Workspace/components/filters/DrawerFilterToolbox.tsx'
11- import { ANIMATION , TOOLBAR } from '@/modules/Theme/utils.ts'
21+ import LayoutSelector from '@/modules/Workspace/components/layout/LayoutSelector.tsx'
22+ import ApplyLayoutButton from '@/modules/Workspace/components/layout/ApplyLayoutButton.tsx'
23+ import LayoutPresetsManager from '@/modules/Workspace/components/layout/LayoutPresetsManager.tsx'
24+ import LayoutOptionsDrawer from '@/modules/Workspace/components/layout/LayoutOptionsDrawer.tsx'
25+ import { useLayoutEngine } from '@/modules/Workspace/hooks/useLayoutEngine'
26+ import useWorkspaceStore from '@/modules/Workspace/hooks/useWorkspaceStore'
27+ import { useKeyboardShortcut } from '@/hooks/useKeyboardShortcut'
28+ import config from '@/config'
29+ import { ANIMATION } from '@/modules/Theme/utils.ts'
1230
1331const CanvasToolbar : FC = ( ) => {
1432 const { t } = useTranslation ( )
1533 const [ expanded , setExpanded ] = useState ( false )
1634 const [ contentVisible , setContentVisible ] = useState ( false )
1735
36+ const dividerOrientation = useBreakpointValue < 'horizontal' | 'vertical' > ( {
37+ base : 'horizontal' ,
38+ xl : 'vertical' ,
39+ } )
40+ const tooltipPlacement = useBreakpointValue < 'top' | 'bottom' > ( {
41+ base : 'top' ,
42+ xl : 'bottom' ,
43+ } )
44+
45+ const { applyLayout } = useLayoutEngine ( )
46+ const { layoutConfig } = useWorkspaceStore ( )
47+ const { isOpen : isLayoutDrawerOpen , onOpen : onLayoutDrawerOpen , onClose : onLayoutDrawerClose } = useDisclosure ( )
48+
49+ useKeyboardShortcut ( {
50+ key : 'l' ,
51+ ctrl : true ,
52+ callback : ( ) => {
53+ applyLayout ( )
54+ } ,
55+ description : 'Apply current layout' ,
56+ } )
57+
1858 useEffect ( ( ) => {
1959 let timeout : NodeJS . Timeout
2060 if ( expanded ) {
@@ -26,64 +66,169 @@ const CanvasToolbar: FC = () => {
2666 } , [ expanded ] )
2767
2868 return (
29- < Panel
30- position = "top-right"
31- data-testid = "content-toolbar"
32- role = "group"
33- aria-label = { t ( 'workspace.canvas.toolbar.search-filter' ) }
34- >
35- < Box
36- m = "1.2px"
37- display = "flex"
38- alignItems = "center"
39- transition = "max-width 0.4s cubic-bezier(0.4,0,0.2,1)"
40- maxWidth = { expanded ? TOOLBAR . MAX_WIDTH : TOOLBAR . MIN_WIDTH }
41- width = "auto"
42- minHeight = "40px"
43- boxShadow = "md"
44- borderRadius = "md"
45- position = "relative"
46- overflow = "hidden"
47- _focusWithin = { { boxShadow : 'outline' } }
69+ < >
70+ < Panel
71+ position = "top-left"
72+ data-testid = "content-toolbar"
73+ role = "group"
74+ aria-label = { t ( 'workspace.canvas.toolbar.search-filter' ) }
4875 >
49- < IconButton
50- data-testid = "toolbox-search-expand"
51- aria-label = { t ( 'workspace.controls.expand' ) }
52- icon = {
53- < >
54- < Icon as = { ChevronLeftIcon } boxSize = "24px" />
55- < SearchIcon />
56- </ >
57- }
58- onClick = { ( ) => setExpanded ( true ) }
59- variant = "ghost"
60- size = "sm"
61- mx = { 2 }
62- display = { expanded ? 'none' : 'inline-flex' }
63- />
64- < HStack
65- spacing = { 2 }
66- ml = { 2 }
67- width = "100%"
68- opacity = { expanded ? 1 : 0 }
69- transition = "opacity 0.4s cubic-bezier(0.4,0,0.2,1)"
70- pointerEvents = { contentVisible ? 'auto' : 'none' }
71- style = { { display : contentVisible ? 'flex' : 'none' } }
76+ < Box
77+ m = "1.2px"
78+ display = "flex"
79+ flexDirection = { { base : 'column' , xl : 'row' } }
80+ transition = "all 0.4s cubic-bezier(0.4,0,0.2,1)"
81+ // maxWidth={{
82+ // base: expanded ? '100vw' : '56px',
83+ // md: expanded ? '90vw' : '56px',
84+ // xl: expanded ? TOOLBAR.MAX_WIDTH : TOOLBAR.MIN_WIDTH,
85+ // }}
86+ // width={{
87+ // base: expanded ? '100vw' : 'auto',
88+ // md: expanded ? 'auto' : 'auto',
89+ // }}
90+ // minHeight={{ base: '48px', md: '40px' }}
91+ boxShadow = "md"
92+ borderRadius = "md"
93+ position = "relative"
94+ overflow = "hidden"
95+ bg = "white"
96+ _dark = { { bg : 'gray.800' } }
97+ _focusWithin = { { boxShadow : 'outline' } }
7298 >
73- < SearchEntities />
74- < DrawerFilterToolbox />
7599 < IconButton
76- data-testid = "toolbox-search-collapse"
77- aria-label = { t ( 'workspace.controls.collapse' ) }
78- icon = { < Icon as = { ChevronRightIcon } boxSize = "24px" /> }
79- onClick = { ( ) => setExpanded ( false ) }
100+ data-testid = "toolbox-search-expand"
101+ aria-label = { t ( 'workspace.controls.expand' ) }
102+ aria-expanded = "false"
103+ aria-controls = "workspace-toolbar-content"
104+ icon = {
105+ < >
106+ < SearchIcon mr = "2px" />
107+ < Icon as = { LuNetwork } boxSize = "18px" />
108+ < Icon
109+ as = { ChevronRightIcon }
110+ boxSize = "24px"
111+ // transform={{ base: 'rotate(-90deg)', xl: 'rotate(0deg)' }}
112+ />
113+ </ >
114+ }
115+ onClick = { ( ) => setExpanded ( true ) }
80116 variant = "ghost"
81117 size = "sm"
82- mr = { 2 }
118+ m = { 2 }
119+ // minWidth="120px"
120+ display = { expanded ? 'none' : 'inline-flex' }
83121 />
84- </ HStack >
85- </ Box >
86- </ Panel >
122+
123+ < Box
124+ id = "workspace-toolbar-content"
125+ role = "region"
126+ aria-label = { t ( 'workspace.canvas.toolbar.search-filter' ) }
127+ display = { contentVisible ? 'flex' : 'none' }
128+ flexDirection = { { base : 'column' , xl : 'row' } }
129+ gap = { { base : 3 , xl : 2 } }
130+ p = { { base : 3 , xl : 2 } }
131+ width = "100%"
132+ opacity = { expanded ? 1 : 0 }
133+ transition = "opacity 0.4s cubic-bezier(0.4,0,0.2,1)"
134+ pointerEvents = { contentVisible ? 'auto' : 'none' }
135+ >
136+ < VStack
137+ spacing = { { base : 2 , xl : 0 } }
138+ align = "stretch"
139+ flex = { { base : '1' , xl : 'initial' } }
140+ sx = { {
141+ '& > *' : {
142+ width : { base : '100%' , xl : 'auto' } ,
143+ } ,
144+ } }
145+ >
146+ < Box display = "flex" flexDirection = { { base : 'column' , md : 'row' } } gap = { 2 } >
147+ < SearchEntities />
148+ < DrawerFilterToolbox />
149+ </ Box >
150+ </ VStack >
151+ { config . features . WORKSPACE_AUTO_LAYOUT && (
152+ < Divider
153+ orientation = { dividerOrientation }
154+ // h={{ base: 'auto', xl: '24px' }}
155+ // w={{ base: 'auto', xl: 'auto' }}
156+ borderColor = "gray.300"
157+ _dark = { { borderColor : 'gray.600' } }
158+ />
159+ ) }
160+ { config . features . WORKSPACE_AUTO_LAYOUT && (
161+ < VStack
162+ role = "region"
163+ data-testid = "layout-controls-panel"
164+ aria-label = { t ( 'workspace.autoLayout.controls.aria-label' ) }
165+ spacing = { { base : 2 , xl : 0 } }
166+ align = "stretch"
167+ flex = { { base : '1' , xl : 'initial' } }
168+ >
169+ < Box
170+ display = "flex"
171+ flexDirection = { { base : 'column' , md : 'row' , xl : 'row' } }
172+ gap = { 2 }
173+ sx = { {
174+ '& > *' : {
175+ width : { base : '100%' , md : 'auto' } ,
176+ } ,
177+ } }
178+ >
179+ < LayoutSelector />
180+ < ApplyLayoutButton />
181+ < Box display = "flex" gap = { 2 } width = "fit-content" >
182+ < LayoutPresetsManager />
183+ < Tooltip label = { t ( 'workspace.autoLayout.options.title' ) } placement = { tooltipPlacement } >
184+ < ChakraIconButton
185+ data-testid = "workspace-layout-options"
186+ aria-label = { t ( 'workspace.autoLayout.options.title' ) }
187+ icon = { < Icon as = { LuSettings } /> }
188+ size = "sm"
189+ variant = "ghost"
190+ onClick = { onLayoutDrawerOpen }
191+ width = { { base : '100%' , md : 'auto' } }
192+ />
193+ </ Tooltip >
194+ </ Box >
195+ </ Box >
196+ </ VStack >
197+ ) }
198+ < Divider
199+ orientation = { dividerOrientation }
200+ // h={{ base: 'auto', xl: '24px' }}
201+ // w={{ base: 'auto', xl: 'auto' }}
202+ borderColor = "gray.300"
203+ _dark = { { borderColor : 'gray.600' } }
204+ />
205+ < IconButton
206+ data-testid = "toolbox-search-collapse"
207+ aria-label = { t ( 'workspace.controls.collapse' ) }
208+ aria-expanded = "true"
209+ aria-controls = "workspace-toolbar-content"
210+ icon = { < Icon as = { ChevronLeftIcon } boxSize = "24px" /> }
211+ onClick = { ( ) => setExpanded ( false ) }
212+ variant = "ghost"
213+ size = "sm"
214+ alignSelf = { { base : 'center' , xl : 'center' } }
215+ mt = { { base : 2 , xl : 0 } }
216+ mb = { { base : 0 , xl : 0 } }
217+ />
218+ </ Box >
219+ </ Box >
220+ </ Panel >
221+
222+ { /* Layout Options Drawer */ }
223+ { config . features . WORKSPACE_AUTO_LAYOUT && (
224+ < LayoutOptionsDrawer
225+ isOpen = { isLayoutDrawerOpen }
226+ onClose = { onLayoutDrawerClose }
227+ algorithmType = { layoutConfig . currentAlgorithm }
228+ options = { layoutConfig . options }
229+ />
230+ ) }
231+ </ >
87232 )
88233}
89234
0 commit comments