mirror of
https://github.com/supabase/supabase.git
synced 2026-07-03 00:24:25 +08:00
* feat: more generic observability banner on db report * feat: try popup in corner * feat: tidy up and fix up event as well * feat: add event for dismiss as well * feat: add supplementary link at bottom of reports * fix: sizing of fonts * feat: banner stack approach * fix: isIndexAdvisorAvailable dep * chore: remove unused import for old banner * feat: remove unused isDismissed * chore: remove unused cn * chore: change prio on query perf page * chore: remove unused sendEvent * chore: better useEffect cleanup * chore: remove unused index advisor notice * fix: priority of banner stack * fix: first time loader flickering * chore: lowercase the word Free * feat: add IS_PLATFORM to make sure metrics api banner is scoped to platform * chore: another copy update for observability link * fix: telemetry keys to match styleguide * fix: use the correct way to apply events * feat: add events for index advisor banner too * chore: delete unused old banner * fix: dismiss buttons not working * feat: add extra event to enable index advisor
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { useState } from 'react'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import { useBannerStack } from './BannerStackProvider'
|
|
import { cn } from 'ui'
|
|
|
|
export const BannerStack = () => {
|
|
const { banners } = useBannerStack()
|
|
const [isHovered, setIsHovered] = useState(false)
|
|
|
|
const activeBanners = banners.filter((b) => !b.isDismissed)
|
|
|
|
const PEEK_HEIGHT = 4
|
|
const CARD_GAP = 4
|
|
const CARD_HEIGHT = 212
|
|
|
|
if (activeBanners.length === 0) return null
|
|
|
|
return (
|
|
<motion.div
|
|
className="fixed bottom-4 right-4 z-50"
|
|
onMouseEnter={() => setIsHovered(true)}
|
|
onMouseLeave={() => setIsHovered(false)}
|
|
animate={{
|
|
y: isHovered ? -8 : 0,
|
|
}}
|
|
transition={{
|
|
type: 'spring',
|
|
stiffness: 300,
|
|
damping: 25,
|
|
}}
|
|
>
|
|
<div className="relative">
|
|
<AnimatePresence mode="popLayout">
|
|
{activeBanners.map((banner, index) => {
|
|
const isBottomBanner = index === 0
|
|
const reverseIndex = activeBanners.length - 1 - index
|
|
const collapsedY = index * PEEK_HEIGHT
|
|
const expandedY = index * (CARD_HEIGHT + CARD_GAP)
|
|
|
|
return (
|
|
<motion.div
|
|
key={banner.id}
|
|
initial={{ opacity: 0, scale: 0.99, y: 8 }}
|
|
animate={{
|
|
opacity: 1,
|
|
scale: isHovered ? 1 : 1 - index * 0.07,
|
|
x: 0,
|
|
y: isHovered ? -expandedY : -collapsedY,
|
|
}}
|
|
exit={{ opacity: 0, scale: 0.99, y: 8 }}
|
|
transition={{
|
|
type: 'spring',
|
|
stiffness: 300,
|
|
damping: 30,
|
|
delay: 0.25,
|
|
}}
|
|
onMouseEnter={() => setIsHovered(true)}
|
|
onMouseLeave={() => setIsHovered(false)}
|
|
style={{
|
|
position: isBottomBanner ? 'relative' : 'absolute',
|
|
bottom: isBottomBanner ? undefined : 0,
|
|
right: isBottomBanner ? undefined : 0,
|
|
zIndex: 30 + reverseIndex,
|
|
transformOrigin: 'center bottom',
|
|
}}
|
|
className={cn(
|
|
'w-full max-w-72',
|
|
!isHovered && index === 0 && 'pointer-events-none'
|
|
)}
|
|
>
|
|
{banner.content}
|
|
</motion.div>
|
|
)
|
|
})}
|
|
</AnimatePresence>
|
|
</div>
|
|
</motion.div>
|
|
)
|
|
}
|