git.iko.soy/rowtype-yoga/ry-blocks
on main help sign in
Theme switching
5ca62b4Mark Eibes
src/Yoga/Block/Atom/Input/View/Container.purs+35−39
@@ -10,33 +10,33 @@ import React.Basic.DOM (CSS, css)
10import React.Basic.Emotion (Style)
10import React.Basic.Emotion (Style)
11import Yoga.Block.Atom.Input.Style as Style
11import Yoga.Block.Atom.Input.Style as Style
12
12
13−type PropsF f
13+type PropsF f =
14− = ( css ∷ f Style
14+ ( css ∷ f Style
15− , isInvalid ∷ f Boolean
15+ , isInvalid ∷ f Boolean
16− | Style.Props f (MandatoryProps ())
16+ | Style.Props f (MandatoryProps ())
17− )
17+ )
18
18
19−type MandatoryProps r
19+type MandatoryProps r =
20− = ( children ∷ Array JSX
20+ ( children ∷ Array JSX
21− , hasFocus ∷ Boolean
21+ , hasFocus ∷ Boolean
22− | r
22+ | r
23− )
23+ )
24
24
25−type Props
25+type Props =
26− = PropsF Id
26+ PropsF Id
27
27
28−type PropsOptional
28+type PropsOptional =
29− = PropsF OptionalProp
29+ PropsF OptionalProp
30
30
31component ∷ ∀ p q. Union p q Props => ReactComponent { | MandatoryProps p }
31component ∷ ∀ p q. Union p q Props => ReactComponent { | MandatoryProps p }
32component = rawContainer
32component = rawContainer
33
33
34−type Propski
34+type Propski =
35− = { css ∷ OptionalProp Style
35+ { css ∷ OptionalProp Style
36− , hasFocus ∷ Boolean
36+ , hasFocus ∷ Boolean
37− , isInvalid ∷ OptionalProp Boolean
37+ , isInvalid ∷ OptionalProp Boolean
38− , children ∷ Array JSX
38+ , children ∷ Array JSX
39− }
39+ }
40
40
41rawContainer ∷ ∀ p. ReactComponent { | p }
41rawContainer ∷ ∀ p. ReactComponent { | p }
42rawContainer =
42rawContainer =
@@ -52,11 +52,11 @@ rawContainer =
52 { className: "ry-input-container"
52 { className: "ry-input-container"
53 , css: Style.inputContainer props
53 , css: Style.inputContainer props
54 , _data:
54 , _data:
55− props.isInvalid
55+ props.isInvalid
56− # foldMap \isInvalid ->
56+ # foldMap \isInvalid ->
57− Object.fromHomogeneous
57+ Object.fromHomogeneous
58− { "invalid": show isInvalid
58+ { "invalid": show isInvalid
59− }
59+ }
60 , ref
60 , ref
61 }
61 }
62 /> props.children
62 /> props.children
@@ -66,11 +66,8 @@ drawPathUntil ∷ Int -> Array Point -> String
66drawPathUntil idx thePath = do
66drawPathUntil idx thePath = do
67 let
67 let
68 fn { x, y } = i x "%" " " y "%"
68 fn { x, y } = i x "%" " " y "%"
69− let
70 firstFew = Array.take idx thePath
69 firstFew = Array.take idx thePath
71− let
72 lastFew = Array.drop idx thePath $> (Array.last firstFew # fromMaybe' \_ -> unsafeCrashWith "ogod")
70 lastFew = Array.drop idx thePath $> (Array.last firstFew # fromMaybe' \_ -> unsafeCrashWith "ogod")
73− let
74 rendered = intercalate "," $ fn <$> (firstFew <> lastFew)
71 rendered = intercalate "," $ fn <$> (firstFew <> lastFew)
75 i "polygon(" rendered ")"
72 i "polygon(" rendered ")"
76
73
@@ -87,7 +84,6 @@ mkPath borderX borderY = do
87 , {- ⌝ -} p (100 - borderX) borderY
84 , {- ⌝ -} p (100 - borderX) borderY
88 , {- ⌜ -} p borderX borderY
85 , {- ⌜ -} p borderX borderY
89 ]
86 ]
90−
91 outside =
87 outside =
92 [ {-⌜ -} p 0 0
88 [ {-⌜ -} p 0 0
93 , {-⌞ -} p 0 100
89 , {-⌞ -} p 0 100
@@ -118,18 +114,18 @@ containerVariants ∷
118 }
114 }
119containerVariants =
115containerVariants =
120 { focussed:
116 { focussed:
121− css
117+ css
122− { clipPath
118+ { clipPath
123− , transition: { duration: 0.6 }
119+ , transition: { duration: 0.6 }
124− }
120+ }
125 , blurred:
121 , blurred:
126− css
122+ css
127− { clipPath:
123+ { clipPath:
128− drawPathUntil (Array.length path + 1) path
124+ drawPathUntil (Array.length path + 1) path
129− }
125+ }
130 }
126 }
131 where
127 where
132 clipPath = 6 Array... (Array.length path) <#> \ln -> drawPathUntil ln path
128 clipPath = 6 Array... (Array.length path) <#> \ln -> drawPathUntil ln path
133
129
134−type Point
130+type Point =
135− = { x ∷ Int, y ∷ Int }
131+ { x ∷ Int, y ∷ Int }
src/Yoga/Block/Container/Style.js+5−1
@@ -1,2 +1,6 @@
1exports.getComputedStyleImpl = (el, window) => window.getComputedStyle(el)
1exports.getComputedStyleImpl = (el, window) => window.getComputedStyle(el)
2−exports.getPropertyValueImpl = (propName, computedStyle) => computedStyle.getPropertyValue(propName)
2+exports.getPropertyValueImpl = (propName, computedStyle) => computedStyle.getPropertyValue(propName)
3+exports.getElementStyle = el => () => el.style
4+exports.setStyleProperty = prop => value => style => () => style.setProperty(prop, value)
5+exports.matchMedia = string => window => () => window.matchMedia(string)
6+exports.matches = matchMedia => () => matchMedia.matches
src/Yoga/Block/Container/Style.purs+35−3
@@ -10,6 +10,7 @@ import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmap,
10import Unsafe.Coerce (unsafeCoerce)
10import Unsafe.Coerce (unsafeCoerce)
11import Web.DOM (Element)
11import Web.DOM (Element)
12import Web.DOM.Document (documentElement)
12import Web.DOM.Document (documentElement)
13+import Web.Event.Internal.Types (EventTarget)
13import Web.HTML (Window, window)
14import Web.HTML (Window, window)
14import Web.HTML.HTMLDocument as HTMLDocument
15import Web.HTML.HTMLDocument as HTMLDocument
15import Web.HTML.Window (document)
16import Web.HTML.Window (document)
@@ -47,13 +48,33 @@ foreign import getPropertyValueImpl ∷ EffectFn2 String ComputedStyle String --
47getPropertyValue ∷ String -> ComputedStyle -> Effect String
48getPropertyValue ∷ String -> ComputedStyle -> Effect String
48getPropertyValue = runEffectFn2 getPropertyValueImpl
49getPropertyValue = runEffectFn2 getPropertyValueImpl
49
50
51+foreign import data ElementStyle ∷ Type
52+
53+foreign import getElementStyle ∷ Element -> Effect ElementStyle
54+
55+foreign import setStyleProperty ∷ String -> String -> ElementStyle -> Effect Unit
56+
57+foreign import data MediaQueryList ∷ Type
58+
59+foreign import matchMedia ∷ String -> Window -> Effect MediaQueryList
60+
61+foreign import matches ∷ MediaQueryList -> Effect Boolean
62+
63+toEventTarget ∷ MediaQueryList -> EventTarget
64+toEventTarget = unsafeCoerce
65+
66+getDocumentElement ∷ MaybeT Effect Element
67+getDocumentElement = do
68+ win <- window # lift
69+ htmlDoc <- document win # lift
70+ let doc = HTMLDocument.toDocument htmlDoc
71+ documentElement doc # MaybeT
72+
50getDarkOrLightMode ∷ Effect (Maybe DarkOrLightMode)
73getDarkOrLightMode ∷ Effect (Maybe DarkOrLightMode)
51getDarkOrLightMode =
74getDarkOrLightMode =
52 runMaybeT do
75 runMaybeT do
53 win <- window # lift
76 win <- window # lift
54− htmlDoc <- document win # lift
55− let doc = HTMLDocument.toDocument htmlDoc
56− docElem ∷ Element <- documentElement doc # MaybeT
77+ docElem ∷ Element <- getDocumentElement
57 computedStyle <- getComputedStyle docElem win # lift
78 computedStyle <- getComputedStyle docElem win # lift
58 pv <- getPropertyValue "--theme-variant" computedStyle # lift
79 pv <- getPropertyValue "--theme-variant" computedStyle # lift
59 if pv == "dark" then
80 if pv == "dark" then
@@ -64,6 +85,17 @@ getDarkOrLightMode =
64 else
85 else
65 Nothing # pure # MaybeT
86 Nothing # pure # MaybeT
66
87
88+setDarkOrLightMode ∷ DarkOrLightMode -> Effect Unit
89+setDarkOrLightMode desiredMode =
90+ runMaybeT_ do
91+ docElem <- getDocumentElement
92+ style <- getElementStyle docElem # lift
93+ style
94+ # setStyleProperty "--theme-variant" case desiredMode of
95+ LightMode -> "light"
96+ DarkMode -> "dark"
97+ # lift
98+
67mkGlobal ∷ Maybe DarkOrLightMode -> Style
99mkGlobal ∷ Maybe DarkOrLightMode -> Style
68mkGlobal maybeMode =
100mkGlobal maybeMode =
69 css
101 css
src/Yoga/Block/Container/View.purs+53−6
@@ -8,13 +8,18 @@ import Effect.Unsafe (unsafePerformEffect)
8import React.Basic.DOM as R
8import React.Basic.DOM as R
9import React.Basic.Emotion as E
9import React.Basic.Emotion as E
10import React.Basic.Hooks (reactComponent)
10import React.Basic.Hooks (reactComponent)
11+import React.Basic.Hooks as React
11import Unsafe.Coerce (unsafeCoerce)
12import Unsafe.Coerce (unsafeCoerce)
13+import Web.Event.EventTarget (addEventListener, eventListener, removeEventListener)
14+import Web.HTML (window)
15+import Web.HTML.Event.EventTypes as Event
12−import Yoga.Block.Container.Style (DarkOrLightMode)
16+import Yoga.Block.Container.Style (DarkOrLightMode(..), matchMedia, matches, setDarkOrLightMode)
13import Yoga.Block.Container.Style as Styles
17import Yoga.Block.Container.Style as Styles
14
18
15type PropsF f =
19type PropsF f =
16 ( content ∷ JSX
20 ( content ∷ JSX
17 , themeVariant ∷ f (Maybe DarkOrLightMode)
21 , themeVariant ∷ f (Maybe DarkOrLightMode)
22+ , onPreferredSystemThemeChange ∷ f (DarkOrLightMode -> Effect Unit)
18 )
23 )
19
24
20type Props =
25type Props =
@@ -23,20 +28,62 @@ type Props =
23component ∷ ∀ p q. Union p q Props => ReactComponent { | p }
28component ∷ ∀ p q. Union p q Props => ReactComponent { | p }
24component = rawComponent
29component = rawComponent
25
30
31+mkPrefersDark ∷ Effect Styles.MediaQueryList
32+mkPrefersDark = matchMedia "(prefers-color-scheme: dark)" =<< window
33+
34+mkPrefersLight ∷ Effect Styles.MediaQueryList
35+mkPrefersLight = matchMedia "(prefers-color-scheme: light)" =<< window
36+
26rawComponent ∷ ∀ p. ReactComponent { | p }
37rawComponent ∷ ∀ p. ReactComponent { | p }
27rawComponent =
38rawComponent =
28 unsafeCoerce
39 unsafeCoerce
29 $ unsafePerformEffect
40 $ unsafePerformEffect
30− $ reactComponent "Container" \({ content, themeVariant } ∷ { | PropsF OptionalProp }) -> React.do
41+ $ reactComponent "Container" \(props@{ content } ∷ { | PropsF OptionalProp }) -> React.do
42+ let propsThemeVariant = props.themeVariant # opToMaybe # join
43+ let notifySystemThemeChanged = props.onPreferredSystemThemeChange ?|| mempty
44+ systemThemeVariant /\ setSystemThemeVariant <- React.useState' Nothing
45+ useEffect propsThemeVariant do
46+ for_ propsThemeVariant setDarkOrLightMode
47+ mempty
48+ useEffectOnce do
49+ prefersDarkMediaQuery <- mkPrefersDark
50+ prefersLightMediaQuery <- mkPrefersLight
51+ -- Init system preference
52+ whenM (matches prefersDarkMediaQuery) do
53+ setSystemThemeVariant (Just DarkMode)
54+ notifySystemThemeChanged DarkMode
55+ whenM (matches prefersLightMediaQuery) do
56+ setSystemThemeVariant (Just LightMode)
57+ notifySystemThemeChanged LightMode
58+ -- Dark Mode listener
59+ darkModeListener <-
60+ eventListener \_ -> do
61+ whenM (matches prefersDarkMediaQuery) do
62+ setSystemThemeVariant (Just DarkMode)
63+ notifySystemThemeChanged DarkMode
64+ addEventListener Event.change darkModeListener true (Styles.toEventTarget prefersDarkMediaQuery)
65+ -- Light Mode listener
66+ lightModeListener <-
67+ eventListener \_ -> do
68+ whenM (matches prefersLightMediaQuery) do
69+ setSystemThemeVariant (Just LightMode)
70+ notifySystemThemeChanged LightMode
71+ addEventListener Event.change darkModeListener true (Styles.toEventTarget prefersDarkMediaQuery)
72+ addEventListener Event.change lightModeListener true (Styles.toEventTarget prefersLightMediaQuery)
73+ pure do
74+ removeEventListener Event.change darkModeListener true (Styles.toEventTarget prefersDarkMediaQuery)
75+ removeEventListener Event.change lightModeListener true (Styles.toEventTarget prefersLightMediaQuery)
31 pure
76 pure
32 $ R.div_
77 $ R.div_
33 $ Array.cons
78 $ Array.cons
34 ( element E.global
79 ( element E.global
35 { styles:
80 { styles:
36− case opToMaybe themeVariant # join of
81+ case propsThemeVariant, systemThemeVariant of
37− Nothing -> Styles.global
82+ Nothing, Nothing -> Styles.global
38− Just Styles.DarkMode -> Styles.darkMode
83+ Just Styles.DarkMode, _ -> Styles.darkMode
39− Just Styles.LightMode -> Styles.lightMode
84+ Just Styles.LightMode, _ -> Styles.lightMode
85+ Nothing, Just DarkMode -> Styles.darkMode
86+ Nothing, Just LightMode -> Styles.lightMode
40 }
87 }
41 )
88 )
42 [ content
89 [ content