Skip to content

Commit f3c185d

Browse files
committed
Merge branch 'master' into fix/prevent-image-attachment-squeeze
2 parents f09d967 + 88ef71e commit f3c185d

19 files changed

Lines changed: 474 additions & 76 deletions

File tree

.github/actions/setup-node/action.yml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
name: Setup
22
description: Sets up Node and installs dependencies
33

4-
inputs:
5-
node-version:
6-
description: 'Specify Node version'
7-
required: false
8-
default: '24'
9-
104
runs:
115
using: 'composite'
126
steps:
137
- name: Setup Node
148
uses: actions/setup-node@v6
159
with:
16-
node-version: ${{ inputs.node-version }}
10+
node-version-file: '.nvmrc'
1711

1812
- name: Set NODE_VERSION env
1913
shell: bash

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
run: yarn coverage
5050

5151
- name: Upload coverage to Codecov
52-
uses: codecov/codecov-action@v5
52+
uses: codecov/codecov-action@v6
5353
with:
5454
verbose: true
5555
token: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/pr-check.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ jobs:
1313

1414
- uses: ./.github/actions/setup-node
1515

16-
- run: echo "${{ github.event.pull_request.title }}" | npx commitlint --verbose
16+
- env:
17+
PR_TITLE: ${{ github.event.pull_request.title }}
18+
run: echo "$PR_TITLE" | npx commitlint --verbose

.github/workflows/size.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ jobs:
1616
runs-on: ubuntu-latest
1717
steps:
1818
- uses: actions/checkout@v6
19+
- uses: actions/setup-node@v6
20+
with:
21+
node-version-file: '.nvmrc'
1922
- uses: preactjs/compressed-size-action@v2
2023
env:
2124
NODE_OPTIONS: --max_old_space_size=4096
22-
YARN_IGNORE_ENGINES: 'true' # skip validation for node20 requirement
2325
with:
2426
repo-token: '${{ secrets.GITHUB_TOKEN }}'
2527
pattern: './dist/**/*.{?(c|m)js,css,json}'

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
## [14.1.0](https://github.com/GetStream/stream-chat-react/compare/v14.0.1...v14.1.0) (2026-05-04)
2+
3+
### Bug Fixes
4+
5+
* add ScrollToLatestMessageButton to ComponentContext ([#3159](https://github.com/GetStream/stream-chat-react/issues/3159)) ([952c125](https://github.com/GetStream/stream-chat-react/commit/952c125120c401258f4654d6ce1c3314ceef6964))
6+
* allow user blocking only in DM-type channels ([#3139](https://github.com/GetStream/stream-chat-react/issues/3139)) ([deda536](https://github.com/GetStream/stream-chat-react/commit/deda536d14da5ea108959e8144d29da0e3afbf25))
7+
* decouple msg bubble width from reaction list width ([#3142](https://github.com/GetStream/stream-chat-react/issues/3142)) ([980c233](https://github.com/GetStream/stream-chat-react/commit/980c233e5993ffff559ede3e8b8942066c0761f8))
8+
* export AttachmentSelectorContext from the SDK ([#3158](https://github.com/GetStream/stream-chat-react/issues/3158)) ([68efeb5](https://github.com/GetStream/stream-chat-react/commit/68efeb59542b1f849b04c0f8c09ac16f077c7d46))
9+
* font & box shadow fixes ([#3135](https://github.com/GetStream/stream-chat-react/issues/3135)) ([6d04cdf](https://github.com/GetStream/stream-chat-react/commit/6d04cdf6dc4f1a67685d0080fd6a530da336b4f7)), closes [#3134](https://github.com/GetStream/stream-chat-react/issues/3134)
10+
* limit reactions host width (segmented/bottom) ([#3154](https://github.com/GetStream/stream-chat-react/issues/3154)) ([be50105](https://github.com/GetStream/stream-chat-react/commit/be50105a8a4e2002ea1d0b13ccbd3bd3fe1fc779))
11+
* make search results scrollable ([#3152](https://github.com/GetStream/stream-chat-react/issues/3152)) ([ead6cb5](https://github.com/GetStream/stream-chat-react/commit/ead6cb55e855104b06774358c8fac24ff2c699b6))
12+
* **MessageList:** prevent message pagination too early on mount ([#3143](https://github.com/GetStream/stream-chat-react/issues/3143)) ([12e282f](https://github.com/GetStream/stream-chat-react/commit/12e282f36706745453b29e4852d799cd498c52ae))
13+
* prevent cutting off button outlines in ContextMenu components ([#3151](https://github.com/GetStream/stream-chat-react/issues/3151)) ([b3469f0](https://github.com/GetStream/stream-chat-react/commit/b3469f012623a1500090e7a8acd1af3403fccc8a))
14+
* remove scrollbar gutters from VML ([#3148](https://github.com/GetStream/stream-chat-react/issues/3148)) ([4a6a8ae](https://github.com/GetStream/stream-chat-react/commit/4a6a8aed3e62eb0da5c56ceeb46eb317205341f1))
15+
16+
### Features
17+
18+
* **a11y:** improve accessibility across dialogs, forms, menus, media, and focus flows ([#3146](https://github.com/GetStream/stream-chat-react/issues/3146)) ([917b7f5](https://github.com/GetStream/stream-chat-react/commit/917b7f5e214e51129e41cc24070b59c759359e3e))
19+
* change textarea default placeholder text ([#3150](https://github.com/GetStream/stream-chat-react/issues/3150)) ([45b1836](https://github.com/GetStream/stream-chat-react/commit/45b18361d54e36e964d3fa7b38814590fad037b4))
20+
* introduce `MessageUI` to `ComponentContext` ([#3140](https://github.com/GetStream/stream-chat-react/issues/3140)) ([16af18d](https://github.com/GetStream/stream-chat-react/commit/16af18de4a2891e47f36fff108ace1f290caf25d))
21+
22+
### Refactors
23+
24+
* message styling ([#3136](https://github.com/GetStream/stream-chat-react/issues/3136)) ([cd3a9c0](https://github.com/GetStream/stream-chat-react/commit/cd3a9c052c8a55a26b0415b1447dcd8f16f001a3))
25+
126
## [14.0.1](https://github.com/GetStream/stream-chat-react/compare/v14.0.0...v14.0.1) (2026-04-17)
227

328
### Bug Fixes
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
.app-channel-preview-overlay {
2+
position: absolute;
3+
inset: 0;
4+
z-index: 3;
5+
display: flex;
6+
align-items: center;
7+
justify-content: center;
8+
backdrop-filter: blur(5px);
9+
background: rgba(255, 255, 255, 0.75);
10+
padding: 12px 0;
11+
12+
.str-chat__theme-dark & {
13+
background: rgba(0, 0, 0, 0.75);
14+
}
15+
}
16+
17+
.app-channel-preview-overlay__content {
18+
position: relative;
19+
display: flex;
20+
flex-direction: column;
21+
align-items: center;
22+
padding: 40px 48px;
23+
overscroll-behavior: contain;
24+
25+
&::before {
26+
content: '';
27+
position: absolute;
28+
inset: -120px;
29+
border-radius: 50%;
30+
background: radial-gradient(
31+
circle,
32+
rgba(255, 255, 255, 0.9) 0%,
33+
rgba(255, 255, 255, 0.85) 40%,
34+
rgba(255, 255, 255, 0) 70%
35+
);
36+
filter: blur(20px);
37+
z-index: -1;
38+
39+
.str-chat__theme-dark & {
40+
background: radial-gradient(
41+
circle,
42+
rgba(0, 0, 0, 0.6) 0%,
43+
rgba(0, 0, 0, 0.55) 40%,
44+
rgba(0, 0, 0, 0) 70%
45+
);
46+
}
47+
}
48+
49+
.str-chat__icon {
50+
width: 32px;
51+
height: 32px;
52+
color: var(--str-chat__text-tertiary);
53+
}
54+
}
55+
56+
.app-channel-preview-overlay__text {
57+
display: flex;
58+
flex-direction: column;
59+
align-items: center;
60+
text-align: center;
61+
gap: var(--str-chat__spacing-xxs);
62+
margin-block: var(--str-chat__spacing-sm) var(--str-chat__spacing-xl);
63+
64+
p {
65+
margin: 0;
66+
}
67+
}
68+
69+
.app-channel-preview-overlay__title {
70+
font: var(--str-chat__font-heading-xs);
71+
color: var(--str-chat__text-color);
72+
}
73+
74+
.app-channel-preview-overlay__description {
75+
font: var(--str-chat__font-caption-default);
76+
color: var(--str-chat__text-secondary);
77+
max-width: 200px;
78+
}
79+
80+
.app-channel-preview-overlay__join-button {
81+
width: 106px;
82+
83+
.str-chat__loading-indicator {
84+
height: 20px;
85+
width: 20px;
86+
}
87+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { useCallback, useState } from 'react';
2+
import type { ChannelMemberResponse } from 'stream-chat';
3+
import {
4+
Button,
5+
IconMessageBubbles,
6+
LoadingIndicator,
7+
useChannelMembersState,
8+
useChannelStateContext,
9+
useChatContext,
10+
useNotificationApi,
11+
} from 'stream-chat-react';
12+
13+
import './ChannelPreviewOverlay.scss';
14+
15+
export const useChannelMembershipState = () => {
16+
const { client } = useChatContext();
17+
const { channel } = useChannelStateContext();
18+
const members = useChannelMembersState(channel);
19+
const membership = members[client.userID!] as ChannelMemberResponse | undefined;
20+
21+
const isMember = typeof membership?.channel_role === 'string';
22+
const canJoin = channel.data?.own_capabilities?.includes('join-channel');
23+
24+
return { canJoin, channel, client, isMember };
25+
};
26+
27+
export const ChannelPreviewOverlay = () => {
28+
const { canJoin, channel, client, isMember } = useChannelMembershipState();
29+
const { addNotification } = useNotificationApi();
30+
const [joining, setJoining] = useState(false);
31+
32+
const handleJoin = useCallback(async () => {
33+
setJoining(true);
34+
try {
35+
await channel.addMembers([client.userID!]);
36+
} catch (error) {
37+
addNotification({
38+
emitter: 'ChannelPreviewOverlay',
39+
incident: {
40+
domain: 'api',
41+
entity: 'channel',
42+
operation: 'join',
43+
},
44+
message: 'Failed to join the group',
45+
severity: 'error',
46+
error: error instanceof Error ? error : new Error(String(error)),
47+
});
48+
} finally {
49+
setJoining(false);
50+
}
51+
}, [addNotification, channel, client.userID]);
52+
53+
if (isMember) return null;
54+
55+
return (
56+
<div className='app-channel-preview-overlay'>
57+
<div className='app-channel-preview-overlay__content'>
58+
<IconMessageBubbles />
59+
<div className='app-channel-preview-overlay__text'>
60+
<p className='app-channel-preview-overlay__title'>
61+
{canJoin ? "You're previewing this group" : 'This is a private group'}
62+
</p>
63+
<p className='app-channel-preview-overlay__description'>
64+
{canJoin
65+
? 'Join to send messages and follow the conversation'
66+
: 'It is not possible to join this group'}
67+
</p>
68+
</div>
69+
{canJoin && (
70+
<Button
71+
appearance='solid'
72+
className='app-channel-preview-overlay__join-button'
73+
disabled={joining}
74+
onClick={handleJoin}
75+
size='md'
76+
variant='primary'
77+
>
78+
{joining ? <LoadingIndicator /> : 'Join Group'}
79+
</Button>
80+
)}
81+
</div>
82+
</div>
83+
);
84+
};

examples/vite/src/ChatLayout/Panels.tsx

Lines changed: 22 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import clsx from 'clsx';
2-
import type {
3-
ChannelFilters,
4-
ChannelMemberResponse,
5-
ChannelOptions,
6-
ChannelSort,
7-
} from 'stream-chat';
8-
import { useCallback, useEffect, useRef } from 'react';
2+
import type { ChannelFilters, ChannelOptions, ChannelSort } from 'stream-chat';
3+
import { useEffect, useRef } from 'react';
94
import {
105
AIStateIndicator,
116
Channel,
@@ -26,14 +21,13 @@ import {
2621
useChatContext,
2722
type ChatViewSelectorEntry,
2823
useThreadsViewContext,
29-
Button,
30-
useChannelMembersState,
3124
} from 'stream-chat-react';
3225

3326
import { useAppSettingsSelector } from '../AppSettings/state';
3427
import { DESKTOP_LAYOUT_BREAKPOINT } from './constants.ts';
3528
import { SidebarResizeHandle, ThreadResizeHandle } from './Resize.tsx';
3629
import { ReturnToSkipNavigation } from '../AccessibilityNavigation/ReturnToSkipNavigation.tsx';
30+
import { ChannelPreviewOverlay } from '../ChannelPreviewOverlay/ChannelPreviewOverlay.tsx';
3731
import { useSidebar } from './SidebarContext.tsx';
3832
import { ThreadStateSync } from './Sync.tsx';
3933

@@ -82,56 +76,31 @@ const ResponsiveChannelPanels = () => {
8276
<WithDragAndDropUpload className='app-chat-view__channel-main'>
8377
<Window>
8478
<ChannelHeader Avatar={ChannelAvatar} />
85-
{messageListType === 'virtualized' ? (
86-
<VirtualizedMessageList returnAllReadData shouldGroupByUser />
87-
) : (
88-
<MessageList returnAllReadData />
89-
)}
90-
<ReturnToSkipNavigation />
91-
<AIStateIndicator />
92-
<MessageComposer
93-
additionalTextareaProps={{
94-
id: CHANNEL_MESSAGE_COMPOSER_TEXTAREA_TARGET_ID,
95-
}}
96-
audioRecordingEnabled
97-
maxRows={10}
98-
asyncMessagesMultiSendEnabled
99-
/>
79+
<div className='app-chat-view__channel-body'>
80+
{messageListType === 'virtualized' ? (
81+
<VirtualizedMessageList returnAllReadData shouldGroupByUser />
82+
) : (
83+
<MessageList returnAllReadData />
84+
)}
85+
<ReturnToSkipNavigation />
86+
<AIStateIndicator />
87+
<MessageComposer
88+
additionalTextareaProps={{
89+
id: CHANNEL_MESSAGE_COMPOSER_TEXTAREA_TARGET_ID,
90+
}}
91+
audioRecordingEnabled
92+
maxRows={10}
93+
asyncMessagesMultiSendEnabled
94+
/>
95+
<ChannelPreviewOverlay />
96+
</div>
10097
</Window>
10198
</WithDragAndDropUpload>
10299
<ChannelThreadPanel />
103100
</div>
104101
);
105102
};
106103

107-
const HeaderStartContent = () => {
108-
const { client } = useChatContext();
109-
const { channel } = useChannelStateContext();
110-
const members = useChannelMembersState(channel);
111-
const membership = members[client.userID!] as ChannelMemberResponse | undefined;
112-
113-
const isMember = typeof membership?.channel_role === 'string';
114-
const canJoin = channel.data?.own_capabilities?.includes('join-channel');
115-
116-
const handleClick = useCallback(() => {
117-
if (isMember) {
118-
channel.removeMembers([client.userID!]).then(() => {
119-
channel.watch();
120-
});
121-
} else {
122-
channel.addMembers([client.userID!]);
123-
}
124-
}, [isMember]);
125-
126-
if (!canJoin) return null;
127-
128-
return (
129-
<Button onClick={handleClick} variant='secondary' appearance='outline' size='sm'>
130-
{isMember ? 'Leave' : 'Join'}
131-
</Button>
132-
);
133-
};
134-
135104
export const ChannelsPanels = ({
136105
filters,
137106
iconOnly,
@@ -193,7 +162,7 @@ export const ChannelsPanels = ({
193162
</WithComponents>
194163
</div>
195164
<SidebarResizeHandle layoutRef={channelsLayoutRef} />
196-
<WithComponents overrides={{ TypingIndicator, HeaderStartContent }}>
165+
<WithComponents overrides={{ TypingIndicator }}>
197166
<Channel>
198167
<ResponsiveChannelPanels />
199168
</Channel>

0 commit comments

Comments
 (0)