@sanity/ui
Version:
The Sanity UI components.
103 lines (80 loc) • 2.94 kB
text/typescript
import {useMemo, useSyncExternalStore} from 'react'
import {useTheme_v2} from '../../theme'
/**
* @internal
*/
export interface _MediaStore {
subscribe: (onStoreChange: () => void) => () => void
getSnapshot: () => number
}
type MediaQueryMinWidth = `(min-width: ${number}px)`
type MediaQueryMaxWidth = `(max-width: ${number}px)`
type MediaQueryMinMaxWidth = `${MediaQueryMinWidth} and ${MediaQueryMaxWidth}`
type MediaQuery = `screen and ${MediaQueryMinWidth | MediaQueryMaxWidth | MediaQueryMinMaxWidth}`
function _getMediaQuery(media: number[], index: number): MediaQuery {
if (index === 0) {
return `screen and (max-width: ${media[index] - 1}px)`
}
if (index === media.length) {
return `screen and (min-width: ${media[index - 1]}px)`
}
return `screen and (min-width: ${media[index - 1]}px) and (max-width: ${media[index] - 1}px)`
}
function _createMediaStore(media: number[]): _MediaStore {
const mediaLen = media.length
let sizes: {mq: MediaQueryList; index: number}[]
// The _createMediaStore function is called in both server and client environments.
// However since subscribe and getSnapshot are only called on the client we lazy init what we need for them
// so that we don't need to run checks for wether it's safe to call `window.matchMedia`
const getSizes = () => {
if (!sizes) {
sizes = []
for (let index = mediaLen; index > -1; index -= 1) {
const mediaQuery = _getMediaQuery(media, index)
sizes.push({index, mq: window.matchMedia(mediaQuery)})
}
}
return sizes
}
const getSnapshot = () => {
for (const {index, mq} of getSizes()) {
if (mq.matches) return index
}
return 0
}
const subscribe = (onStoreChange: () => void) => {
const disposeFns: (() => void)[] = []
for (const {mq} of getSizes()) {
const handleChange = () => {
if (mq.matches) onStoreChange()
}
mq.addEventListener('change', handleChange)
disposeFns.push(() => mq.removeEventListener('change', handleChange))
}
return () => {
for (const disposeFn of disposeFns) {
disposeFn()
}
}
}
return {getSnapshot, subscribe}
}
/**
* Only called during server-side rendering, and hydration if using hydrateRoot
* Since the server environment doesn't have access to the DOM, we can't determine the current value of the media query
* and we assume `(prefers-color-scheme: light)` since it's the most common scheme
*
* @link https://beta.reactjs.org/apis/react/useSyncExternalStore#adding-support-for-server-rendering
*/
function getServerSnapshot() {
return 0
}
/**
* This API might change. DO NOT USE IN PRODUCTION.
* @beta
*/
export function useMediaIndex(): number {
const {media} = useTheme_v2()
const store = useMemo(() => _createMediaStore(media), [media])
return useSyncExternalStore(store.subscribe, store.getSnapshot, getServerSnapshot)
}