11'use client'
22
3- import { useEffect , useRef , useState } from 'react'
3+ import { type ReactNode , useEffect , useRef , useState } from 'react'
44import { Button , cn , scrollFadeAttributes , scrollFadeClass , useScrollEdges } from '@sim/emcn'
55import { ArrowUp , Search } from '@sim/emcn/icons'
66import { useRouter } from 'next/navigation'
@@ -122,7 +122,7 @@ function SearchField({
122122
123123/**
124124 * Sim Search over the organization's sources. Empty, it is the greeting over the
125- * query field, centered like Home; once a query is submitted the field docks at
125+ * query field, centered like Home; once results arrive the field docks at
126126 * the top of the page — where every other organization page's title sits — and
127127 * the results scroll beneath it under the sidebar's edge fade. The submitted
128128 * query lives in the URL; the field holds the draft until the next submit.
@@ -141,10 +141,6 @@ function OrganizationSearchContent() {
141141 const query = q . trim ( )
142142 const scope : ResourceScope = { kind : 'organization' , organizationId : organization . id }
143143
144- const scrollContainerRef = useRef < HTMLDivElement > ( null )
145- const scrollContentRef = useRef < HTMLDivElement > ( null )
146- const scrollEdges = useScrollEdges ( scrollContainerRef , { contentRef : scrollContentRef } )
147-
148144 const summarize = ( message : string , assistantSearch : WorkspaceSearchFilters ) => {
149145 MothershipHandoffStorage . store (
150146 { message, assistantSearch } ,
@@ -159,49 +155,95 @@ function OrganizationSearchContent() {
159155 void setParams ( { q : next } )
160156 }
161157
162- const searching = query . length > 0
158+ const renderLayout = ( results : ReactNode , docked : boolean ) => (
159+ < SearchLayout query = { q } onSubmit = { submit } docked = { docked } >
160+ { results }
161+ </ SearchLayout >
162+ )
163+
164+ return query ? (
165+ < KnowledgeSearchResults
166+ scope = { scope }
167+ query = { query }
168+ onSummarize = { summarize }
169+ renderLayout = { renderLayout }
170+ />
171+ ) : (
172+ renderLayout ( null , false )
173+ )
174+ }
175+
176+ interface SearchLayoutProps {
177+ query : string
178+ onSubmit : ( draft : string ) => void
179+ docked : boolean
180+ children : ReactNode
181+ }
182+
183+ function SearchLayout ( { query, onSubmit, docked, children } : SearchLayoutProps ) {
184+ const { organization } = useOrganizationContext ( )
185+ const scrollContainerRef = useRef < HTMLDivElement > ( null )
186+ const scrollContentRef = useRef < HTMLDivElement > ( null )
187+ const scrollEdges = useScrollEdges ( scrollContainerRef , { contentRef : scrollContentRef } )
163188
164189 return (
165190 < div className = 'flex h-full min-h-0 flex-col bg-[var(--bg)]' >
166- { /* Reserved even while empty so the field docks where the page header sits. */ }
167191 < div className = { PAGE_HEADER_BAR } >
168192 < div className = { HEADER_ACTION_CLUSTER } />
169193 </ div >
170- { searching ? (
171- < >
172- < div className = { cn ( PAGE_COLUMN_CLASS , SIDEBAR_DIVIDER_PAD_ABOVE_CLASS , 'shrink-0 pt-8' ) } >
173- < SearchField key = { q } initialValue = { q } onSubmit = { submit } docked focusOnMount />
194+ < div
195+ className = { cn (
196+ 'flex min-h-0 flex-1 flex-col' ,
197+ ! docked && 'overflow-y-auto [scrollbar-gutter:stable_both-edges]'
198+ ) }
199+ >
200+ < div
201+ className = { cn (
202+ 'flex min-h-0 flex-col' ,
203+ docked ? 'flex-1' : 'min-h-full items-center justify-center px-6 pt-[2vh] pb-[22vh]'
204+ ) }
205+ >
206+ < div
207+ className = { cn (
208+ 'shrink-0' ,
209+ docked
210+ ? cn ( PAGE_COLUMN_CLASS , SIDEBAR_DIVIDER_PAD_ABOVE_CLASS , 'pt-8' )
211+ : 'w-full max-w-chat'
212+ ) }
213+ >
214+ { ! docked && (
215+ < h1 className = 'mb-7 text-balance text-center font-season text-[26px] text-[var(--text-primary)] leading-[1.15] tracking-[-0.01em] sm:text-[28px]' >
216+ Search { organization . name }
217+ </ h1 >
218+ ) }
219+ < SearchField
220+ key = { query }
221+ initialValue = { query }
222+ onSubmit = { onSubmit }
223+ docked = { docked }
224+ focusOnMount
225+ />
174226 </ div >
175227 < div
176228 ref = { scrollContainerRef }
177229 className = { cn (
178- SIDEBAR_DIVIDER_PAD_BELOW_CLASS ,
179- SIDEBAR_DIVIDER_PAD_ABOVE_CLASS ,
180- scrollFadeClass ,
181- 'min-h-0 flex-1 overflow-y-auto [scrollbar-gutter:stable_both-edges]'
230+ docked
231+ ? cn (
232+ SIDEBAR_DIVIDER_PAD_BELOW_CLASS ,
233+ SIDEBAR_DIVIDER_PAD_ABOVE_CLASS ,
234+ scrollFadeClass ,
235+ 'min-h-0 flex-1 overflow-y-auto [scrollbar-gutter:stable_both-edges]'
236+ )
237+ : 'w-full max-w-chat'
182238 ) }
183239 { ...scrollFadeAttributes ( scrollEdges ) }
184240 >
185- { /* The rows carry their own `px-2`; this gutter brings each row's mark under the
186- field's own search glyph, so results read as a column hanging from the field. */ }
187- < div ref = { scrollContentRef } className = { cn ( PAGE_COLUMN_CLASS , 'px-8' ) } >
188- < KnowledgeSearchResults scope = { scope } query = { query } onSummarize = { summarize } />
189- </ div >
190- </ div >
191- </ >
192- ) : (
193- < div className = 'min-h-0 flex-1 overflow-y-auto [scrollbar-gutter:stable_both-edges]' >
194- { /* Asymmetric padding biases the group up so heading and field sit at the optical center, as on Home */ }
195- < div className = 'flex min-h-full flex-col items-center justify-center px-6 pt-[2vh] pb-[22vh]' >
196- < h1 className = 'mb-7 max-w-chat text-balance font-season text-[26px] text-[var(--text-primary)] leading-[1.15] tracking-[-0.01em] sm:text-[28px]' >
197- Search { organization . name }
198- </ h1 >
199- < div className = 'w-full max-w-chat' >
200- < SearchField key = { q } initialValue = { q } onSubmit = { submit } focusOnMount />
241+ < div ref = { scrollContentRef } className = { docked ? cn ( PAGE_COLUMN_CLASS , 'px-8' ) : 'px-2' } >
242+ { children }
201243 </ div >
202244 </ div >
203245 </ div >
204- ) }
246+ </ div >
205247 </ div >
206248 )
207249}
0 commit comments