migrating to react state. step 2 (#18905)

This commit is contained in:
flexsurfer 2024-02-28 11:49:49 +01:00 committed by GitHub
parent 047e45d2a3
commit b7bffb3bd3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 507 additions and 526 deletions

View File

@ -8,36 +8,36 @@
[quo.components.calendar.calendar.years-list.view :as years-list] [quo.components.calendar.calendar.years-list.view :as years-list]
[quo.theme :as theme] [quo.theme :as theme]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent]
[utils.number :as utils.number])) [utils.number :as utils.number]))
(defn- view-internal (defn view
[] [{:keys [on-change start-date end-date]}]
(let [selected-year (reagent/atom (utils/current-year)) (let [theme (theme/use-theme-value)
selected-month (reagent/atom (utils/current-month)) [selected-year set-selected-year] (rn/use-state (utils/current-year))
on-change-year #(reset! selected-year %) [selected-month set-selected-month] (rn/use-state (utils/current-month))
on-change-month (fn [new-date] on-change-year (rn/use-callback #(set-selected-year %))
(reset! selected-year (utils.number/parse-int (:year new-date))) on-change-month (rn/use-callback
(reset! selected-month (utils.number/parse-int (:month new-date))))] (fn [new-date]
(fn [{:keys [on-change start-date end-date theme]}] (set-selected-year
[rn/view (utils.number/parse-int (:year new-date)))
{:style (style/container theme)} (set-selected-month
[years-list/view (utils.number/parse-int (:month new-date)))))]
{:on-change-year on-change-year [rn/view
:year @selected-year}] {:style (style/container theme)}
[rn/view [years-list/view
{:style style/container-main} {:on-change-year on-change-year
[month-picker/view :year selected-year}]
{:year @selected-year [rn/view
:month @selected-month {:style style/container-main}
:on-change on-change-month}] [month-picker/view
[weekdays-header/view] {:year selected-year
[days-grid/view :month selected-month
{:year @selected-year :on-change on-change-month}]
:month @selected-month [weekdays-header/view]
:start-date start-date [days-grid/view
:end-date end-date {:year selected-year
:on-change on-change :month selected-month
:customization-color :blue}]]]))) :start-date start-date
:end-date end-date
(def view (theme/with-theme view-internal)) :on-change on-change
:customization-color :blue}]]]))

View File

@ -3,13 +3,7 @@
[quo.components.colors.color.constants :as constants] [quo.components.colors.color.constants :as constants]
[quo.components.colors.color.view :as color] [quo.components.colors.color.view :as color]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[react-native.core :as rn] [react-native.core :as rn]))
[reagent.core :as reagent]))
(defn- on-change-handler
[selected color-name on-change]
(reset! selected color-name)
(when on-change (on-change color-name)))
(defn get-item-layout (defn get-item-layout
[_ index] [_ index]
@ -18,15 +12,34 @@
:offset (* (+ constants/color-size 8) index) :offset (* (+ constants/color-size 8) index)
:index index}) :index index})
(defn- view-internal (defn view
"Options "Options
- `default-selected` Default selected color name. - `default-selected` Default selected color name.
- `on-change` Callback called when a color is selected `(fn [color-name])`. - `on-change` Callback called when a color is selected `(fn [color-name])`.
- `blur?` Boolean to enable blur background support.}" - `blur?` Boolean to enable blur background support.}"
[{:keys [default-selected blur? on-change feng-shui? container-style]}] [{:keys [default-selected blur? on-change feng-shui? container-style]}]
(let [selected (reagent/atom default-selected) (let [[selected set-selected] (rn/use-state default-selected)
{window-width :width} (rn/get-window) {window-width :width} (rn/get-window)
ref (atom nil)] ref (rn/use-ref-atom nil)
on-ref (rn/use-callback #(reset! ref %))
render-fn (rn/use-callback
(fn [color idx]
[color/view
{:selected? (= color selected)
:on-press (fn [color-name]
(.scrollToIndex ^js @ref
#js
{:animated true
:index idx
:viewPosition 0.5})
(set-selected color-name)
(when on-change (on-change color-name)))
:blur? blur?
:key color
:color color
:idx idx
:window-width window-width}])
[selected blur? on-change @ref])]
(rn/use-mount (rn/use-mount
(fn [] (fn []
(js/setTimeout (js/setTimeout
@ -40,32 +53,14 @@
:viewPosition 0.5}))))) :viewPosition 0.5})))))
50))) 50)))
[rn/flat-list [rn/flat-list
{:ref #(reset! ref %) {:ref on-ref
;; TODO: using :feng-shui? temporarily while b & w is being developed. ;; TODO: using :feng-shui? temporarily while b & w is being developed.
;; https://github.com/status-im/status-mobile/discussions/16676 ;; https://github.com/status-im/status-mobile/discussions/16676
:data (if feng-shui? :data (if feng-shui?
(conj colors/account-colors :feng-shui) (conj colors/account-colors :feng-shui)
colors/account-colors) colors/account-colors)
:render-fn (fn [color idx] :render-fn render-fn
[color/view
{:selected? (= color @selected)
:on-press (fn [e]
(.scrollToIndex ^js @ref
#js
{:animated true
:index idx
:viewPosition 0.5})
(on-change-handler selected e on-change))
:blur? blur?
:key color
:color color
:idx idx
:window-width window-width}])
:get-item-layout get-item-layout :get-item-layout get-item-layout
:horizontal true :horizontal true
:shows-horizontal-scroll-indicator false :shows-horizontal-scroll-indicator false
:content-container-style container-style}])) :content-container-style container-style}]))
(defn view
[props]
[:f> view-internal props])

View File

@ -35,12 +35,11 @@
(defn image (defn image
"Same as rn/image but cache the image source in a js/Set, so the image won't "Same as rn/image but cache the image source in a js/Set, so the image won't
flicker when re-render on android" flicker when re-render on android"
[] [props]
(let [loaded-source (reagent/atom nil) (let [[loaded-source set-loaded-source] (rn/use-state nil)
on-source-loaded #(reset! loaded-source %)] on-source-loaded (rn/use-callback #(set-loaded-source %))]
(fn [props] (if platform/ios?
(if platform/ios? [rn/image props]
[rn/image props] [:<>
[:<> [rn/image (assoc props :source loaded-source)]
[rn/image (assoc props :source @loaded-source)] [caching-image props on-source-loaded]])))
[caching-image props on-source-loaded]]))))

View File

@ -3,25 +3,23 @@
[quo.components.dropdowns.network-dropdown.style :as style] [quo.components.dropdowns.network-dropdown.style :as style]
[quo.components.list-items.preview-list.view :as preview-list] [quo.components.list-items.preview-list.view :as preview-list]
[quo.theme :as quo.theme] [quo.theme :as quo.theme]
[react-native.core :as rn] [react-native.core :as rn]))
[reagent.core :as reagent]))
(defn- internal-view (defn view
[_ _] [{:keys [on-press state] :as props} networks]
(let [pressed? (reagent/atom false)] (let [theme (quo.theme/use-theme-value)
(fn [pressed? set-pressed] (rn/use-state false)
[{:keys [on-press state] :as props} networks] on-press-in (rn/use-callback #(set-pressed true))
[rn/pressable on-press-out (rn/use-callback #(set-pressed false))]
{:style (style/dropdown-container (merge props {:pressed? @pressed?})) [rn/pressable
:accessibility-label :network-dropdown {:style (style/dropdown-container (merge props {:pressed? pressed? :theme theme}))
:disabled (= state :disabled) :accessibility-label :network-dropdown
:on-press on-press :disabled (= state :disabled)
:on-press-in (fn [] (reset! pressed? true)) :on-press on-press
:on-press-out (fn [] (reset! pressed? false))} :on-press-in on-press-in
[preview-list/view :on-press-out on-press-out}
{:type :network [preview-list/view
:list-size (count networks) {:type :network
:size :size-20} :list-size (count networks)
networks]]))) :size :size-20}
networks]]))
(def view (quo.theme/with-theme internal-view))

View File

@ -14,7 +14,6 @@
(def initial-spacing 56) (def initial-spacing 56)
(def end-spacing 22) (def end-spacing 22)
(def y-axis-label-width -33) (def y-axis-label-width -33)
(def inspecting? (reagent/atom false))
(defn- pointer (defn- pointer
[customization-color] [customization-color]
@ -32,14 +31,9 @@
:pointer-color customization-color :pointer-color customization-color
:pointer-strip-enable-gradient true}) :pointer-strip-enable-gradient true})
(defn- get-pointer-props
[pointer-props]
(let [pointer-index (.-pointerIndex ^js pointer-props)]
(reset! inspecting? (not= pointer-index -1))))
(defn- get-line-color (defn- get-line-color
[state theme] [state theme inspecting?]
(if @inspecting? (if inspecting?
(colors/theme-colors colors/neutral-80-opa-40 (colors/theme-colors colors/neutral-80-opa-40
colors/white-opa-20 colors/white-opa-20
theme) theme)
@ -51,11 +45,13 @@
colors/danger-60 colors/danger-60
theme)))) theme))))
(defn- view-internal (defn view
[{:keys [data state customization-color theme reference-value reference-prefix decimal-separator] [{:keys [data state customization-color reference-value reference-prefix decimal-separator]
:or {reference-prefix "$" :or {reference-prefix "$"
decimal-separator :dot}}] decimal-separator :dot}}]
(let [data (if (> (count data) max-data-points) (let [theme (quo.theme/use-theme-value)
[inspecting? set-inspecting] (rn/use-state false)
data (if (> (count data) max-data-points)
(utils/downsample-data data max-data-points) (utils/downsample-data data max-data-points)
data) data)
highest-value (utils/find-highest-value data) highest-value (utils/find-highest-value data)
@ -64,7 +60,11 @@
max-value (- (utils/calculate-rounded-max highest-value) min-value) max-value (- (utils/calculate-rounded-max highest-value) min-value)
step-value (/ max-value 4) step-value (/ max-value 4)
width (:width (rn/get-window)) width (:width (rn/get-window))
line-color (get-line-color state theme) line-color (get-line-color state theme inspecting?)
get-pointer-props (rn/use-callback
(fn [pointer-props]
(let [pointer-index (.-pointerIndex ^js pointer-props)]
(set-inspecting (not= pointer-index -1)))))
rules-color (colors/theme-colors colors/neutral-80-opa-10 rules-color (colors/theme-colors colors/neutral-80-opa-10
colors/white-opa-5 colors/white-opa-5
theme) theme)
@ -119,7 +119,7 @@
:show-strip-on-focus true :show-strip-on-focus true
:reference-line-1-config {:color rules-color} :reference-line-1-config {:color rules-color}
:reference-line-1-position 0 :reference-line-1-position 0
:show-reference-line-2 (and (not @inspecting?) :show-reference-line-2 (and (not inspecting?)
(<= reference-value highest-value) (<= reference-value highest-value)
(>= reference-value lowest-value)) (>= reference-value lowest-value))
:reference-line-2-config {:color y-axis-label-text-color :reference-line-2-config {:color y-axis-label-text-color
@ -138,5 +138,3 @@
:x-axis-label-text-style (style/x-axis-label-text (/ width (count x-axis-label-texts)) :x-axis-label-text-style (style/x-axis-label-text (/ width (count x-axis-label-texts))
y-axis-label-text-color) y-axis-label-text-color)
:x-axis-label-texts x-axis-label-texts}]])) :x-axis-label-texts x-axis-label-texts}]]))
(def view (quo.theme/with-theme view-internal))

View File

@ -20,7 +20,7 @@
(h/fire-event :on-focus (h/get-by-label-text :address-text-input)) (h/fire-event :on-focus (h/get-by-label-text :address-text-input))
(h/has-prop (h/get-by-label-text :address-text-input) (h/has-prop (h/get-by-label-text :address-text-input)
:placeholder-text-color :placeholder-text-color
colors/neutral-40))) colors/neutral-30)))
(h/test "on focus with blur? true" (h/test "on focus with blur? true"
(with-redefs [clipboard/get-string #(% "")] (with-redefs [clipboard/get-string #(% "")]
@ -30,27 +30,22 @@
(h/fire-event :on-focus (h/get-by-label-text :address-text-input)) (h/fire-event :on-focus (h/get-by-label-text :address-text-input))
(h/has-prop (h/get-by-label-text :address-text-input) (h/has-prop (h/get-by-label-text :address-text-input)
:placeholder-text-color :placeholder-text-color
colors/neutral-80-opa-40))) colors/neutral-80-opa-20)))
(h/test "scanned value is properly set" (h/test "default value is properly set"
(let [on-change-text (h/mock-fn) (let [default-value "default-value"]
scanned-value "scanned-value"]
(with-redefs [clipboard/get-string #(% "")] (with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input (h/render [address-input/address-input
{:scanned-value scanned-value {:default-value default-value
:on-change-text on-change-text :ens-regex ens-regex}])
:ens-regex ens-regex}]) (h/has-prop (h/get-by-label-text :address-text-input)
(-> (h/wait-for #(h/get-by-label-text :clear-button)) :value
(.then (fn [] default-value))))
(h/was-called-with on-change-text scanned-value)
(h/has-prop (h/get-by-label-text :address-text-input)
:default-value
scanned-value)))))))
(h/test "clear icon is shown when input has text" (h/test "clear icon is shown when input has text"
(with-redefs [clipboard/get-string #(% "")] (with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input (h/render [address-input/address-input
{:scanned-value "scanned value" {:default-value "default value"
:ens-regex ens-regex}]) :ens-regex ens-regex}])
(-> (h/wait-for #(h/get-by-label-text :clear-button-container)) (-> (h/wait-for #(h/get-by-label-text :clear-button-container))
(.then #(h/is-truthy (h/get-by-label-text :clear-button)))))) (.then #(h/is-truthy (h/get-by-label-text :clear-button))))))
@ -58,7 +53,7 @@
(h/test "on blur with text and blur? false" (h/test "on blur with text and blur? false"
(with-redefs [clipboard/get-string #(% "")] (with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input (h/render [address-input/address-input
{:scanned-value "scanned value" {:default-value "default value"
:ens-regex ens-regex}]) :ens-regex ens-regex}])
(-> (h/wait-for #(h/get-by-label-text :clear-button)) (-> (h/wait-for #(h/get-by-label-text :clear-button))
(.then (fn [] (.then (fn []
@ -71,7 +66,7 @@
(h/test "on blur with text blur? true" (h/test "on blur with text blur? true"
(with-redefs [clipboard/get-string #(% "")] (with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input (h/render [address-input/address-input
{:scanned-value "scanned value" {:default-value "default value"
:blur? true :blur? true
:ens-regex ens-regex}]) :ens-regex ens-regex}])
(-> (h/wait-for #(h/get-by-label-text :clear-button)) (-> (h/wait-for #(h/get-by-label-text :clear-button))
@ -106,7 +101,7 @@
(let [on-clear (h/mock-fn)] (let [on-clear (h/mock-fn)]
(with-redefs [clipboard/get-string #(% "")] (with-redefs [clipboard/get-string #(% "")]
(h/render [address-input/address-input (h/render [address-input/address-input
{:scanned-value "scanned value" {:default-value "default value"
:on-clear on-clear :on-clear on-clear
:ens-regex ens-regex}]) :ens-regex ens-regex}])
(-> (h/wait-for #(h/get-by-label-text :clear-button)) (-> (h/wait-for #(h/get-by-label-text :clear-button))
@ -148,7 +143,7 @@
(-> (h/wait-for #(h/get-by-label-text :clear-button)) (-> (h/wait-for #(h/get-by-label-text :clear-button))
(.then (fn [] (.then (fn []
(h/has-prop (h/get-by-label-text :address-text-input) (h/has-prop (h/get-by-label-text :address-text-input)
:default-value :value
clipboard))))))) clipboard)))))))
(h/test "ENS loading state and call on-detect-ens" (h/test "ENS loading state and call on-detect-ens"

View File

@ -8,7 +8,6 @@
[react-native.clipboard :as clipboard] [react-native.clipboard :as clipboard]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.platform :as platform] [react-native.platform :as platform]
[reagent.core :as reagent]
[utils.i18n :as i18n])) [utils.i18n :as i18n]))
(defn- icon-color (defn- icon-color
@ -55,124 +54,135 @@
(and (not= status :default) (not blur?)) (and (not= status :default) (not blur?))
(colors/theme-colors colors/neutral-30 colors/neutral-60 theme))) (colors/theme-colors colors/neutral-30 colors/neutral-60 theme)))
(defn- f-address-input-internal (defn address-input
[] [{:keys [default-value blur? on-change-text on-blur on-focus on-clear on-scan
(let [status (reagent/atom :default) on-detect-ens on-detect-address on-detect-unclassified address-regex ens-regex
value (reagent/atom "") valid-ens-or-address? container-style]}]
focused? (atom false)] (let [theme (quo.theme/use-theme-value)
(fn [{:keys [scanned-value theme blur? on-change-text on-blur on-focus on-clear on-scan [status set-status] (rn/use-state :default)
on-detect-ens on-detect-address on-detect-unclassified address-regex ens-regex value (rn/use-ref-atom nil)
valid-ens-or-address? container-style]}] [_ trigger-render-value] (rn/use-state @value)
(let [on-change (fn [text] [focused? set-focused] (rn/use-state false)
(when (not= @value text) on-change (rn/use-callback
(let [address? (when address-regex (fn [text]
(boolean (re-matches address-regex text))) (let [address? (when address-regex
ens? (when ens-regex (boolean (re-matches address-regex text)))
(boolean (re-matches ens-regex text)))] ens? (when ens-regex
(if (> (count text) 0) (boolean (re-matches ens-regex text)))]
(reset! status :typing) (reset! value text)
(reset! status :active)) (if (> (count text) 0)
(reset! value text) (set-status :typing)
(when on-change-text (set-status :active))
(on-change-text text)) (when on-change-text
(when (and on-detect-ens ens?) (on-change-text text))
(reset! status :loading) (when (and on-detect-ens ens?)
(on-detect-ens text #(reset! status :typing))) (set-status :loading)
(when (and address? on-detect-address) (on-detect-ens text #(set-status :typing)))
(reset! status :loading) (when (and address? on-detect-address)
(on-detect-address text)) (set-status :loading)
(when (and (not address?) (on-detect-address text))
(not ens?) (when (and (not address?)
on-detect-unclassified) (not ens?)
(on-detect-unclassified text))))) on-detect-unclassified)
on-paste (fn [] (on-detect-unclassified text)))))
(clipboard/get-string set-value (rn/use-callback
(fn [clipboard] (fn [new-value]
(when-not (empty? clipboard) (reset! value new-value)
(on-change clipboard) (on-change new-value)
(reset! value clipboard))))) (trigger-render-value new-value)))
on-clear (fn [] on-paste (rn/use-callback
(reset! value "") (fn []
(reset! status (if @focused? :active :default)) (clipboard/get-string
(when on-change-text (fn [clipboard]
(on-change-text "")) (when-not (empty? clipboard)
(when on-clear (set-value clipboard))))))
(on-clear))) on-clear (rn/use-callback
on-scan #(when on-scan (fn []
(on-scan)) (set-value "")
on-focus (fn [] (set-status (if focused? :active :default))
(when (= (count @value) 0) (when on-change-text
(reset! status :active)) (on-change-text ""))
(reset! focused? true) (when on-clear
(when on-focus (on-focus))) (on-clear)))
on-blur (fn [] [focused?])
(when (= @status :active) on-clear (rn/use-callback
(reset! status :default)) (fn []
(reset! focused? false) (set-value "")
(when on-blur (on-blur))) (set-status (if focused? :active :default))
placeholder-text-color (get-placeholder-text-color @status theme blur?)] (when on-change-text
(rn/use-effect (fn [] (on-change-text ""))
(when-not (empty? scanned-value) (when on-clear
(on-change scanned-value))) (on-clear)))
[scanned-value]) [focused?])
[rn/view {:style (style/container container-style)} on-scan (when on-scan (rn/use-callback #(on-scan set-value)))
[rn/text-input on-focus (rn/use-callback
{:accessibility-label :address-text-input (fn []
:style (style/input-text theme) (when (= (count @value) 0)
:placeholder (i18n/label :t/name-ens-or-address) (set-status :active))
:placeholder-text-color placeholder-text-color (set-focused true)
:default-value @value (when on-focus (on-focus))))
:auto-complete (when platform/ios? :off) on-blur (rn/use-callback
:auto-capitalize :none (fn []
:auto-correct false (when (= status :active)
:spell-check false (set-status :default))
:keyboard-appearance (quo.theme/theme-value :light :dark theme) (set-focused false)
:on-focus on-focus (when on-blur (on-blur)))
:on-blur on-blur [status])
:on-change-text on-change}] placeholder-text-color (rn/use-memo #(get-placeholder-text-color status theme blur?)
(when (or (= @status :default) [status theme blur?])]
(= @status :active)) (rn/use-mount #(on-change (or default-value "")))
[rn/view [rn/view {:style (style/container container-style)}
{:style style/buttons-container [rn/text-input
:accessibility-label :paste-scan-buttons-container} {:accessibility-label :address-text-input
[button/button :style (style/input-text theme)
{:accessibility-label :paste-button :placeholder (i18n/label :t/name-ens-or-address)
:type :outline :placeholder-text-color placeholder-text-color
:size 24 :value @value
:container-style {:margin-right 8} :auto-complete (when platform/ios? :off)
:inner-style (style/accessory-button blur? theme) :auto-capitalize :none
:on-press on-paste} :auto-correct false
(i18n/label :t/paste)] :spell-check false
[button/button :keyboard-appearance (quo.theme/theme-value :light :dark theme)
{:accessibility-label :scan-button :on-focus on-focus
:icon-only? true :on-blur on-blur
:type :outline :on-change-text on-change}]
:size 24 (when (or (= status :default)
:inner-style (style/accessory-button blur? theme) (= status :active))
:on-press on-scan} [rn/view
:main-icons/scan]]) {:style style/buttons-container
(when (= @status :typing) :accessibility-label :paste-scan-buttons-container}
[rn/view [button/button
{:style style/buttons-container {:accessibility-label :paste-button
:accessibility-label :clear-button-container} :type :outline
[clear-button :size 24
{:on-press on-clear :container-style {:margin-right 8}
:blur? blur? :inner-style (style/accessory-button blur? theme)
:theme theme}]]) :on-press on-paste}
(when (and (= @status :loading) (not valid-ens-or-address?)) (i18n/label :t/paste)]
[rn/view (when on-scan
{:style style/buttons-container [button/button
:accessibility-label :loading-button-container} {:accessibility-label :scan-button
[loading-icon blur? theme]]) :icon-only? true
(when (and (= @status :loading) valid-ens-or-address?) :type :outline
[rn/view :size 24
{:style style/buttons-container :inner-style (style/accessory-button blur? theme)
:accessibility-label :positive-button-container} :on-press on-scan}
[positive-state-icon theme]])])))) :main-icons/scan])])
(when (= status :typing)
(defn address-input-internal [rn/view
[props] {:style style/buttons-container
[:f> f-address-input-internal props]) :accessibility-label :clear-button-container}
[clear-button
(def address-input {:on-press on-clear
(quo.theme/with-theme address-input-internal)) :blur? blur?
:theme theme}]])
(when (and (= status :loading) (not valid-ens-or-address?))
[rn/view
{:style style/buttons-container
:accessibility-label :loading-button-container}
[loading-icon blur? theme]])
(when (and (= status :loading) valid-ens-or-address?)
[rn/view
{:style style/buttons-container
:accessibility-label :positive-button-container}
[positive-state-icon theme]])]))

View File

@ -5,8 +5,7 @@
[quo.components.markdown.text :as text] [quo.components.markdown.text :as text]
[quo.theme :as quo.theme] [quo.theme :as quo.theme]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.platform :as platform] [react-native.platform :as platform]))
[reagent.core :as reagent]))
(defn- label-&-counter (defn- label-&-counter
[{:keys [label current-chars char-limit variant-colors]}] [{:keys [label current-chars char-limit variant-colors]}]
@ -61,99 +60,104 @@
:container-style]) :container-style])
(defn- base-input (defn- base-input
[{:keys [on-change-text on-char-limit-reach weight default-value]}] [{:keys [blur? theme error? right-icon left-icon disabled? small? button
(let [status (reagent/atom :default) label char-limit multiline? clearable? on-focus on-blur container-style
internal-on-focus #(reset! status :focus) on-change-text on-char-limit-reach weight default-value]
internal-on-blur #(reset! status :default) :as props}]
multiple-lines? (reagent/atom false) (let [[status set-status] (rn/use-state :default)
set-multiple-lines! #(let [height (oops/oget % "nativeEvent.contentSize.height") internal-on-focus (rn/use-callback #(set-status :focus))
;; In Android height comes with padding internal-on-blur (rn/use-callback #(set-status :default))
min-height (if platform/android? 40 22)] [multiple-lines?
(if (> height min-height) set-multiple-lines] (rn/use-state false)
(reset! multiple-lines? true) on-content-size-change (rn/use-callback
(reset! multiple-lines? false))) (fn [event]
char-count (reagent/atom (count default-value)) (let [height (oops/oget event "nativeEvent.contentSize.height")
update-char-limit! (fn [new-text char-limit] ;; In Android height comes with padding
(when on-change-text (on-change-text new-text)) min-height (if platform/android? 40 22)]
(let [amount-chars (count new-text)] (if (> height min-height)
(reset! char-count amount-chars) (set-multiple-lines true)
(when (and (>= amount-chars char-limit) on-char-limit-reach) (set-multiple-lines false)))))
(on-char-limit-reach amount-chars))))] [char-count
(fn [{:keys [blur? theme error? right-icon left-icon disabled? small? button set-char-count] (rn/use-state (count default-value))
label char-limit multiline? clearable? on-focus on-blur container-style] on-change-text (rn/use-callback
:as props}] (fn [new-text]
(let [status-kw (cond (when on-change-text (on-change-text new-text))
disabled? :disabled (let [amount-chars (count new-text)]
error? :error (set-char-count amount-chars)
:else @status) (when (and (>= amount-chars char-limit) on-char-limit-reach)
colors-by-status (style/status-colors status-kw blur? theme) (on-char-limit-reach amount-chars)))))
variant-colors (style/variants-colors blur? theme) status-kw (cond
clean-props (apply dissoc props custom-props)] disabled? :disabled
[rn/view {:style container-style} error? :error
(when (or label char-limit) :else status)
[label-&-counter colors-by-status (style/status-colors status-kw blur? theme)
{:variant-colors variant-colors variant-colors (style/variants-colors blur? theme)
:label label clean-props (apply dissoc props custom-props)]
:current-chars @char-count [rn/view {:style container-style}
:char-limit char-limit}]) (when (or label char-limit)
[rn/view {:style (style/input-container colors-by-status small? disabled?)} [label-&-counter
(when-let [{:keys [icon-name]} left-icon] {:variant-colors variant-colors
[left-accessory :label label
{:variant-colors variant-colors :current-chars char-count
:small? small? :char-limit char-limit}])
:icon-name icon-name}]) [rn/view {:style (style/input-container colors-by-status small? disabled?)}
[rn/text-input (when-let [{:keys [icon-name]} left-icon]
(cond-> {:style (style/input colors-by-status small? @multiple-lines? weight) [left-accessory
:accessibility-label :input {:variant-colors variant-colors
:placeholder-text-color (:placeholder colors-by-status) :small? small?
:keyboard-appearance (quo.theme/theme-value :light :dark theme) :icon-name icon-name}])
:cursor-color (:cursor variant-colors) [rn/text-input
:editable (not disabled?) (cond-> {:style (style/input colors-by-status small? multiple-lines? weight)
:on-focus (fn [] :accessibility-label :input
(when on-focus (on-focus)) :placeholder-text-color (:placeholder colors-by-status)
(internal-on-focus)) :keyboard-appearance (quo.theme/theme-value :light :dark theme)
:on-blur (fn [] :cursor-color (:cursor variant-colors)
(when on-blur (on-blur)) :editable (not disabled?)
(internal-on-blur))} :on-focus (fn []
:always (merge clean-props) (when on-focus (on-focus))
multiline? (assoc :multiline true (internal-on-focus))
:on-content-size-change set-multiple-lines!) :on-blur (fn []
char-limit (assoc :on-change-text #(update-char-limit! % char-limit)))] (when on-blur (on-blur))
(when-let [{:keys [on-press icon-name style-fn]} right-icon] (internal-on-blur))}
[right-accessory :always (merge clean-props)
{:variant-colors variant-colors multiline? (assoc :multiline true
:small? small? :on-content-size-change on-content-size-change)
:disabled? disabled? char-limit (assoc :on-change-text on-change-text))]
:icon-style-fn style-fn (when-let [{:keys [on-press icon-name style-fn]} right-icon]
:icon-name icon-name [right-accessory
:on-press (fn [] {:variant-colors variant-colors
(when clearable? (reset! char-count 0)) :small? small?
(on-press))}]) :disabled? disabled?
(when-let [{:keys [on-press text]} button] :icon-style-fn style-fn
[right-button :icon-name icon-name
{:colors-by-status colors-by-status :on-press (fn []
:variant-colors variant-colors (when clearable? (set-char-count 0))
:small? small? (on-press))}])
:disabled? disabled? (when-let [{:keys [on-press text]} button]
:on-press on-press [right-button
:text text}])]])))) {:colors-by-status colors-by-status
:variant-colors variant-colors
:small? small?
:disabled? disabled?
:on-press on-press
:text text}])]]))
(defn- password-input (defn- password-input
[{:keys [default-shown?] [{:keys [default-shown?]
:or {default-shown? false}}] :or {default-shown? false}
(let [password-shown? (reagent/atom default-shown?)] :as props}]
(fn [props] (let [[password-shown? set-password-shown] (rn/use-state default-shown?)]
[base-input [base-input
(assoc props (assoc props
:accessibility-label :password-input :accessibility-label :password-input
:auto-capitalize :none :auto-capitalize :none
:auto-complete :password :auto-complete :password
:secure-text-entry (not @password-shown?) :secure-text-entry (not password-shown?)
:right-icon {:style-fn style/password-icon :right-icon {:style-fn style/password-icon
:icon-name (if @password-shown? :i/hide-password :i/reveal) :icon-name (if password-shown? :i/hide-password :i/reveal)
:on-press #(swap! password-shown? not)})]))) :on-press #(set-password-shown (not password-shown?))})]))
(defn input-internal (defn input
"This input supports the following properties: "This input supports the following properties:
- :type - Can be `:text`(default) or `:password`. - :type - Can be `:text`(default) or `:password`.
- :blur? - Boolean to set the blur color variant. - :blur? - Boolean to set the blur color variant.
@ -177,21 +181,15 @@
- :on-change-text - :on-change-text
... ...
" "
[{:keys [type clearable? on-clear on-change-text icon-name] [{:keys [type clearable? on-clear icon-name]
:or {type :text} :or {type :text}
:as props}] :as props}]
(let [base-props (cond-> props (let [base-props (cond-> props
icon-name (assoc-in [:left-icon :icon-name] icon-name) icon-name (assoc-in [:left-icon :icon-name] icon-name)
clearable? (assoc :right-icon clearable? (assoc :right-icon
{:style-fn style/clear-icon {:style-fn style/clear-icon
:icon-name :i/clear :icon-name :i/clear
:on-press #(when on-clear (on-clear))}) :on-press #(when on-clear (on-clear))}))]
on-change-text (assoc :on-change-text
(fn [new-text]
(on-change-text new-text)
(reagent/flush))))]
(if (= type :password) (if (= type :password)
[password-input base-props] [password-input base-props]
[base-input base-props]))) [base-input base-props])))
(def input (quo.theme/with-theme input-internal))

View File

@ -3,8 +3,7 @@
[clojure.string :as string] [clojure.string :as string]
[quo.components.inputs.recovery-phrase.style :as style] [quo.components.inputs.recovery-phrase.style :as style]
[quo.theme :as quo.theme] [quo.theme :as quo.theme]
[react-native.core :as rn] [react-native.core :as rn]))
[reagent.core :as reagent]))
(def ^:private custom-props (def ^:private custom-props
[:customization-color :theme :blur? :cursor-color :multiline :on-focus :on-blur [:customization-color :theme :blur? :cursor-color :multiline :on-focus :on-blur
@ -38,42 +37,42 @@
:idx 0}) :idx 0})
:result))) :result)))
(defn recovery-phrase-input-internal (defn recovery-phrase-input
[_ _] [{:keys [customization-color blur? on-focus on-blur mark-errors?
(let [state (reagent/atom :default) error-pred-current-word error-pred-written-words word-limit
set-focused #(reset! state :focused) container-style]
set-default #(reset! state :default)] :or {customization-color :blue
(fn [{:keys [customization-color theme blur? on-focus on-blur mark-errors? word-limit ##Inf
error-pred-current-word error-pred-written-words word-limit error-pred-current-word (constantly false)
container-style] error-pred-written-words (constantly false)}
:or {customization-color :blue :as props}
word-limit ##Inf text]
error-pred-current-word (constantly false) (let [theme (quo.theme/use-theme-value)
error-pred-written-words (constantly false)} [state set-state] (rn/use-state :default)
:as props} on-focus (rn/use-callback
text] (fn []
(let [extra-props (apply dissoc props custom-props)] (set-state :focused)
[rn/view {:style (style/container container-style)} (when on-focus (on-focus))))
[rn/text-input on-blur (rn/use-callback
(merge {:accessibility-label :recovery-phrase-input (fn []
:style (style/input) (set-state :default)
:placeholder-text-color (style/placeholder-color @state theme blur?) (when on-blur (on-blur))))
:cursor-color (style/cursor-color customization-color theme) extra-props (apply dissoc props custom-props)]
:keyboard-appearance (quo.theme/theme-value :light :dark theme) [rn/view {:style (style/container container-style)}
:multiline true [rn/text-input
:on-focus (fn [] (merge {:accessibility-label :recovery-phrase-input
(set-focused) :style (style/input)
(when on-focus (on-focus))) :placeholder-text-color (style/placeholder-color state theme blur?)
:on-blur (fn [] :cursor-color (style/cursor-color customization-color theme)
(set-default) :keyboard-appearance (quo.theme/theme-value :light :dark theme)
(when on-blur (on-blur)))} :multiline true
extra-props) :on-focus on-focus
(if mark-errors? :on-blur on-blur}
(mark-error-words {:pred-last-word error-pred-current-word extra-props)
:pred-previous-words error-pred-written-words (if mark-errors?
:text text (mark-error-words {:pred-last-word error-pred-current-word
:word-limit word-limit :pred-previous-words error-pred-written-words
:theme theme}) :text text
text)]])))) :word-limit word-limit
:theme theme})
(def recovery-phrase-input (quo.theme/with-theme recovery-phrase-input-internal)) text)]]))

View File

@ -4,8 +4,7 @@
[quo.components.icon :as icon] [quo.components.icon :as icon]
[quo.components.inputs.search-input.style :as style] [quo.components.inputs.search-input.style :as style]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[react-native.core :as rn] [react-native.core :as rn]))
[reagent.core :as reagent]))
(def ^:private tag-separator [rn/view {:style style/tag-separator}]) (def ^:private tag-separator [rn/view {:style style/tag-separator}])
@ -41,54 +40,52 @@
text-input)) text-input))
(defn search-input (defn search-input
[{:keys [value]}] [{:keys [value tags disabled? blur? on-change-text customization-color
(let [state (reagent/atom :default) on-clear on-focus on-blur override-theme container-style]
set-active #(reset! state :active) :or {customization-color :blue}
set-default #(reset! state :default) :as props}
scroll-view-ref (atom nil) & children]
use-value? (boolean value)] (let [[state set-state] (rn/use-state :default)
(fn [{:keys [value tags disabled? blur? on-change-text customization-color on-focus (rn/use-callback
on-clear on-focus on-blur override-theme container-style] (fn []
:or {customization-color :blue} (set-state :active)
:as props} (when on-focus (on-focus))))
& children] on-blur (rn/use-callback
(let [clean-props (apply dissoc props props-to-remove)] (fn []
[rn/view (set-state :default)
{:accessibility-label :search-input (when on-blur (on-blur))))
:style (style/container container-style)} scroll-view-ref (rn/use-ref-atom nil)
[rn/scroll-view on-scroll-view-ref (rn/use-callback #(reset! scroll-view-ref %))
{:ref #(reset! scroll-view-ref %) on-key-press (rn/use-callback #(handle-backspace % @scroll-view-ref))
:style style/scroll-container use-value? (boolean value)
:content-container-style style/scroll-content clean-props (apply dissoc props props-to-remove)]
:horizontal true [rn/view
:shows-horizontal-scroll-indicator false} {:accessibility-label :search-input
(when (seq tags) :style (style/container container-style)}
[inner-tags tags]) [rn/scroll-view
{:ref on-scroll-view-ref
(add-children :style style/scroll-container
[rn/text-input :content-container-style style/scroll-content
(cond-> {:style (style/input-text disabled?) :horizontal true
:cursor-color (style/cursor customization-color override-theme) :shows-horizontal-scroll-indicator false}
:placeholder-text-color (style/placeholder-color @state blur? override-theme) (when (seq tags)
:editable (not disabled?) [inner-tags tags])
:on-key-press #(handle-backspace % @scroll-view-ref) (add-children
:keyboard-appearance (colors/theme-colors :light :dark override-theme) [rn/text-input
:on-change-text (fn [new-text] (cond-> {:style (style/input-text disabled?)
(when on-change-text :cursor-color (style/cursor customization-color override-theme)
(on-change-text new-text)) :placeholder-text-color (style/placeholder-color state blur? override-theme)
(reagent/flush)) :editable (not disabled?)
:on-focus (fn [] :on-key-press on-key-press
(set-active) :keyboard-appearance (colors/theme-colors :light :dark override-theme)
(when on-focus (on-focus))) :on-change-text on-change-text
:on-blur (fn [] :on-focus on-focus
(set-default) :on-blur on-blur}
(when on-blur (on-blur)))} use-value? (assoc :value value)
use-value? (assoc :value value) (seq clean-props) (merge clean-props))]
(seq clean-props) (merge clean-props))] (when-not use-value? children))]
(when-not use-value? children))] (when (or (seq value) (seq children))
[clear-button
(when (or (seq value) (seq children)) {:on-press on-clear
[clear-button :blur? blur?
{:on-press on-clear :override-theme override-theme}])]))
:blur? blur?
:override-theme override-theme}])]))))

View File

@ -4,8 +4,7 @@
[quo.components.inputs.title-input.style :as style] [quo.components.inputs.title-input.style :as style]
[quo.components.markdown.text :as text] [quo.components.markdown.text :as text]
[quo.theme :as quo.theme] [quo.theme :as quo.theme]
[react-native.core :as rn] [react-native.core :as rn]))
[reagent.core :as reagent]))
(defn- pad-0 (defn- pad-0
[value] [value]
@ -13,81 +12,70 @@
(str 0 value) (str 0 value)
value)) value))
(defn- view-internal (defn view
[{:keys [blur? [{:keys [blur? on-change-text auto-focus placeholder max-length default-value return-key-type
on-change-text size on-focus on-blur container-style customization-color disabled?]
auto-focus
placeholder
max-length
default-value
return-key-type
size
theme
on-focus
on-blur
container-style]
:or {max-length 0 :or {max-length 0
auto-focus false auto-focus false
default-value ""}}] default-value ""}}]
(let [focused? (reagent/atom auto-focus) (let [theme (quo.theme/use-theme-value)
value (reagent/atom default-value) [focused? set-focused] (rn/use-state auto-focus)
input-ref (atom nil) [value set-value] (rn/use-state default-value)
on-change (fn [v] input-ref (rn/use-ref-atom nil)
(reset! value v) on-inpur-ref (rn/use-callback #(reset! input-ref %))
(when on-change-text on-press (rn/use-callback
(on-change-text v)))] #(when-not disabled? (.focus ^js @input-ref))
(fn [{:keys [customization-color disabled?]}] [disabled?])
[rn/view on-change (rn/use-callback
{:style (merge (style/container disabled?) container-style)} (fn [v]
[rn/view {:style style/text-input-container} (set-value v)
[rn/text-input (when on-change-text (on-change-text v))))
{:style on-focus (rn/use-callback
(text/text-style (fn []
{:size (or size :heading-1) (when (fn? on-focus) (on-focus))
:weight :semi-bold (set-focused true)))
:style (style/title-text theme)}) on-blur (rn/use-callback
:default-value default-value (fn []
:accessibility-label :profile-title-input (when (fn? on-blur) (on-blur))
:keyboard-appearance (quo.theme/theme-value :light :dark theme) (set-focused false)))]
:return-key-type return-key-type [rn/view
:on-focus (fn [] {:style (merge (style/container disabled?) container-style)}
(when (fn? on-focus) [rn/view {:style style/text-input-container}
(on-focus)) [rn/text-input
(reset! focused? true)) {:style (text/text-style
:on-blur (fn [] {:size (or size :heading-1)
(when (fn? on-blur) :weight :semi-bold
(on-blur)) :style (style/title-text theme)})
(reset! focused? false)) :default-value default-value
:auto-focus auto-focus :accessibility-label :profile-title-input
:input-mode :text :keyboard-appearance (quo.theme/theme-value :light :dark theme)
:on-change-text on-change :return-key-type return-key-type
:editable (not disabled?) :on-focus on-focus
:max-length max-length :on-blur on-blur
:placeholder placeholder :auto-focus auto-focus
:ref #(reset! input-ref %) :input-mode :text
:selection-color (style/get-selection-color customization-color blur? theme) :on-change-text on-change
:placeholder-text-color (if @focused? :editable (not disabled?)
(style/get-focused-placeholder-color blur? theme) :max-length max-length
(style/get-placeholder-color blur? theme))}]] :placeholder placeholder
[rn/view :ref on-inpur-ref
{:style (style/counter-container @focused?)} :selection-color (style/get-selection-color customization-color blur? theme)
(if @focused? :placeholder-text-color (if focused?
[text/text (style/get-focused-placeholder-color blur? theme)
[text/text (style/get-placeholder-color blur? theme))}]]
{:style (style/char-count blur? theme) [rn/view
:size :paragraph-2} {:style (style/counter-container focused?)}
(pad-0 (if focused?
(str [text/text
(count @value)))] [text/text
[text/text {:style (style/char-count blur? theme)
{:style (style/char-count blur? theme) :size :paragraph-2}
:size :paragraph-2} (pad-0
(str "/" (str
(pad-0 (count value)))]
(str max-length)))]] [text/text
[rn/pressable {:style (style/char-count blur? theme)
{:on-press #(when-not disabled? :size :paragraph-2}
(.focus ^js @input-ref))} (str "/" (pad-0 (str max-length)))]]
[icon/icon :i/edit {:color (style/get-char-count-color blur? theme)}]])]]))) [rn/pressable {:on-press on-press}
[icon/icon :i/edit {:color (style/get-char-count-color blur? theme)}]])]]))
(def view (quo.theme/with-theme view-internal))

View File

@ -25,8 +25,8 @@
:blur? (:blur? @state) :blur? (:blur? @state)
:show-blur-background? true} :show-blur-background? true}
[quo/address-input [quo/address-input
(merge @state (merge (dissoc @state :scanned-value)
{:on-scan #(js/alert "Not implemented yet") {:on-scan (fn [on-result] (on-result (:scanned-value @state)))
:ens-regex constants/regx-ens :ens-regex constants/regx-ens
:on-detect-ens (fn [_] :on-detect-ens (fn [_]
(swap! state assoc :valid-ens-or-address? false) (swap! state assoc :valid-ens-or-address? false)

View File

@ -21,10 +21,14 @@
(h/fire-event :change-text (h/fire-event :change-text
(h/get-by-label-text :add-address-to-watch) (h/get-by-label-text :add-address-to-watch)
"0x12E838Ae1f769147b12956485dc56e57138f3AC8") "0x12E838Ae1f769147b12956485dc56e57138f3AC8")
(h/is-truthy (h/get-by-translation-text :t/address-already-in-use))) (-> (h/wait-for #(h/get-by-translation-text :t/address-already-in-use))
(.then (fn []
(h/is-truthy (h/get-by-translation-text :t/address-already-in-use))))))
(h/test "validation messages show for invalid address" (h/test "validation messages show for invalid address"
(h/render [add-address-to-watch/view]) (h/render [add-address-to-watch/view])
(h/is-falsy (h/query-by-label-text :error-message)) (h/is-falsy (h/query-by-label-text :error-message))
(h/fire-event :change-text (h/get-by-label-text :add-address-to-watch) "0x12E838Ae1f769147b") (h/fire-event :change-text (h/get-by-label-text :add-address-to-watch) "0x12E838Ae1f769147b")
(h/is-truthy (h/get-by-translation-text :t/invalid-address)))) (-> (h/wait-for #(h/get-by-translation-text :t/invalid-address))
(.then (fn []
(h/is-truthy (h/get-by-translation-text :t/invalid-address)))))))