feat: browser input (#16487)
This commit is contained in:
parent
00f39225ea
commit
c5a486622d
|
@ -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)))))
|
|
@ -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})
|
|
@ -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))
|
|
@ -12,6 +12,7 @@
|
||||||
quo2.components.buttons.dynamic-button
|
quo2.components.buttons.dynamic-button
|
||||||
quo2.components.buttons.predictive-keyboard.view
|
quo2.components.buttons.predictive-keyboard.view
|
||||||
quo2.components.buttons.slide-button.view
|
quo2.components.buttons.slide-button.view
|
||||||
|
quo2.components.browser.browser-input.view
|
||||||
quo2.components.code.snippet
|
quo2.components.code.snippet
|
||||||
quo2.components.colors.color-picker.view
|
quo2.components.colors.color-picker.view
|
||||||
quo2.components.common.separator.view
|
quo2.components.common.separator.view
|
||||||
|
@ -138,6 +139,9 @@
|
||||||
(def predictive-keyboard quo2.components.buttons.predictive-keyboard.view/view)
|
(def predictive-keyboard quo2.components.buttons.predictive-keyboard.view/view)
|
||||||
(def slide-button quo2.components.buttons.slide-button.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
|
;;;; CODE
|
||||||
(def snippet quo2.components.code.snippet/snippet)
|
(def snippet quo2.components.code.snippet/snippet)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
[quo2.components.buttons.button.component-spec]
|
[quo2.components.buttons.button.component-spec]
|
||||||
[quo2.components.buttons.predictive-keyboard.component-spec]
|
[quo2.components.buttons.predictive-keyboard.component-spec]
|
||||||
[quo2.components.buttons.slide-button.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.colors.color-picker.component-spec]
|
||||||
[quo2.components.counter.--tests--.counter-component-spec]
|
[quo2.components.counter.--tests--.counter-component-spec]
|
||||||
[quo2.components.counter.step.component-spec]
|
[quo2.components.counter.step.component-spec]
|
||||||
|
|
|
@ -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]])
|
|
@ -19,6 +19,7 @@
|
||||||
[status-im2.contexts.quo-preview.buttons.slide-button :as slide-button]
|
[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.dynamic-button :as dynamic-button]
|
||||||
[status-im2.contexts.quo-preview.buttons.predictive-keyboard :as predictive-keyboard]
|
[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.code.snippet :as code-snippet]
|
||||||
[status-im2.contexts.quo-preview.colors.color-picker :as color-picker]
|
[status-im2.contexts.quo-preview.colors.color-picker :as color-picker]
|
||||||
[status-im2.contexts.quo-preview.community.community-card-view :as community-card]
|
[status-im2.contexts.quo-preview.community.community-card-view :as community-card]
|
||||||
|
@ -140,6 +141,9 @@
|
||||||
{:name :predictive-keyboard
|
{:name :predictive-keyboard
|
||||||
:options {:topBar {:visible true}}
|
:options {:topBar {:visible true}}
|
||||||
:component predictive-keyboard/preview-predictive-keyboard}]
|
:component predictive-keyboard/preview-predictive-keyboard}]
|
||||||
|
:browser [{:name :browser-input
|
||||||
|
:options {:topBar {:visible false}}
|
||||||
|
:component browser-input/preview-browser-input}]
|
||||||
:code [{:name :snippet
|
:code [{:name :snippet
|
||||||
:options {:topBar {:visible true}}
|
:options {:topBar {:visible true}}
|
||||||
:component code-snippet/preview-code-snippet}]
|
:component code-snippet/preview-code-snippet}]
|
||||||
|
|
Loading…
Reference in New Issue