Support hooks & atom state management in previews (#20053)

Change the implementation of component previews to support receiving either
state/set-state from use-state or a Reagent atom, thus allowing us to gradually
change preview namespaces to use hooks instead of having to refactor all at once
in a gigantic PR.

All types of preview fields were tested, including multi-select.

Only the components counter.step and selectors.react previews were adapted
to use-state.
This commit is contained in:
Icaro Motta 2024-05-17 16:48:57 -03:00 committed by GitHub
parent 624593ec35
commit ea58e52dc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 172 additions and 123 deletions

View File

@ -1,7 +1,7 @@
(ns status-im.contexts.preview.quo.counter.step (ns status-im.contexts.preview.quo.counter.step
(:require (:require
[quo.core :as quo] [quo.core :as quo]
[reagent.core :as reagent] [react-native.core :as rn]
[status-im.contexts.preview.quo.preview :as preview])) [status-im.contexts.preview.quo.preview :as preview]))
(def descriptor (def descriptor
@ -17,13 +17,13 @@
(defn view (defn view
[] []
(let [state (reagent/atom {:value "5" (let [[state set-state] (rn/use-state {:value "5"
:type :neutral :type :neutral
:in-blur-view? false})] :in-blur-view? false})]
(fn [] [preview/preview-container
[preview/preview-container {:state state
{:state state :set-state set-state
:descriptor descriptor :descriptor descriptor
:blur? (:in-blur-view? @state) :blur? (:in-blur-view? state)
:show-blur-background? (:in-blur-view? @state)} :show-blur-background? (:in-blur-view? state)}
[quo/step (dissoc @state :value) (:value @state)]]))) [quo/step (dissoc state :value) (:value state)]]))

View File

@ -46,70 +46,94 @@
(str (humanize k) ":")) (str (humanize k) ":"))
(defn- customizer-boolean (defn- customizer-boolean
[{:keys [label state] :as args}] [{:keys [label state set-state] :as args}]
(let [theme (quo.theme/use-theme) (let [theme (quo.theme/use-theme)
label (or label (key->boolean-label (:key args))) label (or label (key->boolean-label (:key args)))
field-value (reagent/cursor state [(:key args)]) field-cursor (when-not (fn? set-state)
active? @field-value] (reagent/cursor state [(:key args)]))
field-value (if (fn? set-state)
(get state (:key args))
@field-cursor)
set-field-value (fn [value]
(if (fn? set-state)
(set-state (assoc state (:key args) value))
(reset! field-cursor value)))]
[rn/view {:style style/field-row} [rn/view {:style style/field-row}
[label-view state label theme] [label-view state label theme]
[rn/view {:style (style/boolean-container)} [rn/view {:style (style/boolean-container)}
[rn/pressable [rn/pressable
{:style (style/boolean-button {:active? active? :left? true} theme) {:style (style/boolean-button {:active? field-value :left? true} theme)
:on-press #(reset! field-value true)} :on-press #(set-field-value true)}
[rn/text {:style (style/field-text active? theme)} [rn/text {:style (style/field-text field-value theme)}
"True"]] "True"]]
[rn/pressable [rn/pressable
{:style (style/boolean-button {:active? (not active?) :left? false} theme) {:style (style/boolean-button {:active? (not field-value) :left? false} theme)
:on-press #(reset! field-value false)} :on-press #(set-field-value false)}
[rn/text {:style (style/field-text (not active?) theme)} [rn/text {:style (style/field-text (not field-value) theme)}
"False"]]]])) "False"]]]]))
(defn- customizer-text (defn- customizer-text
[{:keys [label state limit suffix] :as args} theme] [{:keys [label state set-state limit suffix] :as args} theme]
(let [label (or label (key->text-label (:key args))) (let [label (or label (key->text-label (:key args)))
field-value (reagent/cursor state [(:key args)])] field-cursor (when-not (fn? set-state)
(reagent/cursor state [(:key args)]))
field-value (if (fn? set-state)
(get state (:key args))
@field-cursor)
set-field-value (fn [value]
(if (fn? set-state)
(set-state (assoc state (:key args) value))
(reset! field-cursor value)))]
[rn/view {:style style/field-row} [rn/view {:style style/field-row}
[label-view state label theme] [label-view state label theme]
[rn/view {:style style/field-column} [rn/view {:style style/field-column}
[rn/text-input [rn/text-input
(merge (merge
{:value @field-value {:value field-value
:show-cancel false :show-cancel false
:style (style/field-container false theme) :style (style/field-container false theme)
:keyboard-appearance theme :keyboard-appearance theme
:on-change-text (fn [text] :on-change-text (fn [text]
(reset! field-value (if (and suffix (set-field-value (if (and suffix
(> (count text) (count @field-value))) (> (count text) (count field-value)))
(str (string/replace text suffix "") suffix) (str (string/replace text suffix "") suffix)
text)) text))
(reagent/flush))} (when-not (fn? set-state)
(reagent/flush)))}
(when limit (when limit
{:max-length limit}))]]])) {:max-length limit}))]]]))
(defn- customizer-number (defn- customizer-number
[{:keys [label state default] :as args} theme] [{:keys [label state set-state default] :as args} theme]
(let [label (or label (key->text-label (:key args))) (let [label (or label (key->text-label (:key args)))
field-value (reagent/cursor state [(:key args)])] field-cursor (when-not (fn? set-state)
(reagent/cursor state [(:key args)]))
field-value (if (fn? set-state)
(get state (:key args))
@field-cursor)
set-field-value (fn [value]
(if (fn? set-state)
(set-state (assoc state (:key args) value))
(reset! field-cursor value)))]
[rn/view {:style style/field-row} [rn/view {:style style/field-row}
[label-view state label theme] [label-view state label theme]
[rn/view {:style style/field-column} [rn/view {:style style/field-column}
[rn/text-input [rn/text-input
(merge {:value (str field-value)
{:value (str @field-value) :show-cancel false
:show-cancel false :style (style/field-container false theme)
:style (style/field-container false theme) :keyboard-appearance theme
:keyboard-appearance theme :on-change-text (fn [text]
:on-change-text (fn [text] (set-field-value (utils.number/parse-int text default))
(reset! field-value (utils.number/parse-int text default)) (when-not (fn? set-state)
(reagent/flush))})]]])) (reagent/flush)))}]]]))
(defn- find-selected-option (defn- find-selected-option
[id v] [id v]
(first (filter #(= (:key %) id) v))) (first (filter #(= (:key %) id) v)))
(defn- customizer-select-modal (defn- customizer-select-modal
[{:keys [open options field-value]}] [{:keys [open options field-value set-field-value]}]
(let [theme (quo.theme/use-theme)] (let [theme (quo.theme/use-theme)]
[rn/modal [rn/modal
{:visible @open {:visible @open
@ -125,17 +149,17 @@
:let [v (or v (humanize k))]] :let [v (or v (humanize k))]]
^{:key k} ^{:key k}
[rn/pressable [rn/pressable
{:style (style/select-option (= @field-value k) theme) {:style (style/select-option (= field-value k) theme)
:on-press (fn [] :on-press (fn []
(reset! open false) (reset! open false)
(reset! field-value k))} (set-field-value k))}
[rn/text {:style (style/field-text (= @field-value k) theme)} [rn/text {:style (style/field-text (= field-value k) theme)}
v]]))] v]]))]
[rn/view {:style (style/footer theme)} [rn/view {:style (style/footer theme)}
[rn/pressable [rn/pressable
{:style (style/select-button theme) {:style (style/select-button theme)
:on-press (fn [] :on-press (fn []
(reset! field-value nil) (set-field-value nil)
(reset! open false))} (reset! open false))}
[rn/text {:style (style/field-text false theme)} [rn/text {:style (style/field-text false theme)}
"Clear"]] "Clear"]]
@ -163,22 +187,31 @@
(defn- customizer-select (defn- customizer-select
[] []
(let [open (reagent/atom nil)] (let [open (reagent/atom nil)]
(fn [{:keys [label state options] :as args}] (fn [{:keys [label state set-state options] :as args}]
(let [theme (quo.theme/use-theme) (let [theme (quo.theme/use-theme)
label (or label (key->text-label (:key args))) label (or label (key->text-label (:key args)))
field-value (reagent/cursor state [(:key args)]) field-cursor (when-not (fn? set-state)
selected-option (find-selected-option @field-value options)] (reagent/cursor state [(:key args)]))
field-value (if (fn? set-state)
(get state (:key args))
@field-cursor)
set-field-value (fn [value]
(if (fn? set-state)
(set-state (assoc state (:key args) value))
(reset! field-cursor value)))
selected-option (find-selected-option field-value options)]
[rn/view {:style style/field-row} [rn/view {:style style/field-row}
[label-view state label theme] [label-view state label theme]
[rn/view {:style style/field-column} [rn/view {:style style/field-column}
[customizer-select-modal [customizer-select-modal
{:open open {:open open
:options options :options options
:field-value field-value}] :field-value field-value
:set-field-value set-field-value}]
[customizer-select-button {:open open :selected-option selected-option} theme]]])))) [customizer-select-button {:open open :selected-option selected-option} theme]]]))))
(defn- customizer-multi-select-modal (defn- customizer-multi-select-modal
[{:keys [open-atom options selected-keys-atom]}] [{:keys [open-atom options field-value set-field-value]}]
(let [theme (quo.theme/use-theme)] (let [theme (quo.theme/use-theme)]
[rn/modal [rn/modal
{:visible @open-atom {:visible @open-atom
@ -193,13 +226,13 @@
(for [{k :key v :value} options (for [{k :key v :value} options
:let [v (or v (humanize k))]] :let [v (or v (humanize k))]]
^{:key k} ^{:key k}
(let [checked? (boolean (some #(= k %) field-value))
(let [checked? (boolean (some #(= k %) @selected-keys-atom))
remove-key (fn [v] (filterv #(not= % k) v)) remove-key (fn [v] (filterv #(not= % k) v))
on-press (fn [] on-press (fn []
(swap! selected-keys-atom (set-field-value
(if checked? remove-key conj) (if checked?
k))] (remove-key field-value)
(conj field-value k))))]
[rn/pressable [rn/pressable
{:style (style/multi-select-option theme) {:style (style/multi-select-option theme)
:on-press on-press} :on-press on-press}
@ -212,7 +245,7 @@
[rn/pressable [rn/pressable
{:style (style/select-button theme) {:style (style/select-button theme)
:on-press (fn [] :on-press (fn []
(reset! selected-keys-atom nil) (set-field-value nil)
(reset! open-atom false))} (reset! open-atom false))}
[rn/text {:style (style/field-text false theme)} [rn/text {:style (style/field-text false theme)}
"Clear"]] "Clear"]]
@ -246,41 +279,51 @@
(defn- customizer-multi-select (defn- customizer-multi-select
[] []
(let [open (reagent/atom nil)] (let [open (reagent/atom nil)]
(fn [{:keys [label state options] :as args}] (fn [{:keys [label state set-state options] :as args}]
(let [theme (quo.theme/use-theme) (let [theme (quo.theme/use-theme)
label (or label (key->text-label (:key args))) label (or label (key->text-label (:key args)))
selected-keys (reagent/cursor state [(:key args)]) field-cursor (when-not (fn? set-state)
selected-options (filter-by-keys options @selected-keys)] (reagent/cursor state [(:key args)]))
field-value (if (fn? set-state)
(get state (:key args))
@field-cursor)
set-field-value (fn [value]
(if (fn? set-state)
(set-state (assoc state (:key args) value))
(reset! field-cursor value)))
selected-options (filter-by-keys options field-value)]
[rn/view {:style style/field-row} [rn/view {:style style/field-row}
[label-view state label theme] [label-view state label theme]
[rn/view {:style style/field-column} [rn/view {:style style/field-column}
[customizer-multi-select-modal [customizer-multi-select-modal
{:open-atom open {:open-atom open
:selected-keys-atom selected-keys :field-value field-value
:options options}] :set-field-value set-field-value
:options options}]
[customizer-multi-select-button {:open open :selected-options selected-options} theme]]])))) [customizer-multi-select-button {:open open :selected-options selected-options} theme]]]))))
(defn customizer (defn customizer
[state descriptors theme] [state set-state descriptors theme]
[rn/view [rn/view
{:style {:flex-shrink 1 {:style {:flex-shrink 1
:padding-horizontal 20}} :padding-horizontal 20}}
(doall (doall
(for [desc descriptors (for [desc descriptors
:let [desc-path (:path desc) :let [desc-path (:path desc)
new-state (if desc-path descriptor (if (fn? set-state)
(reagent/cursor state desc-path) (assoc desc :state state :set-state set-state)
state) (let [new-state (if desc-path
descriptor (assoc desc :state new-state)]] (reagent/cursor state desc-path)
state)]
(assoc desc :state new-state)))]]
^{:key (:key desc)} ^{:key (:key desc)}
[:<> (case (:type desc)
(case (:type desc) :boolean [customizer-boolean descriptor]
:boolean [customizer-boolean descriptor] :text [customizer-text descriptor theme]
:text [customizer-text descriptor theme] :number [customizer-number descriptor theme]
:number [customizer-number descriptor theme] :select [customizer-select descriptor]
:select [customizer-select descriptor] :multi-select [customizer-multi-select descriptor]
:multi-select [customizer-multi-select descriptor] nil)))])
nil)]))])
(defn customization-color-option (defn customization-color-option
([] ([]
@ -325,7 +368,7 @@
children)]) children)])
(defn- f-preview-container (defn- f-preview-container
[{:keys [title state descriptor blur? blur-dark-only? [{:keys [title state set-state descriptor blur? blur-dark-only?
component-container-style component-container-style
blur-container-style blur-view-props blur-height show-blur-background? full-screen?] blur-container-style blur-view-props blur-height show-blur-background? full-screen?]
:or {blur-height 200}} :or {blur-height 200}}
@ -352,7 +395,7 @@
:on-press rn/dismiss-keyboard!} :on-press rn/dismiss-keyboard!}
(when descriptor (when descriptor
[rn/view {:style style/customizer-container} [rn/view {:style style/customizer-container}
[customizer state descriptor theme]]) [customizer state set-state descriptor theme]])
(if blur? (if blur?
[rn/view {:style (merge style/component-container component-container-style)} [rn/view {:style (merge style/component-container component-container-style)}
(into [blur-view (into [blur-view
@ -371,8 +414,11 @@
(into [rn/view {:style (merge style/component-container component-container-style)}] (into [rn/view {:style (merge style/component-container component-container-style)}]
children))] children))]
(when state (when state
(let [decr-state (if descriptor (select-keys @state (mapv :key (flatten descriptor))) @state) (let [actual-state (if (fn? set-state) state @state)
state-str (with-out-str (cljs.pprint/pprint decr-state))] decr-state (if descriptor
(select-keys actual-state (mapv :key (flatten descriptor)))
actual-state)
state-str (with-out-str (cljs.pprint/pprint decr-state))]
[rn/view {:style {:margin 50}} [rn/view {:style {:margin 50}}
[quo/text {:style {:margin-bottom 10}} "State map (click on map to copy)"] [quo/text {:style {:margin-bottom 10}} "State map (click on map to copy)"]
[rn/pressable [rn/pressable

View File

@ -4,17 +4,16 @@
[quo.core :as quo] [quo.core :as quo]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.preview.quo.preview :as preview])) [status-im.contexts.preview.quo.preview :as preview]))
(defn- gen-quantity (defn gen-quantity
[max-count _] [max-count _]
(rand-int max-count)) (rand-int max-count))
(def ^:private memo-gen-quantity (memoize gen-quantity)) (def memo-gen-quantity (memoize gen-quantity))
(def ^:private descriptor (def descriptor
[{:key :hide-new-reaction-button? [{:key :hide-new-reaction-button?
:type :boolean} :type :boolean}
{:label "Reactions" {:label "Reactions"
@ -37,40 +36,44 @@
(defn preview-react (defn preview-react
[] []
(let [state (reagent/atom {:hide-new-reaction-button? true (let [[state set-state]
:max-count 1000 (rn/use-state {:hide-new-reaction-button? true
:reaction-ids [1 2 3] :max-count 1000
:use-case :default}) :reaction-ids [1 2 3]
pressed-reactions (reagent/atom #{1})] :use-case :default})
(fn [] [pressed-reactions set-pressed-reactions] (rn/use-state #{1})
(let [reactions (mapv (fn [reaction-id]
{:emoji-reaction-id reaction-id reactions (mapv (fn [reaction-id]
:emoji-id reaction-id {:emoji-reaction-id reaction-id
:emoji (get constants/reactions reaction-id) :emoji-id reaction-id
:quantity (memo-gen-quantity (:max-count @state) reaction-id) :emoji (get constants/reactions reaction-id)
:own (contains? @pressed-reactions reaction-id)}) :quantity (memo-gen-quantity (:max-count state) reaction-id)
(:reaction-ids @state))] :own (contains? pressed-reactions reaction-id)})
[preview/preview-container (:reaction-ids state))
{:state state
:descriptor descriptor} on-press (fn [reaction]
[rn/view (let [reaction-id (:emoji-id reaction)]
{:padding-bottom 150 (if (contains? pressed-reactions reaction-id)
:padding-vertical 60 (set-pressed-reactions (disj pressed-reactions reaction-id))
:padding-horizontal 20 (set-pressed-reactions (conj pressed-reactions
:border-radius 16 reaction-id)))))]
:background-color (when (= :pinned (:use-case @state)) [preview/preview-container
(colors/custom-color :blue 50 10)) {:state state
:align-items :flex-start} :set-state set-state
[quo/react :descriptor descriptor}
{:reactions reactions [rn/view
:hide-new-reaction-button? (:hide-new-reaction-button? @state) {:padding-bottom 150
:use-case (:use-case @state) :padding-vertical 60
:on-press (fn [reaction] :padding-horizontal 20
(let [reaction-id (:emoji-id reaction) :border-radius 16
change-pressed (partial swap! pressed-reactions)] :background-color (when (= :pinned (:use-case state))
(if (contains? @pressed-reactions reaction-id) (colors/custom-color :blue 50 10))
(change-pressed disj reaction-id) :align-items :flex-start}
(change-pressed conj reaction-id)))) [quo/react
:on-long-press identity {:reactions reactions
:on-press-new identity}]]])))) :hide-new-reaction-button? (:hide-new-reaction-button? state)
:use-case (:use-case state)
:on-press on-press
:on-long-press identity
:on-press-new identity}]]]))