Bottom sheet component

This commit is contained in:
Roman Volosovskyi 2019-01-25 18:23:22 +02:00
parent 89725d693a
commit 10739cd2e4
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
3 changed files with 204 additions and 0 deletions

View File

@ -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})

View File

@ -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)]))})))

View File

@ -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