Add text input and tooltip components

Add tooltip component
Add text input component
rename

Signed-off-by: Gheorghe Pinzaru <feross95@gmail.com>
This commit is contained in:
Gheorghe Pinzaru 2020-04-27 18:16:48 +03:00
parent a32c94b090
commit 01452794a1
No known key found for this signature in database
GPG Key ID: C9A094959935A952
40 changed files with 866 additions and 50 deletions

View File

@ -1,8 +1,9 @@
{:lint-as {status-im.utils.views/defview clojure.core/defn
status-im.utils.views/letsubs clojure.core/let
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
status-im.utils.styles/def clojure.core/def
status-im.utils.styles/defn clojure.core/defn}
{:lint-as {status-im.utils.views/defview clojure.core/defn
status-im.utils.views/letsubs clojure.core/let
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
quo.previews.preview/list-comp clojure.core/for
status-im.utils.styles/def clojure.core/def
status-im.utils.styles/defn clojure.core/defn}
:linters {:invalid-arity {:skip-args [status-im.utils.fx/defn]}
;;TODO remove number when this is fixed
;;https://github.com/borkdude/clj-kondo/issues/867

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

View File

@ -14,6 +14,7 @@
25DC9C9DC25846BD8D084888 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9A886A2CB448B1ABA0EB62 /* libc++.tbd */; };
3870E1E692E24133A80B07DE /* Inter-SemiBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 693A62DB37BC4CD5A30E5C96 /* Inter-SemiBold.otf */; };
393D26E3080B443A998F4A2F /* Inter-Italic.otf in Resources */ = {isa = PBXBuildFile; fileRef = B07176ACDAA1422E8F0A3D6B /* Inter-Italic.otf */; };
3ABC7AF8245FF85900612C45 /* InterStatus-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 9C76AF5A418D4D65A4CAD1D9 /* InterStatus-Regular.otf */; };
57C854A7993C47A3B1AECD32 /* Inter-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = C6B1215047604CD59A4C74D6 /* Inter-MediumItalic.otf */; };
70ADBB5ECF934DCF8A0E4919 /* Inter-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 1426DF592BA248FC81D955CB /* Inter-Regular.otf */; };
74B758FC20D7C00B003343C3 /* launch-image-universal.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74B758FB20D7C00B003343C3 /* launch-image-universal.storyboard */; };
@ -90,7 +91,6 @@
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = StatusIm/main.m; sourceTree = "<group>"; };
1426DF592BA248FC81D955CB /* Inter-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Inter-Regular.otf"; path = "../resources/fonts/Inter-Regular.otf"; sourceTree = "<group>"; };
38A44830EC5708E89387F641 /* Pods-StatusIm.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StatusIm.release.xcconfig"; path = "Pods/Target Support Files/Pods-StatusIm/Pods-StatusIm.release.xcconfig"; sourceTree = "<group>"; };
3A0B103024581B74004B0F23 /* InterStatus-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "InterStatus-Regular.otf"; path = "../resources/fonts/InterStatus-Regular.otf"; sourceTree = "<group>"; };
439B6B4B407A4E2AACAFE5BE /* RCTStatus.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTStatus.xcodeproj; path = "../modules/react-native-status/ios/RCTStatus/RCTStatus.xcodeproj"; sourceTree = "<group>"; };
4C16DE0B1F89508700AA10DB /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
4E586E1B0E544F64AA9F5BD1 /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
@ -98,6 +98,7 @@
74B758FB20D7C00B003343C3 /* launch-image-universal.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "launch-image-universal.storyboard"; sourceTree = "<group>"; };
8B9A886A2CB448B1ABA0EB62 /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
922C4CA61F4D5F8B0033C753 /* StatusIm.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = StatusIm.entitlements; path = StatusIm/StatusIm.entitlements; sourceTree = "<group>"; };
9C76AF5A418D4D65A4CAD1D9 /* InterStatus-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "InterStatus-Regular.otf"; path = "../resources/fonts/InterStatus-Regular.otf"; sourceTree = "<group>"; };
9EC0135C1E06FB1900155B5C /* RCTWKWebView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWKWebView.xcodeproj; path = "../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView.xcodeproj"; sourceTree = "<group>"; };
9EF083381F3B538A00876A8F /* ReactNativeConfig.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeConfig.xcodeproj; path = "../node_modules/react-native-config/ios/ReactNativeConfig.xcodeproj"; sourceTree = "<group>"; };
A4F2BBE8D4DD4140A6CCAC39 /* Inter-SemiBoldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Inter-SemiBoldItalic.otf"; path = "../resources/fonts/Inter-SemiBoldItalic.otf"; sourceTree = "<group>"; };
@ -181,12 +182,12 @@
CD4A2C27D6D5473184DC1F7E /* Inter-Bold.otf */,
B321D25F4493470980039457 /* Inter-BoldItalic.otf */,
B07176ACDAA1422E8F0A3D6B /* Inter-Italic.otf */,
3A0B103024581B74004B0F23 /* InterStatus-Regular.otf */,
B2A38FC3D3954DE7B2B171F8 /* Inter-Medium.otf */,
C6B1215047604CD59A4C74D6 /* Inter-MediumItalic.otf */,
1426DF592BA248FC81D955CB /* Inter-Regular.otf */,
693A62DB37BC4CD5A30E5C96 /* Inter-SemiBold.otf */,
A4F2BBE8D4DD4140A6CCAC39 /* Inter-SemiBoldItalic.otf */,
9C76AF5A418D4D65A4CAD1D9 /* InterStatus-Regular.otf */,
);
name = Resources;
sourceTree = "<group>";
@ -429,6 +430,7 @@
B2F2D1BC1D9D531B00B7B453 /* Images.xcassets in Resources */,
D84616FB563A48EBB1678699 /* Inter-Bold.otf in Resources */,
D99C50E5E18942A39C8DDF61 /* Inter-BoldItalic.otf in Resources */,
3ABC7AF8245FF85900612C45 /* InterStatus-Regular.otf in Resources */,
393D26E3080B443A998F4A2F /* Inter-Italic.otf in Resources */,
D1786306E0184916B11F4C37 /* Inter-Medium.otf in Resources */,
57C854A7993C47A3B1AECD32 /* Inter-MediumItalic.otf in Resources */,

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "Hide.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Hide@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Hide@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "Show.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Show@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Show@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "Tip.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Tip@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Tip@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

View File

@ -103,6 +103,7 @@
<string>Inter-SemiBoldItalic.otf</string>
<string>Inter-Thin-BETA.otf</string>
<string>Inter-ThinItalic-BETA.otf</string>
<string>InterStatus-Regular.otf</string>
</array>
<key>UIBackgroundModes</key>
<array>

View File

@ -28,9 +28,13 @@
(def set (oget animated "set"))
(def start-clock (oget animated "startClock"))
(def stop-clock (oget animated "stopClock"))
(def clock-running clockRunning)
(def bezier (.-bezier ^js Easing))
(def linear (.-linear ^js Easing))
(def easings {:ease-in (bezier 0.42 0 1 1)
:ease-out (bezier 0 0 0.58 1)})
(defn set-value [anim val]
(ocall anim "setValue" val))
@ -111,3 +115,9 @@
(defn on-scroll [opts]
(ocall redash "onScrollEvent" (clj->js opts)))
(defn b-interpolate [anim-value a b]
(ocall redash "bInterpolate" anim-value a b))
(defn loop* [opts]
(ocall redash "loop" (clj->js opts)))

View File

@ -86,7 +86,7 @@
(when accessibility-label
{:accessibility-label accessibility-label}))
(cond
icon [icons/icon icon]
icon [icons/icon icon {:color (:icon-01 @colors/theme)}]
label [text/text {:color :link} label])]])
(defn header-actions [{:keys [accessories component]}]
@ -102,12 +102,12 @@
[rn/view {:style header-action-placeholder}])])
(defn header-title [{:keys [title subtitle component title-align]}]
[react/fragment
[:<>
(cond
component component
(and title subtitle)
[react/fragment
[:<>
[text/text {:weight :medium
:number-of-lines 1}
title]

View File

@ -5,19 +5,16 @@
[quo.react-native :as rn]
[reagent.core :as reagent]))
(defn text-style [{:keys [size align weight color style]
:or {size :base
weight :regular
align :auto
color :main}}]
(merge (case weight
(defn text-style [{:keys [size align weight color style]}]
;; NOTE(Ferossgo): or in destructoring will keep nil as a value
(merge (case (or weight :regular)
:regular typography/font-regular
:medium typography/font-medium
:semi-bold typography/font-semi-bold
:bold typography/font-bold
:monospace typography/monospace
:inherit nil)
(case color
(case (or color :main)
:main {:color (:text-01 @colors/theme)}
:secondary {:color (:text-02 @colors/theme)}
:secondary-inverse {:color (:text-03 @colors/theme)}
@ -25,7 +22,7 @@
:positive {:color (:positive-01 @colors/theme)}
:negative {:color (:negative-01 @colors/theme)}
:inherit nil)
(case size
(case (or size :base)
:tiny typography/tiny
:small typography/small
:base typography/base
@ -33,7 +30,7 @@
:x-large typography/x-large
:xx-large typography/xx-large
:inherit nil)
{:text-align align}
{:text-align (or align :auto)}
style))
(defn text []

View File

@ -0,0 +1,204 @@
(ns quo.components.text-input
(:require [clojure.spec.alpha :as s]
[reagent.core :as reagent]
[oops.core :refer [ocall]]
[quo.react-native :as rn]
;; TODO(Ferossgp): Move icon component to lib
[status-im.ui.components.icons.vector-icons :as icons]
;; TODO(Ferossgp): Move tooltip into lib
[quo.components.tooltip :as tooltip]
[quo.react :as react]
[quo.platform :as platform]
[quo.design-system.typography :as typography]
[quo.design-system.spacing :as spacing]
[quo.design-system.colors :as colors]
[quo.components.text :as text]))
(s/def ::multiline boolean?)
(s/def ::secure-text-entry boolean?)
(s/def ::show-cancel boolean?)
(s/def ::label (s/nilable string?))
(s/def ::cancel-label (s/nilable string?))
(s/def ::default-value (s/nilable string?))
(s/def ::placeholder (s/nilable string?))
(s/def ::keyboard-type #{})
(s/def ::accessibility-label (s/nilable (s/or :string string? :keyword keyword?)))
(s/def ::on-focus fn?)
(s/def ::on-blur fn?)
(s/def ::on-press fn?)
(s/def ::accessory (s/keys :req-un [::icon]
:opt-un [::on-press]))
(s/def ::after (s/nilable ::accessory))
(s/def ::before (s/nilable ::accessory))
(s/def ::style (s/nilable map?))
(s/def ::input-style (s/nilable map?))
(s/def ::text-input (s/keys :opt-un
[::label
::multiline
::error
::style
::input-style
::keyboard-type
::before
::after
::cancel-label
::on-focus
::on-blur
::show-cancel
::accessibility-label
::bottom-value
::secure-text-entry]))
(defn check-spec [spec prop]
(if (s/valid? spec prop)
true
(do
(s/explain spec prop)
false)))
;; TODO(Ferossgp): Check performance for android layout animations
(when (and platform/android?
(aget rn/ui-manager "setLayoutAnimationEnabledExperimental"))
(ocall rn/ui-manager "setLayoutAnimationEnabledExperimental" true))
(def height 44) ; 22 line-height + 11*2 vertical padding
(def multiline-height 88) ; 3 * 22 three line-height + 11* vertical padding
(defn container-style [])
(defn label-style []
{:margin-bottom (:tiny spacing/spacing)})
(defn text-input-row-style []
{:flex-direction :row
:align-items :center})
(defn text-input-view-style [style]
(merge {:border-radius 8
:flex-direction :row
:flex 1
:align-items :center
:background-color (:ui-01 @colors/theme)}
style))
(defn text-input-style [multiline input-style before after]
(merge typography/font-regular
{:padding-top 11
:padding-bottom 11
:font-size 15
:margin 0
:text-align-vertical :center
:flex 1
:height height}
(when-not before
{:padding-left (:base spacing/spacing)})
(when-not after
{:padding-right (:base spacing/spacing)})
(when multiline
{:text-align-vertical :top
:line-height 22
:height multiline-height})
input-style))
(defn cancel-style []
{:margin-left (:tiny spacing/spacing)
:padding-left (:tiny spacing/spacing)
:justify-content :center
:align-self :stretch})
(defn accessory-style []
(merge (:base spacing/padding-horizontal)
{:flex 1
:justify-content :center}))
(defn accessory-element [{:keys [icon icon-opts style accessibility-label on-press]}]
(let [el (if on-press
rn/touchable-opacity
rn/view)]
[el (merge {:style {:align-self :stretch}}
(when on-press
{:on-press on-press}))
[rn/view (merge {:style (merge (accessory-style)
style)}
(when accessibility-label
{:accessibility-label accessibility-label}))
[icons/icon icon (merge {:color (:icon-01 @colors/theme)}
icon-opts)]]]))
(defn text-input []
(let [focused (reagent/atom nil)
visible (reagent/atom false)
ref (react/create-ref)
on-cancel (fn []
(some-> (react/current-ref ref) (ocall "blur")))]
(fn [{:keys [label multiline error style input-style keyboard-type before after
cancel-label on-focus on-blur show-cancel accessibility-label
bottom-value secure-text-entry container-style]
:or {cancel-label "Cancel"
show-cancel true}
:as props}]
{:pre [(check-spec ::text-input props)]}
(let [after (cond
(and secure-text-entry @visible)
{:icon :main-icons/hide
:on-press #(reset! visible false)}
(and secure-text-entry (not @visible))
{:icon :main-icons/show
:on-press #(reset! visible true)}
:else after)
secure (and secure-text-entry (not @visible))]
[rn/view {:style container-style}
(when label
[text/text {:style (label-style)}
label])
[rn/view {:style (text-input-row-style)}
[rn/view {:style (text-input-view-style style)}
(when before
[accessory-element before])
[rn/text-input
(merge {:style (text-input-style multiline input-style before after)
:ref ref
:placeholder-text-color (:text-02 @colors/theme)
:color (:text-01 @colors/theme)
:underline-color-android :transparent
:auto-capitalize :none
:secure-text-entry secure
:on-focus (fn [evt]
(when on-focus (on-focus evt))
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
(reset! focused true))
:on-blur (fn [evt]
(when on-blur (on-blur evt))
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
(reset! focused false))}
(when (and platform/ios? (not after))
{:clear-button-mode :while-editing})
(when (and platform/ios?
(not= keyboard-type "visible-password"))
{:keyboard-type keyboard-type})
(dissoc props
:style :keyboard-type :on-focus :on-blur
:secure-text-entry :ref))]
(when after
[accessory-element after])]
(when (and platform/ios?
show-cancel
(not multiline)
@focused)
[rn/touchable-opacity {:style (cancel-style)
:on-press on-cancel}
[text/text {:color :link} cancel-label]])]
(when error
[tooltip/tooltip (merge {:bottom-value (cond bottom-value bottom-value
label 30 ; 22 line height 8 margin
)}
(when accessibility-label
{:accessibility-label (str (name accessibility-label) "-error")}))
[text/text {:color :negative
:size :small}
error]])]))))

View File

@ -0,0 +1,65 @@
(ns quo.components.tooltip
(:require [reagent.core :as reagent]
[oops.core :refer [oget]]
[quo.animated :as animated]
[quo.react :as react]
[quo.react-native :as rn]
[quo.design-system.colors :as colors]
[quo.design-system.spacing :as spacing]
[status-im.ui.components.icons.vector-icons :as vector-icons]))
(def ^:private initial-height 22)
(defn tooltip-style [{:keys [bottom-value animation]}]
(merge
(:base spacing/padding-horizontal)
{:position :absolute
:align-items :center
:left 0
:right 0
:top (- bottom-value)
:opacity animation
:transform [{:translateY (animated/b-interpolate animation 10 0)}]}))
(defn container-style []
{:z-index 2
:align-items :center
:shadow-radius 16
:shadow-opacity 1
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}})
(defn content-style []
(merge (:base spacing/padding-horizontal)
{:padding-vertical 6
:elevation 2
:background-color (:ui-background @colors/theme)
:border-radius 8}))
(defn tooltip []
(let [layout (reagent/atom {:height initial-height})
animation-v (animated/value 0)
animation (animated/with-timing
animation-v
{:easing (:ease-in animated/easings)})
on-layout (fn [evt]
(let [width (oget evt "nativeEvent" "layout" "width")
height (oget evt "nativeEvent" "layout" "height")]
(reset! layout {:width width
:height height})))]
(fn [{:keys [bottom-value]} & children]
[:<>
[animated/code {:exec (animated/set animation-v 1)}]
[animated/view {:style (tooltip-style {:bottom-value (- (get @layout :height)
bottom-value)
:animation animation})
:pointer-events :box-none}
[animated/view {:style (container-style)
:pointer-events :box-none}
(into [rn/view {:style (content-style)
:on-layout on-layout}]
children)
[vector-icons/icon :icons/tooltip-tip {:width 18
:height 8
:container-style {:elevation 3}
:color (:ui-background @colors/theme)}]]]])))

View File

@ -2,12 +2,15 @@
(:require [quo.components.animated-header :as animated-header]
[quo.components.header :as header]
[quo.components.safe-area :as safe-area]
[quo.components.text-input :as text-input]
[quo.components.tooltip :as tooltip]
[quo.components.text :as text]))
(def text text/text)
(def header header/header)
(def animated-header animated-header/header)
(def text-input text-input/text-input)
(def tooltip tooltip/tooltip)
(def safe-area-provider safe-area/provider)
(def safe-area-consumer safe-area/consumer)
(def safe-area-view safe-area/view)

View File

@ -1,6 +1,8 @@
(ns quo.previews.header
(:require [quo.core :as quo]
[quo.react-native :as rn]))
[quo.react-native :as rn]
[quo.design-system.colors :as colors])
(:require-macros [quo.previews.preview :as preview]))
(def accessories [nil
[{:icon :main-icons/close
@ -16,17 +18,27 @@
[{:label "Text"
:on-press identity}]])
(def all-props (preview/list-comp [left-accessories accessories
right-accessories accessories
title [nil "This is a title" "This is a very long super title"]
subtitle [nil "This is a subtitle"]
title-align [:left :center]]
{:left-accessories left-accessories
:right-accessories right-accessories
:title title
:subtitle subtitle
:title-align title-align}))
(defn render-item [props]
[rn/view {:border-bottom-color "#EEF2F5"
:border-bottom-width 2}
[quo/header props]])
(defn preview-header []
[rn/scroll-view {:flex 1}
(for [left-accessories accessories
right-accessories accessories
title [nil "This is a title" "This is a very long super title"]
subtitle [nil "This is a subtitle"]
title-align [:left :center]]
[rn/view {:border-bottom-color "#EEF2F5"
:border-bottom-width 2}
[quo/header {:left-accessories left-accessories
:right-accessories right-accessories
:title title
:subtitle subtitle
:title-align title-align}]])])
[rn/view {:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:data all-props
:render-fn render-item
:key-fn str}]])

View File

@ -2,26 +2,62 @@
(:require [oops.core :refer [ocall]]
[quo.previews.header :as header]
[quo.previews.text :as text]
[quo.previews.text-input :as text-input]
[quo.previews.tooltip :as tooltip]
[quo.react-native :as rn]
[quo.core :as quo]
[reagent.core :as reagent]
[quo.design-system.colors :as colors]
[quo.theme :as theme]
[status-im.ui.screens.routing.core :as navigation]))
(def screens [{:name :texts
:insets {:top false}
:component text/preview-text}
{:name :tooltip
:insets {:top false}
:component tooltip/preview-tooltip}
{:name :text-input
:insets {:top false}
:component text-input/preview-text}
{:name :headers
:insets {:top false}
:component header/preview-header}])
(defn theme-switcher []
[rn/view {:style {:flex-direction :row
:margin-vertical 8
:border-radius 4
:background-color (:ui-01 @colors/theme)
:border-width 1
:border-color (:ui-02 @colors/theme)}}
[rn/touchable-opacity {:style {:padding 8
:flex 1
:justify-content :center
:align-items :center}
:on-press #(theme/set-theme :light)}
[quo/text "Set light theme"]]
[rn/view {:width 1
:margin-vertical 4
:background-color (:ui-02 @colors/theme)}]
[rn/touchable-opacity {:style {:padding 8
:flex 1
:justify-content :center
:align-items :center}
:on-press #(theme/set-theme :dark)}
[quo/text "Set dark theme"]]])
(defn main-screen []
[rn/scroll-view {:flex 1
:padding-vertical 8
:padding-horizontal 16}
:padding-horizontal 16
:background-color (:ui-background @colors/theme)}
[theme-switcher]
[rn/view
(for [{:keys [name]} screens]
[rn/touchable-opacity {:on-press #(navigation/navigate-to name nil)}
[rn/view {:style {:padding-vertical 8}}
[rn/text (str "Preview " name)]]])]])
[quo/text (str "Preview " name)]]])]])
(defonce navigation-state (atom nil))

View File

@ -0,0 +1,15 @@
(ns quo.previews.preview)
(defn descriptor->values [{:keys [key options type]}]
{key (case type
:boolean [false true]
:text [nil "Just simple text"] ; NOTE(Ferossgp): add example with long text?
:select (mapv :key options))})
(defmacro list-comp [[binding seq-expr & bindings] body-expr]
(cond (not binding)
`(list ~body-expr)
:else
`(mapcat (fn [~binding] (list-comp ~bindings ~body-expr))
~seq-expr)))

View File

@ -0,0 +1,171 @@
(ns quo.previews.preview
(:require [reagent.core :as reagent]
[quo.react-native :as rn]
[quo.core :as quo]
[quo.design-system.colors :as colors])
(:require-macros quo.previews.preview))
(def container {:flex-direction :row
:padding-vertical 8
:align-items :center})
(defn touchable-style []
{:flex 1
:align-items :center
:justify-content :center
:padding-horizontal 16
:height 44})
(defn select-style []
{:flex 1
:flex-direction :row
:align-items :center
:padding-horizontal 16
:height 44
:border-radius 4
:background-color (:ui-01 @colors/theme)
:border-width 1
:border-color (:ui-02 @colors/theme)})
(defn select-option-style [selected]
(merge (select-style)
{:margin-vertical 8
:justify-content :center}
(if selected
{:background-color (:interactive-02 @colors/theme)}
{:background-color (:ui-01 @colors/theme)})))
(def label-style {:flex 0.4
:padding-right 8})
(defn modal-container []
{:flex 1
:justify-content :center
:padding-horizontal 24
:background-color "rgba(0,0,0,0.4)"})
(defn modal-view []
{:padding-horizontal 16
:padding-vertical 8
:border-radius 8
:flex-direction :column
:background-color (:ui-background @colors/theme)})
(defn customizer-boolean
[{:keys [label key state]}]
(let [state* (reagent/cursor state [key])]
[rn/view {:style container}
[rn/view {:style label-style}
[quo/text label]]
[rn/view {:style {:flex-direction :row
:flex 0.6
:border-radius 4
:background-color (:ui-01 @colors/theme)
:border-width 1
:border-color (:ui-02 @colors/theme)}}
[rn/touchable-opacity {:style (touchable-style)
:on-press #(reset! state* true)}
[quo/text {:color (if @state* :link :secondary)}
"True"]]
[rn/view {:width 1
:margin-vertical 4
:background-color (:ui-02 @colors/theme)}]
[rn/touchable-opacity {:style (touchable-style)
:on-press #(reset! state* false)}
[quo/text {:color (if (not @state*) :link :secondary)}
"False"]]]]))
(defn customizer-text
[{:keys [label key state]}]
(let [state* (reagent/cursor state [key])]
[rn/view {:style container}
[rn/view {:style label-style}
[quo/text label]]
[rn/view {:style {:flex 0.6}}
[quo/text-input {:value @state*
:show-cancel false
:style {:border-radius 4
:border-width 1
:border-color (:ui-02 @colors/theme)}
:on-change-text #(do
(reset! state* %)
(reagent/flush))}]]]))
(defn value-for-key
[id v]
(:value (first (filter #(= (:key %) id) v))))
(defn customizer-select []
(let [open (reagent/atom nil)]
(fn [{:keys [label key state options]}]
(let [state* (reagent/cursor state [key])
selected (value-for-key @state* options)]
[rn/view {:style container}
[rn/view {:style label-style}
[quo/text label]]
[rn/view {:style {:flex 0.6}}
[rn/modal {:visible @open
:on-request-close #(reset! open false)
:transparent :true
:animation :slide}
[rn/view {:style (modal-container)}
[rn/view {:style (modal-view)}
[rn/scroll-view
(doall
(for [{:keys [key value]} options]
^{:key key}
[rn/touchable-opacity {:style (select-option-style (= @state* key))
:on-press #(do
(reset! open false)
(reset! state* key))}
[quo/text {:color (if (= @state* key) :link :secondary)}
value]]))
[rn/view {:flex-direction :row}
[rn/touchable-opacity {:style (select-option-style false)
:on-press #(do
(reset! state* nil)
(reset! open false))}
[quo/text "Clear"]]
[rn/view {:width 16}]
[rn/touchable-opacity {:style (select-option-style false)
:on-press #(reset! open false)}
[quo/text "Close"]]]]]]]
[rn/touchable-opacity {:style (select-style)
:on-press #(reset! open true)}
(if selected
[quo/text {:color :link} selected]
[quo/text "Select option"])
[rn/view {:position :absolute
:right 16
:top 0
:bottom 0
:justify-content :center}
[quo/text "↓"]]]]]))))
(defn customizer [state descriptors]
[rn/view
(doall
(for [{:keys [key type]
:as desc} descriptors
:let [descriptor (merge desc
{:state state})]]
^{:key key}
[rn/view
(case type
:boolean [customizer-boolean descriptor]
:text [customizer-text descriptor]
:select [customizer-select descriptor])]))])
(comment
[{:label "Show error:"
:key :error
:type :boolean}
{:label "Label:"
:key :label
:type :text}
{:label "Type:"
:key :type
:type :select
:options [{:key :primary :value "Primary"}
{:key :secondary :value "Secondary"}]}])

View File

@ -1,14 +1,89 @@
(ns quo.previews.text
(:require [quo.core :as quo]
[quo.react-native :as rn]))
(:require [reagent.core :as reagent]
[quo.core :as quo]
[quo.animated :as animated]
[quo.react-native :as rn]
[quo.design-system.colors :as colors]
[quo.previews.preview :as preview]))
(def all-props (preview/list-comp [size [:tiny :small :base :large :x-large :xx-large]
weight [:regular :medium :semi-bold :bold :monospace]]
{:weight weight
:size size}))
(def descriptor [{:label "Size:"
:key :size
:type :select
:options [{:key :tiny
:value "Tiny"}
{:key :small
:value "Small"}
{:key :base
:value "Base"}
{:key :large
:value "Large"}
{:key :x-large
:value "X-Large"}
{:key :xx-large
:value "XX-Large"}]}
{:label "Weight:"
:key :weight
:type :select
:options [{:key :regular
:value "Regular"}
{:key :medium
:value "Medium"}
{:key :semi-bold
:value "Semi-bold"}
{:key :bold
:value "Bold"}
{:key :monospace
:value "Monospace"}]}
{:label "Color:"
:key :color
:type :select
:options [{:key :main
:value "main"}
{:key :secondary
:value "secondary"}
{:key :secondary-inverse
:value "secondary-inverse"}
{:key :link
:value "link"}
{:key :negative
:value "negative"}
{:key :positive
:value "positive"}]}
{:label "Animated:"
:key :animated?
:type :boolean}])
(defn render-item [props]
[rn/view {:style {:padding-vertical 24
:padding-horizontal 16}}
[quo/text props
(str "Text size " props " number 0 1x2")]])
(defn cool-preview []
(let [state (reagent/atom {})
animation (animated/value 0)]
(fn []
[rn/view {:margin-bottom 50
:padding 16}
[animated/code {:exec (animated/set animation (animated/loop* {:duration 1000}))}]
[preview/customizer state descriptor]
[rn/view {:padding-vertical 16}
[quo/text (merge @state
(when (:animated? @state)
{:opacity animation}))
"This is a demo text 1 2 0 2x2 0x0"]]])))
(defn preview-text []
[rn/scroll-view {:flex 1
:padding-horizontal 16}
(for [size [:tiny :small :base :large :x-large :xx-large]
weight [:regular :medium :semi-bold :bold :monospace]]
^{:key (str)}
[rn/view {:padding-vertical 16}
[quo/text {:weight weight
:size size}
(str "Text size " size ", font weight " weight)]])])
[rn/view {:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])

View File

@ -0,0 +1,81 @@
(ns quo.previews.text-input
(:require [reagent.core :as reagent]
[quo.core :as quo]
[quo.react-native :as rn]
[quo.design-system.colors :as colors]
[quo.previews.preview :as preview]))
(def all-props (preview/list-comp [multiline [false true]
label [nil "Input label"]
default-value [nil "Test initial value"]
placeholder [nil "Placeholder value"]
before [nil {:icon :main-icons/search}]
after [nil {:icon :main-icons/close}]
error [nil "Something went wrong!"]
secure [false true]
show-cancel [false true]]
{:label label
:default-value default-value
:placeholder placeholder
:multiline multiline
:before before
:after after
:error error
:show-cancel show-cancel
:secure-text-entry secure}))
(def descriptor [{:label "Multiline:"
:key :multiline
:type :boolean}
{:label "Show cancel:"
:key :show-cancel
:type :boolean}
{:label "Secure:"
:key :secure-text-entry
:type :boolean}
{:label "After icon:"
:key :after
:type :boolean}
{:label "Before icon:"
:key :before
:type :boolean}
{:label "Show error:"
:key :error
:type :boolean}
{:label "Label"
:key :label
:type :text}])
(defn render-item [props]
[rn/view {:style {:padding-horizontal 16
:padding-vertical 24}}
[quo/text-input props]])
(defn cool-preview []
(let [state (reagent/atom {:secure false
:show-cancel false
:multiline false
:label "I'm a cool label"})
before (reagent/cursor state [:before])
after (reagent/cursor state [:after])
error (reagent/cursor state [:error])]
(fn []
[rn/view {:margin-bottom 50
:padding 16}
[preview/customizer state descriptor]
[quo/text-input (merge @state
{:default-value nil
:placeholder "I'm a cool placeholder"
:before (when @before {:icon :main-icons/search})
:after (when @after {:icon :main-icons/close})
:error (when @error "Something went wrong!")})]])))
(defn preview-text []
[rn/view {:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])

View File

@ -0,0 +1,30 @@
(ns quo.previews.tooltip
(:require [quo.core :as quo]
[quo.react-native :as rn]
[quo.design-system.colors :as colors])
(:require-macros [quo.previews.preview :as preview]))
(def all-props (preview/list-comp
[child [[quo/text {:size :small} "Simple text"]
[quo/text {:color :negative
:size :small}
"Error text"]
[rn/view {:width 100 :height 20 :background-color :red}]
[quo/text "Just text, but long. Officia autem est repellendus ad quia exercitationem veniam."]]]
child))
(defn render-item [children]
[rn/view {:margin-vertical 50}
[rn/view {:height 20
:background-color "rgba(0,0,0,0.1)"}]
[quo/tooltip {}
children]])
(defn preview-tooltip []
[rn/view {:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:data all-props
:render-fn render-item
:key-fn str}]])

View File

@ -1,7 +1,8 @@
(ns quo.react
(:require [oops.core :refer [oget]]
[reagent.core :as reagent]
["react" :as react]))
;; NOTE(Ferossgp): Available in new versions of reagent as `:<>`
(def fragment (reagent/adapt-react-class (oget react "Fragment")))
(def create-ref (oget react "createRef"))
(defn current-ref [ref]
(oget ref "current"))

View File

@ -11,6 +11,45 @@
(def text (reagent/adapt-react-class (.-Text ^js rn)))
(def scroll-view (reagent/adapt-react-class (.-ScrollView ^js rn)))
(def modal (reagent/adapt-react-class (.-Modal ^js rn)))
(def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^js rn)))
(def touchable-highlight (reagent/adapt-react-class (.-TouchableHighlight ^js rn)))
(def text-input (reagent/adapt-react-class (.-TextInput ^js rn)))
(def ui-manager (.-UIManager ^js rn))
(def layout-animation (.-LayoutAnimation ^js rn))
(def configure-next (.-configureNext ^js layout-animation))
(def layout-animation-presets {:ease-in-ease-out (-> ^js layout-animation .-Presets .-easeInEaseOut)
:linear (-> ^js layout-animation .-Presets .-linear)
:spring (-> ^js layout-animation .-Presets .-spring)})
(def switch (reagent/adapt-react-class (.-Switch ^js rn)))
;; Flat-list
(def ^:private rn-flat-list (reagent/adapt-react-class (.-FlatList ^js rn)))
(defn- wrap-render-fn [f]
(fn [data]
(reagent/as-element (f (.-item data) (.-index data) (.-separators data)))))
(defn- wrap-key-fn [f]
(fn [data index]
{:post [(some? %)]}
(f data index)))
(defn- base-list-props
[{:keys [key-fn render-fn empty-component header footer separator data] :as props}]
(merge {:data (to-array data)}
(when key-fn {:keyExtractor (wrap-key-fn key-fn)})
(when render-fn {:renderItem (wrap-render-fn render-fn)})
(when separator {:ItemSeparatorComponent (fn [] (reagent/as-element separator))})
(when empty-component {:ListEmptyComponent (fn [] (reagent/as-element empty-component))})
(when header {:ListHeaderComponent (reagent/as-element header)})
(when footer {:ListFooterComponent (reagent/as-element footer)})
(dissoc props :data :header :footer :empty-component :separator :render-fn :key-fn)))
(defn flat-list [props]
[rn-flat-list (base-list-props props)])

4
src/quo/spec.cljs Normal file
View File

@ -0,0 +1,4 @@
(ns quo.spec
(:require [clojure.spec.alpha :as s]))
(s/def ::style (s/nilable map?))