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,6 +1,7 @@
|
|||
{:lint-as {status-im.utils.views/defview clojure.core/defn
|
||||
status-im.utils.views/letsubs clojure.core/let
|
||||
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
|
||||
status-im.utils.styles/def clojure.core/def
|
||||
status-im.utils.styles/defn clojure.core/defn
|
||||
|
|
|
@ -251,7 +251,7 @@ PODS:
|
|||
- React
|
||||
- react-native-netinfo (4.7.0):
|
||||
- React
|
||||
- react-native-safe-area-context (0.7.3):
|
||||
- react-native-safe-area-context (2.0.0):
|
||||
- React
|
||||
- react-native-shake (3.4.0):
|
||||
- React
|
||||
|
@ -585,7 +585,7 @@ SPEC CHECKSUMS:
|
|||
react-native-image-resizer: 4516052af6ae0248caf4ccf356caecefe60072d7
|
||||
react-native-mail: 7e37dfbe93ff0d4c7df346b738854dbed533e86f
|
||||
react-native-netinfo: ddaca8bbb9e6e914b1a23787ccb879bc642931c9
|
||||
react-native-safe-area-context: e200d4433aba6b7e60b52da5f37af11f7a0b0392
|
||||
react-native-safe-area-context: 60f654e00b6cc416573f6d5dbfce3839958eb57a
|
||||
react-native-shake: de052eaa3eadc4a326b8ddd7ac80c06e8d84528c
|
||||
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
||||
react-native-webview: cf5527893252b3b036eea024a1da6996f7344c74
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"dependencies": {
|
||||
"@react-native-community/cameraroll": "^1.6.1",
|
||||
"@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/netinfo": "^4.4.0",
|
||||
"@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-reanimated": "^1.7.0",
|
||||
"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-shake": "^3.3.1",
|
||||
"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"
|
||||
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":
|
||||
version "0.1.9"
|
||||
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"
|
||||
parse-svg-path "^0.1.2"
|
||||
|
||||
react-native-safe-area-context@^0.7.3:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-0.7.3.tgz#ad6bd4abbabe195332c53810e4ce5851eb21aa2a"
|
||||
integrity sha512-9Uqu1vlXPi+2cKW/CW6OnHxA76mWC4kF3wvlqzq4DY8hn37AeiXtLFs2WkxH4yXQRrnJdP6ivc65Lz+MqwRZAA==
|
||||
react-native-safe-area-context@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-2.0.0.tgz#7ef48e5a83a1e2f7fe9d5321493822b6765fd1ab"
|
||||
integrity sha512-5VtCI3Nluzm7QfTcB/3j4YeWqt25QO1u5KTA1jEg1ckJzV19qCZFyHIpCCkS5+VEX+2JEHfdczhCdwE5sPgyEw==
|
||||
|
||||
react-native-screens@^2.3.0:
|
||||
version "2.5.0"
|
||||
|
|
|
@ -51,13 +51,20 @@
|
|||
:inOut (fn [])}
|
||||
:DeviceEventEmitter {:addListener (fn [])}
|
||||
:Dimensions {:get (fn [])}
|
||||
:useWindowDimensions {}
|
||||
:Platform {:select (fn [])}
|
||||
:I18nManager {:isRTL ""}
|
||||
:NativeEventEmitter (fn [])
|
||||
:LayoutAnimation {:Presets {:easeInEaseOut nil
|
||||
:linear nil
|
||||
:spring nil}
|
||||
:configureNext (fn [])}
|
||||
:requireNativeComponent (fn [] {:propTypes ""})}))
|
||||
|
||||
(set! js/ReactNative react-native)
|
||||
|
||||
(def reanimated-bottom-sheet #js {:default #js {}})
|
||||
|
||||
(def vector-icons #js {:default #js {}})
|
||||
(def webview #js {:WebView #js {}})
|
||||
(def status-keycard #js {:default #js {}})
|
||||
|
@ -88,7 +95,7 @@
|
|||
(def net-info #js {})
|
||||
(def touchid #js {})
|
||||
(def safe-area-context (clj->js {:SafeAreaProvider {:_reactNativeIphoneXHelper {:getStatusBarHeight (fn [])}}
|
||||
:SafeAreaConsumer {}
|
||||
:SafeAreaInsetsContext {:Consumer (fn [])}
|
||||
:SafeAreaView {}}))
|
||||
(def react-native-dark-mode #js {"eventEmitter" {} "initialMode" {}})
|
||||
|
||||
|
@ -112,7 +119,12 @@
|
|||
(def react-native-reanimated #js {:default #js {:createAnimatedComponent identity
|
||||
:eq nil
|
||||
:greaterOrEq nil
|
||||
:greaterThan nil
|
||||
:lessThan nil
|
||||
:lessOrEq nil
|
||||
:add nil
|
||||
:diff nil
|
||||
:divide nil
|
||||
:sub nil
|
||||
:multiply nil
|
||||
:abs nil
|
||||
|
@ -190,5 +202,7 @@
|
|||
"react-native-fs" fs
|
||||
"react-native-mail" react-native-mail
|
||||
"react-native-image-resizer" image-resizer
|
||||
"react-native-haptic-feedback" react-native-haptic-feedback
|
||||
"reanimated-bottom-sheet" reanimated-bottom-sheet
|
||||
"./fleets.js" default-fleets
|
||||
nil))
|
||||
|
|
|
@ -1,25 +1,44 @@
|
|||
(ns quo.animated
|
||||
(:refer-clojure :exclude [set])
|
||||
(:refer-clojure :exclude [set divide])
|
||||
(:require [reagent.core :as reagent]
|
||||
[quo.gesture-handler :as gh]
|
||||
[oops.core :refer [oget ocall]]
|
||||
["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 text (reagent/adapt-react-class (.-Text animated)))
|
||||
(def scroll-view (reagent/adapt-react-class (.-ScrollView 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 neq (oget animated "neq"))
|
||||
(def greater (oget animated "greaterThan"))
|
||||
(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 or* (oget animated "or"))
|
||||
(def and* (oget animated "and"))
|
||||
|
||||
(def diff (oget animated "diff"))
|
||||
(def add (oget animated "add"))
|
||||
(def sub (oget animated "sub"))
|
||||
(def multiply (oget animated "multiply"))
|
||||
(def divide (oget animated "divide"))
|
||||
(def abs (oget animated "abs"))
|
||||
|
||||
(def min* (oget animated "min"))
|
||||
|
@ -66,13 +85,13 @@
|
|||
|
||||
(defn cond*
|
||||
([condition node]
|
||||
(ocall animated "cond"
|
||||
(.cond ^js animated
|
||||
condition
|
||||
(if (vector? node)
|
||||
(clj->js node)
|
||||
node)))
|
||||
([condition if-node else-node]
|
||||
(ocall animated "cond"
|
||||
(.cond ^js animated
|
||||
condition
|
||||
(if (vector? if-node)
|
||||
(clj->js if-node)
|
||||
|
@ -109,6 +128,15 @@
|
|||
(defn with-spring [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]
|
||||
(.withSpringTransition ^js redash val (clj->js config)))
|
||||
|
||||
|
@ -116,7 +144,10 @@
|
|||
(.withTimingTransition ^js redash val (clj->js 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]
|
||||
(ocall redash "onScrollEvent" (clj->js opts)))
|
||||
|
@ -131,3 +162,47 @@
|
|||
|
||||
(defn loop* [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
|
||||
(:require ["react-native-safe-area-context" :as safe-area-context
|
||||
:refer (SafeAreaView SafeAreaProvider SafeAreaConsumer)]
|
||||
:refer (SafeAreaView SafeAreaProvider SafeAreaInsetsContext useSafeAreaInsets)]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(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))
|
||||
|
||||
(defn consumer [component]
|
||||
|
@ -12,3 +12,10 @@
|
|||
(fn [insets]
|
||||
(reagent/as-element
|
||||
[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.list.header :as list-header]
|
||||
[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 header header/header)
|
||||
|
@ -19,6 +20,7 @@
|
|||
(def list-header list-header/header)
|
||||
(def list-footer list-footer/footer)
|
||||
(def list-item list-item/list-item)
|
||||
(def bottom-sheet bottom-sheet/bottom-sheet)
|
||||
(def safe-area-provider safe-area/provider)
|
||||
(def safe-area-consumer safe-area/consumer)
|
||||
(def safe-area-view safe-area/view)
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
:icon-04 "rgba(67,96,223,1)" ; Interactive icon
|
||||
:icon-05 "rgba(255,255,255,1)" ; Icons inverse on accent background
|
||||
: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
|
||||
|
@ -64,6 +65,7 @@
|
|||
:icon-03 "rgba(255,255,255,0.4)"
|
||||
:icon-04 "rgba(97,119,229,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))
|
||||
|
|
|
@ -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.button :as button]
|
||||
[quo.previews.lists :as lists]
|
||||
[quo.previews.bottom-sheet :as bottom-sheet]
|
||||
[quo.react-native :as rn]
|
||||
[quo.core :as quo]
|
||||
[reagent.core :as reagent]
|
||||
|
@ -29,7 +30,10 @@
|
|||
:component button/preview-button}
|
||||
{:name :lists
|
||||
:instes {:top false}
|
||||
:component lists/preview}])
|
||||
:component lists/preview}
|
||||
{:name :bottom-sheet
|
||||
:insets {:top false}
|
||||
:component bottom-sheet/preview}])
|
||||
|
||||
(defn theme-switcher []
|
||||
[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
|
||||
(:require [oops.core :refer [oget]]
|
||||
["react" :as react]))
|
||||
(:refer-clojure :exclude [ref])
|
||||
(: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]
|
||||
(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
|
||||
(:require [reagent.core :as reagent]
|
||||
["react-native" :as rn]))
|
||||
["react-native" :as rn]
|
||||
["@react-native-community/hooks" :as hooks]))
|
||||
|
||||
(def app-registry (.-AppRegistry rn))
|
||||
|
||||
|
@ -15,9 +16,14 @@
|
|||
|
||||
(def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^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 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 layout-animation (.-LayoutAnimation ^js rn))
|
||||
|
@ -53,3 +59,14 @@
|
|||
|
||||
(defn flat-list [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-image-crop-picker" :default image-picker]
|
||||
["react-native-safe-area-context" :as safe-area-context
|
||||
:refer (SafeAreaView SafeAreaProvider SafeAreaConsumer)]
|
||||
:refer (SafeAreaView SafeAreaProvider SafeAreaInsetsContext)]
|
||||
["@react-native-community/clipboard" :default Clipboard])
|
||||
(:require-macros [status-im.utils.views :as views]))
|
||||
|
||||
|
@ -244,6 +244,6 @@
|
|||
comp)))
|
||||
|
||||
(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))
|
||||
|
|
Loading…
Reference in New Issue