1- import { useCallback , useEffect , useRef , useState } from 'react' ;
2- import type { PointerEvent as ReactPointerEvent } from 'react' ;
1+ import { useCallback , useEffect , useState } from 'react' ;
32import type { LocalAttachment } from 'stream-chat' ;
43import {
5- DialogAnchor ,
64 Prompt ,
75 useChatContext ,
86 useDialogIsOpen ,
97 useDialogOnNearestManager ,
108} from 'stream-chat-react' ;
9+ import { DraggableDialog } from './DraggableDialog' ;
1110
1211export const attachmentPromptDialogId = 'app-attachment-prompt-dialog' ;
1312type AttachmentEditorTab = 'unsupported-file' | 'unsupported-object' ;
1413
15- const VIEWPORT_MARGIN = 8 ;
1614const 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-
5144export 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