diff --git a/android/app/src/main/assets/fonts/InterStatus-Regular.otf b/android/app/src/main/assets/fonts/InterStatus-Regular.otf new file mode 100644 index 0000000000..92c5dcb6e6 Binary files /dev/null and b/android/app/src/main/assets/fonts/InterStatus-Regular.otf differ diff --git a/ios/StatusIm.xcodeproj/project.pbxproj b/ios/StatusIm.xcodeproj/project.pbxproj index 5d19c378c0..a2ef327e59 100644 --- a/ios/StatusIm.xcodeproj/project.pbxproj +++ b/ios/StatusIm.xcodeproj/project.pbxproj @@ -90,6 +90,7 @@ 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; }; @@ -180,6 +181,7 @@ 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 */, diff --git a/resources/fonts/InterStatus-Regular.otf b/resources/fonts/InterStatus-Regular.otf new file mode 100644 index 0000000000..92c5dcb6e6 Binary files /dev/null and b/resources/fonts/InterStatus-Regular.otf differ diff --git a/src/quo/README.md b/src/quo/README.md new file mode 100644 index 0000000000..83adb71129 --- /dev/null +++ b/src/quo/README.md @@ -0,0 +1,49 @@ +# Status Quo Components +All components in **Quo** should be independent of the app state. They should be pure, +and easy to reason about. This is required to make the library independent and +easily pulled off as a separate repository when needed. +Do avoid high coupling and direct use of internal styling, the components should be +exported via namespace `quo.core` and used by status app only from here. This will +allow a more flexible way to update components without possible breakages into the +app style. + +**Quo** components should and not have any dependency on the status app, this +will avoid circular dependency and also benefit the independence of the components. + +All components are stored inside `components` namespaces. They are stateless and do +not dispatch and subscribe to re-frame database. All state should be passed by props +and all events can be passed as functions. Avoiding direct connection with re-frame +will allow components to grow and be reused in different places without the +conditionals hell. + +All style system constants are stored inside `design-system` namespaces. They are used +to build components and can be directly required by the status app. Avoid +duplication of these vars and do not use them in code directly as a value. + +For each component introduced, add previews of all possible states. + +Do not introduce components for slightly modified existing components, if they are +not a part of the design system. In case they are required in one place in the app, +use style override. + +# Code style +Ensure that your changes match the style of the rest of the code. +This library uses Clojure Community code style [The Clojure Style Guide](https://github.com/bbatsov/clojure-style-guide) +To ensure consistency run [clj-kondo linter](https://github.com/borkdude/clj-kondo) + +# Best practices + +- Desing components atomically and compose them into bigger components. +- Do not export individual atoms, only components. This way we can limit design +system to be used in too many way which can creating disjointed experiences. +- Avoid external margins for atom components, it can be added on the wrapper +where they are used but can't be removed without overriding. +[Max Stoiber article on margins](https://mxstbr.com/thoughts/margin) +- Design reusable components into [Layout Isolated Components](https://visly.app/blog/layout-isolated-components) +(Article more relates to web, but ideas fits also to mobile dev** +- Explicit is better than implicit, do not rely on platform default, if you expect +a specific value, then override it + +**TBD:** +- Components documentation +- Check props using spec in pre conditions. diff --git a/src/status_im/ui/components/reanimated.cljs b/src/quo/animated.cljs similarity index 70% rename from src/status_im/ui/components/reanimated.cljs rename to src/quo/animated.cljs index f839a6f08d..592cf7a557 100644 --- a/src/status_im/ui/components/reanimated.cljs +++ b/src/quo/animated.cljs @@ -1,4 +1,4 @@ -(ns status-im.ui.components.reanimated +(ns quo.animated (:refer-clojure :exclude [set]) (:require [reagent.core :as reagent] [oops.core :refer [oget ocall]] @@ -101,45 +101,6 @@ (def extrapolate {:clamp (oget animated "Extrapolate" "CLAMP")}) -;; Gesture handler - -(def tap-gesture-handler - (reagent/adapt-react-class - (oget js-deps/react-native-gesture-handler "TapGestureHandler"))) - -(def pan-gesture-handler - (reagent/adapt-react-class - (oget js-deps/react-native-gesture-handler "PanGestureHandler"))) - -(def long-press-gesture-handler - (reagent/adapt-react-class - (oget js-deps/react-native-gesture-handler "LongPressGestureHandler"))) - -(def pure-native-button (oget js-deps/react-native-gesture-handler "PureNativeButton")) - -(def touchable-without-feedback-class - (oget js-deps/react-native-gesture-handler "TouchableWithoutFeedback")) - -(def createNativeWrapper - (oget js-deps/react-native-gesture-handler "createNativeWrapper")) - -(def touchable-without-feedback - (reagent/adapt-react-class touchable-without-feedback-class)) - -(def animated-raw-button - (reagent/adapt-react-class - (createNativeWrapper - (createAnimatedComponent touchable-without-feedback-class)))) - -(def state (oget js-deps/react-native-gesture-handler "State")) - -(def states {:began (oget state "BEGAN") - :active (oget state "ACTIVE") - :cancelled (oget state "CANCELLED") - :end (oget state "END") - :failed (oget state "FAILED") - :undetermined (oget state "UNDETERMINED")}) - ;; utilities (def redash js-deps/react-native-redash) diff --git a/src/quo/components/animated_header.cljs b/src/quo/components/animated_header.cljs new file mode 100644 index 0000000000..bd62212744 --- /dev/null +++ b/src/quo/components/animated_header.cljs @@ -0,0 +1,82 @@ +(ns quo.components.animated-header + (:require [oops.core :refer [oget]] + [quo.animated :as animated] + [quo.components.header :as header] + [quo.components.safe-area :as safe-area] + [quo.design-system.colors :as colors] + [quo.platform :as platform] + [quo.react-native :as rn] + [reagent.core :as reagent])) + +(defn header-wrapper-style [{:keys [value offset]}] + (merge + {:background-color :white} + (when (and offset platform/android?) + {:elevation (animated/interpolate + value + {:inputRange [0 offset] + :outputRange [0 4] + :extrapolate (:clamp animated/extrapolate)})}) + (when (and offset platform/ios?) + {:shadow-opacity (animated/interpolate + value + {:inputRange [0 offset] + :outputRange [0 1] + :extrapolate (:clamp animated/extrapolate)}) + :shadow-radius 16 + :z-index 2 + :shadow-color (:shadow-01 @colors/theme) + :shadow-offset {:width 0 :height 4}}))) + +(defn header-opened-style [{:keys [value offset]}] + (merge + {:position :absolute + :top 0 + :left 0 + :right 0} + (when offset + {:transform [{:translateY + (animated/interpolate + value + {:inputRange [0 offset] + :outputRange [0 (- header/header-height)] + :extrapolateRight (:clamp animated/extrapolate)})}]}))) + +(defn header-container [] + (let [y (animated/value 0) + on-scroll (animated/on-scroll {:y y}) + layout (reagent/atom {}) + offset (reagent/atom 0) + on-layout (fn [evt] + (reset! offset (oget evt "nativeEvent" "layout" "height")))] + (fn [{:keys [extended-header] :as props} & children] + [animated/view {:flex 1 + :pointer-events :box-none} + [animated/view {:pointer-events :box-none + :style (header-wrapper-style {:value y + :offset @offset})} + [header/header (merge {:get-layout (fn [el l] (swap! layout assoc el l))} + (dissoc props :extended-header))] + [rn/view {:pointer-events :box-none} + [animated/view {:style (header-opened-style {:value y + :offset @offset}) + :pointer-events :box-none + :on-layout on-layout} + [extended-header {:value y + :layout @layout + :offset @offset}]]]] + (into [animated/scroll-view {:on-scroll on-scroll + :scrollEventThrottle 1} + [rn/view {:pointer-events :box-none + :height @offset}]] + children)]))) + +(defn header [{:keys [use-insets] :as props} & children] + (if use-insets + [safe-area/consumer + (fn [insets] + [header-container (-> props + (dissoc :use-insets) + (assoc :insets insets)) + children])] + [header-container props children])) diff --git a/src/quo/components/header.cljs b/src/quo/components/header.cljs new file mode 100644 index 0000000000..b600d90bda --- /dev/null +++ b/src/quo/components/header.cljs @@ -0,0 +1,175 @@ +(ns quo.components.header + (:require [oops.core :refer [oget]] + [quo.animated :as animated] + [quo.components.text :as text] + [quo.design-system.colors :as colors] + [quo.design-system.spacing :as spacing] + [quo.react :as react] + [quo.react-native :as rn] + [reagent.core :as reagent] + [status-im.ui.components.icons.vector-icons :as icons])) + +(def header-height 56) + +(defn header-wrapper-style [{:keys [height border-bottom]}] + (merge + {:background-color (:ui-background @colors/theme) + :height height} + (when border-bottom + {:border-bottom-width 1 + :border-bottom-color (:ui-02 @colors/theme)}))) + +(def absolute-fill {:position :absolute + :top 0 + :bottom 0 + :left 0 + :right 0}) + +(def content {:flex 1 + :flex-direction :row + :align-items :center + :justify-content :center}) + +(def left {:position :absolute + :left 0 + :top 0 + :bottom 0 + :justify-content :center + :align-items :flex-start}) + +(def right {:position :absolute + :right 0 + :top 0 + :bottom 0 + :justify-content :center + :align-items :flex-end}) + +(defn title-style [{:keys [left right]} title-align] + (merge + {:position :absolute + :justify-content :center + :top 0 + :bottom 0} + (:tiny spacing/padding-horizontal) + (case title-align + :left {:left (:width left) + :right (:width right)} + {:align-items :center + :left (max (:width left) (:width right)) + :right (max (:width left) (:width right))}))) + +(def header-actions-style + (merge + {:flex 1 + :flex-direction :row + :align-items :center + :justify-content :center} + (:tiny spacing/padding-horizontal))) + +(def header-action-placeholder + {:width (:tiny spacing/spacing)}) + +(def header-icon-touchable + (merge + {:flex 1 + :align-items :center + :justify-content :center} + (:tiny spacing/padding-horizontal))) + +(def element {:align-items :center + :justify-content :center + :flex 1}) + +(defn header-action [{:keys [icon label on-press accessibility-label]}] + [rn/touchable-opacity {:on-press on-press} + [rn/view (merge {:style header-icon-touchable} + (when accessibility-label + {:accessibility-label accessibility-label})) + (cond + icon [icons/icon icon] + label [text/text {:color :link} label])]]) + +(defn header-actions [{:keys [accessories component]}] + [rn/view {:style element} + (cond + (seq accessories) + (into [rn/view {:style header-actions-style}] + (map header-action accessories)) + + component component + + :else + [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] + [text/text {:weight :regular + :number-of-lines 1} + subtitle]] + + title [text/text {:weight :bold + :number-of-lines 2 + :align title-align + :size :large} + title])]) + +(defn header [] + (let [layout (reagent/atom {:left {:width 8 + :height header-height} + :right {:width 8 + :height header-height} + :title {:width 0 + :height header-height}}) + handle-layout (fn [el get-layout] + (fn [evt] + (let [width (oget evt "nativeEvent" "layout" "width") + height (oget evt "nativeEvent" "layout" "height")] + (when get-layout + (get-layout el {:width width + :height height})) + (swap! layout assoc el {:width width + :height height}))))] + (fn [{:keys [left-accessories left-component border-bottom + right-accessories right-component insets get-layout + title subtitle title-component style title-align] + :or {title-align :center}}] + (let [status-bar-height (get insets :top 0) + height (+ header-height status-bar-height)] + [animated/view {:style (header-wrapper-style {:height height + :border-bottom border-bottom})} + [rn/view {:pointer-events :box-none + :height status-bar-height}] + [rn/view {:style (merge {:height header-height} + style) + :pointer-events :box-none} + [rn/view {:style absolute-fill + :pointer-events :box-none} + [rn/view {:style content + :pointer-events :box-none} + [rn/view {:style left + :on-layout (handle-layout :left get-layout) + :pointer-events :box-none} + [header-actions {:accessories left-accessories + :component left-component}]] + + [rn/view {:style (title-style @layout title-align) + :on-layout (handle-layout :title get-layout) + :pointer-events :box-none} + [header-title {:title title + :subtitle subtitle + :title-align title-align + :component title-component}]] + + [rn/view {:style right + :on-layout (handle-layout :right get-layout) + :pointer-events :box-none} + [header-actions {:accessories right-accessories + :component right-component}]]]]]])))) diff --git a/src/quo/components/safe_area.cljs b/src/quo/components/safe_area.cljs new file mode 100644 index 0000000000..d9416f7d9f --- /dev/null +++ b/src/quo/components/safe_area.cljs @@ -0,0 +1,14 @@ +(ns quo.components.safe-area + (:require [status-im.react-native.js-dependencies :refer [safe-area-context]] + [reagent.core :as reagent] + [oops.core :refer [oget]])) + +(def provider (reagent/adapt-react-class (oget safe-area-context "SafeAreaProvider"))) +(def ^:private consumer-raw (reagent/adapt-react-class (oget safe-area-context "SafeAreaConsumer"))) +(def view (reagent/adapt-react-class (oget safe-area-context "SafeAreaView"))) + +(defn consumer [component] + [consumer-raw + (fn [insets] + (reagent/as-element + [component (js->clj insets :keywordize-keys true)]))]) diff --git a/src/quo/components/text.cljs b/src/quo/components/text.cljs new file mode 100644 index 0000000000..d3250b1f98 --- /dev/null +++ b/src/quo/components/text.cljs @@ -0,0 +1,47 @@ +(ns quo.components.text + (:require [quo.animated :as animated] + [quo.design-system.colors :as colors] + [quo.design-system.typography :as typography] + [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 + :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 + :main {:color (:text-01 @colors/theme)} + :secondary {:color (:text-02 @colors/theme)} + :secondary-inverse {:color (:text-03 @colors/theme)} + :link {:color (:text-04 @colors/theme)} + :positive {:color (:positive-01 @colors/theme)} + :negative {:color (:negative-01 @colors/theme)} + :inherit nil) + (case size + :tiny typography/tiny + :small typography/small + :base typography/base + :large typography/large + :x-large typography/x-large + :xx-large typography/xx-large + :inherit nil) + {:text-align align} + style)) + +(defn text [] + (let [this (reagent/current-component) + props (reagent/props this) + component (if (:animated? props) animated/text rn/text)] + (into [component (merge {:style (text-style props)} + (dissoc props + :style :size :weight :color + :align :animated?))] + (reagent/children this)))) diff --git a/src/quo/core.cljs b/src/quo/core.cljs new file mode 100644 index 0000000000..9bd3f1d08b --- /dev/null +++ b/src/quo/core.cljs @@ -0,0 +1,13 @@ +(ns quo.core + (:require [quo.components.animated-header :as animated-header] + [quo.components.header :as header] + [quo.components.safe-area :as safe-area] + [quo.components.text :as text])) + +(def text text/text) +(def header header/header) +(def animated-header animated-header/header) + +(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/design_system/colors.cljs b/src/quo/design_system/colors.cljs new file mode 100644 index 0000000000..26cb449ad8 --- /dev/null +++ b/src/quo/design_system/colors.cljs @@ -0,0 +1,65 @@ +(ns quo.design-system.colors + (:require [reagent.core :as reagent])) + +(def white "#FFFFFF") +(def black "#000000") + +;; Colors mapping from figma to code, note that theme is more extended and +;; one can follow the comments from the light theme to choose what to use in a component. +(comment + {"Accent blue, #4360DF" [:interactive-01 :text-04] + "Accent blue as background, #ECEFFC" [:interactive-02] + "Dark grey, #939BA1" [:text-02 :icon-02] + "Black" [:text-01 :icon-01] + "Main Green/Success, #4EBC60" [:positive-01] + "Shades 10% green, #EDFBEF" [:positive-02] + "Main Red/Error, #FF2D55" [:negative-01] + "Shades 10% Red, #FFEAEE" [:negative-02] + "Light grey, #EEF2F5" [:ui-01] + "White, #FFFFFF" [:ui-background :icon-04] + "Devider, 0.1 of black" [:ui-02]}) + +(def light-theme + {:positive-01 "rgba(68,208,88,1)" ; Primary Positive, text, icons color + :positive-02 "rgba(78,188,96,0.1)" ; Secondary Positive, Supporting color for success illustrations + :negative-01 "rgba(255,45,85,1)" ; Primary Negative, text, icons color + :negative-02 "rgba(255,45,85,0.1))" ; Secondary Negative, Supporting color for errors illustrations + :interactive-01 "rgba(67,96,223,1)" ; Accent color, buttons, own message, actions,active state + :interactive-02 "rgba(236,239,252,1)" ; Light Accent, buttons background, actions background, messages + :interactive-03 "rgba(255,255,255,0.1)" ; Background for interactive above accent + :ui-background "rgba(255,255,255,1)" ; Default view background + :ui-01 "rgba(238,242,245,1)" ; Secondary background + :ui-02 "rgba(0,0,0,0.1)" ; Deviders + :text-01 "rgba(0,0,0,1)" ; Main text color + :text-02 "rgba(147,155,161,1)" ; Secondary text + :text-03 "rgba(255,255,255,0.7)" ; Secondary on accent + :text-04 "rgba(67,96,223,1)" ; Links text color + :icon-01 "rgba(0,0,0,1)" ; Primary icons + :icon-02 "rgba(147,155,161,1)" ; Secondary icons + :icon-03 "rgba(255,255,255,0.4)" ; Secondary icons on accent bg + :icon-04 "rgba(255,255,255,1)" ; Icons inverse on accent background + :shadow-01 "rgba(0,9,26,0.12)" ; Main shadow color +}) + +(def dark-theme + {:positive-01 "rgba(68,208,88,1)" + :positive-02 "rgba(78,188,96,0.1)" + :negative-01 "rgba(252,95,95,1)" + :negative-02 "rgba(252,95,95,0.1)" + :interactive-01 "rgba(97,119,229,1)" + :interactive-02 "rgba(35,37,47,1)" + :interactive-03 "rgba(255,255,255,0.1)" + :ui-background "rgba(20,20,20,1)" + :ui-01 "rgba(37,37,40,1)" + :ui-02 "rgba(0,0,0,0.1)" + :text-01 "rgba(255,255,255,1)" + :text-02 "rgba(131,140,145,1)" + :text-03 "rgba(255,255,255,0.7)" + :text-04 "rgba(97,119,229,1)" + :icon-01 "rgba(255,255,255,1)" + :icon-02 "rgba(131,140,145,1)" + :icon-03 "rgba(255,255,255,0.4)" + :icon-04 "rgba(20,20,20,1)" + :shadow-01 "rgba(0,0,0,0.75)"}) + +(def theme (reagent/atom light-theme)) diff --git a/src/quo/design_system/spacing.cljs b/src/quo/design_system/spacing.cljs new file mode 100644 index 0000000000..3885074274 --- /dev/null +++ b/src/quo/design_system/spacing.cljs @@ -0,0 +1,19 @@ +(ns quo.design-system.spacing) + +(def spacing {:x-tiny 4 + :tiny 8 + :small 12 + :base 16 + :large 24 + :x-large 32 + :xx-large 48}) + +(def padding-horizontal (reduce-kv (fn [m k v] + (assoc m k {:padding-horizontal v})) + {} + spacing)) + +(def padding-vertical (reduce-kv (fn [m k v] + (assoc m k {:padding-vertical v})) + {} + spacing)) diff --git a/src/quo/design_system/typography.cljs b/src/quo/design_system/typography.cljs new file mode 100644 index 0000000000..cc400f57bc --- /dev/null +++ b/src/quo/design_system/typography.cljs @@ -0,0 +1,30 @@ +(ns quo.design-system.typography + (:require [quo.platform :as platform])) + +(def tiny {:font-size 10 + :line-height 14}) + +(def small {:font-size 13 + :line-height 18}) + +(def base {:font-size 15 + :line-height 22}) + +(def large {:font-size 17 + :line-height 24}) + +(def x-large {:font-size 22 + :line-height 30}) + +(def xx-large {:font-size 28 + :line-height 38}) + +(def font-regular {:font-family "Inter-Regular"}) ; 400 + +(def font-medium {:font-family "Inter-Medium"}) ; 500 ff + +(def font-semi-bold {:font-family "Inter-SemiBold"}) ; 600 + +(def font-bold {:font-family "Inter-Bold"}) ; 700 + +(def monospace {:font-family "InterStatus-Regular"}) diff --git a/src/quo/gesture_handler.cljs b/src/quo/gesture_handler.cljs new file mode 100644 index 0000000000..5dad6ea7fa --- /dev/null +++ b/src/quo/gesture_handler.cljs @@ -0,0 +1,42 @@ +(ns quo.gesture-handler + (:require [oops.core :refer [oget]] + [quo.animated :as animated] + [reagent.core :as reagent] + [status-im.react-native.js-dependencies :as js-deps])) + +(def tap-gesture-handler + (reagent/adapt-react-class + (oget js-deps/react-native-gesture-handler "TapGestureHandler"))) + +(def pan-gesture-handler + (reagent/adapt-react-class + (oget js-deps/react-native-gesture-handler "PanGestureHandler"))) + +(def long-press-gesture-handler + (reagent/adapt-react-class + (oget js-deps/react-native-gesture-handler "LongPressGestureHandler"))) + +(def pure-native-button (oget js-deps/react-native-gesture-handler "PureNativeButton")) + +(def touchable-without-feedback-class + (oget js-deps/react-native-gesture-handler "TouchableWithoutFeedback")) + +(def createNativeWrapper + (oget js-deps/react-native-gesture-handler "createNativeWrapper")) + +(def touchable-without-feedback + (reagent/adapt-react-class touchable-without-feedback-class)) + +(def animated-raw-button + (reagent/adapt-react-class + (createNativeWrapper + (animated/createAnimatedComponent touchable-without-feedback-class)))) + +(def state (oget js-deps/react-native-gesture-handler "State")) + +(def states {:began (oget state "BEGAN") + :active (oget state "ACTIVE") + :cancelled (oget state "CANCELLED") + :end (oget state "END") + :failed (oget state "FAILED") + :undetermined (oget state "UNDETERMINED")}) diff --git a/src/quo/platform.cljs b/src/quo/platform.cljs new file mode 100644 index 0000000000..33e5b64c81 --- /dev/null +++ b/src/quo/platform.cljs @@ -0,0 +1,7 @@ +(ns quo.platform + (:require [quo.react-native :as rn])) + +(def os (when rn/platform (.-OS rn/platform))) + +(def android? (= os "android")) +(def ios? (= os "ios")) diff --git a/src/quo/previews/header.cljs b/src/quo/previews/header.cljs new file mode 100644 index 0000000000..0a683c2b65 --- /dev/null +++ b/src/quo/previews/header.cljs @@ -0,0 +1,32 @@ +(ns quo.previews.header + (:require [quo.core :as quo] + [quo.react-native :as rn])) + +(def accessories [nil + [{:icon :main-icons/close + :on-press identity}] + [{:icon :main-icons/close + :on-press identity} + {:icon :main-icons/add + :on-press identity}] + [{:icon :main-icons/add + :on-press identity} + {:label "Text" + :on-press identity}] + [{:label "Text" + :on-press identity}]]) + +(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}]])]) diff --git a/src/quo/previews/main.cljs b/src/quo/previews/main.cljs new file mode 100644 index 0000000000..99c2b492d9 --- /dev/null +++ b/src/quo/previews/main.cljs @@ -0,0 +1,53 @@ +(ns quo.previews.main + (:require [oops.core :refer [ocall]] + [quo.previews.header :as header] + [quo.previews.text :as text] + [quo.react-native :as rn] + [reagent.core :as reagent] + [status-im.react-native.js-dependencies :refer [react-native]] + [status-im.ui.screens.routing.core :as navigation])) + +(def screens [{:name :texts + :insets {:top false} + :component text/preview-text} + {:name :headers + :insets {:top false} + :component header/preview-header}]) + +(defn main-screen [] + [rn/scroll-view {:flex 1 + :padding-vertical 8 + :padding-horizontal 16} + [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)]]])]]) + +(defonce navigation-state (atom nil)) + +(defn- persist-state! [state-obj] + (js/Promise. + (fn [resolve _] + (reset! navigation-state state-obj) + (resolve true)))) + +(defn preview-screens [] + (let [stack (navigation/create-stack)] + [navigation/navigation-container + {:ref navigation/set-navigator-ref + :initial-state @navigation-state + :on-state-change persist-state!} + [stack {} + (into [{:name :main + :insets {:top false} + :component main-screen}] + screens)]])) + +;; TODO(Ferossgp): Add separate build when shadow-cljs will be integrated +;; NOTE(Ferossgp): Separate app can be used to preview all available +;; and possible state for componetns, and for UI testing based on screenshots +(defn init [] + (ocall react-native ["AppRegistry" "registerComponent"] + "StatusIm" + #(reagent/reactify-component preview-screens))) diff --git a/src/quo/previews/text.cljs b/src/quo/previews/text.cljs new file mode 100644 index 0000000000..56507b7d2d --- /dev/null +++ b/src/quo/previews/text.cljs @@ -0,0 +1,14 @@ +(ns quo.previews.text + (:require [quo.core :as quo] + [quo.react-native :as rn])) + +(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)]])]) diff --git a/src/quo/react.cljs b/src/quo/react.cljs new file mode 100644 index 0000000000..44bc87f91e --- /dev/null +++ b/src/quo/react.cljs @@ -0,0 +1,7 @@ +(ns quo.react + (:require [oops.core :refer [oget]] + [reagent.core :as reagent] + [status-im.react-native.js-dependencies :refer [react]])) + +;; NOTE(Ferossgp): Available in new versions of reagent as `:<>` +(def fragment (reagent/adapt-react-class (oget react "Fragment"))) diff --git a/src/quo/react_native.cljs b/src/quo/react_native.cljs new file mode 100644 index 0000000000..904b87f982 --- /dev/null +++ b/src/quo/react_native.cljs @@ -0,0 +1,15 @@ +(ns quo.react-native + (:require [oops.core :refer [oget]] + [reagent.core :as reagent] + [status-im.react-native.js-dependencies :refer [react-native]])) + +(def platform (oget react-native "Platform")) + +(def view (reagent/adapt-react-class (oget react-native "View"))) + +(def text (reagent/adapt-react-class (oget react-native "Text"))) + +(def scroll-view (reagent/adapt-react-class (oget react-native "ScrollView"))) + +(def touchable-opacity (reagent/adapt-react-class (oget react-native "TouchableOpacity"))) +(def touchable-highlight (reagent/adapt-react-class (oget react-native "TouchableHighlight"))) diff --git a/src/quo/theme.cljs b/src/quo/theme.cljs new file mode 100644 index 0000000000..305c6b070d --- /dev/null +++ b/src/quo/theme.cljs @@ -0,0 +1,7 @@ +(ns quo.theme + (:require [quo.design-system.colors :as colors])) + +(defn set-theme [theme] + (reset! colors/theme (case theme + :dark colors/dark-theme + colors/light-theme))) diff --git a/src/status_im/init/core.cljs b/src/status_im/init/core.cljs index 2a17a74084..e661cff3cf 100644 --- a/src/status_im/init/core.cljs +++ b/src/status_im/init/core.cljs @@ -1,15 +1,16 @@ (ns status-im.init.core - (:require [re-frame.core :as re-frame] + (:require [clojure.string :as string] + [quo.theme :as quo-theme] + [re-frame.core :as re-frame] [status-im.multiaccounts.login.core :as multiaccounts.login] [status-im.native-module.core :as status] [status-im.network.net-info :as network] [status-im.react-native.js-dependencies :as rn-dependencies] + [status-im.ui.components.colors :as colors] [status-im.ui.screens.db :refer [app-db]] [status-im.utils.fx :as fx] [status-im.utils.platform :as platform] - [clojure.string :as string] - [status-im.utils.theme :as theme] - [status-im.ui.components.colors :as colors])) + [status-im.utils.theme :as theme])) (defn restore-native-settings! [] (when platform/desktop? @@ -95,4 +96,5 @@ (fn [] (theme/add-mode-change-listener #(re-frame/dispatch [:system-theme-mode-changed %])) (when (theme/is-dark-mode) - (colors/set-theme :dark)))) \ No newline at end of file + (quo-theme/set-theme :dark) + (colors/set-theme :dark)))) diff --git a/src/status_im/multiaccounts/core.cljs b/src/status_im/multiaccounts/core.cljs index 6c61a9ba8e..9b62270cc7 100644 --- a/src/status_im/multiaccounts/core.cljs +++ b/src/status_im/multiaccounts/core.cljs @@ -1,14 +1,15 @@ (ns status-im.multiaccounts.core - (:require [re-frame.core :as re-frame] + (:require [quo.theme :as quo-theme] + [re-frame.core :as re-frame] [status-im.ethereum.stateofus :as stateofus] [status-im.multiaccounts.update.core :as multiaccounts.update] [status-im.native-module.core :as native-module] [status-im.notifications.core :as notifications] - [status-im.utils.fx :as fx] - [status-im.utils.handlers] - [status-im.utils.gfycat.core :as gfycat] - [status-im.utils.identicon :as identicon] [status-im.ui.components.colors :as colors] + [status-im.utils.fx :as fx] + [status-im.utils.gfycat.core :as gfycat] + status-im.utils.handlers + [status-im.utils.identicon :as identicon] [status-im.utils.theme :as theme])) (defn displayed-name @@ -101,15 +102,16 @@ (re-frame/reg-fx ::switch-theme - (fn [theme] - (colors/set-theme - (if (or (= 2 theme) (and (= 0 theme) (theme/is-dark-mode))) - :dark - :light)))) + (fn [theme-id] + (let [theme (if (or (= 2 theme-id) (and (= 0 theme-id) (theme/is-dark-mode))) + :dark + :light)] + (quo-theme/set-theme theme) + (colors/set-theme theme)))) (fx/defn switch-appearance {:events [:multiaccounts.ui/appearance-switched]} [cofx theme] (fx/merge cofx {::switch-theme theme} - (multiaccounts.update/multiaccount-update :appearance theme {}))) \ No newline at end of file + (multiaccounts.update/multiaccount-update :appearance theme {}))) diff --git a/src/status_im/ui/components/tabbar/core.cljs b/src/status_im/ui/components/tabbar/core.cljs index 4aa010c325..6d4bffda9f 100644 --- a/src/status_im/ui/components/tabbar/core.cljs +++ b/src/status_im/ui/components/tabbar/core.cljs @@ -1,16 +1,15 @@ (ns status-im.ui.components.tabbar.core - (:require - [status-im.ui.components.animation :as animation] - [status-im.ui.components.reanimated :as reanimated] - [status-im.ui.components.tabbar.styles :as tabs.styles] - [reagent.core :as reagent] - [oops.core :refer [oget]] - [status-im.ui.components.react :as react] - [status-im.utils.platform :as platform] - [status-im.ui.components.icons.vector-icons :as vector-icons] - [status-im.ui.components.badge :as badge] - [status-im.i18n :as i18n] - [re-frame.core :as re-frame])) + (:require [oops.core :refer [oget]] + [quo.gesture-handler :as gesture-handler] + [re-frame.core :as re-frame] + [reagent.core :as reagent] + [status-im.i18n :as i18n] + [status-im.ui.components.animation :as animation] + [status-im.ui.components.badge :as badge] + [status-im.ui.components.icons.vector-icons :as vector-icons] + [status-im.ui.components.react :as react] + [status-im.ui.components.tabbar.styles :as tabs.styles] + [status-im.utils.platform :as platform])) (defonce visible-native (animation/create-value 0)) (defonce last-to-value (atom 1)) @@ -69,7 +68,7 @@ accessibility-label count-subscription]}] (let [count (when count-subscription @(re-frame/subscribe [count-subscription]))] [react/view {:style tabs.styles/touchable-container} - [reanimated/touchable-without-feedback + [gesture-handler/touchable-without-feedback {:style {:height "100%" :width "100%"} :on-press on-press diff --git a/src/status_im/ui/screens/wallet/accounts/styles.cljs b/src/status_im/ui/screens/wallet/accounts/styles.cljs index c07af8eb71..0827151cd7 100644 --- a/src/status_im/ui/screens/wallet/accounts/styles.cljs +++ b/src/status_im/ui/screens/wallet/accounts/styles.cljs @@ -1,6 +1,6 @@ (ns status-im.ui.screens.wallet.accounts.styles - (:require [status-im.ui.components.colors :as colors] - [status-im.ui.components.reanimated :as reanimated] + (:require [quo.animated :as reanimated] + [status-im.ui.components.colors :as colors] [status-im.utils.platform :as platform])) (def ^:const tabbar-height 56) diff --git a/src/status_im/ui/screens/wallet/accounts/views.cljs b/src/status_im/ui/screens/wallet/accounts/views.cljs index e892d6c600..d2be859061 100644 --- a/src/status_im/ui/screens/wallet/accounts/views.cljs +++ b/src/status_im/ui/screens/wallet/accounts/views.cljs @@ -1,21 +1,21 @@ (ns status-im.ui.screens.wallet.accounts.views - (:require-macros [status-im.utils.views :as views]) - (:require [status-im.ui.components.react :as react] - [status-im.ui.components.icons.vector-icons :as icons] - [status-im.ui.components.toolbar.styles :as toolbar.styles] - [status-im.ui.components.colors :as colors] - [status-im.i18n :as i18n] - [status-im.ui.components.list.views :as list] - [status-im.ui.components.chat-icon.screen :as chat-icon] - [status-im.ui.components.list-item.views :as list-item] - [status-im.wallet.utils :as wallet.utils] - [reagent.core :as reagent] + (:require [oops.core :refer [oget]] + [quo.animated :as reanimated] [re-frame.core :as re-frame] - [status-im.ui.components.reanimated :as reanimated] - [oops.core :refer [oget]] + [reagent.core :as reagent] + [status-im.i18n :as i18n] + [status-im.ui.components.chat-icon.screen :as chat-icon] + [status-im.ui.components.colors :as colors] + [status-im.ui.components.icons.vector-icons :as icons] + [status-im.ui.components.list-item.views :as list-item] + [status-im.ui.components.list.views :as list] + [status-im.ui.components.react :as react] + [status-im.ui.components.toolbar.styles :as toolbar.styles] [status-im.ui.screens.wallet.accounts.sheets :as sheets] [status-im.ui.screens.wallet.accounts.styles :as styles] - [status-im.utils.utils :as utils.utils])) + [status-im.utils.utils :as utils.utils] + [status-im.wallet.utils :as wallet.utils]) + (:require-macros [status-im.utils.views :as views])) (def state (reagent/atom {:tab :assets}))