chore: add floating button page component (#17737)
Co-authored-by: Ulises M <ulises.ssb@hotmail.com>
This commit is contained in:
parent
1d1d586223
commit
2e2d15adfb
|
@ -131,6 +131,13 @@
|
|||
:accessibility-label :appearance-settings-button
|
||||
:chevron true
|
||||
:on-press #(re-frame/dispatch [:navigate-to :quo-preview])}])
|
||||
(when config/quo-preview-enabled?
|
||||
[list.item/list-item
|
||||
{:icon :main-icons/appearance
|
||||
:title "Status IM Components"
|
||||
:accessibility-label :status-im-common-components
|
||||
:chevron true
|
||||
:on-press #(re-frame/dispatch [:navigate-to :status-im-preview])}])
|
||||
[list.item/list-item
|
||||
{:icon :main-icons/appearance
|
||||
:title (i18n/label :t/appearance)
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
(ns status-im2.common.floating-button-page.component-spec
|
||||
(:require [quo.core :as quo]
|
||||
[status-im2.common.floating-button-page.view :as floating-button-page]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "floating button page"
|
||||
(h/test "renders with a header and standard button"
|
||||
(h/render [floating-button-page/view
|
||||
{:header [quo/page-nav
|
||||
{:type :title-description
|
||||
:title "floating button page"
|
||||
:description "press right icon to swap button type"
|
||||
:text-align :left
|
||||
:background :blur
|
||||
:icon-name :i/close}]
|
||||
:footer [quo/button {} "continue"]}])
|
||||
(h/is-truthy (h/get-by-text "continue"))
|
||||
(h/is-truthy (h/get-by-text "floating button page")))
|
||||
|
||||
(h/test "renders with a header and a slide button"
|
||||
(h/render [floating-button-page/view
|
||||
{:header [quo/page-nav
|
||||
{:type :title-description
|
||||
:title "floating button page"
|
||||
:description "press right icon to swap button type"
|
||||
:text-align :left
|
||||
:background :blur
|
||||
:icon-name :i/close}]
|
||||
:footer [quo/slide-button
|
||||
{:track-text "We gotta slide"
|
||||
:track-icon :face-id}]}])
|
||||
(h/is-truthy (h/get-by-text "We gotta slide"))))
|
|
@ -0,0 +1,17 @@
|
|||
(ns status-im2.common.floating-button-page.floating-container.style
|
||||
(:require [react-native.safe-area :as safe-area]))
|
||||
|
||||
(defn content-container
|
||||
[blur? keyboard-shown?]
|
||||
(let [margin-bottom (if keyboard-shown? 0 (safe-area/get-bottom))]
|
||||
(cond-> {:margin-top :auto
|
||||
:overflow :hidden
|
||||
:margin-bottom margin-bottom
|
||||
:padding-vertical 12
|
||||
:padding-horizontal 20}
|
||||
blur? (dissoc :padding-vertical :padding-horizontal))))
|
||||
|
||||
(def blur-inner-container
|
||||
{:background-color :transparent ; required, otherwise blur-view will shrink
|
||||
:padding-vertical 12
|
||||
:padding-horizontal 20})
|
|
@ -0,0 +1,25 @@
|
|||
(ns status-im2.common.floating-button-page.floating-container.view
|
||||
(:require [quo.theme :as quo.theme]
|
||||
[react-native.blur :as blur]
|
||||
[react-native.core :as rn]
|
||||
[status-im2.common.floating-button-page.floating-container.style :as style]))
|
||||
|
||||
(defn- blur-container
|
||||
[child theme]
|
||||
[blur/view
|
||||
{:blur-amount 12
|
||||
:blur-radius 12
|
||||
:blur-type (quo.theme/theme-value :light :dark theme)}
|
||||
[rn/view {:style style/blur-inner-container}
|
||||
child]])
|
||||
|
||||
(defn view-internal
|
||||
[{:keys [theme on-layout keyboard-shown? blur?]} child]
|
||||
[rn/view
|
||||
{:style (style/content-container blur? keyboard-shown?)
|
||||
:on-layout on-layout}
|
||||
(if blur?
|
||||
[blur-container child theme]
|
||||
child)])
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
|
@ -0,0 +1,15 @@
|
|||
(ns status-im2.common.floating-button-page.style)
|
||||
|
||||
(def page-container
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:left 0
|
||||
:right 0})
|
||||
|
||||
(def keyboard-avoiding-view
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:left 0
|
||||
:right 0})
|
|
@ -0,0 +1,102 @@
|
|||
(ns status-im2.common.floating-button-page.view
|
||||
(:require
|
||||
[oops.core :as oops]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.common.floating-button-page.floating-container.view :as floating-container]
|
||||
[status-im2.common.floating-button-page.style :as style]))
|
||||
|
||||
(defn- show-background
|
||||
[{:keys [window-height keyboard-height footer-container-height content-scroll-y
|
||||
content-container-height header-height keyboard-shown?]}]
|
||||
(let [available-space (- window-height
|
||||
(safe-area/get-top)
|
||||
header-height
|
||||
keyboard-height ; Already contains the bottom safe area value
|
||||
footer-container-height)
|
||||
scroll-view-height (- content-container-height content-scroll-y)
|
||||
overlap? (< available-space scroll-view-height)]
|
||||
(and keyboard-shown? overlap?)))
|
||||
|
||||
(defn- set-height-on-layout
|
||||
[ratom]
|
||||
(fn [event]
|
||||
(let [height (oops/oget event "nativeEvent.layout.height")]
|
||||
(reset! ratom height))))
|
||||
|
||||
(defn- init-keyboard-listeners
|
||||
[{:keys [on-did-show]}]
|
||||
(let [keyboard-will-show? (reagent/atom false)
|
||||
keyboard-did-show? (reagent/atom false)
|
||||
add-listener (fn [listener callback]
|
||||
(oops/ocall rn/keyboard "addListener" listener callback))
|
||||
will-show-listener (add-listener "keyboardWillShow"
|
||||
#(reset! keyboard-will-show? true))
|
||||
did-show-listener (add-listener "keyboardDidShow"
|
||||
(fn [e]
|
||||
(reset! keyboard-did-show? true)
|
||||
(when on-did-show (on-did-show e))))
|
||||
will-hide-listener (add-listener "keyboardWillHide"
|
||||
#(reset! keyboard-will-show? false))
|
||||
did-hide-listener (add-listener "keyboardDidHide"
|
||||
#(reset! keyboard-did-show? false))
|
||||
remove-listeners (fn []
|
||||
(doseq [listener [will-show-listener will-hide-listener
|
||||
did-show-listener did-hide-listener]]
|
||||
(oops/ocall listener "remove")))]
|
||||
{:keyboard-will-show? keyboard-will-show?
|
||||
:keyboard-did-show? keyboard-did-show?
|
||||
:remove-listeners remove-listeners}))
|
||||
|
||||
(defn view
|
||||
[{:keys [header footer]} & children]
|
||||
(reagent/with-let [window-height (:height (rn/get-window))
|
||||
footer-container-height (reagent/atom 0)
|
||||
header-height (reagent/atom 0)
|
||||
content-container-height (reagent/atom 0)
|
||||
content-scroll-y (reagent/atom 0)
|
||||
keyboard-height (reagent/atom 0)
|
||||
{:keys [keyboard-will-show?
|
||||
keyboard-did-show?
|
||||
remove-listeners]} (init-keyboard-listeners
|
||||
{:on-did-show
|
||||
(fn [e]
|
||||
(reset! keyboard-height
|
||||
(oops/oget e "endCoordinates.height")))})
|
||||
set-header-height (set-height-on-layout header-height)
|
||||
set-content-container-height (set-height-on-layout content-container-height)
|
||||
set-footer-container-height (set-height-on-layout footer-container-height)
|
||||
set-content-y-scroll (fn [event]
|
||||
(reset! content-scroll-y
|
||||
(oops/oget event "nativeEvent.contentOffset.y")))]
|
||||
(let [keyboard-shown? (if platform/ios? @keyboard-will-show? @keyboard-did-show?)
|
||||
show-background? (show-background {:window-height window-height
|
||||
:footer-container-height @footer-container-height
|
||||
:keyboard-height @keyboard-height
|
||||
:content-scroll-y @content-scroll-y
|
||||
:content-container-height @content-container-height
|
||||
:header-height @header-height
|
||||
:keyboard-shown? keyboard-shown?})]
|
||||
|
||||
[rn/view {:style style/page-container}
|
||||
[rn/view {:on-layout set-header-height}
|
||||
header]
|
||||
[rn/scroll-view
|
||||
{:on-scroll set-content-y-scroll
|
||||
:scroll-event-throttle 64
|
||||
:content-container-style {:flex-grow 1}}
|
||||
(into [rn/view {:on-layout set-content-container-height}]
|
||||
children)]
|
||||
[rn/keyboard-avoiding-view
|
||||
{:style style/keyboard-avoiding-view
|
||||
:keyboard-vertical-offset (if platform/ios? (safe-area/get-top) 0)
|
||||
:pointer-events :box-none}
|
||||
[floating-container/view
|
||||
{:on-layout set-footer-container-height
|
||||
:keyboard-shown? keyboard-shown?
|
||||
:blur? show-background?}
|
||||
footer]]])
|
||||
(finally
|
||||
(remove-listeners))))
|
|
@ -6,14 +6,14 @@
|
|||
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [theme]}]
|
||||
[{:keys [theme title]}]
|
||||
(let [logged-in? (rf/sub [:multiaccount/logged-in?])
|
||||
has-profiles? (boolean (rf/sub [:profile/profiles-overview]))
|
||||
root (if has-profiles? :profiles :intro)
|
||||
light? (= theme :light)]
|
||||
[quo/page-nav
|
||||
{:type :title
|
||||
:title "quo components preview"
|
||||
:title title
|
||||
:text-align :left
|
||||
:icon-name :i/close
|
||||
:right-side [{:icon-name (if light? :i/dark :i/light)
|
||||
|
|
|
@ -487,7 +487,7 @@
|
|||
(defn- main-screen
|
||||
[]
|
||||
[:<>
|
||||
[common/navigation-bar]
|
||||
[common/navigation-bar {:title "Quo components preview"}]
|
||||
[rn/scroll-view {:style (style/main)}
|
||||
(for [category (sort screens-categories)]
|
||||
^{:key (first category)}
|
||||
|
|
|
@ -317,10 +317,10 @@
|
|||
children)])
|
||||
|
||||
(defn- f-preview-container
|
||||
[{:keys [state descriptor blur? blur-dark-only?
|
||||
[{:keys [title state descriptor blur? blur-dark-only?
|
||||
component-container-style
|
||||
blur-container-style blur-view-props blur-height show-blur-background?]
|
||||
:or {blur-height 200}}
|
||||
:or {blur-height 200 title "quo component"}}
|
||||
& children]
|
||||
(let [theme (quo.theme/use-theme-value)]
|
||||
(rn/use-effect (fn []
|
||||
|
@ -332,7 +332,7 @@
|
|||
[rn/view
|
||||
{:style {:top (safe-area/get-top)
|
||||
:flex 1}}
|
||||
[common/navigation-bar]
|
||||
[common/navigation-bar {:title title}]
|
||||
[rn/scroll-view
|
||||
{:style (style/panel-basic)
|
||||
:shows-vertical-scroll-indicator false}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
(ns status-im2.contexts.status-im-preview.common.floating-button-page.style
|
||||
(:require [quo.foundations.colors :as colors]
|
||||
[react-native.safe-area :as safe-area]))
|
||||
|
||||
(defn container
|
||||
[]
|
||||
{:flex 1
|
||||
:margin-top (safe-area/get-top)})
|
||||
|
||||
(def background-image
|
||||
{:position :absolute
|
||||
:top (- (safe-area/get-top))
|
||||
:left 0
|
||||
:right 0
|
||||
:bottom 200})
|
||||
|
||||
(defn page-content
|
||||
[height]
|
||||
{:flex 1
|
||||
:height height
|
||||
:overflow :hidden
|
||||
:background-color (colors/resolve-color :army 30)})
|
|
@ -0,0 +1,55 @@
|
|||
(ns status-im2.contexts.status-im-preview.common.floating-button-page.view
|
||||
(:require [quo.core :as quo]
|
||||
[re-frame.core :as rf]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.common.floating-button-page.view :as floating-button-page]
|
||||
[status-im2.common.resources :as resources]
|
||||
[status-im2.contexts.status-im-preview.common.floating-button-page.style :as style]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [content-height (reagent/atom 450)
|
||||
slide? (reagent/atom false)]
|
||||
(fn []
|
||||
[rn/view {:style (style/container)}
|
||||
(when-not @slide?
|
||||
[rn/image
|
||||
{:style style/background-image
|
||||
:source (resources/get-mock-image :dark-blur-bg)}])
|
||||
[floating-button-page/view
|
||||
{:header [quo/page-nav
|
||||
{:type :title-description
|
||||
:title "floating button page"
|
||||
:description "press right icon to swap button type"
|
||||
:text-align :left
|
||||
:right-side [{:icon-name :i/swap
|
||||
:on-press #(swap! slide? not)}]
|
||||
:background :blur
|
||||
:icon-name :i/close
|
||||
:on-press #(rf/dispatch [:navigate-back])}]
|
||||
:footer (if @slide?
|
||||
[quo/slide-button
|
||||
{:track-text "We gotta slide"
|
||||
:track-icon :face-id
|
||||
:container-style {:z-index 2}
|
||||
:customization-color :blue
|
||||
:on-complete #(js/alert "button slid")}
|
||||
"Save address"]
|
||||
[quo/button
|
||||
{:container-style {:z-index 2}
|
||||
:on-press #(js/alert "button pressed")}
|
||||
"Save address"])}
|
||||
[rn/view {:style (style/page-content @content-height)}
|
||||
[quo/text {:size :heading-1} "Page Content"]
|
||||
[quo/input
|
||||
{:auto-focus true
|
||||
:value ""}]
|
||||
[quo/button
|
||||
{:type :outline
|
||||
:on-press #(swap! content-height (fn [v] (+ v 10)))}
|
||||
"increase height"]
|
||||
[quo/button
|
||||
{:type :outline
|
||||
:on-press #(swap! content-height (fn [v] (- v 10)))}
|
||||
"decrease height"]]]])))
|
|
@ -0,0 +1,59 @@
|
|||
(ns status-im2.contexts.status-im-preview.main
|
||||
(:refer-clojure :exclude [filter])
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.common :as common]
|
||||
[status-im2.contexts.status-im-preview.common.floating-button-page.view :as floating-button-page]
|
||||
[status-im2.contexts.status-im-preview.style :as style]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(def screens-categories
|
||||
{:common [{:name :floating-button-page
|
||||
:component floating-button-page/view}]})
|
||||
|
||||
(defn- category-view
|
||||
[]
|
||||
(let [open? (reagent/atom false)
|
||||
on-press #(swap! open? not)]
|
||||
(fn [category]
|
||||
[rn/view {:style {:margin-vertical 8}}
|
||||
[quo/dropdown
|
||||
{:type :grey
|
||||
:state (if @open? :active :default)
|
||||
:on-press on-press}
|
||||
(name (key category))]
|
||||
(when @open?
|
||||
(for [{category-name :name} (val category)]
|
||||
^{:key category-name}
|
||||
[quo/button
|
||||
{:type :outline
|
||||
:container-style {:margin-vertical 8}
|
||||
:on-press #(rf/dispatch [:navigate-to category-name])}
|
||||
(name category-name)]))])))
|
||||
|
||||
(defn- main-screen
|
||||
[]
|
||||
[:<>
|
||||
[common/navigation-bar {:title "Status IM components"}]
|
||||
[rn/scroll-view {:style (style/main)}
|
||||
(for [category (sort screens-categories)]
|
||||
^{:key (first category)}
|
||||
[category-view category])]])
|
||||
|
||||
(def screens
|
||||
(->> screens-categories
|
||||
(map val)
|
||||
flatten
|
||||
(map (fn [subcategory]
|
||||
(update-in subcategory
|
||||
[:options :topBar]
|
||||
merge
|
||||
{:visible false})))))
|
||||
|
||||
(def main-screens
|
||||
[{:name :status-im-preview
|
||||
:options {:topBar {:visible false}
|
||||
:insets {:top? true}}
|
||||
:component main-screen}])
|
|
@ -0,0 +1,10 @@
|
|||
(ns status-im2.contexts.status-im-preview.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]))
|
||||
|
||||
(defn main
|
||||
[]
|
||||
{:flex 1
|
||||
:padding-bottom 8
|
||||
:padding-horizontal 16
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-90)})
|
|
@ -1,5 +1,6 @@
|
|||
(ns status-im2.core-spec
|
||||
(:require
|
||||
[status-im2.common.floating-button-page.component-spec]
|
||||
[status-im2.contexts.chat.messages.content.audio.component-spec]
|
||||
[status-im2.contexts.communities.actions.community-options.component-spec]
|
||||
[status-im2.contexts.wallet.create-account.edit-derivation-path.component-spec]))
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
[status-im2.contexts.shell.activity-center.view :as activity-center]
|
||||
[status-im2.contexts.shell.jump-to.view :as shell]
|
||||
[status-im2.contexts.shell.share.view :as share]
|
||||
[status-im2.contexts.status-im-preview.main :as status-im-preview]
|
||||
[status-im2.contexts.syncing.find-sync-code.view :as find-sync-code]
|
||||
[status-im2.contexts.syncing.how-to-pair.view :as how-to-pair]
|
||||
[status-im2.contexts.syncing.scan-sync-code-page.view :as scan-sync-code-page]
|
||||
|
@ -46,7 +47,7 @@
|
|||
[status-im2.contexts.wallet.create-account.select-keypair.view :as wallet-select-keypair]
|
||||
[status-im2.contexts.wallet.create-account.view :as wallet-create-account]
|
||||
[status-im2.contexts.wallet.edit-account.view :as wallet-edit-account]
|
||||
[status-im2.contexts.wallet.saved-address.view :as wallet-saved-address]
|
||||
[status-im2.contexts.wallet.saved-addresses.view :as wallet-saved-addresses]
|
||||
[status-im2.contexts.wallet.scan-account.view :as scan-address]
|
||||
[status-im2.contexts.wallet.send.select-address.view :as wallet-select-address]
|
||||
[status-im2.navigation.options :as options]
|
||||
|
@ -283,8 +284,8 @@
|
|||
:options {:insets {:top? true}}
|
||||
:component wallet-create-account/view}
|
||||
|
||||
{:name :wallet-saved-address
|
||||
:component wallet-saved-address/view}
|
||||
{:name :wallet-saved-addresses
|
||||
:component wallet-saved-addresses/view}
|
||||
|
||||
{:name :wallet-select-address
|
||||
:options {:modalPresentationStyle :overCurrentContext}
|
||||
|
@ -305,4 +306,11 @@
|
|||
quo.preview/screens)
|
||||
|
||||
(when config/quo-preview-enabled?
|
||||
quo.preview/main-screens)))
|
||||
quo.preview/main-screens)
|
||||
|
||||
(when config/quo-preview-enabled?
|
||||
status-im-preview/screens)
|
||||
|
||||
(when config/quo-preview-enabled?
|
||||
status-im-preview/main-screens)))
|
||||
|
||||
|
|
|
@ -25,6 +25,13 @@ jest.mock('react-native-languages', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
jest.mock('react-native-static-safe-area-insets', () => ({
|
||||
default: {
|
||||
safeAreaInsetsTop: 0,
|
||||
safeAreaInsetsBottom: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('react-native-permissions', () => require('react-native-permissions/mock'));
|
||||
|
||||
jest.mock('@react-native-community/audio-toolkit', () => ({
|
||||
|
|
Loading…
Reference in New Issue