mirror of
https://github.com/status-im/status-react.git
synced 2025-02-22 15:48:50 +00:00
Add reagent support to transform prop keys inside vectors (#21937)
- Remove now unnecessary wrapper for reanimated/view - Use kebab-case keywords for the hole-view component - Use rn/StyleSheet.absoluteFill along with ClojureScript styles - Fix keys inside view's `:transform` property - Remove some uses of `merge` to pass styles to components - Add tests for `convert-prop-value`
This commit is contained in:
parent
857cb47ceb
commit
45b09603d2
@ -10,11 +10,10 @@
|
||||
([{:keys [style]} child]
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
[rn/view
|
||||
{:style (assoc style
|
||||
:pointer-events :box-none
|
||||
:background-color
|
||||
(or (:background-color style)
|
||||
(colors/theme-colors colors/white colors/neutral-80 theme)))}
|
||||
{:style [style
|
||||
{:pointer-events :box-none
|
||||
:background-color (or (:background-color style)
|
||||
(colors/theme-colors colors/white colors/neutral-80 theme))}]}
|
||||
child])))
|
||||
|
||||
(def view (if platform/ios? blur/view view-android))
|
||||
|
@ -56,10 +56,10 @@
|
||||
24 8
|
||||
12))})
|
||||
|
||||
(defn style-container
|
||||
(defn container-styles
|
||||
[{:keys [size disabled? border-radius background-color border-color icon-only? icon-top
|
||||
icon-left icon-right]}]
|
||||
(merge {:height size
|
||||
icon-left icon-right inner-style]}]
|
||||
[{:height size
|
||||
:align-items :center
|
||||
:justify-content :center
|
||||
:flex-direction (if icon-top :column :row)
|
||||
@ -114,4 +114,5 @@
|
||||
{:width size})
|
||||
(when border-color
|
||||
{:border-color border-color
|
||||
:border-width 1})))
|
||||
:border-width 1})
|
||||
inner-style])
|
||||
|
@ -62,12 +62,9 @@
|
||||
:on-press on-press
|
||||
:allow-multiple-presses? allow-multiple-presses?
|
||||
:on-long-press on-long-press
|
||||
:style (merge
|
||||
(style/shape-style-container size border-radius)
|
||||
container-style)}
|
||||
:style [(style/shape-style-container size border-radius) container-style]}
|
||||
[rn/view
|
||||
{:style (merge
|
||||
(style/style-container {:size size
|
||||
{:style (style/container-styles {:size size
|
||||
:disabled? disabled?
|
||||
:border-radius border-radius
|
||||
:background-color background-color
|
||||
@ -75,8 +72,8 @@
|
||||
:icon-only? icon-only?
|
||||
:icon-top icon-top
|
||||
:icon-left icon-left
|
||||
:icon-right icon-right})
|
||||
inner-style)}
|
||||
:icon-right icon-right
|
||||
:inner-style inner-style})}
|
||||
(when overlay-customization-color
|
||||
[customization-colors/overlay
|
||||
{:customization-color overlay-customization-color
|
||||
|
@ -201,7 +201,7 @@
|
||||
button-disabled? account-avatar-emoji account-avatar-type customization-color icon-avatar
|
||||
context icon]}]
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
[rn/view {:style (merge style/container container-style)}
|
||||
[rn/view {:style [style/container container-style]}
|
||||
(when (left-image-supported-types type)
|
||||
[rn/view {:style style/left-container}
|
||||
[left-image
|
||||
|
@ -18,5 +18,5 @@
|
||||
:colors [color-top color-bottom]
|
||||
:start {:x 0 :y 0}
|
||||
:end {:x 0 :y 1}
|
||||
:style (merge (style/root-container opacity height)
|
||||
container-style)}])))
|
||||
:style [(style/root-container opacity height)
|
||||
container-style]}])))
|
||||
|
@ -131,7 +131,7 @@
|
||||
:char-limit char-limit
|
||||
:theme theme}])
|
||||
[rn/view
|
||||
{:style (merge (style/input-container colors-by-status small? disabled?) input-container-style)}
|
||||
{:style [(style/input-container colors-by-status small? disabled?) input-container-style]}
|
||||
(when-let [{:keys [icon-name]} left-icon]
|
||||
[left-accessory
|
||||
{:variant-colors variant-colors
|
||||
|
@ -36,7 +36,7 @@
|
||||
(colors/theme-colors colors/neutral-100 colors/white theme))
|
||||
:align-self :center
|
||||
:height 280.48
|
||||
:transform [{:rotate "-30deg"} {:translateY -30}]
|
||||
:transform [{:rotate "-30deg"} {:translate-y -30}]
|
||||
:opacity (when-not locked? 0.02)
|
||||
:z-index 1})
|
||||
|
||||
|
@ -48,7 +48,7 @@
|
||||
:margin-left -4}
|
||||
(if (= type :top)
|
||||
{:top 0}
|
||||
{:transform [{:rotateZ "180deg"}]
|
||||
{:transform [{:rotate-z "180deg"}]
|
||||
:bottom 0})
|
||||
style)}]))
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
[{:keys [label data container-style blur?]}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
last-item (rn/use-memo #(last data) [data])]
|
||||
[rn/view {:style (merge (style/container label) container-style)}
|
||||
[rn/view {:style [(style/container label) container-style]}
|
||||
(when label
|
||||
[text/text
|
||||
{:weight :medium
|
||||
@ -29,4 +29,3 @@
|
||||
[data-item/view data-item-props]
|
||||
(when-not (= item last-item)
|
||||
[rn/view {:style (style/settings-separator blur? theme)}])])]]))
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
separator (rn/use-memo (fn [] [rn/view
|
||||
{:style (style/reorder-separator blur? theme)}])
|
||||
[blur? theme])]
|
||||
[rn/view {:style (merge (style/container label) container-style)}
|
||||
[rn/view {:style [(style/container label) container-style]}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
|
@ -11,7 +11,7 @@
|
||||
(let [theme (quo.theme/use-theme)
|
||||
settings-items (remove nil? data)
|
||||
last-index (dec (count settings-items))]
|
||||
[rn/view {:style (merge (style/container label) container-style)}
|
||||
[rn/view {:style [(style/container label) container-style]}
|
||||
(when label
|
||||
[text/text
|
||||
{:weight :medium
|
||||
|
@ -10,12 +10,7 @@
|
||||
{:flex 1}))
|
||||
|
||||
(def avatar-overlay
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:right 0
|
||||
:left 0
|
||||
:bottom 0
|
||||
:justify-content :center
|
||||
{:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(defn qr-image
|
||||
|
@ -11,7 +11,7 @@
|
||||
(defn- avatar-image
|
||||
[{avatar-type :avatar
|
||||
:as props}]
|
||||
[rn/view {:style style/avatar-overlay}
|
||||
[rn/view {:style [rn/stylesheet-absolute-fill style/avatar-overlay]}
|
||||
[rn/view
|
||||
{:style (case avatar-type
|
||||
:wallet-account style/avatar-container-rounded
|
||||
|
@ -97,7 +97,7 @@
|
||||
theme (quo.theme/use-theme)}
|
||||
:as props}]
|
||||
(let [[image-error? set-image-error] (rn/use-state false)]
|
||||
[rn/view {:style (merge {:align-items :flex-start} container-style)}
|
||||
[rn/view {:style [{:align-items :flex-start} container-style]}
|
||||
[rn/view
|
||||
{:style (style/container {:theme theme
|
||||
:type type
|
||||
|
@ -14,9 +14,9 @@
|
||||
(defn network-bridge-add
|
||||
[{:keys [network state theme container-style on-press]}]
|
||||
[rn/pressable
|
||||
{:style (merge (style/container network state theme)
|
||||
{:style [(style/container network state theme)
|
||||
(style/add-container theme)
|
||||
container-style)
|
||||
container-style]
|
||||
:on-press on-press}
|
||||
[icon/icon :i/edit
|
||||
{:size 12
|
||||
@ -36,7 +36,7 @@
|
||||
(if (= status :edit)
|
||||
[network-bridge-add (assoc args :theme theme)]
|
||||
[rn/pressable
|
||||
{:style (merge (style/container network status theme) container-style)
|
||||
{:style [(style/container network status theme) container-style]
|
||||
:accessible true
|
||||
:accessibility-label :container
|
||||
:on-press on-press
|
||||
|
@ -211,3 +211,5 @@
|
||||
(def linking (.-Linking react-native))
|
||||
|
||||
(defn open-url [link] (.openURL ^js linking link))
|
||||
|
||||
(def stylesheet-absolute-fill ^js (.. react-native -StyleSheet -absoluteFill))
|
||||
|
@ -21,9 +21,7 @@
|
||||
["react-native-redash" :refer (withPause)]
|
||||
[react-native.flat-list :as rn-flat-list]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.utils :as rn.utils]
|
||||
[reagent.core :as reagent]
|
||||
[utils.transforms :as transforms]
|
||||
[utils.worklets.core :as worklets.core]))
|
||||
|
||||
(def ^:const default-duration 300)
|
||||
@ -36,13 +34,7 @@
|
||||
;; Animated Components
|
||||
(def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated)))
|
||||
|
||||
(def ^:private view* (reagent/adapt-react-class (.-View reanimated)))
|
||||
|
||||
(defn view
|
||||
[& argv]
|
||||
(let [[reagent-props children] (rn.utils/get-props-and-children argv)
|
||||
updated-props (update reagent-props :style transforms/styles-with-vectors)]
|
||||
(into [view* updated-props] children)))
|
||||
(def view (reagent/adapt-react-class (.-View reanimated)))
|
||||
|
||||
(def scroll-view (reagent/adapt-react-class (.-ScrollView reanimated)))
|
||||
(def image (reagent/adapt-react-class (.-Image reanimated)))
|
||||
@ -186,4 +178,3 @@
|
||||
(with-timing value
|
||||
(clj->js {:duration duration
|
||||
:easing (default-easing)})))))
|
||||
|
||||
|
54
src/react_native/utils_test.cljs
Normal file
54
src/react_native/utils_test.cljs
Normal file
@ -0,0 +1,54 @@
|
||||
(ns react-native.utils-test
|
||||
(:require
|
||||
[cljs.test :refer [deftest is testing]]
|
||||
[utils.reagent :as sut]))
|
||||
|
||||
(sut/set-convert-props-in-vectors!)
|
||||
|
||||
(deftest convert-prop-value-test
|
||||
;; `test-fn` transforms the result to be easily compared during testing
|
||||
(let [test-fn (comp js->clj sut/convert-prop-value)]
|
||||
(testing "camelCase keys are kept as is"
|
||||
(let [props {:foo nil
|
||||
:bar nil}]
|
||||
(is (= (update-keys props name)
|
||||
(test-fn props)))))
|
||||
|
||||
(testing "kebab-case keys are transformed"
|
||||
(let [props {:foo nil
|
||||
:bar nil}]
|
||||
(is (= {"foo" nil
|
||||
"bar" nil}
|
||||
(test-fn props)))))
|
||||
|
||||
(testing "kebab-case keys are transformed when passed inside a vector"
|
||||
(let [props-in-vector [{:foo nil
|
||||
:bar nil}]]
|
||||
(is (= [{"foo" nil
|
||||
"bar" nil}]
|
||||
(test-fn props-in-vector)))))
|
||||
|
||||
(testing "kebab-case keys are transformed recursively when the structure has vectors"
|
||||
(let [props-with-vectors {:foo [{:foo-bar nil
|
||||
:foo-baz nil}]
|
||||
:bar [{:bar-baz nil}
|
||||
{:bar-qux nil}]}]
|
||||
(is (= {"foo" [{"fooBar" nil
|
||||
"fooBaz" nil}]
|
||||
"bar" [{"barBaz" nil} {"barQux" nil}]}
|
||||
(test-fn props-with-vectors))))
|
||||
(testing "Complex example"
|
||||
(let [complex-props {:foo [{:foo-bar nil :foo-baz nil}]
|
||||
:bar [{:bar-baz nil} {:bar-qux nil}]
|
||||
:qux {:foo-qux :bar-qux}
|
||||
:foo-bar-qux {:foo [{:bar :qux}
|
||||
{:bar-qux nil}]
|
||||
:foo-bar :qux
|
||||
:foo-bar-qux [:foo :bar :qux {:foo-bar :foo-bar}]}}]
|
||||
(is (= {"foo" [{"fooBar" nil "fooBaz" nil}]
|
||||
"bar" [{"barBaz" nil} {"barQux" nil}]
|
||||
"qux" {"fooQux" "bar-qux"}
|
||||
"fooBarQux" {"foo" [{"bar" "qux"} {"barQux" nil}]
|
||||
"fooBar" "qux"
|
||||
"fooBarQux" ["foo" "bar" "qux" {"fooBar" "foo-bar"}]}}
|
||||
(test-fn complex-props))))))))
|
@ -15,16 +15,7 @@
|
||||
|
||||
(def flex-spacer {:flex 1})
|
||||
|
||||
(def absolute-fill
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:left 0
|
||||
:right 0})
|
||||
|
||||
(def hole
|
||||
(merge absolute-fill
|
||||
{:z-index 2 :opacity 0.95}))
|
||||
(def hole {:z-index 2 :opacity 0.95})
|
||||
|
||||
(defn root-container
|
||||
[padding-top]
|
||||
|
@ -174,10 +174,10 @@
|
||||
:on-success-scan set-qr-code-succeeded
|
||||
:on-failed-scan set-rescan-timeout}))}]]
|
||||
[hole-view/hole-view
|
||||
{:style style/hole
|
||||
:holes [(assoc qr-view-finder :borderRadius 16)]}
|
||||
{:style [rn/stylesheet-absolute-fill style/hole]
|
||||
:holes [(assoc qr-view-finder :border-radius 16)]}
|
||||
[quo/blur
|
||||
{:style style/absolute-fill
|
||||
{:style rn/stylesheet-absolute-fill
|
||||
:blur-amount 10
|
||||
:blur-type :transparent
|
||||
:overlay-color colors/neutral-80-opa-80-blur
|
||||
|
@ -28,6 +28,7 @@
|
||||
[status-im.setup.interceptors :as interceptors]
|
||||
status-im.subs.root
|
||||
[utils.i18n :as i18n]
|
||||
[utils.reagent]
|
||||
[status-im.setup.status-backend-client :as status-backend-client]))
|
||||
|
||||
;;;; re-frame RN setup
|
||||
@ -35,6 +36,7 @@
|
||||
(set! batching/fake-raf #(js/setTimeout % 0))
|
||||
(def functional-compiler (reagent.core/create-compiler {:function-components true}))
|
||||
(reagent.core/set-default-compiler! functional-compiler)
|
||||
(utils.reagent/set-convert-props-in-vectors!)
|
||||
|
||||
(def adjust-resize 16)
|
||||
|
||||
|
27
src/utils/reagent.cljs
Normal file
27
src/utils/reagent.cljs
Normal file
@ -0,0 +1,27 @@
|
||||
(ns utils.reagent
|
||||
(:require [reagent.impl.template :as template]
|
||||
[reagent.impl.util :as reagent.util]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(defn convert-prop-value
|
||||
"Based on `reagent.impl.template/kv-conv`.
|
||||
Takes the prop map of a reagent component and returns a React-valid property JS Object
|
||||
by transforming kebab-case keys -> camelCase, maps -> JS Objects and vectors -> arrays.
|
||||
|
||||
This version adds support to recursively transform properties inside vectors, to have a
|
||||
more consistent developer experience in React Native."
|
||||
[x]
|
||||
(cond
|
||||
(reagent.util/js-val? x) x
|
||||
(reagent.util/named? x) (name x)
|
||||
(map? x) (reduce-kv template/kv-conv #js {} x)
|
||||
(vector? x) (transforms/map-array convert-prop-value x)
|
||||
(coll? x) (clj->js x)
|
||||
(ifn? x) (fn [& args]
|
||||
(apply x args))
|
||||
:else (clj->js x)))
|
||||
|
||||
(defn set-convert-props-in-vectors!
|
||||
"We override the default reagent implementation with the one that supports vectors."
|
||||
[]
|
||||
(set! template/convert-prop-value convert-prop-value))
|
@ -3,9 +3,7 @@
|
||||
(:require
|
||||
[camel-snake-kebab.core :as csk]
|
||||
[cljs-bean.core :as clj-bean]
|
||||
[oops.core :as oops]
|
||||
[reagent.impl.template :as reagent.template]
|
||||
[reagent.impl.util :as reagent.util]))
|
||||
[oops.core :as oops]))
|
||||
|
||||
(defn js->clj [data] (cljs.core/js->clj data :keywordize-keys true))
|
||||
|
||||
@ -67,27 +65,10 @@
|
||||
(when-not (= json "undefined")
|
||||
(try (.parse js/JSON json) (catch js/Error _ (when (string? json) json)))))
|
||||
|
||||
(declare styles-with-vectors)
|
||||
|
||||
(defn ^:private convert-keys-and-values
|
||||
"Takes a JS Object a key and a value.
|
||||
Transforms the key from a Clojure style prop to a JS style prop, using the reagent cache.
|
||||
Performs a mutual recursion transformation on the value using `styles-with-vectors`.
|
||||
|
||||
Based on `reagent.impl.template/kv-conv`."
|
||||
[obj k v]
|
||||
(doto obj
|
||||
(oops/gobj-set (reagent.template/cached-prop-name k) (styles-with-vectors v))))
|
||||
|
||||
(defn styles-with-vectors
|
||||
"Takes a Clojure style map or a Clojure vector of style maps and returns a JS Object
|
||||
valid to use as React Native styles.
|
||||
The transformation is done by performing mutual recursive calls with `convert-keys-and-values`.
|
||||
|
||||
Based on `reagent.impl.template/convert-prop-value`."
|
||||
[x]
|
||||
(cond (reagent.util/js-val? x) x
|
||||
(reagent.util/named? x) (name x)
|
||||
(map? x) (reduce-kv convert-keys-and-values #js {} x)
|
||||
(vector? x) (to-array (mapv styles-with-vectors x))
|
||||
:else (clj->js x)))
|
||||
(defn map-array
|
||||
"Performs an efficient `map` operation on `coll` but returns a JS array"
|
||||
[f coll]
|
||||
(let [js-array ^js (array)]
|
||||
(doseq [e coll]
|
||||
(.push js-array (f e)))
|
||||
js-array))
|
||||
|
Loading…
x
Reference in New Issue
Block a user