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.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)
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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.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}]
|
||||
|
|
Loading…
Reference in New Issue