@@ -8,11 +8,13 @@ import {
88 ComboboxInput ,
99 ComboboxItem ,
1010 ComboboxList ,
11+ ComboboxListFooter ,
1112 ComboboxTrigger ,
1213} from "@posthog/quill" ;
13- import { type RefObject , useEffect , useRef , useState } from "react" ;
14+ import { defaultFilter } from "cmdk" ;
15+ import { type RefObject , useEffect , useMemo , useRef , useState } from "react" ;
1416
15- const COMBOBOX_LIMIT = 50 ;
17+ const COMBOBOX_INITIAL_LIMIT = 50 ;
1618
1719interface GitHubRepoPickerProps {
1820 value : string | null ;
@@ -27,6 +29,12 @@ interface GitHubRepoPickerProps {
2729 showSearchInput ?: boolean ;
2830 onRefresh ?: ( ) => void ;
2931 isRefreshing ?: boolean ;
32+ open ?: boolean ;
33+ onOpenChange ?: ( open : boolean ) => void ;
34+ searchQuery ?: string ;
35+ onSearchQueryChange ?: ( value : string ) => void ;
36+ hasMore ?: boolean ;
37+ onLoadMore ?: ( ) => void ;
3038}
3139
3240export function GitHubRepoPicker ( {
@@ -40,18 +48,47 @@ export function GitHubRepoPicker({
4048 showSearchInput = true ,
4149 onRefresh,
4250 isRefreshing = false ,
51+ open : controlledOpen ,
52+ onOpenChange,
53+ searchQuery : controlledSearchQuery ,
54+ onSearchQueryChange,
55+ hasMore : controlledHasMore ,
56+ onLoadMore,
4357} : GitHubRepoPickerProps ) {
4458 const triggerRef = useRef < HTMLButtonElement > ( null ) ;
45- const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
59+ const [ uncontrolledOpen , setUncontrolledOpen ] = useState ( false ) ;
60+ const [ uncontrolledSearchQuery , setUncontrolledSearchQuery ] = useState ( "" ) ;
61+ const [ visibleLimit , setVisibleLimit ] = useState ( COMBOBOX_INITIAL_LIMIT ) ;
62+ const open = controlledOpen ?? uncontrolledOpen ;
63+ const searchQuery = controlledSearchQuery ?? uncontrolledSearchQuery ;
64+ const remoteMode =
65+ controlledSearchQuery !== undefined ||
66+ onSearchQueryChange !== undefined ||
67+ controlledHasMore !== undefined ||
68+ onLoadMore !== undefined ;
69+ const showInlineLoadingState = remoteMode && open && isLoading ;
4670 const onlyRepo = repositories . length === 1 ? repositories [ 0 ] : null ;
71+ const trimmedSearchQuery = searchQuery . trim ( ) ;
72+ const filteredRepositoryCount = useMemo ( ( ) => {
73+ if ( ! trimmedSearchQuery ) {
74+ return repositories . length ;
75+ }
76+
77+ return repositories . reduce (
78+ ( count , repo ) =>
79+ count + ( defaultFilter ( repo , trimmedSearchQuery ) > 0 ? 1 : 0 ) ,
80+ 0 ,
81+ ) ;
82+ } , [ repositories , trimmedSearchQuery ] ) ;
83+ const hasMore = controlledHasMore ?? filteredRepositoryCount > visibleLimit ;
4784
4885 useEffect ( ( ) => {
4986 if ( onlyRepo && value !== onlyRepo ) {
5087 onChange ( onlyRepo ) ;
5188 }
5289 } , [ onlyRepo , value , onChange ] ) ;
5390
54- if ( isLoading ) {
91+ if ( isLoading && ! showInlineLoadingState ) {
5592 return (
5693 < Button variant = "outline" disabled size = "sm" >
5794 < GithubLogo size = { 16 } weight = "regular" style = { { flexShrink : 0 } } />
@@ -60,7 +97,7 @@ export function GitHubRepoPicker({
6097 ) ;
6198 }
6299
63- if ( repositories . length === 0 ) {
100+ if ( repositories . length === 0 && ! showInlineLoadingState ) {
64101 return (
65102 < Button variant = "outline" disabled size = "sm" >
66103 < GithubLogo size = { 16 } weight = "regular" style = { { flexShrink : 0 } } />
@@ -92,13 +129,27 @@ export function GitHubRepoPicker({
92129 return (
93130 < Combobox
94131 items = { repositories }
95- limit = { COMBOBOX_LIMIT }
132+ limit = { visibleLimit }
96133 value = { value }
97134 onValueChange = { ( v ) => {
98135 onChange ( v ? ( v as string ) : null ) ;
99136 } }
137+ open = { open }
138+ onOpenChange = { ( nextOpen ) => {
139+ setUncontrolledOpen ( nextOpen ) ;
140+ onOpenChange ?.( nextOpen ) ;
141+ if ( ! nextOpen ) {
142+ setUncontrolledSearchQuery ( "" ) ;
143+ onSearchQueryChange ?.( "" ) ;
144+ setVisibleLimit ( COMBOBOX_INITIAL_LIMIT ) ;
145+ }
146+ } }
100147 inputValue = { searchQuery }
101- onInputValueChange = { setSearchQuery }
148+ onInputValueChange = { ( nextSearchQuery ) => {
149+ setUncontrolledSearchQuery ( nextSearchQuery ) ;
150+ onSearchQueryChange ?.( nextSearchQuery ) ;
151+ setVisibleLimit ( COMBOBOX_INITIAL_LIMIT ) ;
152+ } }
102153 disabled = { disabled }
103154 >
104155 < ComboboxTrigger
@@ -150,7 +201,11 @@ export function GitHubRepoPicker({
150201 ) : null }
151202 </ div >
152203 ) : null }
153- < ComboboxEmpty > No repositories found.</ ComboboxEmpty >
204+ < ComboboxEmpty >
205+ { showInlineLoadingState
206+ ? "Loading repositories..."
207+ : "No repositories found." }
208+ </ ComboboxEmpty >
154209 < ComboboxList >
155210 { ( repo : string ) => (
156211 < ComboboxItem key = { repo } value = { repo } >
@@ -159,12 +214,48 @@ export function GitHubRepoPicker({
159214 ) }
160215 </ ComboboxList >
161216
162- { repositories . length > COMBOBOX_LIMIT && (
163- < div className = "px-2 py-1.5 text-center text-muted-foreground text-xs" >
164- { searchQuery
165- ? `Showing up to ${ COMBOBOX_LIMIT } matches — refine your search`
166- : `Showing ${ COMBOBOX_LIMIT } of ${ repositories . length } — type to filter` }
167- </ div >
217+ { ( hasMore ||
218+ ( remoteMode
219+ ? repositories . length > COMBOBOX_INITIAL_LIMIT
220+ : filteredRepositoryCount > COMBOBOX_INITIAL_LIMIT ) ) && (
221+ < ComboboxListFooter >
222+ < div className = "px-2 pb-2" >
223+ < div className = "px-1 pb-2 text-center text-muted-foreground text-xs" >
224+ { remoteMode
225+ ? trimmedSearchQuery
226+ ? `Showing ${ repositories . length } ${ hasMore ? "+" : "" } matches`
227+ : `Showing ${ repositories . length } ${ hasMore ? "+" : "" } repositories`
228+ : trimmedSearchQuery
229+ ? `Showing ${ Math . min ( visibleLimit , filteredRepositoryCount ) } of ${ filteredRepositoryCount } matches`
230+ : `Showing ${ Math . min ( visibleLimit , repositories . length ) } of ${ repositories . length } ` }
231+ </ div >
232+ { hasMore ? (
233+ < Button
234+ variant = "outline"
235+ size = "sm"
236+ className = "w-full justify-center"
237+ onMouseDown = { ( event ) => {
238+ event . preventDefault ( ) ;
239+ event . stopPropagation ( ) ;
240+ } }
241+ onClick = { ( event ) => {
242+ event . preventDefault ( ) ;
243+ event . stopPropagation ( ) ;
244+ if ( remoteMode ) {
245+ onLoadMore ?.( ) ;
246+ return ;
247+ }
248+
249+ setVisibleLimit (
250+ ( currentLimit ) => currentLimit + COMBOBOX_INITIAL_LIMIT ,
251+ ) ;
252+ } }
253+ >
254+ Load more
255+ </ Button >
256+ ) : null }
257+ </ div >
258+ </ ComboboxListFooter >
168259 ) }
169260 </ ComboboxContent >
170261 </ Combobox >
0 commit comments