Skip to content

Commit 61f12cc

Browse files
committed
enhance(carousel): Close carousel on browser back navigation
1 parent a3c5a33 commit 61f12cc

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

components/carousel.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,20 +114,56 @@ function CarouselOverflow ({ originalSrc, rel }) {
114114
export function CarouselProvider ({ children }) {
115115
const media = useRef(new Map())
116116
const showModal = useShowModal()
117+
const modalCloseRef = useRef(null)
118+
const [confirmedCount, setConfirmedCount] = useState(0)
117119

118120
const showCarousel = useCallback(({ src }) => {
119121
// only show confirmed entries
120122
const confirmedEntries = Array.from(media.current.entries())
121123
.filter(([, entry]) => entry.confirmed)
122124

123125
showModal((close, setOptions) => {
126+
modalCloseRef.current = close
124127
return <Carousel close={close} mediaArr={confirmedEntries} src={src} setOptions={setOptions} />
125128
}, {
126129
fullScreen: true,
127-
overflow: <CarouselOverflow {...media.current.get(src)} />
130+
overflow: <CarouselOverflow {...media.current.get(src)} />,
131+
hash: 'carousel',
132+
onClose: () => {
133+
modalCloseRef.current = null
134+
}
128135
})
129136
}, [showModal])
130137

138+
// Handle initial hash-based carousel opening with proper SSR safety
139+
useEffect(() => {
140+
// Ensure we're in browser environment
141+
if (typeof window === 'undefined') return
142+
143+
const handleHashCarousel = () => {
144+
if (window.location.hash === '#carousel') {
145+
const confirmedEntries = Array.from(media.current.entries())
146+
.filter(([, entry]) => entry.confirmed)
147+
148+
if (confirmedEntries.length > 0) {
149+
showCarousel({ src: confirmedEntries[0][0] })
150+
} else {
151+
// Remove hash if no confirmed media exists
152+
window.history.replaceState(
153+
window.history.state,
154+
'',
155+
window.location.pathname + window.location.search
156+
)
157+
}
158+
}
159+
}
160+
161+
// Only run on mount if hash is present
162+
if (window.location.hash === '#carousel') {
163+
handleHashCarousel()
164+
}
165+
}, [showCarousel, confirmedCount])
166+
131167
const addMedia = useCallback(({ src, originalSrc, rel }) => {
132168
media.current.set(src, { src, originalSrc, rel, confirmed: false })
133169
}, [])
@@ -137,11 +173,29 @@ export function CarouselProvider ({ children }) {
137173
if (mediaItem) {
138174
mediaItem.confirmed = true
139175
media.current.set(src, mediaItem)
176+
177+
const newConfirmedCount = Array.from(media.current.values())
178+
.filter(entry => entry.confirmed).length
179+
setConfirmedCount(newConfirmedCount)
180+
181+
// Handle hash-based carousel opening when first media is confirmed
182+
if (typeof window !== 'undefined' && window.location.hash === '#carousel') {
183+
if (newConfirmedCount === 1) {
184+
showCarousel({ src })
185+
}
186+
}
140187
}
141-
}, [])
188+
}, [showCarousel])
142189

143190
const removeMedia = useCallback((src) => {
191+
const wasConfirmed = media.current.get(src)?.confirmed
144192
media.current.delete(src)
193+
194+
if (wasConfirmed) {
195+
const newConfirmedCount = Array.from(media.current.values())
196+
.filter(entry => entry.confirmed).length
197+
setConfirmedCount(newConfirmedCount)
198+
}
145199
}, [])
146200

147201
const value = useMemo(

components/modal.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,22 @@ export default function useModal () {
5959
return
6060
}
6161

62+
const currentHash = window.location.hash
63+
if (currentHash) {
64+
const hasModalWithHash = modalStack.current.some(
65+
modal => modal.options?.hash && `#${modal.options.hash}` === currentHash
66+
)
67+
if (hasModalWithHash) {
68+
window.history.replaceState(window.history.state, '', window.location.pathname + window.location.search)
69+
}
70+
}
71+
6272
while (modalStack.current.length) {
6373
getCurrentContent()?.options?.onClose?.()
6474
modalStack.current.pop()
6575
}
6676
forceUpdate()
67-
}, [onBack])
77+
}, [onBack, getCurrentContent])
6878

6979
const router = useRouter()
7080
useEffect(() => {
@@ -80,6 +90,21 @@ export default function useModal () {
8090
return () => router.events.off('routeChangeStart', maybeOnClose)
8191
}, [router.events, onClose, getCurrentContent])
8292

93+
useEffect(() => {
94+
const handleHashChange = () => {
95+
const currentContent = getCurrentContent()
96+
if (!currentContent?.options?.hash) return
97+
98+
const expectedHash = `#${currentContent.options.hash}`
99+
if (window.location.hash !== expectedHash) {
100+
onClose()
101+
}
102+
}
103+
104+
window.addEventListener('hashchange', handleHashChange)
105+
return () => window.removeEventListener('hashchange', handleHashChange)
106+
}, [onClose, getCurrentContent])
107+
83108
const modal = useMemo(() => {
84109
if (modalStack.current.length === 0) {
85110
return null
@@ -116,6 +141,12 @@ export default function useModal () {
116141
const showModal = useCallback(
117142
(getContent, options) => {
118143
document.activeElement?.blur()
144+
if (options?.hash) {
145+
const newHash = `#${options.hash}`
146+
if (window.location.hash !== newHash) {
147+
window.history.pushState(window.history.state, '', newHash)
148+
}
149+
}
119150
const ref = { node: getContent(onClose, setOptions), options }
120151
if (options?.replaceModal) {
121152
modalStack.current = [ref]
@@ -124,7 +155,7 @@ export default function useModal () {
124155
}
125156
forceUpdate()
126157
},
127-
[onClose]
158+
[onClose, setOptions]
128159
)
129160

130161
return [modal, showModal]

0 commit comments

Comments
 (0)