Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Restructure feed hover state machine code (#3550)

authored by

dan and committed by
GitHub
8864e9ae 69d37680

+77 -57
+77 -57
src/components/ProfileHoverCard/index.web.tsx
··· 59 59 | 'pressed' 60 60 | 'hovered' 61 61 | 'unhovered' 62 - | 'show-timer-elapsed' 63 - | 'hide-timer-elapsed' 64 - | 'hide-animation-completed' 62 + | 'hovered-long-enough' 63 + | 'unhovered-long-enough' 64 + | 'finished-animating-hide' 65 65 66 66 const SHOW_DELAY = 350 67 67 const SHOW_DURATION = 300 ··· 76 76 const [currentState, dispatch] = React.useReducer( 77 77 // Tip: console.log(state, action) when debugging. 78 78 (state: State, action: Action): State => { 79 - // Regardless of which stage we're in, pressing always hides the card. 79 + // Pressing within a card should always hide it. 80 + // No matter which stage we're in. 80 81 if (action === 'pressed') { 82 + return hidden() 83 + } 84 + 85 + // --- Hidden --- 86 + // In the beginning, the card is not displayed. 87 + function hidden(): State { 81 88 return {stage: 'hidden'} 82 89 } 83 90 91 + // The user can kick things off by hovering a target. 84 92 if (state.stage === 'hidden') { 85 - // Our story starts when the card is hidden. 86 - // If the user hovers, we kick off a grace period before showing the card. 87 93 if (action === 'hovered') { 88 - return { 89 - stage: 'might-show', 90 - effect() { 91 - const id = setTimeout( 92 - () => dispatch('show-timer-elapsed'), 93 - SHOW_DELAY, 94 - ) 95 - return () => { 96 - clearTimeout(id) 97 - } 98 - }, 99 - } 94 + return mightShow(SHOW_DELAY) 95 + } 96 + } 97 + 98 + // --- Might Show --- 99 + // The card is not visible yet but we're considering showing it. 100 + function mightShow(waitMs: number): State { 101 + return { 102 + stage: 'might-show', 103 + effect() { 104 + const id = setTimeout(() => dispatch('hovered-long-enough'), waitMs) 105 + return () => { 106 + clearTimeout(id) 107 + } 108 + }, 100 109 } 101 110 } 102 111 112 + // We'll make a decision at the end of a grace period timeout. 103 113 if (state.stage === 'might-show') { 104 - // We're in the grace period when we decide whether to show the card. 105 - // At this point, two things can happen. Either the user unhovers, and 106 - // we go back to hidden--or they linger enough that we'll show the card. 107 114 if (action === 'unhovered') { 108 - return {stage: 'hidden'} 115 + return hidden() 109 116 } 110 - if (action === 'show-timer-elapsed') { 111 - return {stage: 'showing'} 117 + if (action === 'hovered-long-enough') { 118 + return showing() 112 119 } 113 120 } 114 121 122 + // --- Showing --- 123 + // The card is beginning to show up and then will remain visible. 124 + function showing(): State { 125 + return {stage: 'showing'} 126 + } 127 + 128 + // If the user moves the pointer away, we'll begin to consider hiding it. 115 129 if (state.stage === 'showing') { 116 - // We're showing the card now. 117 - // If the user unhovers, we'll start a grace period before hiding the card. 118 130 if (action === 'unhovered') { 119 - return { 120 - stage: 'might-hide', 121 - effect() { 122 - const id = setTimeout( 123 - () => dispatch('hide-timer-elapsed'), 124 - HIDE_DELAY, 125 - ) 126 - return () => clearTimeout(id) 127 - }, 128 - } 131 + return mightHide(HIDE_DELAY) 129 132 } 130 133 } 131 134 135 + // --- Might Hide --- 136 + // The user has moved hover away from a visible card. 137 + function mightHide(waitMs: number): State { 138 + return { 139 + stage: 'might-hide', 140 + effect() { 141 + const id = setTimeout( 142 + () => dispatch('unhovered-long-enough'), 143 + waitMs, 144 + ) 145 + return () => clearTimeout(id) 146 + }, 147 + } 148 + } 149 + 150 + // We'll make a decision based on whether it received hover again in time. 132 151 if (state.stage === 'might-hide') { 133 - // We're in the grace period when we decide whether to hide the card. 134 - // At this point, two things can happen. Either the user hovers, and 135 - // we go back to showing it--or they linger enough that we'll start hiding the card. 136 152 if (action === 'hovered') { 137 - return {stage: 'showing'} 153 + return showing() 138 154 } 139 - if (action === 'hide-timer-elapsed') { 140 - return { 141 - stage: 'hiding', 142 - effect() { 143 - const id = setTimeout( 144 - () => dispatch('hide-animation-completed'), 145 - HIDE_DURATION, 146 - ) 147 - return () => clearTimeout(id) 148 - }, 149 - } 155 + if (action === 'unhovered-long-enough') { 156 + return hiding(HIDE_DURATION) 157 + } 158 + } 159 + 160 + // --- Hiding --- 161 + // The user waited enough outside that we're hiding the card. 162 + function hiding(animationDurationMs: number): State { 163 + return { 164 + stage: 'hiding', 165 + effect() { 166 + const id = setTimeout( 167 + () => dispatch('finished-animating-hide'), 168 + animationDurationMs, 169 + ) 170 + return () => clearTimeout(id) 171 + }, 150 172 } 151 173 } 152 174 175 + // While hiding, we don't want to be interrupted by anything else. 176 + // When the animation finishes, we loop back to the initial hidden state. 153 177 if (state.stage === 'hiding') { 154 - // We're currently playing the hiding animation. 155 - // We'll ignore all inputs now and wait for the animation to finish. 156 - // At that point, we'll hide the entire thing, going back to square one. 157 - if (action === 'hide-animation-completed') { 158 - return {stage: 'hidden'} 178 + if (action === 'finished-animating-hide') { 179 + return hidden() 159 180 } 160 181 } 161 182 162 - // Something else happened. Keep calm and carry on. 163 183 return state 164 184 }, 165 185 {stage: 'hidden'},