Skip to content

Commit ba85fa0

Browse files
committed
feat: add search source list footer and search source results header
1 parent 5ea9177 commit ba85fa0

File tree

10 files changed

+112
-62
lines changed

10 files changed

+112
-62
lines changed

src/components/Chat/Chat.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ export type ChatPropsForwardedToComponentContext<
3535
| 'SearchBar'
3636
| 'SearchResults'
3737
| 'SearchResultsHeader'
38-
| 'SearchSourceEmptyResults'
38+
| 'SearchSourceResultsEmpty'
39+
| 'SearchSourceResultsHeader'
3940
| 'SearchSourceLoadingResults'
4041
| 'SearchSourceResultList'
41-
| 'SearchSourceResultListEnd'
42+
| 'SearchSourceResultListFooter'
4243
| 'SearchSourceResults'
4344
| 'SearchResultsPresearch'
4445
>;
@@ -194,11 +195,12 @@ export const Chat = <
194195
SearchResults: props.SearchResults,
195196
SearchResultsHeader: props.SearchResultsHeader,
196197
SearchResultsPresearch: props.SearchResultsPresearch,
197-
SearchSourceEmptyResults: props.SearchSourceEmptyResults,
198198
SearchSourceLoadingResults: props.SearchSourceLoadingResults,
199199
SearchSourceResultList: props.SearchSourceResultList,
200-
SearchSourceResultListEnd: props.SearchSourceResultListEnd,
200+
SearchSourceResultListFooter: props.SearchSourceResultListFooter,
201201
SearchSourceResults: props.SearchSourceResults,
202+
SearchSourceResultsEmpty: props.SearchSourceResultsEmpty,
203+
SearchSourceResultsHeader: props.SearchSourceResultsHeader,
202204
SendButton: props.SendButton,
203205
StartRecordingAudioButton: props.StartRecordingAudioButton,
204206
ThreadHead: props.ThreadHead,
@@ -261,12 +263,13 @@ export const Chat = <
261263
props.SearchBar,
262264
props.SearchResults,
263265
props.SearchResultsHeader,
264-
props.SearchSourceEmptyResults,
266+
props.SearchResultsPresearch,
265267
props.SearchSourceLoadingResults,
266268
props.SearchSourceResultList,
267-
props.SearchSourceResultListEnd,
269+
props.SearchSourceResultListFooter,
268270
props.SearchSourceResults,
269-
props.SearchResultsPresearch,
271+
props.SearchSourceResultsEmpty,
272+
props.SearchSourceResultsHeader,
270273
props.SendButton,
271274
props.StartRecordingAudioButton,
272275
props.ThreadHead,

src/components/InfiniteScrollPaginator/InfiniteScrollPaginator.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const mousewheelListener = (event: Event) => {
1515

1616
export type InfiniteScrollPaginatorProps = React.ComponentProps<'div'> & {
1717
listenToScroll?: (distanceFromBottom: number, distanceFromTop: number, threshold: number) => void;
18+
loadNextDebounceMs?: number;
1819
loadNextOnScrollToBottom?: () => void;
1920
loadNextOnScrollToTop?: () => void;
2021
/** Offset from when to start the loadNextPage call */
@@ -26,6 +27,7 @@ export const InfiniteScrollPaginator = (props: PropsWithChildren<InfiniteScrollP
2627
const {
2728
children,
2829
listenToScroll,
30+
loadNextDebounceMs = 500,
2931
loadNextOnScrollToBottom,
3032
loadNextOnScrollToTop,
3133
threshold = DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD,
@@ -60,8 +62,14 @@ export const InfiniteScrollPaginator = (props: PropsWithChildren<InfiniteScrollP
6062
if (distanceFromBottom < Number(threshold)) {
6163
loadNextOnScrollToBottom?.();
6264
}
63-
}, 500),
64-
[listenToScroll, loadNextOnScrollToBottom, loadNextOnScrollToTop, threshold],
65+
}, loadNextDebounceMs),
66+
[
67+
listenToScroll,
68+
loadNextDebounceMs,
69+
loadNextOnScrollToBottom,
70+
loadNextOnScrollToTop,
71+
threshold,
72+
],
6573
);
6674

6775
useEffect(() => {

src/context/ComponentContext.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ import {
5252
import {
5353
SearchProps,
5454
SearchResultsPresearchProps,
55-
SearchSourceEmptyResultsProps,
56-
SearchSourceResultListEndProps,
55+
SearchSourceResultListFooterProps,
5756
SearchSourceResultListProps,
57+
SearchSourceResultsEmptyProps,
5858
} from '../experimental';
5959

6060
import type {
@@ -64,6 +64,7 @@ import type {
6464
UnknownType,
6565
} from '../types/types';
6666
import type { DefaultSearchSources, SearchSource } from '../experimental/Search/SearchController';
67+
import { SearchSourceResultsHeaderProps } from '../experimental/Search/SearchResults/SearchSourceResultsHeader';
6768

6869
export type ComponentContextValue<
6970
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
@@ -185,18 +186,20 @@ export type ComponentContextValue<
185186
SearchResultsPresearch?: React.ComponentType<
186187
SearchResultsPresearchProps<StreamChatGenerics, SearchSources>
187188
>;
188-
/** Custom component to display the search source results UI with 0 items found, defaults to and accepts same props as: [SearchSourceEmptyResults](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/SearchResults/SearchSourceEmptyResults.tsx) */
189-
SearchSourceEmptyResults?: React.ComponentType<SearchSourceEmptyResultsProps>;
190189
/** Custom component to display the search source results UI during the search query execution, defaults to and accepts same props as: [SearchSourceLoadingResults](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/SearchResults/SearchSourceLoadingResults.tsx) */
191-
SearchSourceLoadingResults?: React.ComponentType<SearchSourceEmptyResultsProps>;
190+
SearchSourceLoadingResults?: React.ComponentType<SearchSourceResultsEmptyProps>;
192191
/** Custom component to display the search source items results, defaults to and accepts same props as: [SearchSourceResultList](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/SearchResults/SearchSourceResultList.tsx) */
193192
SearchSourceResultList?: React.ComponentType<
194193
SearchSourceResultListProps<StreamChatGenerics, SearchSources>
195194
>;
196-
/** Custom component to indicate the end of the last page for a searched source, defaults to and accepts same props as: [SearchSourceResultListEnd](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/SearchResults/SearchSourceResultListEnd.tsx) */
197-
SearchSourceResultListEnd?: React.ComponentType<SearchSourceResultListEndProps>;
195+
/** Custom component to indicate the end of the last page for a searched source, defaults to and accepts same props as: [SearchSourceResultListFooter](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/SearchResults/SearchSourceResultListFooter.tsx) */
196+
SearchSourceResultListFooter?: React.ComponentType<SearchSourceResultListFooterProps>;
198197
/** Custom UI component to display search results items for a given search source pane, defaults to and accepts same props as: [SearchSourceResults](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/SearchResults/SourceSearchResults.tsx) */
199198
SearchSourceResults?: React.ComponentType;
199+
/** Custom component to display the search source results UI with 0 items found, defaults to and accepts same props as: [SearchSourceResultsEmpty](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/SearchResults/SearchSourceResultsEmpty.tsx) */
200+
SearchSourceResultsEmpty?: React.ComponentType<SearchSourceResultsEmptyProps>;
201+
/** Custom component to display the header content for a given search source results, no default component is provided. */
202+
SearchSourceResultsHeader?: React.ComponentType<SearchSourceResultsHeaderProps>;
200203
/** Custom UI component for send button, defaults to and accepts same props as: [SendButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/icons.tsx) */
201204
SendButton?: React.ComponentType<SendButtonProps<StreamChatGenerics>>;
202205
/** Custom UI component button for initiating audio recording, defaults to and accepts same props as: [StartRecordingAudioButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MediaRecorder/AudioRecorder/AudioRecordingButtons.tsx) */

src/experimental/Search/SearchResults/SearchSourceResultList.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React, { ComponentType, useCallback } from 'react';
22
import { InfiniteScrollPaginator } from '../../../components/InfiniteScrollPaginator/InfiniteScrollPaginator';
33
import { DefaultSearchResultItems, SearchResultItemComponents } from './SearchResultItem';
4-
import { SearchSourceLoadingResults as DefaultSearchSourceLoadingResults } from './SearchSourceLoadingResults';
5-
import { SearchSourceResultListEnd as DefaultSearchSourceResultListEnd } from './SearchSourceResultListEnd';
4+
import { SearchSourceResultListFooter as DefaultSearchSourceResultListFooter } from './SearchSourceResultListFooter';
65
import { useComponentContext } from '../../../context';
76
import type { DefaultSearchSources, SearchSource, SourceItemsRecord } from '../SearchController';
87
import type { DefaultStreamChatGenerics } from '../../../types';
@@ -11,26 +10,25 @@ export type SearchSourceResultListProps<
1110
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
1211
Sources extends SearchSource[] = DefaultSearchSources<StreamChatGenerics>
1312
> = {
14-
hasMore: boolean;
1513
searchSource: SearchSource;
16-
isLoading?: boolean;
1714
items?: SourceItemsRecord<StreamChatGenerics, Sources>[Sources[number]['type']];
15+
loadMoreDebounceMs?: number;
16+
loadMoreThresholdPx?: number;
1817
SearchResultItems?: SearchResultItemComponents<Sources>;
1918
};
2019

2120
export const SearchSourceResultList = <
2221
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
2322
SearchSources extends SearchSource[] = DefaultSearchSources<StreamChatGenerics>
2423
>({
25-
hasMore,
26-
isLoading,
2724
items,
25+
loadMoreThresholdPx = 80,
26+
loadMoreDebounceMs = 100,
2827
searchSource,
2928
SearchResultItems = DefaultSearchResultItems as SearchResultItemComponents<SearchSources>,
3029
}: SearchSourceResultListProps<StreamChatGenerics, SearchSources>) => {
3130
const {
32-
SearchSourceLoadingResults = DefaultSearchSourceLoadingResults,
33-
SearchSourceResultListEnd = DefaultSearchSourceResultListEnd,
31+
SearchSourceResultListFooter = DefaultSearchSourceResultListFooter,
3432
} = useComponentContext<StreamChatGenerics, NonNullable<unknown>, SearchSources>();
3533
const loadMore = useCallback(() => searchSource.search(), [searchSource]);
3634
const SearchResultItem = SearchResultItems[
@@ -41,12 +39,15 @@ export const SearchSourceResultList = <
4139

4240
return (
4341
<div className='str-chat__search-source-result-list'>
44-
<InfiniteScrollPaginator loadNextOnScrollToBottom={loadMore} threshold={40}>
42+
<InfiniteScrollPaginator
43+
loadNextDebounceMs={loadMoreDebounceMs}
44+
loadNextOnScrollToBottom={loadMore}
45+
threshold={loadMoreThresholdPx}
46+
>
4547
{items?.map((item, i) => (
46-
<SearchResultItem item={item} key={`source-search-result-${searchSource}-${i}`} />
48+
<SearchResultItem item={item} key={`source-search-result-${searchSource.type}-${i}`} />
4749
))}
48-
{isLoading && <SearchSourceLoadingResults searchSource={searchSource} />}
49-
{!hasMore && <SearchSourceResultListEnd searchSource={searchSource} />}
50+
<SearchSourceResultListFooter searchSource={searchSource} />
5051
</InfiniteScrollPaginator>
5152
</div>
5253
);

src/experimental/Search/SearchResults/SearchSourceResultListEnd.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import type { DefaultSearchSources, SearchSource, SearchSourceState } from '../SearchController';
3+
import { SearchSourceLoadingResults as DefaultSearchSourceLoadingResults } from './SearchSourceLoadingResults';
4+
import { useComponentContext, useTranslationContext } from '../../../context';
5+
import { useStateStore } from '../../../store';
6+
import type { DefaultStreamChatGenerics } from '../../../types';
7+
8+
const searchSourceStateSelector = (value: SearchSourceState) => ({
9+
hasMore: value.hasMore,
10+
isLoading: value.isLoading,
11+
});
12+
13+
export type SearchSourceResultListFooterProps = {
14+
searchSource: SearchSource;
15+
};
16+
17+
export const SearchSourceResultListFooter = <
18+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
19+
SearchSources extends SearchSource[] = DefaultSearchSources<StreamChatGenerics>
20+
>({
21+
searchSource,
22+
}: SearchSourceResultListFooterProps) => {
23+
const { t } = useTranslationContext();
24+
const { SearchSourceLoadingResults = DefaultSearchSourceLoadingResults } = useComponentContext<
25+
StreamChatGenerics,
26+
NonNullable<unknown>,
27+
SearchSources
28+
>();
29+
const { hasMore, isLoading } = useStateStore(searchSource.state, searchSourceStateSelector);
30+
31+
return (
32+
<div className='str-chat__search-source-result-list__footer'>
33+
{isLoading ? (
34+
<SearchSourceLoadingResults searchSource={searchSource} />
35+
) : !hasMore ? (
36+
<div className='str-chat__search-source-results---empty'>
37+
{t<string>('All results loaded')}
38+
</div>
39+
) : null}
40+
</div>
41+
);
42+
};
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
2-
import { SearchSourceEmptyResults as DefaultSearchSourceEmptyResults } from './SearchSourceEmptyResults';
32
import { SearchSourceResultList as DefaultSearchSourceResultList } from './SearchSourceResultList';
3+
import { SearchSourceResultsEmpty as DefaultSearchSourceResultsEmpty } from './SearchSourceResultsEmpty';
4+
import { SearchSourceResultsHeader as DefaultSearchSourceResultsHeader } from './SearchSourceResultsHeader';
45
import { useComponentContext } from '../../../context';
56
import { useStateStore } from '../../../store';
67
import type { DefaultSearchSources, SearchSource, SearchSourceState } from '../SearchController';
@@ -20,22 +21,21 @@ export const SearchSourceResults = <
2021
searchSource,
2122
}: SearchSourceResultsProps) => {
2223
const {
23-
SearchSourceEmptyResults = DefaultSearchSourceEmptyResults,
2424
SearchSourceResultList = DefaultSearchSourceResultList,
25+
SearchSourceResultsEmpty = DefaultSearchSourceResultsEmpty,
26+
SearchSourceResultsHeader = DefaultSearchSourceResultsHeader,
2527
} = useComponentContext<StreamChatGenerics, NonNullable<unknown>, SearchSources>();
26-
const { hasMore, isLoading, items } = useStateStore(
27-
searchSource.state,
28-
searchSourceStateSelector,
29-
);
28+
const { isLoading, items } = useStateStore(searchSource.state, searchSourceStateSelector);
3029

31-
return !items && !isLoading ? null : items?.length || isLoading ? (
32-
<SearchSourceResultList
33-
hasMore={hasMore}
34-
isLoading={isLoading}
35-
items={items}
36-
searchSource={searchSource}
37-
/>
38-
) : (
39-
<SearchSourceEmptyResults searchSource={searchSource} />
30+
if (!items && !isLoading) return null;
31+
return (
32+
<div className='str-chat__search-source-results'>
33+
<SearchSourceResultsHeader searchSource={searchSource} />
34+
{items?.length || isLoading ? (
35+
<SearchSourceResultList items={items} searchSource={searchSource} />
36+
) : (
37+
<SearchSourceResultsEmpty searchSource={searchSource} />
38+
)}
39+
</div>
4040
);
4141
};

src/experimental/Search/SearchResults/SearchSourceEmptyResults.tsx renamed to src/experimental/Search/SearchResults/SearchSourceResultsEmpty.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import React from 'react';
22
import { SearchSource } from '../SearchController';
33
import { useTranslationContext } from '../../../context';
44

5-
export type SearchSourceEmptyResultsProps = {
5+
export type SearchSourceResultsEmptyProps = {
66
searchSource: SearchSource;
77
};
88

9-
export const SearchSourceEmptyResults = () => {
9+
export const SearchSourceResultsEmpty = () => {
1010
const { t } = useTranslationContext();
1111
return (
12-
<div className='str-chat__search-source-empty-results'>{t<string>('No results found')}</div>
12+
<div className='str-chat__search-source-results-empty'>{t<string>('No results found')}</div>
1313
);
1414
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { SearchSource } from '../SearchController';
2+
3+
export type SearchSourceResultsHeaderProps = {
4+
searchSource: SearchSource;
5+
};
6+
7+
export const SearchSourceResultsHeader = () => null;

src/experimental/Search/SearchResults/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ export * from './SearchResultItem';
22
export * from './SearchResults';
33
export * from './SearchResultsHeader';
44
export * from './SearchResultsPresearch';
5-
export * from './SearchSourceEmptyResults';
5+
export * from './SearchSourceResultsEmpty';
66
export * from './SearchSourceLoadingResults';
77
export * from './SearchSourceResultList';
8-
export * from './SearchSourceResultListEnd';
8+
export * from './SearchSourceResultListFooter';
99
export * from './SearchSourceResults';

0 commit comments

Comments
 (0)