@@ -20,6 +20,7 @@ type SessionsScreenProps = {
2020
2121type AvailabilityFilter = "all" | "online" | "offline" ;
2222type StatusFilter = "all" | NonNullable < SessionRecord [ "status" ] > ;
23+ type FilterId = "agent" | "availability" | "host" | "status" | "workspace" ;
2324
2425export function SessionsScreen ( { hosts, language, sessions } : SessionsScreenProps ) {
2526 const { width } = useWindowDimensions ( ) ;
@@ -30,6 +31,7 @@ export function SessionsScreen({ hosts, language, sessions }: SessionsScreenProp
3031 const [ workspaceRoot , setWorkspaceRoot ] = useState ( "all" ) ;
3132 const [ availability , setAvailability ] = useState < AvailabilityFilter > ( "all" ) ;
3233 const [ status , setStatus ] = useState < StatusFilter > ( "all" ) ;
34+ const [ openFilter , setOpenFilter ] = useState < FilterId | undefined > ( ) ;
3335 const [ copiedKey , setCopiedKey ] = useState < string | undefined > ( ) ;
3436
3537 const hostLabels = useMemo ( ( ) => {
@@ -136,7 +138,10 @@ export function SessionsScreen({ hosts, language, sessions }: SessionsScreenProp
136138 < TextInput
137139 accessibilityLabel = { t ( language , "搜索 Session" , "Search sessions" ) }
138140 autoCapitalize = "none"
139- onChangeText = { setQuery }
141+ onChangeText = { ( value ) => {
142+ setOpenFilter ( undefined ) ;
143+ setQuery ( value ) ;
144+ } }
140145 placeholder = { t ( language , "搜索 Session、主机、Agent、目录、ID" , "Search session, host, agent, workspace, ID" ) }
141146 placeholderTextColor = { colors . muted }
142147 style = { [ inputStyle , { flex : compact ? undefined : 1 , width : compact ? "100%" : undefined } ] }
@@ -150,46 +155,76 @@ export function SessionsScreen({ hosts, language, sessions }: SessionsScreenProp
150155 < View style = { filterRailStyle } >
151156 < SelectFilter
152157 allLabel = { t ( language , "全部状态" , "All states" ) }
158+ filterId = "status"
153159 label = { t ( language , "状态" , "State" ) }
160+ openFilter = { openFilter }
154161 options = { [
155162 [ "active" , t ( language , "运行中" , "Active" ) ] ,
156163 [ "starting" , t ( language , "启动中" , "Starting" ) ] ,
157164 [ "waiting_authorization" , t ( language , "等待授权" , "Waiting" ) ] ,
158165 [ "failed" , t ( language , "失败" , "Failed" ) ] ,
159166 ] }
160167 value = { status }
161- onChange = { ( value ) => setStatus ( value as StatusFilter ) }
168+ onChange = { ( value ) => {
169+ setStatus ( value as StatusFilter ) ;
170+ setOpenFilter ( undefined ) ;
171+ } }
172+ onToggle = { setOpenFilter }
162173 />
163174 < SelectFilter
164175 allLabel = { t ( language , "全部在线状态" , "All availability" ) }
176+ filterId = "availability"
165177 label = { t ( language , "在线状态" , "Availability" ) }
178+ openFilter = { openFilter }
166179 options = { [
167180 [ "online" , t ( language , "在线" , "Online" ) ] ,
168181 [ "offline" , t ( language , "离线" , "Offline" ) ] ,
169182 ] }
170183 value = { availability }
171- onChange = { ( value ) => setAvailability ( value as AvailabilityFilter ) }
184+ onChange = { ( value ) => {
185+ setAvailability ( value as AvailabilityFilter ) ;
186+ setOpenFilter ( undefined ) ;
187+ } }
188+ onToggle = { setOpenFilter }
172189 />
173190 < SelectFilter
174191 allLabel = { t ( language , "全部主机" , "All hosts" ) }
192+ filterId = "host"
175193 label = { t ( language , "主机" , "Host" ) }
194+ openFilter = { openFilter }
176195 options = { filterOptions . hosts . map ( ( id ) => [ id , hostLabels . get ( id ) ?? shortId ( id ) ] as const ) }
177196 value = { hostId }
178- onChange = { setHostId }
197+ onChange = { ( value ) => {
198+ setHostId ( value ) ;
199+ setOpenFilter ( undefined ) ;
200+ } }
201+ onToggle = { setOpenFilter }
179202 />
180203 < SelectFilter
181204 allLabel = { t ( language , "全部 Agent" , "All agents" ) }
205+ filterId = "agent"
182206 label = { t ( language , "Agent" , "Agent" ) }
207+ openFilter = { openFilter }
183208 options = { filterOptions . agents }
184209 value = { agentKey }
185- onChange = { setAgentKey }
210+ onChange = { ( value ) => {
211+ setAgentKey ( value ) ;
212+ setOpenFilter ( undefined ) ;
213+ } }
214+ onToggle = { setOpenFilter }
186215 />
187216 < SelectFilter
188217 allLabel = { t ( language , "全部目录" , "All workspaces" ) }
218+ filterId = "workspace"
189219 label = { t ( language , "目录" , "Workspace" ) }
220+ openFilter = { openFilter }
190221 options = { filterOptions . workspaces }
191222 value = { workspaceRoot }
192- onChange = { setWorkspaceRoot }
223+ onChange = { ( value ) => {
224+ setWorkspaceRoot ( value ) ;
225+ setOpenFilter ( undefined ) ;
226+ } }
227+ onToggle = { setOpenFilter }
193228 />
194229 </ View >
195230
@@ -229,45 +264,51 @@ export function SessionsScreen({ hosts, language, sessions }: SessionsScreenProp
229264
230265function SelectFilter ( {
231266 allLabel,
267+ filterId,
232268 label,
233269 onChange,
270+ onToggle,
271+ openFilter,
234272 options,
235273 value,
236274} : {
237275 allLabel : string ;
276+ filterId : FilterId ;
238277 label : string ;
239278 onChange : ( value : string ) => void ;
279+ onToggle : ( filter : FilterId | undefined ) => void ;
280+ openFilter : FilterId | undefined ;
240281 options : readonly ( readonly [ string , string ] ) [ ] ;
241282 value : string ;
242283} ) {
243- const [ open , setOpen ] = useState ( false ) ;
284+ const open = openFilter === filterId ;
244285 const items = [ [ "all" , allLabel ] as const , ...options ] ;
245286 const selectedLabel = items . find ( ( [ optionValue ] ) => optionValue === value ) ?. [ 1 ] ?? allLabel ;
287+ const active = value !== "all" ;
246288 return (
247- < View style = { { minWidth : 156 , position : "relative" , zIndex : open ? 30 : 1 } } >
289+ < View style = { { minWidth : 150 , position : "relative" , zIndex : open ? 50 : 1 } } >
248290 < Text style = { [ common . eyebrow , { marginBottom : 4 } ] } > { label } </ Text >
249291 < Pressable
250292 accessibilityLabel = { label }
251293 accessibilityRole = "button"
252- onPress = { ( ) => setOpen ( ( current ) => ! current ) }
253- style = { selectButtonStyle }
294+ onPress = { ( ) => onToggle ( open ? undefined : filterId ) }
295+ style = { [ selectButtonStyle , active ? selectButtonActiveStyle : null ] }
254296 >
255297 < Text numberOfLines = { 1 } style = { selectButtonTextStyle } > { selectedLabel } </ Text >
256- < Text style = { selectCaretStyle } > { open ? "⌃ " : "⌄ " } </ Text >
298+ < Text style = { selectCaretStyle } > { open ? "▲ " : "▼ " } </ Text >
257299 </ Pressable >
258300 { open ? (
259301 < View style = { selectMenuStyle } >
260- < ScrollView keyboardShouldPersistTaps = "handled" style = { { maxHeight : 220 } } >
302+ < ScrollView keyboardShouldPersistTaps = "handled" style = { { maxHeight : 168 } } >
261303 { items . map ( ( [ optionValue , optionLabel ] ) => (
262304 < Pressable
263305 key = { optionValue }
264306 onPress = { ( ) => {
265307 onChange ( optionValue ) ;
266- setOpen ( false ) ;
267308 } }
268309 style = { [
269310 selectOptionStyle ,
270- optionValue === value ? { backgroundColor : colors . lime } : null ,
311+ optionValue === value ? selectOptionActiveStyle : null ,
271312 ] }
272313 >
273314 < Text numberOfLines = { 1 } style = { selectOptionTextStyle } >
@@ -554,33 +595,38 @@ const filterRailStyle = {
554595 alignItems : "flex-start" as const ,
555596 flexDirection : "row" as const ,
556597 flexWrap : "wrap" as const ,
557- gap : 10 ,
598+ gap : 8 ,
558599 paddingBottom : 2 ,
559600} ;
560601
561602const selectButtonStyle = {
562603 alignItems : "center" as const ,
563604 backgroundColor : "#FFFFFF" ,
564605 borderColor : colors . ink ,
565- borderRadius : 8 ,
606+ borderRadius : 7 ,
566607 borderWidth : 1 ,
567608 flexDirection : "row" as const ,
568- gap : 8 ,
569- minHeight : 34 ,
609+ gap : 6 ,
610+ minHeight : 32 ,
570611 paddingHorizontal : 10 ,
571612} ;
572613
614+ const selectButtonActiveStyle = {
615+ backgroundColor : "#F8FFE3" ,
616+ borderColor : colors . graphite ,
617+ } ;
618+
573619const selectButtonTextStyle = {
574620 color : colors . ink ,
575621 flex : 1 ,
576622 fontFamily : typography . sansSemi ,
577- fontSize : 13 ,
623+ fontSize : 12 ,
578624} ;
579625
580626const selectCaretStyle = {
581627 color : colors . muted ,
582628 fontFamily : typography . sansSemi ,
583- fontSize : 13 ,
629+ fontSize : 9 ,
584630} ;
585631
586632const selectMenuStyle = {
@@ -594,24 +640,28 @@ const selectMenuStyle = {
594640 position : "absolute" as const ,
595641 right : 0 ,
596642 shadowColor : colors . ink ,
597- shadowOffset : { width : 3 , height : 3 } ,
643+ shadowOffset : { width : 2 , height : 2 } ,
598644 shadowOpacity : 1 ,
599645 shadowRadius : 0 ,
600- top : 56 ,
646+ top : 52 ,
601647} ;
602648
603649const selectOptionStyle = {
604650 borderBottomColor : colors . line ,
605651 borderBottomWidth : 1 ,
606- minHeight : 34 ,
652+ minHeight : 30 ,
607653 justifyContent : "center" as const ,
608654 paddingHorizontal : 10 ,
609655} ;
610656
657+ const selectOptionActiveStyle = {
658+ backgroundColor : "#F5F1E8" ,
659+ } ;
660+
611661const selectOptionTextStyle = {
612662 color : colors . ink ,
613663 fontFamily : typography . sans ,
614- fontSize : 13 ,
664+ fontSize : 12 ,
615665} ;
616666
617667const rowStyle = {
0 commit comments