From 0b3c3e64eea97648865a60d1fb394a887e6cc103 Mon Sep 17 00:00:00 2001 From: Madeline Trotter Date: Wed, 4 Mar 2020 10:18:19 -0800 Subject: [PATCH 1/2] Add useQRCode hook --- docs/App.jsx | 1 + docs/Examples2/QRCode.example.purs | 53 +++++++ package-lock.json | 15 ++ package.json | 1 + src/Lumi/Components2/QRCode.js | 27 ++++ src/Lumi/Components2/QRCode.purs | 67 +++++++++ src/Lumi/Styles/Button.purs | 214 ++++++++++++++--------------- src/Lumi/Styles/QRCode.purs | 16 +++ src/Lumi/Styles/Theme.purs | 15 +- 9 files changed, 293 insertions(+), 116 deletions(-) create mode 100644 docs/Examples2/QRCode.example.purs create mode 100644 src/Lumi/Components2/QRCode.js create mode 100644 src/Lumi/Components2/QRCode.purs create mode 100644 src/Lumi/Styles/QRCode.purs diff --git a/docs/App.jsx b/docs/App.jsx index 84a7ea45..2ff62185 100644 --- a/docs/App.jsx +++ b/docs/App.jsx @@ -125,6 +125,7 @@ const componentv2Loaders = [ "ButtonGroup", "Clip", "Link", + "QRCode", "Slat" ].map(fromComponentPathv2); diff --git a/docs/Examples2/QRCode.example.purs b/docs/Examples2/QRCode.example.purs new file mode 100644 index 00000000..2a3d18cd --- /dev/null +++ b/docs/Examples2/QRCode.example.purs @@ -0,0 +1,53 @@ +module Lumi.Components2.Examples.QRCode where + +import Prelude +import Effect.Unsafe (unsafePerformEffect) +import Lumi.Components (lumiElement) +import Lumi.Components.Example (example) +import Lumi.Components.Spacing (Space(..), vspace) +import Lumi.Components.Text (p_) +import Lumi.Components2.Box (box) +import Lumi.Components2.Button (button) +import Lumi.Components2.Button as Button +import Lumi.Components2.QRCode (ErrorCorrectLevel(..), useQRCode) +import Lumi.Styles as S +import Lumi.Styles.Border as Border +import React.Basic.DOM as R +import React.Basic.Hooks (JSX, ReactComponent, component, element) +import React.Basic.Hooks as React + +docs :: JSX +docs = + lumiElement box + _ + { content = + [ p_ "A QR Code pointing to \"https://www.lumi.com\"" + , vspace S24 + , example + $ element qrcodeExample { value: "https://www.lumi.com" } + ] + } + +qrcodeExample :: ReactComponent { value :: String } +qrcodeExample = + unsafePerformEffect do + component "QRCode" \props -> React.do + { qrcode, saveOnClick } <- useQRCode props.value L + pure + $ lumiElement box + _ + { content = + [ lumiElement box + <<< Border.border + >>> Border._round + >>> S.styleModifier_ (S.css { padding: S.int 16 }) + $ _ { content = [ qrcode ] } + , vspace S8 + , lumiElement button + <<< Button._secondary + $ _ + { content = [ R.text "Download SVG" ] + , onPress = saveOnClick "qrcode.svg" + } + ] + } diff --git a/package-lock.json b/package-lock.json index 752970af..8fcbf275 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8814,6 +8814,21 @@ "integrity": "sha512-qcnhruybTHVunhJqAuurOEWnz1BOOc/7mpcnyH5yxm5caYCejxq8GOE4ZNWbiPMeTlcG79qUPIOz7gx5OVmk2Q==", "dev": true }, + "qr.js": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", + "integrity": "sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=" + }, + "qrcode.react": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-1.0.0.tgz", + "integrity": "sha512-jBXleohRTwvGBe1ngV+62QvEZ/9IZqQivdwzo9pJM4LQMoCM2VnvNBnKdjvGnKyDZ/l0nCDgsPod19RzlPvm/Q==", + "requires": { + "loose-envify": "^1.4.0", + "prop-types": "^15.6.0", + "qr.js": "0.0.0" + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", diff --git a/package.json b/package.json index 2ddea601..8bbeb049 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "big-integer": "^1.6.44", "jss": "^10.0.0-alpha.16", "jss-preset-default": "^10.0.0-alpha.16", + "qrcode.react": "^1.0.0", "react": "^16.6.1", "react-dnd": "^2.6.0", "react-dnd-html5-backend": "^2.6.0", diff --git a/src/Lumi/Components2/QRCode.js b/src/Lumi/Components2/QRCode.js new file mode 100644 index 00000000..6a7582ca --- /dev/null +++ b/src/Lumi/Components2/QRCode.js @@ -0,0 +1,27 @@ +"use strict"; + +exports.qrcode_ = require("qrcode.react"); + +exports.saveOnClick = ref => filename => () => { + const containerNode = ref.current; + if (containerNode == null) { + throw new Error("Cannot save the contents of an empty ref"); + } + const svgNode = containerNode.querySelector("svg"); + if (svgNode == null) { + throw new Error("Inner SVG node not found"); + } + + const data = new XMLSerializer().serializeToString(svgNode); + const svg = new Blob([data], { type: "image/svg+xml;charset=utf-8" }); + const url = URL.createObjectURL(svg); + + const a = document.createElement("a"); + document.body.appendChild(a); + a.style = "display: none"; + a.href = url; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); +}; diff --git a/src/Lumi/Components2/QRCode.purs b/src/Lumi/Components2/QRCode.purs new file mode 100644 index 00000000..1c0737ed --- /dev/null +++ b/src/Lumi/Components2/QRCode.purs @@ -0,0 +1,67 @@ +module Lumi.Components2.QRCode where + +import Prelude +import Data.Newtype (class Newtype) +import Data.Nullable as Nullable +import Effect (Effect) +import Lumi.Styles (toCSS) +import Lumi.Styles.QRCode as Styles.QRCode +import Lumi.Styles.Theme (LumiTheme, useTheme) +import React.Basic.DOM as R +import React.Basic.Emotion as E +import React.Basic.Hooks (Hook, JSX, ReactComponent, Ref, UseContext, UseRef, coerceHook, element, useRef) +import React.Basic.Hooks as React +import Web.DOM (Node) + +newtype UseQRCode hooks + = UseQRCode (UseRef (Nullable.Nullable Node) (UseContext LumiTheme hooks)) + +derive instance ntUseQRCode :: Newtype (UseQRCode hooks) _ + +data ErrorCorrectLevel + = L + | M + | Q + | H + +errorCorrectLevelToString :: ErrorCorrectLevel -> String +errorCorrectLevelToString = case _ of + L -> "L" + M -> "M" + Q -> "Q" + H -> "H" + +useQRCode :: String -> ErrorCorrectLevel -> Hook UseQRCode { qrcode :: JSX, saveOnClick :: String -> Effect Unit } +useQRCode value level = + coerceHook React.do + theme <- useTheme + ref <- useRef Nullable.null + pure + { qrcode: + E.element R.div' + { children: + [ element qrcode_ + { value + , level: errorCorrectLevelToString level + , renderAs: "svg" + , xmlns: "http://www.w3.org/2000/svg" + , size: 126 + } + ] + , ref + , className: "" + , css: toCSS theme { className: "", css: mempty } Styles.QRCode.qrcode + } + , saveOnClick: saveOnClick ref + } + +foreign import qrcode_ :: + ReactComponent + { value :: String + , level :: String + , renderAs :: String + , xmlns :: String + , size :: Int + } + +foreign import saveOnClick :: Ref (Nullable.Nullable Node) -> String -> Effect Unit diff --git a/src/Lumi/Styles/Button.purs b/src/Lumi/Styles/Button.purs index be7e90b4..f05edc1f 100644 --- a/src/Lumi/Styles/Button.purs +++ b/src/Lumi/Styles/Button.purs @@ -58,17 +58,17 @@ button colo kind state size = case kind of , color: color white , backgroundColor: color hue , "&:hover": - nested - $ css - { borderColor: color hueDarker - , backgroundColor: color hueDarker - } + nested + $ css + { borderColor: color hueDarker + , backgroundColor: color hueDarker + } , "&:active": - nested - $ css - { borderColor: color hueDarkest - , backgroundColor: color hueDarkest - } + nested + $ css + { borderColor: color hueDarkest + , backgroundColor: color hueDarkest + } , "&:disabled": nested disabledStyles } Disabled -> disabledStyles @@ -103,19 +103,19 @@ button colo kind state size = case kind of , color: color black , backgroundColor: color white , "&:hover": - nested - $ css - { borderColor: color hueDarker - , color: color hueDarker - , backgroundColor: color white - } + nested + $ css + { borderColor: color hueDarker + , color: color hueDarker + , backgroundColor: color white + } , "&:active": - nested - $ css - { borderColor: color hueDarkest - , color: color hueDarkest - , backgroundColor: color white - } + nested + $ css + { borderColor: color hueDarkest + , color: color hueDarkest + , backgroundColor: color white + } , "&:disabled": nested disabledStyles } Disabled -> disabledStyles @@ -140,11 +140,11 @@ button colo kind state size = case kind of { cursor: str "default" , color: color hueDisabled , "&:hover, &:active": - nested - $ css - { cursor: str "default" - , textDecoration: none - } + nested + $ css + { cursor: str "default" + , textDecoration: none + } } in merge @@ -177,7 +177,7 @@ button colo kind state size = case kind of , minWidth: int 70 , padding: str "10px 20px" , fontSize: int 14 - , lineHeight: int 20 + , lineHeight: int 1 , whiteSpace: str "nowrap" , textOverflow: str "ellipsis" , overflow: str "hidden" @@ -186,42 +186,38 @@ button colo kind state size = case kind of , borderWidth: int 1 , borderStyle: str "solid" , "@media (min-width: 860px)": - nested - $ fold - [ css - { padding: str "6px 16px" - , height: int 32 - } - , case size of - Small -> - css - { fontSize: int 12 - , lineHeight: int 16 - , height: int 28 - } - Medium -> mempty - Large -> - css - { fontSize: int 15 - , lineHeight: int 24 - , padding: str "12px 24px" - , height: int 48 - } - ExtraLarge -> - css - { fontSize: int 20 - , lineHeight: int 32 - , padding: str "16px 32px" - , height: int 64 - } - ExtraExtraLarge -> - css - { fontSize: int 20 - , lineHeight: int 32 - , padding: str "16px 32px" - , height: int 64 - } - ] + nested + $ fold + [ css + { padding: str "6px 16px" + , height: int 32 + } + , case size of + Small -> + css + { fontSize: int 12 + , height: int 28 + } + Medium -> mempty + Large -> + css + { fontSize: int 15 + , padding: str "12px 24px" + , height: int 48 + } + ExtraLarge -> + css + { fontSize: int 20 + , padding: str "16px 32px" + , height: int 64 + } + ExtraExtraLarge -> + css + { fontSize: int 20 + , padding: str "16px 32px" + , height: int 64 + } + ] } ) @@ -232,32 +228,32 @@ button colo kind state size = case kind of { label: str "loading" , "&:after": nested $ mkLoader theme { radius: "16px", borderWidth: "2px" } , "@media (min-width: 860px)": - nested case size of - Small -> - css - { "&:after": - nested - $ mkLoader theme { radius: "12px", borderWidth: "2px" } - } - Medium -> mempty - Large -> - css - { "&:after": - nested - $ mkLoader theme { radius: "24px", borderWidth: "3px" } - } - ExtraLarge -> - css - { "&:after": - nested - $ mkLoader theme { radius: "34px", borderWidth: "4px" } - } - ExtraExtraLarge -> - css - { "&:after": - nested - $ mkLoader theme { radius: "34px", borderWidth: "4px" } - } + nested case size of + Small -> + css + { "&:after": + nested + $ mkLoader theme { radius: "12px", borderWidth: "2px" } + } + Medium -> mempty + Large -> + css + { "&:after": + nested + $ mkLoader theme { radius: "24px", borderWidth: "3px" } + } + ExtraLarge -> + css + { "&:after": + nested + $ mkLoader theme { radius: "34px", borderWidth: "4px" } + } + ExtraExtraLarge -> + css + { "&:after": + nested + $ mkLoader theme { radius: "34px", borderWidth: "4px" } + } } ] @@ -285,30 +281,30 @@ buttonGroup joined = css { label: str "notJoined" , "& > *:not(:last-child)": - nested - $ css - { marginRight: int 8 - } + nested + $ css + { marginRight: int 8 + } } else css { label: str "joined" , "& > *:not(:last-child)": - nested - $ css - { marginRight: int (-1) - , borderTopRightRadius: int 0 - , borderBottomRightRadius: int 0 - } + nested + $ css + { marginRight: int (-1) + , borderTopRightRadius: int 0 + , borderBottomRightRadius: int 0 + } , "& > *:not(:first-child)": - nested - $ css - { borderTopLeftRadius: int 0 - , borderBottomLeftRadius: int 0 - } + nested + $ css + { borderTopLeftRadius: int 0 + , borderBottomLeftRadius: int 0 + } , "& > *:focus, & > *:hover": - nested - $ css - { zIndex: int ziButtonGroup - } + nested + $ css + { zIndex: int ziButtonGroup + } } diff --git a/src/Lumi/Styles/QRCode.purs b/src/Lumi/Styles/QRCode.purs new file mode 100644 index 00000000..3d8176be --- /dev/null +++ b/src/Lumi/Styles/QRCode.purs @@ -0,0 +1,16 @@ +module Lumi.Styles.QRCode where + +import Prelude +import Lumi.Components (PropsModifier) +import Lumi.Styles (styleModifier_) +import Lumi.Styles.Box (box) +import React.Basic.Emotion (css, str) + +qrcode :: forall props. PropsModifier props +qrcode = + box + >>> styleModifier_ + ( css + { label: str "qrcode" + } + ) diff --git a/src/Lumi/Styles/Theme.purs b/src/Lumi/Styles/Theme.purs index f4564e96..e4bdcc1e 100644 --- a/src/Lumi/Styles/Theme.purs +++ b/src/Lumi/Styles/Theme.purs @@ -4,13 +4,13 @@ import Data.Newtype (class Newtype) import Effect.Unsafe (unsafePerformEffect) import Lumi.Components.Color (Color, ColorMap, ColorName, colorNames, colors) import React.Basic (ReactContext, createContext) -import React.Basic.Hooks (Render, UseContext, useContext) +import React.Basic.Hooks (Hook, UseContext, useContext) newtype LumiTheme = LumiTheme - { colors :: ColorMap Color - , colorNames :: ColorMap ColorName - } + { colors :: ColorMap Color + , colorNames :: ColorMap ColorName + } derive instance newtypeLumiTheme :: Newtype LumiTheme _ @@ -18,8 +18,9 @@ defaultTheme :: LumiTheme defaultTheme = LumiTheme { colors, colorNames } lumiThemeContext :: ReactContext LumiTheme -lumiThemeContext = unsafePerformEffect do - createContext defaultTheme +lumiThemeContext = + unsafePerformEffect do + createContext defaultTheme -useTheme :: forall hooks. Render hooks (UseContext LumiTheme hooks) LumiTheme +useTheme :: Hook (UseContext LumiTheme) LumiTheme useTheme = useContext lumiThemeContext From 518b609ac8eafa21724e6b5fca8fb432f335304c Mon Sep 17 00:00:00 2001 From: Madeline Trotter Date: Wed, 4 Mar 2020 20:44:10 -0800 Subject: [PATCH 2/2] PR feedback --- docs/Examples2/QRCode.example.purs | 84 ++++++++++++++++---------- src/Lumi/Components2/Link.purs | 35 ++++++----- src/Lumi/Components2/QRCode.js | 13 +--- src/Lumi/Components2/QRCode.purs | 96 +++++++++++++++++++----------- 4 files changed, 135 insertions(+), 93 deletions(-) diff --git a/docs/Examples2/QRCode.example.purs b/docs/Examples2/QRCode.example.purs index 2a3d18cd..64d3b59d 100644 --- a/docs/Examples2/QRCode.example.purs +++ b/docs/Examples2/QRCode.example.purs @@ -1,53 +1,75 @@ module Lumi.Components2.Examples.QRCode where import Prelude +import Data.Foldable (traverse_) +import Data.Maybe (Maybe(..), fromMaybe) import Effect.Unsafe (unsafePerformEffect) import Lumi.Components (lumiElement) import Lumi.Components.Example (example) +import Lumi.Components.Input as Input import Lumi.Components.Spacing (Space(..), vspace) -import Lumi.Components.Text (p_) +import Lumi.Components.Text (subsectionHeader_) import Lumi.Components2.Box (box) -import Lumi.Components2.Button (button) -import Lumi.Components2.Button as Button +import Lumi.Components2.Link (link) import Lumi.Components2.QRCode (ErrorCorrectLevel(..), useQRCode) import Lumi.Styles as S import Lumi.Styles.Border as Border -import React.Basic.DOM as R -import React.Basic.Hooks (JSX, ReactComponent, component, element) +import Lumi.Styles.Box (FlexAlign(..)) +import Lumi.Styles.Box as Box +import React.Basic.DOM.Events (capture, targetValue) +import React.Basic.Hooks (JSX, ReactComponent, component, element, useState, (/\)) import React.Basic.Hooks as React +import Web.HTML.History (URL(..)) docs :: JSX docs = - lumiElement box - _ - { content = - [ p_ "A QR Code pointing to \"https://www.lumi.com\"" - , vspace S24 - , example - $ element qrcodeExample { value: "https://www.lumi.com" } - ] - } + flip element {} + $ unsafePerformEffect do + component "QRCodeExample" \_ -> React.do + value /\ setValue <- useState "https://www.lumi.com" + pure + $ lumiElement box + _ + { content = + [ Input.input + Input.text_ + { value = value + , onChange = capture targetValue $ traverse_ (const >>> setValue) + } + , vspace S24 + , example + $ element qrcodeExample { value } + ] + } qrcodeExample :: ReactComponent { value :: String } qrcodeExample = unsafePerformEffect do component "QRCode" \props -> React.do - { qrcode, saveOnClick } <- useQRCode props.value L + { qrcode, url } <- useQRCode ECLLow props.value pure $ lumiElement box - _ - { content = - [ lumiElement box - <<< Border.border - >>> Border._round - >>> S.styleModifier_ (S.css { padding: S.int 16 }) - $ _ { content = [ qrcode ] } - , vspace S8 - , lumiElement button - <<< Button._secondary - $ _ - { content = [ R.text "Download SVG" ] - , onPress = saveOnClick "qrcode.svg" - } - ] - } + <<< Box._align Center + $ _ + { content = + [ lumiElement qrcode + <<< Border.border + >>> Border._round + >>> S.styleModifier_ + ( S.css + { padding: S.int 16 + , width: S.int 140 + } + ) + $ identity + , vspace S8 + , lumiElement link + _ + { href = fromMaybe (URL "") url + , download = Just "qrcode.svg" + , content = + [ subsectionHeader_ "Download SVG" + ] + } + ] + } diff --git a/src/Lumi/Components2/Link.purs b/src/Lumi/Components2/Link.purs index c54300e1..8ef5a74d 100644 --- a/src/Lumi/Components2/Link.purs +++ b/src/Lumi/Components2/Link.purs @@ -24,17 +24,12 @@ type LinkProps , navigate :: Maybe (Effect Unit) , tabIndex :: Int , target :: Maybe String + , download :: Maybe String , content :: Array JSX + , className :: String ) -link :: - LumiComponent - ( className :: String - , content :: Array JSX - , href :: URL - , navigate :: Maybe (Effect Unit) - , target :: Maybe String - ) +link :: LumiComponent LinkProps link = unsafePerformEffect do let @@ -44,7 +39,9 @@ link = { className: "" , href: URL "" , navigate: Nothing + , tabIndex: 0 , target: Nothing + , download: Nothing , content: [] } lumiComponent "Link" defaults \props@{ className } -> React.do @@ -56,15 +53,17 @@ link = , className , href: un URL props.href , onClick: - handler (merge { button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent }) \{ button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent } -> do - case props.navigate, button, metaKey, altKey, ctrlKey, shiftKey of - Just n', Just 0, Just false, Just false, Just false, Just false -> - runEffectFn1 - (handler (stopPropagation <<< preventDefault) $ const n') - syntheticEvent - _, _, _, _, _, _ -> - runEffectFn1 - (handler stopPropagation mempty) - syntheticEvent + handler (merge { button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent }) \{ button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent } -> do + case props.navigate, button, metaKey, altKey, ctrlKey, shiftKey of + Just n', Just 0, Just false, Just false, Just false, Just false -> + runEffectFn1 + (handler (stopPropagation <<< preventDefault) $ const n') + syntheticEvent + _, _, _, _, _, _ -> + runEffectFn1 + (handler stopPropagation mempty) + syntheticEvent , target: toNullable props.target + , tabIndex: props.tabIndex + , download: toNullable props.download } diff --git a/src/Lumi/Components2/QRCode.js b/src/Lumi/Components2/QRCode.js index 6a7582ca..21cdbd03 100644 --- a/src/Lumi/Components2/QRCode.js +++ b/src/Lumi/Components2/QRCode.js @@ -2,7 +2,7 @@ exports.qrcode_ = require("qrcode.react"); -exports.saveOnClick = ref => filename => () => { +exports.generateSVGUrl = ref => () => { const containerNode = ref.current; if (containerNode == null) { throw new Error("Cannot save the contents of an empty ref"); @@ -15,13 +15,6 @@ exports.saveOnClick = ref => filename => () => { const data = new XMLSerializer().serializeToString(svgNode); const svg = new Blob([data], { type: "image/svg+xml;charset=utf-8" }); const url = URL.createObjectURL(svg); - - const a = document.createElement("a"); - document.body.appendChild(a); - a.style = "display: none"; - a.href = url; - a.download = filename; - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); + const dispose = () => URL.revokeObjectURL(url); + return { url, dispose }; }; diff --git a/src/Lumi/Components2/QRCode.purs b/src/Lumi/Components2/QRCode.purs index 1c0737ed..4661e9e9 100644 --- a/src/Lumi/Components2/QRCode.purs +++ b/src/Lumi/Components2/QRCode.purs @@ -1,59 +1,85 @@ module Lumi.Components2.QRCode where import Prelude +import Data.Maybe (Maybe(..)) import Data.Newtype (class Newtype) import Data.Nullable as Nullable import Effect (Effect) +import Effect.Unsafe (unsafePerformEffect) +import Lumi.Components (LumiComponent, lumiComponent) import Lumi.Styles (toCSS) import Lumi.Styles.QRCode as Styles.QRCode -import Lumi.Styles.Theme (LumiTheme, useTheme) +import Lumi.Styles.Theme (useTheme) import React.Basic.DOM as R import React.Basic.Emotion as E -import React.Basic.Hooks (Hook, JSX, ReactComponent, Ref, UseContext, UseRef, coerceHook, element, useRef) +import React.Basic.Hooks (type (/\), Hook, ReactComponent, Ref, UnsafeReference(..), UseEffect, UseMemo, UseRef, UseState, coerceHook, element, useEffect, useMemo, useRef, useState, (/\)) import React.Basic.Hooks as React import Web.DOM (Node) +import Web.HTML.History (URL(..)) newtype UseQRCode hooks - = UseQRCode (UseRef (Nullable.Nullable Node) (UseContext LumiTheme hooks)) + = UseQRCode + ( UseEffect + (UnsafeReference (LumiComponent ())) + ( UseState + (Maybe URL) + ( UseMemo + (String /\ ErrorCorrectLevel) + (LumiComponent ()) + (UseRef (Nullable.Nullable Node) hooks) + ) + ) + ) derive instance ntUseQRCode :: Newtype (UseQRCode hooks) _ data ErrorCorrectLevel - = L - | M - | Q - | H + = ECLLow + | ECLMedium + | ECLQuality + | ECLHigh + +derive instance eqErrorCorrectLevel :: Eq ErrorCorrectLevel errorCorrectLevelToString :: ErrorCorrectLevel -> String errorCorrectLevelToString = case _ of - L -> "L" - M -> "M" - Q -> "Q" - H -> "H" + ECLLow -> "L" + ECLMedium -> "M" + ECLQuality -> "Q" + ECLHigh -> "H" -useQRCode :: String -> ErrorCorrectLevel -> Hook UseQRCode { qrcode :: JSX, saveOnClick :: String -> Effect Unit } -useQRCode value level = +useQRCode :: ErrorCorrectLevel -> String -> Hook UseQRCode { qrcode :: LumiComponent (), url :: Maybe URL } +useQRCode level value = coerceHook React.do - theme <- useTheme ref <- useRef Nullable.null - pure - { qrcode: - E.element R.div' - { children: - [ element qrcode_ - { value - , level: errorCorrectLevelToString level - , renderAs: "svg" - , xmlns: "http://www.w3.org/2000/svg" - , size: 126 - } - ] - , ref - , className: "" - , css: toCSS theme { className: "", css: mempty } Styles.QRCode.qrcode - } - , saveOnClick: saveOnClick ref - } + qrcode <- + useMemo (value /\ level) \_ -> + unsafePerformEffect do + lumiComponent "QRCode" {} \props -> React.do + theme <- useTheme + pure + $ E.element R.div' + { children: + [ element qrcode_ + { value + , level: errorCorrectLevelToString level + , renderAs: "svg" + , xmlns: "http://www.w3.org/2000/svg" + , size: Nullable.null + , bgColor: "rgba(255,255,255,0.0)" + , fgColor: "rgba(0,0,0,1.0)" + } + ] + , ref + , className: props.className + , css: toCSS theme props Styles.QRCode.qrcode + } + url /\ setUrl <- useState Nothing + useEffect (UnsafeReference qrcode) do + svgUrl <- generateSVGUrl ref + setUrl \_ -> Just $ URL svgUrl.url + pure svgUrl.dispose + pure { qrcode, url } foreign import qrcode_ :: ReactComponent @@ -61,7 +87,9 @@ foreign import qrcode_ :: , level :: String , renderAs :: String , xmlns :: String - , size :: Int + , size :: Nullable.Nullable Int + , bgColor :: String + , fgColor :: String } -foreign import saveOnClick :: Ref (Nullable.Nullable Node) -> String -> Effect Unit +foreign import generateSVGUrl :: Ref (Nullable.Nullable Node) -> Effect { url :: String, dispose :: Effect Unit }