stream-chat-react 13.2.2 → 13.3.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.
Files changed (87) hide show
  1. package/dist/components/Attachment/Attachment.d.ts +5 -3
  2. package/dist/components/Attachment/Attachment.js +12 -5
  3. package/dist/components/Attachment/AttachmentContainer.d.ts +4 -3
  4. package/dist/components/Attachment/AttachmentContainer.js +19 -11
  5. package/dist/components/Attachment/Geolocation.d.ts +13 -0
  6. package/dist/components/Attachment/Geolocation.js +34 -0
  7. package/dist/components/Attachment/icons.d.ts +2 -0
  8. package/dist/components/Attachment/icons.js +5 -0
  9. package/dist/components/Attachment/index.d.ts +3 -1
  10. package/dist/components/Attachment/index.js +3 -1
  11. package/dist/components/Attachment/utils.d.ts +4 -1
  12. package/dist/components/Channel/Channel.d.ts +1 -1
  13. package/dist/components/Channel/Channel.js +2 -0
  14. package/dist/components/ChannelPreview/utils.js +3 -0
  15. package/dist/components/Chat/hooks/useChat.js +1 -1
  16. package/dist/components/Dialog/DialogAnchor.d.ts +3 -2
  17. package/dist/components/Dialog/DialogAnchor.js +7 -2
  18. package/dist/components/Form/Dropdown.d.ts +14 -0
  19. package/dist/components/Form/Dropdown.js +49 -0
  20. package/dist/components/Form/SwitchField.js +3 -1
  21. package/dist/components/Location/ShareLocationDialog.d.ts +18 -0
  22. package/dist/components/Location/ShareLocationDialog.js +139 -0
  23. package/dist/components/Location/hooks/useLiveLocationSharingManager.d.ts +18 -0
  24. package/dist/components/Location/hooks/useLiveLocationSharingManager.js +57 -0
  25. package/dist/components/Location/index.d.ts +1 -0
  26. package/dist/components/Location/index.js +1 -0
  27. package/dist/components/Message/MessageSimple.js +6 -1
  28. package/dist/components/Message/index.d.ts +4 -1
  29. package/dist/components/Message/index.js +4 -1
  30. package/dist/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.d.ts +3 -1
  31. package/dist/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.js +35 -26
  32. package/dist/components/MessageInput/AttachmentPreviewList/GeolocationPreview.d.ts +13 -0
  33. package/dist/components/MessageInput/AttachmentPreviewList/GeolocationPreview.js +25 -0
  34. package/dist/components/MessageInput/AttachmentSelector.d.ts +2 -1
  35. package/dist/components/MessageInput/AttachmentSelector.js +34 -12
  36. package/dist/components/MessageInput/MessageInput.d.ts +3 -1
  37. package/dist/components/MessageInput/MessageInput.js +7 -3
  38. package/dist/components/MessageInput/hooks/index.d.ts +1 -0
  39. package/dist/components/MessageInput/hooks/index.js +1 -0
  40. package/dist/components/MessageInput/hooks/useAttachmentsForPreview.d.ts +17 -0
  41. package/dist/components/MessageInput/hooks/useAttachmentsForPreview.js +22 -0
  42. package/dist/components/Poll/PollActions/AddCommentForm.js +8 -0
  43. package/dist/components/Poll/PollActions/SuggestPollOptionForm.js +6 -3
  44. package/dist/components/TextareaComposer/SuggestionList/SuggestionListItem.js +4 -1
  45. package/dist/components/index.d.ts +2 -1
  46. package/dist/components/index.js +2 -0
  47. package/dist/context/ComponentContext.d.ts +3 -0
  48. package/dist/css/v2/index.css +1 -1
  49. package/dist/css/v2/index.layout.css +1 -1
  50. package/dist/experimental/index.browser.cjs +11 -0
  51. package/dist/experimental/index.browser.cjs.map +2 -2
  52. package/dist/experimental/index.node.cjs +11 -0
  53. package/dist/experimental/index.node.cjs.map +2 -2
  54. package/dist/i18n/Streami18n.d.ts +20 -0
  55. package/dist/i18n/de.json +21 -1
  56. package/dist/i18n/en.json +21 -1
  57. package/dist/i18n/es.json +21 -1
  58. package/dist/i18n/fr.json +21 -1
  59. package/dist/i18n/hi.json +21 -1
  60. package/dist/i18n/it.json +21 -1
  61. package/dist/i18n/ja.json +21 -1
  62. package/dist/i18n/ko.json +21 -1
  63. package/dist/i18n/nl.json +21 -1
  64. package/dist/i18n/pt.json +21 -1
  65. package/dist/i18n/ru.json +21 -1
  66. package/dist/i18n/tr.json +21 -1
  67. package/dist/index.browser.cjs +1928 -1073
  68. package/dist/index.browser.cjs.map +4 -4
  69. package/dist/index.node.cjs +1941 -1073
  70. package/dist/index.node.cjs.map +4 -4
  71. package/dist/scss/v2/AttachmentList/AttachmentList-layout.scss +50 -0
  72. package/dist/scss/v2/AttachmentList/AttachmentList-theme.scss +56 -0
  73. package/dist/scss/v2/AttachmentPreviewList/AttachmentPreviewList-layout.scss +3 -0
  74. package/dist/scss/v2/AttachmentPreviewList/AttachmentPreviewList-theme.scss +11 -0
  75. package/dist/scss/v2/Dialog/Dialog-layout.scss +1 -2
  76. package/dist/scss/v2/Form/Form-layout.scss +40 -0
  77. package/dist/scss/v2/Form/Form-theme.scss +62 -0
  78. package/dist/scss/v2/Location/Location-layout.scss +52 -0
  79. package/dist/scss/v2/Location/Location-theme.scss +32 -0
  80. package/dist/scss/v2/MessageInput/MessageInput-theme.scss +7 -0
  81. package/dist/scss/v2/Modal/Modal-layout.scss +2 -0
  82. package/dist/scss/v2/Poll/Poll-layout.scss +0 -35
  83. package/dist/scss/v2/Poll/Poll-theme.scss +0 -28
  84. package/dist/scss/v2/_icons.scss +1 -0
  85. package/dist/scss/v2/index.layout.scss +1 -0
  86. package/dist/scss/v2/index.scss +1 -0
  87. package/package.json +4 -4
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { ReactPlayerProps } from 'react-player';
3
- import type { Attachment as StreamAttachment } from 'stream-chat';
3
+ import type { SharedLocationResponse, Attachment as StreamAttachment } from 'stream-chat';
4
4
  import type { AttachmentActionsProps } from './AttachmentActions';
5
5
  import type { AudioProps } from './Audio';
6
6
  import type { VoiceRecordingProps } from './VoiceRecording';
@@ -9,10 +9,11 @@ import type { FileAttachmentProps } from './FileAttachment';
9
9
  import type { GalleryProps, ImageProps } from '../Gallery';
10
10
  import type { UnsupportedAttachmentProps } from './UnsupportedAttachment';
11
11
  import type { ActionHandlerReturnType } from '../Message/hooks/useActionHandler';
12
- export declare const ATTACHMENT_GROUPS_ORDER: readonly ["card", "gallery", "image", "media", "audio", "voiceRecording", "file", "unsupported"];
12
+ import type { GeolocationProps } from './Geolocation';
13
+ export declare const ATTACHMENT_GROUPS_ORDER: readonly ["card", "gallery", "image", "media", "audio", "voiceRecording", "file", "geolocation", "unsupported"];
13
14
  export type AttachmentProps = {
14
15
  /** The message attachments to render, see [attachment structure](https://getstream.io/chat/docs/javascript/message_format/?language=javascript) **/
15
- attachments: StreamAttachment[];
16
+ attachments: (StreamAttachment | SharedLocationResponse)[];
16
17
  /** The handler function to call when an action is performed on an attachment, examples include canceling a \/giphy command or shuffling the results. */
17
18
  actionHandler?: ActionHandlerReturnType;
18
19
  /** Custom UI component for displaying attachment actions, defaults to and accepts same props as: [AttachmentActions](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/AttachmentActions.tsx) */
@@ -25,6 +26,7 @@ export type AttachmentProps = {
25
26
  File?: React.ComponentType<FileAttachmentProps>;
26
27
  /** Custom UI component for displaying a gallery of image type attachments, defaults to and accepts same props as: [Gallery](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/Gallery.tsx) */
27
28
  Gallery?: React.ComponentType<GalleryProps>;
29
+ Geolocation?: React.ComponentType<GeolocationProps>;
28
30
  /** Custom UI component for displaying an image type attachment, defaults to and accepts same props as: [Image](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/Image.tsx) */
29
31
  Image?: React.ComponentType<ImageProps>;
30
32
  /** Optional flag to signal that an attachment is a displayed as a part of a quoted message */
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from 'react';
2
- import { isAudioAttachment, isFileAttachment, isImageAttachment, isScrapedContent, isVideoAttachment, isVoiceRecordingAttachment, } from 'stream-chat';
3
- import { AudioContainer, CardContainer, FileContainer, GalleryContainer, ImageContainer, MediaContainer, UnsupportedAttachmentContainer, VoiceRecordingContainer, } from './AttachmentContainer';
2
+ import { isAudioAttachment, isFileAttachment, isImageAttachment, isScrapedContent, isSharedLocationResponse, isVideoAttachment, isVoiceRecordingAttachment, } from 'stream-chat';
3
+ import { AudioContainer, CardContainer, FileContainer, GalleryContainer, GeolocationContainer, ImageContainer, MediaContainer, UnsupportedAttachmentContainer, VoiceRecordingContainer, } from './AttachmentContainer';
4
4
  import { SUPPORTED_VIDEO_FORMATS } from './utils';
5
5
  const CONTAINER_MAP = {
6
6
  audio: AudioContainer,
@@ -18,6 +18,7 @@ export const ATTACHMENT_GROUPS_ORDER = [
18
18
  'audio',
19
19
  'voiceRecording',
20
20
  'file',
21
+ 'geolocation',
21
22
  'unsupported',
22
23
  ];
23
24
  /**
@@ -35,9 +36,14 @@ const renderGroupedAttachments = ({ attachments, ...rest }) => {
35
36
  const containers = attachments
36
37
  .filter((attachment) => !isImageAttachment(attachment))
37
38
  .reduce((typeMap, attachment) => {
38
- const attachmentType = getAttachmentType(attachment);
39
- const Container = CONTAINER_MAP[attachmentType];
40
- typeMap[attachmentType].push(React.createElement(Container, { key: `${attachmentType}-${typeMap[attachmentType].length}`, ...rest, attachment: attachment }));
39
+ if (isSharedLocationResponse(attachment)) {
40
+ typeMap.geolocation.push(React.createElement(GeolocationContainer, { ...rest, key: 'geolocation-container', location: attachment }));
41
+ }
42
+ else {
43
+ const attachmentType = getAttachmentType(attachment);
44
+ const Container = CONTAINER_MAP[attachmentType];
45
+ typeMap[attachmentType].push(React.createElement(Container, { key: `${attachmentType}-${typeMap[attachmentType].length}`, ...rest, attachment: attachment }));
46
+ }
41
47
  return typeMap;
42
48
  }, {
43
49
  audio: [],
@@ -50,6 +56,7 @@ const renderGroupedAttachments = ({ attachments, ...rest }) => {
50
56
  image: [],
51
57
  // eslint-disable-next-line sort-keys
52
58
  gallery: [],
59
+ geolocation: [],
53
60
  voiceRecording: [],
54
61
  });
55
62
  if (uploadedImages.length > 1) {
@@ -1,9 +1,9 @@
1
1
  import type { PropsWithChildren } from 'react';
2
2
  import React from 'react';
3
- import type { Attachment } from 'stream-chat';
4
- import type { AttachmentComponentType, GalleryAttachment, RenderAttachmentProps, RenderGalleryProps } from './utils';
3
+ import type { Attachment, SharedLocationResponse } from 'stream-chat';
4
+ import type { AttachmentComponentType, GalleryAttachment, GeolocationContainerProps, RenderAttachmentProps, RenderGalleryProps } from './utils';
5
5
  export type AttachmentContainerProps = {
6
- attachment: Attachment | GalleryAttachment;
6
+ attachment: Attachment | GalleryAttachment | SharedLocationResponse;
7
7
  componentType: AttachmentComponentType;
8
8
  };
9
9
  export declare const AttachmentWithinContainer: ({ attachment, children, componentType, }: PropsWithChildren<AttachmentContainerProps>) => React.JSX.Element;
@@ -15,4 +15,5 @@ export declare const FileContainer: ({ attachment, File, }: RenderAttachmentProp
15
15
  export declare const AudioContainer: ({ attachment, Audio, }: RenderAttachmentProps) => React.JSX.Element;
16
16
  export declare const VoiceRecordingContainer: ({ attachment, isQuoted, VoiceRecording, }: RenderAttachmentProps) => React.JSX.Element;
17
17
  export declare const MediaContainer: (props: RenderAttachmentProps) => React.JSX.Element;
18
+ export declare const GeolocationContainer: ({ Geolocation, location, }: GeolocationContainerProps) => React.JSX.Element;
18
19
  export declare const UnsupportedAttachmentContainer: ({ attachment, UnsupportedAttachment, }: RenderAttachmentProps) => React.JSX.Element;
@@ -2,19 +2,21 @@ import React, { useLayoutEffect, useRef, useState } from 'react';
2
2
  import ReactPlayer from 'react-player';
3
3
  import clsx from 'clsx';
4
4
  import * as linkify from 'linkifyjs';
5
+ import { isSharedLocationResponse } from 'stream-chat';
5
6
  import { AttachmentActions as DefaultAttachmentActions } from './AttachmentActions';
6
7
  import { Audio as DefaultAudio } from './Audio';
7
8
  import { VoiceRecording as DefaultVoiceRecording } from './VoiceRecording';
8
9
  import { Gallery as DefaultGallery, ImageComponent as DefaultImage } from '../Gallery';
9
10
  import { Card as DefaultCard } from './Card';
10
11
  import { FileAttachment as DefaultFile } from './FileAttachment';
12
+ import { Geolocation as DefaultGeolocation } from './Geolocation';
11
13
  import { UnsupportedAttachment as DefaultUnsupportedAttachment } from './UnsupportedAttachment';
12
14
  import { isGalleryAttachmentType, isSvgAttachment } from './utils';
13
15
  import { useChannelStateContext } from '../../context/ChannelStateContext';
14
16
  export const AttachmentWithinContainer = ({ attachment, children, componentType, }) => {
15
17
  const isGAT = isGalleryAttachmentType(attachment);
16
18
  let extra = '';
17
- if (!isGAT) {
19
+ if (!isGAT && !isSharedLocationResponse(attachment)) {
18
20
  extra =
19
21
  componentType === 'card' && !attachment?.image_url && !attachment?.thumb_url
20
22
  ? 'no-image'
@@ -57,17 +59,21 @@ export const GalleryContainer = ({ attachment, Gallery = DefaultGallery, }) => {
57
59
  const { imageAttachmentSizeHandler } = useChannelStateContext();
58
60
  const [attachmentConfigurations, setAttachmentConfigurations] = useState([]);
59
61
  useLayoutEffect(() => {
60
- if (imageElements.current &&
61
- imageElements.current.every((element) => !!element) &&
62
- imageAttachmentSizeHandler) {
63
- const newConfigurations = [];
64
- imageElements.current.forEach((element, i) => {
65
- const config = imageAttachmentSizeHandler(attachment.images[i], element);
66
- newConfigurations.push(config);
67
- });
68
- setAttachmentConfigurations(newConfigurations);
62
+ if (!imageElements.current || !imageAttachmentSizeHandler)
63
+ return;
64
+ const newConfigurations = [];
65
+ const nonNullImageElements = imageElements.current.filter((e) => !!e);
66
+ if (nonNullImageElements.length < imageElements.current.length) {
67
+ imageElements.current = nonNullImageElements;
69
68
  }
70
- }, [imageElements, imageAttachmentSizeHandler, attachment]);
69
+ imageElements.current.forEach((element, i) => {
70
+ if (!element)
71
+ return;
72
+ const config = imageAttachmentSizeHandler(attachment.images[i], element);
73
+ newConfigurations.push(config);
74
+ });
75
+ setAttachmentConfigurations(newConfigurations);
76
+ }, [imageAttachmentSizeHandler, attachment]);
71
77
  const images = attachment.images.map((image, i) => ({
72
78
  ...image,
73
79
  previewUrl: attachmentConfigurations[i]?.url || 'about:blank',
@@ -146,5 +152,7 @@ export const MediaContainer = (props) => {
146
152
  content,
147
153
  React.createElement(AttachmentActionsContainer, { ...props })))) : (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: componentType }, content));
148
154
  };
155
+ export const GeolocationContainer = ({ Geolocation = DefaultGeolocation, location, }) => (React.createElement(AttachmentWithinContainer, { attachment: location, componentType: 'geolocation' },
156
+ React.createElement(Geolocation, { location: location })));
149
157
  export const UnsupportedAttachmentContainer = ({ attachment, UnsupportedAttachment = DefaultUnsupportedAttachment, }) => (React.createElement(React.Fragment, null,
150
158
  React.createElement(UnsupportedAttachment, { attachment: attachment })));
@@ -0,0 +1,13 @@
1
+ import type { ComponentType } from 'react';
2
+ import React from 'react';
3
+ import type { Coords, SharedLocationResponse } from 'stream-chat';
4
+ export type GeolocationMapProps = Coords;
5
+ export type GeolocationProps = {
6
+ location: SharedLocationResponse;
7
+ GeolocationAttachmentMapPlaceholder?: ComponentType<GeolocationAttachmentMapPlaceholderProps>;
8
+ GeolocationMap?: ComponentType<GeolocationMapProps>;
9
+ };
10
+ export declare const Geolocation: ({ GeolocationAttachmentMapPlaceholder, GeolocationMap, location, }: GeolocationProps) => React.JSX.Element;
11
+ export type GeolocationAttachmentMapPlaceholderProps = {
12
+ location: SharedLocationResponse;
13
+ };
@@ -0,0 +1,34 @@
1
+ import { useEffect } from 'react';
2
+ import { useRef, useState } from 'react';
3
+ import React from 'react';
4
+ import { useChatContext, useTranslationContext } from '../../context';
5
+ import { ExternalLinkIcon, GeolocationIcon } from './icons';
6
+ export const Geolocation = ({ GeolocationAttachmentMapPlaceholder = DefaultGeolocationAttachmentMapPlaceholder, GeolocationMap, location, }) => {
7
+ const { channel, client } = useChatContext();
8
+ const { t } = useTranslationContext();
9
+ const [stoppedSharing, setStoppedSharing] = useState(!!location.end_at && new Date(location.end_at).getTime() < new Date().getTime());
10
+ const timeoutRef = useRef(undefined);
11
+ const isMyLocation = location.user_id === client.userID;
12
+ const isLiveLocation = !!location.end_at;
13
+ useEffect(() => {
14
+ if (!location.end_at)
15
+ return;
16
+ clearTimeout(timeoutRef.current);
17
+ timeoutRef.current = setTimeout(() => setStoppedSharing(true), new Date(location.end_at).getTime() - Date.now());
18
+ }, [location.end_at]);
19
+ return (React.createElement("div", { className: 'str-chat__message-attachment-geolocation', "data-testid": 'attachment-geolocation' },
20
+ React.createElement("div", { className: 'str-chat__message-attachment-geolocation__location-preview' }, GeolocationMap ? (React.createElement(GeolocationMap, { latitude: location.latitude, longitude: location.longitude })) : (React.createElement(GeolocationAttachmentMapPlaceholder, { location: location }))),
21
+ React.createElement("div", { className: 'str-chat__message-attachment-geolocation__status' }, isLiveLocation ? (stoppedSharing ? (t('Location sharing ended')) : isMyLocation ? (React.createElement("div", { className: 'str-chat__message-attachment-geolocation__status--active' },
22
+ React.createElement("button", { className: 'str-chat__message-attachment-geolocation__stop-sharing-button', onClick: () => channel?.stopLiveLocationSharing(location) }, t('Stop sharing')),
23
+ React.createElement("div", { className: 'str-chat__message-attachment-geolocation__status--active-until' }, t('Live until {{ timestamp }}', {
24
+ timestamp: t('timestamp/LiveLocation', { timestamp: location.end_at }),
25
+ })))) : (React.createElement("div", { className: 'str-chat__message-attachment-geolocation__status--active' },
26
+ React.createElement("div", { className: 'str-chat__message-attachment-geolocation__status--active-status' }, t('Live location')),
27
+ React.createElement("div", { className: 'str-chat__message-attachment-geolocation__status--active-until' }, t('Live until {{ timestamp }}', {
28
+ timestamp: t('timestamp/LiveLocation', { timestamp: location.end_at }),
29
+ }))))) : (t('Current location')))));
30
+ };
31
+ const DefaultGeolocationAttachmentMapPlaceholder = ({ location, }) => (React.createElement("div", { className: 'str-chat__message-attachment-geolocation__placeholder', "data-testid": 'geolocation-attachment-map-placeholder' },
32
+ React.createElement(GeolocationIcon, null),
33
+ React.createElement("a", { className: 'str-chat__message-attachment-geolocation__placeholder-link', href: `https://maps.google.com?q=${[location.latitude, location.longitude].join()}`, rel: 'noreferrer', target: '_blank' },
34
+ React.createElement(ExternalLinkIcon, null))));
@@ -3,3 +3,5 @@ import type { IconProps } from '../../types/types';
3
3
  export declare const DownloadIcon: ({ className }: IconProps) => React.JSX.Element;
4
4
  export declare const PlayTriangleIcon: () => React.JSX.Element;
5
5
  export declare const PauseIcon: () => React.JSX.Element;
6
+ export declare const GeolocationIcon: () => React.JSX.Element;
7
+ export declare const ExternalLinkIcon: () => React.JSX.Element;
@@ -5,3 +5,8 @@ export const PlayTriangleIcon = () => (React.createElement("svg", { fill: 'none'
5
5
  React.createElement("path", { d: 'M0.5 0V14L11.5 7L0.5 0Z', fill: '#080707' })));
6
6
  export const PauseIcon = () => (React.createElement("svg", { fill: 'none', viewBox: '0 0 12 14', xmlns: 'http://www.w3.org/2000/svg' },
7
7
  React.createElement("path", { d: 'M0 14H4V0H0V14ZM8 0V14H12V0H8Z', fill: '#080707' })));
8
+ export const GeolocationIcon = () => (React.createElement("svg", { className: 'str-chat__message-geolocation__icon', fill: 'currentColor', viewBox: '0 0 255.856 255.856', xmlns: 'http://www.w3.org/2000/svg' },
9
+ React.createElement("path", { d: 'M127.928 38.8c-30.75 0-55.768 25.017-55.768 55.767s25.018 55.767 55.768 55.767 55.768-25.017 55.768-55.767S158.678 38.8 127.928 38.8zm0 96.533c-22.479 0-40.768-18.288-40.768-40.767S105.449 53.8 127.928 53.8s40.768 18.288 40.768 40.767-18.288 40.766-40.768 40.766z', strokeWidth: '60' }),
10
+ React.createElement("path", { d: 'M127.928 0C75.784 0 33.362 42.422 33.362 94.566c0 30.072 25.22 74.875 40.253 98.904 9.891 15.809 20.52 30.855 29.928 42.365 15.101 18.474 20.506 20.02 24.386 20.02 3.938 0 9.041-1.547 24.095-20.031 9.429-11.579 20.063-26.616 29.944-42.342 15.136-24.088 40.527-68.971 40.527-98.917C222.495 42.422 180.073 0 127.928 0zm43.641 181.803c-19.396 31.483-37.203 52.757-43.73 58.188-6.561-5.264-24.079-26.032-43.746-58.089-22.707-37.015-35.73-68.848-35.73-87.336C48.362 50.693 84.055 15 127.928 15s79.566 35.693 79.566 79.566c.001 18.382-13.094 50.178-35.925 87.237z' })));
11
+ export const ExternalLinkIcon = () => (React.createElement("svg", { fill: 'currentColor', viewBox: '0 0 16 16', xmlns: 'http://www.w3.org/2000/svg' },
12
+ React.createElement("path", { d: 'M12.586 2H10a1 1 0 1 1 0-2h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V3.414l-6.793 6.793a1 1 0 0 1-1.414-1.414L12.586 2zM6 1a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h8.967a1 1 0 0 0 1-.99L13 9.99a1 1 0 1 1 2 .02l-.033 3.023a3 3 0 0 1-3 2.967H3a3 3 0 0 1-3-3V4a3 3 0 0 1 3-3h3z' })));
@@ -5,7 +5,9 @@ export * from './Audio';
5
5
  export * from './audioSampling';
6
6
  export * from './Card';
7
7
  export * from './components';
8
- export * from './UnsupportedAttachment';
9
8
  export * from './FileAttachment';
9
+ export * from './Geolocation';
10
+ export * from './UnsupportedAttachment';
10
11
  export * from './utils';
11
12
  export { useAudioController } from './hooks/useAudioController';
13
+ export * from '../Location/hooks/useLiveLocationSharingManager';
@@ -5,7 +5,9 @@ export * from './Audio';
5
5
  export * from './audioSampling';
6
6
  export * from './Card';
7
7
  export * from './components';
8
- export * from './UnsupportedAttachment';
9
8
  export * from './FileAttachment';
9
+ export * from './Geolocation';
10
+ export * from './UnsupportedAttachment';
10
11
  export * from './utils';
11
12
  export { useAudioController } from './hooks/useAudioController';
13
+ export * from '../Location/hooks/useLiveLocationSharingManager';
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { Attachment } from 'stream-chat';
2
+ import type { Attachment, SharedLocationResponse } from 'stream-chat';
3
3
  import type { ATTACHMENT_GROUPS_ORDER, AttachmentProps } from './Attachment';
4
4
  export declare const SUPPORTED_VIDEO_FORMATS: string[];
5
5
  export type AttachmentComponentType = (typeof ATTACHMENT_GROUPS_ORDER)[number];
@@ -14,6 +14,9 @@ export type RenderAttachmentProps = Omit<AttachmentProps, 'attachments'> & {
14
14
  export type RenderGalleryProps = Omit<AttachmentProps, 'attachments'> & {
15
15
  attachment: GalleryAttachment;
16
16
  };
17
+ export type GeolocationContainerProps = Omit<AttachmentProps, 'attachments'> & {
18
+ location: SharedLocationResponse;
19
+ };
17
20
  export declare const isGalleryAttachmentType: (attachment: Attachment | GalleryAttachment) => attachment is GalleryAttachment;
18
21
  export declare const isSvgAttachment: (attachment: Attachment) => boolean;
19
22
  export declare const divMod: (num: number, divisor: number) => number[];
@@ -5,7 +5,7 @@ import type { OnMentionAction } from './hooks/useMentionsHandlers';
5
5
  import type { LoadingErrorIndicatorProps } from '../Loading';
6
6
  import type { ComponentContextValue } from '../../context';
7
7
  import type { ChannelUnreadUiState, GiphyVersions, ImageAttachmentSizeHandler, VideoAttachmentSizeHandler } from '../../types/types';
8
- type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageIsThreadReplyInChannelButtonIndicator' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'ReminderNotification' | 'SendButton' | 'SendToChannelCheckbox' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
8
+ type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'ShareLocationDialog' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageIsThreadReplyInChannelButtonIndicator' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'ReminderNotification' | 'SendButton' | 'SendToChannelCheckbox' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
9
9
  export type ChannelProps = ChannelPropsForwardedToComponentContext & {
10
10
  /** Custom handler function that runs when the active channel has unread messages and the app is running on a separate browser tab */
11
11
  activeUnreadHandler?: (unread: number, documentTitle: string) => void;
@@ -759,6 +759,7 @@ const ChannelInner = (props) => {
759
759
  ReminderNotification: props.ReminderNotification,
760
760
  SendButton: props.SendButton,
761
761
  SendToChannelCheckbox: props.SendToChannelCheckbox,
762
+ ShareLocationDialog: props.ShareLocationDialog,
762
763
  StartRecordingAudioButton: props.StartRecordingAudioButton,
763
764
  StopAIGenerationButton: props.StopAIGenerationButton,
764
765
  StreamedMessageText: props.StreamedMessageText,
@@ -824,6 +825,7 @@ const ChannelInner = (props) => {
824
825
  props.ReminderNotification,
825
826
  props.SendButton,
826
827
  props.SendToChannelCheckbox,
828
+ props.ShareLocationDialog,
827
829
  props.StartRecordingAudioButton,
828
830
  props.StopAIGenerationButton,
829
831
  props.StreamedMessageText,
@@ -57,6 +57,9 @@ export const getLatestMessagePreview = (channel, t, userLanguage = 'en', isMessa
57
57
  if (latestMessage.attachments?.length) {
58
58
  return t('🏙 Attachment...');
59
59
  }
60
+ if (latestMessage.shared_location) {
61
+ return t('📍Shared location');
62
+ }
60
63
  return t('Empty message...');
61
64
  };
62
65
  export const getGroupChannelDisplayInfo = (channel) => {
@@ -24,7 +24,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
24
24
  useEffect(() => {
25
25
  if (!client)
26
26
  return;
27
- const version = "13.2.2";
27
+ const version = "13.3.0";
28
28
  const userAgent = client.getUserAgent();
29
29
  if (!userAgent.includes('stream-chat-react')) {
30
30
  // result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
@@ -5,8 +5,9 @@ export interface DialogAnchorOptions {
5
5
  open: boolean;
6
6
  placement: Placement;
7
7
  referenceElement: HTMLElement | null;
8
+ allowFlip?: boolean;
8
9
  }
9
- export declare function useDialogAnchor<T extends HTMLElement>({ open, placement, referenceElement, }: DialogAnchorOptions): {
10
+ export declare function useDialogAnchor<T extends HTMLElement>({ allowFlip, open, placement, referenceElement, }: DialogAnchorOptions): {
10
11
  attributes: {
11
12
  [key: string]: {
12
13
  [key: string]: string;
@@ -22,4 +23,4 @@ export type DialogAnchorProps = PropsWithChildren<Partial<DialogAnchorOptions>>
22
23
  focus?: boolean;
23
24
  trapFocus?: boolean;
24
25
  } & ComponentProps<'div'>;
25
- export declare const DialogAnchor: ({ children, className, focus, id, placement, referenceElement, tabIndex, trapFocus, ...restDivProps }: DialogAnchorProps) => React.JSX.Element | null;
26
+ export declare const DialogAnchor: ({ allowFlip, children, className, focus, id, placement, referenceElement, tabIndex, trapFocus, ...restDivProps }: DialogAnchorProps) => React.JSX.Element | null;
@@ -4,10 +4,14 @@ import { FocusScope } from '@react-aria/focus';
4
4
  import { usePopper } from 'react-popper';
5
5
  import { DialogPortalEntry } from './DialogPortal';
6
6
  import { useDialog, useDialogIsOpen } from './hooks';
7
- export function useDialogAnchor({ open, placement, referenceElement, }) {
7
+ export function useDialogAnchor({ allowFlip, open, placement, referenceElement, }) {
8
8
  const [popperElement, setPopperElement] = useState(null);
9
9
  const { attributes, styles, update } = usePopper(referenceElement, popperElement, {
10
10
  modifiers: [
11
+ {
12
+ enabled: !!allowFlip, // Prevent flipping
13
+ name: 'flip',
14
+ },
11
15
  {
12
16
  name: 'eventListeners',
13
17
  options: {
@@ -37,10 +41,11 @@ export function useDialogAnchor({ open, placement, referenceElement, }) {
37
41
  styles,
38
42
  };
39
43
  }
40
- export const DialogAnchor = ({ children, className, focus = true, id, placement = 'auto', referenceElement = null, tabIndex, trapFocus, ...restDivProps }) => {
44
+ export const DialogAnchor = ({ allowFlip = true, children, className, focus = true, id, placement = 'auto', referenceElement = null, tabIndex, trapFocus, ...restDivProps }) => {
41
45
  const dialog = useDialog({ id });
42
46
  const open = useDialogIsOpen(id);
43
47
  const { attributes, setPopperElement, styles } = useDialogAnchor({
48
+ allowFlip,
44
49
  open,
45
50
  placement,
46
51
  referenceElement,
@@ -0,0 +1,14 @@
1
+ import type { PropsWithChildren } from 'react';
2
+ import React from 'react';
3
+ import type { Placement } from '@popperjs/core';
4
+ type DropdownContextValue = {
5
+ close(): void;
6
+ };
7
+ export declare const useDropdownContext: () => DropdownContextValue;
8
+ export type DropdownProps = PropsWithChildren<{
9
+ className?: string;
10
+ openButtonProps?: React.HTMLAttributes<HTMLButtonElement>;
11
+ placement?: Placement;
12
+ }>;
13
+ export declare const Dropdown: (props: DropdownProps) => React.JSX.Element;
14
+ export {};
@@ -0,0 +1,49 @@
1
+ import { useRef } from 'react';
2
+ import { useEffect } from 'react';
3
+ import React, { useState } from 'react';
4
+ import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
5
+ import { DialogManagerProvider, useTranslationContext } from '../../context';
6
+ const DropdownContext = React.createContext({
7
+ close: () => null,
8
+ });
9
+ const DropdownContextProvider = ({ children, ...props }) => (React.createElement(DropdownContext.Provider, { value: props }, children));
10
+ export const useDropdownContext = () => React.useContext(DropdownContext);
11
+ export const Dropdown = (props) => {
12
+ const dropdownDialogId = `dropdown`;
13
+ return (React.createElement("div", { className: 'str-chat__dropdown' },
14
+ React.createElement(DialogManagerProvider, { id: dropdownDialogId },
15
+ React.createElement(DropdownInner, { ...props, dialogId: dropdownDialogId }))));
16
+ };
17
+ const DropdownInner = ({ children, dialogId, openButtonProps, placement = 'bottom', }) => {
18
+ const { t } = useTranslationContext();
19
+ const [openButton, setOpenButton] = useState(null);
20
+ const [dropdownWidth, setDropdownWidth] = useState('');
21
+ const dropdownRef = useRef(null);
22
+ const dialog = useDialog({ id: dialogId });
23
+ const dropdownDialogIsOpen = useDialogIsOpen(dialogId);
24
+ useEffect(() => {
25
+ if (!openButton || typeof ResizeObserver === 'undefined')
26
+ return;
27
+ let timeout;
28
+ const observer = new ResizeObserver(([button]) => {
29
+ if (timeout)
30
+ clearTimeout(timeout);
31
+ timeout = setTimeout(() => {
32
+ const width = button.target.getBoundingClientRect().width + 'px';
33
+ if (!dropdownRef.current) {
34
+ setDropdownWidth(width);
35
+ return;
36
+ }
37
+ dropdownRef.current.style.width = width;
38
+ }, 100);
39
+ });
40
+ observer.observe(openButton);
41
+ return () => {
42
+ observer.disconnect();
43
+ };
44
+ }, [openButton]);
45
+ return (React.createElement(DropdownContextProvider, { close: dialog.close },
46
+ React.createElement("button", { "aria-expanded": dropdownDialogIsOpen, "aria-haspopup": 'true', "aria-label": t('aria/Open Menu'), className: 'str-chat__dropdown__open-button', "data-testid": 'dropdown-open-button', ...openButtonProps, onClick: () => dialog?.toggle(), ref: setOpenButton }),
47
+ React.createElement(DialogAnchor, { allowFlip: false, id: dialogId, placement: placement, referenceElement: openButton, tabIndex: -1, trapFocus: true },
48
+ React.createElement("div", { className: 'str-chat__dropdown__items', ref: dropdownRef, style: { width: dropdownWidth } }, children))));
49
+ };
@@ -8,7 +8,9 @@ export const SwitchField = ({ children, ...props }) => {
8
8
  event.preventDefault();
9
9
  inputRef.current.click();
10
10
  };
11
- return (React.createElement("div", { className: 'str-chat__form__field str-chat__form__switch-field' },
11
+ return (React.createElement("div", { className: clsx('str-chat__form__field str-chat__form__switch-field', {
12
+ 'str-chat__form__field str-chat__form__switch-field--disabled': props.disabled,
13
+ }) },
12
14
  React.createElement("label", null,
13
15
  React.createElement("div", { className: 'str-chat__form__field str-chat__form__switch-field-content' }, children),
14
16
  React.createElement("input", { type: 'checkbox', ...props, ref: inputRef }),
@@ -0,0 +1,18 @@
1
+ import React, { type ComponentType } from 'react';
2
+ import type { Coords } from 'stream-chat';
3
+ export type ShareGeolocationMapProps = Partial<Coords> & {
4
+ loadingLocation: boolean;
5
+ restartLocationWatching: () => void;
6
+ geolocationPositionError?: GeolocationPositionError;
7
+ };
8
+ export type ShareLocationDialogProps = {
9
+ close: () => void;
10
+ shareDurations?: number[];
11
+ GeolocationMap?: ComponentType<ShareGeolocationMapProps>;
12
+ DurationDropdownItems?: ComponentType<DurationDropdownItemsProps>;
13
+ };
14
+ export declare const ShareLocationDialog: ({ close, GeolocationMap, shareDurations, }: ShareLocationDialogProps) => React.JSX.Element;
15
+ export type DurationDropdownItemsProps = {
16
+ durations: number[];
17
+ selectDuration: (duration: number) => void;
18
+ };
@@ -0,0 +1,139 @@
1
+ import React, { useCallback, useEffect, useMemo, useState, } from 'react';
2
+ import { useChatContext, useTranslationContext } from '../../context';
3
+ import { useMessageComposer } from '../MessageInput';
4
+ import { ModalHeader } from '../Modal/ModalHeader';
5
+ import { SimpleSwitchField } from '../Form/SwitchField';
6
+ import { Dropdown, useDropdownContext } from '../Form/Dropdown';
7
+ const MIN_LIVE_LOCATION_SHARE_DURATION = 60 * 1000; // 1 minute;
8
+ const DEFAULT_SHARE_LOCATION_DURATIONS = [
9
+ 15 * 60 * 1000, // 15 minutes
10
+ 60 * 60 * 1000, // 1 hour
11
+ 8 * 60 * 60 * 1000, // 8 hours
12
+ ];
13
+ const DefaultGeolocationMap = () => null;
14
+ export const ShareLocationDialog = ({ close, GeolocationMap = DefaultGeolocationMap, shareDurations = DEFAULT_SHARE_LOCATION_DURATIONS, }) => {
15
+ const { client } = useChatContext();
16
+ const { t } = useTranslationContext();
17
+ const messageComposer = useMessageComposer();
18
+ const [durations, setDurations] = useState([]);
19
+ const [selectedDuration, setSelectedDuration] = useState(undefined);
20
+ const [geolocationPosition, setGeolocationPosition] = useState(null);
21
+ const [loadingLocation, setLoadingLocation] = useState(false);
22
+ const [geolocationPositionError, setGeolocationPositionError] = useState(undefined);
23
+ const validShareDurations = useMemo(() => shareDurations.filter((d) => d >= MIN_LIVE_LOCATION_SHARE_DURATION), [shareDurations]);
24
+ const openDropdownButtonProps = useMemo(() => ({
25
+ children: (() => (React.createElement("div", null, t('duration/Share Location', {
26
+ milliseconds: selectedDuration ?? durations[0],
27
+ }))))(), // todo: make it a component
28
+ }), [durations, selectedDuration, t]);
29
+ const getPosition = useCallback(() => new Promise((resolve, reject) => {
30
+ navigator.geolocation.getCurrentPosition((position) => {
31
+ resolve(position);
32
+ }, (positionError) => {
33
+ console.warn(positionError);
34
+ reject(positionError);
35
+ }, { timeout: 1000 });
36
+ }), []);
37
+ const setupPositionWatching = useCallback(() => {
38
+ setLoadingLocation(true);
39
+ const watch = navigator.geolocation.watchPosition((position) => {
40
+ setGeolocationPosition(position);
41
+ setLoadingLocation(false);
42
+ setGeolocationPositionError(undefined);
43
+ }, (error) => {
44
+ setGeolocationPosition(null);
45
+ setLoadingLocation(false);
46
+ setGeolocationPositionError(error);
47
+ }, { timeout: 1000 });
48
+ return () => {
49
+ navigator.geolocation.clearWatch(watch);
50
+ };
51
+ }, []);
52
+ useEffect(() => setupPositionWatching(), [setupPositionWatching]);
53
+ return (React.createElement("div", { className: 'str-chat__dialog str-chat__share-location-dialog', "data-testid": 'share-location-dialog' },
54
+ React.createElement(ModalHeader, { close: close, title: t('Share Location') }),
55
+ React.createElement("div", { className: 'str-chat__dialog__body' },
56
+ React.createElement(GeolocationMap, { geolocationPositionError: geolocationPositionError, latitude: geolocationPosition?.coords.latitude, loadingLocation: loadingLocation, longitude: geolocationPosition?.coords.longitude, restartLocationWatching: setupPositionWatching }),
57
+ validShareDurations.length > 0 && (React.createElement("div", { className: 'str-chat__live-location-activation' },
58
+ React.createElement(SimpleSwitchField, { checked: durations.length > 0, "data-testid": 'share-location-dialog-live-location-switch', disabled: !geolocationPosition, labelText: t('Share live location for'), onChange: (e) => {
59
+ e.stopPropagation();
60
+ if (durations.length > 0) {
61
+ setDurations([]);
62
+ setSelectedDuration(undefined);
63
+ }
64
+ else {
65
+ setDurations(validShareDurations);
66
+ setSelectedDuration(validShareDurations[0]);
67
+ }
68
+ } }),
69
+ durations.length > 0 && (React.createElement(Dropdown, { openButtonProps: openDropdownButtonProps, placement: 'bottom-start' },
70
+ React.createElement(DurationDropdownItems, { durations: durations, selectDuration: setSelectedDuration })))))),
71
+ React.createElement("div", { className: 'str-chat__dialog__controls' },
72
+ React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--cancel', onClick: () => {
73
+ messageComposer.locationComposer.initState();
74
+ close();
75
+ } }, t('Cancel')),
76
+ React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--submit', disabled: !geolocationPosition, onClick: async () => {
77
+ let coords = geolocationPosition && {
78
+ latitude: geolocationPosition.coords.latitude,
79
+ longitude: geolocationPosition.coords.longitude,
80
+ };
81
+ if (!coords) {
82
+ coords = (await getPosition()).coords;
83
+ }
84
+ messageComposer.locationComposer.setData({
85
+ ...coords,
86
+ durationMs: selectedDuration,
87
+ });
88
+ close();
89
+ }, type: 'submit' }, t('Attach')),
90
+ React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--submit', disabled: !geolocationPosition, onClick: async () => {
91
+ let coords = geolocationPosition && {
92
+ latitude: geolocationPosition.coords.latitude,
93
+ longitude: geolocationPosition.coords.longitude,
94
+ };
95
+ if (!coords) {
96
+ try {
97
+ coords = (await getPosition()).coords;
98
+ }
99
+ catch (e) {
100
+ client.notifications.addError({
101
+ message: t('Failed to retrieve location'),
102
+ options: {
103
+ originalError: e instanceof Error ? e : undefined,
104
+ type: 'browser-api:location:get:failed',
105
+ },
106
+ origin: { emitter: 'ShareLocationDialog' },
107
+ });
108
+ return;
109
+ }
110
+ }
111
+ messageComposer.locationComposer.setData({
112
+ ...coords,
113
+ durationMs: selectedDuration,
114
+ });
115
+ try {
116
+ await messageComposer.sendLocation();
117
+ }
118
+ catch (err) {
119
+ client.notifications.addError({
120
+ message: t('Failed to share location'),
121
+ options: {
122
+ originalError: err instanceof Error ? err : undefined,
123
+ type: 'api:location:share:failed',
124
+ },
125
+ origin: { emitter: 'ShareLocationDialog' },
126
+ });
127
+ return;
128
+ }
129
+ close();
130
+ }, type: 'submit' }, t('Share')))));
131
+ };
132
+ const DurationDropdownItems = ({ durations, selectDuration, }) => {
133
+ const { t } = useTranslationContext();
134
+ const { close } = useDropdownContext();
135
+ return durations.map((duration) => (React.createElement("button", { className: 'str-chat__live-location-sharing-duration-option', key: `duration-${duration}}`, onClick: () => {
136
+ selectDuration(duration);
137
+ close();
138
+ }, role: 'option' }, t('duration/Share Location', { milliseconds: duration }))));
139
+ };
@@ -0,0 +1,18 @@
1
+ import { LiveLocationManager } from 'stream-chat';
2
+ import type { LiveLocationManagerConstructorParameters, StreamChat } from 'stream-chat';
3
+ /**
4
+ * Checks whether the current browser is Safari.
5
+ */
6
+ export declare const isSafari: () => boolean;
7
+ /**
8
+ * Checks whether the current browser is Firefox.
9
+ */
10
+ export declare const isFirefox: () => boolean;
11
+ /**
12
+ * Checks whether the current browser is Google Chrome.
13
+ */
14
+ export declare const isChrome: () => boolean;
15
+ export declare const useLiveLocationSharingManager: ({ client, getDeviceId, watchLocation, }: Omit<LiveLocationManagerConstructorParameters, 'client' | 'getDeviceId'> & {
16
+ client?: StreamChat | null;
17
+ getDeviceId?: () => string;
18
+ }) => LiveLocationManager | null;