From c5a486622d96e875809720c1e98b27e22b6fe087 Mon Sep 17 00:00:00 2001 From: BalogunofAfrica <45393944+BalogunofAfrica@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:28:43 +0100 Subject: [PATCH] feat: browser input (#16487) --- .../browser/browser_input/component_spec.cljs | 58 +++++++ .../browser/browser_input/style.cljs | 56 +++++++ .../browser/browser_input/view.cljs | 144 ++++++++++++++++++ src/quo2/core.cljs | 4 + src/quo2/core_spec.cljs | 1 + .../quo_preview/browser/browser_input.cljs | 64 ++++++++ src/status_im2/contexts/quo_preview/main.cljs | 4 + 7 files changed, 331 insertions(+) create mode 100644 src/quo2/components/browser/browser_input/component_spec.cljs create mode 100644 src/quo2/components/browser/browser_input/style.cljs create mode 100644 src/quo2/components/browser/browser_input/view.cljs create mode 100644 src/status_im2/contexts/quo_preview/browser/browser_input.cljs diff --git a/src/quo2/components/browser/browser_input/component_spec.cljs b/src/quo2/components/browser/browser_input/component_spec.cljs new file mode 100644 index 0000000000..27e8e9ffc2 --- /dev/null +++ b/src/quo2/components/browser/browser_input/component_spec.cljs @@ -0,0 +1,58 @@ +(ns quo2.components.browser.browser-input.component-spec + (:require [quo2.components.browser.browser-input.view :as browser-input] + [test-helpers.component :as h])) + +(h/describe "Browser input" + (h/test "Renders empty in default state" + (h/render [browser-input/browser-input + {:on-change-text (h/mock-fn) + :value ""}]) + (h/is-truthy (h/get-by-label-text :browser-input))) + + (h/test "On change text is fired" + (let [on-change-text (h/mock-fn)] + (h/render [browser-input/browser-input + {:on-change-text on-change-text + :value "mock text"}]) + (h/fire-event :change-text (h/get-by-label-text :browser-input) "mock-text-new") + (h/was-called on-change-text))) + + (h/describe "Input Label" + (h/test "Doesn't render label text when input is focused" + (h/render [browser-input/browser-input + {:auto-focus true}]) + (h/is-null (h/query-by-label-text :browser-input-label))) + + (h/test "Renders label text when input has a value and input is not focused" + (h/render [browser-input/browser-input + {:default-value "mock default"}]) + (h/is-truthy (h/query-by-label-text :browser-input-label))) + + (h/test "Renders site favicon when specified" + (h/render [browser-input/browser-input + {:default-value "mock default" :favicon :i/verified}]) + (h/is-truthy (h/query-by-label-text :browser-input-favicon))) + + (h/test "Renders lock icon when lock is enabled" + (h/render [browser-input/browser-input + {:default-value "mock default" :locked? true}]) + (h/is-truthy (h/query-by-label-text :browser-input-locked-icon)))) + + (h/describe "Clear button" + (h/test "Doesn't render in default state" + (h/render [browser-input/browser-input]) + (h/is-null (h/query-by-label-text :browser-input-clear-button))) + + (h/test "Renders when there is a value" + (h/render [browser-input/browser-input + {:default-value "mock text"}]) + (h/is-truthy (h/query-by-label-text :browser-input-clear-button))) + + (h/test "Is pressable" + (let [on-clear (h/mock-fn)] + (h/render [browser-input/browser-input + {:default-value "mock text" + :on-clear on-clear}]) + (h/is-truthy (h/query-by-label-text :browser-input-clear-button)) + (h/fire-event :press (h/query-by-label-text :browser-input-clear-button)) + (h/was-called on-clear))))) diff --git a/src/quo2/components/browser/browser_input/style.cljs b/src/quo2/components/browser/browser_input/style.cljs new file mode 100644 index 0000000000..f0c4dde4ae --- /dev/null +++ b/src/quo2/components/browser/browser_input/style.cljs @@ -0,0 +1,56 @@ +(ns quo2.components.browser.browser-input.style + (:require [quo2.components.markdown.text :as text] + [quo2.foundations.colors :as colors])) + +(def clear-icon-container + {:align-items :center + :height 20 + :justify-content :center + :margin-left 8 + :top 4 + :width 20}) + +(def favicon-icon-container + {:margin-right 2}) + +(defn input + [disabled?] + (assoc (text/text-style {:size :paragraph-1 + :weight :regular}) + :flex 1 + :min-height 36 + :min-width 120 + :opacity (if disabled? 0.3 1))) + +(def lock-icon-container + {:margin-left 2}) + +(defn active-container + [display?] + {:align-items :center + :flex-direction :row + :justify-content :center + :opacity (if display? 1 0)}) + +(def default-container + {:align-items :center + :bottom 0 + :flex-direction :row + :left 0 + :padding-horizontal 20 + :position :absolute + :right 0 + :top 0 + :z-index 10}) + +(defn text + [] + (assoc (text/text-style {:size :paragraph-1 + :weight :medium}) + :color + (colors/theme-colors colors/neutral-100 colors/white))) + +(def root-container + {:height 60 + :padding-horizontal 20 + :padding-vertical 8}) diff --git a/src/quo2/components/browser/browser_input/view.cljs b/src/quo2/components/browser/browser_input/view.cljs new file mode 100644 index 0000000000..1df8961a10 --- /dev/null +++ b/src/quo2/components/browser/browser_input/view.cljs @@ -0,0 +1,144 @@ +(ns quo2.components.browser.browser-input.view + (:require [quo2.components.icon :as icon] + [quo2.components.browser.browser-input.style :as style] + [react-native.core :as rn] + [reagent.core :as reagent] + [quo2.foundations.colors :as colors] + [quo2.theme :as quo2.theme] + [clojure.string :as string] + [react-native.platform :as platform])) + +(defn remove-http-https-www + [value] + (string/replace value #"(https?://(www\.)?|http://)" "")) + +(defn clear-icon-color + [blur? theme] + (if blur? + (colors/theme-colors colors/neutral-80-opa-30 colors/white-opa-10 theme) + (colors/theme-colors colors/neutral-40 colors/neutral-60 theme))) + +(defn lock-icon-color + [blur? theme] + (if blur? + (colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-40 theme) + (colors/theme-colors colors/neutral-50 colors/neutral-40 theme))) + +(defn- clear-button + [{:keys [on-press blur? theme]}] + [rn/touchable-opacity + {:accessibility-label :browser-input-clear-button + :on-press on-press + :style style/clear-icon-container} + [icon/icon :i/clear + {:color (clear-icon-color blur? theme)}]]) + +(defn lock-icon + [{:keys [blur? theme]}] + [rn/view + [icon/icon :i/locked + {:accessibility-label :browser-input-locked-icon + :color (lock-icon-color blur? theme) + :container-style style/lock-icon-container + :size 16}]]) + +(defn cursor-color + [customization-color theme] + (colors/theme-colors (colors/custom-color customization-color 50) + (colors/custom-color customization-color 60) + theme)) + +(defn placeholder-color + [state blur? theme] + (cond + (and blur? (= state :active)) + (colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-20 theme) + + blur? + (colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-30 theme) + + (= state :active) + (colors/theme-colors colors/neutral-30 colors/neutral-60 theme) + + :else + (colors/theme-colors colors/neutral-40 colors/neutral-50 theme))) + +(def ^:private props-to-remove + [:cursor-color :placeholder-text-color :editable :on-change-text :on-focus + :on-blur :on-clear :value :disabled? :blur? :customization-color :theme]) + +(defn browser-input-internal + [{:keys [default-value] + :or {default-value ""}}] + (let [state (reagent/atom :default) + value (reagent/atom default-value) + set-active #(reset! state :active) + set-default #(reset! state :default) + set-value #(reset! value %) + ref (atom nil) + clear-input (fn [] + (.clear ^js @ref) + (reset! value "")) + focus-input (fn [] + (set-active) + (.focus ^js @ref))] + (fn [{:keys [disabled? blur? on-change-text customization-color + on-clear on-focus on-blur theme get-ref locked? + favicon favicon-color favicon-size] + :as props}] + (let [clean-props (apply dissoc props props-to-remove)] + [rn/view {:style style/root-container} + (when (and (seq @value) (= @state :default)) + [rn/touchable-opacity + {:style style/default-container + :on-press focus-input} + (when favicon + [icon/icon favicon + {:accessibility-label :browser-input-favicon + :color favicon-color + :container-style style/favicon-icon-container + :size favicon-size}]) + [rn/text + {:accessibility-label :browser-input-label + :style (style/text)} (remove-http-https-www @value)] + (when locked? + [lock-icon + {:blur? blur? + :theme theme}])]) + [rn/view {:style (style/active-container (or (empty? @value) (= @state :active)))} + [rn/text-input + (merge + clean-props + {:accessibility-label :browser-input + :auto-capitalize :none + :auto-correct false + :cursor-color (cursor-color customization-color theme) + :editable (not disabled?) + :keyboard-appearance (colors/theme-colors :light :dark theme) + :keyboard-type :web-search + :on-blur (fn [] + (set-default) + (when on-blur (on-blur))) + :on-change-text (fn [new-text] + (set-value new-text) + (when on-change-text (on-change-text new-text))) + :on-focus (fn [] + (set-active) + (when on-focus (on-focus))) + :placeholder-text-color (placeholder-color @state blur? theme) + :ref (fn [r] + (reset! ref r) + (when get-ref (get-ref r))) + :selection-color (when platform/ios? + (cursor-color customization-color theme)) + :select-text-on-focus true + :style (style/input disabled?)})] + (when (seq @value) + [clear-button + {:blur? blur? + :on-press (fn [] + (clear-input) + (when on-clear (on-clear))) + :theme theme}])]])))) + +(def browser-input (quo2.theme/with-theme browser-input-internal)) diff --git a/src/quo2/core.cljs b/src/quo2/core.cljs index eaad40777a..a140906ee7 100644 --- a/src/quo2/core.cljs +++ b/src/quo2/core.cljs @@ -12,6 +12,7 @@ quo2.components.buttons.dynamic-button quo2.components.buttons.predictive-keyboard.view quo2.components.buttons.slide-button.view + quo2.components.browser.browser-input.view quo2.components.code.snippet quo2.components.colors.color-picker.view quo2.components.common.separator.view @@ -138,6 +139,9 @@ (def predictive-keyboard quo2.components.buttons.predictive-keyboard.view/view) (def slide-button quo2.components.buttons.slide-button.view/view) +;;;; BROWSER +(def browser-input quo2.components.browser.browser-input.view/browser-input) + ;;;; CODE (def snippet quo2.components.code.snippet/snippet) diff --git a/src/quo2/core_spec.cljs b/src/quo2/core_spec.cljs index ee241d8f97..7fbb88a386 100644 --- a/src/quo2/core_spec.cljs +++ b/src/quo2/core_spec.cljs @@ -5,6 +5,7 @@ [quo2.components.buttons.button.component-spec] [quo2.components.buttons.predictive-keyboard.component-spec] [quo2.components.buttons.slide-button.component-spec] + [quo2.components.browser.browser-input.component-spec] [quo2.components.colors.color-picker.component-spec] [quo2.components.counter.--tests--.counter-component-spec] [quo2.components.counter.step.component-spec] diff --git a/src/status_im2/contexts/quo_preview/browser/browser_input.cljs b/src/status_im2/contexts/quo_preview/browser/browser_input.cljs new file mode 100644 index 0000000000..0e52ab7a4e --- /dev/null +++ b/src/status_im2/contexts/quo_preview/browser/browser_input.cljs @@ -0,0 +1,64 @@ +(ns status-im2.contexts.quo-preview.browser.browser-input + (:require [quo2.core :as quo] + [quo2.foundations.colors :as colors] + [react-native.core :as rn] + [react-native.safe-area :as safe-area] + [reagent.core :as reagent] + [utils.re-frame :as rf] + [status-im2.contexts.quo-preview.preview :as preview])) + +(def descriptor + [{:label "Show Favicon" + :key :favicon? + :type :boolean} + {:label "Locked" + :key :locked? + :type :boolean} + {:label "Disabled" + :key :disabled? + :type :boolean}]) + +(defn cool-preview + [] + (reagent/with-let [keyboard-shown? (reagent/atom false) + keyboard-show-listener (.addListener rn/keyboard + "keyboardWillShow" + #(reset! keyboard-shown? true)) + keyboard-hide-listener (.addListener rn/keyboard + "keyboardWillHide" + #(reset! keyboard-shown? false)) + {:keys [bottom top]} (safe-area/get-insets) + state (reagent/atom {:blur? false + :disabled? false + :favicon? false + :placeholder "Search or enter dapp domain" + :locked? false})] + [rn/keyboard-avoiding-view + {:style {:flex 1 :padding-top top}} + [quo/page-nav + {:align-mid? true + :mid-section {:type :text-only :main-text ""} + :left-section {:icon :i/arrow-left + :on-press + #(rf/dispatch [:navigate-back])}}] + [rn/flat-list + {:header [preview/customizer state descriptor] + :key-fn str + :keyboard-should-persist-taps :always + :style {:flex 1}}] + [rn/view + [quo/browser-input + (assoc @state + :customization-color :blue + :favicon (when (:favicon? @state) :i/verified))] + [rn/view {:style {:height (if-not @keyboard-shown? bottom 0)}}]]] + (finally + (.remove keyboard-show-listener) + (.remove keyboard-hide-listener)))) + +(defn preview-browser-input + [] + [rn/view + {:style {:background-color (colors/theme-colors colors/white colors/neutral-95) + :flex 1}} + [cool-preview]]) diff --git a/src/status_im2/contexts/quo_preview/main.cljs b/src/status_im2/contexts/quo_preview/main.cljs index 06a2b6324f..1a7bf06cd6 100644 --- a/src/status_im2/contexts/quo_preview/main.cljs +++ b/src/status_im2/contexts/quo_preview/main.cljs @@ -19,6 +19,7 @@ [status-im2.contexts.quo-preview.buttons.slide-button :as slide-button] [status-im2.contexts.quo-preview.buttons.dynamic-button :as dynamic-button] [status-im2.contexts.quo-preview.buttons.predictive-keyboard :as predictive-keyboard] + [status-im2.contexts.quo-preview.browser.browser-input :as browser-input] [status-im2.contexts.quo-preview.code.snippet :as code-snippet] [status-im2.contexts.quo-preview.colors.color-picker :as color-picker] [status-im2.contexts.quo-preview.community.community-card-view :as community-card] @@ -140,6 +141,9 @@ {:name :predictive-keyboard :options {:topBar {:visible true}} :component predictive-keyboard/preview-predictive-keyboard}] + :browser [{:name :browser-input + :options {:topBar {:visible false}} + :component browser-input/preview-browser-input}] :code [{:name :snippet :options {:topBar {:visible true}} :component code-snippet/preview-code-snippet}]