Add bottom sheet
EXPERIMENTAL: uses reanimated lib so we can use reanimated buttons inside and have simultaneous handlers Add react hooks Use hooks mocks Use timing for drag transition Use view on android Signed-off-by: Gheorghe Pinzaru <feross95@gmail.com>
This commit is contained in:
parent
d3c9175514
commit
12aa20f467
|
@ -1,11 +1,12 @@
|
||||||
{:lint-as {status-im.utils.views/defview clojure.core/defn
|
{:lint-as {status-im.utils.views/defview clojure.core/defn
|
||||||
status-im.utils.views/letsubs clojure.core/let
|
status-im.utils.views/letsubs clojure.core/let
|
||||||
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
|
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
|
||||||
|
quo.react/with-deps-check clojure.core/fn
|
||||||
quo.previews.preview/list-comp clojure.core/for
|
quo.previews.preview/list-comp clojure.core/for
|
||||||
status-im.utils.styles/def clojure.core/def
|
status-im.utils.styles/def clojure.core/def
|
||||||
status-im.utils.styles/defn clojure.core/defn
|
status-im.utils.styles/defn clojure.core/defn
|
||||||
taoensso.tufte/defnp clojure.core/defn}
|
taoensso.tufte/defnp clojure.core/defn}
|
||||||
:linters {:invalid-arity {:skip-args [status-im.utils.fx/defn]}
|
:linters {:invalid-arity {:skip-args [status-im.utils.fx/defn]}
|
||||||
;;TODO remove number when this is fixed
|
;;TODO remove number when this is fixed
|
||||||
;;https://github.com/borkdude/clj-kondo/issues/867
|
;;https://github.com/borkdude/clj-kondo/issues/867
|
||||||
:unresolved-symbol {:exclude [PersistentPriorityMap.EMPTY number]}}}
|
:unresolved-symbol {:exclude [PersistentPriorityMap.EMPTY number]}}}
|
||||||
|
|
|
@ -251,7 +251,7 @@ PODS:
|
||||||
- React
|
- React
|
||||||
- react-native-netinfo (4.7.0):
|
- react-native-netinfo (4.7.0):
|
||||||
- React
|
- React
|
||||||
- react-native-safe-area-context (0.7.3):
|
- react-native-safe-area-context (2.0.0):
|
||||||
- React
|
- React
|
||||||
- react-native-shake (3.4.0):
|
- react-native-shake (3.4.0):
|
||||||
- React
|
- React
|
||||||
|
@ -585,7 +585,7 @@ SPEC CHECKSUMS:
|
||||||
react-native-image-resizer: 4516052af6ae0248caf4ccf356caecefe60072d7
|
react-native-image-resizer: 4516052af6ae0248caf4ccf356caecefe60072d7
|
||||||
react-native-mail: 7e37dfbe93ff0d4c7df346b738854dbed533e86f
|
react-native-mail: 7e37dfbe93ff0d4c7df346b738854dbed533e86f
|
||||||
react-native-netinfo: ddaca8bbb9e6e914b1a23787ccb879bc642931c9
|
react-native-netinfo: ddaca8bbb9e6e914b1a23787ccb879bc642931c9
|
||||||
react-native-safe-area-context: e200d4433aba6b7e60b52da5f37af11f7a0b0392
|
react-native-safe-area-context: 60f654e00b6cc416573f6d5dbfce3839958eb57a
|
||||||
react-native-shake: de052eaa3eadc4a326b8ddd7ac80c06e8d84528c
|
react-native-shake: de052eaa3eadc4a326b8ddd7ac80c06e8d84528c
|
||||||
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
||||||
react-native-webview: cf5527893252b3b036eea024a1da6996f7344c74
|
react-native-webview: cf5527893252b3b036eea024a1da6996f7344c74
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-community/cameraroll": "^1.6.1",
|
"@react-native-community/cameraroll": "^1.6.1",
|
||||||
"@react-native-community/clipboard": "^1.2.2",
|
"@react-native-community/clipboard": "^1.2.2",
|
||||||
|
"@react-native-community/hooks": "^2.5.1",
|
||||||
"@react-native-community/masked-view": "^0.1.6",
|
"@react-native-community/masked-view": "^0.1.6",
|
||||||
"@react-native-community/netinfo": "^4.4.0",
|
"@react-native-community/netinfo": "^4.4.0",
|
||||||
"@react-navigation/bottom-tabs": "^5.1.1",
|
"@react-navigation/bottom-tabs": "^5.1.1",
|
||||||
|
@ -47,7 +48,7 @@
|
||||||
"react-native-navigation-twopane": "git+https://github.com/status-im/react-native-navigation-twopane.git#v0.0.2-status",
|
"react-native-navigation-twopane": "git+https://github.com/status-im/react-native-navigation-twopane.git#v0.0.2-status",
|
||||||
"react-native-reanimated": "^1.7.0",
|
"react-native-reanimated": "^1.7.0",
|
||||||
"react-native-redash": "^14.0.3",
|
"react-native-redash": "^14.0.3",
|
||||||
"react-native-safe-area-context": "^0.7.3",
|
"react-native-safe-area-context": "^2.0.0",
|
||||||
"react-native-screens": "^2.3.0",
|
"react-native-screens": "^2.3.0",
|
||||||
"react-native-shake": "^3.3.1",
|
"react-native-shake": "^3.3.1",
|
||||||
"react-native-splash-screen": "^3.2.0",
|
"react-native-splash-screen": "^3.2.0",
|
||||||
|
|
|
@ -1314,6 +1314,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/clipboard/-/clipboard-1.2.2.tgz#956b29df141199fd9ed47e820baf9693f9f50b55"
|
resolved "https://registry.yarnpkg.com/@react-native-community/clipboard/-/clipboard-1.2.2.tgz#956b29df141199fd9ed47e820baf9693f9f50b55"
|
||||||
integrity sha512-WJkJSWA/fhuBhHL3rh6UDdB8+AZNMvAHoyo/ERnNxl9KZqruq7K+AIaQQlggEAsIVBIhjKt65fT+Zynj7gF8Cg==
|
integrity sha512-WJkJSWA/fhuBhHL3rh6UDdB8+AZNMvAHoyo/ERnNxl9KZqruq7K+AIaQQlggEAsIVBIhjKt65fT+Zynj7gF8Cg==
|
||||||
|
|
||||||
|
"@react-native-community/hooks@^2.5.1":
|
||||||
|
version "2.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-native-community/hooks/-/hooks-2.5.1.tgz#545c76d1a6203532a8e776578bbaaa64bb754cf6"
|
||||||
|
integrity sha512-P9gwIUGpa/h8p5ASwY8QFTthXw/e/rt4mzZRfe3Xh5L13mTuOFXsYVwe9f8JAUx512cUKUsdTg6Dsg3/jTlxeg==
|
||||||
|
|
||||||
"@react-native-community/masked-view@^0.1.6":
|
"@react-native-community/masked-view@^0.1.6":
|
||||||
version "0.1.9"
|
version "0.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.9.tgz#383aca2fb053e3e14405c99cce2d5805df730821"
|
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.9.tgz#383aca2fb053e3e14405c99cce2d5805df730821"
|
||||||
|
@ -6557,10 +6562,10 @@ react-native-redash@^14.0.3:
|
||||||
normalize-svg-path "^1.0.1"
|
normalize-svg-path "^1.0.1"
|
||||||
parse-svg-path "^0.1.2"
|
parse-svg-path "^0.1.2"
|
||||||
|
|
||||||
react-native-safe-area-context@^0.7.3:
|
react-native-safe-area-context@^2.0.0:
|
||||||
version "0.7.3"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-0.7.3.tgz#ad6bd4abbabe195332c53810e4ce5851eb21aa2a"
|
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-2.0.0.tgz#7ef48e5a83a1e2f7fe9d5321493822b6765fd1ab"
|
||||||
integrity sha512-9Uqu1vlXPi+2cKW/CW6OnHxA76mWC4kF3wvlqzq4DY8hn37AeiXtLFs2WkxH4yXQRrnJdP6ivc65Lz+MqwRZAA==
|
integrity sha512-5VtCI3Nluzm7QfTcB/3j4YeWqt25QO1u5KTA1jEg1ckJzV19qCZFyHIpCCkS5+VEX+2JEHfdczhCdwE5sPgyEw==
|
||||||
|
|
||||||
react-native-screens@^2.3.0:
|
react-native-screens@^2.3.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
|
|
|
@ -18,46 +18,53 @@
|
||||||
(clj->js {:NativeModules {:RNGestureHandlerModule {:Direction (fn [])}
|
(clj->js {:NativeModules {:RNGestureHandlerModule {:Direction (fn [])}
|
||||||
:ReanimatedModule {:configureProps (fn [])}}
|
:ReanimatedModule {:configureProps (fn [])}}
|
||||||
|
|
||||||
:View {}
|
:View {}
|
||||||
:FlatList {}
|
:FlatList {}
|
||||||
:Text {}
|
:Text {}
|
||||||
:ProgressBarAndroid {}
|
:ProgressBarAndroid {}
|
||||||
:StatusBar {}
|
:StatusBar {}
|
||||||
:ScrollView {}
|
:ScrollView {}
|
||||||
:KeyboardAvoidingView {}
|
:KeyboardAvoidingView {}
|
||||||
:TextInput {}
|
:TextInput {}
|
||||||
:Image {}
|
:Image {}
|
||||||
:Picker {:Item {}}
|
:Picker {:Item {}}
|
||||||
:Switch {}
|
:Switch {}
|
||||||
:Modal {}
|
:Modal {}
|
||||||
:Keyboard {:dismiss (fn [])}
|
:Keyboard {:dismiss (fn [])}
|
||||||
:Linking {}
|
:Linking {}
|
||||||
:TouchableWithoutFeedback {}
|
:TouchableWithoutFeedback {}
|
||||||
:TouchableHighlight {}
|
:TouchableHighlight {}
|
||||||
:TouchableOpacity {}
|
:TouchableOpacity {}
|
||||||
:ActivityIndicator {}
|
:ActivityIndicator {}
|
||||||
:StyleSheet {:create (fn [])}
|
:StyleSheet {:create (fn [])}
|
||||||
:Animated {:createAnimatedComponent identity
|
:Animated {:createAnimatedComponent identity
|
||||||
:Value (fn [])
|
:Value (fn [])
|
||||||
:ValueXY (fn [])
|
:ValueXY (fn [])
|
||||||
:View {}
|
:View {}
|
||||||
:FlatList {}
|
:FlatList {}
|
||||||
:ScrollView {}
|
:ScrollView {}
|
||||||
:Text {}}
|
:Text {}}
|
||||||
:Easing {:bezier (fn [])
|
:Easing {:bezier (fn [])
|
||||||
:poly (fn [])
|
:poly (fn [])
|
||||||
:out (fn [])
|
:out (fn [])
|
||||||
:in (fn [])
|
:in (fn [])
|
||||||
:inOut (fn [])}
|
:inOut (fn [])}
|
||||||
:DeviceEventEmitter {:addListener (fn [])}
|
:DeviceEventEmitter {:addListener (fn [])}
|
||||||
:Dimensions {:get (fn [])}
|
:Dimensions {:get (fn [])}
|
||||||
:Platform {:select (fn [])}
|
:useWindowDimensions {}
|
||||||
:I18nManager {:isRTL ""}
|
:Platform {:select (fn [])}
|
||||||
:NativeEventEmitter (fn [])
|
:I18nManager {:isRTL ""}
|
||||||
:requireNativeComponent (fn [] {:propTypes ""})}))
|
:NativeEventEmitter (fn [])
|
||||||
|
:LayoutAnimation {:Presets {:easeInEaseOut nil
|
||||||
|
:linear nil
|
||||||
|
:spring nil}
|
||||||
|
:configureNext (fn [])}
|
||||||
|
:requireNativeComponent (fn [] {:propTypes ""})}))
|
||||||
|
|
||||||
(set! js/ReactNative react-native)
|
(set! js/ReactNative react-native)
|
||||||
|
|
||||||
|
(def reanimated-bottom-sheet #js {:default #js {}})
|
||||||
|
|
||||||
(def vector-icons #js {:default #js {}})
|
(def vector-icons #js {:default #js {}})
|
||||||
(def webview #js {:WebView #js {}})
|
(def webview #js {:WebView #js {}})
|
||||||
(def status-keycard #js {:default #js {}})
|
(def status-keycard #js {:default #js {}})
|
||||||
|
@ -88,7 +95,7 @@
|
||||||
(def net-info #js {})
|
(def net-info #js {})
|
||||||
(def touchid #js {})
|
(def touchid #js {})
|
||||||
(def safe-area-context (clj->js {:SafeAreaProvider {:_reactNativeIphoneXHelper {:getStatusBarHeight (fn [])}}
|
(def safe-area-context (clj->js {:SafeAreaProvider {:_reactNativeIphoneXHelper {:getStatusBarHeight (fn [])}}
|
||||||
:SafeAreaConsumer {}
|
:SafeAreaInsetsContext {:Consumer (fn [])}
|
||||||
:SafeAreaView {}}))
|
:SafeAreaView {}}))
|
||||||
(def react-native-dark-mode #js {"eventEmitter" {} "initialMode" {}})
|
(def react-native-dark-mode #js {"eventEmitter" {} "initialMode" {}})
|
||||||
|
|
||||||
|
@ -112,7 +119,12 @@
|
||||||
(def react-native-reanimated #js {:default #js {:createAnimatedComponent identity
|
(def react-native-reanimated #js {:default #js {:createAnimatedComponent identity
|
||||||
:eq nil
|
:eq nil
|
||||||
:greaterOrEq nil
|
:greaterOrEq nil
|
||||||
|
:greaterThan nil
|
||||||
|
:lessThan nil
|
||||||
|
:lessOrEq nil
|
||||||
:add nil
|
:add nil
|
||||||
|
:diff nil
|
||||||
|
:divide nil
|
||||||
:sub nil
|
:sub nil
|
||||||
:multiply nil
|
:multiply nil
|
||||||
:abs nil
|
:abs nil
|
||||||
|
@ -190,5 +202,7 @@
|
||||||
"react-native-fs" fs
|
"react-native-fs" fs
|
||||||
"react-native-mail" react-native-mail
|
"react-native-mail" react-native-mail
|
||||||
"react-native-image-resizer" image-resizer
|
"react-native-image-resizer" image-resizer
|
||||||
|
"react-native-haptic-feedback" react-native-haptic-feedback
|
||||||
|
"reanimated-bottom-sheet" reanimated-bottom-sheet
|
||||||
"./fleets.js" default-fleets
|
"./fleets.js" default-fleets
|
||||||
nil))
|
nil))
|
||||||
|
|
|
@ -1,25 +1,44 @@
|
||||||
(ns quo.animated
|
(ns quo.animated
|
||||||
(:refer-clojure :exclude [set])
|
(:refer-clojure :exclude [set divide])
|
||||||
(:require [reagent.core :as reagent]
|
(:require [reagent.core :as reagent]
|
||||||
|
[quo.gesture-handler :as gh]
|
||||||
[oops.core :refer [oget ocall]]
|
[oops.core :refer [oget ocall]]
|
||||||
["react-native-reanimated" :default animated :refer (clockRunning Easing)]
|
["react-native-reanimated" :default animated :refer (clockRunning Easing)]
|
||||||
["react-native-redash" :as redash]))
|
["react-native-redash" :as redash]
|
||||||
|
quo.react)
|
||||||
|
(:require-macros [quo.react :refer [maybe-js-deps]]))
|
||||||
|
|
||||||
(def view (reagent/adapt-react-class (.-View animated)))
|
(def view (reagent/adapt-react-class (.-View animated)))
|
||||||
(def text (reagent/adapt-react-class (.-Text animated)))
|
(def text (reagent/adapt-react-class (.-Text animated)))
|
||||||
(def scroll-view (reagent/adapt-react-class (.-ScrollView animated)))
|
(def scroll-view (reagent/adapt-react-class (.-ScrollView animated)))
|
||||||
(def code (reagent/adapt-react-class (.-Code animated)))
|
(def code (reagent/adapt-react-class (.-Code animated)))
|
||||||
|
|
||||||
|
(def useCode (.-useCode animated))
|
||||||
|
|
||||||
|
(defn code!
|
||||||
|
([setup-fn]
|
||||||
|
(useCode
|
||||||
|
(fn [] (setup-fn))))
|
||||||
|
([setup-fn deps]
|
||||||
|
(useCode
|
||||||
|
(fn [] (setup-fn))
|
||||||
|
(maybe-js-deps deps))))
|
||||||
|
|
||||||
(def eq (oget animated "eq"))
|
(def eq (oget animated "eq"))
|
||||||
(def neq (oget animated "neq"))
|
(def neq (oget animated "neq"))
|
||||||
|
(def greater (oget animated "greaterThan"))
|
||||||
(def greater-or-eq (oget animated "greaterOrEq"))
|
(def greater-or-eq (oget animated "greaterOrEq"))
|
||||||
|
(def less (oget animated "lessThan"))
|
||||||
|
(def less-or-eq (oget animated "lessOrEq"))
|
||||||
(def not* (oget animated "not"))
|
(def not* (oget animated "not"))
|
||||||
(def or* (oget animated "or"))
|
(def or* (oget animated "or"))
|
||||||
(def and* (oget animated "and"))
|
(def and* (oget animated "and"))
|
||||||
|
|
||||||
|
(def diff (oget animated "diff"))
|
||||||
(def add (oget animated "add"))
|
(def add (oget animated "add"))
|
||||||
(def sub (oget animated "sub"))
|
(def sub (oget animated "sub"))
|
||||||
(def multiply (oget animated "multiply"))
|
(def multiply (oget animated "multiply"))
|
||||||
|
(def divide (oget animated "divide"))
|
||||||
(def abs (oget animated "abs"))
|
(def abs (oget animated "abs"))
|
||||||
|
|
||||||
(def min* (oget animated "min"))
|
(def min* (oget animated "min"))
|
||||||
|
@ -66,13 +85,13 @@
|
||||||
|
|
||||||
(defn cond*
|
(defn cond*
|
||||||
([condition node]
|
([condition node]
|
||||||
(ocall animated "cond"
|
(.cond ^js animated
|
||||||
condition
|
condition
|
||||||
(if (vector? node)
|
(if (vector? node)
|
||||||
(clj->js node)
|
(clj->js node)
|
||||||
node)))
|
node)))
|
||||||
([condition if-node else-node]
|
([condition if-node else-node]
|
||||||
(ocall animated "cond"
|
(.cond ^js animated
|
||||||
condition
|
condition
|
||||||
(if (vector? if-node)
|
(if (vector? if-node)
|
||||||
(clj->js if-node)
|
(clj->js if-node)
|
||||||
|
@ -109,6 +128,15 @@
|
||||||
(defn with-spring [config]
|
(defn with-spring [config]
|
||||||
(ocall redash "withSpring" (clj->js config)))
|
(ocall redash "withSpring" (clj->js config)))
|
||||||
|
|
||||||
|
(defn with-decay [config]
|
||||||
|
(.withDecay ^js redash (clj->js config)))
|
||||||
|
|
||||||
|
(defn with-offset [config]
|
||||||
|
(.withOffset ^js redash (clj->js config)))
|
||||||
|
|
||||||
|
(defn diff-clamp [node min max]
|
||||||
|
(.diffClamp ^js redash node min max))
|
||||||
|
|
||||||
(defn with-spring-transition [val config]
|
(defn with-spring-transition [val config]
|
||||||
(.withSpringTransition ^js redash val (clj->js config)))
|
(.withSpringTransition ^js redash val (clj->js config)))
|
||||||
|
|
||||||
|
@ -116,7 +144,10 @@
|
||||||
(.withTimingTransition ^js redash val (clj->js config)))
|
(.withTimingTransition ^js redash val (clj->js config)))
|
||||||
|
|
||||||
(defn re-timing [config]
|
(defn re-timing [config]
|
||||||
(ocall redash "timing" (clj->js config)))
|
(.timing ^js redash (clj->js config)))
|
||||||
|
|
||||||
|
(defn re-spring [config]
|
||||||
|
(.spring ^js redash (clj->js config)))
|
||||||
|
|
||||||
(defn on-scroll [opts]
|
(defn on-scroll [opts]
|
||||||
(ocall redash "onScrollEvent" (clj->js opts)))
|
(ocall redash "onScrollEvent" (clj->js opts)))
|
||||||
|
@ -131,3 +162,47 @@
|
||||||
|
|
||||||
(defn loop* [opts]
|
(defn loop* [opts]
|
||||||
(ocall redash "loop" (clj->js opts)))
|
(ocall redash "loop" (clj->js opts)))
|
||||||
|
|
||||||
|
(defn use-value [value]
|
||||||
|
(.useValue ^js redash value))
|
||||||
|
|
||||||
|
(defn use-clock []
|
||||||
|
(.useClock ^js redash))
|
||||||
|
|
||||||
|
(defn snap-point [value velocity snap-points]
|
||||||
|
(.snapPoint ^js redash value velocity (to-array snap-points)))
|
||||||
|
|
||||||
|
(defn with-easing
|
||||||
|
[{val :value
|
||||||
|
:keys [snap-points velocity offset state easing duration on-snap]
|
||||||
|
:or {duration 250
|
||||||
|
easing (:ease-out easings)}}]
|
||||||
|
(let [position (value 0)
|
||||||
|
c (clock)
|
||||||
|
animation-over (value 1)
|
||||||
|
interrupted (and* (eq state (:began gh/states))
|
||||||
|
(clock-running c))
|
||||||
|
vel (multiply velocity 1.5)
|
||||||
|
to (snap-point position vel snap-points)
|
||||||
|
finish-animation [(set offset position)
|
||||||
|
(stop-clock c)
|
||||||
|
(call* [position] on-snap)
|
||||||
|
(set animation-over 1)]]
|
||||||
|
(block
|
||||||
|
[(cond* interrupted finish-animation)
|
||||||
|
(cond* animation-over
|
||||||
|
(set position offset))
|
||||||
|
(cond* (neq state (:end gh/states))
|
||||||
|
[(set animation-over 0)
|
||||||
|
(set position (add offset val))])
|
||||||
|
(cond* (and* (eq state (:end gh/states))
|
||||||
|
(not* animation-over))
|
||||||
|
[(set position (re-timing
|
||||||
|
{:clock c
|
||||||
|
:easing easing
|
||||||
|
:duration duration
|
||||||
|
:from position
|
||||||
|
:to to}))
|
||||||
|
(cond* (not* (clock-running c))
|
||||||
|
finish-animation)])
|
||||||
|
position])))
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
(ns quo.components.bottom-sheet.style
|
||||||
|
(:require [quo.design-system.colors :as colors]
|
||||||
|
[quo.design-system.spacing :as spacing]))
|
||||||
|
|
||||||
|
(def border-radius 16)
|
||||||
|
(def vertical-padding (:tiny spacing/spacing))
|
||||||
|
(def margin-top 56)
|
||||||
|
|
||||||
|
(def container
|
||||||
|
{:position :absolute
|
||||||
|
:left 0
|
||||||
|
:top 0
|
||||||
|
:right 0
|
||||||
|
:bottom 0
|
||||||
|
:flex 1
|
||||||
|
:justify-content :flex-end})
|
||||||
|
|
||||||
|
(defn backdrop []
|
||||||
|
{:flex 1
|
||||||
|
:position :absolute
|
||||||
|
:left 0
|
||||||
|
:top 0
|
||||||
|
:right 0
|
||||||
|
:bottom 0
|
||||||
|
:background-color (:backdrop @colors/theme)})
|
||||||
|
|
||||||
|
(defn content-container
|
||||||
|
[window-height]
|
||||||
|
{:background-color (:ui-background @colors/theme)
|
||||||
|
:border-top-left-radius border-radius
|
||||||
|
:border-top-right-radius border-radius
|
||||||
|
:height (* window-height 2)})
|
||||||
|
|
||||||
|
(def content-header
|
||||||
|
{:height border-radius
|
||||||
|
:align-self :stretch
|
||||||
|
:justify-content :center
|
||||||
|
:align-items :center})
|
||||||
|
|
||||||
|
(def handle
|
||||||
|
{:width 31
|
||||||
|
:height 4
|
||||||
|
:background-color (:icon-02 @colors/theme)
|
||||||
|
:opacity 0.4
|
||||||
|
:border-radius 2})
|
|
@ -0,0 +1,191 @@
|
||||||
|
(ns quo.components.bottom-sheet.view
|
||||||
|
(:require [reagent.core :as reagent]
|
||||||
|
[quo.animated :as animated]
|
||||||
|
[quo.react-native :as rn]
|
||||||
|
[quo.react :as react]
|
||||||
|
[quo.platform :as platform]
|
||||||
|
[cljs-bean.core :as bean]
|
||||||
|
[quo.components.safe-area :as safe-area]
|
||||||
|
[quo.components.bottom-sheet.style :as styles]
|
||||||
|
[quo.gesture-handler :as gesture-handler]))
|
||||||
|
|
||||||
|
(def opacity-coeff 0.8)
|
||||||
|
(def close-duration 150)
|
||||||
|
(def spring-config {:damping 15
|
||||||
|
:mass 0.7
|
||||||
|
:stiffness 150
|
||||||
|
:overshootClamping false
|
||||||
|
:restSpeedThreshold 0.1
|
||||||
|
:restDisplacementThreshold 0.1})
|
||||||
|
|
||||||
|
;; NOTE(Ferossgp): RNGH does not work in modal react native
|
||||||
|
(defn modal [{:keys [visible] :as props} & children]
|
||||||
|
(if platform/android?
|
||||||
|
(when visible (into [:<>] children))
|
||||||
|
(into [rn/modal props] children)))
|
||||||
|
|
||||||
|
(defn bottom-sheet-raw [props]
|
||||||
|
(let [{on-cancel :onCancel
|
||||||
|
disable-drag? :disableDrag?
|
||||||
|
show-handle? :showHandle?
|
||||||
|
visible? :visible?
|
||||||
|
backdrop-dismiss? :backdropDismiss?
|
||||||
|
back-button-cancel :backButtonCancel
|
||||||
|
children :children
|
||||||
|
:or {show-handle? true
|
||||||
|
backdrop-dismiss? true
|
||||||
|
back-button-cancel true}}
|
||||||
|
(bean/bean props)
|
||||||
|
|
||||||
|
{window-height :height} (rn/use-window-dimensions)
|
||||||
|
safe-area (safe-area/use-safe-area)
|
||||||
|
max-height (- window-height (:top safe-area) styles/margin-top)
|
||||||
|
content-height (react/state 0)
|
||||||
|
visible (react/state false)
|
||||||
|
|
||||||
|
on-close (fn []
|
||||||
|
(when @visible
|
||||||
|
(reset! visible false)
|
||||||
|
(when on-cancel (on-cancel))))
|
||||||
|
|
||||||
|
master-translation-y (animated/use-value 0)
|
||||||
|
master-velocity-y (animated/use-value (:undetermined gesture-handler/states))
|
||||||
|
master-state (animated/use-value (:undetermined gesture-handler/states))
|
||||||
|
tap-state (animated/use-value 0)
|
||||||
|
manual-open (animated/use-value 0)
|
||||||
|
manual-close (animated/value 0)
|
||||||
|
offset (animated/use-value 0)
|
||||||
|
clock (animated/use-clock)
|
||||||
|
body-ref (react/create-ref)
|
||||||
|
master-ref (react/create-ref)
|
||||||
|
tap-gesture-handler (animated/on-gesture {:state tap-state})
|
||||||
|
on-master-event (animated/event [{:nativeEvent
|
||||||
|
{:translationY master-translation-y
|
||||||
|
:state master-state
|
||||||
|
:velocityY master-velocity-y}}])
|
||||||
|
on-body-event on-master-event
|
||||||
|
sheet-height (min max-height @content-height)
|
||||||
|
open-snap-point (* -1 sheet-height)
|
||||||
|
close-snap-point 0
|
||||||
|
close-sheet (fn []
|
||||||
|
(animated/set-value manual-close 1))
|
||||||
|
open-sheet (fn []
|
||||||
|
(animated/set-value manual-open 1))
|
||||||
|
on-snap (fn [pos]
|
||||||
|
(when (= close-snap-point (aget pos 0))
|
||||||
|
(on-close)))
|
||||||
|
resistance (animated/cond* (animated/less-or-eq master-translation-y 0)
|
||||||
|
(animated/divide master-translation-y 2)
|
||||||
|
master-translation-y)
|
||||||
|
translate-y (animated/with-easing
|
||||||
|
{:value resistance
|
||||||
|
:velocity master-velocity-y
|
||||||
|
:offset offset
|
||||||
|
:state master-state
|
||||||
|
:on-snap on-snap
|
||||||
|
:snap-points [open-snap-point close-snap-point]})
|
||||||
|
opacity (animated/cond*
|
||||||
|
open-snap-point
|
||||||
|
(animated/interpolate
|
||||||
|
translate-y
|
||||||
|
{:inputRange [(animated/multiply open-snap-point opacity-coeff) 0]
|
||||||
|
:outputRange [1 0]
|
||||||
|
:extrapolate (:clamp animated/extrapolate)}))
|
||||||
|
on-layout (fn [evt]
|
||||||
|
(->> ^js evt
|
||||||
|
.-nativeEvent
|
||||||
|
.-layout
|
||||||
|
.-height
|
||||||
|
(+ styles/border-radius)
|
||||||
|
(reset! content-height))
|
||||||
|
(js/requestAnimationFrame open-sheet))]
|
||||||
|
(animated/code!
|
||||||
|
(fn []
|
||||||
|
(animated/block
|
||||||
|
[(animated/on-change tap-state
|
||||||
|
[(animated/cond* (animated/and* (animated/eq tap-state (:end gesture-handler/states))
|
||||||
|
(animated/not* manual-close))
|
||||||
|
[(animated/set manual-close 1)
|
||||||
|
(animated/set tap-state (:undetermined gesture-handler/states))])])
|
||||||
|
(animated/cond* manual-open
|
||||||
|
[(animated/set offset
|
||||||
|
(animated/re-spring {:from offset
|
||||||
|
:to open-snap-point
|
||||||
|
:clock clock
|
||||||
|
:config spring-config}))
|
||||||
|
(animated/cond* (animated/not* (animated/clock-running clock))
|
||||||
|
(animated/set manual-open 0))])
|
||||||
|
(animated/cond* (animated/and* manual-close
|
||||||
|
(animated/not* manual-open))
|
||||||
|
[(animated/set offset
|
||||||
|
(animated/re-timing {:from offset
|
||||||
|
:to close-snap-point
|
||||||
|
:clock clock
|
||||||
|
:easing (:ease-out animated/easings)
|
||||||
|
:duration close-duration}))
|
||||||
|
(animated/cond* (animated/not* (animated/clock-running clock))
|
||||||
|
[(animated/set manual-close 0)
|
||||||
|
(animated/call* [] on-close)])])]))
|
||||||
|
[open-snap-point on-close])
|
||||||
|
;; NOTE(Ferossgp): Remove me when RNGH will suport modal
|
||||||
|
(rn/use-back-handler
|
||||||
|
(fn []
|
||||||
|
(when back-button-cancel
|
||||||
|
(close-sheet))
|
||||||
|
true))
|
||||||
|
(react/effect!
|
||||||
|
(fn []
|
||||||
|
(cond
|
||||||
|
visible?
|
||||||
|
(do
|
||||||
|
(rn/dismiss-keyboard!)
|
||||||
|
(reset! visible visible?))
|
||||||
|
|
||||||
|
@visible
|
||||||
|
(close-sheet)))
|
||||||
|
[visible?])
|
||||||
|
(reagent/as-element
|
||||||
|
[rn/modal {:visible @visible
|
||||||
|
:transparent true
|
||||||
|
:status-bar-translucent true
|
||||||
|
:presentation-style :overFullScreen
|
||||||
|
:hardware-accelerated true
|
||||||
|
:on-request-close (fn []
|
||||||
|
(when back-button-cancel
|
||||||
|
(close-sheet)))}
|
||||||
|
[rn/view {:style styles/container
|
||||||
|
:pointer-events :box-none}
|
||||||
|
[gesture-handler/tap-gesture-handler (merge {:enabled backdrop-dismiss?}
|
||||||
|
tap-gesture-handler)
|
||||||
|
[animated/view {:style (merge (styles/backdrop)
|
||||||
|
{:opacity opacity})}]]
|
||||||
|
[animated/view {:style (merge (styles/content-container window-height)
|
||||||
|
{:transform [{:translateY translate-y}
|
||||||
|
{:translateY (* window-height 2)}]})}
|
||||||
|
[gesture-handler/pan-gesture-handler {:ref master-ref
|
||||||
|
:wait-for body-ref
|
||||||
|
:enabled (not disable-drag?)
|
||||||
|
:onGestureEvent on-master-event
|
||||||
|
:onHandlerStateChange on-master-event}
|
||||||
|
[animated/view {:style styles/content-header}
|
||||||
|
(when show-handle?
|
||||||
|
[rn/view {:style styles/handle}])]]
|
||||||
|
[gesture-handler/pan-gesture-handler {:ref body-ref
|
||||||
|
:wait-for master-ref
|
||||||
|
:enabled (and (not disable-drag?)
|
||||||
|
(not= sheet-height max-height))
|
||||||
|
:onGestureEvent on-body-event
|
||||||
|
:onHandlerStateChange on-body-event}
|
||||||
|
[animated/view {:pointer-events :box-none
|
||||||
|
:height sheet-height}
|
||||||
|
[animated/scroll-view {:bounces false
|
||||||
|
:flex 1
|
||||||
|
:scroll-enabled (= sheet-height max-height)}
|
||||||
|
[animated/view {:style {:padding-top styles/vertical-padding
|
||||||
|
:padding-bottom (+ styles/vertical-padding
|
||||||
|
(:bottom safe-area))}
|
||||||
|
:on-layout on-layout}
|
||||||
|
(into [:<>] (react/get-children children))]]]]]]])))
|
||||||
|
|
||||||
|
(defn bottom-sheet [props & children]
|
||||||
|
(into [:> bottom-sheet-raw props] children))
|
|
@ -1,10 +1,10 @@
|
||||||
(ns quo.components.safe-area
|
(ns quo.components.safe-area
|
||||||
(:require ["react-native-safe-area-context" :as safe-area-context
|
(:require ["react-native-safe-area-context" :as safe-area-context
|
||||||
:refer (SafeAreaView SafeAreaProvider SafeAreaConsumer)]
|
:refer (SafeAreaView SafeAreaProvider SafeAreaInsetsContext useSafeAreaInsets)]
|
||||||
[reagent.core :as reagent]))
|
[reagent.core :as reagent]))
|
||||||
|
|
||||||
(def provider (reagent/adapt-react-class SafeAreaProvider))
|
(def provider (reagent/adapt-react-class SafeAreaProvider))
|
||||||
(def ^:private consumer-raw (reagent/adapt-react-class SafeAreaConsumer))
|
(def ^:private consumer-raw (reagent/adapt-react-class (.-Consumer ^js SafeAreaInsetsContext)))
|
||||||
(def view (reagent/adapt-react-class SafeAreaView))
|
(def view (reagent/adapt-react-class SafeAreaView))
|
||||||
|
|
||||||
(defn consumer [component]
|
(defn consumer [component]
|
||||||
|
@ -12,3 +12,10 @@
|
||||||
(fn [insets]
|
(fn [insets]
|
||||||
(reagent/as-element
|
(reagent/as-element
|
||||||
[component (js->clj insets :keywordize-keys true)]))])
|
[component (js->clj insets :keywordize-keys true)]))])
|
||||||
|
|
||||||
|
(defn use-safe-area []
|
||||||
|
(let [insets (useSafeAreaInsets)]
|
||||||
|
{:top (.-top ^js insets)
|
||||||
|
:bottom (.-bottom ^js insets)
|
||||||
|
:left (.-left ^js insets)
|
||||||
|
:right (.-right ^js insets)}))
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
[quo.components.button.view :as button]
|
[quo.components.button.view :as button]
|
||||||
[quo.components.list.header :as list-header]
|
[quo.components.list.header :as list-header]
|
||||||
[quo.components.list.footer :as list-footer]
|
[quo.components.list.footer :as list-footer]
|
||||||
[quo.components.list.item :as list-item]))
|
[quo.components.list.item :as list-item]
|
||||||
|
[quo.components.bottom-sheet.view :as bottom-sheet]))
|
||||||
|
|
||||||
(def text text/text)
|
(def text text/text)
|
||||||
(def header header/header)
|
(def header header/header)
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
(def list-header list-header/header)
|
(def list-header list-header/header)
|
||||||
(def list-footer list-footer/footer)
|
(def list-footer list-footer/footer)
|
||||||
(def list-item list-item/list-item)
|
(def list-item list-item/list-item)
|
||||||
|
(def bottom-sheet bottom-sheet/bottom-sheet)
|
||||||
(def safe-area-provider safe-area/provider)
|
(def safe-area-provider safe-area/provider)
|
||||||
(def safe-area-consumer safe-area/consumer)
|
(def safe-area-consumer safe-area/consumer)
|
||||||
(def safe-area-view safe-area/view)
|
(def safe-area-view safe-area/view)
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
:icon-04 "rgba(67,96,223,1)" ; Interactive icon
|
:icon-04 "rgba(67,96,223,1)" ; Interactive icon
|
||||||
:icon-05 "rgba(255,255,255,1)" ; Icons inverse on accent background
|
:icon-05 "rgba(255,255,255,1)" ; Icons inverse on accent background
|
||||||
:shadow-01 "rgba(0,9,26,0.12)" ; Main shadow color
|
:shadow-01 "rgba(0,9,26,0.12)" ; Main shadow color
|
||||||
|
:backdrop "rgba(0,0,0,0.4)" ; Backdrop for modals and bottom sheet
|
||||||
})
|
})
|
||||||
|
|
||||||
(def dark-theme
|
(def dark-theme
|
||||||
|
@ -64,6 +65,7 @@
|
||||||
:icon-03 "rgba(255,255,255,0.4)"
|
:icon-03 "rgba(255,255,255,0.4)"
|
||||||
:icon-04 "rgba(97,119,229,1)"
|
:icon-04 "rgba(97,119,229,1)"
|
||||||
:icon-05 "rgba(20,20,20,1)"
|
:icon-05 "rgba(20,20,20,1)"
|
||||||
:shadow-01 "rgba(0,0,0,0.75)"})
|
:shadow-01 "rgba(0,0,0,0.75)"
|
||||||
|
:backdrop "rgba(0,0,0,0.4)"})
|
||||||
|
|
||||||
(def theme (reagent/atom light-theme))
|
(def theme (reagent/atom light-theme))
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
(ns quo.previews.bottom-sheet
|
||||||
|
(:require [reagent.core :as reagent]
|
||||||
|
[quo.core :as quo]
|
||||||
|
[quo.react-native :as rn]
|
||||||
|
[quo.design-system.colors :as colors]
|
||||||
|
[quo.previews.preview :as preview]))
|
||||||
|
|
||||||
|
(def descriptor [{:label "Show handle:"
|
||||||
|
:key :show-handle?
|
||||||
|
:type :boolean}
|
||||||
|
{:label "Backdrop dismiss:"
|
||||||
|
:key :backdrop-dismiss?
|
||||||
|
:type :boolean}
|
||||||
|
{:label "Disable drag:"
|
||||||
|
:key :disable-drag?
|
||||||
|
:type :boolean}
|
||||||
|
{:label "Android back cancel:"
|
||||||
|
:key :back-button-cancel
|
||||||
|
:type :boolean}
|
||||||
|
{:label "Scrollable:"
|
||||||
|
:key :scrollable
|
||||||
|
:type :boolean}])
|
||||||
|
|
||||||
|
(defn cool-preview []
|
||||||
|
(let [state (reagent/atom {:show-handle? true
|
||||||
|
:backdrop-dismiss? true
|
||||||
|
:disable-drag? false
|
||||||
|
:back-button-cancel true})
|
||||||
|
visible (reagent/atom false)
|
||||||
|
scrollable (reagent/cursor state [:scrollable])]
|
||||||
|
(fn []
|
||||||
|
[rn/view {:margin-bottom 50
|
||||||
|
:padding 16}
|
||||||
|
[preview/customizer state descriptor]
|
||||||
|
[:<>
|
||||||
|
[rn/view {:style {:align-items :center
|
||||||
|
:padding 16}}
|
||||||
|
[rn/touchable-opacity {:on-press #(reset! visible true)}
|
||||||
|
[rn/view {:style {:padding-horizontal 16
|
||||||
|
:padding-vertical 8
|
||||||
|
:border-radius 4
|
||||||
|
:background-color (:interactive-01 @colors/theme)}}
|
||||||
|
[quo/text {:color :secondary-inverse}
|
||||||
|
(str "Open sheet: " @visible)]]]]
|
||||||
|
|
||||||
|
[quo/bottom-sheet (merge @state
|
||||||
|
{:visible? @visible
|
||||||
|
:on-cancel #(reset! visible false)})
|
||||||
|
[rn/view {:style {:height (if @scrollable 1200 400)
|
||||||
|
:justify-content :center
|
||||||
|
:align-items :center}}
|
||||||
|
[rn/touchable-opacity {:on-press #(reset! visible false)}
|
||||||
|
[quo/text {:color :link} "Close"]]
|
||||||
|
[rn/touchable-opacity {:on-press #(swap! scrollable not)
|
||||||
|
:style {:padding-vertical 16}}
|
||||||
|
[quo/text {:color :link} "Toggle size"]]
|
||||||
|
[quo/text "Hello world!"]]]]])))
|
||||||
|
|
||||||
|
(defn preview []
|
||||||
|
(fn []
|
||||||
|
[rn/view {:background-color (:ui-background @colors/theme)
|
||||||
|
:flex 1}
|
||||||
|
[rn/flat-list {:flex 1
|
||||||
|
:keyboardShouldPersistTaps :always
|
||||||
|
:header [cool-preview]
|
||||||
|
:key-fn str}]]))
|
|
@ -5,6 +5,7 @@
|
||||||
[quo.previews.tooltip :as tooltip]
|
[quo.previews.tooltip :as tooltip]
|
||||||
[quo.previews.button :as button]
|
[quo.previews.button :as button]
|
||||||
[quo.previews.lists :as lists]
|
[quo.previews.lists :as lists]
|
||||||
|
[quo.previews.bottom-sheet :as bottom-sheet]
|
||||||
[quo.react-native :as rn]
|
[quo.react-native :as rn]
|
||||||
[quo.core :as quo]
|
[quo.core :as quo]
|
||||||
[reagent.core :as reagent]
|
[reagent.core :as reagent]
|
||||||
|
@ -29,7 +30,10 @@
|
||||||
:component button/preview-button}
|
:component button/preview-button}
|
||||||
{:name :lists
|
{:name :lists
|
||||||
:instes {:top false}
|
:instes {:top false}
|
||||||
:component lists/preview}])
|
:component lists/preview}
|
||||||
|
{:name :bottom-sheet
|
||||||
|
:insets {:top false}
|
||||||
|
:component bottom-sheet/preview}])
|
||||||
|
|
||||||
(defn theme-switcher []
|
(defn theme-switcher []
|
||||||
[rn/view {:style {:flex-direction :row
|
[rn/view {:style {:flex-direction :row
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
(ns quo.react)
|
||||||
|
|
||||||
|
(defmacro maybe-js-deps [deps]
|
||||||
|
`(if ~deps (into-array ~deps) js/undefined))
|
||||||
|
|
||||||
|
(defmacro with-deps-check [[prev-deps] f deps]
|
||||||
|
`(let [~prev-deps (quo.react/ref ~deps)]
|
||||||
|
(when (not= @~prev-deps ~deps)
|
||||||
|
(reset! ~prev-deps ~deps))
|
||||||
|
~f))
|
||||||
|
|
||||||
|
(defmacro with-effect
|
||||||
|
"Takes optional vector of dependencies and body to be executed in an effect."
|
||||||
|
[deps & body]
|
||||||
|
(let [[deps setup-fn] (if (vector? deps)
|
||||||
|
[deps body]
|
||||||
|
[nil (cons deps body)])]
|
||||||
|
`(effect! #(do ~@setup-fn) ~deps)))
|
||||||
|
|
||||||
|
(defmacro with-layout-effect
|
||||||
|
"Takes optional vector of dependencies and body to be executed in a layout effect."
|
||||||
|
[deps & body]
|
||||||
|
(let [[deps setup-fn] (if (vector? deps)
|
||||||
|
[deps body]
|
||||||
|
[nil (cons deps body)])]
|
||||||
|
`(layout-effect! #(do ~@setup-fn) ~deps)))
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,125 @@
|
||||||
(ns quo.react
|
(ns quo.react
|
||||||
(:require [oops.core :refer [oget]]
|
(:refer-clojure :exclude [ref])
|
||||||
["react" :as react]))
|
(:require [oops.core :refer [oget oset!]]
|
||||||
|
["react" :as react])
|
||||||
|
(:require-macros [quo.react :refer [with-deps-check
|
||||||
|
maybe-js-deps]]))
|
||||||
|
|
||||||
(def create-ref (oget react "createRef"))
|
(def create-ref react/createRef)
|
||||||
|
|
||||||
(defn current-ref [ref]
|
(defn current-ref [ref]
|
||||||
(oget ref "current"))
|
(oget ref "current"))
|
||||||
|
|
||||||
|
;; Inspired from UIX, Rum and Rumext
|
||||||
|
(defn set-ref-val!
|
||||||
|
[ref val]
|
||||||
|
(oset! ref "current" val)
|
||||||
|
val)
|
||||||
|
|
||||||
|
(deftype StateHook [value set-value]
|
||||||
|
cljs.core/IHash
|
||||||
|
(-hash [o] (goog/getUid o))
|
||||||
|
|
||||||
|
cljs.core/IDeref
|
||||||
|
(-deref [o]
|
||||||
|
value)
|
||||||
|
|
||||||
|
cljs.core/IReset
|
||||||
|
(-reset! [o new-value]
|
||||||
|
(set-value new-value))
|
||||||
|
|
||||||
|
cljs.core/ISwap
|
||||||
|
(-swap! [o f]
|
||||||
|
(set-value f))
|
||||||
|
(-swap! [o f a]
|
||||||
|
(set-value #(f % a)))
|
||||||
|
(-swap! [o f a b]
|
||||||
|
(set-value #(f % a b)))
|
||||||
|
(-swap! [o f a b xs]
|
||||||
|
(set-value #(apply f % a b xs))))
|
||||||
|
|
||||||
|
(defn state [value]
|
||||||
|
(let [[value set-value] (react/useState value)
|
||||||
|
sh (react/useMemo #(StateHook. value set-value) #js [])]
|
||||||
|
(react/useMemo (fn []
|
||||||
|
(set! (.-value sh) value)
|
||||||
|
(set! (.-set-value sh) set-value)
|
||||||
|
sh)
|
||||||
|
#js [value set-value])))
|
||||||
|
|
||||||
|
(defn use-ref [val]
|
||||||
|
(let [ref (react/useRef val)]
|
||||||
|
(reify
|
||||||
|
cljs.core/IHash
|
||||||
|
(-hash [_] (goog/getUid ref))
|
||||||
|
|
||||||
|
cljs.core/IDeref
|
||||||
|
(-deref [_]
|
||||||
|
(current-ref ref))
|
||||||
|
|
||||||
|
cljs.core/IReset
|
||||||
|
(-reset! [_ new-value]
|
||||||
|
(set-ref-val! ref new-value))
|
||||||
|
|
||||||
|
cljs.core/ISwap
|
||||||
|
(-swap! [_ f]
|
||||||
|
(-reset! ref (f (current-ref ref))))
|
||||||
|
(-swap! [_ f a]
|
||||||
|
(-reset! ref (f (current-ref ref) a)))
|
||||||
|
(-swap! [_ f a b]
|
||||||
|
(-reset! ref (f (current-ref ref) a b)))
|
||||||
|
(-swap! [_ f a b xs]
|
||||||
|
(-reset! ref (apply f (current-ref ref) a b xs))))))
|
||||||
|
|
||||||
|
(defn ref [value]
|
||||||
|
(let [vref (use-ref value)]
|
||||||
|
(react/useMemo (fn [] vref) #js [])))
|
||||||
|
|
||||||
|
(defn effect!
|
||||||
|
([setup-fn]
|
||||||
|
(react/useEffect
|
||||||
|
#(let [ret (setup-fn)]
|
||||||
|
(if (fn? ret) ret js/undefined))))
|
||||||
|
([setup-fn deps]
|
||||||
|
(with-deps-check [prev-deps*]
|
||||||
|
(react/useEffect
|
||||||
|
(fn []
|
||||||
|
(reset! prev-deps* deps)
|
||||||
|
(let [ret (setup-fn)]
|
||||||
|
(if (fn? ret) ret js/undefined)))
|
||||||
|
(maybe-js-deps @prev-deps*))
|
||||||
|
deps)))
|
||||||
|
|
||||||
|
(defn layout-effect!
|
||||||
|
([setup-fn]
|
||||||
|
(react/useLayoutEffect
|
||||||
|
#(let [ret (setup-fn)]
|
||||||
|
(if (fn? ret) ret js/undefined))))
|
||||||
|
([setup-fn deps]
|
||||||
|
(with-deps-check [prev-deps*]
|
||||||
|
(react/useLayoutEffect
|
||||||
|
(fn []
|
||||||
|
(reset! prev-deps* deps)
|
||||||
|
(let [ret (setup-fn)]
|
||||||
|
(if (fn? ret) ret js/undefined)))
|
||||||
|
(maybe-js-deps @prev-deps*))
|
||||||
|
deps)))
|
||||||
|
|
||||||
|
(defn callback
|
||||||
|
([f] (react/useCallback f))
|
||||||
|
([f deps]
|
||||||
|
(with-deps-check [prev-deps*]
|
||||||
|
(react/useCallback f (maybe-js-deps @prev-deps*))
|
||||||
|
deps)))
|
||||||
|
|
||||||
|
(defn memo
|
||||||
|
([f] (react/useMemo f))
|
||||||
|
([f deps]
|
||||||
|
(with-deps-check [prev-deps*]
|
||||||
|
(react/useMemo f (maybe-js-deps @prev-deps*))
|
||||||
|
deps)))
|
||||||
|
|
||||||
|
(defn get-children [^js children]
|
||||||
|
(->> children
|
||||||
|
(react/Children.toArray)
|
||||||
|
(into [])))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
(ns quo.react-native
|
(ns quo.react-native
|
||||||
(:require [reagent.core :as reagent]
|
(:require [reagent.core :as reagent]
|
||||||
["react-native" :as rn]))
|
["react-native" :as rn]
|
||||||
|
["@react-native-community/hooks" :as hooks]))
|
||||||
|
|
||||||
(def app-registry (.-AppRegistry rn))
|
(def app-registry (.-AppRegistry rn))
|
||||||
|
|
||||||
|
@ -15,9 +16,14 @@
|
||||||
|
|
||||||
(def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^js rn)))
|
(def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^js rn)))
|
||||||
(def touchable-highlight (reagent/adapt-react-class (.-TouchableHighlight ^js rn)))
|
(def touchable-highlight (reagent/adapt-react-class (.-TouchableHighlight ^js rn)))
|
||||||
|
(def touchable-without-feedback (reagent/adapt-react-class (.-TouchableWithoutFeedback ^js rn)))
|
||||||
(def text-input (reagent/adapt-react-class (.-TextInput ^js rn)))
|
(def text-input (reagent/adapt-react-class (.-TextInput ^js rn)))
|
||||||
|
|
||||||
|
(def keyboard-avoiding-view (reagent/adapt-react-class (.-KeyboardAvoidingView ^js rn)))
|
||||||
|
|
||||||
|
(def keyboard (.-Keyboard ^js rn))
|
||||||
|
(def dismiss-keyboard! #(.dismiss ^js keyboard))
|
||||||
|
|
||||||
(def ui-manager (.-UIManager ^js rn))
|
(def ui-manager (.-UIManager ^js rn))
|
||||||
|
|
||||||
(def layout-animation (.-LayoutAnimation ^js rn))
|
(def layout-animation (.-LayoutAnimation ^js rn))
|
||||||
|
@ -53,3 +59,14 @@
|
||||||
|
|
||||||
(defn flat-list [props]
|
(defn flat-list [props]
|
||||||
[rn-flat-list (base-list-props props)])
|
[rn-flat-list (base-list-props props)])
|
||||||
|
|
||||||
|
;; Hooks
|
||||||
|
|
||||||
|
(defn use-window-dimensions []
|
||||||
|
(let [window (rn/useWindowDimensions)]
|
||||||
|
{:font-scale (.-fontScale window)
|
||||||
|
:height (.-height ^js window)
|
||||||
|
:scale (.-scale ^js window)
|
||||||
|
:width (.-window ^js window)}))
|
||||||
|
|
||||||
|
(def use-back-handler (.-useBackHandler hooks))
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
["react-native" :as react-native :refer (Keyboard)]
|
["react-native" :as react-native :refer (Keyboard)]
|
||||||
["react-native-image-crop-picker" :default image-picker]
|
["react-native-image-crop-picker" :default image-picker]
|
||||||
["react-native-safe-area-context" :as safe-area-context
|
["react-native-safe-area-context" :as safe-area-context
|
||||||
:refer (SafeAreaView SafeAreaProvider SafeAreaConsumer)]
|
:refer (SafeAreaView SafeAreaProvider SafeAreaInsetsContext)]
|
||||||
["@react-native-community/clipboard" :default Clipboard])
|
["@react-native-community/clipboard" :default Clipboard])
|
||||||
(:require-macros [status-im.utils.views :as views]))
|
(:require-macros [status-im.utils.views :as views]))
|
||||||
|
|
||||||
|
@ -244,6 +244,6 @@
|
||||||
comp)))
|
comp)))
|
||||||
|
|
||||||
(def safe-area-provider (reagent/adapt-react-class SafeAreaProvider))
|
(def safe-area-provider (reagent/adapt-react-class SafeAreaProvider))
|
||||||
(def safe-area-consumer (reagent/adapt-react-class SafeAreaConsumer))
|
(def safe-area-consumer (reagent/adapt-react-class (.-Consumer ^js SafeAreaInsetsContext)))
|
||||||
|
|
||||||
(def safe-area-view (reagent/adapt-react-class SafeAreaView))
|
(def safe-area-view (reagent/adapt-react-class SafeAreaView))
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
|
|
||||||
(defn main []
|
(defn main []
|
||||||
(reagent/create-class
|
(reagent/create-class
|
||||||
{:component-did-mount utils.universal-links/initialize
|
{:component-did-mount utils.universal-links/initialize
|
||||||
:component-will-unmount utils.universal-links/finalize
|
:component-will-unmount utils.universal-links/finalize
|
||||||
:reagent-render
|
:reagent-render
|
||||||
(fn []
|
(fn []
|
||||||
|
|
Loading…
Reference in New Issue