|
1 | 1 | module Lumi.Components.Responsive where |
2 | 2 |
|
3 | 3 | import Prelude |
| 4 | +import Effect (Effect) |
| 5 | +import Effect.Uncurried (EffectFn1, runEffectFn1) |
| 6 | +import Effect.Unsafe (unsafePerformEffect) |
| 7 | +import React.Basic.Hooks (Hook, JSX, ReactComponent, component, element, unsafeHook) |
| 8 | +import React.Basic.Hooks as React |
4 | 9 |
|
5 | | -import JSS (JSS, jss) |
6 | | -import React.Basic (JSX, element) |
7 | | -import React.Basic.DOM (CSS, css, unsafeCreateDOMComponent) |
8 | | - |
9 | | -type ResponsiveProps = |
10 | | - { style :: CSS |
11 | | - , children :: Array JSX |
12 | | - } |
13 | | - |
14 | | -mobile :: ResponsiveProps -> JSX |
15 | | -mobile = element (unsafeCreateDOMComponent "lumi-mobile") |
16 | | - |
17 | | -mobile_ :: Array JSX -> JSX |
18 | | -mobile_ = mobile <<< { style: css {}, children: _ } |
19 | | - |
20 | | -desktop :: ResponsiveProps -> JSX |
21 | | -desktop = element (unsafeCreateDOMComponent "lumi-desktop") |
22 | | - |
23 | | -desktop_ :: Array JSX -> JSX |
24 | | -desktop_ = desktop <<< { style: css {}, children: _ } |
25 | | - |
26 | | -styles :: JSS |
27 | | -styles = jss |
28 | | - { "@global": |
29 | | - { "lumi-desktop": |
30 | | - { "display": "flex" |
31 | | - , "flex-shrink": "0" |
32 | | - , "@media (max-width: 860px)": |
33 | | - { "display": "none !important" |
34 | | - } |
35 | | - } |
36 | | - , "lumi-mobile": |
37 | | - { "display": "flex" |
38 | | - , "flex-shrink": "0" |
39 | | - , "@media (min-width: 861px)": |
40 | | - { "display": "none !important" |
41 | | - } |
42 | | - } |
43 | | - } |
44 | | - } |
| 10 | +foreign import data MediaQuery :: Type |
| 11 | + |
| 12 | +foreign import data UseMediaQuery :: Type -> Type |
| 13 | + |
| 14 | +useMedia :: String -> Hook UseMediaQuery Boolean |
| 15 | +useMedia mq = unsafeHook (runEffectFn1 useMedia_ mq) |
| 16 | + |
| 17 | +foreign import useMedia_ :: EffectFn1 String Boolean |
| 18 | + |
| 19 | +useIsPhone :: Hook UseMediaQuery Boolean |
| 20 | +useIsPhone = map not (useMedia ("(min-width: " <> show minWidthNonPhone <> "px)")) |
| 21 | + |
| 22 | +useIsMobile :: Hook UseMediaQuery Boolean |
| 23 | +useIsMobile = map not useIsDesktop |
| 24 | + |
| 25 | +useIsDesktop :: Hook UseMediaQuery Boolean |
| 26 | +useIsDesktop = useMedia ("(min-width: " <> show minWidthDesktop <> "px)") |
| 27 | + |
| 28 | +-- | Prefer `useIsPhone` or `phone` to using this value |
| 29 | +-- | directly. Named in the negative because -- | this |
| 30 | +-- | width is the first size that is _not_ a phone and |
| 31 | +-- | trying to use this width as a "max-width" leaves |
| 32 | +-- | out fractional pixels (447.5px) which sometimes |
| 33 | +-- | occur on high-dpi displays. |
| 34 | +minWidthNonPhone :: Int |
| 35 | +minWidthNonPhone = 448 |
| 36 | + |
| 37 | +minWidthDesktop :: Int |
| 38 | +minWidthDesktop = 860 |
| 39 | + |
| 40 | +mobile :: (Unit -> JSX) -> JSX |
| 41 | +mobile = element c <<< { render: _ } |
| 42 | + -- WARNING: Don't add any arguments the `mobile` function! |
| 43 | + -- `c` must be fully applied at module creation! |
| 44 | + where |
| 45 | + c = |
| 46 | + unsafePerformEffect do |
| 47 | + mkMediaComponent "Mobile" useIsMobile |
| 48 | + |
| 49 | +phone :: (Unit -> JSX) -> JSX |
| 50 | +phone = element c <<< { render: _ } |
| 51 | + -- WARNING: Don't add any arguments the `phone` function! |
| 52 | + -- `c` must be fully applied at module creation! |
| 53 | + where |
| 54 | + c = |
| 55 | + unsafePerformEffect do |
| 56 | + mkMediaComponent "Phone" useIsPhone |
| 57 | + |
| 58 | +desktop :: (Unit -> JSX) -> JSX |
| 59 | +desktop = element c <<< { render: _ } |
| 60 | + -- WARNING: Don't add any arguments the `desktop` function! |
| 61 | + -- `c` must be fully applied at module creation! |
| 62 | + where |
| 63 | + c = |
| 64 | + unsafePerformEffect do |
| 65 | + mkMediaComponent "Desktop" useIsDesktop |
| 66 | + |
| 67 | +mkMediaComponent :: |
| 68 | + String -> |
| 69 | + Hook UseMediaQuery Boolean -> |
| 70 | + Effect (ReactComponent { render :: Unit -> JSX }) |
| 71 | +mkMediaComponent name umq = do |
| 72 | + component ("MediaQuery(" <> name <> ")") \{ render } -> React.do |
| 73 | + isMatch <- umq |
| 74 | + if isMatch then |
| 75 | + pure (render unit) |
| 76 | + else |
| 77 | + mempty |
0 commit comments