useChartHoverState.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { useCallback, useEffect, useState } from 'react'
  2. interface ChartHoverState {
  3. hoveredIndex: number | null
  4. hoveredChart: string | null
  5. syncHover: boolean
  6. syncTooltip: boolean
  7. }
  8. const CHART_HOVER_SYNC_STORAGE_KEY = 'briven-chart-hover-sync-enabled'
  9. const CHART_TOOLTIP_SYNC_STORAGE_KEY = 'briven-chart-tooltip-sync-enabled'
  10. // Global state shared across all hook instances
  11. let globalState: ChartHoverState = {
  12. hoveredIndex: null,
  13. hoveredChart: null,
  14. syncHover: true,
  15. syncTooltip: true,
  16. }
  17. // Subscribers for state changes
  18. const subscribers = new Set<(state: ChartHoverState) => void>()
  19. // Load initial sync settings from localStorage
  20. try {
  21. if (typeof window !== 'undefined') {
  22. const hoverSyncStored = localStorage.getItem(CHART_HOVER_SYNC_STORAGE_KEY)
  23. const tooltipSyncStored = localStorage.getItem(CHART_TOOLTIP_SYNC_STORAGE_KEY)
  24. if (hoverSyncStored !== null) {
  25. globalState.syncHover = JSON.parse(hoverSyncStored)
  26. }
  27. if (tooltipSyncStored !== null) {
  28. globalState.syncTooltip = JSON.parse(tooltipSyncStored)
  29. }
  30. }
  31. } catch (error) {
  32. console.warn('Failed to load chart sync settings from localStorage:', error)
  33. }
  34. function notifySubscribers() {
  35. subscribers.forEach((callback) => callback(globalState))
  36. }
  37. function updateGlobalState(updates: Partial<ChartHoverState>) {
  38. const prevState = globalState
  39. globalState = { ...globalState, ...updates }
  40. // Save sync settings to localStorage when they change
  41. if (updates.syncHover !== undefined) {
  42. try {
  43. localStorage.setItem(CHART_HOVER_SYNC_STORAGE_KEY, JSON.stringify(globalState.syncHover))
  44. } catch (error) {
  45. console.warn('Failed to save chart hover sync setting to localStorage:', error)
  46. }
  47. }
  48. if (updates.syncTooltip !== undefined) {
  49. try {
  50. localStorage.setItem(CHART_TOOLTIP_SYNC_STORAGE_KEY, JSON.stringify(globalState.syncTooltip))
  51. } catch (error) {
  52. console.warn('Failed to save chart tooltip sync setting to localStorage:', error)
  53. }
  54. }
  55. // Only notify if state actually changed
  56. if (JSON.stringify(prevState) !== JSON.stringify(globalState)) {
  57. notifySubscribers()
  58. }
  59. }
  60. export function useChartHoverState(chartId: string) {
  61. const [state, setState] = useState<ChartHoverState>(globalState)
  62. // Subscribe to global state changes
  63. useEffect(() => {
  64. const callback = (newState: ChartHoverState) => {
  65. setState(newState)
  66. }
  67. subscribers.add(callback)
  68. return () => {
  69. subscribers.delete(callback)
  70. }
  71. }, [])
  72. // Set hover state for this chart
  73. const setHover = useCallback(
  74. (index: number | null) => {
  75. if (globalState.syncHover) {
  76. // If sync is enabled, update global state
  77. updateGlobalState({
  78. hoveredIndex: index,
  79. hoveredChart: index !== null ? chartId : null,
  80. })
  81. } else {
  82. // If sync is disabled, only update local state
  83. setState((prev) => ({
  84. ...prev,
  85. hoveredIndex: index,
  86. hoveredChart: index !== null ? chartId : null,
  87. }))
  88. }
  89. },
  90. [chartId]
  91. )
  92. // Clear hover state
  93. const clearHover = useCallback(() => {
  94. if (globalState.syncHover) {
  95. updateGlobalState({
  96. hoveredIndex: null,
  97. hoveredChart: null,
  98. })
  99. } else {
  100. setState((prev) => ({
  101. ...prev,
  102. hoveredIndex: null,
  103. hoveredChart: null,
  104. }))
  105. }
  106. }, [])
  107. // Set sync settings (for settings component)
  108. const setSyncHover = useCallback((enabled: boolean) => {
  109. updateGlobalState({
  110. syncHover: enabled,
  111. // If turning off hover sync, also turn off tooltip sync
  112. ...(enabled === false && { syncTooltip: false }),
  113. })
  114. }, [])
  115. const setSyncTooltip = useCallback((enabled: boolean) => {
  116. updateGlobalState({
  117. syncTooltip: enabled,
  118. // If turning on tooltip sync, also turn on hover sync
  119. ...(enabled === true && { syncHover: true }),
  120. })
  121. }, [])
  122. // Determine if this chart should show synced state
  123. const isCurrentChart = state.hoveredChart === chartId
  124. const shouldShowSyncedState = state.syncHover && state.hoveredChart !== null && !isCurrentChart
  125. return {
  126. // Current state
  127. hoveredIndex: shouldShowSyncedState
  128. ? state.hoveredIndex
  129. : isCurrentChart
  130. ? state.hoveredIndex
  131. : null,
  132. syncHover: state.syncHover,
  133. syncTooltip: state.syncTooltip,
  134. hoveredChart: state.hoveredChart,
  135. // Actions
  136. setHover,
  137. clearHover,
  138. setSyncHover,
  139. setSyncTooltip,
  140. // Helpers
  141. isHovered: state.hoveredIndex !== null && (isCurrentChart || shouldShowSyncedState),
  142. isCurrentChart,
  143. }
  144. }