Skip to content

Commit 102041d

Browse files
authored
Add useQRCode hook (#134)
* Add useQRCode hook * PR feedback
1 parent cd7c4f5 commit 102041d

File tree

10 files changed

+353
-134
lines changed

10 files changed

+353
-134
lines changed

docs/App.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ const componentv2Loaders = [
125125
"ButtonGroup",
126126
"Clip",
127127
"Link",
128+
"QRCode",
128129
"Slat"
129130
].map(fromComponentPathv2);
130131

docs/Examples2/QRCode.example.purs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
module Lumi.Components2.Examples.QRCode where
2+
3+
import Prelude
4+
import Data.Foldable (traverse_)
5+
import Data.Maybe (Maybe(..), fromMaybe)
6+
import Effect.Unsafe (unsafePerformEffect)
7+
import Lumi.Components (lumiElement)
8+
import Lumi.Components.Example (example)
9+
import Lumi.Components.Input as Input
10+
import Lumi.Components.Spacing (Space(..), vspace)
11+
import Lumi.Components.Text (subsectionHeader_)
12+
import Lumi.Components2.Box (box)
13+
import Lumi.Components2.Link (link)
14+
import Lumi.Components2.QRCode (ErrorCorrectLevel(..), useQRCode)
15+
import Lumi.Styles as S
16+
import Lumi.Styles.Border as Border
17+
import Lumi.Styles.Box (FlexAlign(..))
18+
import Lumi.Styles.Box as Box
19+
import React.Basic.DOM.Events (capture, targetValue)
20+
import React.Basic.Hooks (JSX, ReactComponent, component, element, useState, (/\))
21+
import React.Basic.Hooks as React
22+
import Web.HTML.History (URL(..))
23+
24+
docs :: JSX
25+
docs =
26+
flip element {}
27+
$ unsafePerformEffect do
28+
component "QRCodeExample" \_ -> React.do
29+
value /\ setValue <- useState "https://www.lumi.com"
30+
pure
31+
$ lumiElement box
32+
_
33+
{ content =
34+
[ Input.input
35+
Input.text_
36+
{ value = value
37+
, onChange = capture targetValue $ traverse_ (const >>> setValue)
38+
}
39+
, vspace S24
40+
, example
41+
$ element qrcodeExample { value }
42+
]
43+
}
44+
45+
qrcodeExample :: ReactComponent { value :: String }
46+
qrcodeExample =
47+
unsafePerformEffect do
48+
component "QRCode" \props -> React.do
49+
{ qrcode, url } <- useQRCode ECLLow props.value
50+
pure
51+
$ lumiElement box
52+
<<< Box._align Center
53+
$ _
54+
{ content =
55+
[ lumiElement qrcode
56+
<<< Border.border
57+
>>> Border._round
58+
>>> S.styleModifier_
59+
( S.css
60+
{ padding: S.int 16
61+
, width: S.int 140
62+
}
63+
)
64+
$ identity
65+
, vspace S8
66+
, lumiElement link
67+
_
68+
{ href = fromMaybe (URL "") url
69+
, download = Just "qrcode.svg"
70+
, content =
71+
[ subsectionHeader_ "Download SVG"
72+
]
73+
}
74+
]
75+
}

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"big-integer": "^1.6.44",
3434
"jss": "^10.0.0-alpha.16",
3535
"jss-preset-default": "^10.0.0-alpha.16",
36+
"qrcode.react": "^1.0.0",
3637
"react": "^16.6.1",
3738
"react-dnd": "^2.6.0",
3839
"react-dnd-html5-backend": "^2.6.0",

src/Lumi/Components2/Link.purs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,12 @@ type LinkProps
2424
, navigate :: Maybe (Effect Unit)
2525
, tabIndex :: Int
2626
, target :: Maybe String
27+
, download :: Maybe String
2728
, content :: Array JSX
29+
, className :: String
2830
)
2931

30-
link ::
31-
LumiComponent
32-
( className :: String
33-
, content :: Array JSX
34-
, href :: URL
35-
, navigate :: Maybe (Effect Unit)
36-
, target :: Maybe String
37-
)
32+
link :: LumiComponent LinkProps
3833
link =
3934
unsafePerformEffect do
4035
let
@@ -44,7 +39,9 @@ link =
4439
{ className: ""
4540
, href: URL ""
4641
, navigate: Nothing
42+
, tabIndex: 0
4743
, target: Nothing
44+
, download: Nothing
4845
, content: []
4946
}
5047
lumiComponent "Link" defaults \props@{ className } -> React.do
@@ -56,15 +53,17 @@ link =
5653
, className
5754
, href: un URL props.href
5855
, onClick:
59-
handler (merge { button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent }) \{ button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent } -> do
60-
case props.navigate, button, metaKey, altKey, ctrlKey, shiftKey of
61-
Just n', Just 0, Just false, Just false, Just false, Just false ->
62-
runEffectFn1
63-
(handler (stopPropagation <<< preventDefault) $ const n')
64-
syntheticEvent
65-
_, _, _, _, _, _ ->
66-
runEffectFn1
67-
(handler stopPropagation mempty)
68-
syntheticEvent
56+
handler (merge { button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent }) \{ button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent } -> do
57+
case props.navigate, button, metaKey, altKey, ctrlKey, shiftKey of
58+
Just n', Just 0, Just false, Just false, Just false, Just false ->
59+
runEffectFn1
60+
(handler (stopPropagation <<< preventDefault) $ const n')
61+
syntheticEvent
62+
_, _, _, _, _, _ ->
63+
runEffectFn1
64+
(handler stopPropagation mempty)
65+
syntheticEvent
6966
, target: toNullable props.target
67+
, tabIndex: props.tabIndex
68+
, download: toNullable props.download
7069
}

src/Lumi/Components2/QRCode.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"use strict";
2+
3+
exports.qrcode_ = require("qrcode.react");
4+
5+
exports.generateSVGUrl = ref => () => {
6+
const containerNode = ref.current;
7+
if (containerNode == null) {
8+
throw new Error("Cannot save the contents of an empty ref");
9+
}
10+
const svgNode = containerNode.querySelector("svg");
11+
if (svgNode == null) {
12+
throw new Error("Inner SVG node not found");
13+
}
14+
15+
const data = new XMLSerializer().serializeToString(svgNode);
16+
const svg = new Blob([data], { type: "image/svg+xml;charset=utf-8" });
17+
const url = URL.createObjectURL(svg);
18+
const dispose = () => URL.revokeObjectURL(url);
19+
return { url, dispose };
20+
};

src/Lumi/Components2/QRCode.purs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
module Lumi.Components2.QRCode where
2+
3+
import Prelude
4+
import Data.Maybe (Maybe(..))
5+
import Data.Newtype (class Newtype)
6+
import Data.Nullable as Nullable
7+
import Effect (Effect)
8+
import Effect.Unsafe (unsafePerformEffect)
9+
import Lumi.Components (LumiComponent, lumiComponent)
10+
import Lumi.Styles (toCSS)
11+
import Lumi.Styles.QRCode as Styles.QRCode
12+
import Lumi.Styles.Theme (useTheme)
13+
import React.Basic.DOM as R
14+
import React.Basic.Emotion as E
15+
import React.Basic.Hooks (type (/\), Hook, ReactComponent, Ref, UnsafeReference(..), UseEffect, UseMemo, UseRef, UseState, coerceHook, element, useEffect, useMemo, useRef, useState, (/\))
16+
import React.Basic.Hooks as React
17+
import Web.DOM (Node)
18+
import Web.HTML.History (URL(..))
19+
20+
newtype UseQRCode hooks
21+
= UseQRCode
22+
( UseEffect
23+
(UnsafeReference (LumiComponent ()))
24+
( UseState
25+
(Maybe URL)
26+
( UseMemo
27+
(String /\ ErrorCorrectLevel)
28+
(LumiComponent ())
29+
(UseRef (Nullable.Nullable Node) hooks)
30+
)
31+
)
32+
)
33+
34+
derive instance ntUseQRCode :: Newtype (UseQRCode hooks) _
35+
36+
data ErrorCorrectLevel
37+
= ECLLow
38+
| ECLMedium
39+
| ECLQuality
40+
| ECLHigh
41+
42+
derive instance eqErrorCorrectLevel :: Eq ErrorCorrectLevel
43+
44+
errorCorrectLevelToString :: ErrorCorrectLevel -> String
45+
errorCorrectLevelToString = case _ of
46+
ECLLow -> "L"
47+
ECLMedium -> "M"
48+
ECLQuality -> "Q"
49+
ECLHigh -> "H"
50+
51+
useQRCode :: ErrorCorrectLevel -> String -> Hook UseQRCode { qrcode :: LumiComponent (), url :: Maybe URL }
52+
useQRCode level value =
53+
coerceHook React.do
54+
ref <- useRef Nullable.null
55+
qrcode <-
56+
useMemo (value /\ level) \_ ->
57+
unsafePerformEffect do
58+
lumiComponent "QRCode" {} \props -> React.do
59+
theme <- useTheme
60+
pure
61+
$ E.element R.div'
62+
{ children:
63+
[ element qrcode_
64+
{ value
65+
, level: errorCorrectLevelToString level
66+
, renderAs: "svg"
67+
, xmlns: "http://www.w3.org/2000/svg"
68+
, size: Nullable.null
69+
, bgColor: "rgba(255,255,255,0.0)"
70+
, fgColor: "rgba(0,0,0,1.0)"
71+
}
72+
]
73+
, ref
74+
, className: props.className
75+
, css: toCSS theme props Styles.QRCode.qrcode
76+
}
77+
url /\ setUrl <- useState Nothing
78+
useEffect (UnsafeReference qrcode) do
79+
svgUrl <- generateSVGUrl ref
80+
setUrl \_ -> Just $ URL svgUrl.url
81+
pure svgUrl.dispose
82+
pure { qrcode, url }
83+
84+
foreign import qrcode_ ::
85+
ReactComponent
86+
{ value :: String
87+
, level :: String
88+
, renderAs :: String
89+
, xmlns :: String
90+
, size :: Nullable.Nullable Int
91+
, bgColor :: String
92+
, fgColor :: String
93+
}
94+
95+
foreign import generateSVGUrl :: Ref (Nullable.Nullable Node) -> Effect { url :: String, dispose :: Effect Unit }

0 commit comments

Comments
 (0)