Skip to content
136 changes: 120 additions & 16 deletions ai-docs/ai-migration-v14-v15.md

Large diffs are not rendered by default.

275 changes: 224 additions & 51 deletions ai-docs/v15-breaking-changes.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions examples/tutorial/src/7-emoji-picker/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ const slotRenderers: ChatViewSlotRenderers = {
<Channel channel={source}>
<ChannelHeader />
<MessageList />
<MessageComposer emojiSearchIndex={SearchIndex} />
<MessageComposer />
</Channel>
),
thread: ({ source }) => (
<Thread thread={source}>
<ThreadHeader />
<MessageList />
<MessageComposer emojiSearchIndex={SearchIndex} />
<MessageComposer />
</Thread>
),
};
Expand Down
1 change: 0 additions & 1 deletion examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,6 @@ const App = () => {
// channels-view effect resolves after navigation (works across the threads β†’ channels
// switch; closes the covering thread only when it actually covers).
MessageAlsoSentInChannelIndicator: AlsoSentInChannelIndicator,
emojiSearchIndex: SearchIndex,
EmojiPicker: EmojiPickerWithCustomOptions,
NotificationList: ConfigurableNotificationList,
MessageReactions: CustomMessageReactions,
Expand Down
5 changes: 3 additions & 2 deletions src/components/Attachment/LinkPreview/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { BaseImage } from '../../BaseImage';
import { BaseImage as DefaultBaseImage } from '../../BaseImage';
import { SafeAnchor } from '../../SafeAnchor';
import { useAttachmentContext } from '../../../context/AttachmentContext';

Expand All @@ -8,7 +8,7 @@ import type { RenderAttachmentProps } from '../utils';
import type { Dimensions } from '../../../types/types';
import { UnableToRenderCard } from './UnableToRenderCard';
import clsx from 'clsx';
import { useComponentContextIcons } from '../../../context';
import { useComponentContext, useComponentContextIcons } from '../../../context';

type CardRootProps = {
cardUrl: string | undefined;
Expand Down Expand Up @@ -42,6 +42,7 @@ type CardHeaderProps = Pick<CardProps, 'title' | 'type' | 'image_url' | 'thumb_u

const CardHeader = (props: CardHeaderProps) => {
const { dimensions, image, image_url, thumb_url, title } = props;
const { BaseImage = DefaultBaseImage } = useComponentContext();

return image ? (
<div
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 @@ -6,7 +6,7 @@ import type { BaseImageProps } from '../BaseImage';
import type { GalleryItem } from '../Gallery/GalleryContext';
import { BaseImage as DefaultBaseImage } from '../BaseImage';
import { Gallery as DefaultGallery, GalleryUI } from '../Gallery';
import { LoadingIndicator } from '../Loading';
import { LoadingIndicator as DefaultLoadingIndicator } from '../Loading';
import { GlobalModal, type ModalCloseSource } from '../Modal';
import {
MessageContext,
Expand Down Expand Up @@ -165,6 +165,7 @@ const ThumbnailButton = ({
showOverlay,
}: ThumbnailButtonProps) => {
const { IconRetry } = useComponentContextIcons();
const { LoadingIndicator = DefaultLoadingIndicator } = useComponentContext();
const { t } = useTranslationContext();
const imageUrl = item.imageUrl;
const isVideo = Boolean(item.videoUrl || item.videoThumbnailUrl);
Expand Down
28 changes: 26 additions & 2 deletions src/components/Attachment/__tests__/Card.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import { cleanup, render, waitFor } from '@testing-library/react';

import { Card } from '../LinkPreview/Card';
import { Card, type CardProps } from '../LinkPreview/Card';

import { fromPartial } from '@total-typescript/shoehorn';
import { TranslationContext } from '../../../context';
import { TranslationContext, WithComponents } from '../../../context';
import { ChatProvider } from '../../../context/ChatContext';
import { ComponentProvider } from '../../../context/ComponentContext';

Expand Down Expand Up @@ -285,6 +285,30 @@ describe('Card', () => {
});
});

it('should render the BaseImage from ComponentContext in the card header', () => {
const CustomBaseImage = (props: { src?: string; title?: string }) => (
<div data-src={props.src} data-testid='custom-base-image' title={props.title} />
);
const { getByTestId, queryByTestId } = render(
<TranslationContext.Provider value={mockTranslationContextValue()}>
<WithComponents overrides={{ BaseImage: CustomBaseImage }}>
<Card
{...fromPartial<CardProps>({
image_url: 'https://example.com/image.png',
og_scrape_url: 'https://example.com',
title: 'Card title',
})}
/>
</WithComponents>
</TranslationContext.Provider>,
);

const image = getByTestId('custom-base-image');
expect(image).toHaveAttribute('data-src', 'https://example.com/image.png');
expect(image).toHaveAttribute('title', 'Card title');
expect(queryByTestId('image-test')).not.toBeInTheDocument();
});

it('should display URL in source link if author_name is not available', async () => {
const ogScrapeUrl =
'https://www.theverge.com/2020/6/15/21291288/sony-ps5-software-user-interface-ui-design-dashboard-teaser-video';
Expand Down
3 changes: 2 additions & 1 deletion src/components/Avatar/GroupAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export type GroupAvatarProps = {

/**
* Avatar for several users: a single `Avatar` for fewer than 2 members, otherwise up to 4 avatars,
* or 2 plus a `+N` badge when there are more than 4 members.
* or 2 plus a `+N` badge when there are more than 4 members. The tiles are always the default
* `Avatar`, not the `Avatar` slot: the slot can hold `ChannelAvatar`, which renders `GroupAvatar`.
*/
export const GroupAvatar = ({
badgeSize,
Expand Down
24 changes: 23 additions & 1 deletion src/components/Avatar/__tests__/GroupAvatar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React from 'react';

import { cleanup, render } from '@testing-library/react';

import { ChannelAvatar } from '../ChannelAvatar';
import { ChannelAvatar, type ChannelAvatarProps } from '../ChannelAvatar';
import { GroupAvatar } from '../GroupAvatar';
import { WithComponents } from '../../../context';

afterEach(cleanup);

Expand Down Expand Up @@ -156,6 +157,27 @@ describe('GroupAvatar', () => {
'custom-class',
);
});

// The `Avatar` slot can hold `ChannelAvatar`, which renders `GroupAvatar`; reading the slot here
// would recurse forever.
it('renders its own Avatar tiles when the Avatar slot holds ChannelAvatar', () => {
let slotRenders = 0;
const SlotAvatar = (props: ChannelAvatarProps) => {
slotRenders += 1;
if (slotRenders > 5)
throw new Error('GroupAvatar rendered the Avatar slot recursively');
return <ChannelAvatar {...props} />;
};
const { getAllByTestId, getByTestId } = render(
<WithComponents overrides={{ Avatar: SlotAvatar }}>
<SlotAvatar displayMembers={[member1]} size='xl' />
<GroupAvatar displayMembers={[member1, member2]} size='xl' />
</WithComponents>,
);

expect(getByTestId('group-avatar')).toBeInTheDocument();
expect(getAllByTestId('avatar')).toHaveLength(3);
});
});

describe('ChannelAvatar', () => {
Expand Down
2 changes: 0 additions & 2 deletions src/components/ChannelListItem/ChannelListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ export type ChannelListItemProps = {
channel: Channel;
/** Whether the channel is the active one. Defaults to whether it is shown in the workspace. */
active?: boolean;
/** Current selected channel object */
activeChannel?: Channel;
/** Custom class for the row button. */
className?: string;
/**
Expand Down
Loading
Loading