88import { Reorder } from "motion/react" ;
99import { useCallback , useEffect , useRef , useState } from "react" ;
1010import { useHotkeys } from "react-hotkeys-hook" ;
11+ import { useResizeObserver } from "usehooks-ts" ;
1112import { useShallow } from "zustand/shallow" ;
1213
1314import { Button } from "@hypr/ui/components/ui/button" ;
@@ -100,35 +101,11 @@ function Header({ tabs }: { tabs: Tab[] }) {
100101 ) ;
101102 const tabsScrollContainerRef = useRef < HTMLDivElement > ( null ) ;
102103 const handleNewEmptyTab = useNewEmptyTab ( ) ;
103- const [ scrollState , setScrollState ] = useState ( {
104- atStart : true ,
105- atEnd : true ,
106- } ) ;
107-
108- const updateScrollState = useCallback ( ( ) => {
109- const container = tabsScrollContainerRef . current ;
110- if ( ! container ) return ;
111-
112- const { scrollLeft, scrollWidth, clientWidth } = container ;
113- const atStart = scrollLeft <= 1 ;
114- const atEnd = scrollLeft + clientWidth >= scrollWidth - 1 ;
115- setScrollState ( { atStart, atEnd } ) ;
116- } , [ ] ) ;
117-
118- useEffect ( ( ) => {
119- const container = tabsScrollContainerRef . current ;
120- if ( ! container ) return ;
121-
122- updateScrollState ( ) ;
123- container . addEventListener ( "scroll" , updateScrollState ) ;
124- const resizeObserver = new ResizeObserver ( updateScrollState ) ;
125- resizeObserver . observe ( container ) ;
126-
127- return ( ) => {
128- container . removeEventListener ( "scroll" , updateScrollState ) ;
129- resizeObserver . disconnect ( ) ;
130- } ;
131- } , [ updateScrollState , tabs ] ) ;
104+ const [ isSearchManuallyExpanded , setIsSearchManuallyExpanded ] =
105+ useState ( false ) ;
106+ const { ref : rightContainerRef , hasSpace : hasSpaceForSearch } =
107+ useHasSpaceForSearch ( ) ;
108+ const scrollState = useScrollState ( tabsScrollContainerRef , [ tabs ] ) ;
132109
133110 const setTabRef = useScrollActiveTabIntoView ( tabs ) ;
134111 useTabsShortcuts ( ) ;
@@ -146,6 +123,7 @@ function Header({ tabs }: { tabs: Tab[] }) {
146123 < Button
147124 size = "icon"
148125 variant = "ghost"
126+ className = "shrink-0"
149127 onClick = { ( ) => leftsidebar . setExpanded ( true ) }
150128 >
151129 < PanelLeftOpenIcon size = { 16 } className = "text-neutral-600" />
@@ -225,21 +203,27 @@ function Header({ tabs }: { tabs: Tab[] }) {
225203 </ div >
226204
227205 < div
206+ ref = { rightContainerRef }
228207 data-tauri-drag-region
229208 className = "flex-1 flex h-full items-center justify-between"
230209 >
231- < Button
232- onClick = { handleNewEmptyTab }
233- variant = "ghost"
234- size = "icon"
235- className = "text-neutral-600"
236- >
237- < PlusIcon size = { 16 } />
238- </ Button >
210+ { ! ( isSearchManuallyExpanded && ! hasSpaceForSearch ) && (
211+ < Button
212+ onClick = { handleNewEmptyTab }
213+ variant = "ghost"
214+ size = "icon"
215+ className = "text-neutral-600"
216+ >
217+ < PlusIcon size = { 16 } />
218+ </ Button >
219+ ) }
239220
240- < div className = "flex items-center gap-1 h-full" >
221+ < div className = "flex items-center gap-1 h-full ml-auto " >
241222 < Update />
242- < Search />
223+ < Search
224+ hasSpace = { hasSpaceForSearch }
225+ onManualExpandChange = { setIsSearchManuallyExpanded }
226+ />
243227 </ div >
244228 </ div >
245229 </ div >
@@ -552,6 +536,52 @@ export function StandardTabWrapper({
552536 ) ;
553537}
554538
539+ function useHasSpaceForSearch ( ) {
540+ const ref = useRef < HTMLDivElement > ( null ) ;
541+ const { width = 0 } = useResizeObserver ( {
542+ ref : ref as React . RefObject < HTMLDivElement > ,
543+ } ) ;
544+ return { ref, hasSpace : width >= 220 } ;
545+ }
546+
547+ function useScrollState (
548+ ref : React . RefObject < HTMLDivElement | null > ,
549+ deps : unknown [ ] = [ ] ,
550+ ) {
551+ const [ scrollState , setScrollState ] = useState ( {
552+ atStart : true ,
553+ atEnd : true ,
554+ } ) ;
555+
556+ const updateScrollState = useCallback ( ( ) => {
557+ const container = ref . current ;
558+ if ( ! container ) return ;
559+
560+ const { scrollLeft, scrollWidth, clientWidth } = container ;
561+ setScrollState ( {
562+ atStart : scrollLeft <= 1 ,
563+ atEnd : scrollLeft + clientWidth >= scrollWidth - 1 ,
564+ } ) ;
565+ } , [ ref ] ) ;
566+
567+ useEffect ( ( ) => {
568+ const container = ref . current ;
569+ if ( ! container ) return ;
570+
571+ updateScrollState ( ) ;
572+ container . addEventListener ( "scroll" , updateScrollState ) ;
573+ const resizeObserver = new ResizeObserver ( updateScrollState ) ;
574+ resizeObserver . observe ( container ) ;
575+
576+ return ( ) => {
577+ container . removeEventListener ( "scroll" , updateScrollState ) ;
578+ resizeObserver . disconnect ( ) ;
579+ } ;
580+ } , [ updateScrollState , ...deps ] ) ;
581+
582+ return scrollState ;
583+ }
584+
555585function useScrollActiveTabIntoView ( tabs : Tab [ ] ) {
556586 const tabRefsMap = useRef < Map < string , HTMLDivElement > > ( new Map ( ) ) ;
557587
0 commit comments