stream-chat-react 12.11.1 → 12.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Channel/Channel.d.ts +1 -1
- package/dist/components/Channel/Channel.js +41 -26
- package/dist/components/Channel/channelState.d.ts +1 -1
- package/dist/components/Channel/channelState.js +2 -1
- package/dist/components/ChannelList/ChannelList.d.ts +2 -2
- package/dist/components/ChannelList/ChannelList.js +15 -9
- package/dist/components/ChannelPreview/ChannelPreview.d.ts +6 -6
- package/dist/components/ChannelPreview/ChannelPreview.js +3 -4
- package/dist/components/ChannelPreview/ChannelPreviewMessenger.d.ts +1 -1
- package/dist/components/ChannelPreview/utils.d.ts +2 -2
- package/dist/components/ChannelSearch/SearchBar.js +7 -8
- package/dist/components/ChannelSearch/SearchResults.js +8 -7
- package/dist/components/Chat/Chat.d.ts +5 -2
- package/dist/components/Chat/Chat.js +12 -2
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/Chat/hooks/useCreateChatContext.js +3 -1
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.d.ts +1 -1
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +1 -1
- package/dist/components/InfiniteScrollPaginator/InfiniteScrollPaginator.d.ts +1 -0
- package/dist/components/InfiniteScrollPaginator/InfiniteScrollPaginator.js +8 -2
- package/dist/components/MediaRecorder/classes/MediaRecorderController.d.ts +6 -1
- package/dist/components/MediaRecorder/classes/MediaRecorderController.js +6 -8
- package/dist/components/Message/QuotedMessage.d.ts +3 -1
- package/dist/components/Message/QuotedMessage.js +14 -11
- package/dist/components/Message/renderText/renderText.d.ts +3 -3
- package/dist/components/Message/renderText/renderText.js +3 -3
- package/dist/components/Message/types.d.ts +2 -2
- package/dist/components/MessageInput/MessageInputFlat.js +2 -1
- package/dist/components/MessageInput/QuotedMessagePreview.d.ts +3 -1
- package/dist/components/MessageInput/QuotedMessagePreview.js +4 -3
- package/dist/components/MessageList/utils.js +3 -0
- package/dist/context/ChatContext.d.ts +3 -1
- package/dist/context/ComponentContext.d.ts +23 -0
- package/dist/context/MessageContext.d.ts +2 -2
- package/dist/css/v2/index.css +1 -1
- package/dist/css/v2/index.layout.css +1 -1
- package/dist/experimental/Search/Search.d.ts +12 -0
- package/dist/experimental/Search/Search.js +25 -0
- package/dist/experimental/Search/SearchBar/SearchBar.d.ts +2 -0
- package/dist/experimental/Search/SearchBar/SearchBar.js +56 -0
- package/dist/experimental/Search/SearchBar/index.d.ts +1 -0
- package/dist/experimental/Search/SearchBar/index.js +1 -0
- package/dist/experimental/Search/SearchContext.d.ts +23 -0
- package/dist/experimental/Search/SearchContext.js +10 -0
- package/dist/experimental/Search/SearchResults/SearchResultItem.d.ts +19 -0
- package/dist/experimental/Search/SearchResults/SearchResultItem.js +62 -0
- package/dist/experimental/Search/SearchResults/SearchResults.d.ts +3 -0
- package/dist/experimental/Search/SearchResults/SearchResults.js +21 -0
- package/dist/experimental/Search/SearchResults/SearchResultsHeader.d.ts +3 -0
- package/dist/experimental/Search/SearchResults/SearchResultsHeader.js +31 -0
- package/dist/experimental/Search/SearchResults/SearchResultsPresearch.d.ts +6 -0
- package/dist/experimental/Search/SearchResults/SearchResultsPresearch.js +6 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultList.d.ts +9 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultList.js +22 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultListFooter.d.ts +3 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultListFooter.js +16 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResults.d.ts +7 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResults.js +21 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsEmpty.d.ts +2 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsEmpty.js +6 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsHeader.d.ts +1 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsHeader.js +1 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsLoadingIndicator.d.ts +2 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsLoadingIndicator.js +8 -0
- package/dist/experimental/Search/SearchResults/index.d.ts +9 -0
- package/dist/experimental/Search/SearchResults/index.js +9 -0
- package/dist/experimental/Search/SearchSourceResultsContext.d.ts +13 -0
- package/dist/experimental/Search/SearchSourceResultsContext.js +10 -0
- package/dist/experimental/Search/hooks/index.d.ts +2 -0
- package/dist/experimental/Search/hooks/index.js +2 -0
- package/dist/experimental/Search/hooks/useSearchFocusedMessage.d.ts +2 -0
- package/dist/experimental/Search/hooks/useSearchFocusedMessage.js +8 -0
- package/dist/experimental/Search/hooks/useSearchQueriesInProgress.d.ts +6 -0
- package/dist/experimental/Search/hooks/useSearchQueriesInProgress.js +22 -0
- package/dist/experimental/Search/index.d.ts +5 -0
- package/dist/experimental/Search/index.js +5 -0
- package/dist/experimental/index.browser.cjs +11286 -301
- package/dist/experimental/index.browser.cjs.map +4 -4
- package/dist/experimental/index.d.ts +1 -0
- package/dist/experimental/index.js +1 -0
- package/dist/experimental/index.node.cjs +13176 -301
- package/dist/experimental/index.node.cjs.map +4 -4
- package/dist/i18n/Streami18n.d.ts +7 -0
- package/dist/i18n/de.json +7 -0
- package/dist/i18n/en.json +7 -0
- package/dist/i18n/es.json +7 -0
- package/dist/i18n/fr.json +8 -1
- package/dist/i18n/hi.json +7 -0
- package/dist/i18n/it.json +7 -0
- package/dist/i18n/ja.json +7 -0
- package/dist/i18n/ko.json +7 -0
- package/dist/i18n/nl.json +7 -0
- package/dist/i18n/pt.json +8 -1
- package/dist/i18n/ru.json +7 -0
- package/dist/i18n/tr.json +7 -0
- package/dist/index.browser.cjs +22862 -21873
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +17830 -16963
- package/dist/index.node.cjs.map +4 -4
- package/dist/scss/v2/Message/Message-layout.scss +1 -1
- package/dist/scss/v2/Message/Message-theme.scss +5 -5
- package/package.json +9 -10
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { DefaultStreamChatGenerics } from '../../types';
|
|
3
|
+
export type SearchProps = {
|
|
4
|
+
directMessagingChannelType?: string;
|
|
5
|
+
/** Sets the input element into disabled state */
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** Clear search state / results on every click outside the search input, defaults to false */
|
|
8
|
+
exitSearchOnInputBlur?: boolean;
|
|
9
|
+
/** Custom placeholder text to be displayed in the search input */
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const Search: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ directMessagingChannelType, disabled, exitSearchOnInputBlur, placeholder, }: SearchProps) => React.JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { SearchBar as DefaultSearchBar } from './SearchBar/SearchBar';
|
|
4
|
+
import { SearchResults as DefaultSearchResults } from './SearchResults/SearchResults';
|
|
5
|
+
import { SearchContextProvider } from './SearchContext';
|
|
6
|
+
import { useChatContext, useComponentContext } from '../../context';
|
|
7
|
+
import { useStateStore } from '../../store';
|
|
8
|
+
const searchControllerStateSelector = (nextValue) => ({ isActive: nextValue.isActive });
|
|
9
|
+
export const Search = ({ directMessagingChannelType = 'messaging', disabled, exitSearchOnInputBlur, placeholder, }) => {
|
|
10
|
+
const { SearchBar = DefaultSearchBar, SearchResults = DefaultSearchResults } = useComponentContext();
|
|
11
|
+
const { searchController } = useChatContext();
|
|
12
|
+
const { isActive } = useStateStore(searchController.state, searchControllerStateSelector);
|
|
13
|
+
return (React.createElement(SearchContextProvider, { value: {
|
|
14
|
+
directMessagingChannelType,
|
|
15
|
+
disabled,
|
|
16
|
+
exitSearchOnInputBlur,
|
|
17
|
+
placeholder,
|
|
18
|
+
searchController,
|
|
19
|
+
} },
|
|
20
|
+
React.createElement("div", { className: clsx('str-chat__search', {
|
|
21
|
+
'str-chat__search--active': isActive,
|
|
22
|
+
}), "data-testid": 'search' },
|
|
23
|
+
React.createElement(SearchBar, null),
|
|
24
|
+
React.createElement(SearchResults, null))));
|
|
25
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { useSearchContext } from '../SearchContext';
|
|
4
|
+
import { useSearchQueriesInProgress } from '../hooks';
|
|
5
|
+
import { useTranslationContext } from '../../../context';
|
|
6
|
+
import { useStateStore } from '../../../store';
|
|
7
|
+
const searchControllerStateSelector = (nextValue) => ({
|
|
8
|
+
isActive: nextValue.isActive,
|
|
9
|
+
searchQuery: nextValue.searchQuery,
|
|
10
|
+
});
|
|
11
|
+
export const SearchBar = () => {
|
|
12
|
+
const { t } = useTranslationContext();
|
|
13
|
+
const { disabled, exitSearchOnInputBlur, placeholder, searchController } = useSearchContext();
|
|
14
|
+
const queriesInProgress = useSearchQueriesInProgress(searchController);
|
|
15
|
+
const [input, setInput] = useState(null);
|
|
16
|
+
const { isActive, searchQuery } = useStateStore(searchController.state, searchControllerStateSelector);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!input)
|
|
19
|
+
return;
|
|
20
|
+
const handleKeyDown = (event) => {
|
|
21
|
+
if (event.key === 'Escape') {
|
|
22
|
+
input.blur();
|
|
23
|
+
searchController.exit();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
27
|
+
return () => {
|
|
28
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
29
|
+
};
|
|
30
|
+
}, [searchController, input]);
|
|
31
|
+
return (React.createElement("div", { className: 'str-chat__search-bar', "data-testid": 'search-bar' },
|
|
32
|
+
React.createElement("div", { className: clsx('str-chat__search-input--wrapper', {
|
|
33
|
+
'str-chat__search-input--wrapper-active': isActive,
|
|
34
|
+
}) },
|
|
35
|
+
React.createElement("div", { className: 'str-chat__search-input--icon' }),
|
|
36
|
+
React.createElement("input", { className: 'str-chat__search-input', "data-testid": 'search-input', disabled: disabled, onBlur: () => {
|
|
37
|
+
if (exitSearchOnInputBlur)
|
|
38
|
+
searchController.exit();
|
|
39
|
+
}, onChange: (event) => {
|
|
40
|
+
if (event.target.value) {
|
|
41
|
+
searchController.search(event.target.value);
|
|
42
|
+
}
|
|
43
|
+
else if (!event.target.value) {
|
|
44
|
+
searchController.clear();
|
|
45
|
+
}
|
|
46
|
+
}, onFocus: searchController.activate, placeholder: placeholder ?? t('Search'), ref: setInput, type: 'text', value: searchQuery }),
|
|
47
|
+
searchQuery && (React.createElement("button", { className: 'str-chat__search-input--clear-button', "data-testid": 'clear-input-button', disabled: queriesInProgress.length > 0, onClick: () => {
|
|
48
|
+
searchController.clear();
|
|
49
|
+
input?.focus();
|
|
50
|
+
} },
|
|
51
|
+
React.createElement("div", { className: 'str-chat__search-input--clear-button-icon' })))),
|
|
52
|
+
isActive ? (React.createElement("button", { className: clsx('str-chat__search-bar-button str-chat__search-bar-button--exit-search'), "data-testid": 'search-bar-button', onClick: () => {
|
|
53
|
+
input?.blur();
|
|
54
|
+
searchController.exit();
|
|
55
|
+
} }, t('Cancel'))) : null));
|
|
56
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SearchBar';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SearchBar';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { PropsWithChildren } from 'react';
|
|
2
|
+
import type { SearchController } from 'stream-chat';
|
|
3
|
+
import type { DefaultStreamChatGenerics } from '../../types';
|
|
4
|
+
export type SearchContextValue<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
5
|
+
/** The type of channel to create on user result select, defaults to `messaging` */
|
|
6
|
+
directMessagingChannelType: string;
|
|
7
|
+
/** Instance of the search controller that handles the data management */
|
|
8
|
+
searchController: SearchController<StreamChatGenerics>;
|
|
9
|
+
/** Sets the input element into disabled state */
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
/** Clear search state / results on every click outside the search input, defaults to true */
|
|
12
|
+
exitSearchOnInputBlur?: boolean;
|
|
13
|
+
/** Custom placeholder text to be displayed in the search input */
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const SearchContext: React.Context<SearchContextValue<DefaultStreamChatGenerics> | undefined>;
|
|
17
|
+
/**
|
|
18
|
+
* Context provider for components rendered within the `Search` component
|
|
19
|
+
*/
|
|
20
|
+
export declare const SearchContextProvider: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ children, value, }: PropsWithChildren<{
|
|
21
|
+
value: SearchContextValue<StreamChatGenerics>;
|
|
22
|
+
}>) => React.JSX.Element;
|
|
23
|
+
export declare const useSearchContext: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>() => SearchContextValue<StreamChatGenerics>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
export const SearchContext = createContext(undefined);
|
|
3
|
+
/**
|
|
4
|
+
* Context provider for components rendered within the `Search` component
|
|
5
|
+
*/
|
|
6
|
+
export const SearchContextProvider = ({ children, value, }) => (React.createElement(SearchContext.Provider, { value: value }, children));
|
|
7
|
+
export const useSearchContext = () => {
|
|
8
|
+
const contextValue = useContext(SearchContext);
|
|
9
|
+
return contextValue;
|
|
10
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React, { ComponentType } from 'react';
|
|
2
|
+
import type { Channel, MessageResponse, User } from 'stream-chat';
|
|
3
|
+
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
4
|
+
export type ChannelSearchResultItemProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
5
|
+
item: Channel<StreamChatGenerics>;
|
|
6
|
+
};
|
|
7
|
+
export declare const ChannelSearchResultItem: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ item, }: ChannelSearchResultItemProps<StreamChatGenerics>) => React.JSX.Element;
|
|
8
|
+
export type ChannelByMessageSearchResultItemProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
9
|
+
item: MessageResponse<StreamChatGenerics>;
|
|
10
|
+
};
|
|
11
|
+
export declare const MessageSearchResultItem: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ item, }: ChannelByMessageSearchResultItemProps<StreamChatGenerics>) => React.JSX.Element | undefined;
|
|
12
|
+
export type UserSearchResultItemProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
13
|
+
item: User<StreamChatGenerics>;
|
|
14
|
+
};
|
|
15
|
+
export declare const UserSearchResultItem: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ item, }: UserSearchResultItemProps<StreamChatGenerics>) => React.JSX.Element;
|
|
16
|
+
export type SearchResultItemComponents = Record<string, ComponentType<{
|
|
17
|
+
item: any;
|
|
18
|
+
}>>;
|
|
19
|
+
export declare const DefaultSearchResultItems: SearchResultItemComponents;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import uniqBy from 'lodash.uniqby';
|
|
2
|
+
import React, { useCallback, useMemo } from 'react';
|
|
3
|
+
import { useSearchContext } from '../SearchContext';
|
|
4
|
+
import { Avatar } from '../../../components/Avatar';
|
|
5
|
+
import { ChannelPreview } from '../../../components/ChannelPreview';
|
|
6
|
+
import { useChannelListContext, useChatContext } from '../../../context';
|
|
7
|
+
import { DEFAULT_JUMP_TO_PAGE_SIZE } from '../../../constants/limits';
|
|
8
|
+
export const ChannelSearchResultItem = ({ item, }) => {
|
|
9
|
+
const { setActiveChannel } = useChatContext();
|
|
10
|
+
const { setChannels } = useChannelListContext();
|
|
11
|
+
const onSelect = useCallback(() => {
|
|
12
|
+
setActiveChannel(item);
|
|
13
|
+
setChannels?.((channels) => uniqBy([item, ...channels], 'cid'));
|
|
14
|
+
}, [item, setActiveChannel, setChannels]);
|
|
15
|
+
return (React.createElement(ChannelPreview, { channel: item, className: 'str-chat__search-result', onSelect: onSelect }));
|
|
16
|
+
};
|
|
17
|
+
export const MessageSearchResultItem = ({ item, }) => {
|
|
18
|
+
const { channel: activeChannel, client, searchController, setActiveChannel, } = useChatContext();
|
|
19
|
+
const { setChannels } = useChannelListContext();
|
|
20
|
+
const channel = useMemo(() => {
|
|
21
|
+
const { channel: channelData } = item;
|
|
22
|
+
const type = channelData?.type ?? 'unknown';
|
|
23
|
+
const id = channelData?.id ?? 'unknown';
|
|
24
|
+
return client.channel(type, id);
|
|
25
|
+
}, [client, item]);
|
|
26
|
+
const onSelect = useCallback(async () => {
|
|
27
|
+
if (!channel)
|
|
28
|
+
return;
|
|
29
|
+
await channel.state.loadMessageIntoState(item.id, undefined, DEFAULT_JUMP_TO_PAGE_SIZE);
|
|
30
|
+
// FIXME: message focus should be handled by yet non-existent msg list controller in client packaged
|
|
31
|
+
searchController._internalState.partialNext({ focusedMessage: item });
|
|
32
|
+
setActiveChannel(channel);
|
|
33
|
+
setChannels?.((channels) => uniqBy([channel, ...channels], 'cid'));
|
|
34
|
+
}, [channel, item, searchController, setActiveChannel, setChannels]);
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
36
|
+
const getLatestMessagePreview = useCallback(() => item.text, [item]);
|
|
37
|
+
if (!channel)
|
|
38
|
+
return;
|
|
39
|
+
return (React.createElement(ChannelPreview, { active: channel.cid === activeChannel?.cid &&
|
|
40
|
+
item.id === searchController._internalState.getLatestValue().focusedMessage?.id, channel: channel, className: 'str-chat__search-result', getLatestMessagePreview: getLatestMessagePreview, onSelect: onSelect }));
|
|
41
|
+
};
|
|
42
|
+
export const UserSearchResultItem = ({ item, }) => {
|
|
43
|
+
const { client, setActiveChannel } = useChatContext();
|
|
44
|
+
const { setChannels } = useChannelListContext();
|
|
45
|
+
const { directMessagingChannelType } = useSearchContext();
|
|
46
|
+
const onClick = useCallback(() => {
|
|
47
|
+
const newChannel = client.channel(directMessagingChannelType, {
|
|
48
|
+
members: [client.userID, item.id],
|
|
49
|
+
});
|
|
50
|
+
newChannel.watch();
|
|
51
|
+
setActiveChannel(newChannel);
|
|
52
|
+
setChannels?.((channels) => uniqBy([newChannel, ...channels], 'cid'));
|
|
53
|
+
}, [client, item, setActiveChannel, setChannels, directMessagingChannelType]);
|
|
54
|
+
return (React.createElement("button", { "aria-label": `Select User Channel: ${item.name || ''}`, className: 'str-chat__search-result', "data-testid": 'search-result-user', onClick: onClick, role: 'option' },
|
|
55
|
+
React.createElement(Avatar, { className: 'str-chat__avatar--channel-preview', image: item.image, name: item.name || item.id, user: item }),
|
|
56
|
+
React.createElement("div", { className: 'str-chat__search-result--display-name' }, item.name || item.id)));
|
|
57
|
+
};
|
|
58
|
+
export const DefaultSearchResultItems = {
|
|
59
|
+
channels: ChannelSearchResultItem,
|
|
60
|
+
messages: MessageSearchResultItem,
|
|
61
|
+
users: UserSearchResultItem,
|
|
62
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SearchSourceResults as DefaultSourceSearchResults } from './SearchSourceResults';
|
|
3
|
+
import { SearchResultsHeader as DefaultSearchResultsHeader } from './SearchResultsHeader';
|
|
4
|
+
import { SearchResultsPresearch as DefaultSearchResultsPresearch } from './SearchResultsPresearch';
|
|
5
|
+
import { useSearchContext } from '../SearchContext';
|
|
6
|
+
import { useComponentContext, useTranslationContext } from '../../../context';
|
|
7
|
+
import { useStateStore } from '../../../store';
|
|
8
|
+
const searchControllerStateSelector = (nextValue) => ({
|
|
9
|
+
activeSources: nextValue.sources.filter((s) => s.isActive),
|
|
10
|
+
isActive: nextValue.isActive,
|
|
11
|
+
searchQuery: nextValue.searchQuery,
|
|
12
|
+
});
|
|
13
|
+
export const SearchResults = () => {
|
|
14
|
+
const { t } = useTranslationContext('ResultsContainer');
|
|
15
|
+
const { SearchResultsHeader = DefaultSearchResultsHeader, SearchResultsPresearch = DefaultSearchResultsPresearch, SearchSourceResults = DefaultSourceSearchResults, } = useComponentContext();
|
|
16
|
+
const { searchController } = useSearchContext();
|
|
17
|
+
const { activeSources, isActive, searchQuery } = useStateStore(searchController.state, searchControllerStateSelector);
|
|
18
|
+
return !isActive ? null : (React.createElement("div", { "aria-label": t('aria/Search results'), className: 'str-chat__search-results' },
|
|
19
|
+
React.createElement(SearchResultsHeader, null),
|
|
20
|
+
!searchQuery ? (React.createElement(SearchResultsPresearch, { activeSources: activeSources })) : (activeSources.map((source) => (React.createElement(SearchSourceResults, { key: source.type, searchSource: source }))))));
|
|
21
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useSearchContext } from '../SearchContext';
|
|
4
|
+
import { useTranslationContext } from '../../../context';
|
|
5
|
+
import { useStateStore } from '../../../store';
|
|
6
|
+
const searchSourceStateSelector = (nextValue) => ({
|
|
7
|
+
isActive: nextValue.isActive,
|
|
8
|
+
});
|
|
9
|
+
const SearchSourceFilterButton = ({ source, }) => {
|
|
10
|
+
const { t } = useTranslationContext();
|
|
11
|
+
const { searchController } = useSearchContext();
|
|
12
|
+
const { isActive } = useStateStore(source.state, searchSourceStateSelector);
|
|
13
|
+
const label = `search-results-header-filter-source-button-label--${source.type}`;
|
|
14
|
+
return (React.createElement("button", { "aria-label": t('aria/Search results header filter button'), className: clsx('str-chat__search-results-header__filter-source-button', {
|
|
15
|
+
'str-chat__search-results-header__filter-source-button--active': isActive,
|
|
16
|
+
}), key: label, onClick: () => {
|
|
17
|
+
if (source.isActive) {
|
|
18
|
+
searchController.deactivateSource(source.type);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
searchController.activateSource(source.type);
|
|
22
|
+
if (searchController.searchQuery && !source.items?.length)
|
|
23
|
+
source.search(searchController.searchQuery);
|
|
24
|
+
}
|
|
25
|
+
} }, t(label)));
|
|
26
|
+
};
|
|
27
|
+
export const SearchResultsHeader = () => {
|
|
28
|
+
const { searchController } = useSearchContext();
|
|
29
|
+
return (React.createElement("div", { className: 'str-chat__search-results-header', "data-testid": 'search-results-header' },
|
|
30
|
+
React.createElement("div", { className: 'str-chat__search-results-header__filter-source-buttons', "data-testid": 'filter-source-buttons' }, searchController.sources.map((source) => (React.createElement(SearchSourceFilterButton, { key: `search-source-filter-button-${source.type}`, source: source }))))));
|
|
31
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslationContext } from '../../../context';
|
|
3
|
+
export const SearchResultsPresearch = () => {
|
|
4
|
+
const { t } = useTranslationContext();
|
|
5
|
+
return (React.createElement("div", { className: 'str-chat__search-results-presearch' }, t('Start typing to search')));
|
|
6
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SearchResultItemComponents } from './SearchResultItem';
|
|
3
|
+
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
4
|
+
export type SearchSourceResultListProps = {
|
|
5
|
+
loadMoreDebounceMs?: number;
|
|
6
|
+
loadMoreThresholdPx?: number;
|
|
7
|
+
SearchResultItems?: SearchResultItemComponents;
|
|
8
|
+
};
|
|
9
|
+
export declare const SearchSourceResultList: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ loadMoreDebounceMs, loadMoreThresholdPx, SearchResultItems, }: SearchSourceResultListProps) => React.JSX.Element | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DefaultSearchResultItems } from './SearchResultItem';
|
|
3
|
+
import { SearchSourceResultListFooter as DefaultSearchSourceResultListFooter } from './SearchSourceResultListFooter';
|
|
4
|
+
import { useSearchSourceResultsContext } from '../SearchSourceResultsContext';
|
|
5
|
+
import { InfiniteScrollPaginator } from '../../../components/InfiniteScrollPaginator/InfiniteScrollPaginator';
|
|
6
|
+
import { useComponentContext } from '../../../context';
|
|
7
|
+
import { useStateStore } from '../../../store';
|
|
8
|
+
const searchSourceStateSelector = (nextValue) => ({
|
|
9
|
+
items: nextValue.items,
|
|
10
|
+
});
|
|
11
|
+
export const SearchSourceResultList = ({ loadMoreDebounceMs = 100, loadMoreThresholdPx = 80, SearchResultItems = DefaultSearchResultItems, }) => {
|
|
12
|
+
const { SearchSourceResultListFooter = DefaultSearchSourceResultListFooter } = useComponentContext();
|
|
13
|
+
const { searchSource } = useSearchSourceResultsContext();
|
|
14
|
+
const { items } = useStateStore(searchSource.state, searchSourceStateSelector);
|
|
15
|
+
const SearchResultItem = SearchResultItems[searchSource.type];
|
|
16
|
+
if (!SearchResultItem)
|
|
17
|
+
return null;
|
|
18
|
+
return (React.createElement("div", { className: 'str-chat__search-source-result-list', "data-testid": 'search-source-result-list' },
|
|
19
|
+
React.createElement(InfiniteScrollPaginator, { loadNextDebounceMs: loadMoreDebounceMs, loadNextOnScrollToBottom: searchSource.search, threshold: loadMoreThresholdPx },
|
|
20
|
+
items?.map((item, i) => (React.createElement(SearchResultItem, { item: item, key: `source-search-result-${searchSource.type}-${i}` }))),
|
|
21
|
+
React.createElement(SearchSourceResultListFooter, null))));
|
|
22
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SearchSourceResultsLoadingIndicator as DefaultSearchSourceResultsLoadingIndicator } from './SearchSourceResultsLoadingIndicator';
|
|
3
|
+
import { useSearchSourceResultsContext } from '../SearchSourceResultsContext';
|
|
4
|
+
import { useComponentContext, useTranslationContext } from '../../../context';
|
|
5
|
+
import { useStateStore } from '../../../store';
|
|
6
|
+
const searchSourceStateSelector = (value) => ({
|
|
7
|
+
hasNext: value.hasNext,
|
|
8
|
+
isLoading: value.isLoading,
|
|
9
|
+
});
|
|
10
|
+
export const SearchSourceResultListFooter = () => {
|
|
11
|
+
const { t } = useTranslationContext();
|
|
12
|
+
const { SearchSourceResultsLoadingIndicator = DefaultSearchSourceResultsLoadingIndicator, } = useComponentContext();
|
|
13
|
+
const { searchSource } = useSearchSourceResultsContext();
|
|
14
|
+
const { hasNext, isLoading } = useStateStore(searchSource.state, searchSourceStateSelector);
|
|
15
|
+
return (React.createElement("div", { className: 'str-chat__search-source-result-list__footer', "data-testid": 'search-footer' }, isLoading ? (React.createElement(SearchSourceResultsLoadingIndicator, null)) : !hasNext ? (React.createElement("div", { className: 'str-chat__search-source-results---empty' }, t('All results loaded'))) : null));
|
|
16
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SearchSource } from 'stream-chat';
|
|
3
|
+
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
4
|
+
export type SearchSourceResultsProps = {
|
|
5
|
+
searchSource: SearchSource;
|
|
6
|
+
};
|
|
7
|
+
export declare const SearchSourceResults: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ searchSource, }: SearchSourceResultsProps) => React.JSX.Element | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SearchSourceResultList as DefaultSearchSourceResultList } from './SearchSourceResultList';
|
|
3
|
+
import { SearchSourceResultsEmpty as DefaultSearchSourceResultsEmpty } from './SearchSourceResultsEmpty';
|
|
4
|
+
import { SearchSourceResultsHeader as DefaultSearchSourceResultsHeader } from './SearchSourceResultsHeader';
|
|
5
|
+
import { SearchSourceResultsContextProvider } from '../SearchSourceResultsContext';
|
|
6
|
+
import { useComponentContext } from '../../../context';
|
|
7
|
+
import { useStateStore } from '../../../store';
|
|
8
|
+
const searchSourceStateSelector = (nextValue) => ({
|
|
9
|
+
isLoading: nextValue.isLoading,
|
|
10
|
+
items: nextValue.items,
|
|
11
|
+
});
|
|
12
|
+
export const SearchSourceResults = ({ searchSource, }) => {
|
|
13
|
+
const { SearchSourceResultList = DefaultSearchSourceResultList, SearchSourceResultsEmpty = DefaultSearchSourceResultsEmpty, SearchSourceResultsHeader = DefaultSearchSourceResultsHeader, } = useComponentContext();
|
|
14
|
+
const { isLoading, items } = useStateStore(searchSource.state, searchSourceStateSelector);
|
|
15
|
+
if (!items && !isLoading)
|
|
16
|
+
return null;
|
|
17
|
+
return (React.createElement(SearchSourceResultsContextProvider, { value: { searchSource } },
|
|
18
|
+
React.createElement("div", { className: 'str-chat__search-source-results', "data-testid": 'search-source-results' },
|
|
19
|
+
React.createElement(SearchSourceResultsHeader, null),
|
|
20
|
+
items?.length || isLoading ? (React.createElement(SearchSourceResultList, null)) : (React.createElement(SearchSourceResultsEmpty, null)))));
|
|
21
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslationContext } from '../../../context';
|
|
3
|
+
export const SearchSourceResultsEmpty = () => {
|
|
4
|
+
const { t } = useTranslationContext();
|
|
5
|
+
return (React.createElement("div", { className: 'str-chat__search-source-results-empty' }, t('No results found')));
|
|
6
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SearchSourceResultsHeader: () => null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SearchSourceResultsHeader = () => null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslationContext } from '../../../context';
|
|
3
|
+
import { useSearchSourceResultsContext } from '../SearchSourceResultsContext';
|
|
4
|
+
export const SearchSourceResultsLoadingIndicator = () => {
|
|
5
|
+
const { t } = useTranslationContext();
|
|
6
|
+
const { searchSource } = useSearchSourceResultsContext();
|
|
7
|
+
return (React.createElement("div", { className: 'str-chat__search-source-results__loading-indicator', "data-testid": 'search-loading-indicator' }, t(`Searching for ${searchSource.type}...`)));
|
|
8
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './SearchResultItem';
|
|
2
|
+
export * from './SearchResults';
|
|
3
|
+
export * from './SearchResultsHeader';
|
|
4
|
+
export * from './SearchResultsPresearch';
|
|
5
|
+
export * from './SearchSourceResultsEmpty';
|
|
6
|
+
export * from './SearchSourceResultsLoadingIndicator';
|
|
7
|
+
export * from './SearchSourceResultList';
|
|
8
|
+
export * from './SearchSourceResultListFooter';
|
|
9
|
+
export * from './SearchSourceResults';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './SearchResultItem';
|
|
2
|
+
export * from './SearchResults';
|
|
3
|
+
export * from './SearchResultsHeader';
|
|
4
|
+
export * from './SearchResultsPresearch';
|
|
5
|
+
export * from './SearchSourceResultsEmpty';
|
|
6
|
+
export * from './SearchSourceResultsLoadingIndicator';
|
|
7
|
+
export * from './SearchSourceResultList';
|
|
8
|
+
export * from './SearchSourceResultListFooter';
|
|
9
|
+
export * from './SearchSourceResults';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { PropsWithChildren } from 'react';
|
|
2
|
+
import type { SearchSource } from 'stream-chat';
|
|
3
|
+
export type SearchSourceResultsContextValue = {
|
|
4
|
+
searchSource: SearchSource;
|
|
5
|
+
};
|
|
6
|
+
export declare const SearchSourceResultsContext: React.Context<SearchSourceResultsContextValue | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* Context provider for components rendered within the `SearchSourceResults`
|
|
9
|
+
*/
|
|
10
|
+
export declare const SearchSourceResultsContextProvider: ({ children, value, }: PropsWithChildren<{
|
|
11
|
+
value: SearchSourceResultsContextValue;
|
|
12
|
+
}>) => React.JSX.Element;
|
|
13
|
+
export declare const useSearchSourceResultsContext: () => SearchSourceResultsContextValue;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
export const SearchSourceResultsContext = createContext(undefined);
|
|
3
|
+
/**
|
|
4
|
+
* Context provider for components rendered within the `SearchSourceResults`
|
|
5
|
+
*/
|
|
6
|
+
export const SearchSourceResultsContextProvider = ({ children, value, }) => (React.createElement(SearchSourceResultsContext.Provider, { value: value }, children));
|
|
7
|
+
export const useSearchSourceResultsContext = () => {
|
|
8
|
+
const contextValue = useContext(SearchSourceResultsContext);
|
|
9
|
+
return contextValue;
|
|
10
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useChatContext } from '../../../context';
|
|
2
|
+
import { useStateStore } from '../../../store';
|
|
3
|
+
const searchControllerStateSelector = (nextValue) => ({ focusedMessage: nextValue.focusedMessage });
|
|
4
|
+
export const useSearchFocusedMessage = () => {
|
|
5
|
+
const { searchController } = useChatContext('Channel');
|
|
6
|
+
const { focusedMessage } = useStateStore(searchController._internalState, searchControllerStateSelector);
|
|
7
|
+
return focusedMessage;
|
|
8
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SearchController } from 'stream-chat';
|
|
2
|
+
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
3
|
+
export type UseSearchQueriesInProgressParams<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
4
|
+
searchController: SearchController<StreamChatGenerics>;
|
|
5
|
+
};
|
|
6
|
+
export declare const useSearchQueriesInProgress: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(searchController: SearchController<StreamChatGenerics>) => string[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useStateStore } from '../../../store';
|
|
3
|
+
const searchControllerStateSelector = (value) => ({
|
|
4
|
+
sources: value.sources,
|
|
5
|
+
});
|
|
6
|
+
export const useSearchQueriesInProgress = (searchController) => {
|
|
7
|
+
const [queriesInProgress, setQueriesInProgress] = useState([]);
|
|
8
|
+
const { sources } = useStateStore(searchController.state, searchControllerStateSelector);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const subscriptions = sources.map((source) => source.state.subscribeWithSelector((value) => ({ isLoading: value.isLoading }), ({ isLoading }) => {
|
|
11
|
+
setQueriesInProgress((prev) => {
|
|
12
|
+
if (isLoading)
|
|
13
|
+
return prev.concat(source.type);
|
|
14
|
+
return prev.filter((type) => type !== source.type);
|
|
15
|
+
});
|
|
16
|
+
}));
|
|
17
|
+
return () => {
|
|
18
|
+
subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
19
|
+
};
|
|
20
|
+
}, [sources]);
|
|
21
|
+
return queriesInProgress;
|
|
22
|
+
};
|