Add animated list item into Quo

Change press animation ns

Add list item component

Fix pressable does not reset animation state

Improve long-press handling

Signed-off-by: Gheorghe Pinzaru <feross95@gmail.com>
This commit is contained in:
Gheorghe Pinzaru 2020-05-21 10:22:44 +03:00
parent 2f65cedd2d
commit d28ae1212d
No known key found for this signature in database
GPG Key ID: C9A094959935A952
8 changed files with 323 additions and 15 deletions

View File

@ -1,4 +1,4 @@
(ns quo.components.button.animation
(ns quo.components.animated.pressable
(:require [quo.animated :as animated]
[quo.gesture-handler :as gesture-handler]))
@ -50,8 +50,8 @@
active (animated/eq state (:began gesture-handler/states))
gesture-handler (animated/on-gesture {:state state})
duration (animated/cond* active time-in time-out)
long-duration (animated/cond* active long-press-duration 0)
long-pressed (animated/value 0)
long-duration (animated/cond* active long-press-duration 0)
long-timing (animated/with-timing-transition active
{:duration long-duration})
animation (animated/with-timing-transition active
@ -70,9 +70,15 @@
handle-press-start (fn [] (when on-press-start (on-press-start)))
handle-long-press (fn [] (when on-long-press (on-long-press)))]
[:<>
[animated/code
{:exec (animated/cond* (animated/eq long-timing 1)
(animated/set long-pressed 1))}]
(when on-long-press
[animated/code
{:exec (animated/block
[(animated/cond* (animated/eq long-timing 1)
(animated/set long-pressed 1))
(animated/cond* long-pressed
[(animated/set long-pressed 0)
(animated/call* [] handle-long-press)
(animated/set state (:undetermined gesture-handler/states))])])}])
[animated/code
{:key (str on-press on-long-press on-press-start)
:exec (animated/on-change state
@ -80,11 +86,8 @@
(animated/call* [] handle-press-start))
(animated/cond* (animated/and* (animated/eq state (:end gesture-handler/states))
(animated/not* long-pressed))
(animated/call* [] handle-press))
(animated/cond* (animated/and* (animated/eq state (:end gesture-handler/states))
long-pressed)
[(animated/set long-pressed 0)
(animated/call* [] handle-long-press)])])}]
[(animated/call* [] handle-press)
(animated/set state (:undetermined gesture-handler/states))])])}]
[gesture-handler/tap-gesture-handler
(merge gesture-handler
{:shouldCancelWhenOutside true

View File

@ -1,5 +1,5 @@
(ns quo.components.button.view
(:require [quo.components.button.animation :as animation]
(:require [quo.components.animated.pressable :as animation]
[quo.react-native :as rn]
[quo.haptic :as haptic]
[quo.design-system.colors :as colors]

View File

@ -0,0 +1,11 @@
(ns quo.components.list.footer
(:require [quo.react-native :as rn]
[quo.design-system.spacing :as spacing]
[quo.components.text :as text]))
(defn footer [& children]
[rn/view {:style (merge (:base spacing/padding-horizontal)
(:small spacing/padding-vertical))}
(into [text/text {:color :secondary}]
children)])

View File

@ -0,0 +1,11 @@
(ns quo.components.list.header
(:require [quo.react-native :as rn]
[quo.design-system.spacing :as spacing]
[quo.components.text :as text]))
(defn header [& children]
[rn/view {:style (merge (:base spacing/padding-horizontal)
(:x-tiny spacing/padding-vertical))}
(into [text/text {:color :secondary
:style {:margin-top 10}}]
children)])

View File

@ -0,0 +1,163 @@
(ns quo.components.list.item
(:require [quo.react-native :as rn]
[quo.platform :as platform]
[quo.design-system.spacing :as spacing]
[quo.design-system.colors :as colors]
[quo.components.text :as text]
;; FIXME:
[status-im.ui.components.radio :as radio]
[status-im.ui.components.checkbox.view :as checkbox]
[status-im.ui.components.icons.vector-icons :as icons]
[quo.components.animated.pressable :as animated]))
(defn themes [theme]
(case theme
:main {:icon-color (:icon-04 @colors/theme)
:icon-bg-color (:interactive-02 @colors/theme)
:active-background (:interactive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-01 @colors/theme)}
:accent {:icon-color (:icon-04 @colors/theme)
:icon-bg-color (:interactive-02 @colors/theme)
:active-background (:interactive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-04 @colors/theme)}
:negative {:icon-color (:negative-01 @colors/theme)
:icon-bg-color (:negative-02 @colors/theme)
:active-background (:negative-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:negative-01 @colors/theme)}
:positive {:icon-color (:positive-01 @colors/theme)
:icon-bg-color (:positive-02 @colors/theme)
:active-background (:positive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:positive-01 @colors/theme)}
:disabled {:icon-color (:icon-02 @colors/theme)
:icon-bg-color (:ui-01 @colors/theme)
:active-background (:ui-01 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-02 @colors/theme)}))
(defn size->icon-size [size]
(case size
:small 36
40))
(defn size->container-size [size]
(case size
:small 52
64))
(defn container [{:keys [size]} & children]
(into [rn/view {:style (merge (:tiny spacing/padding-horizontal)
{:min-height (size->container-size size)
:padding-vertical 8
:flex-direction :row
:flex 1
:align-items :center
:justify-content :space-between})}]
children))
(defn- icon-column [{:keys [icon icon-bg-color icon-color size]}]
(when icon
(let [icon-size (if (= size :default)
40
36)]
[rn/view {:style (:tiny spacing/padding-horizontal)}
(cond
(vector? icon)
icon
(keyword? icon)
[rn/view {:style {:width icon-size
:height icon-size
:align-items :center
:justify-content :center
:border-radius (/ icon-size 2)
:background-color icon-bg-color}}
[icons/icon icon {:color icon-color}]])])))
(defn title-column [{:keys [title text-color subtitle subtitle-max-lines]}]
[rn/view {:style (merge (:tiny spacing/padding-horizontal)
{:justify-content :center})}
(cond
(and title subtitle)
[:<>
[text/text {:weight :medium
:style {:color text-color}
:ellipsize-mode :tail
:number-of-lines 1}
title]
[text/text {:weight :regular
:color :secondary
:ellipsize-mode :tail
:number-of-lines subtitle-max-lines}
subtitle]]
title [text/text {:number-of-lines 1
:style {:color text-color}
:ellipsize-mode :tail
:size :large}
title])])
(defn left-side [props]
[rn/view {:style {:flex-direction :row
:flex 1
:align-items :center}}
[icon-column props]
[title-column props]])
(defn right-side [{:keys [chevron on-press active accessory accessory-text]}]
[rn/view {:style {:align-items :center
:flex-direction :row}}
[rn/view {:style (:tiny spacing/padding-horizontal)}
(case accessory
:radio [radio/radio active]
:checkbox [checkbox/checkbox {:checked? active}]
:switch [rn/switch {:value active
:track-color #js {:true (:interactive-01 @colors/theme)
:false nil}
:on-value-change on-press}]
:text [text/text {:color :secondary
:number-of-lines 1}
accessory-text]
nil)]
(when (and chevron platform/ios?)
[rn/view {:style {:padding-right (:tiny spacing/spacing)}}
[icons/icon :main-icons/next {:container-style {:opacity 0.4
:align-items :center
:justify-content :center}
:resize-mode :center
:color (:icon-02 @colors/theme)}]])])
(defn list-item
[{:keys [theme accessory disabled subtitle-max-lines icon title
subtitle active on-press on-long-press chevron size
accessory-text]
:or {subtitle-max-lines 1
theme :main}}]
(let [theme (if disabled :disabled theme)
{:keys [icon-color text-color icon-bg-color
active-background passive-background]}
(themes theme)]
[rn/view {:background-color (if active active-background passive-background)}
[animated/pressable {:type :list-item
:disabled disabled
:background-color active-background
:on-press on-press
:on-long-press on-long-press}
[container {:size size}
[left-side {:icon-color icon-color
:text-color text-color
:icon-bg-color icon-bg-color
:icon icon
:title title
:size size
:subtitle subtitle
:subtitle-max-lines subtitle-max-lines}]
[right-side {:chevron chevron
:active active
:on-press on-press
:accessory-text accessory-text
:accessory accessory}]]]]))

View File

@ -5,7 +5,10 @@
[quo.components.text-input :as text-input]
[quo.components.tooltip :as tooltip]
[quo.components.text :as text]
[quo.components.button.view :as button]))
[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]))
(def text text/text)
(def header header/header)
@ -13,6 +16,9 @@
(def text-input text-input/text-input)
(def tooltip tooltip/tooltip)
(def button button/button)
(def list-header list-header/header)
(def list-footer list-footer/footer)
(def list-item list-item/list-item)
(def safe-area-provider safe-area/provider)
(def safe-area-consumer safe-area/consumer)
(def safe-area-view safe-area/view)

110
src/quo/previews/lists.cljs Normal file
View File

@ -0,0 +1,110 @@
(ns quo.previews.lists
(: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 all-props (preview/list-comp [] {}))
(defn avatar []
[rn/view {:border-radius 20
:width 40
:height 40
:justify-content :center
:align-items :center
:background-color :red}
[quo/text {:weight :bold
:size :large}
"T"]])
(defn icon-element [type]
(case type
:icon :main-icons/add-contact
:component [avatar]
nil))
(def descriptor [{:label "Accessory:"
:key :accessory
:type :select
:options [{:key :radio
:value "Radio"}
{:key :checkbox
:value "Checkbox"}
{:key :switch
:value "Switch"}
{:key :text
:value "Text"}
{:key :default
:value "Default"}]}
{:label "Size:"
:key :size
:type :select
:options [{:key :small
:value "Small"}
{:key :default
:value "Default"}]}
{:label "Icon:"
:key :icon
:type :select
:options [{:key :icon
:value "Icon"}
{:key :component
:value "Component"}]}
{:label "Theme:"
:key :theme
:type :select
:options [{:key :main
:value "Main"}
{:key :accent
:value "Accent"}
{:key :negative
:value "Negative"}
{:key :positive
:value "Positive"}]}
{:label "Selectable"
:key :selectable
:type :boolean}
{:label "Chevron"
:key :chevron
:type :boolean}
{:label "Disabled:"
:key :disabled
:type :boolean}
{:label "Title"
:key :title
:type :text}
{:label "Subtitle"
:key :subtitle
:type :text}])
(defn render-item [_]
[rn/view {:style {:padding-vertical 24}}])
(defn cool-preview []
(let [state (reagent/atom {:title "Title"
:active false})
icon (reagent/cursor state [:icon])
active (reagent/cursor state [:active])
selectable (reagent/cursor state [:selectable])]
(fn []
[rn/view {:margin-bottom 50}
[rn/view {:padding-horizontal 16}
[preview/customizer state descriptor]]
[rn/view {:padding-vertical 16}
[quo/list-item (merge (dissoc @state :active :selectable)
(when @selectable
{:active @active
:on-press #(swap! active not)})
{:accessory-text "Accessory"
:icon (icon-element @icon)})]]])))
(defn preview []
[rn/view {:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])

View File

@ -4,6 +4,7 @@
[quo.previews.text-input :as text-input]
[quo.previews.tooltip :as tooltip]
[quo.previews.button :as button]
[quo.previews.lists :as lists]
[quo.react-native :as rn]
[quo.core :as quo]
[reagent.core :as reagent]
@ -23,9 +24,12 @@
{:name :headers
:insets {:top false}
:component header/preview-header}
{:name :button
:insets {:top false}
:component button/preview-button}])
{:name :button
:insets {:top false}
:component button/preview-button}
{:name :lists
:instes {:top false}
:component lists/preview}])
(defn theme-switcher []
[rn/view {:style {:flex-direction :row