11import * as React from 'react' ;
2- import { keyframes } from '@emotion/react' ;
32import type { FileWithHandle } from 'browser-fs-access' ;
43
54import type { SxProps } from '@mui/joy/styles/types' ;
6- import { Box , Button , Checkbox , ColorPaletteProp , Dropdown , IconButton , ListDivider , ListItem , ListItemDecorator , Menu , MenuButton , MenuItem } from '@mui/joy' ;
5+ import { Box , Button , Checkbox , ColorPaletteProp , Dropdown , IconButton , ListDivider , ListItem , ListItemDecorator , MenuButton } from '@mui/joy' ;
76import AddRoundedIcon from '@mui/icons-material/AddRounded' ;
87import AddToDriveRoundedIcon from '@mui/icons-material/AddToDriveRounded' ;
98import AttachFileRoundedIcon from '@mui/icons-material/AttachFileRounded' ;
@@ -16,10 +15,10 @@ import ScreenshotMonitorIcon from '@mui/icons-material/ScreenshotMonitor';
1615import { useBrowseStore } from '~/modules/browse/store-module-browsing' ;
1716
1817import { ButtonAttachFilesMemo , openFileForAttaching } from '~/common/components/ButtonAttachFiles' ;
18+ import { RichMenu , RichMenuButton , RichMenuItem , richMenuItemSx } from '~/common/components/RichMenu' ;
1919import { TooltipOutlined } from '~/common/components/TooltipOutlined' ;
2020import { supportsClipboardRead } from '~/common/util/clipboardUtils' ;
2121import { takeScreenCapture } from '~/common/util/screenCaptureUtils' ;
22- import { themeZIndexOverMobileDrawer } from '~/common/app.theme' ;
2322
2423import { ButtonAttachCameraMemo } from './ButtonAttachCamera' ;
2524import { ButtonAttachClipboardMemo } from './ButtonAttachClipboard' ;
@@ -29,43 +28,7 @@ import { ButtonAttachWebMemo } from './ButtonAttachWeb';
2928import { hasGoogleDriveCapability } from './useGoogleDrivePicker' ;
3029
3130
32- // configuration
33- export const ATTACH_BUTTON_RADIUS = '18px' ; // for the rich (non-compact) menu button
34-
35-
36- // animations for the rich (non-compact) menu
37- const animationMenu = keyframes ` from {opacity: 0;} to {opacity: 1;}` ;
38- const animationMenuItem = keyframes ` from {opacity: 0;transform: translateY(-6px);} to {opacity: 1;transform: translateY(0);}` ;
39-
4031const _style = {
41- menuItem : {
42- // pl: 3,
43- // pr: 2,
44- py : 0.5 , // was 1
45- minHeight : 60 ,
46- // minHeight: '3.25rem', // now 52, was 60
47- } ,
48- menuItemContent : {
49- display : 'flex' ,
50- flexDirection : 'column' ,
51- gap : 0.125 ,
52- } ,
53- menuItemContentDisabled : {
54- display : 'flex' ,
55- flexDirection : 'column' ,
56- gap : 0.125 ,
57- opacity : 0.5 ,
58- } ,
59- menuItemName : {
60- typography : 'title-sm' ,
61- fontWeight : 600 ,
62- // fontSize: '15px',
63- } ,
64- menuItemDescription : {
65- fontSize : 'xs' ,
66- color : 'text.tertiary' ,
67- // fontWeight: 400,
68- } ,
6932 liveFeedButton : {
7033 ml : 1 ,
7134 // outline: '1px solid transparent',
@@ -98,50 +61,8 @@ function LiveFeedButton(props: { isActive: boolean, tooltip: string, onClick: ()
9861}
9962
10063
101- // Rich menu item (used in menu-rich mode)
102- function RichMenuItem ( props : {
103- name : React . ReactNode ;
104- description : React . ReactNode ;
105- Icon : React . ComponentType ;
106- onClick : ( ) => void ;
107- delay ?: number ;
108- disabled ?: boolean ;
109- color ?: ColorPaletteProp ;
110- endAction ?: React . ReactNode ;
111- } ) {
112- return (
113- < MenuItem
114- onClick = { props . onClick }
115- disabled = { props . disabled }
116- color = { props . color }
117- sx = { ! props . delay ? _style . menuItem : {
118- ..._style . menuItem ,
119- animation : `${ animationMenuItem } 0.12s cubic-bezier(0.25, 0.46, 0.45, 0.94) ${ props . delay } s both` ,
120- } }
121- >
122- < ListItemDecorator >
123- < props . Icon />
124- </ ListItemDecorator >
125- < Box sx = { props . disabled ? _style . menuItemContentDisabled : _style . menuItemContent } >
126- < Box sx = { _style . menuItemName } >
127- { props . name }
128- </ Box >
129- < Box sx = { _style . menuItemDescription } >
130- { props . description }
131- </ Box >
132- </ Box >
133- { props . endAction && (
134- < Box sx = { { ml : 'auto' , display : 'flex' , alignItems : 'center' } } >
135- { props . endAction }
136- </ Box >
137- ) }
138- </ MenuItem >
139- ) ;
140- }
141-
142-
14364// Auto-download toggle (shown when browsing capability exists)
144- function AutoDownloadToggle ( props : { delay ?: number } ) {
65+ function AutoDownloadToggle ( ) {
14566
14667 // external state
14768 const enableComposerAttach = useBrowseStore ( s => s . enableComposerAttach ) ;
@@ -155,17 +76,7 @@ function AutoDownloadToggle(props: { delay?: number }) {
15576
15677 < ListDivider inset = 'gutter' sx = { { my : 1 } } />
15778
158- < ListItem
159- sx = { {
160- ..._style . menuItem ,
161- animation : `${ animationMenuItem } 0.12s cubic-bezier(0.25, 0.46, 0.45, 0.94) ${ props . delay } s both` ,
162- } }
163- // onClick={(event) => {
164- // event.preventDefault();
165- // event.stopPropagation();
166- // setEnableComposerAttach(!enableComposerAttach);
167- // }}
168- >
79+ < ListItem sx = { richMenuItemSx . item } >
16980 < ListItemDecorator >
17081 < Checkbox
17182 size = 'sm'
@@ -176,11 +87,11 @@ function AutoDownloadToggle(props: { delay?: number }) {
17687 sx = { { ml : 0.375 } }
17788 />
17889 </ ListItemDecorator >
179- < Box sx = { _style . menuItemContent } >
90+ < Box sx = { richMenuItemSx . content } >
18091 < Box sx = { { typography : 'title-sm' } } >
18192 Attach pasted URLs
18293 </ Box >
183- < Box sx = { _style . menuItemDescription } >
94+ < Box sx = { richMenuItemSx . description } >
18495 Download and attach pasted web links
18596 </ Box >
18697 </ Box >
@@ -293,9 +204,7 @@ function AttachmentSources(props: {
293204
294205 < Dropdown >
295206 { props . menuButton ? props . menuButton : ! isMessage ? (
296- < MenuButton slots = { { root : IconButton } } >
297- < AddRoundedIcon />
298- </ MenuButton >
207+ < RichMenuButton icon = { < AddRoundedIcon /> } />
299208 ) : (
300209 < MenuButton slots = { { root : Button } } slotProps = { {
301210 root : {
@@ -309,48 +218,28 @@ function AttachmentSources(props: {
309218 Attach
310219 </ MenuButton >
311220 ) }
312- < Menu sx = { { '--List-padding' : '0.5rem' , zIndex : themeZIndexOverMobileDrawer /* menu-compact or menu-message: above dialogs */ } } >
221+ < RichMenu compact /* menu-compact or menu-message: above dialogs (default zIndex) */ >
313222
314223 { /* Files */ }
315- { /*<MenuItem onClick={handleAttachFilePicker}>*/ }
316- { /* <ListItemDecorator><AttachFileRoundedIcon /></ListItemDecorator>*/ }
317- { /* {props.onlyImages ? 'Images' : 'File'}*/ }
318- { /*</MenuItem>*/ }
319224 < RichMenuItem name = { props . onlyImages ? 'Images' : 'Files' } description = 'PDF, DOCX, images, code' color = { props . color } Icon = { AttachFileRoundedIcon } onClick = { handleAttachFilePicker } />
320225
321226 { /* Web */ }
322227 { ! props . onlyImages && /*props.canBrowse &&*/ (
323- // <MenuItem onClick={props.onOpenWebInput} disabled={!props.canBrowse}>
324- // <ListItemDecorator><LanguageRoundedIcon /></ListItemDecorator>
325- // Web
326- // </MenuItem>
327228 < RichMenuItem name = 'Web' description = 'Import from web pages' color = { props . color } Icon = { LanguageRoundedIcon } onClick = { props . onOpenWebInput } disabled = { ! props . canBrowse } />
328229 ) }
329230
330231 { /* Google Drive */ }
331232 { ! props . onlyImages && hasGoogleDriveCapability && ! ! props . onOpenGoogleDrivePicker && (
332- // <MenuItem onClick={props.onOpenGoogleDrivePicker}>
333- // <ListItemDecorator><AddToDriveRoundedIcon /></ListItemDecorator>
334- // Drive
335- // </MenuItem>
336233 < RichMenuItem name = 'Drive' description = 'Attach Google Drive files' color = { props . color } Icon = { AddToDriveRoundedIcon } onClick = { props . onOpenGoogleDrivePicker } />
337234 ) }
338235
339236 { /* Clipboard */ }
340237 { ! props . onlyImages && supportsClipboardRead ( ) && (
341- // <MenuItem onClick={props.onAttachClipboard}>
342- // <ListItemDecorator><ContentPasteGoIcon /></ListItemDecorator>
343- // Paste
344- // </MenuItem>
345238 < RichMenuItem name = 'Clipboard' description = 'Auto-convert to the best format' color = { props . color } Icon = { ContentPasteGoIcon } onClick = { props . onAttachClipboard } />
346239 ) }
347240
348241 { /* Screen Capture */ }
349242 { props . hasScreenCapture && (
350- // <MenuItem onClick={handleTakeScreenCapture} disabled={capturingScreen}>
351- // <ListItemDecorator><ScreenshotMonitorIcon /></ListItemDecorator>
352- // Screen
353- // </MenuItem>
354243 < RichMenuItem
355244 name = 'Screen'
356245 color = { screenCaptureError ? 'danger' : props . color }
@@ -364,10 +253,6 @@ function AttachmentSources(props: {
364253
365254 { /* Camera */ }
366255 { props . hasCamera && isMessage && (
367- // <MenuItem onClick={props.onOpenCamera}>
368- // <ListItemDecorator><CameraAltOutlinedIcon /></ListItemDecorator>
369- // Camera
370- // </MenuItem>
371256 < RichMenuItem
372257 name = 'Camera'
373258 color = { props . color }
@@ -378,7 +263,7 @@ function AttachmentSources(props: {
378263 />
379264 ) }
380265
381- </ Menu >
266+ </ RichMenu >
382267 </ Dropdown >
383268
384269 { /* [mobile] Responsive Camera OCR button */ }
@@ -391,65 +276,22 @@ function AttachmentSources(props: {
391276 // menu-rich mode (desktop) - labeled button trigger with animated, descriptive menu items
392277 return (
393278 < Dropdown >
394- < MenuButton
395- slots = { { root : Button } }
396- slotProps = { {
397- root : {
398- // size: 'sm',
399- variant : 'plain' ,
400- color : props . color ,
401- startDecorator : < AddRoundedIcon /> ,
402- fullWidth : true , // to match other buttons in the col
403- sx : {
404- minWidth : 100 ,
405- justifyContent : 'flex-start' ,
406- borderRadius : ATTACH_BUTTON_RADIUS ,
407- textWrap : 'nowrap' ,
408- ...( props . richButtonStandOut && {
409- backgroundColor : 'background.popup' ,
410- border : '1px solid' ,
411- borderColor : `${ props . color || 'neutral' } .outlinedBorder` ,
412- } ) ,
413- // when aria-expanded is true (menu open), remove top border radius
414- '&[aria-expanded="true"]' : {
415- borderTopRightRadius : 0 ,
416- borderTopLeftRadius : 0 ,
417- backgroundColor : `${ props . color || 'neutral' } .softHoverBg` ,
418- } ,
419- } ,
420- } ,
421- } }
422- >
423- Attach
424- </ MenuButton >
425279
426- < Menu
427- // variant='soft'
280+ < RichMenuButton
281+ icon = { < AddRoundedIcon /> }
282+ label = 'Attach'
428283 color = { props . color }
429- placement = 'top-start'
430- popperOptions = { { modifiers : [ { name : 'offset' , options : { offset : [ - 10 /* 62 */ , - 2 ] } } ] } }
431- sx = { {
432- minWidth : 280 ,
433- '--List-padding' : '0.5rem' ,
434- zIndex : themeZIndexOverMobileDrawer ,
435- animation : `${ animationMenu } 0.12s cubic-bezier(0.25, 0.46, 0.45, 0.94)` ,
436- // boxShadow: '0 16px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
437- boxShadow : 'md' ,
438- borderRadius : ATTACH_BUTTON_RADIUS ,
439- border : '1px solid' ,
440- borderColor : `${ props . color || 'neutral' } .outlinedBorder` ,
441- backgroundColor : 'background.popup' ,
442- overflow : 'hidden' ,
443- } }
444- >
284+ standOut = { props . richButtonStandOut }
285+ />
286+
287+ < RichMenu stagger color = { props . color } >
445288
446289 { /* File Attachment */ }
447290 < RichMenuItem
448291 name = { props . onlyImages ? 'Images' : 'Files' }
449292 Icon = { AttachFileRoundedIcon }
450293 description = { props . onlyImages ? 'PNG, JPG, WEBP images to edit' : 'PDF, DOCX, images, code' }
451294 onClick = { handleAttachFilePicker }
452- delay = { 0 }
453295 />
454296
455297 { /* Web/URL Attachment */ }
@@ -460,7 +302,6 @@ function AttachmentSources(props: {
460302 description = 'Import web pages, including screenshots'
461303 onClick = { props . onOpenWebInput }
462304 disabled = { ! props . canBrowse }
463- delay = { 0.02 }
464305 />
465306 ) }
466307
@@ -471,7 +312,6 @@ function AttachmentSources(props: {
471312 Icon = { AddToDriveRoundedIcon }
472313 description = 'Attach Google Drive files'
473314 onClick = { props . onOpenGoogleDrivePicker }
474- delay = { 0.04 }
475315 />
476316 ) }
477317
@@ -483,7 +323,6 @@ function AttachmentSources(props: {
483323 // description='Auto-converts images and text to the best format'
484324 description = 'Auto-adapts images and text'
485325 onClick = { props . onAttachClipboard }
486- delay = { 0.06 }
487326 />
488327 ) }
489328
@@ -523,7 +362,6 @@ function AttachmentSources(props: {
523362 onClick = { handleTakeScreenCapture }
524363 disabled = { capturingScreen }
525364 color = { screenCaptureError ? 'danger' : undefined }
526- delay = { 0.08 }
527365 endAction = { props . onStartLiveScreenFeed && < LiveFeedButton isActive = { ! ! props . hasActiveScreenFeed } tooltip = 'Live Screen chat' onClick = { props . onStartLiveScreenFeed } /> }
528366 />
529367 ) }
@@ -535,17 +373,16 @@ function AttachmentSources(props: {
535373 Icon = { CameraAltOutlinedIcon }
536374 description = 'Capture photos with optional OCR'
537375 onClick = { props . onOpenCamera }
538- delay = { 0.1 }
539376 endAction = { props . onStartLiveCameraFeed && < LiveFeedButton isActive = { ! ! props . hasActiveCameraFeed } tooltip = 'Live Camera chat' onClick = { props . onStartLiveCameraFeed } /> }
540377 />
541378 ) }
542379
543380 { /* URL Auto-Download Toggle - only show when browse capability exists */ }
544381 { ! props . onlyImages && props . canBrowse && (
545- < AutoDownloadToggle delay = { 0.12 } />
382+ < AutoDownloadToggle />
546383 ) }
547384
548- </ Menu >
385+ </ RichMenu >
549386 </ Dropdown >
550387 ) ;
551388}
0 commit comments