Files
Francesco Sansalvadore 156f904018 feat: floating mobile toolbar (#43444)
- move navigation components to `apps/studio/components/layouts/Navigation`
- add [FloatingMobileToolbar](https://github.com/supabase/supabase/pull/43444/changes#diff-3dffe47fd51ca851d612d8728e03b2dc344ec213d4f3a46a824d3fa32a7cc851) as quick access to tools such as search, assistant, inline editor, etc - behind feature flag and feature preview (true by default as it's a bit
annoying to have to enable it all the time as previews are stored in
local-storage)
- fix sidebar panels closing on viewport resizing (regression from previous pr)

https://github.com/user-attachments/assets/d6881e3b-5128-4306-bb82-3ca39c755dba

<img width="986" height="697" alt="Screenshot 2026-03-12 at 12 40 11"
src="https://github.com/user-attachments/assets/da8511e2-7d01-4237-b814-596031c747c5"
/>
2026-03-13 12:13:07 +00:00

98 lines
2.9 KiB
TypeScript

import { useCallback, useRef, useState } from 'react'
import {
DRAG_THRESHOLD_PX,
getNextPosition,
type NavSize,
type Position,
type Viewport,
} from './FloatingMobileToolbar.utils'
type DragStartState = {
x: number
y: number
startX: number
startY: number
pointerId: number
}
export function useFloatingToolbarDrag(navRef: React.RefObject<HTMLElement | null>) {
const [position, setPosition] = useState<Position | null>(null)
const dragStartRef = useRef<DragStartState | null>(null)
const applyMove = useCallback(
(clientX: number, clientY: number) => {
const state = dragStartRef.current
if (!state) return
const rect = navRef.current?.getBoundingClientRect()
const viewport: Viewport = {
width: typeof window !== 'undefined' ? window.innerWidth : 0,
height: typeof window !== 'undefined' ? window.innerHeight : 0,
}
const navSize: NavSize = {
width: rect?.width ?? 200,
height: rect?.height ?? 48,
}
const next = getNextPosition(state, clientX, clientY, viewport, navSize, DRAG_THRESHOLD_PX)
if (next) setPosition(next)
},
[navRef]
)
const handlePointerDown = useCallback(
(e: React.PointerEvent) => {
e.preventDefault()
e.stopPropagation()
const rect = navRef.current?.getBoundingClientRect()
if (!rect) return
const currentX = position?.x ?? rect.left
const currentY = position?.y ?? rect.top
dragStartRef.current = {
x: currentX,
y: currentY,
startX: e.clientX,
startY: e.clientY,
pointerId: e.pointerId,
}
const onMove = (moveEvent: PointerEvent) => {
if (dragStartRef.current?.pointerId !== moveEvent.pointerId) return
const dist = Math.hypot(
moveEvent.clientX - dragStartRef.current.startX,
moveEvent.clientY - dragStartRef.current.startY
)
if (dist >= DRAG_THRESHOLD_PX) {
navRef.current?.setPointerCapture?.(moveEvent.pointerId)
}
applyMove(moveEvent.clientX, moveEvent.clientY)
}
const onUpOrCancel = (upEvent: PointerEvent) => {
if (dragStartRef.current?.pointerId !== upEvent.pointerId) return
;(upEvent.target as HTMLElement)?.releasePointerCapture?.(upEvent.pointerId)
window.removeEventListener('pointermove', onMove)
window.removeEventListener('pointerup', onUpOrCancel)
window.removeEventListener('pointercancel', onUpOrCancel)
dragStartRef.current = null
}
window.addEventListener('pointermove', onMove)
window.addEventListener('pointerup', onUpOrCancel)
window.addEventListener('pointercancel', onUpOrCancel)
},
[navRef, position, applyMove]
)
const handlePointerMove = useCallback(
(e: React.PointerEvent) => {
applyMove(e.clientX, e.clientY)
},
[applyMove]
)
return {
position,
dragStartRef,
handlePointerDown,
handlePointerMove,
}
}