@@ -38,6 +38,141 @@ import {
3838
3939type Props = Record < string , unknown > ;
4040
41+ const dialogStyle : React . CSSProperties = {
42+ display : "flex" ,
43+ flexDirection : "column" ,
44+ height : "72vh" ,
45+ maxWidth : "980px" ,
46+ overflow : "hidden" ,
47+ padding : 0 ,
48+ width : "min(980px, calc(100vw - 64px))" ,
49+ } ;
50+
51+ const modalStyle : React . CSSProperties = {
52+ display : "flex" ,
53+ flex : 1 ,
54+ flexDirection : "column" ,
55+ margin : 0 ,
56+ minHeight : 0 ,
57+ overflow : "hidden" ,
58+ padding : 0 ,
59+ pointerEvents : "all" ,
60+ } ;
61+
62+ const searchHeaderStyle : React . CSSProperties = {
63+ alignItems : "center" ,
64+ borderBottom : "1px solid rgba(31, 31, 31, 0.12)" ,
65+ display : "flex" ,
66+ flexShrink : 0 ,
67+ gap : "8px" ,
68+ padding : "12px 16px" ,
69+ } ;
70+
71+ const bodyStyle : React . CSSProperties = {
72+ display : "flex" ,
73+ flex : 1 ,
74+ minHeight : 0 ,
75+ overflow : "hidden" ,
76+ } ;
77+
78+ const resultsPanelStyle : React . CSSProperties = {
79+ borderRight : "1px solid rgba(31, 31, 31, 0.12)" ,
80+ flexShrink : 0 ,
81+ minHeight : 0 ,
82+ overflowY : "auto" ,
83+ width : "38%" ,
84+ } ;
85+
86+ const previewColumnStyle : React . CSSProperties = {
87+ display : "flex" ,
88+ flex : 1 ,
89+ flexDirection : "column" ,
90+ minHeight : 0 ,
91+ overflow : "hidden" ,
92+ } ;
93+
94+ const previewPanelStyle : React . CSSProperties = {
95+ flex : 1 ,
96+ minHeight : 0 ,
97+ overflowY : "auto" ,
98+ padding : "32px" ,
99+ } ;
100+
101+ const emptyPanelStyle : React . CSSProperties = {
102+ alignItems : "center" ,
103+ display : "flex" ,
104+ height : "100%" ,
105+ justifyContent : "center" ,
106+ padding : "24px" ,
107+ } ;
108+
109+ const messagePanelStyle : React . CSSProperties = {
110+ alignItems : "center" ,
111+ color : "rgba(31, 31, 31, 0.5)" ,
112+ display : "flex" ,
113+ flex : 1 ,
114+ fontSize : "14px" ,
115+ justifyContent : "center" ,
116+ minHeight : 0 ,
117+ padding : "48px 16px" ,
118+ textAlign : "center" ,
119+ width : "100%" ,
120+ } ;
121+
122+ const focusSearchInput = ( input : HTMLInputElement | null ) : void => {
123+ input ?. focus ( ) ;
124+ } ;
125+
126+ const resultTitleStyle : React . CSSProperties = {
127+ color : "#1f1f1f" ,
128+ fontSize : "14px" ,
129+ lineHeight : 1.4 ,
130+ wordBreak : "break-word" ,
131+ } ;
132+
133+ const resultExcerptStyle : React . CSSProperties = {
134+ color : "rgba(31, 31, 31, 0.55)" ,
135+ display : "block" ,
136+ fontSize : "12px" ,
137+ lineHeight : 1.35 ,
138+ marginTop : "4px" ,
139+ } ;
140+
141+ const previewTitleStyle : React . CSSProperties = {
142+ color : "#1f1f1f" ,
143+ fontSize : "22px" ,
144+ lineHeight : 1.25 ,
145+ margin : "14px 0" ,
146+ } ;
147+
148+ const previewMetaStyle : React . CSSProperties = {
149+ color : "rgba(31, 31, 31, 0.55)" ,
150+ fontSize : "12px" ,
151+ letterSpacing : "0.02em" ,
152+ } ;
153+
154+ const previewBodyStyle : React . CSSProperties = {
155+ borderTop : "1px solid rgba(31, 31, 31, 0.12)" ,
156+ color : "#1f1f1f" ,
157+ fontSize : "15px" ,
158+ lineHeight : 1.55 ,
159+ marginTop : "24px" ,
160+ paddingTop : "16px" ,
161+ } ;
162+
163+ const getResultRowStyle = ( active : boolean ) : React . CSSProperties => ( {
164+ alignItems : "flex-start" ,
165+ background : active ? "rgba(95, 87, 192, 0.08)" : "transparent" ,
166+ border : 0 ,
167+ boxShadow : active ? "inset 3px 0 0 #5f57c0" : undefined ,
168+ cursor : "pointer" ,
169+ display : "flex" ,
170+ gap : "10px" ,
171+ padding : "12px 16px" ,
172+ textAlign : "left" ,
173+ width : "100%" ,
174+ } ) ;
175+
41176const getNodeBadgeText = ( node : DiscourseNode ) : string =>
42177 ( node . tag ?. trim ( ) || node . text ) . slice ( 0 , 3 ) . toUpperCase ( ) ;
43178
@@ -79,24 +214,21 @@ const ResultRow = ({
79214 < button
80215 type = "button"
81216 aria-selected = { active }
82- className = { `flex w-full cursor-pointer gap-2.5 border-0 px-4 py-3 text-left ${
83- active ? "bg-blue-50" : "bg-transparent"
84- } `}
217+ className = "dg-advanced-node-search-result"
85218 onClick = { onClick }
86219 onMouseEnter = { onMouseEnter }
87220 role = "option"
221+ style = { getResultRowStyle ( active ) }
88222 >
89223 < Tag minimal style = { getTagStyle ( nodeConfig ) } >
90224 { nodeConfig ? getNodeBadgeText ( nodeConfig ) : result . nodeTypeLabel }
91225 </ Tag >
92- < span className = "min-w-0" >
93- < span className = "text-sm leading-snug text-gray-900" >
226+ < span style = { { minWidth : 0 } } >
227+ < span style = { resultTitleStyle } >
94228 { renderHighlightedText ( stripTypePrefix ( result . title ) , keywords ) }
95229 </ span >
96230 { result . excerpt && (
97- < span className = "mt-1 block text-xs leading-snug text-gray-500" >
98- { result . excerpt }
99- </ span >
231+ < span style = { resultExcerptStyle } > { result . excerpt } </ span >
100232 ) }
101233 </ span >
102234 </ button >
@@ -117,37 +249,41 @@ const PreviewPane = ({
117249} ) => {
118250 if ( ! result ) {
119251 return (
120- < div className = "flex h-full items-center justify-center p-6" >
121- < NonIdealState
122- icon = "search"
123- title = "Search DG nodes"
124- description = "Type a keyword to preview matching discourse graph nodes."
125- />
252+ < div style = { previewColumnStyle } >
253+ < div style = { emptyPanelStyle } >
254+ < NonIdealState
255+ icon = "search"
256+ title = "Search DG nodes"
257+ description = "Type a keyword to preview matching discourse graph nodes."
258+ />
259+ </ div >
126260 </ div >
127261 ) ;
128262 }
129263
130264 if ( isLoading || ! content ) {
131265 return (
132- < div className = "flex h-full items-center justify-center p-6" >
133- < Spinner size = { SpinnerSize . SMALL } />
266+ < div style = { previewColumnStyle } >
267+ < div style = { emptyPanelStyle } >
268+ < Spinner size = { SpinnerSize . SMALL } />
269+ </ div >
134270 </ div >
135271 ) ;
136272 }
137273
138274 return (
139- < div className = "overflow-y-auto p-8" >
275+ < div style = { previewPanelStyle } >
140276 < Tag minimal style = { getTagStyle ( nodeConfig ) } >
141277 { nodeConfig ? nodeConfig . text : result . nodeTypeLabel }
142278 </ Tag >
143- < h2 className = "my-3.5 text-2xl leading-tight text-gray-900" >
279+ < h2 style = { previewTitleStyle } >
144280 { renderHighlightedText ( stripTypePrefix ( content . title ) , keywords ) }
145281 </ h2 >
146- < div className = "text-xs tracking-wide text-gray-500" >
282+ < div style = { previewMetaStyle } >
147283 Last modified: { formatMetadataDate ( result . lastModified ) } · Created:{ " " }
148284 { formatMetadataDate ( result . createdAt ) } · Author: { result . authorName }
149285 </ div >
150- < div className = "mt-6 border-t border-gray-200 pt-4 text-[15px] leading-relaxed text-gray-900" >
286+ < div style = { previewBodyStyle } >
151287 { content . lines . length ? (
152288 content . lines . map ( ( line , index ) => < p key = { index } > { line } </ p > )
153289 ) : (
@@ -197,7 +333,17 @@ const AdvancedNodeSearchDialog = ({
197333
198334 useEffect ( ( ) => {
199335 if ( ! isOpen ) return ;
200- inputRef . current ?. focus ( ) ;
336+
337+ const focusInput = ( ) => focusSearchInput ( inputRef . current ) ;
338+
339+ focusInput ( ) ;
340+ const rafId = requestAnimationFrame ( focusInput ) ;
341+ const timeoutId = window . setTimeout ( focusInput , 0 ) ;
342+
343+ return ( ) => {
344+ cancelAnimationFrame ( rafId ) ;
345+ window . clearTimeout ( timeoutId ) ;
346+ } ;
201347 } , [ isOpen ] ) ;
202348
203349 useEffect ( ( ) => {
@@ -372,27 +518,33 @@ const AdvancedNodeSearchDialog = ({
372518 results . length ,
373519 ] ) ;
374520
521+ const showSplitView = contentState === "results" ;
522+
375523 return (
376524 < Dialog
377525 autoFocus = { false }
378526 canEscapeKeyClose
379527 canOutsideClickClose
380- className = "roamjs-canvas-dialog w-[min(980px,calc(100vw-64px))] max-w-[980px]"
528+ className = "roamjs-canvas-dialog"
529+ enforceFocus = { false }
381530 isOpen = { isOpen }
382531 onClose = { onClose }
383- title = "DG node search"
532+ style = { dialogStyle }
384533 >
385534 < div
386- className = "pointer-events-auto"
387535 onClick = { ( event ) => event . stopPropagation ( ) }
388536 onKeyDown = { onKeyDown }
389537 onMouseDown = { ( event ) => event . stopPropagation ( ) }
390538 onMouseUp = { ( event ) => event . stopPropagation ( ) }
539+ style = { modalStyle }
391540 >
392- < div className = "flex items-center gap-2 border-b border-gray-200 px-4 py-3" >
541+ < div style = { searchHeaderStyle } >
393542 < InputGroup
394543 fill
395- inputRef = { inputRef }
544+ inputRef = { ( element ) => {
545+ inputRef . current = element ;
546+ if ( isOpen ) focusSearchInput ( element ) ;
547+ } }
396548 leftIcon = "search"
397549 onChange = { ( event : React . ChangeEvent < HTMLInputElement > ) =>
398550 setSearchTerm ( event . target . value )
@@ -402,67 +554,56 @@ const AdvancedNodeSearchDialog = ({
402554 />
403555 < Button icon = "cross" minimal onClick = { onClose } title = "Close search" />
404556 </ div >
405- < div className = "grid h-[560px] min-h-[420px] grid-cols-[minmax(280px,38%)_minmax(0,1fr)]" >
406- < div
407- aria-label = "Search results"
408- className = "overflow-y-auto border-r border-gray-200"
409- ref = { resultsPanelRef }
410- role = "listbox"
411- >
412- { contentState === "error" && (
413- < div className = "flex h-full items-center justify-center p-6" >
414- < NonIdealState
415- icon = "error"
416- title = "Search unavailable"
417- description = "Reload the extension and try again."
418- />
557+ < div style = { bodyStyle } >
558+ { showSplitView ? (
559+ < >
560+ < div
561+ aria-label = "Search results"
562+ ref = { resultsPanelRef }
563+ role = "listbox"
564+ style = { resultsPanelStyle }
565+ >
566+ { results . map ( ( result , index ) => (
567+ < ResultRow
568+ active = { index === activeIndex }
569+ key = { result . uid }
570+ keywords = { keywords }
571+ nodeConfig = { nodeConfigByType [ result . type ] }
572+ onClick = { ( ) => setActiveIndex ( index ) }
573+ onMouseEnter = { ( ) => setActiveIndex ( index ) }
574+ result = { result }
575+ />
576+ ) ) }
419577 </ div >
420- ) }
421- { contentState === "indexing" && (
422- < div className = "flex h-full items-center justify-center p-6" >
578+ < PreviewPane
579+ content = { activeContent }
580+ isLoading = {
581+ isEnrichingResults &&
582+ ! ! activeResult &&
583+ ! contentCacheRef . current . has ( activeResult . uid )
584+ }
585+ keywords = { keywords }
586+ nodeConfig = {
587+ activeResult ? nodeConfigByType [ activeResult . type ] : undefined
588+ }
589+ result = { activeResult }
590+ />
591+ </ >
592+ ) : (
593+ < div style = { messagePanelStyle } >
594+ { contentState === "indexing" && (
423595 < Spinner size = { SpinnerSize . SMALL } />
424- </ div >
425- ) }
426- { contentState === "initial" && (
427- < div className = "flex h-full items-center justify-center p-6" >
428- < NonIdealState title = "Search DG nodes" />
429- </ div >
430- ) }
431- { contentState === "empty" && (
432- < div className = "flex h-full items-center justify-center p-6" >
433- < NonIdealState
434- icon = "search"
435- title = "No results"
436- description = "Try another keyword."
437- />
438- </ div >
439- ) }
440- { contentState === "results" &&
441- results . map ( ( result , index ) => (
442- < ResultRow
443- active = { index === activeIndex }
444- key = { result . uid }
445- keywords = { keywords }
446- nodeConfig = { nodeConfigByType [ result . type ] }
447- onClick = { ( ) => setActiveIndex ( index ) }
448- onMouseEnter = { ( ) => setActiveIndex ( index ) }
449- result = { result }
450- />
451- ) ) }
452- </ div >
453- < PreviewPane
454- content = { activeContent }
455- isLoading = {
456- isEnrichingResults &&
457- ! ! activeResult &&
458- ! contentCacheRef . current . has ( activeResult . uid )
459- }
460- keywords = { keywords }
461- nodeConfig = {
462- activeResult ? nodeConfigByType [ activeResult . type ] : undefined
463- }
464- result = { activeResult }
465- />
596+ ) }
597+ { contentState === "empty" && (
598+ < span > No matches. Try another keyword.</ span >
599+ ) }
600+ { contentState === "error" && (
601+ < span >
602+ Search unavailable. Reload the extension and try again.
603+ </ span >
604+ ) }
605+ </ div >
606+ ) }
466607 </ div >
467608 </ div >
468609 </ Dialog >
0 commit comments