Files
supabase/apps/studio/components/layouts/Navigation/FloatingMobileToolbar/FloatingMobileToolbar.utils.ts
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

110 lines
3.0 KiB
TypeScript

import type { CSSProperties } from 'react'
export const DRAG_THRESHOLD_PX = 8
export const GAP_FROM_BOTTOM = 50
export const SHEET_OPEN_GAP_FRACTION = 0.15
export const DEFAULT_NAV_HEIGHT = 56
export function isMenuContent(content: unknown): boolean {
return content !== null && typeof content !== 'string'
}
export function shouldShowMenuButton(pathname: string): boolean {
return (
pathname.startsWith('/project/') ||
pathname.startsWith('/org/') ||
pathname.startsWith('/account')
)
}
export type Viewport = { width: number; height: number }
export type NavSize = { width: number; height: number }
export type Position = { x: number; y: number }
export function clampPosition(
current: Position,
delta: { dx: number; dy: number },
viewport: Viewport,
navSize: NavSize
): Position {
const maxX = Math.max(0, viewport.width - navSize.width)
const maxY = Math.max(0, viewport.height - navSize.height)
return {
x: Math.max(0, Math.min(maxX, current.x + delta.dx)),
y: Math.max(0, Math.min(maxY, current.y + delta.dy)),
}
}
export type DragStartState = {
x: number
y: number
startX: number
startY: number
}
export function getNextPosition(
dragStart: DragStartState,
clientX: number,
clientY: number,
viewport: Viewport,
navSize: NavSize,
threshold: number = DRAG_THRESHOLD_PX
): Position | null {
const dist = Math.hypot(clientX - dragStart.startX, clientY - dragStart.startY)
if (dist < threshold) return null
const dx = clientX - dragStart.startX
const dy = clientY - dragStart.startY
return clampPosition({ x: dragStart.x, y: dragStart.y }, { dx, dy }, viewport, navSize)
}
export function getToolbarStyle(params: {
position: Position | null
navSize: NavSize
isSheetOpen: boolean
viewport: Viewport
isDragging: boolean
}): CSSProperties {
const { position, navSize, isSheetOpen, viewport, isDragging } = params
const { width: navW, height: navH } = navSize
const vw = viewport.width
const vh = viewport.height
const transition = isDragging
? 'transform 0ms, z-index 0s'
: 'transform 300ms cubic-bezier(0.4, 0, 0.2, 1), z-index 0s'
const base: CSSProperties = {
zIndex: isSheetOpen ? 101 : 41,
transition,
touchAction: 'none',
}
const centerX = vw > 0 && navW > 0 ? vw / 2 - navW / 2 : 0
const sheetOpenGapPx = vh * SHEET_OPEN_GAP_FRACTION
const topWhenSheetOpen = vh > 0 ? sheetOpenGapPx / 2 - navH / 2 : 0
const defaultYClosed = vh > 0 ? vh - GAP_FROM_BOTTOM - (navH > 0 ? navH : DEFAULT_NAV_HEIGHT) : 0
if (position === null) {
return {
...base,
left: '50%',
top: 0,
transform: isSheetOpen
? `translate(-50%, ${topWhenSheetOpen}px)`
: `translate(-50%, ${defaultYClosed}px)`,
}
}
if (isSheetOpen) {
return {
...base,
left: 0,
top: 0,
transform: `translate(${centerX}px, ${topWhenSheetOpen}px)`,
}
}
return {
...base,
left: 0,
top: 0,
transform: `translate(${position.x}px, ${position.y}px)`,
}
}