git.iko.soy/rowtype-yoga/ry-blocks
on main help sign in
UseMediaQuery
e3df443Mark Eibes
.vscode/settings.json+2−2
@@ -1,6 +1,6 @@
1{
1{
2 "editor.formatOnSave": true,
2 "editor.formatOnSave": true,
3− "workbench.colorTheme": "Halcyon",
3+ "workbench.colorTheme": "Night Owl Light",
4 "search.useGlobalIgnoreFiles": true,
4 "search.useGlobalIgnoreFiles": true,
5 "files.watcherExclude": {
5 "files.watcherExclude": {
6 "**/.spago/**": true,
6 "**/.spago/**": true,
@@ -9,4 +9,4 @@
9 "**/.storybook/**": true,
9 "**/.storybook/**": true,
10 "**/.github/**": true
10 "**/.github/**": true
11 }
11 }
12−}
12+}
src/MediaQuery.jsnew file+24
@@ -0,0 +1,24 @@
1+// This is for the browser tests to work
2+// JSDOM does not have support for matchMedia
3+// So we just pretend nothing every matches
4+// And never invoke the listeners
5+const stubMatchMedia = () => {
6+ if (window.matchMedia) return
7+ window.matchMedia = (name) => ({
8+ matches: false,
9+ addEventListener: () => {},
10+ removeEventListener: () => {}
11+ })
12+}
13+export function matchMedia(string) {
14+ return (window) => () => {
15+ stubMatchMedia()
16+ return window.matchMedia(string)
17+ }
18+}
19+export function matches(matchMedia) {
20+ return () => {
21+ stubMatchMedia()
22+ return matchMedia.matches
23+ }
24+}
src/MediaQuery.pursnew file+9
@@ -0,0 +1,9 @@
1+module MediaQuery where
2+
3+import Effect (Effect)
4+import MediaQuery.Types (MediaQueryList)
5+import Web.HTML (Window)
6+
7+foreign import matchMedia ∷ String → Window → Effect MediaQueryList
8+
9+foreign import matches ∷ MediaQueryList → Effect Boolean
src/MediaQuery/Types.pursnew file+9
@@ -0,0 +1,9 @@
1+module MediaQuery.Types where
2+
3+import Unsafe.Coerce (unsafeCoerce)
4+import Web.Event.EventTarget (EventTarget)
5+
6+foreign import data MediaQueryList ∷ Type
7+
8+toEventTarget ∷ MediaQueryList → EventTarget
9+toEventTarget = unsafeCoerce
src/Yoga/Block/Container/Style.js−24
@@ -1,15 +1,3 @@
1−// This is for the browser tests to work
2−// JSDOM does not have support for matchMedia
3−// So we just pretend nothing every matches
4−// And never invoke the listeners
5−const stubMatchMedia = () => {
6− if (window.matchMedia) return
7− window.matchMedia = (name) => ({
8− matches: false,
9− addEventListener: () => {},
10− removeEventListener: () => {}
11− })
12−}
13
1
14export function getComputedStyleImpl(el, window) {
2export function getComputedStyleImpl(el, window) {
15 return window.getComputedStyle(el)
3 return window.getComputedStyle(el)
@@ -23,15 +11,3 @@ export function getElementStyle(el) {
23export function setStyleProperty(prop) {
11export function setStyleProperty(prop) {
24 return (value) => (style) => () => style.setProperty(prop, value)
12 return (value) => (style) => () => style.setProperty(prop, value)
25}
13}
26−export function matchMedia(string) {
27− return (window) => () => {
28− stubMatchMedia()
29− return window.matchMedia(string)
30− }
31−}
32−export function matches(matchMedia) {
33− return () => {
34− stubMatchMedia()
35− return matchMedia.matches
36− }
37−}
src/Yoga/Block/Container/Style.purs−9
@@ -58,15 +58,6 @@ foreign import getElementStyle ∷ Element → Effect ElementStyle
58
58
59foreign import setStyleProperty ∷ String → String → ElementStyle → Effect Unit
59foreign import setStyleProperty ∷ String → String → ElementStyle → Effect Unit
60
60
61−foreign import data MediaQueryList ∷ Type
62−
63−foreign import matchMedia ∷ String → Window → Effect MediaQueryList
64−
65−foreign import matches ∷ MediaQueryList → Effect Boolean
66−
67−toEventTarget ∷ MediaQueryList → EventTarget
68−toEventTarget = unsafeCoerce
69−
70getDocumentElement ∷ MaybeT Effect Element
61getDocumentElement ∷ MaybeT Effect Element
71getDocumentElement = do
62getDocumentElement = do
72 win ← window # lift
63 win ← window # lift
src/Yoga/Block/Container/View.purs+11−8
@@ -10,13 +10,16 @@ import Yoga.Prelude.View
10
10
11import Data.Array as Array
11import Data.Array as Array
12import Fahrtwind as F
12import Fahrtwind as F
13+import MediaQuery (matchMedia, matches)
14+import MediaQuery.Types (MediaQueryList)
15+import MediaQuery.Types as MediaQueryList
13import React.Basic.DOM as R
16import React.Basic.DOM as R
14import React.Basic.Emotion as E
17import React.Basic.Emotion as E
15import React.Basic.Hooks as React
18import React.Basic.Hooks as React
16import Web.Event.EventTarget (addEventListener, eventListener, removeEventListener)
19import Web.Event.EventTarget (addEventListener, eventListener, removeEventListener)
17import Web.HTML (window)
20import Web.HTML (window)
18import Web.HTML.Event.EventTypes as Event
21import Web.HTML.Event.EventTypes as Event
19−import Yoga.Block.Container.Style (DarkOrLightMode(..), matchMedia, matches, setDarkOrLightMode)
22+import Yoga.Block.Container.Style (DarkOrLightMode(..), setDarkOrLightMode)
20import Yoga.Block.Container.Style as Styles
23import Yoga.Block.Container.Style as Styles
21
24
22type PropsF f =
25type PropsF f =
@@ -32,10 +35,10 @@ type Props =
32component ∷ ∀ p q. Union p q Props => ReactComponent { | p }
35component ∷ ∀ p q. Union p q Props => ReactComponent { | p }
33component = rawComponent
36component = rawComponent
34
37
35−mkPrefersDark ∷ Effect Styles.MediaQueryList
38+mkPrefersDark ∷ Effect MediaQueryList
36mkPrefersDark = matchMedia "(prefers-color-scheme: dark)" =<< window
39mkPrefersDark = matchMedia "(prefers-color-scheme: dark)" =<< window
37
40
38−mkPrefersLight ∷ Effect Styles.MediaQueryList
41+mkPrefersLight ∷ Effect MediaQueryList
39mkPrefersLight = matchMedia "(prefers-color-scheme: light)" =<< window
42mkPrefersLight = matchMedia "(prefers-color-scheme: light)" =<< window
40
43
41rawComponent ∷ ∀ p. ReactComponent { | p }
44rawComponent ∷ ∀ p. ReactComponent { | p }
@@ -64,18 +67,18 @@ rawComponent =
64 whenM (matches prefersDarkMediaQuery) do
67 whenM (matches prefersDarkMediaQuery) do
65 setSystemThemeVariant (Just DarkMode)
68 setSystemThemeVariant (Just DarkMode)
66 notifySystemThemeChanged DarkMode
69 notifySystemThemeChanged DarkMode
67− addEventListener Event.change darkModeListener true (Styles.toEventTarget prefersDarkMediaQuery)
70+ addEventListener Event.change darkModeListener true (MediaQueryList.toEventTarget prefersDarkMediaQuery)
68 -- Light Mode listener
71 -- Light Mode listener
69 lightModeListener <-
72 lightModeListener <-
70 eventListener \_ -> do
73 eventListener \_ -> do
71 whenM (matches prefersLightMediaQuery) do
74 whenM (matches prefersLightMediaQuery) do
72 setSystemThemeVariant (Just LightMode)
75 setSystemThemeVariant (Just LightMode)
73 notifySystemThemeChanged LightMode
76 notifySystemThemeChanged LightMode
74− addEventListener Event.change darkModeListener true (Styles.toEventTarget prefersDarkMediaQuery)
77+ addEventListener Event.change darkModeListener true (MediaQueryList.toEventTarget prefersDarkMediaQuery)
75− addEventListener Event.change lightModeListener true (Styles.toEventTarget prefersLightMediaQuery)
78+ addEventListener Event.change lightModeListener true (MediaQueryList.toEventTarget prefersLightMediaQuery)
76 pure do
79 pure do
77− removeEventListener Event.change darkModeListener true (Styles.toEventTarget prefersDarkMediaQuery)
80+ removeEventListener Event.change darkModeListener true (MediaQueryList.toEventTarget prefersDarkMediaQuery)
78− removeEventListener Event.change lightModeListener true (Styles.toEventTarget prefersLightMediaQuery)
81+ removeEventListener Event.change lightModeListener true (MediaQueryList.toEventTarget prefersLightMediaQuery)
79 pure
82 pure
80 $ fragment
83 $ fragment
81 [ R.div' </ { ref }
84 [ R.div' </ { ref }
src/Yoga/Block/Hook/UseMediaQuery.pursnew file+36
@@ -0,0 +1,36 @@
1+module Yoga.Block.Hook.UseMediaQuery where
2+
3+import Prelude
4+
5+import Data.Newtype (class Newtype)
6+import Data.Tuple.Nested ((/\))
7+import Effect.Unsafe (unsafePerformEffect)
8+import MediaQuery (matchMedia, matches)
9+import MediaQuery.Types as MQL
10+import React.Basic.Hooks (UseEffect, UseState, Hook, coerceHook)
11+import React.Basic.Hooks as React
12+import Web.Event.EventTarget (addEventListener, eventListener, removeEventListener)
13+import Web.HTML (window)
14+import Web.HTML.Event.EventTypes as Event
15+
16+newtype UseMediaQuery hooks = UseMediaQuery
17+ (UseEffect String (UseState Boolean hooks))
18+
19+derive instance Newtype (UseMediaQuery hooks) _
20+
21+useMediaQuery :: String -> Hook UseMediaQuery Boolean
22+useMediaQuery query = coerceHook React.do
23+ let check = window >>= matchMedia query >>= matches
24+
25+ queryMatches /\ setMatches <-
26+ React.useState' (unsafePerformEffect check)
27+
28+ React.useEffect query do
29+ queryList <- window >>= matchMedia query
30+ let target = MQL.toEventTarget queryList
31+ listener <- eventListener (const (check >>= setMatches))
32+ addEventListener Event.change listener true target
33+ pure do
34+ removeEventListener Event.change listener true target
35+
36+ pure queryMatches