Skip to content

Commit fd0c477

Browse files
committed
chore(demo): add WebSocketEventPromptDialog for event triggering simulation
1 parent 0c0d65c commit fd0c477

16 files changed

Lines changed: 5468 additions & 329 deletions

examples/vite/src/AppSettings/ActionsMenu/ActionsMenu.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import {
1818
AttachmentPromptDialog,
1919
attachmentPromptDialogId,
2020
} from './AttachmentPromptDialog';
21+
import {
22+
WebSocketEventPromptDialog,
23+
webSocketEventPromptDialogId,
24+
} from './WebSocketEventPromptDialog';
2125

2226
const actionsMenuDialogId = 'app-actions-menu';
2327

@@ -91,6 +95,20 @@ function TriggerAttachmentAction({ onTrigger }: { onTrigger: () => void }) {
9195
);
9296
}
9397

98+
function TriggerWebSocketEventAction({ onTrigger }: { onTrigger: () => void }) {
99+
const { closeMenu } = useContextMenuContext();
100+
101+
return (
102+
<ContextMenuButton
103+
label='Trigger WS Event'
104+
onClick={() => {
105+
closeMenu();
106+
onTrigger();
107+
}}
108+
/>
109+
);
110+
}
111+
94112
const ActionsMenuInner = ({ iconOnly }: { iconOnly: boolean }) => {
95113
const [menuButtonElement, setMenuButtonElement] = useState<HTMLButtonElement | null>(
96114
null,
@@ -104,6 +122,9 @@ const ActionsMenuInner = ({ iconOnly }: { iconOnly: boolean }) => {
104122
const { dialog: attachmentDialog } = useDialogOnNearestManager({
105123
id: attachmentPromptDialogId,
106124
});
125+
const { dialog: webSocketEventDialog } = useDialogOnNearestManager({
126+
id: webSocketEventPromptDialogId,
127+
});
107128
const menuIsOpen = useDialogIsOpen(actionsMenuDialogId, dialogManager?.id);
108129

109130
return (
@@ -127,9 +148,11 @@ const ActionsMenuInner = ({ iconOnly }: { iconOnly: boolean }) => {
127148
>
128149
<TriggerNotificationAction onTrigger={notificationDialog.open} />
129150
<TriggerAttachmentAction onTrigger={attachmentDialog.open} />
151+
<TriggerWebSocketEventAction onTrigger={webSocketEventDialog.open} />
130152
</ContextMenu>
131153
<NotificationPromptDialog referenceElement={menuButtonElement} />
132154
<AttachmentPromptDialog referenceElement={menuButtonElement} />
155+
<WebSocketEventPromptDialog referenceElement={menuButtonElement} />
133156
</div>
134157
);
135158
};
Lines changed: 87 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
import { useCallback, useEffect, useRef, useState } from 'react';
2-
import type { PointerEvent as ReactPointerEvent } from 'react';
1+
import { useCallback, useEffect, useState } from 'react';
32
import type { LocalAttachment } from 'stream-chat';
43
import {
5-
DialogAnchor,
64
Prompt,
75
useChatContext,
86
useDialogIsOpen,
97
useDialogOnNearestManager,
108
} from 'stream-chat-react';
9+
import { DraggableDialog } from './DraggableDialog';
1110

1211
export const attachmentPromptDialogId = 'app-attachment-prompt-dialog';
1312
type AttachmentEditorTab = 'unsupported-file' | 'unsupported-object';
1413

15-
const VIEWPORT_MARGIN = 8;
1614
const defaultUnsupportedAttachment = {
1715
asset_url: 'https://example.com/unsupported.bin',
1816
file_size: 128000,
@@ -43,11 +41,6 @@ const initialUnsupportedObjectValue = JSON.stringify(
4341
2,
4442
);
4543

46-
const clamp = (value: number, min: number, max: number) => {
47-
if (max < min) return min;
48-
return Math.min(Math.max(value, min), max);
49-
};
50-
5144
export const AttachmentPromptDialog = ({
5245
referenceElement,
5346
}: {
@@ -61,8 +54,6 @@ export const AttachmentPromptDialog = ({
6154
initialUnsupportedObjectValue,
6255
);
6356
const [errorMessage, setErrorMessage] = useState<string | null>(null);
64-
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
65-
const shellRef = useRef<HTMLDivElement | null>(null);
6657
const { channel } = useChatContext();
6758
const { dialog, dialogManager } = useDialogOnNearestManager({
6859
id: attachmentPromptDialogId,
@@ -75,41 +66,6 @@ export const AttachmentPromptDialog = ({
7566
setUnsupportedFileInput(initialUnsupportedFileValue);
7667
setUnsupportedObjectInput(initialUnsupportedObjectValue);
7768
setErrorMessage(null);
78-
setDragOffset({ x: 0, y: 0 });
79-
}, [dialogIsOpen]);
80-
81-
useEffect(() => {
82-
if (!dialogIsOpen) return;
83-
84-
const clampToViewport = () => {
85-
const shell = shellRef.current;
86-
if (!shell) return;
87-
88-
const rect = shell.getBoundingClientRect();
89-
const nextLeft = clamp(
90-
rect.left,
91-
VIEWPORT_MARGIN,
92-
window.innerWidth - rect.width - VIEWPORT_MARGIN,
93-
);
94-
const nextTop = clamp(
95-
rect.top,
96-
VIEWPORT_MARGIN,
97-
window.innerHeight - rect.height - VIEWPORT_MARGIN,
98-
);
99-
100-
if (nextLeft === rect.left && nextTop === rect.top) return;
101-
102-
setDragOffset((current) => ({
103-
x: current.x + (nextLeft - rect.left),
104-
y: current.y + (nextTop - rect.top),
105-
}));
106-
};
107-
108-
window.addEventListener('resize', clampToViewport);
109-
110-
return () => {
111-
window.removeEventListener('resize', clampToViewport);
112-
};
11369
}, [dialogIsOpen]);
11470

11571
const closeDialog = useCallback(() => {
@@ -145,159 +101,99 @@ export const AttachmentPromptDialog = ({
145101
[channel, closeDialog, unsupportedFileInput, unsupportedObjectInput],
146102
);
147103

148-
const handleHeaderPointerDown = useCallback(
149-
(event: ReactPointerEvent<HTMLDivElement>) => {
150-
if (event.button !== 0) return;
151-
if (!(event.target instanceof HTMLElement)) return;
152-
if (event.target.closest('button')) return;
153-
154-
const shell = shellRef.current;
155-
if (!shell) return;
156-
157-
event.preventDefault();
158-
159-
const startClientX = event.clientX;
160-
const startClientY = event.clientY;
161-
const startOffset = dragOffset;
162-
const startRect = shell.getBoundingClientRect();
163-
164-
const handlePointerMove = (moveEvent: PointerEvent) => {
165-
const nextLeft = clamp(
166-
startRect.left + (moveEvent.clientX - startClientX),
167-
VIEWPORT_MARGIN,
168-
window.innerWidth - startRect.width - VIEWPORT_MARGIN,
169-
);
170-
const nextTop = clamp(
171-
startRect.top + (moveEvent.clientY - startClientY),
172-
VIEWPORT_MARGIN,
173-
window.innerHeight - startRect.height - VIEWPORT_MARGIN,
174-
);
175-
176-
setDragOffset({
177-
x: startOffset.x + (nextLeft - startRect.left),
178-
y: startOffset.y + (nextTop - startRect.top),
179-
});
180-
};
181-
182-
const handlePointerUp = () => {
183-
window.removeEventListener('pointermove', handlePointerMove);
184-
window.removeEventListener('pointerup', handlePointerUp);
185-
};
186-
187-
window.addEventListener('pointermove', handlePointerMove);
188-
window.addEventListener('pointerup', handlePointerUp);
189-
},
190-
[dragOffset],
191-
);
192-
193-
const shellStyle = {
194-
transform: `translate(${dragOffset.x}px, ${dragOffset.y}px)`,
195-
};
196-
197104
return (
198-
<DialogAnchor
199-
allowFlip
200-
className='app__attachment-dialog'
105+
<DraggableDialog
106+
dialogClassName='app__attachment-dialog'
107+
dialogId={attachmentPromptDialogId}
108+
dialogIsOpen={dialogIsOpen}
201109
dialogManagerId={dialogManager?.id}
202-
id={attachmentPromptDialogId}
203-
placement='right-start'
110+
dragHandleClassName='app__attachment-dialog__drag-handle'
111+
onClose={closeDialog}
112+
promptClassName='app__attachment-dialog__prompt'
204113
referenceElement={referenceElement}
205-
tabIndex={-1}
206-
trapFocus
207-
updatePositionOnContentResize
114+
shellClassName='app__attachment-dialog__shell'
115+
title='Message Composer'
208116
>
209-
<div className='app__attachment-dialog__shell' ref={shellRef} style={shellStyle}>
210-
<Prompt.Root className='app__attachment-dialog__prompt'>
117+
<Prompt.Body className='app__attachment-dialog__body'>
118+
<div className='app__attachment-dialog__subsection'>
119+
<h3 className='app__attachment-dialog__subsection-title'>
120+
Attach Unsupported Attachment
121+
</h3>
211122
<div
212-
className='app__attachment-dialog__drag-handle'
213-
onPointerDown={handleHeaderPointerDown}
123+
aria-label='Attachment type'
124+
className='app__attachment-dialog__tabs'
125+
role='tablist'
214126
>
215-
<Prompt.Header close={closeDialog} title='Message Composer' />
127+
<button
128+
aria-selected={activeTab === 'unsupported-file'}
129+
className='app__attachment-dialog__tab'
130+
onClick={() => {
131+
setActiveTab('unsupported-file');
132+
if (errorMessage) setErrorMessage(null);
133+
}}
134+
role='tab'
135+
type='button'
136+
>
137+
Unsupported file
138+
</button>
139+
<button
140+
aria-selected={activeTab === 'unsupported-object'}
141+
className='app__attachment-dialog__tab'
142+
onClick={() => {
143+
setActiveTab('unsupported-object');
144+
if (errorMessage) setErrorMessage(null);
145+
}}
146+
role='tab'
147+
type='button'
148+
>
149+
Unsupported object
150+
</button>
216151
</div>
217-
<Prompt.Body className='app__attachment-dialog__body'>
218-
<div className='app__attachment-dialog__subsection'>
219-
<h3 className='app__attachment-dialog__subsection-title'>
220-
Attach Unsupported Attachment
221-
</h3>
222-
<div
223-
aria-label='Attachment type'
224-
className='app__attachment-dialog__tabs'
225-
role='tablist'
152+
<label className='app__attachment-dialog__field'>
153+
<span className='app__attachment-dialog__field-label'>Attachment JSON</span>
154+
<textarea
155+
className='app__attachment-dialog__textarea'
156+
onChange={(event) => {
157+
if (activeTab === 'unsupported-file') {
158+
setUnsupportedFileInput(event.target.value);
159+
} else {
160+
setUnsupportedObjectInput(event.target.value);
161+
}
162+
if (errorMessage) setErrorMessage(null);
163+
}}
164+
rows={12}
165+
spellCheck={false}
166+
value={
167+
activeTab === 'unsupported-file'
168+
? unsupportedFileInput
169+
: unsupportedObjectInput
170+
}
171+
/>
172+
</label>
173+
<div className='app__attachment-dialog__subsection-actions'>
174+
{activeTab === 'unsupported-file' ? (
175+
<Prompt.FooterControlsButtonPrimary
176+
size='sm'
177+
onClick={() => attachToComposer('unsupported-file')}
178+
>
179+
Attach Unsupported file
180+
</Prompt.FooterControlsButtonPrimary>
181+
) : (
182+
<Prompt.FooterControlsButtonPrimary
183+
size='sm'
184+
onClick={() => attachToComposer('unsupported-object')}
226185
>
227-
<button
228-
aria-selected={activeTab === 'unsupported-file'}
229-
className='app__attachment-dialog__tab'
230-
onClick={() => {
231-
setActiveTab('unsupported-file');
232-
if (errorMessage) setErrorMessage(null);
233-
}}
234-
role='tab'
235-
type='button'
236-
>
237-
Unsupported file
238-
</button>
239-
<button
240-
aria-selected={activeTab === 'unsupported-object'}
241-
className='app__attachment-dialog__tab'
242-
onClick={() => {
243-
setActiveTab('unsupported-object');
244-
if (errorMessage) setErrorMessage(null);
245-
}}
246-
role='tab'
247-
type='button'
248-
>
249-
Unsupported object
250-
</button>
251-
</div>
252-
<label className='app__attachment-dialog__field'>
253-
<span className='app__attachment-dialog__field-label'>
254-
Attachment JSON
255-
</span>
256-
<textarea
257-
className='app__attachment-dialog__textarea'
258-
onChange={(event) => {
259-
if (activeTab === 'unsupported-file') {
260-
setUnsupportedFileInput(event.target.value);
261-
} else {
262-
setUnsupportedObjectInput(event.target.value);
263-
}
264-
if (errorMessage) setErrorMessage(null);
265-
}}
266-
rows={12}
267-
spellCheck={false}
268-
value={
269-
activeTab === 'unsupported-file'
270-
? unsupportedFileInput
271-
: unsupportedObjectInput
272-
}
273-
/>
274-
</label>
275-
<div className='app__attachment-dialog__subsection-actions'>
276-
{activeTab === 'unsupported-file' ? (
277-
<Prompt.FooterControlsButtonPrimary
278-
size='sm'
279-
onClick={() => attachToComposer('unsupported-file')}
280-
>
281-
Attach Unsupported file
282-
</Prompt.FooterControlsButtonPrimary>
283-
) : (
284-
<Prompt.FooterControlsButtonPrimary
285-
size='sm'
286-
onClick={() => attachToComposer('unsupported-object')}
287-
>
288-
Attach Unsupported object
289-
</Prompt.FooterControlsButtonPrimary>
290-
)}
291-
</div>
292-
{errorMessage && (
293-
<div className='app__attachment-dialog__error' role='alert'>
294-
{errorMessage}
295-
</div>
296-
)}
186+
Attach Unsupported object
187+
</Prompt.FooterControlsButtonPrimary>
188+
)}
189+
</div>
190+
{errorMessage && (
191+
<div className='app__attachment-dialog__error' role='alert'>
192+
{errorMessage}
297193
</div>
298-
</Prompt.Body>
299-
</Prompt.Root>
300-
</div>
301-
</DialogAnchor>
194+
)}
195+
</div>
196+
</Prompt.Body>
197+
</DraggableDialog>
302198
);
303199
};

0 commit comments

Comments
 (0)