Files
supabase/packages/dev-tools/DevToolbarTrigger.tsx
Jordi EnricandClaude Sonnet 4.6 34925ac0fb feat(dev-tools): extend toolbar for preview envs and add resource warnings tab
- Enable DevToolbar in Vercel preview deployments by checking
  NEXT_PUBLIC_VERCEL_ENV === 'preview' alongside NODE_ENV === 'development'
  in all guards and the index.ts tree-shaking condition
- Rename "Dev Telemetry" → "Dev Toolbar"; badge shows "Preview" or "Local"
- Add ExtraTab extension point to DevToolbar so consumers can inject tabs
- Add ResourceWarningsTab in studio: same off/warn/crit toggles for all
  resource warning types, wired into the existing DevToolbar via _app.tsx
- Remove standalone SupabaseDevToolbar (superseded by the tab above)
- localStorage persistence already handled by the existing STORAGE_KEY
  logic in DevToolbarContext — enabled state survives refreshes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 11:50:54 +02:00

115 lines
3.3 KiB
TypeScript

'use client'
import { Activity, EyeOff } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import type { MouseEvent } from 'react'
import { Button, cn, Popover_Shadcn_, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_ } from 'ui'
import { useDevToolbar } from './DevToolbarContext'
const IS_DEV =
process.env.NODE_ENV === 'development' || process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview'
export function DevToolbarTrigger() {
const { isEnabled, isOpen, setIsOpen, events, dismissToolbar } = useDevToolbar()
const [popoverOpen, setPopoverOpen] = useState(false)
const closeTimeoutRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
return () => {
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current)
closeTimeoutRef.current = null
}
}
}, [])
if (!IS_DEV || !isEnabled) {
return null
}
const eventCount = events.length
const handleClick = () => {
setPopoverOpen(false)
setIsOpen(true)
}
const handleDismiss = () => {
setPopoverOpen(false)
dismissToolbar()
}
const handleMouseEnter = () => {
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current)
closeTimeoutRef.current = null
}
setPopoverOpen(true)
}
const handleMouseLeave = () => {
closeTimeoutRef.current = setTimeout(() => {
setPopoverOpen(false)
}, 100)
}
const handleTriggerMouseLeave = (event: MouseEvent<HTMLButtonElement>) => {
handleMouseLeave()
event.currentTarget.blur()
}
return (
<Popover_Shadcn_ open={popoverOpen} onOpenChange={setPopoverOpen}>
<PopoverTrigger_Shadcn_ asChild>
<Button
type="text"
className={cn(
'relative rounded-full h-[32px] px-2',
'text-foreground-light hover:text-foreground',
'focus-visible:outline-0 focus-visible:outline-transparent focus-visible:outline-offset-0',
isOpen && 'text-foreground bg-surface-300'
)}
aria-label="Open dev toolbar"
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleTriggerMouseLeave}
>
<Activity className="w-4 h-4" />
{eventCount > 0 && (
<span
className={cn(
'absolute -top-1 -right-1',
'h-4 min-w-4 px-0.5',
'inline-flex items-center justify-center',
'rounded-full bg-destructive text-white',
'text-[10px] font-medium leading-none'
)}
>
{eventCount > 99 ? '99+' : eventCount}
</span>
)}
</Button>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_
side="left"
align="center"
sideOffset={4}
className="w-auto p-1"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onOpenAutoFocus={(event) => event.preventDefault()}
>
<button
type="button"
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-surface-200 text-foreground-light hover:text-foreground whitespace-nowrap"
onClick={handleDismiss}
>
<EyeOff className="w-4 h-4 shrink-0" />
<span>Hide</span>
</button>
</PopoverContent_Shadcn_>
</Popover_Shadcn_>
)
}