Files
supabase/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx
kemal.earth ce48c64f68 feat(studio): generic observability banner (#40957)
* 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
2025-12-04 11:42:31 +00:00

50 lines
1.4 KiB
TypeScript

import { createContext, useContext, useState, useCallback } from 'react'
export interface Banner {
id: string
content: React.ReactNode
isDismissed: boolean
priority?: number
onDismiss?: () => void
}
interface BannerStackContextType {
banners: Banner[]
addBanner: (banner: Banner) => void
dismissBanner: (id: string) => void
}
const BannerStackContext = createContext<BannerStackContextType | undefined>(undefined)
export const BannerStackProvider = ({ children }: { children: React.ReactNode }) => {
const [banners, setBanners] = useState<Banner[]>([])
const addBanner = useCallback((banner: Banner) => {
setBanners((prev) => {
const exists = prev.some((b) => b.id === banner.id)
if (exists) return prev
const newBanners = [...prev, banner]
return newBanners.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))
})
}, [])
const dismissBanner = useCallback((id: string) => {
setBanners((prev) => prev.map((b) => (b.id === id ? { ...b, isDismissed: true } : b)))
setTimeout(() => {
setBanners((prev) => prev.filter((b) => b.id !== id))
}, 300)
}, [])
return (
<BannerStackContext.Provider value={{ banners, addBanner, dismissBanner }}>
{children}
</BannerStackContext.Provider>
)
}
export const useBannerStack = () => {
const context = useContext(BannerStackContext)
if (!context) throw new Error('useBannerStack must be used within BannerStackProvider')
return context
}