Bottom sheet component
This commit is contained in:
parent
89725d693a
commit
10739cd2e4
|
@ -0,0 +1,56 @@
|
|||
(ns status-im.ui.components.bottom-sheet.styles
|
||||
(:require [status-im.ui.components.colors :as colors]
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
||||
(def border-radius 16)
|
||||
(def bottom-padding (if platform/iphone-x? 34 8))
|
||||
(def bottom-view-height 1000)
|
||||
|
||||
(def container
|
||||
{:position :absolute
|
||||
:left 0
|
||||
:top 0
|
||||
:right 0
|
||||
:bottom 0
|
||||
:flex 1
|
||||
:justify-content :flex-end})
|
||||
|
||||
(defn shadow [opacity-value]
|
||||
{:flex 1
|
||||
:position :absolute
|
||||
:left 0
|
||||
:top 0
|
||||
:right 0
|
||||
:bottom 0
|
||||
:opacity opacity-value
|
||||
:background-color colors/black-transparent-40})
|
||||
|
||||
(defn content-container
|
||||
[content-height bottom-value]
|
||||
{:background-color colors/white
|
||||
:border-top-left-radius border-radius
|
||||
:border-top-right-radius border-radius
|
||||
:height (+ content-height border-radius bottom-view-height)
|
||||
:bottom (- bottom-view-height)
|
||||
:align-self :stretch
|
||||
:transform [{:translateY bottom-value}]
|
||||
:justify-content :flex-start
|
||||
:align-items :center
|
||||
:padding-bottom bottom-padding})
|
||||
|
||||
(def content-header
|
||||
{:height border-radius
|
||||
:align-self :stretch
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(def handle
|
||||
{:width 31
|
||||
:height 4
|
||||
:background-color colors/gray-transparent-40
|
||||
:border-radius 2})
|
||||
|
||||
(def bottom-view
|
||||
{:background-color colors/white
|
||||
:height bottom-view-height
|
||||
:align-self :stretch})
|
|
@ -0,0 +1,146 @@
|
|||
(ns status-im.ui.components.bottom-sheet.view
|
||||
(:require [status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.animation :as animation]
|
||||
[status-im.ui.components.bottom-sheet.styles :as styles]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(def initial-animation-duration 300)
|
||||
(def release-animation-duration 150)
|
||||
(def cancellation-animation-duration 100)
|
||||
(def swipe-opacity-range 100)
|
||||
(def cancellation-height 180)
|
||||
(def min-opacity 0.05)
|
||||
(def min-velocity 0.1)
|
||||
|
||||
(defn- animate
|
||||
[{:keys [opacity new-opacity-value
|
||||
bottom new-bottom-value
|
||||
duration callback]}]
|
||||
(animation/start
|
||||
(animation/parallel
|
||||
[(animation/timing opacity
|
||||
{:toValue new-opacity-value
|
||||
:duration duration
|
||||
:useNativeDriver true})
|
||||
(animation/timing bottom
|
||||
{:toValue new-bottom-value
|
||||
:duration duration
|
||||
:useNativeDriver true})])
|
||||
(when (fn? callback) callback)))
|
||||
|
||||
(defn animate-sign-panel
|
||||
[opacity-value bottom-value]
|
||||
(animate {:bottom bottom-value
|
||||
:new-bottom-value 0
|
||||
:opacity opacity-value
|
||||
:new-opacity-value 1
|
||||
:duration initial-animation-duration}))
|
||||
|
||||
(defn- on-move
|
||||
[{:keys [height bottom-value opacity-value]}]
|
||||
(fn [_ state]
|
||||
(let [dy (.-dy state)]
|
||||
(cond (pos? dy)
|
||||
(let [opacity (max min-opacity (- 1 (/ dy (- height swipe-opacity-range))))]
|
||||
(animation/set-value bottom-value dy)
|
||||
(animation/set-value opacity-value opacity))
|
||||
(neg? dy)
|
||||
(animation/set-value bottom-value (/ dy 2))))))
|
||||
|
||||
(defn cancelled? [height dy vy]
|
||||
(or
|
||||
(<= min-velocity vy)
|
||||
(> cancellation-height (- height dy))))
|
||||
|
||||
(defn- cancel
|
||||
([opts] (cancel opts nil))
|
||||
([{:keys [height bottom-value show-sheet? opacity-value]} callback]
|
||||
(animate {:bottom bottom-value
|
||||
:new-bottom-value height
|
||||
:opacity opacity-value
|
||||
:new-opacity-value 0
|
||||
:duration cancellation-animation-duration
|
||||
:callback #(do (reset! show-sheet? false)
|
||||
(animation/set-value bottom-value height)
|
||||
(animation/set-value opacity-value 0)
|
||||
(when (fn? callback) (callback)))})))
|
||||
|
||||
(defn- on-release
|
||||
[{:keys [height bottom-value opacity-value on-cancel] :as opts}]
|
||||
(fn [_ state]
|
||||
(let [{:strs [dy vy]} (js->clj state)]
|
||||
(if (cancelled? height dy vy)
|
||||
(cancel opts on-cancel)
|
||||
(animate {:bottom bottom-value
|
||||
:new-bottom-value 0
|
||||
:opacity opacity-value
|
||||
:new-opacity-value 1
|
||||
:duration release-animation-duration})))))
|
||||
|
||||
(defn swipe-pan-responder [opts]
|
||||
(.create
|
||||
react/pan-responder
|
||||
(clj->js
|
||||
{:onMoveShouldSetPanResponder (fn [_ state]
|
||||
(or (< 10 (js/Math.abs (.-dx state)))
|
||||
(< 5 (js/Math.abs (.-dy state)))))
|
||||
:onPanResponderMove (on-move opts)
|
||||
:onPanResponderRelease (on-release opts)
|
||||
:onPanResponderTerminate (on-release opts)})))
|
||||
|
||||
(defn pan-handlers [pan-responder]
|
||||
(js->clj (.-panHandlers pan-responder)))
|
||||
|
||||
(defn- bottom-sheet-view
|
||||
[{:keys [opacity-value bottom-value]}]
|
||||
(reagent.core/create-class
|
||||
{:component-did-mount
|
||||
#(animate-sign-panel opacity-value bottom-value)
|
||||
:reagent-render
|
||||
(fn [{:keys [opacity-value bottom-value
|
||||
height content on-cancel]
|
||||
:as opts}]
|
||||
[react/view
|
||||
(merge
|
||||
(pan-handlers (swipe-pan-responder opts))
|
||||
{:style styles/container})
|
||||
[react/touchable-highlight
|
||||
{:on-press #(cancel opts on-cancel)
|
||||
:style styles/container}
|
||||
|
||||
[react/animated-view (styles/shadow opacity-value)]]
|
||||
[react/animated-view
|
||||
{:style (styles/content-container height bottom-value)}
|
||||
[react/view styles/content-header
|
||||
[react/view styles/handle]]
|
||||
content
|
||||
[react/view {:style styles/bottom-view}]]])}))
|
||||
|
||||
(defn bottom-sheet
|
||||
[{:keys [show? content-height on-cancel]} _]
|
||||
{:pre [(fn? on-cancel) (pos? content-height)]}
|
||||
(let [show-sheet? (reagent/atom show?)
|
||||
total-content-height (+ content-height styles/border-radius
|
||||
styles/bottom-padding)
|
||||
bottom-value (animation/create-value total-content-height)
|
||||
opacity-value (animation/create-value 0)
|
||||
opts {:height total-content-height
|
||||
:bottom-value bottom-value
|
||||
:opacity-value opacity-value
|
||||
:show-sheet? show-sheet?
|
||||
:on-cancel on-cancel}]
|
||||
(reagent.core/create-class
|
||||
{:component-will-update
|
||||
(fn [this [_ new-args]]
|
||||
(let [old-args (second (.-argv (.-props this)))
|
||||
old-show? (:show? old-args)
|
||||
new-show? (:show? new-args)]
|
||||
(cond (and (not old-show?) new-show?)
|
||||
(reset! show-sheet? true)
|
||||
|
||||
(and old-show? (not new-show?) (true? @show-sheet?))
|
||||
(cancel opts))))
|
||||
:reagent-render
|
||||
(fn [_ content]
|
||||
(when @show-sheet?
|
||||
[bottom-sheet-view (assoc opts :content content)]))})))
|
|
@ -22,10 +22,12 @@
|
|||
;; BLACK
|
||||
(def black "#000000") ;; Used as the default text color
|
||||
(def black-transparent (alpha black 0.1)) ;; Used as background color for rounded button on dark background and as background color for containers like "Backup seed phrase"
|
||||
(def black-transparent-40 (alpha black 0.4))
|
||||
(def gray-light black-transparent) ;; Used as divider color
|
||||
|
||||
;; DARK GREY
|
||||
(def gray "#939ba1") ;; Dark grey, used as a background for a light foreground and as section header and secondary text color
|
||||
(def gray-transparent-40 (alpha gray 0.4))
|
||||
;; LIGHT GREY
|
||||
(def gray-lighter "#eef2f5") ;; Light Grey, used as a background or shadow
|
||||
|
||||
|
|
Loading…
Reference in New Issue