Skip to content
Merged
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
40 changes: 26 additions & 14 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
ChannelSort,
LocalMessage,
TextComposerMiddleware,
Event,
createCommandInjectionMiddleware,
createDraftCommandInjectionMiddleware,
createActiveCommandGuardMiddleware,
Expand All @@ -28,6 +27,7 @@ import {
Window,
WithComponents,
ReactionsList,
WithDragAndDropUpload,
} from 'stream-chat-react';
import { createTextComposerEmojiMiddleware, EmojiPicker } from 'stream-chat-react/emojis';
import { init, SearchIndex } from 'emoji-mart';
Expand All @@ -38,6 +38,14 @@ import { useAppSettingsState } from './AppSettings';

init({ data });

const parseUserIdFromToken = (token: string) => {
const [, payload] = token.split('.');

if (!payload) throw new Error('Token is missing');

return JSON.parse(atob(payload))?.user_id;
};

const apiKey = import.meta.env.VITE_STREAM_API_KEY;
const token =
new URLSearchParams(window.location.search).get('token') ||
Expand Down Expand Up @@ -79,7 +87,7 @@ const useUser = () => {
}, [userId]);

const tokenProvider = useCallback(() => {
return token
return token && userId === parseUserIdFromToken(token)
? Promise.resolve(token)
: fetch(
`https://pronto.getstream.io/api/auth/create-token?environment=shared-chat-redesign&user_id=${userId}`,
Expand Down Expand Up @@ -190,18 +198,22 @@ const App = () => {
additionalChannelSearchProps={{ searchForChannels: true }}
/>
<Channel>
<Window>
<ChannelHeader Avatar={ChannelAvatar} />
<MessageList returnAllReadData />
<AIStateIndicator />
<MessageInput
focus
audioRecordingEnabled
maxRows={10}
asyncMessagesMultiSendEnabled
/>
</Window>
<Thread virtualized />
<WithDragAndDropUpload>
<Window>
<ChannelHeader Avatar={ChannelAvatar} />
<MessageList returnAllReadData />
<AIStateIndicator />
<MessageInput
focus
audioRecordingEnabled
maxRows={10}
asyncMessagesMultiSendEnabled
/>
</Window>
</WithDragAndDropUpload>
<WithDragAndDropUpload className='str-chat__dropzone-root--thread'>
<Thread virtualized />
</WithDragAndDropUpload>
</Channel>
</ChatView.Channels>
<ChatView.Threads>
Expand Down
8 changes: 5 additions & 3 deletions examples/vite/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,18 @@ body {
//max-width: none;
}

.str-chat__dropzone-root--thread,
.str-chat__thread-list-container,
.str-chat__thread-container {
flex: 0 0 360px;
//flex: 0 0 360px;
max-width: 360px;
}

.str-chat__chat-view__threads {
.str-chat__dropzone-root--thread,
.str-chat__thread-container {
flex: 1 1 auto;
min-width: 360px;
//min-width: 360px;
max-width: none;
}
}
Expand All @@ -119,7 +121,7 @@ body {
}
}

@container (max-width: 760px) {
@container (max-width: 860px) {
.str-chat__channel-list,
.str-chat__chat-view__selector {
display: none;
Expand Down
4 changes: 2 additions & 2 deletions examples/vite/src/stream-imports-layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
@use 'stream-chat-react/dist/scss/v2/common/CTAButton/CTAButton-layout';
@use 'stream-chat-react/dist/scss/v2/common/CircleFAButton/CircleFAButton-layout';
//@use 'stream-chat-react/dist/scss/v2/Dialog/Dialog-layout';
@use 'stream-chat-react/dist/scss/v2/DragAndDropContainer/DragAndDropContainer-layout';
@use 'stream-chat-react/dist/scss/v2/DropzoneContainer/DropzoneContainer-layout'; // X
//@use 'stream-chat-react/dist/scss/v2/DragAndDropContainer/DragAndDropContainer-layout';
//@use 'stream-chat-react/dist/scss/v2/DropzoneContainer/DropzoneContainer-layout'; // X
//@use 'stream-chat-react/dist/scss/v2/EditMessageForm/EditMessageForm-layout';
//@use 'stream-chat-react/dist/scss/v2/Form/Form-layout';
@use 'stream-chat-react/dist/scss/v2/ImageCarousel/ImageCarousel-layout';
Expand Down
4 changes: 2 additions & 2 deletions examples/vite/src/stream-imports-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
@use 'stream-chat-react/dist/scss/v2/ChannelPreview/ChannelPreview-theme';
@use 'stream-chat-react/dist/scss/v2/ChannelSearch/ChannelSearch-theme';
//@use 'stream-chat-react/dist/scss/v2/Dialog/Dialog-theme';
@use 'stream-chat-react/dist/scss/v2/DragAndDropContainer/DragAndDropContainer-theme';
@use 'stream-chat-react/dist/scss/v2/DropzoneContainer/DropzoneContainer-theme';
//@use 'stream-chat-react/dist/scss/v2/DragAndDropContainer/DragAndDropContainer-theme';
//@use 'stream-chat-react/dist/scss/v2/DropzoneContainer/DropzoneContainer-theme';
//@use 'stream-chat-react/dist/scss/v2/Form/Form-theme';
//@use 'stream-chat-react/dist/scss/v2/Icon/Icon-theme';
@use 'stream-chat-react/dist/scss/v2/ImageCarousel/ImageCarousel-theme';
Expand Down
Empty file.
51 changes: 51 additions & 0 deletions src/components/DragAndDrop/styling/DragAndDropContainer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Ported from stream-chat-css DragAndDropContainer (layout + theme)

.str-chat {
/* Top border of the component */
--str-chat__drag-and-drop-container-border-block-start: 2px solid transparent;

/* Bottom border of the component */
--str-chat__drag-and-drop-container-border-block-end: 2px solid transparent;

/* Top border of the component on dragover */
--str-chat__drag-and-drop-container-on-dragover-border-block-start: 2px solid
var(--str-chat__primary-color);

/* Bottom border of the component on dragover */
--str-chat__drag-and-drop-container-on-dragover-border-block-end: 2px solid
var(--str-chat__primary-color);

/* Left (right in RTL layout) border of the component on dragover */
--str-chat__drag-and-drop-container-on-dragover-border-inline-start: none;

/* Right (left in RTL layout) border of the component on dragover */
--str-chat__drag-and-drop-container-on-dragover-border-inline-end: none;
}

.str-chat__drag-and-drop-container--dragging {
cursor: grabbing;
}

.str-chat__drag-and-drop-container__item[draggable='true'] {
cursor: grab;

&:active {
background: transparent;
}
}

.str-chat__drag-and-drop-container__item {
display: flex;
width: 100%;
padding-block: 0.25rem;
border-bottom: var(--str-chat__drag-and-drop-container-border-block-start);
border-top: var(--str-chat__drag-and-drop-container-border-block-start);

&.str-chat__drag-and-drop-container__item--dragged-over-from-top {
border-bottom: var(--str-chat__drag-and-drop-container-on-dragover-border-block-end);
}

&.str-chat__drag-and-drop-container__item--dragged-over-from-bottom {
border-top: var(--str-chat__drag-and-drop-container-on-dragover-border-block-start);
}
}
1 change: 1 addition & 0 deletions src/components/DragAndDrop/styling/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@use "DragAndDropContainer";
48 changes: 35 additions & 13 deletions src/components/MessageInput/WithDragAndDropUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useMessageInputContext, useTranslationContext } from '../../context';
import { useAttachmentManagerState, useMessageComposer } from './hooks';
import { useStateStore } from '../../store';
import { useIsCooldownActive } from './hooks/useIsCooldownActive';
import { IconFileArrowLeftIn } from '../Icons';

const DragAndDropUploadContext = React.createContext<{
subscribeToDrop: ((fn: (files: File[]) => void) => () => void) | null;
Expand Down Expand Up @@ -72,8 +73,6 @@ export const WithDragAndDropUpload = ({
style?: CSSProperties;
}>) => {
const dropHandlersRef = useRef<Set<(f: File[]) => void>>(new Set());
const { t } = useTranslationContext();

const messageInputContext = useMessageInputContext();
const dragAndDropUploadContext = useDragAndDropUploadContext();
const messageComposer = useMessageComposer();
Expand Down Expand Up @@ -108,7 +107,11 @@ export const WithDragAndDropUpload = ({
dropHandlersRef.current.forEach((fn) => fn(files));
}, []);

const { getRootProps, isDragActive, isDragReject } = useDropzone({
const {
getRootProps,
isDragActive,
isDragReject: isDragRejected,
} = useDropzone({
accept,
// apply `disabled` rules if available, otherwise allow anything and
// let the `uploadNewFiles` handle the limitations internally
Expand All @@ -126,26 +129,45 @@ export const WithDragAndDropUpload = ({
return <Component className={className}>{children}</Component>;
}

const rootClassName = clsx('str-chat__dropzone-root', className);

return (
<DragAndDropUploadContext.Provider
value={{
subscribeToDrop,
}}
>
<Component {...getRootProps({ className, style })}>
{/* TODO: could be a replaceable component */}
<DragAndDropUploadContext.Provider value={{ subscribeToDrop }}>
<Component {...getRootProps({ className: rootClassName, style })}>
{isDragActive && (
<div
className={clsx('str-chat__dropzone-container', {
'str-chat__dropzone-container--not-accepted': isDragReject,
'str-chat__dropzone-container--not-accepted': isDragRejected,
})}
role='presentation'
>
{!isDragReject && <p>{t('Drag your files here')}</p>}
{isDragReject && <p>{t('Some of the files will not be accepted')}</p>}
<FileDragAndDropContent isDragRejected={isDragRejected} />
</div>
)}
{children}
</Component>
</DragAndDropUploadContext.Provider>
);
};

export type FileDragAndDropContentProps = {
isDragRejected: boolean;
};

export const FileDragAndDropContent = ({
isDragRejected,
}: FileDragAndDropContentProps) => {
const { t } = useTranslationContext();
return (
<div className='str-chat__dropzone-container__content'>
{isDragRejected ? (
<p>{t('Some of the files will not be accepted')}</p>
) : (
<>
<IconFileArrowLeftIn />
<p>{t('Drag your files here')}</p>
</>
)}
</div>
);
};
6 changes: 5 additions & 1 deletion src/components/MessageInput/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ export * from './MessageInput';
export * from './MessageInputFlat';
export * from './QuotedMessagePreview';
export * from './SendButton';
export { WithDragAndDropUpload } from './WithDragAndDropUpload';
export {
FileDragAndDropContent,
type FileDragAndDropContentProps,
WithDragAndDropUpload,
} from './WithDragAndDropUpload';
65 changes: 65 additions & 0 deletions src/components/MessageInput/styling/DropzoneContainer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
.str-chat {
/* The text/icon color of the dropzone container */
--str-chat__dropzone-container-color: var(--text-primary);

/* The background color of the dropzone container */
--str-chat__dropzone-container-background-color: var(--background-core-overlay-light);

/* The backdrop filter applied to the dropzone container */
--str-chat__dropzone-container-backdrop-filter: blur(3.5px);
}

.str-chat__dropzone-root {
position: relative;
min-height: 0;
min-width: 0;
max-width: 100%;
max-height: 100%;
flex: 1;
}

// When wrapper has no content (e.g. Thread returns null when closed), take no space — keeps wrapper mounted to avoid remounts.
.str-chat__dropzone-root:empty {
display: none;
}

.str-chat__dropzone-container {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
inset: 0;
z-index: 5;
background-color: var(--str-chat__dropzone-container-background-color);
color: var(--str-chat__dropzone-container-color);
backdrop-filter: var(--str-chat__dropzone-container-backdrop-filter);
font: var(--str-chat__heading-sm-text);

.str-chat__dropzone-container__content {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-xs, 8px);
}

svg {
height: 32px;
width: 32px;
}

p {
margin: unset;

}
}

// When backdrop-filter is not supported, use a dimmed background only (no blur).
@supports not (backdrop-filter: blur(1px)) {
.str-chat__dropzone-container {
backdrop-filter: none;
background-color: var(
--str-chat__dropzone-container-background-color-fallback,
rgba(0, 0, 0, 0.4)
);
}
}
1 change: 1 addition & 0 deletions src/components/MessageInput/styling/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@use 'AttachmentPreviewThumbnail';
@use 'AttachmentSelector';
@use 'CommandChip';
@use 'DropzoneContainer';
@use 'CommandsMenu';
@use 'LinkPreviewList';
@use 'MessageComposer';
Expand Down
3 changes: 3 additions & 0 deletions src/context/ComponentContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
EmojiSearchIndex,
EmptyStateIndicatorProps,
EventComponentProps,
FileDragAndDropContentProps,
FixedHeightMessageProps,
GalleryProps,
GiphyPreviewMessageProps,
Expand Down Expand Up @@ -103,6 +104,8 @@ export type ComponentContextValue = {
CooldownTimer?: React.ComponentType;
/** Custom UI component for date separators, defaults to and accepts same props as: [DateSeparator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/DateSeparator.tsx) */
DateSeparator?: React.ComponentType<DateSeparatorProps>;
/** Custom UI component to display the contents on file drag-and-drop overlay, defaults to and accepts same props as: [FileDragAndDropContent](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/WithDragAndDropUpload.tsx) */
FileDragAndDropContent?: React.ComponentType<FileDragAndDropContentProps>;
/** Custom UI component to override default preview of edited message, defaults to and accepts same props as: [EditedMessagePreview](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/EditedMessagePreview.tsx) */
EditedMessagePreview?: React.ComponentType<EditedMessagePreviewProps>;
/** Custom UI component for rendering button with emoji picker in MessageInput */
Expand Down
1 change: 1 addition & 0 deletions src/styling/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
@use '../components/Channel/styling' as Channel;
@use '../components/ChatView/styling' as ChatView;
@use '../components/DateSeparator/styling' as DateSeparator;
@use '../components/DragAndDrop/styling' as DragAndDrop;
@use '../components/Gallery/styling' as Gallery;
@use '../components/MediaRecorder/AudioRecorder/styling' as AudioRecorder;
@use '../components/Message/styling' as Message;
Expand Down
Loading