diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index a2d82ac584..39d16e9e6e 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -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 diff --git a/android/app/src/main/res/drawable-mdpi/hide.png b/android/app/src/main/res/drawable-mdpi/hide.png new file mode 100644 index 0000000000..1a927c1f8b Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/hide.png differ diff --git a/android/app/src/main/res/drawable-mdpi/show.png b/android/app/src/main/res/drawable-mdpi/show.png new file mode 100644 index 0000000000..ae2e6018ff Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/show.png differ diff --git a/android/app/src/main/res/drawable-mdpi/tooltip_tip.png b/android/app/src/main/res/drawable-mdpi/tooltip_tip.png new file mode 100644 index 0000000000..8c737ae939 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/tooltip_tip.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/hide.png b/android/app/src/main/res/drawable-xhdpi/hide.png new file mode 100644 index 0000000000..0f223f700a Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/hide.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/show.png b/android/app/src/main/res/drawable-xhdpi/show.png new file mode 100644 index 0000000000..9940abf449 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/show.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/tooltip_tip.png b/android/app/src/main/res/drawable-xhdpi/tooltip_tip.png new file mode 100644 index 0000000000..ebaaa0272d Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/tooltip_tip.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/hide.png b/android/app/src/main/res/drawable-xxhdpi/hide.png new file mode 100644 index 0000000000..69480da8d3 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/hide.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/show.png b/android/app/src/main/res/drawable-xxhdpi/show.png new file mode 100644 index 0000000000..5e02e6e75f Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/show.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/tooltip_tip.png b/android/app/src/main/res/drawable-xxhdpi/tooltip_tip.png new file mode 100644 index 0000000000..eb70c36a01 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/tooltip_tip.png differ diff --git a/ios/StatusIm.xcodeproj/project.pbxproj b/ios/StatusIm.xcodeproj/project.pbxproj index 977e101d83..d8667414e1 100644 --- a/ios/StatusIm.xcodeproj/project.pbxproj +++ b/ios/StatusIm.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; 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 = ""; }; - 3A0B103024581B74004B0F23 /* InterStatus-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "InterStatus-Regular.otf"; path = "../resources/fonts/InterStatus-Regular.otf"; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; 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 = ""; }; + 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 = ""; }; 9EC0135C1E06FB1900155B5C /* RCTWKWebView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWKWebView.xcodeproj; path = "../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView.xcodeproj"; sourceTree = ""; }; 9EF083381F3B538A00876A8F /* ReactNativeConfig.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeConfig.xcodeproj; path = "../node_modules/react-native-config/ios/ReactNativeConfig.xcodeproj"; sourceTree = ""; }; 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 = ""; }; @@ -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 = ""; @@ -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 */, diff --git a/ios/StatusIm/Images.xcassets/hide.imageset/Contents.json b/ios/StatusIm/Images.xcassets/hide.imageset/Contents.json new file mode 100644 index 0000000000..444f64381a --- /dev/null +++ b/ios/StatusIm/Images.xcassets/hide.imageset/Contents.json @@ -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 + } +} diff --git a/ios/StatusIm/Images.xcassets/hide.imageset/Hide.png b/ios/StatusIm/Images.xcassets/hide.imageset/Hide.png new file mode 100644 index 0000000000..1a927c1f8b Binary files /dev/null and b/ios/StatusIm/Images.xcassets/hide.imageset/Hide.png differ diff --git a/ios/StatusIm/Images.xcassets/hide.imageset/Hide@2x.png b/ios/StatusIm/Images.xcassets/hide.imageset/Hide@2x.png new file mode 100644 index 0000000000..0f223f700a Binary files /dev/null and b/ios/StatusIm/Images.xcassets/hide.imageset/Hide@2x.png differ diff --git a/ios/StatusIm/Images.xcassets/hide.imageset/Hide@3x.png b/ios/StatusIm/Images.xcassets/hide.imageset/Hide@3x.png new file mode 100644 index 0000000000..69480da8d3 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/hide.imageset/Hide@3x.png differ diff --git a/ios/StatusIm/Images.xcassets/show.imageset/Contents.json b/ios/StatusIm/Images.xcassets/show.imageset/Contents.json new file mode 100644 index 0000000000..4c7749b529 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/show.imageset/Contents.json @@ -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 + } +} diff --git a/ios/StatusIm/Images.xcassets/show.imageset/Show.png b/ios/StatusIm/Images.xcassets/show.imageset/Show.png new file mode 100644 index 0000000000..ae2e6018ff Binary files /dev/null and b/ios/StatusIm/Images.xcassets/show.imageset/Show.png differ diff --git a/ios/StatusIm/Images.xcassets/show.imageset/Show@2x.png b/ios/StatusIm/Images.xcassets/show.imageset/Show@2x.png new file mode 100644 index 0000000000..9940abf449 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/show.imageset/Show@2x.png differ diff --git a/ios/StatusIm/Images.xcassets/show.imageset/Show@3x.png b/ios/StatusIm/Images.xcassets/show.imageset/Show@3x.png new file mode 100644 index 0000000000..5e02e6e75f Binary files /dev/null and b/ios/StatusIm/Images.xcassets/show.imageset/Show@3x.png differ diff --git a/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Contents.json b/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Contents.json new file mode 100644 index 0000000000..0628edbec5 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Contents.json @@ -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 + } +} diff --git a/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Tip.png b/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Tip.png new file mode 100644 index 0000000000..8c737ae939 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Tip.png differ diff --git a/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Tip@2x.png b/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Tip@2x.png new file mode 100644 index 0000000000..ebaaa0272d Binary files /dev/null and b/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Tip@2x.png differ diff --git a/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Tip@3x.png b/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Tip@3x.png new file mode 100644 index 0000000000..eb70c36a01 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/tooltip-tip.imageset/Tip@3x.png differ diff --git a/ios/StatusIm/Info.plist b/ios/StatusIm/Info.plist index 5f00f23418..12c29884ff 100644 --- a/ios/StatusIm/Info.plist +++ b/ios/StatusIm/Info.plist @@ -103,6 +103,7 @@ Inter-SemiBoldItalic.otf Inter-Thin-BETA.otf Inter-ThinItalic-BETA.otf + InterStatus-Regular.otf UIBackgroundModes diff --git a/src/quo/animated.cljs b/src/quo/animated.cljs index 376657847d..a7a15c49a2 100644 --- a/src/quo/animated.cljs +++ b/src/quo/animated.cljs @@ -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))) diff --git a/src/quo/components/header.cljs b/src/quo/components/header.cljs index b600d90bda..e99f08f742 100644 --- a/src/quo/components/header.cljs +++ b/src/quo/components/header.cljs @@ -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] diff --git a/src/quo/components/text.cljs b/src/quo/components/text.cljs index d3250b1f98..234260c22b 100644 --- a/src/quo/components/text.cljs +++ b/src/quo/components/text.cljs @@ -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 [] diff --git a/src/quo/components/text_input.cljs b/src/quo/components/text_input.cljs new file mode 100644 index 0000000000..4d173620b3 --- /dev/null +++ b/src/quo/components/text_input.cljs @@ -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]])])))) diff --git a/src/quo/components/tooltip.cljs b/src/quo/components/tooltip.cljs new file mode 100644 index 0000000000..7cc758dfbf --- /dev/null +++ b/src/quo/components/tooltip.cljs @@ -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)}]]]]))) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index 9bd3f1d08b..ea7a932bea 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -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) diff --git a/src/quo/previews/header.cljs b/src/quo/previews/header.cljs index 0a683c2b65..3df2b3b590 100644 --- a/src/quo/previews/header.cljs +++ b/src/quo/previews/header.cljs @@ -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}]]) diff --git a/src/quo/previews/main.cljs b/src/quo/previews/main.cljs index 5bce49da23..3f35ea5034 100644 --- a/src/quo/previews/main.cljs +++ b/src/quo/previews/main.cljs @@ -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)) diff --git a/src/quo/previews/preview.clj b/src/quo/previews/preview.clj new file mode 100644 index 0000000000..c57e450337 --- /dev/null +++ b/src/quo/previews/preview.clj @@ -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))) diff --git a/src/quo/previews/preview.cljs b/src/quo/previews/preview.cljs new file mode 100644 index 0000000000..eb782d78cd --- /dev/null +++ b/src/quo/previews/preview.cljs @@ -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"}]}]) diff --git a/src/quo/previews/text.cljs b/src/quo/previews/text.cljs index 56507b7d2d..cf912d923d 100644 --- a/src/quo/previews/text.cljs +++ b/src/quo/previews/text.cljs @@ -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}]]) diff --git a/src/quo/previews/text_input.cljs b/src/quo/previews/text_input.cljs new file mode 100644 index 0000000000..bd54946008 --- /dev/null +++ b/src/quo/previews/text_input.cljs @@ -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}]]) diff --git a/src/quo/previews/tooltip.cljs b/src/quo/previews/tooltip.cljs new file mode 100644 index 0000000000..d618ba6288 --- /dev/null +++ b/src/quo/previews/tooltip.cljs @@ -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}]]) diff --git a/src/quo/react.cljs b/src/quo/react.cljs index a493630a2d..e97c3e61ab 100644 --- a/src/quo/react.cljs +++ b/src/quo/react.cljs @@ -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")) diff --git a/src/quo/react_native.cljs b/src/quo/react_native.cljs index 26961b865a..80145d3d60 100644 --- a/src/quo/react_native.cljs +++ b/src/quo/react_native.cljs @@ -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)]) diff --git a/src/quo/spec.cljs b/src/quo/spec.cljs new file mode 100644 index 0000000000..75a6bf5e70 --- /dev/null +++ b/src/quo/spec.cljs @@ -0,0 +1,4 @@ +(ns quo.spec + (:require [clojure.spec.alpha :as s])) + +(s/def ::style (s/nilable map?))