From ea58e52dc1e5fb9eb90aa86c6d80e77a88b9006a Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Fri, 17 May 2024 16:48:57 -0300 Subject: [PATCH] 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. --- .../contexts/preview/quo/counter/step.cljs | 22 +- .../contexts/preview/quo/preview.cljs | 190 +++++++++++------- .../contexts/preview/quo/selectors/react.cljs | 83 ++++---- 3 files changed, 172 insertions(+), 123 deletions(-) diff --git a/src/status_im/contexts/preview/quo/counter/step.cljs b/src/status_im/contexts/preview/quo/counter/step.cljs index 183ab3724f..f7e7bb3d7e 100644 --- a/src/status_im/contexts/preview/quo/counter/step.cljs +++ b/src/status_im/contexts/preview/quo/counter/step.cljs @@ -1,7 +1,7 @@ (ns status-im.contexts.preview.quo.counter.step (:require [quo.core :as quo] - [reagent.core :as reagent] + [react-native.core :as rn] [status-im.contexts.preview.quo.preview :as preview])) (def descriptor @@ -17,13 +17,13 @@ (defn view [] - (let [state (reagent/atom {:value "5" - :type :neutral - :in-blur-view? false})] - (fn [] - [preview/preview-container - {:state state - :descriptor descriptor - :blur? (:in-blur-view? @state) - :show-blur-background? (:in-blur-view? @state)} - [quo/step (dissoc @state :value) (:value @state)]]))) + (let [[state set-state] (rn/use-state {:value "5" + :type :neutral + :in-blur-view? false})] + [preview/preview-container + {:state state + :set-state set-state + :descriptor descriptor + :blur? (:in-blur-view? state) + :show-blur-background? (:in-blur-view? state)} + [quo/step (dissoc state :value) (:value state)]])) diff --git a/src/status_im/contexts/preview/quo/preview.cljs b/src/status_im/contexts/preview/quo/preview.cljs index cf4656cdae..9b648d8de7 100644 --- a/src/status_im/contexts/preview/quo/preview.cljs +++ b/src/status_im/contexts/preview/quo/preview.cljs @@ -46,70 +46,94 @@ (str (humanize k) ":")) (defn- customizer-boolean - [{:keys [label state] :as args}] - (let [theme (quo.theme/use-theme) - label (or label (key->boolean-label (:key args))) - field-value (reagent/cursor state [(:key args)]) - active? @field-value] + [{:keys [label state set-state] :as args}] + (let [theme (quo.theme/use-theme) + label (or label (key->boolean-label (: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} [label-view state label theme] [rn/view {:style (style/boolean-container)} [rn/pressable - {:style (style/boolean-button {:active? active? :left? true} theme) - :on-press #(reset! field-value true)} - [rn/text {:style (style/field-text active? theme)} + {:style (style/boolean-button {:active? field-value :left? true} theme) + :on-press #(set-field-value true)} + [rn/text {:style (style/field-text field-value theme)} "True"]] [rn/pressable - {:style (style/boolean-button {:active? (not active?) :left? false} theme) - :on-press #(reset! field-value false)} - [rn/text {:style (style/field-text (not active?) theme)} + {:style (style/boolean-button {:active? (not field-value) :left? false} theme) + :on-press #(set-field-value false)} + [rn/text {:style (style/field-text (not field-value) theme)} "False"]]]])) (defn- customizer-text - [{:keys [label state limit suffix] :as args} theme] - (let [label (or label (key->text-label (:key args))) - field-value (reagent/cursor state [(:key args)])] + [{:keys [label state set-state limit suffix] :as args} theme] + (let [label (or label (key->text-label (: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} [label-view state label theme] [rn/view {:style style/field-column} [rn/text-input (merge - {:value @field-value + {:value field-value :show-cancel false :style (style/field-container false theme) :keyboard-appearance theme :on-change-text (fn [text] - (reset! field-value (if (and suffix - (> (count text) (count @field-value))) - (str (string/replace text suffix "") suffix) - text)) - (reagent/flush))} + (set-field-value (if (and suffix + (> (count text) (count field-value))) + (str (string/replace text suffix "") suffix) + text)) + (when-not (fn? set-state) + (reagent/flush)))} (when limit {:max-length limit}))]]])) (defn- customizer-number - [{:keys [label state default] :as args} theme] - (let [label (or label (key->text-label (:key args))) - field-value (reagent/cursor state [(:key args)])] + [{:keys [label state set-state default] :as args} theme] + (let [label (or label (key->text-label (: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} [label-view state label theme] [rn/view {:style style/field-column} [rn/text-input - (merge - {:value (str @field-value) - :show-cancel false - :style (style/field-container false theme) - :keyboard-appearance theme - :on-change-text (fn [text] - (reset! field-value (utils.number/parse-int text default)) - (reagent/flush))})]]])) + {:value (str field-value) + :show-cancel false + :style (style/field-container false theme) + :keyboard-appearance theme + :on-change-text (fn [text] + (set-field-value (utils.number/parse-int text default)) + (when-not (fn? set-state) + (reagent/flush)))}]]])) (defn- find-selected-option [id v] (first (filter #(= (:key %) id) v))) (defn- customizer-select-modal - [{:keys [open options field-value]}] + [{:keys [open options field-value set-field-value]}] (let [theme (quo.theme/use-theme)] [rn/modal {:visible @open @@ -125,17 +149,17 @@ :let [v (or v (humanize k))]] ^{:key k} [rn/pressable - {:style (style/select-option (= @field-value k) theme) + {:style (style/select-option (= field-value k) theme) :on-press (fn [] (reset! open false) - (reset! field-value k))} - [rn/text {:style (style/field-text (= @field-value k) theme)} + (set-field-value k))} + [rn/text {:style (style/field-text (= field-value k) theme)} v]]))] [rn/view {:style (style/footer theme)} [rn/pressable {:style (style/select-button theme) :on-press (fn [] - (reset! field-value nil) + (set-field-value nil) (reset! open false))} [rn/text {:style (style/field-text false theme)} "Clear"]] @@ -163,22 +187,31 @@ (defn- customizer-select [] (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) label (or label (key->text-label (:key args))) - field-value (reagent/cursor state [(:key args)]) - selected-option (find-selected-option @field-value options)] + 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))) + selected-option (find-selected-option field-value options)] [rn/view {:style style/field-row} [label-view state label theme] [rn/view {:style style/field-column} [customizer-select-modal - {:open open - :options options - :field-value field-value}] + {:open open + :options options + :field-value field-value + :set-field-value set-field-value}] [customizer-select-button {:open open :selected-option selected-option} theme]]])))) (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)] [rn/modal {:visible @open-atom @@ -193,13 +226,13 @@ (for [{k :key v :value} options :let [v (or v (humanize k))]] ^{:key k} - - (let [checked? (boolean (some #(= k %) @selected-keys-atom)) + (let [checked? (boolean (some #(= k %) field-value)) remove-key (fn [v] (filterv #(not= % k) v)) on-press (fn [] - (swap! selected-keys-atom - (if checked? remove-key conj) - k))] + (set-field-value + (if checked? + (remove-key field-value) + (conj field-value k))))] [rn/pressable {:style (style/multi-select-option theme) :on-press on-press} @@ -212,7 +245,7 @@ [rn/pressable {:style (style/select-button theme) :on-press (fn [] - (reset! selected-keys-atom nil) + (set-field-value nil) (reset! open-atom false))} [rn/text {:style (style/field-text false theme)} "Clear"]] @@ -246,41 +279,51 @@ (defn- customizer-multi-select [] (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) label (or label (key->text-label (:key args))) - selected-keys (reagent/cursor state [(:key args)]) - selected-options (filter-by-keys options @selected-keys)] + 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))) + selected-options (filter-by-keys options field-value)] [rn/view {:style style/field-row} [label-view state label theme] [rn/view {:style style/field-column} [customizer-multi-select-modal - {:open-atom open - :selected-keys-atom selected-keys - :options options}] + {:open-atom open + :field-value field-value + :set-field-value set-field-value + :options options}] [customizer-multi-select-button {:open open :selected-options selected-options} theme]]])))) (defn customizer - [state descriptors theme] + [state set-state descriptors theme] [rn/view {:style {:flex-shrink 1 :padding-horizontal 20}} (doall (for [desc descriptors :let [desc-path (:path desc) - new-state (if desc-path - (reagent/cursor state desc-path) - state) - descriptor (assoc desc :state new-state)]] + descriptor (if (fn? set-state) + (assoc desc :state state :set-state set-state) + (let [new-state (if desc-path + (reagent/cursor state desc-path) + state)] + (assoc desc :state new-state)))]] ^{:key (:key desc)} - [:<> - (case (:type desc) - :boolean [customizer-boolean descriptor] - :text [customizer-text descriptor theme] - :number [customizer-number descriptor theme] - :select [customizer-select descriptor] - :multi-select [customizer-multi-select descriptor] - nil)]))]) + (case (:type desc) + :boolean [customizer-boolean descriptor] + :text [customizer-text descriptor theme] + :number [customizer-number descriptor theme] + :select [customizer-select descriptor] + :multi-select [customizer-multi-select descriptor] + nil)))]) (defn customization-color-option ([] @@ -325,7 +368,7 @@ children)]) (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 blur-container-style blur-view-props blur-height show-blur-background? full-screen?] :or {blur-height 200}} @@ -352,7 +395,7 @@ :on-press rn/dismiss-keyboard!} (when descriptor [rn/view {:style style/customizer-container} - [customizer state descriptor theme]]) + [customizer state set-state descriptor theme]]) (if blur? [rn/view {:style (merge style/component-container component-container-style)} (into [blur-view @@ -371,8 +414,11 @@ (into [rn/view {:style (merge style/component-container component-container-style)}] children))] (when state - (let [decr-state (if descriptor (select-keys @state (mapv :key (flatten descriptor))) @state) - state-str (with-out-str (cljs.pprint/pprint decr-state))] + (let [actual-state (if (fn? set-state) state @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}} [quo/text {:style {:margin-bottom 10}} "State map (click on map to copy)"] [rn/pressable diff --git a/src/status_im/contexts/preview/quo/selectors/react.cljs b/src/status_im/contexts/preview/quo/selectors/react.cljs index 628181965c..1996f5bf33 100644 --- a/src/status_im/contexts/preview/quo/selectors/react.cljs +++ b/src/status_im/contexts/preview/quo/selectors/react.cljs @@ -4,17 +4,16 @@ [quo.core :as quo] [quo.foundations.colors :as colors] [react-native.core :as rn] - [reagent.core :as reagent] [status-im.constants :as constants] [status-im.contexts.preview.quo.preview :as preview])) -(defn- gen-quantity +(defn gen-quantity [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? :type :boolean} {:label "Reactions" @@ -37,40 +36,44 @@ (defn preview-react [] - (let [state (reagent/atom {:hide-new-reaction-button? true - :max-count 1000 - :reaction-ids [1 2 3] - :use-case :default}) - pressed-reactions (reagent/atom #{1})] + (let [[state set-state] + (rn/use-state {:hide-new-reaction-button? true + :max-count 1000 + :reaction-ids [1 2 3] + :use-case :default}) - (fn [] - (let [reactions (mapv (fn [reaction-id] - {:emoji-reaction-id reaction-id - :emoji-id reaction-id - :emoji (get constants/reactions reaction-id) - :quantity (memo-gen-quantity (:max-count @state) reaction-id) - :own (contains? @pressed-reactions reaction-id)}) - (:reaction-ids @state))] - [preview/preview-container - {:state state - :descriptor descriptor} - [rn/view - {:padding-bottom 150 - :padding-vertical 60 - :padding-horizontal 20 - :border-radius 16 - :background-color (when (= :pinned (:use-case @state)) - (colors/custom-color :blue 50 10)) - :align-items :flex-start} - [quo/react - {:reactions reactions - :hide-new-reaction-button? (:hide-new-reaction-button? @state) - :use-case (:use-case @state) - :on-press (fn [reaction] - (let [reaction-id (:emoji-id reaction) - change-pressed (partial swap! pressed-reactions)] - (if (contains? @pressed-reactions reaction-id) - (change-pressed disj reaction-id) - (change-pressed conj reaction-id)))) - :on-long-press identity - :on-press-new identity}]]])))) + [pressed-reactions set-pressed-reactions] (rn/use-state #{1}) + + reactions (mapv (fn [reaction-id] + {:emoji-reaction-id reaction-id + :emoji-id reaction-id + :emoji (get constants/reactions reaction-id) + :quantity (memo-gen-quantity (:max-count state) reaction-id) + :own (contains? pressed-reactions reaction-id)}) + (:reaction-ids state)) + + on-press (fn [reaction] + (let [reaction-id (:emoji-id reaction)] + (if (contains? pressed-reactions reaction-id) + (set-pressed-reactions (disj pressed-reactions reaction-id)) + (set-pressed-reactions (conj pressed-reactions + reaction-id)))))] + [preview/preview-container + {:state state + :set-state set-state + :descriptor descriptor} + [rn/view + {:padding-bottom 150 + :padding-vertical 60 + :padding-horizontal 20 + :border-radius 16 + :background-color (when (= :pinned (:use-case state)) + (colors/custom-color :blue 50 10)) + :align-items :flex-start} + [quo/react + {:reactions reactions + :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}]]]))