Add new text input component
This commit is contained in:
parent
3483bb1107
commit
ff3ba6c0f0
|
@ -0,0 +1,170 @@
|
|||
(ns quo2.components.input.style
|
||||
(:require [quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]))
|
||||
|
||||
(def variants-colors
|
||||
"Colors that keep the same across input's status change"
|
||||
{:light {:label colors/neutral-50
|
||||
:icon colors/neutral-50
|
||||
:cursor (colors/custom-color :blue 50)
|
||||
:button-border colors/neutral-30
|
||||
:clear-icon colors/neutral-40
|
||||
:password-icon colors/neutral-50}
|
||||
:light-blur {:label colors/neutral-80-opa-40
|
||||
:icon colors/neutral-80-opa-70
|
||||
:cursor (colors/custom-color :blue 50)
|
||||
:button-border colors/neutral-80-opa-30
|
||||
:password-icon colors/neutral-100
|
||||
:clear-icon colors/neutral-80-opa-30}
|
||||
:dark {:label colors/neutral-40
|
||||
:icon colors/neutral-40
|
||||
:cursor (colors/custom-color :blue 60)
|
||||
:button-border colors/neutral-70
|
||||
:password-icon colors/white
|
||||
:clear-icon colors/neutral-60}
|
||||
:dark-blur {:label colors/white-opa-40
|
||||
:icon colors/white-opa-70
|
||||
:cursor colors/white
|
||||
:button-border colors/white-opa-10
|
||||
:password-icon colors/white
|
||||
:clear-icon colors/white-opa-10}})
|
||||
|
||||
(def status-colors
|
||||
{:light {:default {:border-color colors/neutral-20
|
||||
:placeholder colors/neutral-40
|
||||
:text colors/neutral-100}
|
||||
:focus {:border-color colors/neutral-40
|
||||
:placeholder colors/neutral-30
|
||||
:text colors/neutral-100}
|
||||
:error {:border-color colors/danger-opa-40
|
||||
:placeholder colors/neutral-40
|
||||
:text colors/neutral-100}
|
||||
:disabled {:border-color colors/neutral-20
|
||||
:placeholder colors/neutral-40
|
||||
:text colors/neutral-40}}
|
||||
:light-blur {:default {:border-color colors/neutral-80-opa-10
|
||||
:placeholder colors/neutral-80-opa-40
|
||||
:text colors/neutral-100}
|
||||
:focus {:border-color colors/neutral-80-opa-20
|
||||
:placeholder colors/neutral-80-opa-20
|
||||
:text colors/neutral-100}
|
||||
:error {:border-color colors/danger-opa-40
|
||||
:placeholder colors/neutral-80-opa-40
|
||||
:text colors/neutral-100}
|
||||
:disabled {:border-color colors/neutral-80-opa-10
|
||||
:placeholder colors/neutral-80-opa-30
|
||||
:text colors/neutral-80-opa-30}}
|
||||
:dark {:default {:border-color colors/neutral-80
|
||||
:placeholder colors/neutral-50
|
||||
:text colors/white}
|
||||
:focus {:border-color colors/neutral-60
|
||||
:placeholder colors/neutral-60
|
||||
:text colors/white}
|
||||
:error {:border-color colors/danger-opa-40
|
||||
:placeholder colors/white-opa-40
|
||||
:text colors/white}
|
||||
:disabled {:border-color colors/neutral-80
|
||||
:placeholder colors/neutral-40
|
||||
:text colors/neutral-40}}
|
||||
:dark-blur {:default {:border-color colors/white-opa-10
|
||||
:placeholder colors/white-opa-40
|
||||
:text colors/white}
|
||||
:focus {:border-color colors/white-opa-40
|
||||
:placeholder colors/white-opa-20
|
||||
:text colors/white}
|
||||
:error {:border-color colors/danger-opa-40
|
||||
:placeholder colors/white-opa-40
|
||||
:text colors/white}
|
||||
:disabled {:border-color colors/white-opa-10
|
||||
:placeholder colors/white-opa-20
|
||||
:text colors/white-opa-20}}})
|
||||
|
||||
(defn input-container
|
||||
[colors-by-status small? disabled?]
|
||||
{:flex-direction :row
|
||||
:padding-horizontal 8
|
||||
:border-width 1
|
||||
:border-color (:border-color colors-by-status)
|
||||
:border-radius (if small? 10 14)
|
||||
:opacity (if disabled? 0.3 1)})
|
||||
|
||||
(defn left-icon-container
|
||||
[small?]
|
||||
{:margin-left (if small? 0 4)
|
||||
:margin-right (if small? 4 8)
|
||||
:margin-top (if small? 5 9)
|
||||
:height 20
|
||||
:width 20})
|
||||
|
||||
(defn icon
|
||||
[colors-by-variant]
|
||||
{:color (:icon colors-by-variant)
|
||||
:size 20})
|
||||
|
||||
(defn input
|
||||
[colors-by-status small? multiple-lines?]
|
||||
(merge (text/text-style {:size :paragraph-1 :weight :regular})
|
||||
{:flex 1
|
||||
:text-align-vertical :top
|
||||
:padding-horizontal 0
|
||||
:padding-vertical (if small? 4 8)
|
||||
:color (:text colors-by-status)}
|
||||
(when-not multiple-lines?
|
||||
{:height (if small? 30 38)})))
|
||||
|
||||
(defn right-icon-touchable-area
|
||||
[small?]
|
||||
{:margin-left (if small? 4 8)
|
||||
:padding-right (if small? 0 4)
|
||||
:padding-top (if small? 5 9)})
|
||||
|
||||
(defn password-icon
|
||||
[variant-colors]
|
||||
{:size 20
|
||||
:color (:password-icon variant-colors)})
|
||||
|
||||
(defn clear-icon
|
||||
[variant-colors]
|
||||
{:size 20
|
||||
:color (:clear-icon variant-colors)})
|
||||
|
||||
(def texts-container
|
||||
{:flex 1
|
||||
:flex-direction :row
|
||||
:height 18
|
||||
:margin-bottom 8})
|
||||
|
||||
(def label-container {:flex 1})
|
||||
|
||||
(defn label-color
|
||||
[variant-colors]
|
||||
{:color (:label variant-colors)})
|
||||
|
||||
(def counter-container
|
||||
{:flex 1
|
||||
:align-items :flex-end})
|
||||
|
||||
(defn counter-color
|
||||
[current-chars char-limit variant-colors]
|
||||
{:color (if (> current-chars char-limit)
|
||||
colors/danger-60
|
||||
(:label variant-colors))})
|
||||
|
||||
(defn button
|
||||
[colors-by-variant small?]
|
||||
{:justify-content :center
|
||||
:align-items :center
|
||||
:height 24
|
||||
:border-width 1
|
||||
:border-color (:button-border colors-by-variant)
|
||||
:border-radius 8
|
||||
:margin-vertical (if small? 3 7)
|
||||
:margin-left 4
|
||||
:margin-right (if small? -4 0)
|
||||
:padding-horizontal 7
|
||||
:padding-top 1.5
|
||||
:padding-bottom 2.5})
|
||||
|
||||
(defn button-text
|
||||
[colors-by-status]
|
||||
{:color (:text colors-by-status)})
|
|
@ -0,0 +1,173 @@
|
|||
(ns quo2.components.input.view
|
||||
(:require [oops.core :as oops]
|
||||
[quo2.components.icon :as icon]
|
||||
[quo2.components.input.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn- label-&-counter
|
||||
[{:keys [label current-chars char-limit variant-colors]}]
|
||||
(let [count-text (when char-limit (str current-chars "/" char-limit))]
|
||||
[rn/view {:style style/texts-container}
|
||||
[rn/view {:style style/label-container}
|
||||
[text/text
|
||||
{:style (style/label-color variant-colors)
|
||||
:weight :medium
|
||||
:size :paragraph-2}
|
||||
label]]
|
||||
[rn/view {:style style/counter-container}
|
||||
[text/text
|
||||
{:style (style/counter-color current-chars char-limit variant-colors)
|
||||
:weight :regular
|
||||
:size :paragraph-2}
|
||||
count-text]]]))
|
||||
|
||||
(defn- left-accessory
|
||||
[{:keys [variant-colors small icon-name]}]
|
||||
[rn/view {:style (style/left-icon-container small)}
|
||||
[icon/icon icon-name (style/icon variant-colors)]])
|
||||
|
||||
(defn- right-accessory
|
||||
[{:keys [variant-colors small disabled on-press icon-style-fn icon-name]}]
|
||||
[rn/touchable-opacity
|
||||
{:style (style/right-icon-touchable-area small)
|
||||
:disabled disabled
|
||||
:on-press on-press}
|
||||
[icon/icon icon-name (icon-style-fn variant-colors)]])
|
||||
|
||||
(defn- right-button
|
||||
[{:keys [variant-colors colors-by-status small disabled on-press text]}]
|
||||
[rn/touchable-opacity
|
||||
{:style (style/button variant-colors small)
|
||||
:disabled disabled
|
||||
:on-press on-press}
|
||||
[rn/text {:style (style/button-text colors-by-status)}
|
||||
text]])
|
||||
|
||||
(def ^:private custom-props
|
||||
"Custom properties that must be removed from properties map passed to InputText."
|
||||
[:type :variant :error :right-icon :left-icon :disabled :small :button :label
|
||||
:char-limit :on-char-limit-reach :icon-name])
|
||||
|
||||
(defn- base-input
|
||||
[{:keys [on-change-text on-char-limit-reach]}]
|
||||
(let [status (reagent/atom :default)
|
||||
on-focus #(reset! status :focus)
|
||||
on-blur #(reset! status :default)
|
||||
multiple-lines? (reagent/atom false)
|
||||
set-multiple-lines! #(let [height (oops/oget % "nativeEvent.contentSize.height")]
|
||||
(if (> height 57)
|
||||
(reset! multiple-lines? true)
|
||||
(reset! multiple-lines? false)))
|
||||
char-count (reagent/atom 0)
|
||||
update-char-limit! (fn [new-text char-limit]
|
||||
(when on-change-text (on-change-text new-text))
|
||||
(let [amount-chars (count new-text)]
|
||||
(reset! char-count amount-chars)
|
||||
(when (>= amount-chars char-limit)
|
||||
(on-char-limit-reach amount-chars))))]
|
||||
(fn [{:keys [variant error right-icon left-icon disabled small button label char-limit
|
||||
multiline clearable]
|
||||
:or {variant :light}
|
||||
:as props}]
|
||||
(let [status-path (cond
|
||||
disabled :disabled
|
||||
error :error
|
||||
:else @status)
|
||||
colors-by-status (get-in style/status-colors [variant status-path])
|
||||
variant-colors (style/variants-colors variant)
|
||||
clean-props (apply dissoc props custom-props)]
|
||||
[rn/view
|
||||
(when (or label char-limit)
|
||||
[label-&-counter
|
||||
{:variant-colors variant-colors
|
||||
:label label
|
||||
:current-chars @char-count
|
||||
:char-limit char-limit}])
|
||||
[rn/view {:style (style/input-container colors-by-status small disabled)}
|
||||
(when-let [{:keys [icon-name]} left-icon]
|
||||
[left-accessory
|
||||
{:variant-colors variant-colors
|
||||
:small small
|
||||
:icon-name icon-name}])
|
||||
[rn/text-input
|
||||
(cond-> {:style (style/input colors-by-status small @multiple-lines?)
|
||||
:placeholder-text-color (:placeholder colors-by-status)
|
||||
:cursor-color (:cursor variant-colors)
|
||||
:editable (not disabled)
|
||||
:on-focus on-focus
|
||||
:on-blur on-blur}
|
||||
:always (merge clean-props)
|
||||
multiline (assoc :on-content-size-change set-multiple-lines!)
|
||||
char-limit (assoc :on-change-text #(update-char-limit! char-limit %)))]
|
||||
(when-let [{:keys [on-press icon-name style-fn]} right-icon]
|
||||
[right-accessory
|
||||
{:variant-colors variant-colors
|
||||
:small small
|
||||
:disabled disabled
|
||||
:icon-style-fn style-fn
|
||||
:icon-name icon-name
|
||||
:on-press (fn []
|
||||
(when clearable (reset! char-count 0))
|
||||
(on-press))}])
|
||||
(when-let [{:keys [on-press text]} button]
|
||||
[right-button
|
||||
{:colors-by-status colors-by-status
|
||||
:variant-colors variant-colors
|
||||
:small small
|
||||
:disabled disabled
|
||||
:on-press on-press
|
||||
:text text}])]]))))
|
||||
|
||||
(defn- password-input
|
||||
[_]
|
||||
(let [password-shown? (reagent/atom false)]
|
||||
(fn [props]
|
||||
[base-input
|
||||
(assoc props
|
||||
:auto-capitalize :none
|
||||
:auto-complete :new-password
|
||||
:secure-text-entry (not @password-shown?)
|
||||
:right-icon {:style-fn style/password-icon
|
||||
:icon-name (if @password-shown? :i/hide :i/reveal)
|
||||
:on-press #(swap! password-shown? not)})])))
|
||||
|
||||
(defn input
|
||||
"This input supports the following properties:
|
||||
- :type - Can be `:text`(default) or `:password`.
|
||||
- :variant - :light(default), :light-blur, :dark or :dark-blur.
|
||||
- :small - Boolean to specify if this input is rendered in its small version.
|
||||
- :multiline - Boolean to specify if this input support multiple lines.
|
||||
- :icon-name - The name of an icon to display at the left of the input.
|
||||
- :error - Boolean to specify it this input marks an error.
|
||||
- :disabled - Boolean to specify if this input is disabled or not.
|
||||
- :clearable - Booolean to specify if this input has a clear button at the end.
|
||||
- :on-clear - Function executed when the clear button is pressed.
|
||||
- :button - Map containing `:on-press` & `:text` keys, if provided renders a button
|
||||
- :label - A label for this input.
|
||||
- :char-limit - A number to set a maximum char limit for this input.
|
||||
- :on-char-limit-reach - Function executed each time char limit is reached or exceeded.
|
||||
and supports the usual React Native's TextInput properties to control its behaviour:
|
||||
- :value
|
||||
- :default-value
|
||||
- :on-change
|
||||
- :on-change-text
|
||||
...
|
||||
"
|
||||
[{:keys [type clearable on-clear on-change-text icon-name]
|
||||
:or {type :text}
|
||||
:as props}]
|
||||
(let [base-props (cond-> props
|
||||
icon-name (assoc-in [:left-icon :icon-name] icon-name)
|
||||
clearable (assoc :right-icon
|
||||
{:style-fn style/clear-icon
|
||||
:icon-name :i/clear
|
||||
:on-press #(when on-clear (on-clear))})
|
||||
on-change-text (assoc :on-change-text
|
||||
(fn [new-text]
|
||||
(on-change-text new-text)
|
||||
(reagent/flush))))]
|
||||
(if (= type :password)
|
||||
[password-input base-props]
|
||||
[base-input base-props])))
|
|
@ -30,6 +30,7 @@
|
|||
quo2.components.icon
|
||||
quo2.components.info.info-message
|
||||
quo2.components.info.information-box
|
||||
quo2.components.input.view
|
||||
quo2.components.list-items.channel
|
||||
quo2.components.list-items.menu-item
|
||||
quo2.components.list-items.preview-list
|
||||
|
@ -139,6 +140,9 @@
|
|||
(def drawer-buttons quo2.components.drawers.drawer-buttons.view/view)
|
||||
(def permission-context quo2.components.drawers.permission-context.view/view)
|
||||
|
||||
;;;; INPUTS
|
||||
(def input quo2.components.input.view/input)
|
||||
|
||||
;;;; LIST ITEMS
|
||||
(def channel-list-item quo2.components.list-items.channel/list-item)
|
||||
(def menu-item quo2.components.list-items.menu-item/menu-item)
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
|
||||
;;Blur
|
||||
(def neutral-5-opa-70 (alpha neutral-5 0.7))
|
||||
(def neutral-80-blur-opa-80 "rgba(25,36,56,0.8)")
|
||||
(def neutral-90-opa-70 (alpha neutral-90 0.7))
|
||||
|
||||
;;80 with transparency
|
||||
|
@ -152,6 +153,10 @@
|
|||
(def success-60-opa-40 (alpha success-60 0.4))
|
||||
|
||||
;;;;Danger
|
||||
(def danger "#E95460")
|
||||
|
||||
;; Danger with transparency
|
||||
(def danger-opa-40 (alpha danger 0.4))
|
||||
|
||||
;;Solid
|
||||
(def danger-50 "#E65F5C")
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
(ns status-im2.contexts.quo-preview.inputs.input
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo2.components.input.view :as quo2]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "Type:"
|
||||
:key :type
|
||||
:type :select
|
||||
:options [{:key :text
|
||||
:value "Text"}
|
||||
{:key :password
|
||||
:value "Password"}]}
|
||||
{:label "Variant:"
|
||||
:key :variant
|
||||
:type :select
|
||||
:options [{:key :light
|
||||
:value "Light"}
|
||||
{:key :dark
|
||||
:value "Dark"}
|
||||
{:key :light-blur
|
||||
:value "Light blur"}
|
||||
{:key :dark-blur
|
||||
:value "Dark blur"}]}
|
||||
{:label "Error:"
|
||||
:key :error
|
||||
:type :boolean}
|
||||
{:label "Icon:"
|
||||
:key :icon-name
|
||||
:type :boolean}
|
||||
{:label "Disabled:"
|
||||
:key :disabled
|
||||
:type :boolean}
|
||||
{:label "Clearable:"
|
||||
:key :clearable
|
||||
:type :boolean}
|
||||
{:label "Small:"
|
||||
:key :small
|
||||
:type :boolean}
|
||||
{:label "Multiline:"
|
||||
:key :multiline
|
||||
:type :boolean}
|
||||
{:label "Button:"
|
||||
:key :button
|
||||
:type :boolean}
|
||||
{:label "Label:"
|
||||
:key :label
|
||||
:type :text}
|
||||
{:label "Char limit:"
|
||||
:key :char-limit
|
||||
:type :select
|
||||
:options [{:key 10
|
||||
:value "10"}
|
||||
{:key 50
|
||||
:value "50"}
|
||||
{:key 100
|
||||
:value "100"}]}
|
||||
{:label "Value:"
|
||||
:key :value
|
||||
:type :text}])
|
||||
|
||||
(defn cool-preview
|
||||
[]
|
||||
(let [state (reagent/atom {:type :text
|
||||
:variant :light-blur
|
||||
:placeholder "Type something"
|
||||
:error false
|
||||
:icon-name false
|
||||
:value ""
|
||||
:clearable false
|
||||
:on-char-limit-reach #(js/alert
|
||||
(str "Char limit reached: " %))})]
|
||||
(fn []
|
||||
(let [background-color (case (:variant @state)
|
||||
:dark-blur "rgb(39, 61, 81)"
|
||||
:dark colors/neutral-95
|
||||
:light-blur "rgb(233,247,247)"
|
||||
:white)
|
||||
blank-label? (string/blank? (:label @state))
|
||||
icon? (boolean (:icon-name @state))
|
||||
button-props {:on-press #(js/alert "Button pressed!")
|
||||
:text "My button"}]
|
||||
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
|
||||
[rn/view {:style {:padding-bottom 150}}
|
||||
[rn/view {:style {:flex 1}}
|
||||
[preview/customizer state descriptor]]
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:align-items :center
|
||||
:padding-vertical 60
|
||||
:background-color background-color}}
|
||||
[rn/view {:style {:width 300}}
|
||||
[quo2/input
|
||||
(cond-> @state
|
||||
:always (assoc
|
||||
:on-clear #(swap! state assoc :value "")
|
||||
:on-change-text #(swap! state assoc :value %))
|
||||
(:button @state) (assoc :button button-props)
|
||||
blank-label? (dissoc :label)
|
||||
icon? (assoc :icon-name :i/placeholder))]]]]]))))
|
||||
|
||||
(defn preview-input
|
||||
[]
|
||||
[rn/view
|
||||
{:style {:background-color (colors/theme-colors colors/white colors/neutral-90)
|
||||
:flex 1}}
|
||||
[rn/flat-list
|
||||
{:style {:flex 1}
|
||||
:keyboardShouldPersistTaps :always
|
||||
:header [cool-preview]
|
||||
:key-fn str}]])
|
|
@ -71,6 +71,7 @@
|
|||
[status-im2.contexts.quo-preview.tags.status-tags :as status-tags]
|
||||
[status-im2.contexts.quo-preview.tags.tags :as tags]
|
||||
[status-im2.contexts.quo-preview.tags.token-tag :as token-tag]
|
||||
[status-im2.contexts.quo-preview.inputs.input :as input]
|
||||
[status-im2.contexts.quo-preview.wallet.lowest-price :as lowest-price]
|
||||
[status-im2.contexts.quo-preview.wallet.network-amount :as network-amount]
|
||||
[status-im2.contexts.quo-preview.wallet.network-breakdown :as network-breakdown]
|
||||
|
@ -165,6 +166,9 @@
|
|||
{:name :information-box
|
||||
:insets {:top false}
|
||||
:component information-box/preview-information-box}]
|
||||
:inputs [{:name :input
|
||||
:insets {:top false}
|
||||
:component input/preview-input}]
|
||||
:list-items [{:name :channel
|
||||
:insets {:top false}
|
||||
:component channel/preview-channel}
|
||||
|
|
Loading…
Reference in New Issue