BannerStackProvider.tsx 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. import { createContext, useCallback, useContext, useState } from 'react'
  2. export const BANNER_ID = {
  3. METRICS_API: 'metrics-api-banner',
  4. INDEX_ADVISOR: 'index-advisor-banner',
  5. TABLE_EDITOR_QUEUE_OPERATIONS: 'table-editor-queue-operations-banner',
  6. RLS_EVENT_TRIGGER: 'rls-event-trigger-banner',
  7. RLS_TESTER: 'rls-tester-banner',
  8. FREE_MICRO_UPGRADE: 'free-micro-upgrade-banner',
  9. } as const
  10. export type BannerId = (typeof BANNER_ID)[keyof typeof BANNER_ID]
  11. export interface Banner {
  12. id: BannerId
  13. content: React.ReactNode
  14. isDismissed: boolean
  15. priority?: number
  16. onDismiss?: () => void
  17. }
  18. interface BannerStackContextType {
  19. banners: Banner[]
  20. addBanner: (banner: Banner) => void
  21. dismissBanner: (id: BannerId) => void
  22. }
  23. const BannerStackContext = createContext<BannerStackContextType | undefined>(undefined)
  24. export const BannerStackProvider = ({ children }: { children: React.ReactNode }) => {
  25. const [banners, setBanners] = useState<Banner[]>([])
  26. const addBanner = useCallback((banner: Banner) => {
  27. setBanners((prev) => {
  28. const exists = prev.some((b) => b.id === banner.id)
  29. if (exists) return prev
  30. const newBanners = [...prev, banner]
  31. return newBanners.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))
  32. })
  33. }, [])
  34. const dismissBanner = useCallback((id: string) => {
  35. setBanners((prev) => prev.map((b) => (b.id === id ? { ...b, isDismissed: true } : b)))
  36. setTimeout(() => {
  37. setBanners((prev) => prev.filter((b) => b.id !== id))
  38. }, 300)
  39. }, [])
  40. return (
  41. <BannerStackContext.Provider value={{ banners, addBanner, dismissBanner }}>
  42. {children}
  43. </BannerStackContext.Provider>
  44. )
  45. }
  46. export const useBannerStack = () => {
  47. const context = useContext(BannerStackContext)
  48. if (!context) throw new Error('useBannerStack must be used within BannerStackProvider')
  49. return context
  50. }