Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
17 changes: 12 additions & 5 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,18 @@ const App = () => {
{ type: 'public' },
// public example channels
{
cid: {
$in: ['random', 'general', 'music', 'jokes'].map(
(channelId) => `messaging:${channelId}`,
),
},
$and: [
{
cid: {
$in: ['random', 'general', 'music', 'jokes'].map(
(channelId) => `messaging:${channelId}`,
),
},
},
{
members: { $in: [userId] },
},
],
},
],
}),
Expand Down
75 changes: 22 additions & 53 deletions examples/vite/src/ChatLayout/Panels.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import clsx from 'clsx';
import type {
ChannelFilters,
ChannelMemberResponse,
ChannelOptions,
ChannelSort,
} from 'stream-chat';
import { useCallback, useEffect, useRef } from 'react';
import type { ChannelFilters, ChannelOptions, ChannelSort } from 'stream-chat';
import { useEffect, useRef } from 'react';
import {
AIStateIndicator,
Channel,
Expand All @@ -26,14 +21,13 @@ import {
useChatContext,
type ChatViewSelectorEntry,
useThreadsViewContext,
Button,
useChannelMembersState,
} from 'stream-chat-react';

import { useAppSettingsSelector } from '../AppSettings/state';
import { DESKTOP_LAYOUT_BREAKPOINT } from './constants.ts';
import { SidebarResizeHandle, ThreadResizeHandle } from './Resize.tsx';
import { ReturnToSkipNavigation } from '../AccessibilityNavigation/ReturnToSkipNavigation.tsx';
import { PublicChannelOverlay } from '../PublicChannelOverlay/PublicChannelOverlay.tsx';
import { useSidebar } from './SidebarContext.tsx';
import { ThreadStateSync } from './Sync.tsx';

Expand Down Expand Up @@ -82,56 +76,31 @@ const ResponsiveChannelPanels = () => {
<WithDragAndDropUpload className='app-chat-view__channel-main'>
<Window>
<ChannelHeader Avatar={ChannelAvatar} />
{messageListType === 'virtualized' ? (
<VirtualizedMessageList returnAllReadData shouldGroupByUser />
) : (
<MessageList returnAllReadData />
)}
<ReturnToSkipNavigation />
<AIStateIndicator />
<MessageComposer
additionalTextareaProps={{
id: CHANNEL_MESSAGE_COMPOSER_TEXTAREA_TARGET_ID,
}}
audioRecordingEnabled
maxRows={10}
asyncMessagesMultiSendEnabled
/>
<div className='app-chat-view__channel-body'>
{messageListType === 'virtualized' ? (
<VirtualizedMessageList returnAllReadData shouldGroupByUser />
) : (
<MessageList returnAllReadData />
)}
<ReturnToSkipNavigation />
<AIStateIndicator />
<MessageComposer
additionalTextareaProps={{
id: CHANNEL_MESSAGE_COMPOSER_TEXTAREA_TARGET_ID,
}}
audioRecordingEnabled
maxRows={10}
asyncMessagesMultiSendEnabled
/>
<PublicChannelOverlay />
</div>
</Window>
</WithDragAndDropUpload>
<ChannelThreadPanel />
</div>
);
};

const HeaderStartContent = () => {
const { client } = useChatContext();
const { channel } = useChannelStateContext();
const members = useChannelMembersState(channel);
const membership = members[client.userID!] as ChannelMemberResponse | undefined;

const isMember = typeof membership?.channel_role === 'string';
const canJoin = channel.data?.own_capabilities?.includes('join-channel');

const handleClick = useCallback(() => {
if (isMember) {
channel.removeMembers([client.userID!]).then(() => {
channel.watch();
});
} else {
channel.addMembers([client.userID!]);
}
}, [isMember]);

if (!canJoin) return null;

return (
<Button onClick={handleClick} variant='secondary' appearance='outline' size='sm'>
{isMember ? 'Leave' : 'Join'}
</Button>
);
};

export const ChannelsPanels = ({
filters,
iconOnly,
Expand Down Expand Up @@ -193,7 +162,7 @@ export const ChannelsPanels = ({
</WithComponents>
</div>
<SidebarResizeHandle layoutRef={channelsLayoutRef} />
<WithComponents overrides={{ TypingIndicator, HeaderStartContent }}>
<WithComponents overrides={{ TypingIndicator }}>
<Channel>
<ResponsiveChannelPanels />
</Channel>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
.app-public-channel-overlay {
position: absolute;
inset: 0;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(5px);
background: rgba(255, 255, 255, 0.75);
padding: 12px 0;

.str-chat__theme-dark & {
background: rgba(0, 0, 0, 0.75);
}
}

.app-public-channel-overlay__content {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 48px;
overscroll-behavior: contain;

&::before {
content: '';
position: absolute;
inset: -120px;
border-radius: 50%;
background: radial-gradient(
circle,
rgba(255, 255, 255, 0.9) 0%,
rgba(255, 255, 255, 0.85) 40%,
rgba(255, 255, 255, 0) 70%
);
filter: blur(20px);
z-index: -1;

.str-chat__theme-dark & {
background: radial-gradient(
circle,
rgba(0, 0, 0, 0.6) 0%,
rgba(0, 0, 0, 0.55) 40%,
rgba(0, 0, 0, 0) 70%
);
}
}

.str-chat__icon {
width: 32px;
height: 32px;
color: var(--str-chat__text-tertiary);
}
}

.app-public-channel-overlay__text {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: var(--str-chat__spacing-xxs);
margin-block: var(--str-chat__spacing-sm) var(--str-chat__spacing-xl);

p {
margin: 0;
}
}

.app-public-channel-overlay__title {
font: var(--str-chat__font-heading-xs);
color: var(--str-chat__text-color);
}

.app-public-channel-overlay__description {
font: var(--str-chat__font-caption-default);
color: var(--str-chat__text-secondary);
max-width: 200px;
}

.app-public-channel-overlay__join-button {
width: 106px;

.str-chat__loading-indicator {
height: 20px;
width: 20px;
}
}
74 changes: 74 additions & 0 deletions examples/vite/src/PublicChannelOverlay/PublicChannelOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useCallback, useState } from 'react';
import type { ChannelMemberResponse } from 'stream-chat';
import {
Button,
IconMessageBubbles,
LoadingIndicator,
useChannelMembersState,
useChannelStateContext,
useChatContext,
useNotificationApi,
} from 'stream-chat-react';

import './PublicChannelOverlay.scss';

export const PublicChannelOverlay = () => {
const { client } = useChatContext();
const { channel } = useChannelStateContext();
const members = useChannelMembersState(channel);
const membership = members[client.userID!] as ChannelMemberResponse | undefined;
const { addNotification } = useNotificationApi();
const [joining, setJoining] = useState(false);

const isMember = typeof membership?.channel_role === 'string';
const canJoin = channel.data?.own_capabilities?.includes('join-channel');

const handleJoin = useCallback(async () => {
setJoining(true);
try {
await channel.addMembers([client.userID!]);
} catch (error) {
addNotification({
emitter: 'PublicChannelOverlay',
incident: {
domain: 'api',
entity: 'channel',
operation: 'join',
},
message: 'Failed to join the group',
severity: 'error',
error: error instanceof Error ? error : new Error(String(error)),
});
} finally {
setJoining(false);
}
}, [addNotification, channel, client.userID]);

if (isMember || !canJoin) return null;

return (
<div className='app-public-channel-overlay'>
<div className='app-public-channel-overlay__content'>
<IconMessageBubbles />
<div className='app-public-channel-overlay__text'>
<p className='app-public-channel-overlay__title'>
You're previewing this group
</p>
<p className='app-public-channel-overlay__description'>
Join to send messages and follow the conversation
</p>
</div>
<Button
appearance='solid'
className='app-public-channel-overlay__join-button'
disabled={joining}
onClick={handleJoin}
size='md'
variant='primary'
>
{joining ? <LoadingIndicator /> : 'Join Group'}
</Button>
</div>
</div>
);
};
8 changes: 8 additions & 0 deletions examples/vite/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ body {
height: 100%;
}

.app-chat-view__channel-body {
position: relative;
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
}

.app-chat-view__threads-main > * {
flex: 1 1 auto;
min-width: 0;
Expand Down
Loading