Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/components/Attachment/Geolocation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { useEffect } from 'react';
import { useRef, useState } from 'react';
import React from 'react';
import type { Coords, SharedLocationResponse } from 'stream-chat';
import { useChatContext, useTranslationContext } from '../../context';
import {
useChatContext,
useComponentContext,
useTranslationContext,
} from '../../context';
import { ExternalLinkIcon } from './icons';
import { IconLocation } from '../Icons';
import { IconLocation as DefaultIconLocation } from '../Icons';
import { Button } from '../Button';

export type GeolocationMapProps = Coords;
Expand Down Expand Up @@ -102,6 +106,7 @@ const DefaultGeolocationAttachmentMapPlaceholder = ({
location,
}: GeolocationAttachmentMapPlaceholderProps) => {
const { t } = useTranslationContext();
const { icons: { IconLocation = DefaultIconLocation } = {} } = useComponentContext();

return (
<div
Expand Down
17 changes: 10 additions & 7 deletions src/components/Attachment/Giphy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
useComponentContext,
useTranslationContext,
} from '../../context';
import { IconGiphy } from '../Icons';
import { IconGiphy as DefaultIconGiphy } from '../Icons';
import { type CSSProperties, useLayoutEffect, useMemo, useRef, useState } from 'react';
import type { ImageAttachmentConfiguration } from '../../types/types';

Expand Down Expand Up @@ -78,9 +78,12 @@ export const Giphy = ({ attachment }: GiphyAttachmentProps) => {
);
};

const GiphyBadge = () => (
<div className='str-chat__giphy-badge'>
<IconGiphy />
Giphy
</div>
);
const GiphyBadge = () => {
const { icons: { IconGiphy = DefaultIconGiphy } = {} } = useComponentContext();
return (
<div className='str-chat__giphy-badge'>
<IconGiphy />
Giphy
</div>
);
};
4 changes: 3 additions & 1 deletion src/components/Attachment/LinkPreview/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import React from 'react';
import { BaseImage } from '../../BaseImage';
import { SafeAnchor } from '../../SafeAnchor';
import { useChannelStateContext } from '../../../context/ChannelStateContext';
import { useComponentContext } from '../../../context';

import type { Attachment } from 'stream-chat';
import type { RenderAttachmentProps } from '../utils';
import type { Dimensions } from '../../../types/types';
import { IconLink } from '../../Icons';
import { IconLink as DefaultIconLink } from '../../Icons';
import { UnableToRenderCard } from './UnableToRenderCard';
import clsx from 'clsx';

Expand Down Expand Up @@ -64,6 +65,7 @@ type CardContentProps = RenderAttachmentProps['attachment'];
const CardContent = (props: CardContentProps) => {
const { og_scrape_url, text, title, title_link } = props;
const url = title_link || og_scrape_url;
const { icons: { IconLink = DefaultIconLink } = {} } = useComponentContext();

return (
<div className='str-chat__message-attachment-card--content'>
Expand Down
37 changes: 20 additions & 17 deletions src/components/Attachment/LinkPreview/CardAudio.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { type AudioPlayerState, ProgressBar, useAudioPlayer } from '../../AudioPlayback';
import { useMessageContext } from '../../../context';
import { useComponentContext, useMessageContext } from '../../../context';
import { useStateStore } from '../../../store';
import { PlayButton } from '../../Button';
import type { AudioProps } from '../Audio';
import React from 'react';
import { IconLink } from '../../Icons';
import { IconLink as DefaultIconLink } from '../../Icons';
import { SafeAnchor } from '../../SafeAnchor';
import type { CardProps } from './Card';

Expand All @@ -21,22 +21,25 @@ const SourceLink = ({
author_name,
showUrl,
url,
}: Pick<CardProps, 'author_name'> & { url: string; showUrl?: boolean }) => (
<div
className='str-chat__message-attachment-card--source-link'
data-testid='card-source-link'
>
<IconLink />
<SafeAnchor
className='str-chat__message-attachment-card--url'
href={url}
rel='noopener noreferrer'
target='_blank'
}: Pick<CardProps, 'author_name'> & { url: string; showUrl?: boolean }) => {
const { icons: { IconLink = DefaultIconLink } = {} } = useComponentContext();
return (
<div
className='str-chat__message-attachment-card--source-link'
data-testid='card-source-link'
>
{showUrl ? url : author_name || getHostFromURL(url)}
</SafeAnchor>
</div>
);
<IconLink />
<SafeAnchor
className='str-chat__message-attachment-card--url'
href={url}
rel='noopener noreferrer'
target='_blank'
>
{showUrl ? url : author_name || getHostFromURL(url)}
</SafeAnchor>
</div>
);
};

const audioPlayerStateSelector = (state: AudioPlayerState) => ({
durationSeconds: state.durationSeconds,
Expand Down
3 changes: 2 additions & 1 deletion src/components/Attachment/ModalGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Gallery as DefaultGallery, GalleryUI } from '../Gallery';
import { LoadingIndicator } from '../Loading';
import { GlobalModal, type ModalCloseSource } from '../Modal';
import { useComponentContext, useTranslationContext } from '../../context';
import { IconRetry } from '../Icons';
import { IconRetry as DefaultIconRetry } from '../Icons';
import { VideoThumbnail } from '../VideoPlayer/VideoThumbnail';

const MAX_VISIBLE_THUMBNAILS = 4;
Expand Down Expand Up @@ -144,6 +144,7 @@ const ThumbnailButton = ({
showOverlay,
}: ThumbnailButtonProps) => {
const { t } = useTranslationContext();
const { icons: { IconRetry = DefaultIconRetry } = {} } = useComponentContext();
const imageUrl = item.imageUrl;
const [isLoadFailed, setIsLoadFailed] = useState(false);
const [isImageLoading, setIsImageLoading] = useState(Boolean(imageUrl));
Expand Down
7 changes: 5 additions & 2 deletions src/components/Attachment/UnsupportedAttachment.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React from 'react';
import type { Attachment } from 'stream-chat';
import { useTranslationContext } from '../../context';
import { IconUnsupportedAttachment } from '../Icons';
import { useComponentContext, useTranslationContext } from '../../context';
import { IconUnsupportedAttachment as DefaultIconUnsupportedAttachment } from '../Icons';

export type UnsupportedAttachmentProps = {
attachment: Attachment;
};

export const UnsupportedAttachment = () => {
const { icons: { IconUnsupportedAttachment = DefaultIconUnsupportedAttachment } = {} } =
useComponentContext();

const { t } = useTranslationContext('UnsupportedAttachment');
return (
<div
Expand Down
6 changes: 4 additions & 2 deletions src/components/Attachment/VisibilityDisclaimer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import { IconEyeFill } from '../Icons';
import { useTranslationContext } from '../../context';
import { IconEyeFill as DefaultIconEyeFill } from '../Icons';
import { useComponentContext, useTranslationContext } from '../../context';

export const VisibilityDisclaimer = () => {
const { icons: { IconEyeFill = DefaultIconEyeFill } = {} } = useComponentContext();

const { t } = useTranslationContext();
return (
<div className='str-chat__visibility-disclaimer'>
Expand Down
6 changes: 4 additions & 2 deletions src/components/Attachment/components/DownloadButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';
import clsx from 'clsx';
import { sanitizeUrl } from '@braintree/sanitize-url';

import { useTranslationContext } from '../../../context';
import { IconDownload } from '../../Icons';
import { useComponentContext, useTranslationContext } from '../../../context';
import { IconDownload as DefaultIconDownload } from '../../Icons';

export type DownloadButtonProps = {
/** Attachment asset URL (e.g. `asset_url`). */
Expand All @@ -25,6 +25,8 @@ export const DownloadButton = ({
suggestedFileName,
tooltipTitle,
}: DownloadButtonProps) => {
const { icons: { IconDownload = DefaultIconDownload } = {} } = useComponentContext();

const { t } = useTranslationContext();
if (!assetUrl) return null;
const href = sanitizeUrl(assetUrl);
Expand Down
15 changes: 11 additions & 4 deletions src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ import React, {
useMemo,
useState,
} from 'react';
import { IconUser } from '../Icons';
import { useComponentContext } from '../../context';
import { IconUser as DefaultIconUser } from '../Icons';

export type AvatarProps = {
/** Custom icon rendered when there is no image and no initials */
/**
* Custom icon rendered when there is no image and no initials.
* @deprecated Use the `icons.IconUser` slot on `ComponentContext` (via `<WithComponents overrides={{ icons: { IconUser: ... } }}>`) instead.
* Passing this prop still wins over the context slot for backwards compatibility.
*/
FallbackIcon?: ComponentType<ComponentPropsWithoutRef<'svg'>>;
/** URL of the avatar image */
imageUrl?: string;
Expand Down Expand Up @@ -46,13 +51,15 @@ const getInitials = (name?: string) => {
*/
export const Avatar = ({
className,
FallbackIcon = IconUser,
FallbackIcon,
imageUrl,
isOnline,
size,
userName,
...rest
}: AvatarProps) => {
const { icons: { IconUser = DefaultIconUser } = {} } = useComponentContext();
const ResolvedFallbackIcon = FallbackIcon ?? IconUser;
const [error, setError] = useState(false);

useEffect(() => () => setError(false), [imageUrl]);
Expand Down Expand Up @@ -108,7 +115,7 @@ export const Avatar = ({
{sizeAwareInitials}
</div>
)}
{!sizeAwareInitials.length && <FallbackIcon />}
{!sizeAwareInitials.length && <ResolvedFallbackIcon />}
</>
)}
</div>
Expand Down
17 changes: 11 additions & 6 deletions src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import clsx from 'clsx';
import React, { type ComponentProps } from 'react';
import { IconExclamationMarkFill } from '../Icons';
import { useComponentContext } from '../../context';
import { IconExclamationMarkFill as DefaultIconExclamationMarkFill } from '../Icons';

export type BadgeVariant =
| 'default'
Expand Down Expand Up @@ -47,8 +48,12 @@ export const ErrorBadge = ({
className,
size = 'sm',
...rest
}: Omit<BadgeProps, 'variant'>) => (
<Badge {...rest} className={className} size={size} variant='error'>
<IconExclamationMarkFill />
</Badge>
);
}: Omit<BadgeProps, 'variant'>) => {
const { icons: { IconExclamationMarkFill = DefaultIconExclamationMarkFill } = {} } =
useComponentContext();
return (
<Badge {...rest} className={className} size={size} variant='error'>
<IconExclamationMarkFill />
</Badge>
);
};
25 changes: 18 additions & 7 deletions src/components/Badge/MediaBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { IconMicrophoneSolid, IconVideoFill } from '../Icons';
import { useComponentContext } from '../../context';
import {
IconMicrophoneSolid as DefaultIconMicrophoneSolid,
IconVideoFill as DefaultIconVideoFill,
} from '../Icons';
import React, { type ComponentType } from 'react';
import type { LocalAttachment } from 'stream-chat';
import clsx from 'clsx';
Expand All @@ -10,13 +14,20 @@ export type MediaBadgeProps = {
variant: 'video' | 'voice-recording' | string;
};

const MediaBadgeVariantToIcon: Record<MediaBadgeVariant, ComponentType> = {
video: IconVideoFill,
voiceRecording: IconMicrophoneSolid,
};

export const MediaBadge = ({ attachment, variant }: MediaBadgeProps) => {
const Icon = MediaBadgeVariantToIcon[variant];
const {
icons: {
IconMicrophoneSolid = DefaultIconMicrophoneSolid,
IconVideoFill = DefaultIconVideoFill,
} = {},
} = useComponentContext();

const mediaBadgeVariantToIcon: Record<MediaBadgeVariant, ComponentType> = {
video: IconVideoFill,
voiceRecording: IconMicrophoneSolid,
};

const Icon = mediaBadgeVariantToIcon[variant];
Comment on lines 17 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix variant mapping key to match the exact string type.

The MediaBadgeVariant type defines the voice variant as 'voice-recording', but the mediaBadgeVariantToIcon dictionary uses the camelCased key voiceRecording. Consequently, when variant='voice-recording' is passed, the icon will fail to resolve and render.

🐛 Proposed fix for the dictionary key
   const mediaBadgeVariantToIcon: Record<MediaBadgeVariant, ComponentType> = {
     video: IconVideoFill,
-    voiceRecording: IconMicrophoneSolid,
+    'voice-recording': IconMicrophoneSolid,
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const MediaBadge = ({ attachment, variant }: MediaBadgeProps) => {
const Icon = MediaBadgeVariantToIcon[variant];
const {
icons: {
IconMicrophoneSolid = DefaultIconMicrophoneSolid,
IconVideoFill = DefaultIconVideoFill,
} = {},
} = useComponentContext();
const mediaBadgeVariantToIcon: Record<MediaBadgeVariant, ComponentType> = {
video: IconVideoFill,
voiceRecording: IconMicrophoneSolid,
};
const Icon = mediaBadgeVariantToIcon[variant];
export const MediaBadge = ({ attachment, variant }: MediaBadgeProps) => {
const {
icons: {
IconMicrophoneSolid = DefaultIconMicrophoneSolid,
IconVideoFill = DefaultIconVideoFill,
} = {},
} = useComponentContext();
const mediaBadgeVariantToIcon: Record<MediaBadgeVariant, ComponentType> = {
video: IconVideoFill,
'voice-recording': IconMicrophoneSolid,
};
const Icon = mediaBadgeVariantToIcon[variant];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Badge/MediaBadge.tsx` around lines 17 - 30, Update the voice
entry in MediaBadge’s mediaBadgeVariantToIcon mapping to use the exact
MediaBadgeVariant value "voice-recording" instead of "voiceRecording", while
preserving the existing microphone icon mapping.

return (
<div
className={clsx('str-chat__media-badge', {
Expand Down
5 changes: 4 additions & 1 deletion src/components/BaseImage/ImagePlaceholder.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React from 'react';
import clsx from 'clsx';
import { useTranslationContext } from '../../context/TranslationContext';
import { IconImage } from '../Icons';
import { useComponentContext } from '../../context';
import { IconImage as DefaultIconImage } from '../Icons';

export type ImagePlaceholderProps = {
className?: string;
};

export const ImagePlaceholder = ({ className }: ImagePlaceholderProps) => {
const { icons: { IconImage = DefaultIconImage } = {} } = useComponentContext();

const { t } = useTranslationContext();
return (
<div
Expand Down
14 changes: 12 additions & 2 deletions src/components/Button/PlayButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@ import React from 'react';
import { Button } from './Button';
import type { ComponentProps } from 'react';
import clsx from 'clsx';
import { IconPauseFill, IconPlayFill } from '../Icons';
import { useTranslationContext } from '../../context';
import {
IconPauseFill as DefaultIconPauseFill,
IconPlayFill as DefaultIconPlayFill,
} from '../Icons';
import { useComponentContext, useTranslationContext } from '../../context';

export type PlayButtonProps = ComponentProps<'button'> & {
isPlaying: boolean;
};

export const PlayButton = ({ className, isPlaying, ...props }: PlayButtonProps) => {
const {
icons: {
IconPauseFill = DefaultIconPauseFill,
IconPlayFill = DefaultIconPlayFill,
} = {},
} = useComponentContext();

const { t } = useTranslationContext();
return (
<Button
Expand Down
Loading
Loading