FloatingMobileToolbar.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import { useParams, useViewport } from 'common'
  2. import { AnimatePresence } from 'framer-motion'
  3. import { Menu, Search, X } from 'lucide-react'
  4. import { useRef } from 'react'
  5. import { Button, cn, KeyboardShortcut } from 'ui'
  6. import { getToolbarStyle } from './FloatingMobileToolbar.utils'
  7. import { useFloatingToolbarDrag } from './useFloatingToolbarDrag'
  8. import { useFloatingToolbarNavSize } from './useFloatingToolbarNavSize'
  9. import { useFloatingToolbarSheet } from './useFloatingToolbarSheet'
  10. import { useFloatingToolbarSidebarClick } from './useFloatingToolbarSidebarClick'
  11. import { AdvisorButton } from '@/components/layouts/AppLayout/AdvisorButton'
  12. import { AssistantButton } from '@/components/layouts/AppLayout/AssistantButton'
  13. import { InlineEditorButton } from '@/components/layouts/AppLayout/InlineEditorButton'
  14. import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
  15. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  16. import { HelpButton } from '@/components/ui/HelpPanel/HelpButton'
  17. export const FloatingMobileToolbar = ({ hideMobileMenu }: { hideMobileMenu?: boolean }) => {
  18. const navRef = useRef<HTMLElement | null>(null)
  19. const sheet = useFloatingToolbarSheet(hideMobileMenu)
  20. const drag = useFloatingToolbarDrag(navRef)
  21. const handleNavClickCapture = useFloatingToolbarSidebarClick()
  22. const navSize = useFloatingToolbarNavSize(navRef, sheet.isSheetOpen)
  23. const viewport = useViewport()
  24. const { handleSearchClick, isSearchOpen } = sheet
  25. const style = getToolbarStyle({
  26. position: drag.position,
  27. navSize,
  28. isSheetOpen: sheet.isSheetOpen,
  29. viewport,
  30. isDragging: drag.dragStartRef.current !== null,
  31. })
  32. const { ref: projectRef } = useParams()
  33. return (
  34. <nav
  35. ref={navRef}
  36. aria-label="Floating toolbar"
  37. className={cn(
  38. 'flex pointer-events-auto cursor-grab active:cursor-grabbing flex-row items-centerw-auto',
  39. 'gap-2',
  40. 'fixed md:hidden'
  41. )}
  42. style={style}
  43. onClickCapture={handleNavClickCapture}
  44. onPointerDown={drag.handlePointerDown}
  45. onPointerMove={drag.handlePointerMove}
  46. >
  47. <div
  48. className={cn(
  49. 'flex pointer-events-auto cursor-grab active:cursor-grabbing flex-row items-center justify-between w-auto rounded-full',
  50. 'bg-overlay/80 backdrop-blur-md px-2.5 py-2 gap-2',
  51. 'border border-strong shadow-[0px_3px_6px_-2px_rgba(0,0,0,0.07),0px_10px_30px_0px_rgba(0,0,0,0.10)]'
  52. )}
  53. >
  54. <AnimatePresence initial={false}>
  55. <ButtonTooltip
  56. key="search"
  57. type={isSearchOpen ? 'secondary' : 'outline'}
  58. size="tiny"
  59. id="search-trigger"
  60. className={cn(
  61. 'rounded-full w-[32px] h-[32px] flex items-center justify-center p-0',
  62. isSearchOpen && 'text-background'
  63. )}
  64. tooltip={{
  65. content: {
  66. className: 'p-1 pl-2.5',
  67. text: (
  68. <div className="flex items-center gap-2.5">
  69. <span>Search</span>
  70. <KeyboardShortcut keys={['Meta', 'K']} />
  71. </div>
  72. ),
  73. },
  74. }}
  75. onClick={handleSearchClick}
  76. >
  77. <Search size={16} strokeWidth={1} />
  78. </ButtonTooltip>
  79. <span key="help" data-sidebar-id={SIDEBAR_KEYS.HELP_PANEL}>
  80. <HelpButton />
  81. </span>
  82. <span key="advisor" data-sidebar-id={SIDEBAR_KEYS.ADVISOR_PANEL}>
  83. <AdvisorButton projectRef={projectRef} />
  84. </span>
  85. {!!projectRef && (
  86. <>
  87. <span key="editor" data-sidebar-id={SIDEBAR_KEYS.EDITOR_PANEL}>
  88. <InlineEditorButton />
  89. </span>
  90. <span key="assistant" data-sidebar-id={SIDEBAR_KEYS.AI_ASSISTANT}>
  91. <AssistantButton />
  92. </span>
  93. </>
  94. )}
  95. {sheet.showMenuButton && sheet.isSheetOpen && (
  96. <Button
  97. key="menu"
  98. title="Menu dropdown button"
  99. type={sheet.isMenuOpen ? 'secondary' : 'default'}
  100. className={cn(
  101. 'flex lg:hidden mr-1 rounded-md min-w-[30px] w-[30px] h-[30px] data-open:bg-overlay-hover/30',
  102. !sheet.isMenuOpen && 'bg-surface-300!'
  103. )}
  104. icon={<Menu />}
  105. onClick={sheet.handleMenuClick}
  106. />
  107. )}
  108. </AnimatePresence>
  109. </div>
  110. <AnimatePresence initial={false}>
  111. <Button
  112. title="close"
  113. type="text"
  114. className={cn(
  115. 'flex flex-row items-center justify-center rounded-full',
  116. 'bg-overlay/50 backdrop-blur-md my-auto p-1! gap-2',
  117. 'border border-strong shadow-[0px_3px_6px_-2px_rgba(0,0,0,0.07),0px_10px_30px_0px_rgba(0,0,0,0.10)]',
  118. 'w-10! h-10! min-w-10! min-h-10!',
  119. 'rounded-full',
  120. !sheet.isSheetOpen && 'hidden'
  121. )}
  122. icon={<X />}
  123. onClick={sheet.handleClose}
  124. />
  125. </AnimatePresence>
  126. </nav>
  127. )
  128. }