UseMediaQuery

This commit is contained in:
Mark Eibes 2022-07-09 13:09:36 +02:00
parent 5b149c0fe9
commit e3df4432ca
8 changed files with 91 additions and 43 deletions

View File

@ -1,6 +1,6 @@
{
"editor.formatOnSave": true,
"workbench.colorTheme": "Halcyon",
"workbench.colorTheme": "Night Owl Light",
"search.useGlobalIgnoreFiles": true,
"files.watcherExclude": {
"**/.spago/**": true,
@ -9,4 +9,4 @@
"**/.storybook/**": true,
"**/.github/**": true
}
}
}

24
src/MediaQuery.js Normal file
View File

@ -0,0 +1,24 @@
// This is for the browser tests to work
// JSDOM does not have support for matchMedia
// So we just pretend nothing every matches
// And never invoke the listeners
const stubMatchMedia = () => {
if (window.matchMedia) return
window.matchMedia = (name) => ({
matches: false,
addEventListener: () => {},
removeEventListener: () => {}
})
}
export function matchMedia(string) {
return (window) => () => {
stubMatchMedia()
return window.matchMedia(string)
}
}
export function matches(matchMedia) {
return () => {
stubMatchMedia()
return matchMedia.matches
}
}

9
src/MediaQuery.purs Normal file
View File

@ -0,0 +1,9 @@
module MediaQuery where
import Effect (Effect)
import MediaQuery.Types (MediaQueryList)
import Web.HTML (Window)
foreign import matchMedia ∷ String → Window → Effect MediaQueryList
foreign import matches ∷ MediaQueryList → Effect Boolean

View File

@ -0,0 +1,9 @@
module MediaQuery.Types where
import Unsafe.Coerce (unsafeCoerce)
import Web.Event.EventTarget (EventTarget)
foreign import data MediaQueryList ∷ Type
toEventTarget ∷ MediaQueryList → EventTarget
toEventTarget = unsafeCoerce

View File

@ -1,15 +1,3 @@
// This is for the browser tests to work
// JSDOM does not have support for matchMedia
// So we just pretend nothing every matches
// And never invoke the listeners
const stubMatchMedia = () => {
if (window.matchMedia) return
window.matchMedia = (name) => ({
matches: false,
addEventListener: () => {},
removeEventListener: () => {}
})
}
export function getComputedStyleImpl(el, window) {
return window.getComputedStyle(el)
@ -23,15 +11,3 @@ export function getElementStyle(el) {
export function setStyleProperty(prop) {
return (value) => (style) => () => style.setProperty(prop, value)
}
export function matchMedia(string) {
return (window) => () => {
stubMatchMedia()
return window.matchMedia(string)
}
}
export function matches(matchMedia) {
return () => {
stubMatchMedia()
return matchMedia.matches
}
}

View File

@ -58,15 +58,6 @@ foreign import getElementStyle ∷ Element → Effect ElementStyle
foreign import setStyleProperty ∷ String → String → ElementStyle → Effect Unit
foreign import data MediaQueryList ∷ Type
foreign import matchMedia ∷ String → Window → Effect MediaQueryList
foreign import matches ∷ MediaQueryList → Effect Boolean
toEventTarget ∷ MediaQueryList → EventTarget
toEventTarget = unsafeCoerce
getDocumentElement ∷ MaybeT Effect Element
getDocumentElement = do
win ← window # lift

View File

@ -10,13 +10,16 @@ import Yoga.Prelude.View
import Data.Array as Array
import Fahrtwind as F
import MediaQuery (matchMedia, matches)
import MediaQuery.Types (MediaQueryList)
import MediaQuery.Types as MediaQueryList
import React.Basic.DOM as R
import React.Basic.Emotion as E
import React.Basic.Hooks as React
import Web.Event.EventTarget (addEventListener, eventListener, removeEventListener)
import Web.HTML (window)
import Web.HTML.Event.EventTypes as Event
import Yoga.Block.Container.Style (DarkOrLightMode(..), matchMedia, matches, setDarkOrLightMode)
import Yoga.Block.Container.Style (DarkOrLightMode(..), setDarkOrLightMode)
import Yoga.Block.Container.Style as Styles
type PropsF f =
@ -32,10 +35,10 @@ type Props =
component ∷ ∀ p q. Union p q Props => ReactComponent { | p }
component = rawComponent
mkPrefersDark ∷ Effect Styles.MediaQueryList
mkPrefersDark ∷ Effect MediaQueryList
mkPrefersDark = matchMedia "(prefers-color-scheme: dark)" =<< window
mkPrefersLight ∷ Effect Styles.MediaQueryList
mkPrefersLight ∷ Effect MediaQueryList
mkPrefersLight = matchMedia "(prefers-color-scheme: light)" =<< window
rawComponent ∷ ∀ p. ReactComponent { | p }
@ -64,18 +67,18 @@ rawComponent =
whenM (matches prefersDarkMediaQuery) do
setSystemThemeVariant (Just DarkMode)
notifySystemThemeChanged DarkMode
addEventListener Event.change darkModeListener true (Styles.toEventTarget prefersDarkMediaQuery)
addEventListener Event.change darkModeListener true (MediaQueryList.toEventTarget prefersDarkMediaQuery)
-- Light Mode listener
lightModeListener <-
eventListener \_ -> do
whenM (matches prefersLightMediaQuery) do
setSystemThemeVariant (Just LightMode)
notifySystemThemeChanged LightMode
addEventListener Event.change darkModeListener true (Styles.toEventTarget prefersDarkMediaQuery)
addEventListener Event.change lightModeListener true (Styles.toEventTarget prefersLightMediaQuery)
addEventListener Event.change darkModeListener true (MediaQueryList.toEventTarget prefersDarkMediaQuery)
addEventListener Event.change lightModeListener true (MediaQueryList.toEventTarget prefersLightMediaQuery)
pure do
removeEventListener Event.change darkModeListener true (Styles.toEventTarget prefersDarkMediaQuery)
removeEventListener Event.change lightModeListener true (Styles.toEventTarget prefersLightMediaQuery)
removeEventListener Event.change darkModeListener true (MediaQueryList.toEventTarget prefersDarkMediaQuery)
removeEventListener Event.change lightModeListener true (MediaQueryList.toEventTarget prefersLightMediaQuery)
pure
$ fragment
[ R.div' </ { ref }

View File

@ -0,0 +1,36 @@
module Yoga.Block.Hook.UseMediaQuery where
import Prelude
import Data.Newtype (class Newtype)
import Data.Tuple.Nested ((/\))
import Effect.Unsafe (unsafePerformEffect)
import MediaQuery (matchMedia, matches)
import MediaQuery.Types as MQL
import React.Basic.Hooks (UseEffect, UseState, Hook, coerceHook)
import React.Basic.Hooks as React
import Web.Event.EventTarget (addEventListener, eventListener, removeEventListener)
import Web.HTML (window)
import Web.HTML.Event.EventTypes as Event
newtype UseMediaQuery hooks = UseMediaQuery
(UseEffect String (UseState Boolean hooks))
derive instance Newtype (UseMediaQuery hooks) _
useMediaQuery :: String -> Hook UseMediaQuery Boolean
useMediaQuery query = coerceHook React.do
let check = window >>= matchMedia query >>= matches
queryMatches /\ setMatches <-
React.useState' (unsafePerformEffect check)
React.useEffect query do
queryList <- window >>= matchMedia query
let target = MQL.toEventTarget queryList
listener <- eventListener (const (check >>= setMatches))
addEventListener Event.change listener true target
pure do
removeEventListener Event.change listener true target
pure queryMatches